/* Minification failed. Returning unminified contents.
(61389,57-58): run-time error JS1195: Expected expression: >
(61391,71-72): run-time error JS1195: Expected expression: >
(61391,96-97): run-time error JS1004: Expected ';': )
(61409,17-18): run-time error JS1002: Syntax error: }
(61429,9-10): run-time error JS1002: Syntax error: }
(61431,65-66): run-time error JS1004: Expected ';': {
(61436,5-6): run-time error JS1002: Syntax error: }
(61437,28-29): run-time error JS1004: Expected ';': {
(61569,6-7): run-time error JS1195: Expected expression: ,
(61570,113-114): run-time error JS1004: Expected ';': {
(61996,6-7): run-time error JS1195: Expected expression: ,
(61997,36-37): run-time error JS1004: Expected ';': {
(62013,6-7): run-time error JS1195: Expected expression: ,
(62014,68-69): run-time error JS1004: Expected ';': {
(62021,6-7): run-time error JS1195: Expected expression: ,
(62022,75-76): run-time error JS1004: Expected ';': {
(62041,6-7): run-time error JS1195: Expected expression: ,
(62042,31-32): run-time error JS1195: Expected expression: )
(62042,33-34): run-time error JS1004: Expected ';': {
(62047,6-7): run-time error JS1195: Expected expression: ,
(62048,34-35): run-time error JS1004: Expected ';': {
(62108,6-7): run-time error JS1195: Expected expression: ,
(62109,34-35): run-time error JS1195: Expected expression: )
(62109,36-37): run-time error JS1004: Expected ';': {
(62183,6-7): run-time error JS1195: Expected expression: ,
(62184,34-35): run-time error JS1195: Expected expression: )
(62184,36-37): run-time error JS1004: Expected ';': {
(62235,1-2): run-time error JS1002: Syntax error: }
(84850,42-43): run-time error JS1195: Expected expression: >
(84860,3-4): run-time error JS1195: Expected expression: )
(84865,34-35): run-time error JS1195: Expected expression: >
(84893,2-3): run-time error JS1002: Syntax error: }
(84901,42-43): run-time error JS1195: Expected expression: >
(84903,5-6): run-time error JS1195: Expected expression: )
(84907,39-40): run-time error JS1195: Expected expression: >
(84911,4-5): run-time error JS1195: Expected expression: )
(84922,2-3): run-time error JS1002: Syntax error: }
(84923,2-5): run-time error JS1197: Too many errors. The file might not be a JavaScript file: var
(62053,7-19): run-time error JS1018: 'return' statement outside of function: return false
(61992,9,61995,10): run-time error JS1018: 'return' statement outside of function: return {
            Top: yy,
            Left: xx
        }
(61568,9-21): run-time error JS1018: 'return' statement outside of function: return false
(61554,17-29): run-time error JS1018: 'return' statement outside of function: return false
(61556,17-28): run-time error JS1018: 'return' statement outside of function: return true
(61442,13-25): run-time error JS1018: 'return' statement outside of function: return false
(61435,9-21): run-time error JS1018: 'return' statement outside of function: return false
 */
var defined = function (context) {
    return typeof context !== 'undefined' && context !== null;
}

var isDefinedAndNotEmpty = function (context) {
    if (defined(context)) {
        if (context != null && context != '') {
            return true;
        }
    }
    return false;
}

String.isNullOrEmpty = function (string) {
    if (string == null) {
        return true;
    }
    if (string.length == 0) {
        return true;
    }
}
String.prototype.toBoolean = function () {
    return this.toLocaleLowerCase() === "true" || this == "1";
}
Boolean.prototype.toBoolean = function () {
    return this.valueOf();
}

Number.prototype.toBoolean = function () {
    var val = this.toInteger();
    if (val > 0)
        return true;
    else
        return false;
}


String.prototype.toNumber = function () {
    var parsedInt = _.parseInt(this.replace(/[A-Za-z$-]/g, ""));
    if (_.isNumber(parsedInt) && !isNaN(parsedInt)) {
        return parsedInt;
    }
    return 0;
}
String.prototype.toInteger = function () {
    var parsedInt = _.parseInt(this);
    if (_.isNumber(parsedInt) && !isNaN(parsedInt)) {
        return parsedInt;
    }
    return 0;
}
String.prototype.toValidString = function () {
    if (isDefinedAndNotEmpty(this)) {
        return this;
    }
    else {
        return "";
    }
}

Number.prototype.toInteger = function () {
    if (!isNaN(this)) {
        return this.valueOf();
    }
    return 0;
}
var Guid = function () { };
Guid.new = (function () {
    function s4() {
        return Math.floor((1 + Math.random()) * 0x10000)
            .toString(16)
            .substring(1);
    }

    return function () {
        return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
            s4() + '-' + s4() + s4() + s4();
    };
})();
Guid.empty = function() {
    return '00000000-0000-0000-0000-000000000000';
};

String.prototype.removeWhitespaces = function () {
    return this.replace(/ /g, '');
}

String.prototype.replaceAll = function (str1, str2, ignore) {
    return this.replace(new RegExp(str1.replace(/([\,\!\\\^\$\{\}\[\]\(\)\.\*\+\?\|\<\>\-\&])/g, function (c) { return "\\" + c; }), "g" + (ignore ? "i" : "")), str2);
};

Date.prototype.getQuarter = function() {
    var month = this.getMonth() + 1;
    return (Math.ceil(month / 3));
}

Date.isLeapYear = function (year) {
    return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0));
};

Date.getDaysInMonth = function (year, month) {
    return [31, (Date.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
};

Date.prototype.isLeapYear = function () {
    return Date.isLeapYear(this.getFullYear());
};

Date.prototype.getDaysInMonth = function () {
    return Date.getDaysInMonth(this.getFullYear(), this.getMonth());
};

Date.prototype.addMonths = function (value) {
    var n = this.getDate();
    this.setDate(1);
    this.setMonth(this.getMonth() + value);
    this.setDate(Math.min(n, this.getDaysInMonth()));
    return this;
};;
var CALLBACKACTION_siteSettingsAccessibilityOnoff = "";

var callbackActions = {
    "siteSettingsAccessibilityOnoff": function (form, value) {
        if (defined(form) && defined(value)) {
            form.find("#site-accessibility-email").prop("disabled", !value.toBoolean());
            form.find("#site-accessibility-key").prop("disabled", !value.toBoolean());
        }
    },
}

;
var ANCHOR = "anchor";
var WRAPPER = "wrapper";
var COMPONENT_BAR = "component-bar";
var EDITOR = "editor";
var VIEWER = "viewer";
var CONTEXT_EDITOR = "contexteditor";
var CONTEXT_VIEWER = "contextviewer";
var SELECT_WRAPPER = "select-wrapper";
var SELECT_WRAPPER_MENU = "resizer-actions-menu";
var SELECT_WRAPPER_FIXED = "select-wrapper-fixed";
var HTML = "html";
var DOCK_WRAPPER = "dock-wrapper";
var VIEWER_CONTEXT = 0;
var EDITOR_CONTEXT = 1;
var SLAVE = '_slave';
var VIEWER_TEMPLATE = 0;
var EDITOR_TEMPLATE = 1;
var MAIN = "848cbb01-b945-4336-b149-03dcf135dae6";//"main";
var MAIN_COMPONENT = "main";
var PAGE = "f433edc4-3aae-4bfe-ab34-1c399ff86c30";
var PAGE_COMPONENT = "page";
var ACTIVE_PALETTE = 'active-palette';
var DISPLAY_PAGE = "page";
var SOUNDID = "29f8ca10-8ec9-481d-b7ae-2d7a16e41a9f";
var BACKGROUND_COLOR = "background-color";
var LOCATION = 'location';
var FIXED_LOCATION = 'fixed-location';
var BACKGROUND_COLOR_HOVER = "background-color-hover";
var IMAGE_SETTINGS_BGCOLOR = "image-section-bgcolor";
var IMAGE_SETTINGS_COLOR = "image-section-color";
var IMAGE_SETTING_OPTIONALCOLOR = "image-section-optionalcolor";
var IMAGE_SETTINGS_HEADERCOLOR = "image-section-headercolor";
var OPTIONAL = 'optional';
var PLACEHOLDER = 'placeholder';
var TEXT_COLOR_HOVER = "text-color-hover";
var BORDER_COLOR_HOVER = "border-color-hover";
var IMAGE_STRETCHING = "image-stretching";
var SET_IMAGE_LINK = "set-image-link";
var COLUMNS = 'columns';
var ROWS = 'rows';
var COLOR = "color";
var SECONDARY_COLOR = "secondary-color";
var IMAGE_RATIO = "image-ratio";
var TEXT_BOX_TYPE = 'text-box-type';
var GALLERY_ITEM_BOTTOM_MARGIN = 'gallery-item-bottom-margin';
var PANEL = "div";
var IMAGE = "img";
var IMAGE_PROPERTY = 'image';
var HOVER = "hover";
var IMAGE_SWAP = "image-swap";
var MORTGAGE_CALCULATOR = "mortgage-calculator";
var EVALUATE_HOME = "evaluate-home";
var BORDER_WIDTH = "border-width";
var BORDER_RADIUS = "border-radius";
var BORDER_COLOR = "border-color";
var MARGINS_WIDTH = "margins-width";
var STRETCH_TO_FULL_WIDTH = "stretch-to-full-width";
var WIDTH = "width";
var HEIGHT = "height";
var TOP = "top";
var LEFT = "left";
var OFFSET_X = 'offset-x';
var OFFSET_Y = 'offset-y';
var TYPE = 'type';
var EXPAND = 'expand';
var ORDER = 'order';
var MODE_VALUE = 'mode-value';
var MODE = 'mode';
var PREDEFINED = 'predefined';
var NAME = 'name';
var TITLE = 'title';
var CHANGE = "change";
var IS_PINED = 'is-pined';
var CLICK = "click";
var TEXT_ALIGN = "text-align";
var HOVER_STYLE = "hover-style";
var OPEN_LINK_ON_BLANK = "open-link-on-blank";
var FONT_SIZE = "font-size";
var FONT_FAMILY = "font-family";
var BUTTON = 'button';
var CONTACT_US = 'contact-us';
var PAUSE = 'pause';
var TEXT = 'text';
var INTERVAL = 'interval';
var LINE_HEIGHT = 'line-height';
var PARAGRAPH = 'paragraph';
var HEADERTEXT = 'headertext';
var HEADER = 'header';
var FOOTER = 'footer';
var BODY = 'body';
var PARAGRAPH_SEO = 'paragraph-seo';
var HEADERTEXT_SEO = 'headertext-seo';
var CONTENT = 'content';
var IMAGE_ON_HOVER = 'image-on-hover';
var IMAGE_ON_PRESSED = 'image-on-pressed';
var GALLERY = "gallery";
var SLIDESHOW = "slideshow";
var HOUSE_PHOTO_TOUR = "house-photo-tour";
var HOUSE_PHOTO_TOUR_ITEM = "house-photo-tour-item";
var HTML_CONTAINER = "html-container";
var MENU = "menu";
var VIDEO = "video";
var SOUND = "sound";
var AUTOPLAY = "autoplay";
var HIDE = "hide";
var PDF = "pdf";
var PARAGRAPH_FORMAT = "paragraph-format";
var META_TITLE = "meta-title";
var META_DESCRIPTION = "meta-description";
var META_KEYWORDS = "meta-keywords";
var SRC = "src";
var DESCRIPTION = 'description';
var PROVIDER = 'provider';
var LOOP = 'loop';
var REL = 'rel';
var ENABLEJSAPI = 'enablejsapi';
var API = 'api';
var NAV = 'nav';
var DOT = 'dot';
var CAPTION_POSITION = 'caption-position';
var SIGNIN = 'signin';
var SHOW_OPTIMIZED = "show-optimized-image";
var SHOW_OPTIMIZED_HOVER = "show-optimized-hover-image";
var SHOW_OPTIMIZED_PRESSED = "show-optimized-pressed-image";
var SHOW_OPTIMIZED_PLACEHOLDER = "show-optimized-placeholder";

var CLIENT_ID_SOUNDCLOUD_FOR_DEVELOPER = "8e80248ca2cadb2f6e90453edcb21ad6"; //account login wwwtmmm@gmail.com password as0123
var SOUNDCLOUD_RESOLVE_URL = "http://api.soundcloud.com/resolve.json?url=";
var LIST = "list";
var VERTICAL = 'vertical';
var GENERAL = 'general';
var LINES = 'lines';
var THUMBNAILS = 'thumbnails';
var SIMPLE = 'simple';
var BLANKIMAGE = "data:text/plain;base64,R0lGODdh+gD6AOMAACdQfv///117nsnT3niRruTp7pOnvkJljq69zgAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAA+gD6AAAE/hDISau9OOvNu/9gKI5kaZ5oqq5s675wLM90bd94ru987//AoHBILBqPyKRyyWw6n9CodEqtWq/YrHbL7Xq/4LB4TC6bz+i0es1uu9/wuHxOr9vv+Lx+z+/7/4CBgoOEhYaHiImKi4yNjo+QkZKTlJWWl5iZmpucnZ6foKGio6SlpqeoqaqrrK2ur7CxsrO0tba3uLm6u7y9vr/AwcLDxMXGx8jJysvMzc7P0NHS09TV1tfY2drb3N3e3+Dh4uPk5ebn6Onq6+zt7u/w8fLz9PX29/j5+vv8/f7/AAMKHEiwoLIACAVIQMgQBEOEQxgamFAAIiuGAxY+dLhRCEYJ/gcaajBg4AAohgQkELAIYmWAIi5VBijAAWHKTwxpAogZgieRhAAGBJi4weZJlDtZHhAaYIBJDD4ROhU6kUABpxSWziQ6QUDFAQJYChBagOsFqgAQPgUwduuEhyw3IaxIk+eBijnXVogKt+lHkHgRcg0JNyMAwgwRZFg5YKXOw3AVp4XrSbBgnghmHrg7FGpclAakhg3w9O7EzI9RH3B5U7VLvVkRZubqWq3Gm5UDECDLs6Jk1J5fvtWdVDjQCqGFA6hI1Ohy4pMVYiBLmiL0485zExjN9LZM5Xs/E+eZPbnIyTed94V+weXjyXDTs+9klKlF5z7DgzdKHrp5vMPJ/idffBkQZtZ6Ah6VEmLGQZcfBfnxdx90vhUXoHfofZBdhhZsyIlzmVlUIQDAtSdeSv0l6NOIzjGn4Xwudjjfh9AhJoFqnJkF4YkWclhhchPUBh2OAxiWwYZEGumhJpSRaBFneV3QJFIuZUTleiD1dRODmh3ZEWQPFfAUUwW8l0mThE2gVVOwDSfSlU3Bd5NVASDQnQRe1ZldW3W2SUGTeDKFwFprgpfNkgARJl1AAmwW4kDmCTbQASEWgJtBmGaq6aacdurpp6CGKuqopJZq6qmopqrqqqy26uqrsMYq66y01mrrrbjmquuuvPbq66/ABivssMQWa+yxyCar7LLMNDbr7LPQRivttNRWa+212Gar7bbcduvtt+CGK+645JZr7rnopqvuuuy26+678MYr77zdRAAAOw==";
var NO_IMAGE_YET = "../Content/image/no-image-yet.png";
var Z_INDEX = 'z-index';
var TEMPLATE_MASTER_URL = "/Template";
var REGULAR_INPUT_SIZE = "^[-+]?[0-9]*\.?[0-9]+(px|em|\%|$){1}$";
var REGULAR_INPUT_Z_INDEX = "^[-+]?[0-9]*\.?[0-9]+$";
var REGULAR_INPUT_LINK = "\\b(http|https)://[a-z0-9.-]+\\b";
var REGULAR_INPUT_LINK_VARIABLE = "REGULAR_INPUT_LINK";
var MIN_WIDTH_UL_MENU = "480px";
var MESSAGE_WAS_SENT = "The message was sent!";
var MIN_ELEMENT_HEIGHT = "20px";
var HEADER_PAGE_ID = "0ebb514d-53f2-48c4-8073-98e9fb93e1fa";
var FOOTER_PAGE_ID = "1992fecc-5276-4b6b-8bdd-46c8cff82f47";
var MENU_COMPONENT_ID = "88cbc4c2-bb93-45e9-a318-57218c7c0171";
var MEESSAGE_EMAIL_THANK_YOU_NAME_EMAIL = "Thank You! Your information has been sent to <CustomerEmail>  for review. You should hear back from them shortly. Make sure to add <RecipientEmail> to your contacts so responses are not swept into your spam folder";
var MEESSAGE_EMAIL_SUCCESS_NAME_EMAIL = "Success!  Your information has been sent to <CustomerEmail> for review. You should hear back from them shortly. Make sure to add <RecipientEmail> to your contacts so responses are not swept into your spam folder";
var MEESSAGE_EMAIL_SUCCESS_DOMANIAN_NAME = "Success! Your information has been sent to <DomainName> for review. You should hear back from them shortly. Make sure to add <RecipientEmail> to your contacts so responses are not swept into your spam folder";
var MESSAGE_WAS_NOT_SENT = "The message was not sent";
var SECCESS_TITLE_PAGE = "Success!";
var PHOTO_TOUR_TEXT_COLOR = "photo-tour-text-color";
var SECCESS_BUTTON_TEXT = "Ok";
var SECCESS_HEADER_COLOR = "#616161";
var SECCESS_BUTTON_COLOR = "#616161";
var SECCESS_CONTENT_COLOR = "#ffffff";
var SECCESS_TEXT_COLOR = "black";
var SECCESS_FONT_SIZE = "27px";
var SECCESS_FONT_FAMILY = "open sans";
var SUCCESS_LOGO_IMAGE = "/Images/logo-successfully-new.png";
var SUCCESS_PAGE_MESSAGE = "success-page-massage";
var SUCCESS_PAGE_TEXT_COLOR = "success-page-text-color";
var SUCCESS_PAGE_IMAGE_LOGO = "success-page-image-logo";
var SUCCESS_PAGE_CONTENT_COLOR = "success-page-content-color";
var SUCCESS_PAGE_TEXT_COLOR = "success-page-text-color";
var SUCCESS_PAGE_TEXT_FONT_SIZE = "success-page-text-font-size";
var SUCCESS_PAGE_TEXT_FONT_FAMILY = "success-page-text-font-family";
var SUCCESS_PAGE_LINK_REDIRECT_BUTTON = "success-page-link-redirect-button";
var SUCCESS_PAGE_HEADER_COLOR = "success-page-header-color";
var SUCCESS_PAGE_BUTTON_COLOR = "success-page-button-color";
var SUCCESS_PAGE_HEADER_TEXT = "success-page-header-text";
var SUCCESS_PAGE_BUTTON_TEXT = "success-page-button-text";
var SUCCESS_PAGE_MASTER_LINK = "success-page-master-link";
var DEFAULT_BG_COLOR_FORMS = "rgba(0, 0, 0, 0)";
var GROUP_WRAPPER = "group-wrapper";
var GROUP_WRAPPER_MENU = "group-actions-menu";
var GROUP_WRAPPER_FIXED = "group-wrapper-fixed";
var GROUP_WRAPPER_OUTER = "outer";
var GROUPID = 'group-id';
var GROUP = "group";
var COMPONENT = "component";
var SELECT_WRAPPER = 'select-wrapper';
var DESERIALIZE_STRATEGY_STANDARD = "Standard";
var DESERIALIZE_STRATEGY_COMPARE_CONTROLS = "CompareControls";
var DESERIALIZE_STRATEGIES = [DESERIALIZE_STRATEGY_STANDARD, DESERIALIZE_STRATEGY_COMPARE_CONTROLS];
var ACTION_TYPE_CREATE_WEB_SITE = 1;
var ACTION_TYPE_CREATE_TEMPLATE = 4;
var ACTION_TYPE_EDIT_WEBSITE = 2;
var ACTION_TYPE_EDIT_TEMPLATE = 3;
var ACTION_TYPE_CREATE_COMPONENT_TEMPLATE = 9;
var ACTION_TYPE_EDIT_COMPONENT_TEMPLATE = 10;
var ACTION_TYPE_CREATE_SECTION_TEMPLATE = 11;
var ACTION_TYPE_EDIT_SECTION_TEMPLATE = 12;

var GROUPS_SHELL = [];
var DESIGN_POPOVER_CUSTOM = "design-popover-custom";
var ADDING_CATEGORY_TO_PRODUCT_POPOVER_CUSTOM = "adding-category-to-product-popover-custom";
var BATCH_OPERATIONS_POPOVER_CUSTOM = "batch-operations-popover-custom";
var FORM = "form";
var LABEL = "_label";
var TEXTBOX = "_textbox";
var TEXTAREA = "_textarea";
var RADIOLIST = "_radio-list";
var SELECTLIST = "_select-list";
var CHECKBOX = "_checkbox";
var AUTOCOMPLETE_ADDRESS = "_autocomplete-address";
var SELECTEDLABEL = "label-value";
var SELECTEDLABELTYPE = "label-type";
var CAPTION_COMPONENTS_TO_LABEL = "caption-components-to-label";
var CAPTION = 'caption';
var LABELTYPENAME = "Name";
var SUBMIT = "_submit";
var ATTACHMENT = "_attachment";
var CANCEL = "_cancel";
var CAPTCHA = "_captcha";
var FRAME = "frame";
var UPCLICK_TYPE_PICTURE = 'Picture';
var UPCLICK_TYPE_CERTIFICATE = 'Certificate';
var UPCLICK_TYPE_SOUND = 'Sound';
var UPCLICK_TYPE_PDF = 'Pdf';
var JPLAYER_SUFFIX = '_jplayer';
var JPLAYER_CONTAINER_SUFFIX = '_jplayer_container';
var DUPLICATE = "duplicate";
var COPY = "copy";
var PASTE = "paste";
var ADDITIONAL_COMPONENTS = "additional-components";
var PDF_NOT_UPLOADED = "PDF file is not uploaded yet";
var MAIN_MENU = "main-menu";
var CKEDITOR_PAGE = 'ckeditorcmspage://';
var DEFAULT_PARAGRAPH_TEXT = "This is a TEXT object frame. You can type directly over text, or copy and paste text into this frame. Highlight text and you will see the EDITING palette. There you can affect changes to text and create links to other pages. (*see User Guide for more detailed instructions on use).";
var DEFAULT_HEADER_TEXT = "Heading";
var PREDEFINED_VIEW_SLIDESHOW = "<div class=\"item active fitwidth\"><a><img src=\"/Images/slideshow_predefined.png\"></a></div>";

var DEVICE_TYPE = 'deviceType';
var SWITCHER_DESKTOP = 'desktop';
var SWITCHER_MOBILE = 'mobile';
var GOOGLE_ANALYTICS = 'googleanalytics';
var GOOGLE_ANALYTICS_SCRIPT = 'googleanalytics-script';
var RULER_GUIDES = 'ruler-guides';
var GOOGLE_VERIFICATION_CODE = 'google-verification-code';
var BING_VERIFICATION_CODE = 'bing-verification-code';
var SEARCHENGINE = "searchengine";
var SITEMAPXML = "sitemapxml";
var ISPROTECTED = "isprotected";
var PROTECTEDWORD = "protected-word";
var PROTECTEDEMAIL = "protected-email";

var CALLBACK_ACTION = "callbackaction";

var SITE_SETTINGS_ACCESSIBILITY = "SiteSettingsAccessibility";
var PAGE_MANAGEMENT_SETTINGS = "page-management-settings";
var SITE_SETTINGS_TEMPLATE = "site-settings-template";
var SITE_AUTOSAVE_SETTINGS = "site-auto-save-settings";
var SITE_SETTINGS_HEADER_CONTENT = "header-content";
var PARENT_PAGE = 'parent-page';
var HOME = 'home';
var HIDE_FROM_MENU = 'hide-from-menu';
var SECURE = 'secure';
var HIDE_COMPONENT = 'hide-component';
var ALT = 'alt';
var ONLY_DESKTOP = 'only-desktop';
var DEVICE_DESKTOP_TYPE = 'desktop';
var DEVICE_MOBILE_TYPE = 'mobile';
var STD_COMPONENT_FIXED = 'std-component-fixed';

var DOC_READY_STATE_COMPLETE = 'complete';
var FORM_SUBJECT = 'form-subject';
var FORM_SPACE_AFTER_ITEM = 'form-space-after-item';
var STORE = 'store';
var STORE_CART = 'store-cart';
var STORE_CART_LINK = 'store-cart-link';
var STORE_CART_CHECKOUT = 'store-cart-checkout';
var STORE_THANK_YOU = 'store-thank-you';
var STORE_PRODUCT = 'store-product';
var STORE_GALLERY = 'store-gallery';
var MANAGE_STORE_PRODUCTS = 'manage-store-products';
var MANAGE_STORE_PRODUCTS_ADD_CATEGORY = '#manage-store-products-category';
var MANAGE_STORE_PRODUCTS_ADDING_PRODUCTS_TO_CATEGORY = '#manage-store-products-adding-products-to-category';
var MANAGE_STORE_PRODUCTS_ADDING_CATEGORIES_TO_PRODUCTS = '#manage-store-products-adding-category-to-products';
var MANAGE_STORE_PRODUCTS_ADDING_DISCOUNT_TO_PRODUCTS = '#manage-store-products-adding-discount-to-products';
var MANAGE_STORE_PRODUCTS_CHANGE_PRODUCTS_VISABILITY = '#manage-store-products-change-products-visability';

var TEXT_ORDER_NUMBER = 'text-order-number';
var TEXT_TOTAL_COST = 'text-total-cost';
var TEXT_SHIPPING_TO = 'text-shipping-to';
var PAYPAL_EMAIL = 'paypal-email';
var ISSERVICE = 'isservice';
var DETAILS = 'details';
var SKU = 'sku';
var SOCIAL = 'social';
var PRICE = 'price';
var QUANTITY = 'quantity';
var ADD_TO_CART = 'add-to-cart';
var LAYOUT = 'layout';
var TITLE_COLOR = 'title-color';
var TITLE_FONT_FAMILY = 'title-font-family';
var TEXT_COLOR = 'text-color';
var TEXT_FONT_FAMILY = 'text-font-family';
var DIVIDER_COLOR = 'divider-color';
var DIVIDER_WIDTH = 'divider-width';
var DIVIDER = 'divider';
var SHARE_TO_FACEBOOK = 'share-to-facebook';
var SHARE_TO_TWITTER = 'share-to-twitter';
var SHARE_TO_GPLUS = 'share-to-gplus';

var STORE_PRODUCT_PRICE = 'store-product-price';
var STORE_PRODUCT_SKU = 'store-product-sku';
var STORE_PRODUCT_QUANTITY = 'store-product-quantity';
var STORE_PRODUCT_ADD_TO_CART = 'store-product-add-to-cart';
var STORE_PRODUCT_SOCIAL = 'store-product-social';
var STORE_PRODUCT_TITLE = 'store-product-title';
var STORE_PRODUCT_DESCRIPTION = 'store-product-description';
var STORE_PRODUCT_IMAGES = 'store-product-images';
var STORE_PRODUCT_OPTIONS = 'store-product-options';

var STORE_GALLERY_PRODUCT = 'store-gallery-product';
var STORE_GALLERY_PRODUCT_DESCRIPTION = 'store-gallery-product-description';
var STORE_GALLERY_PRODUCT_IMAGE = 'store-gallery-product-image';
var STORE_GALLERY_PRODUCT_LABEL = 'store-gallery-product-label';
var STORE_GALLERY_PRODUCT_PRICE = 'store-gallery-product-price';
var STORE_GALLERY_PRODUCT_TITLE = 'store-gallery-product-title';
var STORE_GALLERY_SHOW_MORE = 'store-gallery-show-more';

var STORE_CATEGORIES = 'store-categories';

var ACTION_ADD_TO_FORM = 'add-component-to-form';
var ACTION_REMOVE_FROM_FORM = 'remove-component-from-form';
var ACTION_SIGN_IN = 'sign-in';
var ACTION_SIGN_OUT = 'sign-out';
var ACTION_EDITOR_OPEN = 'editor-open';
var ACTION_EDITOR_CLOSED = 'editor-closed';
var PAGE_SECURE_PROPERTY = 'secure';
var RETURN_POLICY_URL = 'return-policy-url';
var SHIPPING_POLICY_URL = 'shipping-policy-url';
var CURRENCY = 'currency';
var WEIGHT_UNIT = 'weight-unit';
var SITE_COLORS = 'site-colors';

var CUSTOM_USER_FIELDS = 'custom-user-fields';
var STORE_PRODUCT_DEFAULT_IMAGE = '//testwebsitecreator.blob.core.windows.net/ecommerce-image/RWSemptyproduct.svg';
var SIGNIN_USER_DEFAULT_IMAGE = '//testwebsitecreator.blob.core.windows.net/ecommerce-image/bust1.png';
var STORE_SOCIAL_FACEBOOK_IMAGE = '//testwebsitecreator.blob.core.windows.net/ecommerce-image/fb.png';
var STORE_SOCIAL_TWITTER_IMAGE = '//testwebsitecreator.blob.core.windows.net/ecommerce-image/twitter.png';
var STORE_SOCIAL_GPLUS_IMAGE = '//testwebsitecreator.blob.core.windows.net/ecommerce-image/g.png';
var ENABLE_LOGGING = false;

var RECAPTCHA_SITE_KEY = '6LfL3V8UAAAAABtWhgn1QoBaQCxmCJRSUKtSf9mk';
var USE_CAPTCHA = 'use-captcha';

var REQUIRED_FIELD = 'required-field';
var AUTO_SAVE_PERIOD = 'auto-save-period';
var REGEXP_CHECK_HTML_SCRIPT_AND_IFRAME_TAG = '<\s*(script|iframe)[^>]*>';

var INDEXEDDB_BASENAME = 'uwcBase';
var INDEXEDDB_STORE_TEMPLATE = 'templateValues';

var BLOGGING = 'blogging';
var META_DATE = 'meta-date';
var IS_IMAGE_PRIMARY = 'is-image-primary';
var BLOGGING_EMPTY_IMAGE_URL = '../Content/image/no-image-yet.png';

var MAP = 'map';
var LATITUDE = 'latitude';
var LONGITUDE = 'longitude';
var SHOW_MAP_TYPE = 'show-map-type';
var SHOW_ZOOM = 'show-zoom';
var MAP_INTERACTIVE = 'map-interactive';
var SHOW_STREET_VIEW = 'show-street-view';

var MANAGE_MAILS_COMPONENT = '#form-manage-mails';
var MANAGE_MAILS_CURRENT_COMPONENT = '#form-manage-mails-current';
var MANAGE_MAILS_GRID_URL = '/Account/ControlFormMailsListGrid';
var MAIL_HISTORY_PROPERTY = 'mail-history';
var LOGS_PROPERTY = 'logs';

var componentType = {
    common: 1,
    templateable: 2,
    template: 3,
    section: 4,
    sectionChildren: 5
};
/*
Simple Javascript undo and redo.
https://github.com/ArthurClemens/Javascript-Undo-Manager
change ashovkoviy
add addSpecificActionToEnd
add getLatestPropertyId
add getLatestPropertyId
*/
var UndoManager = function () {
    "use strict";

    var commands = [],
        index = -1,
        isExecuting = false,
        callback,
        
        // functions
        execute;

    execute = function(command, action) {
        if (!command || typeof command[action] !== "function") {
            return this;
        }
        isExecuting = true;

        command[action]();

        isExecuting = false;
        return this;
    };

    return {

        /*
        Add specific action to the end of queue.
        */
        addSpecificActionToEnd: function (specificCommand, action) {
            if (isExecuting) {
                return this;
            }
            
            var command = commands[index];
            command[action] = specificCommand[action];
        },

        /*
        Add a command to the queue.
        */
        add: function (command) {
            if (isExecuting) {
                return this;
            }
            // if we are here after having called undo,
            // invalidate items higher on the stack
            commands.splice(index + 1, commands.length - index);

            commands.push(command);

            // set the current index to the end
            index = commands.length - 1;
            if (callback) {
                callback();
            }
            return this;
        },
        /*
        Pass a function to be called on undo and redo actions.
        */
        setCallback: function (callbackFunc) {
            callback = callbackFunc;
        },

        /*
        Perform undo: call the undo function at the current index and decrease the index by 1.
        */
        undo: function () {
            var command = commands[index];
            if (!command) {
                return this;
            }
            //if undo execute for last element we switch to 
            if (index == commands.length - 1 && commands.length > 0) {
                //index -= 1;
            }
            execute(command, "undo");
            index -= 1;
            if (callback) {
                callback();
            }
            return this;
        },

        /*
        Perform redo: call the redo function at the next index and increase the index by 1.
        */
        redo: function () {
            var command = commands[index + 1];
            if (!command) {
                return this;
            }
            execute(command, "redo");
            index += 1;
            if (callback) {
                callback();
            }
            return this;
        },

        /*
        Clears the memory, losing all stored states. Reset the index.
        */
        clear: function () {
            var prev_size = commands.length;

            commands = [];
            index = -1;

            if (callback && (prev_size > 0)) {
                callback();
            }
        },

        hasUndo: function () {
            return index !== -1;
        },

        hasRedo: function () {
            return index < (commands.length - 1);
        },

        getCommands: function () {
            return commands;
        },

        getLatestProperty: function () {
            if (index !== -1 && defined(commands[index].property)) {
                return commands[index].property;
            }
            return null;
        },
        
        getLatestComponentId: function () {
            if (index !== -1 && defined(commands[index].componentId)) {
                return commands[index].componentId;
            }
            return null;
        },

        getLatestCommand: function () {
        if (index !== -1) {
            return commands[index];
        }
        return null;
        }

    };
};

/*
LICENSE

The MIT License

Copyright (c) 2010-2014 Arthur Clemens, arthurclemens@gmail.com

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: 

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/;
/*!
 * jQuery JavaScript Library v2.1.1
 * http://jquery.com/
 *
 * Includes Sizzle.js
 * http://sizzlejs.com/
 *
 * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors
 * Released under the MIT license
 * http://jquery.org/license
 *
 * Date: 2014-05-01T17:11Z
 */

(function( global, factory ) {

	if ( typeof module === "object" && typeof module.exports === "object" ) {
		// For CommonJS and CommonJS-like environments where a proper window is present,
		// execute the factory and get jQuery
		// For environments that do not inherently posses a window with a document
		// (such as Node.js), expose a jQuery-making factory as module.exports
		// This accentuates the need for the creation of a real window
		// e.g. var jQuery = require("jquery")(window);
		// See ticket #14549 for more info
		module.exports = global.document ?
			factory( global, true ) :
			function( w ) {
				if ( !w.document ) {
					throw new Error( "jQuery requires a window with a document" );
				}
				return factory( w );
			};
	} else {
		factory( global );
	}

// Pass this if window is not defined yet
}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {

// Can't do this because several apps including ASP.NET trace
// the stack via arguments.caller.callee and Firefox dies if
// you try to trace through "use strict" call chains. (#13335)
// Support: Firefox 18+
//

var arr = [];

var slice = arr.slice;

var concat = arr.concat;

var push = arr.push;

var indexOf = arr.indexOf;

var class2type = {};

var toString = class2type.toString;

var hasOwn = class2type.hasOwnProperty;

var support = {};



var
	// Use the correct document accordingly with window argument (sandbox)
	document = window.document,

	version = "2.1.1",

	// Define a local copy of jQuery
	jQuery = function( selector, context ) {
		// The jQuery object is actually just the init constructor 'enhanced'
		// Need init if jQuery is called (just allow error to be thrown if not included)
		return new jQuery.fn.init( selector, context );
	},

	// Support: Android<4.1
	// Make sure we trim BOM and NBSP
	rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,

	// Matches dashed string for camelizing
	rmsPrefix = /^-ms-/,
	rdashAlpha = /-([\da-z])/gi,

	// Used by jQuery.camelCase as callback to replace()
	fcamelCase = function( all, letter ) {
		return letter.toUpperCase();
	};

jQuery.fn = jQuery.prototype = {
	// The current version of jQuery being used
	jquery: version,

	constructor: jQuery,

	// Start with an empty selector
	selector: "",

	// The default length of a jQuery object is 0
	length: 0,

	toArray: function() {
		return slice.call( this );
	},

	// Get the Nth element in the matched element set OR
	// Get the whole matched element set as a clean array
	get: function( num ) {
		return num != null ?

			// Return just the one element from the set
			( num < 0 ? this[ num + this.length ] : this[ num ] ) :

			// Return all the elements in a clean array
			slice.call( this );
	},

	// Take an array of elements and push it onto the stack
	// (returning the new matched element set)
	pushStack: function( elems ) {

		// Build a new jQuery matched element set
		var ret = jQuery.merge( this.constructor(), elems );

		// Add the old object onto the stack (as a reference)
		ret.prevObject = this;
		ret.context = this.context;

		// Return the newly-formed element set
		return ret;
	},

	// Execute a callback for every element in the matched set.
	// (You can seed the arguments with an array of args, but this is
	// only used internally.)
	each: function( callback, args ) {
		return jQuery.each( this, callback, args );
	},

	map: function( callback ) {
		return this.pushStack( jQuery.map(this, function( elem, i ) {
			return callback.call( elem, i, elem );
		}));
	},

	slice: function() {
		return this.pushStack( slice.apply( this, arguments ) );
	},

	first: function() {
		return this.eq( 0 );
	},

	last: function() {
		return this.eq( -1 );
	},

	eq: function( i ) {
		var len = this.length,
			j = +i + ( i < 0 ? len : 0 );
		return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
	},

	end: function() {
		return this.prevObject || this.constructor(null);
	},

	// For internal use only.
	// Behaves like an Array's method, not like a jQuery method.
	push: push,
	sort: arr.sort,
	splice: arr.splice
};

jQuery.extend = jQuery.fn.extend = function() {
	var options, name, src, copy, copyIsArray, clone,
		target = arguments[0] || {},
		i = 1,
		length = arguments.length,
		deep = false;

	// Handle a deep copy situation
	if ( typeof target === "boolean" ) {
		deep = target;

		// skip the boolean and the target
		target = arguments[ i ] || {};
		i++;
	}

	// Handle case when target is a string or something (possible in deep copy)
	if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
		target = {};
	}

	// extend jQuery itself if only one argument is passed
	if ( i === length ) {
		target = this;
		i--;
	}

	for ( ; i < length; i++ ) {
		// Only deal with non-null/undefined values
		if ( (options = arguments[ i ]) != null ) {
			// Extend the base object
			for ( name in options ) {
				src = target[ name ];
				copy = options[ name ];

				// Prevent never-ending loop
				if ( target === copy ) {
					continue;
				}

				// Recurse if we're merging plain objects or arrays
				if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
					if ( copyIsArray ) {
						copyIsArray = false;
						clone = src && jQuery.isArray(src) ? src : [];

					} else {
						clone = src && jQuery.isPlainObject(src) ? src : {};
					}

					// Never move original objects, clone them
					target[ name ] = jQuery.extend( deep, clone, copy );

				// Don't bring in undefined values
				} else if ( copy !== undefined ) {
					target[ name ] = copy;
				}
			}
		}
	}

	// Return the modified object
	return target;
};

jQuery.extend({
	// Unique for each copy of jQuery on the page
	expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),

	// Assume jQuery is ready without the ready module
	isReady: true,

	error: function( msg ) {
		throw new Error( msg );
	},

	noop: function() {},

	// See test/unit/core.js for details concerning isFunction.
	// Since version 1.3, DOM methods and functions like alert
	// aren't supported. They return false on IE (#2968).
	isFunction: function( obj ) {
		return jQuery.type(obj) === "function";
	},

	isArray: Array.isArray,

	isWindow: function( obj ) {
		return obj != null && obj === obj.window;
	},

	isNumeric: function( obj ) {
		// parseFloat NaNs numeric-cast false positives (null|true|false|"")
		// ...but misinterprets leading-number strings, particularly hex literals ("0x...")
		// subtraction forces infinities to NaN
		return !jQuery.isArray( obj ) && obj - parseFloat( obj ) >= 0;
	},

	isPlainObject: function( obj ) {
		// Not plain objects:
		// - Any object or value whose internal [[Class]] property is not "[object Object]"
		// - DOM nodes
		// - window
		if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
			return false;
		}

		if ( obj.constructor &&
				!hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) {
			return false;
		}

		// If the function hasn't returned already, we're confident that
		// |obj| is a plain object, created by {} or constructed with new Object
		return true;
	},

	isEmptyObject: function( obj ) {
		var name;
		for ( name in obj ) {
			return false;
		}
		return true;
	},

	type: function( obj ) {
		if ( obj == null ) {
			return obj + "";
		}
		// Support: Android < 4.0, iOS < 6 (functionish RegExp)
		return typeof obj === "object" || typeof obj === "function" ?
			class2type[ toString.call(obj) ] || "object" :
			typeof obj;
	},

	// Evaluates a script in a global context
	globalEval: function( code ) {
		var script,
			indirect = eval;

		code = jQuery.trim( code );

		if ( code ) {
			// If the code includes a valid, prologue position
			// strict mode pragma, execute code by injecting a
			// script tag into the document.
			if ( code.indexOf("use strict") === 1 ) {
				script = document.createElement("script");
				script.text = code;
				document.head.appendChild( script ).parentNode.removeChild( script );
			} else {
			// Otherwise, avoid the DOM node creation, insertion
			// and removal by using an indirect global eval
				indirect( code );
			}
		}
	},

	// Convert dashed to camelCase; used by the css and data modules
	// Microsoft forgot to hump their vendor prefix (#9572)
	camelCase: function( string ) {
		return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
	},

	nodeName: function( elem, name ) {
		return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
	},

	// args is for internal usage only
	each: function( obj, callback, args ) {
		var value,
			i = 0,
			length = obj.length,
			isArray = isArraylike( obj );

		if ( args ) {
			if ( isArray ) {
				for ( ; i < length; i++ ) {
					value = callback.apply( obj[ i ], args );

					if ( value === false ) {
						break;
					}
				}
			} else {
				for ( i in obj ) {
					value = callback.apply( obj[ i ], args );

					if ( value === false ) {
						break;
					}
				}
			}

		// A special, fast, case for the most common use of each
		} else {
			if ( isArray ) {
				for ( ; i < length; i++ ) {
					value = callback.call( obj[ i ], i, obj[ i ] );

					if ( value === false ) {
						break;
					}
				}
			} else {
				for ( i in obj ) {
					value = callback.call( obj[ i ], i, obj[ i ] );

					if ( value === false ) {
						break;
					}
				}
			}
		}

		return obj;
	},

	// Support: Android<4.1
	trim: function( text ) {
		return text == null ?
			"" :
			( text + "" ).replace( rtrim, "" );
	},

	// results is for internal usage only
	makeArray: function( arr, results ) {
		var ret = results || [];

		if ( arr != null ) {
			if ( isArraylike( Object(arr) ) ) {
				jQuery.merge( ret,
					typeof arr === "string" ?
					[ arr ] : arr
				);
			} else {
				push.call( ret, arr );
			}
		}

		return ret;
	},

	inArray: function( elem, arr, i ) {
		return arr == null ? -1 : indexOf.call( arr, elem, i );
	},

	merge: function( first, second ) {
		var len = +second.length,
			j = 0,
			i = first.length;

		for ( ; j < len; j++ ) {
			first[ i++ ] = second[ j ];
		}

		first.length = i;

		return first;
	},

	grep: function( elems, callback, invert ) {
		var callbackInverse,
			matches = [],
			i = 0,
			length = elems.length,
			callbackExpect = !invert;

		// Go through the array, only saving the items
		// that pass the validator function
		for ( ; i < length; i++ ) {
			callbackInverse = !callback( elems[ i ], i );
			if ( callbackInverse !== callbackExpect ) {
				matches.push( elems[ i ] );
			}
		}

		return matches;
	},

	// arg is for internal usage only
	map: function( elems, callback, arg ) {
		var value,
			i = 0,
			length = elems.length,
			isArray = isArraylike( elems ),
			ret = [];

		// Go through the array, translating each of the items to their new values
		if ( isArray ) {
			for ( ; i < length; i++ ) {
				value = callback( elems[ i ], i, arg );

				if ( value != null ) {
					ret.push( value );
				}
			}

		// Go through every key on the object,
		} else {
			for ( i in elems ) {
				value = callback( elems[ i ], i, arg );

				if ( value != null ) {
					ret.push( value );
				}
			}
		}

		// Flatten any nested arrays
		return concat.apply( [], ret );
	},

	// A global GUID counter for objects
	guid: 1,

	// Bind a function to a context, optionally partially applying any
	// arguments.
	proxy: function( fn, context ) {
		var tmp, args, proxy;

		if ( typeof context === "string" ) {
			tmp = fn[ context ];
			context = fn;
			fn = tmp;
		}

		// Quick check to determine if target is callable, in the spec
		// this throws a TypeError, but we will just return undefined.
		if ( !jQuery.isFunction( fn ) ) {
			return undefined;
		}

		// Simulated bind
		args = slice.call( arguments, 2 );
		proxy = function() {
			return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
		};

		// Set the guid of unique handler to the same of original handler, so it can be removed
		proxy.guid = fn.guid = fn.guid || jQuery.guid++;

		return proxy;
	},

	now: Date.now,

	// jQuery.support is not used in Core but other projects attach their
	// properties to it so it needs to exist.
	support: support
});

// Populate the class2type map
jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
	class2type[ "[object " + name + "]" ] = name.toLowerCase();
});

function isArraylike( obj ) {
	var length = obj.length,
		type = jQuery.type( obj );

	if ( type === "function" || jQuery.isWindow( obj ) ) {
		return false;
	}

	if ( obj.nodeType === 1 && length ) {
		return true;
	}

	return type === "array" || length === 0 ||
		typeof length === "number" && length > 0 && ( length - 1 ) in obj;
}
var Sizzle =
/*!
 * Sizzle CSS Selector Engine v1.10.19
 * http://sizzlejs.com/
 *
 * Copyright 2013 jQuery Foundation, Inc. and other contributors
 * Released under the MIT license
 * http://jquery.org/license
 *
 * Date: 2014-04-18
 */
(function( window ) {

var i,
	support,
	Expr,
	getText,
	isXML,
	tokenize,
	compile,
	select,
	outermostContext,
	sortInput,
	hasDuplicate,

	// Local document vars
	setDocument,
	document,
	docElem,
	documentIsHTML,
	rbuggyQSA,
	rbuggyMatches,
	matches,
	contains,

	// Instance-specific data
	expando = "sizzle" + -(new Date()),
	preferredDoc = window.document,
	dirruns = 0,
	done = 0,
	classCache = createCache(),
	tokenCache = createCache(),
	compilerCache = createCache(),
	sortOrder = function( a, b ) {
		if ( a === b ) {
			hasDuplicate = true;
		}
		return 0;
	},

	// General-purpose constants
	strundefined = typeof undefined,
	MAX_NEGATIVE = 1 << 31,

	// Instance methods
	hasOwn = ({}).hasOwnProperty,
	arr = [],
	pop = arr.pop,
	push_native = arr.push,
	push = arr.push,
	slice = arr.slice,
	// Use a stripped-down indexOf if we can't use a native one
	indexOf = arr.indexOf || function( elem ) {
		var i = 0,
			len = this.length;
		for ( ; i < len; i++ ) {
			if ( this[i] === elem ) {
				return i;
			}
		}
		return -1;
	},

	booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",

	// Regular expressions

	// Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
	whitespace = "[\\x20\\t\\r\\n\\f]",
	// http://www.w3.org/TR/css3-syntax/#characters
	characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",

	// Loosely modeled on CSS identifier characters
	// An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors
	// Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
	identifier = characterEncoding.replace( "w", "w#" ),

	// Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
	attributes = "\\[" + whitespace + "*(" + characterEncoding + ")(?:" + whitespace +
		// Operator (capture 2)
		"*([*^$|!~]?=)" + whitespace +
		// "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
		"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace +
		"*\\]",

	pseudos = ":(" + characterEncoding + ")(?:\\((" +
		// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
		// 1. quoted (capture 3; capture 4 or capture 5)
		"('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
		// 2. simple (capture 6)
		"((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
		// 3. anything else (capture 2)
		".*" +
		")\\)|)",

	// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
	rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),

	rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
	rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),

	rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ),

	rpseudo = new RegExp( pseudos ),
	ridentifier = new RegExp( "^" + identifier + "$" ),

	matchExpr = {
		"ID": new RegExp( "^#(" + characterEncoding + ")" ),
		"CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
		"TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
		"ATTR": new RegExp( "^" + attributes ),
		"PSEUDO": new RegExp( "^" + pseudos ),
		"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
			"*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
			"*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
		"bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
		// For use in libraries implementing .is()
		// We use this for POS matching in `select`
		"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
			whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
	},

	rinputs = /^(?:input|select|textarea|button)$/i,
	rheader = /^h\d$/i,

	rnative = /^[^{]+\{\s*\[native \w/,

	// Easily-parseable/retrievable ID or TAG or CLASS selectors
	rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,

	rsibling = /[+~]/,
	rescape = /'|\\/g,

	// CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
	runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
	funescape = function( _, escaped, escapedWhitespace ) {
		var high = "0x" + escaped - 0x10000;
		// NaN means non-codepoint
		// Support: Firefox<24
		// Workaround erroneous numeric interpretation of +"0x"
		return high !== high || escapedWhitespace ?
			escaped :
			high < 0 ?
				// BMP codepoint
				String.fromCharCode( high + 0x10000 ) :
				// Supplemental Plane codepoint (surrogate pair)
				String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
	};

// Optimize for push.apply( _, NodeList )
try {
	push.apply(
		(arr = slice.call( preferredDoc.childNodes )),
		preferredDoc.childNodes
	);
	// Support: Android<4.0
	// Detect silently failing push.apply
	arr[ preferredDoc.childNodes.length ].nodeType;
} catch ( e ) {
	push = { apply: arr.length ?

		// Leverage slice if possible
		function( target, els ) {
			push_native.apply( target, slice.call(els) );
		} :

		// Support: IE<9
		// Otherwise append directly
		function( target, els ) {
			var j = target.length,
				i = 0;
			// Can't trust NodeList.length
			while ( (target[j++] = els[i++]) ) {}
			target.length = j - 1;
		}
	};
}

function Sizzle( selector, context, results, seed ) {
	var match, elem, m, nodeType,
		// QSA vars
		i, groups, old, nid, newContext, newSelector;

	if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
		setDocument( context );
	}

	context = context || document;
	results = results || [];

	if ( !selector || typeof selector !== "string" ) {
		return results;
	}

	if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {
		return [];
	}

	if ( documentIsHTML && !seed ) {

		// Shortcuts
		if ( (match = rquickExpr.exec( selector )) ) {
			// Speed-up: Sizzle("#ID")
			if ( (m = match[1]) ) {
				if ( nodeType === 9 ) {
					elem = context.getElementById( m );
					// Check parentNode to catch when Blackberry 4.6 returns
					// nodes that are no longer in the document (jQuery #6963)
					if ( elem && elem.parentNode ) {
						// Handle the case where IE, Opera, and Webkit return items
						// by name instead of ID
						if ( elem.id === m ) {
							results.push( elem );
							return results;
						}
					} else {
						return results;
					}
				} else {
					// Context is not a document
					if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
						contains( context, elem ) && elem.id === m ) {
						results.push( elem );
						return results;
					}
				}

			// Speed-up: Sizzle("TAG")
			} else if ( match[2] ) {
				push.apply( results, context.getElementsByTagName( selector ) );
				return results;

			// Speed-up: Sizzle(".CLASS")
			} else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) {
				push.apply( results, context.getElementsByClassName( m ) );
				return results;
			}
		}

		// QSA path
		if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
			nid = old = expando;
			newContext = context;
			newSelector = nodeType === 9 && selector;

			// qSA works strangely on Element-rooted queries
			// We can work around this by specifying an extra ID on the root
			// and working up from there (Thanks to Andrew Dupont for the technique)
			// IE 8 doesn't work on object elements
			if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
				groups = tokenize( selector );

				if ( (old = context.getAttribute("id")) ) {
					nid = old.replace( rescape, "\\$&" );
				} else {
					context.setAttribute( "id", nid );
				}
				nid = "[id='" + nid + "'] ";

				i = groups.length;
				while ( i-- ) {
					groups[i] = nid + toSelector( groups[i] );
				}
				newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context;
				newSelector = groups.join(",");
			}

			if ( newSelector ) {
				try {
					push.apply( results,
						newContext.querySelectorAll( newSelector )
					);
					return results;
				} catch(qsaError) {
				} finally {
					if ( !old ) {
						context.removeAttribute("id");
					}
				}
			}
		}
	}

	// All others
	return select( selector.replace( rtrim, "$1" ), context, results, seed );
}

/**
 * Create key-value caches of limited size
 * @returns {Function(string, Object)} Returns the Object data after storing it on itself with
 *	property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
 *	deleting the oldest entry
 */
function createCache() {
	var keys = [];

	function cache( key, value ) {
		// Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
		if ( keys.push( key + " " ) > Expr.cacheLength ) {
			// Only keep the most recent entries
			delete cache[ keys.shift() ];
		}
		return (cache[ key + " " ] = value);
	}
	return cache;
}

/**
 * Mark a function for special use by Sizzle
 * @param {Function} fn The function to mark
 */
function markFunction( fn ) {
	fn[ expando ] = true;
	return fn;
}

/**
 * Support testing using an element
 * @param {Function} fn Passed the created div and expects a boolean result
 */
function assert( fn ) {
	var div = document.createElement("div");

	try {
		return !!fn( div );
	} catch (e) {
		return false;
	} finally {
		// Remove from its parent by default
		if ( div.parentNode ) {
			div.parentNode.removeChild( div );
		}
		// release memory in IE
		div = null;
	}
}

/**
 * Adds the same handler for all of the specified attrs
 * @param {String} attrs Pipe-separated list of attributes
 * @param {Function} handler The method that will be applied
 */
function addHandle( attrs, handler ) {
	var arr = attrs.split("|"),
		i = attrs.length;

	while ( i-- ) {
		Expr.attrHandle[ arr[i] ] = handler;
	}
}

/**
 * Checks document order of two siblings
 * @param {Element} a
 * @param {Element} b
 * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
 */
function siblingCheck( a, b ) {
	var cur = b && a,
		diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
			( ~b.sourceIndex || MAX_NEGATIVE ) -
			( ~a.sourceIndex || MAX_NEGATIVE );

	// Use IE sourceIndex if available on both nodes
	if ( diff ) {
		return diff;
	}

	// Check if b follows a
	if ( cur ) {
		while ( (cur = cur.nextSibling) ) {
			if ( cur === b ) {
				return -1;
			}
		}
	}

	return a ? 1 : -1;
}

/**
 * Returns a function to use in pseudos for input types
 * @param {String} type
 */
function createInputPseudo( type ) {
	return function( elem ) {
		var name = elem.nodeName.toLowerCase();
		return name === "input" && elem.type === type;
	};
}

/**
 * Returns a function to use in pseudos for buttons
 * @param {String} type
 */
function createButtonPseudo( type ) {
	return function( elem ) {
		var name = elem.nodeName.toLowerCase();
		return (name === "input" || name === "button") && elem.type === type;
	};
}

/**
 * Returns a function to use in pseudos for positionals
 * @param {Function} fn
 */
function createPositionalPseudo( fn ) {
	return markFunction(function( argument ) {
		argument = +argument;
		return markFunction(function( seed, matches ) {
			var j,
				matchIndexes = fn( [], seed.length, argument ),
				i = matchIndexes.length;

			// Match elements found at the specified indexes
			while ( i-- ) {
				if ( seed[ (j = matchIndexes[i]) ] ) {
					seed[j] = !(matches[j] = seed[j]);
				}
			}
		});
	});
}

/**
 * Checks a node for validity as a Sizzle context
 * @param {Element|Object=} context
 * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
 */
function testContext( context ) {
	return context && typeof context.getElementsByTagName !== strundefined && context;
}

// Expose support vars for convenience
support = Sizzle.support = {};

/**
 * Detects XML nodes
 * @param {Element|Object} elem An element or a document
 * @returns {Boolean} True iff elem is a non-HTML XML node
 */
isXML = Sizzle.isXML = function( elem ) {
	// documentElement is verified for cases where it doesn't yet exist
	// (such as loading iframes in IE - #4833)
	var documentElement = elem && (elem.ownerDocument || elem).documentElement;
	return documentElement ? documentElement.nodeName !== "HTML" : false;
};

/**
 * Sets document-related variables once based on the current document
 * @param {Element|Object} [doc] An element or document object to use to set the document
 * @returns {Object} Returns the current document
 */
setDocument = Sizzle.setDocument = function( node ) {
	var hasCompare,
		doc = node ? node.ownerDocument || node : preferredDoc,
		parent = doc.defaultView;

	// If no document and documentElement is available, return
	if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
		return document;
	}

	// Set our document
	document = doc;
	docElem = doc.documentElement;

	// Support tests
	documentIsHTML = !isXML( doc );

	// Support: IE>8
	// If iframe document is assigned to "document" variable and if iframe has been reloaded,
	// IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936
	// IE6-8 do not support the defaultView property so parent will be undefined
	if ( parent && parent !== parent.top ) {
		// IE11 does not have attachEvent, so all must suffer
		if ( parent.addEventListener ) {
			parent.addEventListener( "unload", function() {
				setDocument();
			}, false );
		} else if ( parent.attachEvent ) {
			parent.attachEvent( "onunload", function() {
				setDocument();
			});
		}
	}

	/* Attributes
	---------------------------------------------------------------------- */

	// Support: IE<8
	// Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans)
	support.attributes = assert(function( div ) {
		div.className = "i";
		return !div.getAttribute("className");
	});

	/* getElement(s)By*
	---------------------------------------------------------------------- */

	// Check if getElementsByTagName("*") returns only elements
	support.getElementsByTagName = assert(function( div ) {
		div.appendChild( doc.createComment("") );
		return !div.getElementsByTagName("*").length;
	});

	// Check if getElementsByClassName can be trusted
	support.getElementsByClassName = rnative.test( doc.getElementsByClassName ) && assert(function( div ) {
		div.innerHTML = "<div class='a'></div><div class='a i'></div>";

		// Support: Safari<4
		// Catch class over-caching
		div.firstChild.className = "i";
		// Support: Opera<10
		// Catch gEBCN failure to find non-leading classes
		return div.getElementsByClassName("i").length === 2;
	});

	// Support: IE<10
	// Check if getElementById returns elements by name
	// The broken getElementById methods don't pick up programatically-set names,
	// so use a roundabout getElementsByName test
	support.getById = assert(function( div ) {
		docElem.appendChild( div ).id = expando;
		return !doc.getElementsByName || !doc.getElementsByName( expando ).length;
	});

	// ID find and filter
	if ( support.getById ) {
		Expr.find["ID"] = function( id, context ) {
			if ( typeof context.getElementById !== strundefined && documentIsHTML ) {
				var m = context.getElementById( id );
				// Check parentNode to catch when Blackberry 4.6 returns
				// nodes that are no longer in the document #6963
				return m && m.parentNode ? [ m ] : [];
			}
		};
		Expr.filter["ID"] = function( id ) {
			var attrId = id.replace( runescape, funescape );
			return function( elem ) {
				return elem.getAttribute("id") === attrId;
			};
		};
	} else {
		// Support: IE6/7
		// getElementById is not reliable as a find shortcut
		delete Expr.find["ID"];

		Expr.filter["ID"] =  function( id ) {
			var attrId = id.replace( runescape, funescape );
			return function( elem ) {
				var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
				return node && node.value === attrId;
			};
		};
	}

	// Tag
	Expr.find["TAG"] = support.getElementsByTagName ?
		function( tag, context ) {
			if ( typeof context.getElementsByTagName !== strundefined ) {
				return context.getElementsByTagName( tag );
			}
		} :
		function( tag, context ) {
			var elem,
				tmp = [],
				i = 0,
				results = context.getElementsByTagName( tag );

			// Filter out possible comments
			if ( tag === "*" ) {
				while ( (elem = results[i++]) ) {
					if ( elem.nodeType === 1 ) {
						tmp.push( elem );
					}
				}

				return tmp;
			}
			return results;
		};

	// Class
	Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
		if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) {
			return context.getElementsByClassName( className );
		}
	};

	/* QSA/matchesSelector
	---------------------------------------------------------------------- */

	// QSA and matchesSelector support

	// matchesSelector(:active) reports false when true (IE9/Opera 11.5)
	rbuggyMatches = [];

	// qSa(:focus) reports false when true (Chrome 21)
	// We allow this because of a bug in IE8/9 that throws an error
	// whenever `document.activeElement` is accessed on an iframe
	// So, we allow :focus to pass through QSA all the time to avoid the IE error
	// See http://bugs.jquery.com/ticket/13378
	rbuggyQSA = [];

	if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) {
		// Build QSA regex
		// Regex strategy adopted from Diego Perini
		assert(function( div ) {
			// Select is set to empty string on purpose
			// This is to test IE's treatment of not explicitly
			// setting a boolean content attribute,
			// since its presence should be enough
			// http://bugs.jquery.com/ticket/12359
			div.innerHTML = "<select msallowclip=''><option selected=''></option></select>";

			// Support: IE8, Opera 11-12.16
			// Nothing should be selected when empty strings follow ^= or $= or *=
			// The test attribute must be unknown in Opera but "safe" for WinRT
			// http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
			if ( div.querySelectorAll("[msallowclip^='']").length ) {
				rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
			}

			// Support: IE8
			// Boolean attributes and "value" are not treated correctly
			if ( !div.querySelectorAll("[selected]").length ) {
				rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
			}

			// Webkit/Opera - :checked should return selected option elements
			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
			// IE8 throws error here and will not see later tests
			if ( !div.querySelectorAll(":checked").length ) {
				rbuggyQSA.push(":checked");
			}
		});

		assert(function( div ) {
			// Support: Windows 8 Native Apps
			// The type and name attributes are restricted during .innerHTML assignment
			var input = doc.createElement("input");
			input.setAttribute( "type", "hidden" );
			div.appendChild( input ).setAttribute( "name", "D" );

			// Support: IE8
			// Enforce case-sensitivity of name attribute
			if ( div.querySelectorAll("[name=d]").length ) {
				rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
			}

			// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
			// IE8 throws error here and will not see later tests
			if ( !div.querySelectorAll(":enabled").length ) {
				rbuggyQSA.push( ":enabled", ":disabled" );
			}

			// Opera 10-11 does not throw on post-comma invalid pseudos
			div.querySelectorAll("*,:x");
			rbuggyQSA.push(",.*:");
		});
	}

	if ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||
		docElem.webkitMatchesSelector ||
		docElem.mozMatchesSelector ||
		docElem.oMatchesSelector ||
		docElem.msMatchesSelector) )) ) {

		assert(function( div ) {
			// Check to see if it's possible to do matchesSelector
			// on a disconnected node (IE 9)
			support.disconnectedMatch = matches.call( div, "div" );

			// This should fail with an exception
			// Gecko does not error, returns false instead
			matches.call( div, "[s!='']:x" );
			rbuggyMatches.push( "!=", pseudos );
		});
	}

	rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
	rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );

	/* Contains
	---------------------------------------------------------------------- */
	hasCompare = rnative.test( docElem.compareDocumentPosition );

	// Element contains another
	// Purposefully does not implement inclusive descendent
	// As in, an element does not contain itself
	contains = hasCompare || rnative.test( docElem.contains ) ?
		function( a, b ) {
			var adown = a.nodeType === 9 ? a.documentElement : a,
				bup = b && b.parentNode;
			return a === bup || !!( bup && bup.nodeType === 1 && (
				adown.contains ?
					adown.contains( bup ) :
					a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
			));
		} :
		function( a, b ) {
			if ( b ) {
				while ( (b = b.parentNode) ) {
					if ( b === a ) {
						return true;
					}
				}
			}
			return false;
		};

	/* Sorting
	---------------------------------------------------------------------- */

	// Document order sorting
	sortOrder = hasCompare ?
	function( a, b ) {

		// Flag for duplicate removal
		if ( a === b ) {
			hasDuplicate = true;
			return 0;
		}

		// Sort on method existence if only one input has compareDocumentPosition
		var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
		if ( compare ) {
			return compare;
		}

		// Calculate position if both inputs belong to the same document
		compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?
			a.compareDocumentPosition( b ) :

			// Otherwise we know they are disconnected
			1;

		// Disconnected nodes
		if ( compare & 1 ||
			(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {

			// Choose the first element that is related to our preferred document
			if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {
				return -1;
			}
			if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {
				return 1;
			}

			// Maintain original order
			return sortInput ?
				( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
				0;
		}

		return compare & 4 ? -1 : 1;
	} :
	function( a, b ) {
		// Exit early if the nodes are identical
		if ( a === b ) {
			hasDuplicate = true;
			return 0;
		}

		var cur,
			i = 0,
			aup = a.parentNode,
			bup = b.parentNode,
			ap = [ a ],
			bp = [ b ];

		// Parentless nodes are either documents or disconnected
		if ( !aup || !bup ) {
			return a === doc ? -1 :
				b === doc ? 1 :
				aup ? -1 :
				bup ? 1 :
				sortInput ?
				( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
				0;

		// If the nodes are siblings, we can do a quick check
		} else if ( aup === bup ) {
			return siblingCheck( a, b );
		}

		// Otherwise we need full lists of their ancestors for comparison
		cur = a;
		while ( (cur = cur.parentNode) ) {
			ap.unshift( cur );
		}
		cur = b;
		while ( (cur = cur.parentNode) ) {
			bp.unshift( cur );
		}

		// Walk down the tree looking for a discrepancy
		while ( ap[i] === bp[i] ) {
			i++;
		}

		return i ?
			// Do a sibling check if the nodes have a common ancestor
			siblingCheck( ap[i], bp[i] ) :

			// Otherwise nodes in our document sort first
			ap[i] === preferredDoc ? -1 :
			bp[i] === preferredDoc ? 1 :
			0;
	};

	return doc;
};

Sizzle.matches = function( expr, elements ) {
	return Sizzle( expr, null, null, elements );
};

Sizzle.matchesSelector = function( elem, expr ) {
	// Set document vars if needed
	if ( ( elem.ownerDocument || elem ) !== document ) {
		setDocument( elem );
	}

	// Make sure that attribute selectors are quoted
	expr = expr.replace( rattributeQuotes, "='$1']" );

	if ( support.matchesSelector && documentIsHTML &&
		( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
		( !rbuggyQSA     || !rbuggyQSA.test( expr ) ) ) {

		try {
			var ret = matches.call( elem, expr );

			// IE 9's matchesSelector returns false on disconnected nodes
			if ( ret || support.disconnectedMatch ||
					// As well, disconnected nodes are said to be in a document
					// fragment in IE 9
					elem.document && elem.document.nodeType !== 11 ) {
				return ret;
			}
		} catch(e) {}
	}

	return Sizzle( expr, document, null, [ elem ] ).length > 0;
};

Sizzle.contains = function( context, elem ) {
	// Set document vars if needed
	if ( ( context.ownerDocument || context ) !== document ) {
		setDocument( context );
	}
	return contains( context, elem );
};

Sizzle.attr = function( elem, name ) {
	// Set document vars if needed
	if ( ( elem.ownerDocument || elem ) !== document ) {
		setDocument( elem );
	}

	var fn = Expr.attrHandle[ name.toLowerCase() ],
		// Don't get fooled by Object.prototype properties (jQuery #13807)
		val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
			fn( elem, name, !documentIsHTML ) :
			undefined;

	return val !== undefined ?
		val :
		support.attributes || !documentIsHTML ?
			elem.getAttribute( name ) :
			(val = elem.getAttributeNode(name)) && val.specified ?
				val.value :
				null;
};

Sizzle.error = function( msg ) {
	throw new Error( "Syntax error, unrecognized expression: " + msg );
};

/**
 * Document sorting and removing duplicates
 * @param {ArrayLike} results
 */
Sizzle.uniqueSort = function( results ) {
	var elem,
		duplicates = [],
		j = 0,
		i = 0;

	// Unless we *know* we can detect duplicates, assume their presence
	hasDuplicate = !support.detectDuplicates;
	sortInput = !support.sortStable && results.slice( 0 );
	results.sort( sortOrder );

	if ( hasDuplicate ) {
		while ( (elem = results[i++]) ) {
			if ( elem === results[ i ] ) {
				j = duplicates.push( i );
			}
		}
		while ( j-- ) {
			results.splice( duplicates[ j ], 1 );
		}
	}

	// Clear input after sorting to release objects
	// See https://github.com/jquery/sizzle/pull/225
	sortInput = null;

	return results;
};

/**
 * Utility function for retrieving the text value of an array of DOM nodes
 * @param {Array|Element} elem
 */
getText = Sizzle.getText = function( elem ) {
	var node,
		ret = "",
		i = 0,
		nodeType = elem.nodeType;

	if ( !nodeType ) {
		// If no nodeType, this is expected to be an array
		while ( (node = elem[i++]) ) {
			// Do not traverse comment nodes
			ret += getText( node );
		}
	} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
		// Use textContent for elements
		// innerText usage removed for consistency of new lines (jQuery #11153)
		if ( typeof elem.textContent === "string" ) {
			return elem.textContent;
		} else {
			// Traverse its children
			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
				ret += getText( elem );
			}
		}
	} else if ( nodeType === 3 || nodeType === 4 ) {
		return elem.nodeValue;
	}
	// Do not include comment or processing instruction nodes

	return ret;
};

Expr = Sizzle.selectors = {

	// Can be adjusted by the user
	cacheLength: 50,

	createPseudo: markFunction,

	match: matchExpr,

	attrHandle: {},

	find: {},

	relative: {
		">": { dir: "parentNode", first: true },
		" ": { dir: "parentNode" },
		"+": { dir: "previousSibling", first: true },
		"~": { dir: "previousSibling" }
	},

	preFilter: {
		"ATTR": function( match ) {
			match[1] = match[1].replace( runescape, funescape );

			// Move the given value to match[3] whether quoted or unquoted
			match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape );

			if ( match[2] === "~=" ) {
				match[3] = " " + match[3] + " ";
			}

			return match.slice( 0, 4 );
		},

		"CHILD": function( match ) {
			/* matches from matchExpr["CHILD"]
				1 type (only|nth|...)
				2 what (child|of-type)
				3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
				4 xn-component of xn+y argument ([+-]?\d*n|)
				5 sign of xn-component
				6 x of xn-component
				7 sign of y-component
				8 y of y-component
			*/
			match[1] = match[1].toLowerCase();

			if ( match[1].slice( 0, 3 ) === "nth" ) {
				// nth-* requires argument
				if ( !match[3] ) {
					Sizzle.error( match[0] );
				}

				// numeric x and y parameters for Expr.filter.CHILD
				// remember that false/true cast respectively to 0/1
				match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
				match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );

			// other types prohibit arguments
			} else if ( match[3] ) {
				Sizzle.error( match[0] );
			}

			return match;
		},

		"PSEUDO": function( match ) {
			var excess,
				unquoted = !match[6] && match[2];

			if ( matchExpr["CHILD"].test( match[0] ) ) {
				return null;
			}

			// Accept quoted arguments as-is
			if ( match[3] ) {
				match[2] = match[4] || match[5] || "";

			// Strip excess characters from unquoted arguments
			} else if ( unquoted && rpseudo.test( unquoted ) &&
				// Get excess from tokenize (recursively)
				(excess = tokenize( unquoted, true )) &&
				// advance to the next closing parenthesis
				(excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {

				// excess is a negative index
				match[0] = match[0].slice( 0, excess );
				match[2] = unquoted.slice( 0, excess );
			}

			// Return only captures needed by the pseudo filter method (type and argument)
			return match.slice( 0, 3 );
		}
	},

	filter: {

		"TAG": function( nodeNameSelector ) {
			var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
			return nodeNameSelector === "*" ?
				function() { return true; } :
				function( elem ) {
					return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
				};
		},

		"CLASS": function( className ) {
			var pattern = classCache[ className + " " ];

			return pattern ||
				(pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
				classCache( className, function( elem ) {
					return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" );
				});
		},

		"ATTR": function( name, operator, check ) {
			return function( elem ) {
				var result = Sizzle.attr( elem, name );

				if ( result == null ) {
					return operator === "!=";
				}
				if ( !operator ) {
					return true;
				}

				result += "";

				return operator === "=" ? result === check :
					operator === "!=" ? result !== check :
					operator === "^=" ? check && result.indexOf( check ) === 0 :
					operator === "*=" ? check && result.indexOf( check ) > -1 :
					operator === "$=" ? check && result.slice( -check.length ) === check :
					operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
					operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
					false;
			};
		},

		"CHILD": function( type, what, argument, first, last ) {
			var simple = type.slice( 0, 3 ) !== "nth",
				forward = type.slice( -4 ) !== "last",
				ofType = what === "of-type";

			return first === 1 && last === 0 ?

				// Shortcut for :nth-*(n)
				function( elem ) {
					return !!elem.parentNode;
				} :

				function( elem, context, xml ) {
					var cache, outerCache, node, diff, nodeIndex, start,
						dir = simple !== forward ? "nextSibling" : "previousSibling",
						parent = elem.parentNode,
						name = ofType && elem.nodeName.toLowerCase(),
						useCache = !xml && !ofType;

					if ( parent ) {

						// :(first|last|only)-(child|of-type)
						if ( simple ) {
							while ( dir ) {
								node = elem;
								while ( (node = node[ dir ]) ) {
									if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {
										return false;
									}
								}
								// Reverse direction for :only-* (if we haven't yet done so)
								start = dir = type === "only" && !start && "nextSibling";
							}
							return true;
						}

						start = [ forward ? parent.firstChild : parent.lastChild ];

						// non-xml :nth-child(...) stores cache data on `parent`
						if ( forward && useCache ) {
							// Seek `elem` from a previously-cached index
							outerCache = parent[ expando ] || (parent[ expando ] = {});
							cache = outerCache[ type ] || [];
							nodeIndex = cache[0] === dirruns && cache[1];
							diff = cache[0] === dirruns && cache[2];
							node = nodeIndex && parent.childNodes[ nodeIndex ];

							while ( (node = ++nodeIndex && node && node[ dir ] ||

								// Fallback to seeking `elem` from the start
								(diff = nodeIndex = 0) || start.pop()) ) {

								// When found, cache indexes on `parent` and break
								if ( node.nodeType === 1 && ++diff && node === elem ) {
									outerCache[ type ] = [ dirruns, nodeIndex, diff ];
									break;
								}
							}

						// Use previously-cached element index if available
						} else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {
							diff = cache[1];

						// xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)
						} else {
							// Use the same loop as above to seek `elem` from the start
							while ( (node = ++nodeIndex && node && node[ dir ] ||
								(diff = nodeIndex = 0) || start.pop()) ) {

								if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {
									// Cache the index of each encountered element
									if ( useCache ) {
										(node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];
									}

									if ( node === elem ) {
										break;
									}
								}
							}
						}

						// Incorporate the offset, then check against cycle size
						diff -= last;
						return diff === first || ( diff % first === 0 && diff / first >= 0 );
					}
				};
		},

		"PSEUDO": function( pseudo, argument ) {
			// pseudo-class names are case-insensitive
			// http://www.w3.org/TR/selectors/#pseudo-classes
			// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
			// Remember that setFilters inherits from pseudos
			var args,
				fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
					Sizzle.error( "unsupported pseudo: " + pseudo );

			// The user may use createPseudo to indicate that
			// arguments are needed to create the filter function
			// just as Sizzle does
			if ( fn[ expando ] ) {
				return fn( argument );
			}

			// But maintain support for old signatures
			if ( fn.length > 1 ) {
				args = [ pseudo, pseudo, "", argument ];
				return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
					markFunction(function( seed, matches ) {
						var idx,
							matched = fn( seed, argument ),
							i = matched.length;
						while ( i-- ) {
							idx = indexOf.call( seed, matched[i] );
							seed[ idx ] = !( matches[ idx ] = matched[i] );
						}
					}) :
					function( elem ) {
						return fn( elem, 0, args );
					};
			}

			return fn;
		}
	},

	pseudos: {
		// Potentially complex pseudos
		"not": markFunction(function( selector ) {
			// Trim the selector passed to compile
			// to avoid treating leading and trailing
			// spaces as combinators
			var input = [],
				results = [],
				matcher = compile( selector.replace( rtrim, "$1" ) );

			return matcher[ expando ] ?
				markFunction(function( seed, matches, context, xml ) {
					var elem,
						unmatched = matcher( seed, null, xml, [] ),
						i = seed.length;

					// Match elements unmatched by `matcher`
					while ( i-- ) {
						if ( (elem = unmatched[i]) ) {
							seed[i] = !(matches[i] = elem);
						}
					}
				}) :
				function( elem, context, xml ) {
					input[0] = elem;
					matcher( input, null, xml, results );
					return !results.pop();
				};
		}),

		"has": markFunction(function( selector ) {
			return function( elem ) {
				return Sizzle( selector, elem ).length > 0;
			};
		}),

		"contains": markFunction(function( text ) {
			return function( elem ) {
				return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
			};
		}),

		// "Whether an element is represented by a :lang() selector
		// is based solely on the element's language value
		// being equal to the identifier C,
		// or beginning with the identifier C immediately followed by "-".
		// The matching of C against the element's language value is performed case-insensitively.
		// The identifier C does not have to be a valid language name."
		// http://www.w3.org/TR/selectors/#lang-pseudo
		"lang": markFunction( function( lang ) {
			// lang value must be a valid identifier
			if ( !ridentifier.test(lang || "") ) {
				Sizzle.error( "unsupported lang: " + lang );
			}
			lang = lang.replace( runescape, funescape ).toLowerCase();
			return function( elem ) {
				var elemLang;
				do {
					if ( (elemLang = documentIsHTML ?
						elem.lang :
						elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {

						elemLang = elemLang.toLowerCase();
						return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
					}
				} while ( (elem = elem.parentNode) && elem.nodeType === 1 );
				return false;
			};
		}),

		// Miscellaneous
		"target": function( elem ) {
			var hash = window.location && window.location.hash;
			return hash && hash.slice( 1 ) === elem.id;
		},

		"root": function( elem ) {
			return elem === docElem;
		},

		"focus": function( elem ) {
			return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
		},

		// Boolean properties
		"enabled": function( elem ) {
			return elem.disabled === false;
		},

		"disabled": function( elem ) {
			return elem.disabled === true;
		},

		"checked": function( elem ) {
			// In CSS3, :checked should return both checked and selected elements
			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
			var nodeName = elem.nodeName.toLowerCase();
			return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
		},

		"selected": function( elem ) {
			// Accessing this property makes selected-by-default
			// options in Safari work properly
			if ( elem.parentNode ) {
				elem.parentNode.selectedIndex;
			}

			return elem.selected === true;
		},

		// Contents
		"empty": function( elem ) {
			// http://www.w3.org/TR/selectors/#empty-pseudo
			// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
			//   but not by others (comment: 8; processing instruction: 7; etc.)
			// nodeType < 6 works because attributes (2) do not appear as children
			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
				if ( elem.nodeType < 6 ) {
					return false;
				}
			}
			return true;
		},

		"parent": function( elem ) {
			return !Expr.pseudos["empty"]( elem );
		},

		// Element/input types
		"header": function( elem ) {
			return rheader.test( elem.nodeName );
		},

		"input": function( elem ) {
			return rinputs.test( elem.nodeName );
		},

		"button": function( elem ) {
			var name = elem.nodeName.toLowerCase();
			return name === "input" && elem.type === "button" || name === "button";
		},

		"text": function( elem ) {
			var attr;
			return elem.nodeName.toLowerCase() === "input" &&
				elem.type === "text" &&

				// Support: IE<8
				// New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
				( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" );
		},

		// Position-in-collection
		"first": createPositionalPseudo(function() {
			return [ 0 ];
		}),

		"last": createPositionalPseudo(function( matchIndexes, length ) {
			return [ length - 1 ];
		}),

		"eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
			return [ argument < 0 ? argument + length : argument ];
		}),

		"even": createPositionalPseudo(function( matchIndexes, length ) {
			var i = 0;
			for ( ; i < length; i += 2 ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		}),

		"odd": createPositionalPseudo(function( matchIndexes, length ) {
			var i = 1;
			for ( ; i < length; i += 2 ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		}),

		"lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
			var i = argument < 0 ? argument + length : argument;
			for ( ; --i >= 0; ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		}),

		"gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
			var i = argument < 0 ? argument + length : argument;
			for ( ; ++i < length; ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		})
	}
};

Expr.pseudos["nth"] = Expr.pseudos["eq"];

// Add button/input type pseudos
for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
	Expr.pseudos[ i ] = createInputPseudo( i );
}
for ( i in { submit: true, reset: true } ) {
	Expr.pseudos[ i ] = createButtonPseudo( i );
}

// Easy API for creating new setFilters
function setFilters() {}
setFilters.prototype = Expr.filters = Expr.pseudos;
Expr.setFilters = new setFilters();

tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
	var matched, match, tokens, type,
		soFar, groups, preFilters,
		cached = tokenCache[ selector + " " ];

	if ( cached ) {
		return parseOnly ? 0 : cached.slice( 0 );
	}

	soFar = selector;
	groups = [];
	preFilters = Expr.preFilter;

	while ( soFar ) {

		// Comma and first run
		if ( !matched || (match = rcomma.exec( soFar )) ) {
			if ( match ) {
				// Don't consume trailing commas as valid
				soFar = soFar.slice( match[0].length ) || soFar;
			}
			groups.push( (tokens = []) );
		}

		matched = false;

		// Combinators
		if ( (match = rcombinators.exec( soFar )) ) {
			matched = match.shift();
			tokens.push({
				value: matched,
				// Cast descendant combinators to space
				type: match[0].replace( rtrim, " " )
			});
			soFar = soFar.slice( matched.length );
		}

		// Filters
		for ( type in Expr.filter ) {
			if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
				(match = preFilters[ type ]( match ))) ) {
				matched = match.shift();
				tokens.push({
					value: matched,
					type: type,
					matches: match
				});
				soFar = soFar.slice( matched.length );
			}
		}

		if ( !matched ) {
			break;
		}
	}

	// Return the length of the invalid excess
	// if we're just parsing
	// Otherwise, throw an error or return tokens
	return parseOnly ?
		soFar.length :
		soFar ?
			Sizzle.error( selector ) :
			// Cache the tokens
			tokenCache( selector, groups ).slice( 0 );
};

function toSelector( tokens ) {
	var i = 0,
		len = tokens.length,
		selector = "";
	for ( ; i < len; i++ ) {
		selector += tokens[i].value;
	}
	return selector;
}

function addCombinator( matcher, combinator, base ) {
	var dir = combinator.dir,
		checkNonElements = base && dir === "parentNode",
		doneName = done++;

	return combinator.first ?
		// Check against closest ancestor/preceding element
		function( elem, context, xml ) {
			while ( (elem = elem[ dir ]) ) {
				if ( elem.nodeType === 1 || checkNonElements ) {
					return matcher( elem, context, xml );
				}
			}
		} :

		// Check against all ancestor/preceding elements
		function( elem, context, xml ) {
			var oldCache, outerCache,
				newCache = [ dirruns, doneName ];

			// We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
			if ( xml ) {
				while ( (elem = elem[ dir ]) ) {
					if ( elem.nodeType === 1 || checkNonElements ) {
						if ( matcher( elem, context, xml ) ) {
							return true;
						}
					}
				}
			} else {
				while ( (elem = elem[ dir ]) ) {
					if ( elem.nodeType === 1 || checkNonElements ) {
						outerCache = elem[ expando ] || (elem[ expando ] = {});
						if ( (oldCache = outerCache[ dir ]) &&
							oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {

							// Assign to newCache so results back-propagate to previous elements
							return (newCache[ 2 ] = oldCache[ 2 ]);
						} else {
							// Reuse newcache so results back-propagate to previous elements
							outerCache[ dir ] = newCache;

							// A match means we're done; a fail means we have to keep checking
							if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
								return true;
							}
						}
					}
				}
			}
		};
}

function elementMatcher( matchers ) {
	return matchers.length > 1 ?
		function( elem, context, xml ) {
			var i = matchers.length;
			while ( i-- ) {
				if ( !matchers[i]( elem, context, xml ) ) {
					return false;
				}
			}
			return true;
		} :
		matchers[0];
}

function multipleContexts( selector, contexts, results ) {
	var i = 0,
		len = contexts.length;
	for ( ; i < len; i++ ) {
		Sizzle( selector, contexts[i], results );
	}
	return results;
}

function condense( unmatched, map, filter, context, xml ) {
	var elem,
		newUnmatched = [],
		i = 0,
		len = unmatched.length,
		mapped = map != null;

	for ( ; i < len; i++ ) {
		if ( (elem = unmatched[i]) ) {
			if ( !filter || filter( elem, context, xml ) ) {
				newUnmatched.push( elem );
				if ( mapped ) {
					map.push( i );
				}
			}
		}
	}

	return newUnmatched;
}

function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
	if ( postFilter && !postFilter[ expando ] ) {
		postFilter = setMatcher( postFilter );
	}
	if ( postFinder && !postFinder[ expando ] ) {
		postFinder = setMatcher( postFinder, postSelector );
	}
	return markFunction(function( seed, results, context, xml ) {
		var temp, i, elem,
			preMap = [],
			postMap = [],
			preexisting = results.length,

			// Get initial elements from seed or context
			elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),

			// Prefilter to get matcher input, preserving a map for seed-results synchronization
			matcherIn = preFilter && ( seed || !selector ) ?
				condense( elems, preMap, preFilter, context, xml ) :
				elems,

			matcherOut = matcher ?
				// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
				postFinder || ( seed ? preFilter : preexisting || postFilter ) ?

					// ...intermediate processing is necessary
					[] :

					// ...otherwise use results directly
					results :
				matcherIn;

		// Find primary matches
		if ( matcher ) {
			matcher( matcherIn, matcherOut, context, xml );
		}

		// Apply postFilter
		if ( postFilter ) {
			temp = condense( matcherOut, postMap );
			postFilter( temp, [], context, xml );

			// Un-match failing elements by moving them back to matcherIn
			i = temp.length;
			while ( i-- ) {
				if ( (elem = temp[i]) ) {
					matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
				}
			}
		}

		if ( seed ) {
			if ( postFinder || preFilter ) {
				if ( postFinder ) {
					// Get the final matcherOut by condensing this intermediate into postFinder contexts
					temp = [];
					i = matcherOut.length;
					while ( i-- ) {
						if ( (elem = matcherOut[i]) ) {
							// Restore matcherIn since elem is not yet a final match
							temp.push( (matcherIn[i] = elem) );
						}
					}
					postFinder( null, (matcherOut = []), temp, xml );
				}

				// Move matched elements from seed to results to keep them synchronized
				i = matcherOut.length;
				while ( i-- ) {
					if ( (elem = matcherOut[i]) &&
						(temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) {

						seed[temp] = !(results[temp] = elem);
					}
				}
			}

		// Add elements to results, through postFinder if defined
		} else {
			matcherOut = condense(
				matcherOut === results ?
					matcherOut.splice( preexisting, matcherOut.length ) :
					matcherOut
			);
			if ( postFinder ) {
				postFinder( null, results, matcherOut, xml );
			} else {
				push.apply( results, matcherOut );
			}
		}
	});
}

function matcherFromTokens( tokens ) {
	var checkContext, matcher, j,
		len = tokens.length,
		leadingRelative = Expr.relative[ tokens[0].type ],
		implicitRelative = leadingRelative || Expr.relative[" "],
		i = leadingRelative ? 1 : 0,

		// The foundational matcher ensures that elements are reachable from top-level context(s)
		matchContext = addCombinator( function( elem ) {
			return elem === checkContext;
		}, implicitRelative, true ),
		matchAnyContext = addCombinator( function( elem ) {
			return indexOf.call( checkContext, elem ) > -1;
		}, implicitRelative, true ),
		matchers = [ function( elem, context, xml ) {
			return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
				(checkContext = context).nodeType ?
					matchContext( elem, context, xml ) :
					matchAnyContext( elem, context, xml ) );
		} ];

	for ( ; i < len; i++ ) {
		if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
			matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
		} else {
			matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );

			// Return special upon seeing a positional matcher
			if ( matcher[ expando ] ) {
				// Find the next relative operator (if any) for proper handling
				j = ++i;
				for ( ; j < len; j++ ) {
					if ( Expr.relative[ tokens[j].type ] ) {
						break;
					}
				}
				return setMatcher(
					i > 1 && elementMatcher( matchers ),
					i > 1 && toSelector(
						// If the preceding token was a descendant combinator, insert an implicit any-element `*`
						tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
					).replace( rtrim, "$1" ),
					matcher,
					i < j && matcherFromTokens( tokens.slice( i, j ) ),
					j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
					j < len && toSelector( tokens )
				);
			}
			matchers.push( matcher );
		}
	}

	return elementMatcher( matchers );
}

function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
	var bySet = setMatchers.length > 0,
		byElement = elementMatchers.length > 0,
		superMatcher = function( seed, context, xml, results, outermost ) {
			var elem, j, matcher,
				matchedCount = 0,
				i = "0",
				unmatched = seed && [],
				setMatched = [],
				contextBackup = outermostContext,
				// We must always have either seed elements or outermost context
				elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),
				// Use integer dirruns iff this is the outermost matcher
				dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
				len = elems.length;

			if ( outermost ) {
				outermostContext = context !== document && context;
			}

			// Add elements passing elementMatchers directly to results
			// Keep `i` a string if there are no elements so `matchedCount` will be "00" below
			// Support: IE<9, Safari
			// Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id
			for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
				if ( byElement && elem ) {
					j = 0;
					while ( (matcher = elementMatchers[j++]) ) {
						if ( matcher( elem, context, xml ) ) {
							results.push( elem );
							break;
						}
					}
					if ( outermost ) {
						dirruns = dirrunsUnique;
					}
				}

				// Track unmatched elements for set filters
				if ( bySet ) {
					// They will have gone through all possible matchers
					if ( (elem = !matcher && elem) ) {
						matchedCount--;
					}

					// Lengthen the array for every element, matched or not
					if ( seed ) {
						unmatched.push( elem );
					}
				}
			}

			// Apply set filters to unmatched elements
			matchedCount += i;
			if ( bySet && i !== matchedCount ) {
				j = 0;
				while ( (matcher = setMatchers[j++]) ) {
					matcher( unmatched, setMatched, context, xml );
				}

				if ( seed ) {
					// Reintegrate element matches to eliminate the need for sorting
					if ( matchedCount > 0 ) {
						while ( i-- ) {
							if ( !(unmatched[i] || setMatched[i]) ) {
								setMatched[i] = pop.call( results );
							}
						}
					}

					// Discard index placeholder values to get only actual matches
					setMatched = condense( setMatched );
				}

				// Add matches to results
				push.apply( results, setMatched );

				// Seedless set matches succeeding multiple successful matchers stipulate sorting
				if ( outermost && !seed && setMatched.length > 0 &&
					( matchedCount + setMatchers.length ) > 1 ) {

					Sizzle.uniqueSort( results );
				}
			}

			// Override manipulation of globals by nested matchers
			if ( outermost ) {
				dirruns = dirrunsUnique;
				outermostContext = contextBackup;
			}

			return unmatched;
		};

	return bySet ?
		markFunction( superMatcher ) :
		superMatcher;
}

compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
	var i,
		setMatchers = [],
		elementMatchers = [],
		cached = compilerCache[ selector + " " ];

	if ( !cached ) {
		// Generate a function of recursive functions that can be used to check each element
		if ( !match ) {
			match = tokenize( selector );
		}
		i = match.length;
		while ( i-- ) {
			cached = matcherFromTokens( match[i] );
			if ( cached[ expando ] ) {
				setMatchers.push( cached );
			} else {
				elementMatchers.push( cached );
			}
		}

		// Cache the compiled function
		cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );

		// Save selector and tokenization
		cached.selector = selector;
	}
	return cached;
};

/**
 * A low-level selection function that works with Sizzle's compiled
 *  selector functions
 * @param {String|Function} selector A selector or a pre-compiled
 *  selector function built with Sizzle.compile
 * @param {Element} context
 * @param {Array} [results]
 * @param {Array} [seed] A set of elements to match against
 */
select = Sizzle.select = function( selector, context, results, seed ) {
	var i, tokens, token, type, find,
		compiled = typeof selector === "function" && selector,
		match = !seed && tokenize( (selector = compiled.selector || selector) );

	results = results || [];

	// Try to minimize operations if there is no seed and only one group
	if ( match.length === 1 ) {

		// Take a shortcut and set the context if the root selector is an ID
		tokens = match[0] = match[0].slice( 0 );
		if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
				support.getById && context.nodeType === 9 && documentIsHTML &&
				Expr.relative[ tokens[1].type ] ) {

			context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
			if ( !context ) {
				return results;

			// Precompiled matchers will still verify ancestry, so step up a level
			} else if ( compiled ) {
				context = context.parentNode;
			}

			selector = selector.slice( tokens.shift().value.length );
		}

		// Fetch a seed set for right-to-left matching
		i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
		while ( i-- ) {
			token = tokens[i];

			// Abort if we hit a combinator
			if ( Expr.relative[ (type = token.type) ] ) {
				break;
			}
			if ( (find = Expr.find[ type ]) ) {
				// Search, expanding context for leading sibling combinators
				if ( (seed = find(
					token.matches[0].replace( runescape, funescape ),
					rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context
				)) ) {

					// If seed is empty or no tokens remain, we can return early
					tokens.splice( i, 1 );
					selector = seed.length && toSelector( tokens );
					if ( !selector ) {
						push.apply( results, seed );
						return results;
					}

					break;
				}
			}
		}
	}

	// Compile and execute a filtering function if one is not provided
	// Provide `match` to avoid retokenization if we modified the selector above
	( compiled || compile( selector, match ) )(
		seed,
		context,
		!documentIsHTML,
		results,
		rsibling.test( selector ) && testContext( context.parentNode ) || context
	);
	return results;
};

// One-time assignments

// Sort stability
support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;

// Support: Chrome<14
// Always assume duplicates if they aren't passed to the comparison function
support.detectDuplicates = !!hasDuplicate;

// Initialize against the default document
setDocument();

// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
// Detached nodes confoundingly follow *each other*
support.sortDetached = assert(function( div1 ) {
	// Should return 1, but returns 4 (following)
	return div1.compareDocumentPosition( document.createElement("div") ) & 1;
});

// Support: IE<8
// Prevent attribute/property "interpolation"
// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
if ( !assert(function( div ) {
	div.innerHTML = "<a href='#'></a>";
	return div.firstChild.getAttribute("href") === "#" ;
}) ) {
	addHandle( "type|href|height|width", function( elem, name, isXML ) {
		if ( !isXML ) {
			return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
		}
	});
}

// Support: IE<9
// Use defaultValue in place of getAttribute("value")
if ( !support.attributes || !assert(function( div ) {
	div.innerHTML = "<input/>";
	div.firstChild.setAttribute( "value", "" );
	return div.firstChild.getAttribute( "value" ) === "";
}) ) {
	addHandle( "value", function( elem, name, isXML ) {
		if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
			return elem.defaultValue;
		}
	});
}

// Support: IE<9
// Use getAttributeNode to fetch booleans when getAttribute lies
if ( !assert(function( div ) {
	return div.getAttribute("disabled") == null;
}) ) {
	addHandle( booleans, function( elem, name, isXML ) {
		var val;
		if ( !isXML ) {
			return elem[ name ] === true ? name.toLowerCase() :
					(val = elem.getAttributeNode( name )) && val.specified ?
					val.value :
				null;
		}
	});
}

return Sizzle;

})( window );



jQuery.find = Sizzle;
jQuery.expr = Sizzle.selectors;
jQuery.expr[":"] = jQuery.expr.pseudos;
jQuery.unique = Sizzle.uniqueSort;
jQuery.text = Sizzle.getText;
jQuery.isXMLDoc = Sizzle.isXML;
jQuery.contains = Sizzle.contains;



var rneedsContext = jQuery.expr.match.needsContext;

var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/);



var risSimple = /^.[^:#\[\.,]*$/;

// Implement the identical functionality for filter and not
function winnow( elements, qualifier, not ) {
	if ( jQuery.isFunction( qualifier ) ) {
		return jQuery.grep( elements, function( elem, i ) {
			/* jshint -W018 */
			return !!qualifier.call( elem, i, elem ) !== not;
		});

	}

	if ( qualifier.nodeType ) {
		return jQuery.grep( elements, function( elem ) {
			return ( elem === qualifier ) !== not;
		});

	}

	if ( typeof qualifier === "string" ) {
		if ( risSimple.test( qualifier ) ) {
			return jQuery.filter( qualifier, elements, not );
		}

		qualifier = jQuery.filter( qualifier, elements );
	}

	return jQuery.grep( elements, function( elem ) {
		return ( indexOf.call( qualifier, elem ) >= 0 ) !== not;
	});
}

jQuery.filter = function( expr, elems, not ) {
	var elem = elems[ 0 ];

	if ( not ) {
		expr = ":not(" + expr + ")";
	}

	return elems.length === 1 && elem.nodeType === 1 ?
		jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :
		jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
			return elem.nodeType === 1;
		}));
};

jQuery.fn.extend({
	find: function( selector ) {
		var i,
			len = this.length,
			ret = [],
			self = this;

		if ( typeof selector !== "string" ) {
			return this.pushStack( jQuery( selector ).filter(function() {
				for ( i = 0; i < len; i++ ) {
					if ( jQuery.contains( self[ i ], this ) ) {
						return true;
					}
				}
			}) );
		}

		for ( i = 0; i < len; i++ ) {
			jQuery.find( selector, self[ i ], ret );
		}

		// Needed because $( selector, context ) becomes $( context ).find( selector )
		ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );
		ret.selector = this.selector ? this.selector + " " + selector : selector;
		return ret;
	},
	filter: function( selector ) {
		return this.pushStack( winnow(this, selector || [], false) );
	},
	not: function( selector ) {
		return this.pushStack( winnow(this, selector || [], true) );
	},
	is: function( selector ) {
		return !!winnow(
			this,

			// If this is a positional/relative selector, check membership in the returned set
			// so $("p:first").is("p:last") won't return true for a doc with two "p".
			typeof selector === "string" && rneedsContext.test( selector ) ?
				jQuery( selector ) :
				selector || [],
			false
		).length;
	}
});


// Initialize a jQuery object


// A central reference to the root jQuery(document)
var rootjQuery,

	// A simple way to check for HTML strings
	// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
	// Strict HTML recognition (#11290: must start with <)
	rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,

	init = jQuery.fn.init = function( selector, context ) {
		var match, elem;

		// HANDLE: $(""), $(null), $(undefined), $(false)
		if ( !selector ) {
			return this;
		}

		// Handle HTML strings
		if ( typeof selector === "string" ) {
			if ( selector[0] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) {
				// Assume that strings that start and end with <> are HTML and skip the regex check
				match = [ null, selector, null ];

			} else {
				match = rquickExpr.exec( selector );
			}

			// Match html or make sure no context is specified for #id
			if ( match && (match[1] || !context) ) {

				// HANDLE: $(html) -> $(array)
				if ( match[1] ) {
					context = context instanceof jQuery ? context[0] : context;

					// scripts is true for back-compat
					// Intentionally let the error be thrown if parseHTML is not present
					jQuery.merge( this, jQuery.parseHTML(
						match[1],
						context && context.nodeType ? context.ownerDocument || context : document,
						true
					) );

					// HANDLE: $(html, props)
					if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
						for ( match in context ) {
							// Properties of context are called as methods if possible
							if ( jQuery.isFunction( this[ match ] ) ) {
								this[ match ]( context[ match ] );

							// ...and otherwise set as attributes
							} else {
								this.attr( match, context[ match ] );
							}
						}
					}

					return this;

				// HANDLE: $(#id)
				} else {
					elem = document.getElementById( match[2] );

					// Check parentNode to catch when Blackberry 4.6 returns
					// nodes that are no longer in the document #6963
					if ( elem && elem.parentNode ) {
						// Inject the element directly into the jQuery object
						this.length = 1;
						this[0] = elem;
					}

					this.context = document;
					this.selector = selector;
					return this;
				}

			// HANDLE: $(expr, $(...))
			} else if ( !context || context.jquery ) {
				return ( context || rootjQuery ).find( selector );

			// HANDLE: $(expr, context)
			// (which is just equivalent to: $(context).find(expr)
			} else {
				return this.constructor( context ).find( selector );
			}

		// HANDLE: $(DOMElement)
		} else if ( selector.nodeType ) {
			this.context = this[0] = selector;
			this.length = 1;
			return this;

		// HANDLE: $(function)
		// Shortcut for document ready
		} else if ( jQuery.isFunction( selector ) ) {
			return typeof rootjQuery.ready !== "undefined" ?
				rootjQuery.ready( selector ) :
				// Execute immediately if ready is not present
				selector( jQuery );
		}

		if ( selector.selector !== undefined ) {
			this.selector = selector.selector;
			this.context = selector.context;
		}

		return jQuery.makeArray( selector, this );
	};

// Give the init function the jQuery prototype for later instantiation
init.prototype = jQuery.fn;

// Initialize central reference
rootjQuery = jQuery( document );


var rparentsprev = /^(?:parents|prev(?:Until|All))/,
	// methods guaranteed to produce a unique set when starting from a unique set
	guaranteedUnique = {
		children: true,
		contents: true,
		next: true,
		prev: true
	};

jQuery.extend({
	dir: function( elem, dir, until ) {
		var matched = [],
			truncate = until !== undefined;

		while ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) {
			if ( elem.nodeType === 1 ) {
				if ( truncate && jQuery( elem ).is( until ) ) {
					break;
				}
				matched.push( elem );
			}
		}
		return matched;
	},

	sibling: function( n, elem ) {
		var matched = [];

		for ( ; n; n = n.nextSibling ) {
			if ( n.nodeType === 1 && n !== elem ) {
				matched.push( n );
			}
		}

		return matched;
	}
});

jQuery.fn.extend({
	has: function( target ) {
		var targets = jQuery( target, this ),
			l = targets.length;

		return this.filter(function() {
			var i = 0;
			for ( ; i < l; i++ ) {
				if ( jQuery.contains( this, targets[i] ) ) {
					return true;
				}
			}
		});
	},

	closest: function( selectors, context ) {
		var cur,
			i = 0,
			l = this.length,
			matched = [],
			pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
				jQuery( selectors, context || this.context ) :
				0;

		for ( ; i < l; i++ ) {
			for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) {
				// Always skip document fragments
				if ( cur.nodeType < 11 && (pos ?
					pos.index(cur) > -1 :

					// Don't pass non-elements to Sizzle
					cur.nodeType === 1 &&
						jQuery.find.matchesSelector(cur, selectors)) ) {

					matched.push( cur );
					break;
				}
			}
		}

		return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched );
	},

	// Determine the position of an element within
	// the matched set of elements
	index: function( elem ) {

		// No argument, return index in parent
		if ( !elem ) {
			return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
		}

		// index in selector
		if ( typeof elem === "string" ) {
			return indexOf.call( jQuery( elem ), this[ 0 ] );
		}

		// Locate the position of the desired element
		return indexOf.call( this,

			// If it receives a jQuery object, the first element is used
			elem.jquery ? elem[ 0 ] : elem
		);
	},

	add: function( selector, context ) {
		return this.pushStack(
			jQuery.unique(
				jQuery.merge( this.get(), jQuery( selector, context ) )
			)
		);
	},

	addBack: function( selector ) {
		return this.add( selector == null ?
			this.prevObject : this.prevObject.filter(selector)
		);
	}
});

function sibling( cur, dir ) {
	while ( (cur = cur[dir]) && cur.nodeType !== 1 ) {}
	return cur;
}

jQuery.each({
	parent: function( elem ) {
		var parent = elem.parentNode;
		return parent && parent.nodeType !== 11 ? parent : null;
	},
	parents: function( elem ) {
		return jQuery.dir( elem, "parentNode" );
	},
	parentsUntil: function( elem, i, until ) {
		return jQuery.dir( elem, "parentNode", until );
	},
	next: function( elem ) {
		return sibling( elem, "nextSibling" );
	},
	prev: function( elem ) {
		return sibling( elem, "previousSibling" );
	},
	nextAll: function( elem ) {
		return jQuery.dir( elem, "nextSibling" );
	},
	prevAll: function( elem ) {
		return jQuery.dir( elem, "previousSibling" );
	},
	nextUntil: function( elem, i, until ) {
		return jQuery.dir( elem, "nextSibling", until );
	},
	prevUntil: function( elem, i, until ) {
		return jQuery.dir( elem, "previousSibling", until );
	},
	siblings: function( elem ) {
		return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
	},
	children: function( elem ) {
		return jQuery.sibling( elem.firstChild );
	},
	contents: function( elem ) {
		return elem.contentDocument || jQuery.merge( [], elem.childNodes );
	}
}, function( name, fn ) {
	jQuery.fn[ name ] = function( until, selector ) {
		var matched = jQuery.map( this, fn, until );

		if ( name.slice( -5 ) !== "Until" ) {
			selector = until;
		}

		if ( selector && typeof selector === "string" ) {
			matched = jQuery.filter( selector, matched );
		}

		if ( this.length > 1 ) {
			// Remove duplicates
			if ( !guaranteedUnique[ name ] ) {
				jQuery.unique( matched );
			}

			// Reverse order for parents* and prev-derivatives
			if ( rparentsprev.test( name ) ) {
				matched.reverse();
			}
		}

		return this.pushStack( matched );
	};
});
var rnotwhite = (/\S+/g);



// String to Object options format cache
var optionsCache = {};

// Convert String-formatted options into Object-formatted ones and store in cache
function createOptions( options ) {
	var object = optionsCache[ options ] = {};
	jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {
		object[ flag ] = true;
	});
	return object;
}

/*
 * Create a callback list using the following parameters:
 *
 *	options: an optional list of space-separated options that will change how
 *			the callback list behaves or a more traditional option object
 *
 * By default a callback list will act like an event callback list and can be
 * "fired" multiple times.
 *
 * Possible options:
 *
 *	once:			will ensure the callback list can only be fired once (like a Deferred)
 *
 *	memory:			will keep track of previous values and will call any callback added
 *					after the list has been fired right away with the latest "memorized"
 *					values (like a Deferred)
 *
 *	unique:			will ensure a callback can only be added once (no duplicate in the list)
 *
 *	stopOnFalse:	interrupt callings when a callback returns false
 *
 */
jQuery.Callbacks = function( options ) {

	// Convert options from String-formatted to Object-formatted if needed
	// (we check in cache first)
	options = typeof options === "string" ?
		( optionsCache[ options ] || createOptions( options ) ) :
		jQuery.extend( {}, options );

	var // Last fire value (for non-forgettable lists)
		memory,
		// Flag to know if list was already fired
		fired,
		// Flag to know if list is currently firing
		firing,
		// First callback to fire (used internally by add and fireWith)
		firingStart,
		// End of the loop when firing
		firingLength,
		// Index of currently firing callback (modified by remove if needed)
		firingIndex,
		// Actual callback list
		list = [],
		// Stack of fire calls for repeatable lists
		stack = !options.once && [],
		// Fire callbacks
		fire = function( data ) {
			memory = options.memory && data;
			fired = true;
			firingIndex = firingStart || 0;
			firingStart = 0;
			firingLength = list.length;
			firing = true;
			for ( ; list && firingIndex < firingLength; firingIndex++ ) {
				if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
					memory = false; // To prevent further calls using add
					break;
				}
			}
			firing = false;
			if ( list ) {
				if ( stack ) {
					if ( stack.length ) {
						fire( stack.shift() );
					}
				} else if ( memory ) {
					list = [];
				} else {
					self.disable();
				}
			}
		},
		// Actual Callbacks object
		self = {
			// Add a callback or a collection of callbacks to the list
			add: function() {
				if ( list ) {
					// First, we save the current length
					var start = list.length;
					(function add( args ) {
						jQuery.each( args, function( _, arg ) {
							var type = jQuery.type( arg );
							if ( type === "function" ) {
								if ( !options.unique || !self.has( arg ) ) {
									list.push( arg );
								}
							} else if ( arg && arg.length && type !== "string" ) {
								// Inspect recursively
								add( arg );
							}
						});
					})( arguments );
					// Do we need to add the callbacks to the
					// current firing batch?
					if ( firing ) {
						firingLength = list.length;
					// With memory, if we're not firing then
					// we should call right away
					} else if ( memory ) {
						firingStart = start;
						fire( memory );
					}
				}
				return this;
			},
			// Remove a callback from the list
			remove: function() {
				if ( list ) {
					jQuery.each( arguments, function( _, arg ) {
						var index;
						while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
							list.splice( index, 1 );
							// Handle firing indexes
							if ( firing ) {
								if ( index <= firingLength ) {
									firingLength--;
								}
								if ( index <= firingIndex ) {
									firingIndex--;
								}
							}
						}
					});
				}
				return this;
			},
			// Check if a given callback is in the list.
			// If no argument is given, return whether or not list has callbacks attached.
			has: function( fn ) {
				return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
			},
			// Remove all callbacks from the list
			empty: function() {
				list = [];
				firingLength = 0;
				return this;
			},
			// Have the list do nothing anymore
			disable: function() {
				list = stack = memory = undefined;
				return this;
			},
			// Is it disabled?
			disabled: function() {
				return !list;
			},
			// Lock the list in its current state
			lock: function() {
				stack = undefined;
				if ( !memory ) {
					self.disable();
				}
				return this;
			},
			// Is it locked?
			locked: function() {
				return !stack;
			},
			// Call all callbacks with the given context and arguments
			fireWith: function( context, args ) {
				if ( list && ( !fired || stack ) ) {
					args = args || [];
					args = [ context, args.slice ? args.slice() : args ];
					if ( firing ) {
						stack.push( args );
					} else {
						fire( args );
					}
				}
				return this;
			},
			// Call all the callbacks with the given arguments
			fire: function() {
				self.fireWith( this, arguments );
				return this;
			},
			// To know if the callbacks have already been called at least once
			fired: function() {
				return !!fired;
			}
		};

	return self;
};


jQuery.extend({

	Deferred: function( func ) {
		var tuples = [
				// action, add listener, listener list, final state
				[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
				[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
				[ "notify", "progress", jQuery.Callbacks("memory") ]
			],
			state = "pending",
			promise = {
				state: function() {
					return state;
				},
				always: function() {
					deferred.done( arguments ).fail( arguments );
					return this;
				},
				then: function( /* fnDone, fnFail, fnProgress */ ) {
					var fns = arguments;
					return jQuery.Deferred(function( newDefer ) {
						jQuery.each( tuples, function( i, tuple ) {
							var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
							// deferred[ done | fail | progress ] for forwarding actions to newDefer
							deferred[ tuple[1] ](function() {
								var returned = fn && fn.apply( this, arguments );
								if ( returned && jQuery.isFunction( returned.promise ) ) {
									returned.promise()
										.done( newDefer.resolve )
										.fail( newDefer.reject )
										.progress( newDefer.notify );
								} else {
									newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
								}
							});
						});
						fns = null;
					}).promise();
				},
				// Get a promise for this deferred
				// If obj is provided, the promise aspect is added to the object
				promise: function( obj ) {
					return obj != null ? jQuery.extend( obj, promise ) : promise;
				}
			},
			deferred = {};

		// Keep pipe for back-compat
		promise.pipe = promise.then;

		// Add list-specific methods
		jQuery.each( tuples, function( i, tuple ) {
			var list = tuple[ 2 ],
				stateString = tuple[ 3 ];

			// promise[ done | fail | progress ] = list.add
			promise[ tuple[1] ] = list.add;

			// Handle state
			if ( stateString ) {
				list.add(function() {
					// state = [ resolved | rejected ]
					state = stateString;

				// [ reject_list | resolve_list ].disable; progress_list.lock
				}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
			}

			// deferred[ resolve | reject | notify ]
			deferred[ tuple[0] ] = function() {
				deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
				return this;
			};
			deferred[ tuple[0] + "With" ] = list.fireWith;
		});

		// Make the deferred a promise
		promise.promise( deferred );

		// Call given func if any
		if ( func ) {
			func.call( deferred, deferred );
		}

		// All done!
		return deferred;
	},

	// Deferred helper
	when: function( subordinate /* , ..., subordinateN */ ) {
		var i = 0,
			resolveValues = slice.call( arguments ),
			length = resolveValues.length,

			// the count of uncompleted subordinates
			remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,

			// the master Deferred. If resolveValues consist of only a single Deferred, just use that.
			deferred = remaining === 1 ? subordinate : jQuery.Deferred(),

			// Update function for both resolve and progress values
			updateFunc = function( i, contexts, values ) {
				return function( value ) {
					contexts[ i ] = this;
					values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
					if ( values === progressValues ) {
						deferred.notifyWith( contexts, values );
					} else if ( !( --remaining ) ) {
						deferred.resolveWith( contexts, values );
					}
				};
			},

			progressValues, progressContexts, resolveContexts;

		// add listeners to Deferred subordinates; treat others as resolved
		if ( length > 1 ) {
			progressValues = new Array( length );
			progressContexts = new Array( length );
			resolveContexts = new Array( length );
			for ( ; i < length; i++ ) {
				if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
					resolveValues[ i ].promise()
						.done( updateFunc( i, resolveContexts, resolveValues ) )
						.fail( deferred.reject )
						.progress( updateFunc( i, progressContexts, progressValues ) );
				} else {
					--remaining;
				}
			}
		}

		// if we're not waiting on anything, resolve the master
		if ( !remaining ) {
			deferred.resolveWith( resolveContexts, resolveValues );
		}

		return deferred.promise();
	}
});


// The deferred used on DOM ready
var readyList;

jQuery.fn.ready = function( fn ) {
	// Add the callback
	jQuery.ready.promise().done( fn );

	return this;
};

jQuery.extend({
	// Is the DOM ready to be used? Set to true once it occurs.
	isReady: false,

	// A counter to track how many items to wait for before
	// the ready event fires. See #6781
	readyWait: 1,

	// Hold (or release) the ready event
	holdReady: function( hold ) {
		if ( hold ) {
			jQuery.readyWait++;
		} else {
			jQuery.ready( true );
		}
	},

	// Handle when the DOM is ready
	ready: function( wait ) {

		// Abort if there are pending holds or we're already ready
		if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
			return;
		}

		// Remember that the DOM is ready
		jQuery.isReady = true;

		// If a normal DOM Ready event fired, decrement, and wait if need be
		if ( wait !== true && --jQuery.readyWait > 0 ) {
			return;
		}

		// If there are functions bound, to execute
		readyList.resolveWith( document, [ jQuery ] );

		// Trigger any bound ready events
		if ( jQuery.fn.triggerHandler ) {
			jQuery( document ).triggerHandler( "ready" );
			jQuery( document ).off( "ready" );
		}
	}
});

/**
 * The ready event handler and self cleanup method
 */
function completed() {
	document.removeEventListener( "DOMContentLoaded", completed, false );
	window.removeEventListener( "load", completed, false );
	jQuery.ready();
}

jQuery.ready.promise = function( obj ) {
	if ( !readyList ) {

		readyList = jQuery.Deferred();

		// Catch cases where $(document).ready() is called after the browser event has already occurred.
		// we once tried to use readyState "interactive" here, but it caused issues like the one
		// discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
		if ( document.readyState === "complete" ) {
			// Handle it asynchronously to allow scripts the opportunity to delay ready
			setTimeout( jQuery.ready );

		} else {

			// Use the handy event callback
			document.addEventListener( "DOMContentLoaded", completed, false );

			// A fallback to window.onload, that will always work
			window.addEventListener( "load", completed, false );
		}
	}
	return readyList.promise( obj );
};

// Kick off the DOM ready check even if the user does not
jQuery.ready.promise();




// Multifunctional method to get and set values of a collection
// The value/s can optionally be executed if it's a function
var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
	var i = 0,
		len = elems.length,
		bulk = key == null;

	// Sets many values
	if ( jQuery.type( key ) === "object" ) {
		chainable = true;
		for ( i in key ) {
			jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
		}

	// Sets one value
	} else if ( value !== undefined ) {
		chainable = true;

		if ( !jQuery.isFunction( value ) ) {
			raw = true;
		}

		if ( bulk ) {
			// Bulk operations run against the entire set
			if ( raw ) {
				fn.call( elems, value );
				fn = null;

			// ...except when executing function values
			} else {
				bulk = fn;
				fn = function( elem, key, value ) {
					return bulk.call( jQuery( elem ), value );
				};
			}
		}

		if ( fn ) {
			for ( ; i < len; i++ ) {
				fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
			}
		}
	}

	return chainable ?
		elems :

		// Gets
		bulk ?
			fn.call( elems ) :
			len ? fn( elems[0], key ) : emptyGet;
};


/**
 * Determines whether an object can have data
 */
jQuery.acceptData = function( owner ) {
	// Accepts only:
	//  - Node
	//    - Node.ELEMENT_NODE
	//    - Node.DOCUMENT_NODE
	//  - Object
	//    - Any
	/* jshint -W018 */
	return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
};


function Data() {
	// Support: Android < 4,
	// Old WebKit does not have Object.preventExtensions/freeze method,
	// return new empty object instead with no [[set]] accessor
	Object.defineProperty( this.cache = {}, 0, {
		get: function() {
			return {};
		}
	});

	this.expando = jQuery.expando + Math.random();
}

Data.uid = 1;
Data.accepts = jQuery.acceptData;

Data.prototype = {
	key: function( owner ) {
		// We can accept data for non-element nodes in modern browsers,
		// but we should not, see #8335.
		// Always return the key for a frozen object.
		if ( !Data.accepts( owner ) ) {
			return 0;
		}

		var descriptor = {},
			// Check if the owner object already has a cache key
			unlock = owner[ this.expando ];

		// If not, create one
		if ( !unlock ) {
			unlock = Data.uid++;

			// Secure it in a non-enumerable, non-writable property
			try {
				descriptor[ this.expando ] = { value: unlock };
				Object.defineProperties( owner, descriptor );

			// Support: Android < 4
			// Fallback to a less secure definition
			} catch ( e ) {
				descriptor[ this.expando ] = unlock;
				jQuery.extend( owner, descriptor );
			}
		}

		// Ensure the cache object
		if ( !this.cache[ unlock ] ) {
			this.cache[ unlock ] = {};
		}

		return unlock;
	},
	set: function( owner, data, value ) {
		var prop,
			// There may be an unlock assigned to this node,
			// if there is no entry for this "owner", create one inline
			// and set the unlock as though an owner entry had always existed
			unlock = this.key( owner ),
			cache = this.cache[ unlock ];

		// Handle: [ owner, key, value ] args
		if ( typeof data === "string" ) {
			cache[ data ] = value;

		// Handle: [ owner, { properties } ] args
		} else {
			// Fresh assignments by object are shallow copied
			if ( jQuery.isEmptyObject( cache ) ) {
				jQuery.extend( this.cache[ unlock ], data );
			// Otherwise, copy the properties one-by-one to the cache object
			} else {
				for ( prop in data ) {
					cache[ prop ] = data[ prop ];
				}
			}
		}
		return cache;
	},
	get: function( owner, key ) {
		// Either a valid cache is found, or will be created.
		// New caches will be created and the unlock returned,
		// allowing direct access to the newly created
		// empty data object. A valid owner object must be provided.
		var cache = this.cache[ this.key( owner ) ];

		return key === undefined ?
			cache : cache[ key ];
	},
	access: function( owner, key, value ) {
		var stored;
		// In cases where either:
		//
		//   1. No key was specified
		//   2. A string key was specified, but no value provided
		//
		// Take the "read" path and allow the get method to determine
		// which value to return, respectively either:
		//
		//   1. The entire cache object
		//   2. The data stored at the key
		//
		if ( key === undefined ||
				((key && typeof key === "string") && value === undefined) ) {

			stored = this.get( owner, key );

			return stored !== undefined ?
				stored : this.get( owner, jQuery.camelCase(key) );
		}

		// [*]When the key is not a string, or both a key and value
		// are specified, set or extend (existing objects) with either:
		//
		//   1. An object of properties
		//   2. A key and value
		//
		this.set( owner, key, value );

		// Since the "set" path can have two possible entry points
		// return the expected data based on which path was taken[*]
		return value !== undefined ? value : key;
	},
	remove: function( owner, key ) {
		var i, name, camel,
			unlock = this.key( owner ),
			cache = this.cache[ unlock ];

		if ( key === undefined ) {
			this.cache[ unlock ] = {};

		} else {
			// Support array or space separated string of keys
			if ( jQuery.isArray( key ) ) {
				// If "name" is an array of keys...
				// When data is initially created, via ("key", "val") signature,
				// keys will be converted to camelCase.
				// Since there is no way to tell _how_ a key was added, remove
				// both plain key and camelCase key. #12786
				// This will only penalize the array argument path.
				name = key.concat( key.map( jQuery.camelCase ) );
			} else {
				camel = jQuery.camelCase( key );
				// Try the string as a key before any manipulation
				if ( key in cache ) {
					name = [ key, camel ];
				} else {
					// If a key with the spaces exists, use it.
					// Otherwise, create an array by matching non-whitespace
					name = camel;
					name = name in cache ?
						[ name ] : ( name.match( rnotwhite ) || [] );
				}
			}

			i = name.length;
			while ( i-- ) {
				delete cache[ name[ i ] ];
			}
		}
	},
	hasData: function( owner ) {
		return !jQuery.isEmptyObject(
			this.cache[ owner[ this.expando ] ] || {}
		);
	},
	discard: function( owner ) {
		if ( owner[ this.expando ] ) {
			delete this.cache[ owner[ this.expando ] ];
		}
	}
};
var data_priv = new Data();

var data_user = new Data();



/*
	Implementation Summary

	1. Enforce API surface and semantic compatibility with 1.9.x branch
	2. Improve the module's maintainability by reducing the storage
		paths to a single mechanism.
	3. Use the same single mechanism to support "private" and "user" data.
	4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
	5. Avoid exposing implementation details on user objects (eg. expando properties)
	6. Provide a clear path for implementation upgrade to WeakMap in 2014
*/
var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
	rmultiDash = /([A-Z])/g;

function dataAttr( elem, key, data ) {
	var name;

	// If nothing was found internally, try to fetch any
	// data from the HTML5 data-* attribute
	if ( data === undefined && elem.nodeType === 1 ) {
		name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
		data = elem.getAttribute( name );

		if ( typeof data === "string" ) {
			try {
				data = data === "true" ? true :
					data === "false" ? false :
					data === "null" ? null :
					// Only convert to a number if it doesn't change the string
					+data + "" === data ? +data :
					rbrace.test( data ) ? jQuery.parseJSON( data ) :
					data;
			} catch( e ) {}

			// Make sure we set the data so it isn't changed later
			data_user.set( elem, key, data );
		} else {
			data = undefined;
		}
	}
	return data;
}

jQuery.extend({
	hasData: function( elem ) {
		return data_user.hasData( elem ) || data_priv.hasData( elem );
	},

	data: function( elem, name, data ) {
		return data_user.access( elem, name, data );
	},

	removeData: function( elem, name ) {
		data_user.remove( elem, name );
	},

	// TODO: Now that all calls to _data and _removeData have been replaced
	// with direct calls to data_priv methods, these can be deprecated.
	_data: function( elem, name, data ) {
		return data_priv.access( elem, name, data );
	},

	_removeData: function( elem, name ) {
		data_priv.remove( elem, name );
	}
});

jQuery.fn.extend({
	data: function( key, value ) {
		var i, name, data,
			elem = this[ 0 ],
			attrs = elem && elem.attributes;

		// Gets all values
		if ( key === undefined ) {
			if ( this.length ) {
				data = data_user.get( elem );

				if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) {
					i = attrs.length;
					while ( i-- ) {

						// Support: IE11+
						// The attrs elements can be null (#14894)
						if ( attrs[ i ] ) {
							name = attrs[ i ].name;
							if ( name.indexOf( "data-" ) === 0 ) {
								name = jQuery.camelCase( name.slice(5) );
								dataAttr( elem, name, data[ name ] );
							}
						}
					}
					data_priv.set( elem, "hasDataAttrs", true );
				}
			}

			return data;
		}

		// Sets multiple values
		if ( typeof key === "object" ) {
			return this.each(function() {
				data_user.set( this, key );
			});
		}

		return access( this, function( value ) {
			var data,
				camelKey = jQuery.camelCase( key );

			// The calling jQuery object (element matches) is not empty
			// (and therefore has an element appears at this[ 0 ]) and the
			// `value` parameter was not undefined. An empty jQuery object
			// will result in `undefined` for elem = this[ 0 ] which will
			// throw an exception if an attempt to read a data cache is made.
			if ( elem && value === undefined ) {
				// Attempt to get data from the cache
				// with the key as-is
				data = data_user.get( elem, key );
				if ( data !== undefined ) {
					return data;
				}

				// Attempt to get data from the cache
				// with the key camelized
				data = data_user.get( elem, camelKey );
				if ( data !== undefined ) {
					return data;
				}

				// Attempt to "discover" the data in
				// HTML5 custom data-* attrs
				data = dataAttr( elem, camelKey, undefined );
				if ( data !== undefined ) {
					return data;
				}

				// We tried really hard, but the data doesn't exist.
				return;
			}

			// Set the data...
			this.each(function() {
				// First, attempt to store a copy or reference of any
				// data that might've been store with a camelCased key.
				var data = data_user.get( this, camelKey );

				// For HTML5 data-* attribute interop, we have to
				// store property names with dashes in a camelCase form.
				// This might not apply to all properties...*
				data_user.set( this, camelKey, value );

				// *... In the case of properties that might _actually_
				// have dashes, we need to also store a copy of that
				// unchanged property.
				if ( key.indexOf("-") !== -1 && data !== undefined ) {
					data_user.set( this, key, value );
				}
			});
		}, null, value, arguments.length > 1, null, true );
	},

	removeData: function( key ) {
		return this.each(function() {
			data_user.remove( this, key );
		});
	}
});


jQuery.extend({
	queue: function( elem, type, data ) {
		var queue;

		if ( elem ) {
			type = ( type || "fx" ) + "queue";
			queue = data_priv.get( elem, type );

			// Speed up dequeue by getting out quickly if this is just a lookup
			if ( data ) {
				if ( !queue || jQuery.isArray( data ) ) {
					queue = data_priv.access( elem, type, jQuery.makeArray(data) );
				} else {
					queue.push( data );
				}
			}
			return queue || [];
		}
	},

	dequeue: function( elem, type ) {
		type = type || "fx";

		var queue = jQuery.queue( elem, type ),
			startLength = queue.length,
			fn = queue.shift(),
			hooks = jQuery._queueHooks( elem, type ),
			next = function() {
				jQuery.dequeue( elem, type );
			};

		// If the fx queue is dequeued, always remove the progress sentinel
		if ( fn === "inprogress" ) {
			fn = queue.shift();
			startLength--;
		}

		if ( fn ) {

			// Add a progress sentinel to prevent the fx queue from being
			// automatically dequeued
			if ( type === "fx" ) {
				queue.unshift( "inprogress" );
			}

			// clear up the last queue stop function
			delete hooks.stop;
			fn.call( elem, next, hooks );
		}

		if ( !startLength && hooks ) {
			hooks.empty.fire();
		}
	},

	// not intended for public consumption - generates a queueHooks object, or returns the current one
	_queueHooks: function( elem, type ) {
		var key = type + "queueHooks";
		return data_priv.get( elem, key ) || data_priv.access( elem, key, {
			empty: jQuery.Callbacks("once memory").add(function() {
				data_priv.remove( elem, [ type + "queue", key ] );
			})
		});
	}
});

jQuery.fn.extend({
	queue: function( type, data ) {
		var setter = 2;

		if ( typeof type !== "string" ) {
			data = type;
			type = "fx";
			setter--;
		}

		if ( arguments.length < setter ) {
			return jQuery.queue( this[0], type );
		}

		return data === undefined ?
			this :
			this.each(function() {
				var queue = jQuery.queue( this, type, data );

				// ensure a hooks for this queue
				jQuery._queueHooks( this, type );

				if ( type === "fx" && queue[0] !== "inprogress" ) {
					jQuery.dequeue( this, type );
				}
			});
	},
	dequeue: function( type ) {
		return this.each(function() {
			jQuery.dequeue( this, type );
		});
	},
	clearQueue: function( type ) {
		return this.queue( type || "fx", [] );
	},
	// Get a promise resolved when queues of a certain type
	// are emptied (fx is the type by default)
	promise: function( type, obj ) {
		var tmp,
			count = 1,
			defer = jQuery.Deferred(),
			elements = this,
			i = this.length,
			resolve = function() {
				if ( !( --count ) ) {
					defer.resolveWith( elements, [ elements ] );
				}
			};

		if ( typeof type !== "string" ) {
			obj = type;
			type = undefined;
		}
		type = type || "fx";

		while ( i-- ) {
			tmp = data_priv.get( elements[ i ], type + "queueHooks" );
			if ( tmp && tmp.empty ) {
				count++;
				tmp.empty.add( resolve );
			}
		}
		resolve();
		return defer.promise( obj );
	}
});
var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source;

var cssExpand = [ "Top", "Right", "Bottom", "Left" ];

var isHidden = function( elem, el ) {
		// isHidden might be called from jQuery#filter function;
		// in that case, element will be second argument
		elem = el || elem;
		return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
	};

var rcheckableType = (/^(?:checkbox|radio)$/i);



(function() {
	var fragment = document.createDocumentFragment(),
		div = fragment.appendChild( document.createElement( "div" ) ),
		input = document.createElement( "input" );

	// #11217 - WebKit loses check when the name is after the checked attribute
	// Support: Windows Web Apps (WWA)
	// `name` and `type` need .setAttribute for WWA
	input.setAttribute( "type", "radio" );
	input.setAttribute( "checked", "checked" );
	input.setAttribute( "name", "t" );

	div.appendChild( input );

	// Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3
	// old WebKit doesn't clone checked state correctly in fragments
	support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;

	// Make sure textarea (and checkbox) defaultValue is properly cloned
	// Support: IE9-IE11+
	div.innerHTML = "<textarea>x</textarea>";
	support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
})();
var strundefined = typeof undefined;



support.focusinBubbles = "onfocusin" in window;


var
	rkeyEvent = /^key/,
	rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/,
	rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
	rtypenamespace = /^([^.]*)(?:\.(.+)|)$/;

function returnTrue() {
	return true;
}

function returnFalse() {
	return false;
}

function safeActiveElement() {
	try {
		return document.activeElement;
	} catch ( err ) { }
}

/*
 * Helper functions for managing events -- not part of the public interface.
 * Props to Dean Edwards' addEvent library for many of the ideas.
 */
jQuery.event = {

	global: {},

	add: function( elem, types, handler, data, selector ) {

		var handleObjIn, eventHandle, tmp,
			events, t, handleObj,
			special, handlers, type, namespaces, origType,
			elemData = data_priv.get( elem );

		// Don't attach events to noData or text/comment nodes (but allow plain objects)
		if ( !elemData ) {
			return;
		}

		// Caller can pass in an object of custom data in lieu of the handler
		if ( handler.handler ) {
			handleObjIn = handler;
			handler = handleObjIn.handler;
			selector = handleObjIn.selector;
		}

		// Make sure that the handler has a unique ID, used to find/remove it later
		if ( !handler.guid ) {
			handler.guid = jQuery.guid++;
		}

		// Init the element's event structure and main handler, if this is the first
		if ( !(events = elemData.events) ) {
			events = elemData.events = {};
		}
		if ( !(eventHandle = elemData.handle) ) {
			eventHandle = elemData.handle = function( e ) {
				// Discard the second event of a jQuery.event.trigger() and
				// when an event is called after a page has unloaded
				return typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ?
					jQuery.event.dispatch.apply( elem, arguments ) : undefined;
			};
		}

		// Handle multiple events separated by a space
		types = ( types || "" ).match( rnotwhite ) || [ "" ];
		t = types.length;
		while ( t-- ) {
			tmp = rtypenamespace.exec( types[t] ) || [];
			type = origType = tmp[1];
			namespaces = ( tmp[2] || "" ).split( "." ).sort();

			// There *must* be a type, no attaching namespace-only handlers
			if ( !type ) {
				continue;
			}

			// If event changes its type, use the special event handlers for the changed type
			special = jQuery.event.special[ type ] || {};

			// If selector defined, determine special event api type, otherwise given type
			type = ( selector ? special.delegateType : special.bindType ) || type;

			// Update special based on newly reset type
			special = jQuery.event.special[ type ] || {};

			// handleObj is passed to all event handlers
			handleObj = jQuery.extend({
				type: type,
				origType: origType,
				data: data,
				handler: handler,
				guid: handler.guid,
				selector: selector,
				needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
				namespace: namespaces.join(".")
			}, handleObjIn );

			// Init the event handler queue if we're the first
			if ( !(handlers = events[ type ]) ) {
				handlers = events[ type ] = [];
				handlers.delegateCount = 0;

				// Only use addEventListener if the special events handler returns false
				if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
					if ( elem.addEventListener ) {
						elem.addEventListener( type, eventHandle, false );
					}
				}
			}

			if ( special.add ) {
				special.add.call( elem, handleObj );

				if ( !handleObj.handler.guid ) {
					handleObj.handler.guid = handler.guid;
				}
			}

			// Add to the element's handler list, delegates in front
			if ( selector ) {
				handlers.splice( handlers.delegateCount++, 0, handleObj );
			} else {
				handlers.push( handleObj );
			}

			// Keep track of which events have ever been used, for event optimization
			jQuery.event.global[ type ] = true;
		}

	},

	// Detach an event or set of events from an element
	remove: function( elem, types, handler, selector, mappedTypes ) {

		var j, origCount, tmp,
			events, t, handleObj,
			special, handlers, type, namespaces, origType,
			elemData = data_priv.hasData( elem ) && data_priv.get( elem );

		if ( !elemData || !(events = elemData.events) ) {
			return;
		}

		// Once for each type.namespace in types; type may be omitted
		types = ( types || "" ).match( rnotwhite ) || [ "" ];
		t = types.length;
		while ( t-- ) {
			tmp = rtypenamespace.exec( types[t] ) || [];
			type = origType = tmp[1];
			namespaces = ( tmp[2] || "" ).split( "." ).sort();

			// Unbind all events (on this namespace, if provided) for the element
			if ( !type ) {
				for ( type in events ) {
					jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
				}
				continue;
			}

			special = jQuery.event.special[ type ] || {};
			type = ( selector ? special.delegateType : special.bindType ) || type;
			handlers = events[ type ] || [];
			tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" );

			// Remove matching events
			origCount = j = handlers.length;
			while ( j-- ) {
				handleObj = handlers[ j ];

				if ( ( mappedTypes || origType === handleObj.origType ) &&
					( !handler || handler.guid === handleObj.guid ) &&
					( !tmp || tmp.test( handleObj.namespace ) ) &&
					( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
					handlers.splice( j, 1 );

					if ( handleObj.selector ) {
						handlers.delegateCount--;
					}
					if ( special.remove ) {
						special.remove.call( elem, handleObj );
					}
				}
			}

			// Remove generic event handler if we removed something and no more handlers exist
			// (avoids potential for endless recursion during removal of special event handlers)
			if ( origCount && !handlers.length ) {
				if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
					jQuery.removeEvent( elem, type, elemData.handle );
				}

				delete events[ type ];
			}
		}

		// Remove the expando if it's no longer used
		if ( jQuery.isEmptyObject( events ) ) {
			delete elemData.handle;
			data_priv.remove( elem, "events" );
		}
	},

	trigger: function( event, data, elem, onlyHandlers ) {

		var i, cur, tmp, bubbleType, ontype, handle, special,
			eventPath = [ elem || document ],
			type = hasOwn.call( event, "type" ) ? event.type : event,
			namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : [];

		cur = tmp = elem = elem || document;

		// Don't do events on text and comment nodes
		if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
			return;
		}

		// focus/blur morphs to focusin/out; ensure we're not firing them right now
		if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
			return;
		}

		if ( type.indexOf(".") >= 0 ) {
			// Namespaced trigger; create a regexp to match event type in handle()
			namespaces = type.split(".");
			type = namespaces.shift();
			namespaces.sort();
		}
		ontype = type.indexOf(":") < 0 && "on" + type;

		// Caller can pass in a jQuery.Event object, Object, or just an event type string
		event = event[ jQuery.expando ] ?
			event :
			new jQuery.Event( type, typeof event === "object" && event );

		// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
		event.isTrigger = onlyHandlers ? 2 : 3;
		event.namespace = namespaces.join(".");
		event.namespace_re = event.namespace ?
			new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
			null;

		// Clean up the event in case it is being reused
		event.result = undefined;
		if ( !event.target ) {
			event.target = elem;
		}

		// Clone any incoming data and prepend the event, creating the handler arg list
		data = data == null ?
			[ event ] :
			jQuery.makeArray( data, [ event ] );

		// Allow special events to draw outside the lines
		special = jQuery.event.special[ type ] || {};
		if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
			return;
		}

		// Determine event propagation path in advance, per W3C events spec (#9951)
		// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
		if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {

			bubbleType = special.delegateType || type;
			if ( !rfocusMorph.test( bubbleType + type ) ) {
				cur = cur.parentNode;
			}
			for ( ; cur; cur = cur.parentNode ) {
				eventPath.push( cur );
				tmp = cur;
			}

			// Only add window if we got to document (e.g., not plain obj or detached DOM)
			if ( tmp === (elem.ownerDocument || document) ) {
				eventPath.push( tmp.defaultView || tmp.parentWindow || window );
			}
		}

		// Fire handlers on the event path
		i = 0;
		while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {

			event.type = i > 1 ?
				bubbleType :
				special.bindType || type;

			// jQuery handler
			handle = ( data_priv.get( cur, "events" ) || {} )[ event.type ] && data_priv.get( cur, "handle" );
			if ( handle ) {
				handle.apply( cur, data );
			}

			// Native handler
			handle = ontype && cur[ ontype ];
			if ( handle && handle.apply && jQuery.acceptData( cur ) ) {
				event.result = handle.apply( cur, data );
				if ( event.result === false ) {
					event.preventDefault();
				}
			}
		}
		event.type = type;

		// If nobody prevented the default action, do it now
		if ( !onlyHandlers && !event.isDefaultPrevented() ) {

			if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&
				jQuery.acceptData( elem ) ) {

				// Call a native DOM method on the target with the same name name as the event.
				// Don't do default actions on window, that's where global variables be (#6170)
				if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) {

					// Don't re-trigger an onFOO event when we call its FOO() method
					tmp = elem[ ontype ];

					if ( tmp ) {
						elem[ ontype ] = null;
					}

					// Prevent re-triggering of the same event, since we already bubbled it above
					jQuery.event.triggered = type;
					elem[ type ]();
					jQuery.event.triggered = undefined;

					if ( tmp ) {
						elem[ ontype ] = tmp;
					}
				}
			}
		}

		return event.result;
	},

	dispatch: function( event ) {

		// Make a writable jQuery.Event from the native event object
		event = jQuery.event.fix( event );

		var i, j, ret, matched, handleObj,
			handlerQueue = [],
			args = slice.call( arguments ),
			handlers = ( data_priv.get( this, "events" ) || {} )[ event.type ] || [],
			special = jQuery.event.special[ event.type ] || {};

		// Use the fix-ed jQuery.Event rather than the (read-only) native event
		args[0] = event;
		event.delegateTarget = this;

		// Call the preDispatch hook for the mapped type, and let it bail if desired
		if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
			return;
		}

		// Determine handlers
		handlerQueue = jQuery.event.handlers.call( this, event, handlers );

		// Run delegates first; they may want to stop propagation beneath us
		i = 0;
		while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {
			event.currentTarget = matched.elem;

			j = 0;
			while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {

				// Triggered event must either 1) have no namespace, or
				// 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
				if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {

					event.handleObj = handleObj;
					event.data = handleObj.data;

					ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
							.apply( matched.elem, args );

					if ( ret !== undefined ) {
						if ( (event.result = ret) === false ) {
							event.preventDefault();
							event.stopPropagation();
						}
					}
				}
			}
		}

		// Call the postDispatch hook for the mapped type
		if ( special.postDispatch ) {
			special.postDispatch.call( this, event );
		}

		return event.result;
	},

	handlers: function( event, handlers ) {
		var i, matches, sel, handleObj,
			handlerQueue = [],
			delegateCount = handlers.delegateCount,
			cur = event.target;

		// Find delegate handlers
		// Black-hole SVG <use> instance trees (#13180)
		// Avoid non-left-click bubbling in Firefox (#3861)
		if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) {

			for ( ; cur !== this; cur = cur.parentNode || this ) {

				// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
				if ( cur.disabled !== true || event.type !== "click" ) {
					matches = [];
					for ( i = 0; i < delegateCount; i++ ) {
						handleObj = handlers[ i ];

						// Don't conflict with Object.prototype properties (#13203)
						sel = handleObj.selector + " ";

						if ( matches[ sel ] === undefined ) {
							matches[ sel ] = handleObj.needsContext ?
								jQuery( sel, this ).index( cur ) >= 0 :
								jQuery.find( sel, this, null, [ cur ] ).length;
						}
						if ( matches[ sel ] ) {
							matches.push( handleObj );
						}
					}
					if ( matches.length ) {
						handlerQueue.push({ elem: cur, handlers: matches });
					}
				}
			}
		}

		// Add the remaining (directly-bound) handlers
		if ( delegateCount < handlers.length ) {
			handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });
		}

		return handlerQueue;
	},

	// Includes some event props shared by KeyEvent and MouseEvent
	props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),

	fixHooks: {},

	keyHooks: {
		props: "char charCode key keyCode".split(" "),
		filter: function( event, original ) {

			// Add which for key events
			if ( event.which == null ) {
				event.which = original.charCode != null ? original.charCode : original.keyCode;
			}

			return event;
		}
	},

	mouseHooks: {
		props: "button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
		filter: function( event, original ) {
			var eventDoc, doc, body,
				button = original.button;

			// Calculate pageX/Y if missing and clientX/Y available
			if ( event.pageX == null && original.clientX != null ) {
				eventDoc = event.target.ownerDocument || document;
				doc = eventDoc.documentElement;
				body = eventDoc.body;

				event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
				event.pageY = original.clientY + ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) - ( doc && doc.clientTop  || body && body.clientTop  || 0 );
			}

			// Add which for click: 1 === left; 2 === middle; 3 === right
			// Note: button is not normalized, so don't use it
			if ( !event.which && button !== undefined ) {
				event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
			}

			return event;
		}
	},

	fix: function( event ) {
		if ( event[ jQuery.expando ] ) {
			return event;
		}

		// Create a writable copy of the event object and normalize some properties
		var i, prop, copy,
			type = event.type,
			originalEvent = event,
			fixHook = this.fixHooks[ type ];

		if ( !fixHook ) {
			this.fixHooks[ type ] = fixHook =
				rmouseEvent.test( type ) ? this.mouseHooks :
				rkeyEvent.test( type ) ? this.keyHooks :
				{};
		}
		copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;

		event = new jQuery.Event( originalEvent );

		i = copy.length;
		while ( i-- ) {
			prop = copy[ i ];
			event[ prop ] = originalEvent[ prop ];
		}

		// Support: Cordova 2.5 (WebKit) (#13255)
		// All events should have a target; Cordova deviceready doesn't
		if ( !event.target ) {
			event.target = document;
		}

		// Support: Safari 6.0+, Chrome < 28
		// Target should not be a text node (#504, #13143)
		if ( event.target.nodeType === 3 ) {
			event.target = event.target.parentNode;
		}

		return fixHook.filter ? fixHook.filter( event, originalEvent ) : event;
	},

	special: {
		load: {
			// Prevent triggered image.load events from bubbling to window.load
			noBubble: true
		},
		focus: {
			// Fire native event if possible so blur/focus sequence is correct
			trigger: function() {
				if ( this !== safeActiveElement() && this.focus ) {
					this.focus();
					return false;
				}
			},
			delegateType: "focusin"
		},
		blur: {
			trigger: function() {
				if ( this === safeActiveElement() && this.blur ) {
					this.blur();
					return false;
				}
			},
			delegateType: "focusout"
		},
		click: {
			// For checkbox, fire native event so checked state will be right
			trigger: function() {
				if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) {
					this.click();
					return false;
				}
			},

			// For cross-browser consistency, don't fire native .click() on links
			_default: function( event ) {
				return jQuery.nodeName( event.target, "a" );
			}
		},

		beforeunload: {
			postDispatch: function( event ) {

				// Support: Firefox 20+
				// Firefox doesn't alert if the returnValue field is not set.
				if ( event.result !== undefined && event.originalEvent ) {
					event.originalEvent.returnValue = event.result;
				}
			}
		}
	},

	simulate: function( type, elem, event, bubble ) {
		// Piggyback on a donor event to simulate a different one.
		// Fake originalEvent to avoid donor's stopPropagation, but if the
		// simulated event prevents default then we do the same on the donor.
		var e = jQuery.extend(
			new jQuery.Event(),
			event,
			{
				type: type,
				isSimulated: true,
				originalEvent: {}
			}
		);
		if ( bubble ) {
			jQuery.event.trigger( e, null, elem );
		} else {
			jQuery.event.dispatch.call( elem, e );
		}
		if ( e.isDefaultPrevented() ) {
			event.preventDefault();
		}
	}
};

jQuery.removeEvent = function( elem, type, handle ) {
	if ( elem.removeEventListener ) {
		elem.removeEventListener( type, handle, false );
	}
};

jQuery.Event = function( src, props ) {
	// Allow instantiation without the 'new' keyword
	if ( !(this instanceof jQuery.Event) ) {
		return new jQuery.Event( src, props );
	}

	// Event object
	if ( src && src.type ) {
		this.originalEvent = src;
		this.type = src.type;

		// Events bubbling up the document may have been marked as prevented
		// by a handler lower down the tree; reflect the correct value.
		this.isDefaultPrevented = src.defaultPrevented ||
				src.defaultPrevented === undefined &&
				// Support: Android < 4.0
				src.returnValue === false ?
			returnTrue :
			returnFalse;

	// Event type
	} else {
		this.type = src;
	}

	// Put explicitly provided properties onto the event object
	if ( props ) {
		jQuery.extend( this, props );
	}

	// Create a timestamp if incoming event doesn't have one
	this.timeStamp = src && src.timeStamp || jQuery.now();

	// Mark it as fixed
	this[ jQuery.expando ] = true;
};

// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
jQuery.Event.prototype = {
	isDefaultPrevented: returnFalse,
	isPropagationStopped: returnFalse,
	isImmediatePropagationStopped: returnFalse,

	preventDefault: function() {
		var e = this.originalEvent;

		this.isDefaultPrevented = returnTrue;

		if ( e && e.preventDefault ) {
			e.preventDefault();
		}
	},
	stopPropagation: function() {
		var e = this.originalEvent;

		this.isPropagationStopped = returnTrue;

		if ( e && e.stopPropagation ) {
			e.stopPropagation();
		}
	},
	stopImmediatePropagation: function() {
		var e = this.originalEvent;

		this.isImmediatePropagationStopped = returnTrue;

		if ( e && e.stopImmediatePropagation ) {
			e.stopImmediatePropagation();
		}

		this.stopPropagation();
	}
};

// Create mouseenter/leave events using mouseover/out and event-time checks
// Support: Chrome 15+
jQuery.each({
	mouseenter: "mouseover",
	mouseleave: "mouseout",
	pointerenter: "pointerover",
	pointerleave: "pointerout"
}, function( orig, fix ) {
	jQuery.event.special[ orig ] = {
		delegateType: fix,
		bindType: fix,

		handle: function( event ) {
			var ret,
				target = this,
				related = event.relatedTarget,
				handleObj = event.handleObj;

			// For mousenter/leave call the handler if related is outside the target.
			// NB: No relatedTarget if the mouse left/entered the browser window
			if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
				event.type = handleObj.origType;
				ret = handleObj.handler.apply( this, arguments );
				event.type = fix;
			}
			return ret;
		}
	};
});

// Create "bubbling" focus and blur events
// Support: Firefox, Chrome, Safari
if ( !support.focusinBubbles ) {
	jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {

		// Attach a single capturing handler on the document while someone wants focusin/focusout
		var handler = function( event ) {
				jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
			};

		jQuery.event.special[ fix ] = {
			setup: function() {
				var doc = this.ownerDocument || this,
					attaches = data_priv.access( doc, fix );

				if ( !attaches ) {
					doc.addEventListener( orig, handler, true );
				}
				data_priv.access( doc, fix, ( attaches || 0 ) + 1 );
			},
			teardown: function() {
				var doc = this.ownerDocument || this,
					attaches = data_priv.access( doc, fix ) - 1;

				if ( !attaches ) {
					doc.removeEventListener( orig, handler, true );
					data_priv.remove( doc, fix );

				} else {
					data_priv.access( doc, fix, attaches );
				}
			}
		};
	});
}

jQuery.fn.extend({

	on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
		var origFn, type;

		// Types can be a map of types/handlers
		if ( typeof types === "object" ) {
			// ( types-Object, selector, data )
			if ( typeof selector !== "string" ) {
				// ( types-Object, data )
				data = data || selector;
				selector = undefined;
			}
			for ( type in types ) {
				this.on( type, selector, data, types[ type ], one );
			}
			return this;
		}

		if ( data == null && fn == null ) {
			// ( types, fn )
			fn = selector;
			data = selector = undefined;
		} else if ( fn == null ) {
			if ( typeof selector === "string" ) {
				// ( types, selector, fn )
				fn = data;
				data = undefined;
			} else {
				// ( types, data, fn )
				fn = data;
				data = selector;
				selector = undefined;
			}
		}
		if ( fn === false ) {
			fn = returnFalse;
		} else if ( !fn ) {
			return this;
		}

		if ( one === 1 ) {
			origFn = fn;
			fn = function( event ) {
				// Can use an empty set, since event contains the info
				jQuery().off( event );
				return origFn.apply( this, arguments );
			};
			// Use same guid so caller can remove using origFn
			fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
		}
		return this.each( function() {
			jQuery.event.add( this, types, fn, data, selector );
		});
	},
	one: function( types, selector, data, fn ) {
		return this.on( types, selector, data, fn, 1 );
	},
	off: function( types, selector, fn ) {
		var handleObj, type;
		if ( types && types.preventDefault && types.handleObj ) {
			// ( event )  dispatched jQuery.Event
			handleObj = types.handleObj;
			jQuery( types.delegateTarget ).off(
				handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
				handleObj.selector,
				handleObj.handler
			);
			return this;
		}
		if ( typeof types === "object" ) {
			// ( types-object [, selector] )
			for ( type in types ) {
				this.off( type, selector, types[ type ] );
			}
			return this;
		}
		if ( selector === false || typeof selector === "function" ) {
			// ( types [, fn] )
			fn = selector;
			selector = undefined;
		}
		if ( fn === false ) {
			fn = returnFalse;
		}
		return this.each(function() {
			jQuery.event.remove( this, types, fn, selector );
		});
	},

	trigger: function( type, data ) {
		return this.each(function() {
			jQuery.event.trigger( type, data, this );
		});
	},
	triggerHandler: function( type, data ) {
		var elem = this[0];
		if ( elem ) {
			return jQuery.event.trigger( type, data, elem, true );
		}
	}
});


var
	rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
	rtagName = /<([\w:]+)/,
	rhtml = /<|&#?\w+;/,
	rnoInnerhtml = /<(?:script|style|link)/i,
	// checked="checked" or checked
	rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
	rscriptType = /^$|\/(?:java|ecma)script/i,
	rscriptTypeMasked = /^true\/(.*)/,
	rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,

	// We have to close these tags to support XHTML (#13200)
	wrapMap = {

		// Support: IE 9
		option: [ 1, "<select multiple='multiple'>", "</select>" ],

		thead: [ 1, "<table>", "</table>" ],
		col: [ 2, "<table><colgroup>", "</colgroup></table>" ],
		tr: [ 2, "<table><tbody>", "</tbody></table>" ],
		td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],

		_default: [ 0, "", "" ]
	};

// Support: IE 9
wrapMap.optgroup = wrapMap.option;

wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
wrapMap.th = wrapMap.td;

// Support: 1.x compatibility
// Manipulating tables requires a tbody
function manipulationTarget( elem, content ) {
	return jQuery.nodeName( elem, "table" ) &&
		jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ?

		elem.getElementsByTagName("tbody")[0] ||
			elem.appendChild( elem.ownerDocument.createElement("tbody") ) :
		elem;
}

// Replace/restore the type attribute of script elements for safe DOM manipulation
function disableScript( elem ) {
	elem.type = (elem.getAttribute("type") !== null) + "/" + elem.type;
	return elem;
}
function restoreScript( elem ) {
	var match = rscriptTypeMasked.exec( elem.type );

	if ( match ) {
		elem.type = match[ 1 ];
	} else {
		elem.removeAttribute("type");
	}

	return elem;
}

// Mark scripts as having already been evaluated
function setGlobalEval( elems, refElements ) {
	var i = 0,
		l = elems.length;

	for ( ; i < l; i++ ) {
		data_priv.set(
			elems[ i ], "globalEval", !refElements || data_priv.get( refElements[ i ], "globalEval" )
		);
	}
}

function cloneCopyEvent( src, dest ) {
	var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events;

	if ( dest.nodeType !== 1 ) {
		return;
	}

	// 1. Copy private data: events, handlers, etc.
	if ( data_priv.hasData( src ) ) {
		pdataOld = data_priv.access( src );
		pdataCur = data_priv.set( dest, pdataOld );
		events = pdataOld.events;

		if ( events ) {
			delete pdataCur.handle;
			pdataCur.events = {};

			for ( type in events ) {
				for ( i = 0, l = events[ type ].length; i < l; i++ ) {
					jQuery.event.add( dest, type, events[ type ][ i ] );
				}
			}
		}
	}

	// 2. Copy user data
	if ( data_user.hasData( src ) ) {
		udataOld = data_user.access( src );
		udataCur = jQuery.extend( {}, udataOld );

		data_user.set( dest, udataCur );
	}
}

function getAll( context, tag ) {
	var ret = context.getElementsByTagName ? context.getElementsByTagName( tag || "*" ) :
			context.querySelectorAll ? context.querySelectorAll( tag || "*" ) :
			[];

	return tag === undefined || tag && jQuery.nodeName( context, tag ) ?
		jQuery.merge( [ context ], ret ) :
		ret;
}

// Support: IE >= 9
function fixInput( src, dest ) {
	var nodeName = dest.nodeName.toLowerCase();

	// Fails to persist the checked state of a cloned checkbox or radio button.
	if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
		dest.checked = src.checked;

	// Fails to return the selected option to the default selected state when cloning options
	} else if ( nodeName === "input" || nodeName === "textarea" ) {
		dest.defaultValue = src.defaultValue;
	}
}

jQuery.extend({
	clone: function( elem, dataAndEvents, deepDataAndEvents ) {
		var i, l, srcElements, destElements,
			clone = elem.cloneNode( true ),
			inPage = jQuery.contains( elem.ownerDocument, elem );

		// Support: IE >= 9
		// Fix Cloning issues
		if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&
				!jQuery.isXMLDoc( elem ) ) {

			// We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2
			destElements = getAll( clone );
			srcElements = getAll( elem );

			for ( i = 0, l = srcElements.length; i < l; i++ ) {
				fixInput( srcElements[ i ], destElements[ i ] );
			}
		}

		// Copy the events from the original to the clone
		if ( dataAndEvents ) {
			if ( deepDataAndEvents ) {
				srcElements = srcElements || getAll( elem );
				destElements = destElements || getAll( clone );

				for ( i = 0, l = srcElements.length; i < l; i++ ) {
					cloneCopyEvent( srcElements[ i ], destElements[ i ] );
				}
			} else {
				cloneCopyEvent( elem, clone );
			}
		}

		// Preserve script evaluation history
		destElements = getAll( clone, "script" );
		if ( destElements.length > 0 ) {
			setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
		}

		// Return the cloned set
		return clone;
	},

	buildFragment: function( elems, context, scripts, selection ) {
		var elem, tmp, tag, wrap, contains, j,
			fragment = context.createDocumentFragment(),
			nodes = [],
			i = 0,
			l = elems.length;

		for ( ; i < l; i++ ) {
			elem = elems[ i ];

			if ( elem || elem === 0 ) {

				// Add nodes directly
				if ( jQuery.type( elem ) === "object" ) {
					// Support: QtWebKit
					// jQuery.merge because push.apply(_, arraylike) throws
					jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );

				// Convert non-html into a text node
				} else if ( !rhtml.test( elem ) ) {
					nodes.push( context.createTextNode( elem ) );

				// Convert html into DOM nodes
				} else {
					tmp = tmp || fragment.appendChild( context.createElement("div") );

					// Deserialize a standard representation
					tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase();
					wrap = wrapMap[ tag ] || wrapMap._default;
					tmp.innerHTML = wrap[ 1 ] + elem.replace( rxhtmlTag, "<$1></$2>" ) + wrap[ 2 ];

					// Descend through wrappers to the right content
					j = wrap[ 0 ];
					while ( j-- ) {
						tmp = tmp.lastChild;
					}

					// Support: QtWebKit
					// jQuery.merge because push.apply(_, arraylike) throws
					jQuery.merge( nodes, tmp.childNodes );

					// Remember the top-level container
					tmp = fragment.firstChild;

					// Fixes #12346
					// Support: Webkit, IE
					tmp.textContent = "";
				}
			}
		}

		// Remove wrapper from fragment
		fragment.textContent = "";

		i = 0;
		while ( (elem = nodes[ i++ ]) ) {

			// #4087 - If origin and destination elements are the same, and this is
			// that element, do not do anything
			if ( selection && jQuery.inArray( elem, selection ) !== -1 ) {
				continue;
			}

			contains = jQuery.contains( elem.ownerDocument, elem );

			// Append to fragment
			tmp = getAll( fragment.appendChild( elem ), "script" );

			// Preserve script evaluation history
			if ( contains ) {
				setGlobalEval( tmp );
			}

			// Capture executables
			if ( scripts ) {
				j = 0;
				while ( (elem = tmp[ j++ ]) ) {
					if ( rscriptType.test( elem.type || "" ) ) {
						scripts.push( elem );
					}
				}
			}
		}

		return fragment;
	},

	cleanData: function( elems ) {
		var data, elem, type, key,
			special = jQuery.event.special,
			i = 0;

		for ( ; (elem = elems[ i ]) !== undefined; i++ ) {
			if ( jQuery.acceptData( elem ) ) {
				key = elem[ data_priv.expando ];

				if ( key && (data = data_priv.cache[ key ]) ) {
					if ( data.events ) {
						for ( type in data.events ) {
							if ( special[ type ] ) {
								jQuery.event.remove( elem, type );

							// This is a shortcut to avoid jQuery.event.remove's overhead
							} else {
								jQuery.removeEvent( elem, type, data.handle );
							}
						}
					}
					if ( data_priv.cache[ key ] ) {
						// Discard any remaining `private` data
						delete data_priv.cache[ key ];
					}
				}
			}
			// Discard any remaining `user` data
			delete data_user.cache[ elem[ data_user.expando ] ];
		}
	}
});

jQuery.fn.extend({
	text: function( value ) {
		return access( this, function( value ) {
			return value === undefined ?
				jQuery.text( this ) :
				this.empty().each(function() {
					if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
						this.textContent = value;
					}
				});
		}, null, value, arguments.length );
	},

	append: function() {
		return this.domManip( arguments, function( elem ) {
			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
				var target = manipulationTarget( this, elem );
				target.appendChild( elem );
			}
		});
	},

	prepend: function() {
		return this.domManip( arguments, function( elem ) {
			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
				var target = manipulationTarget( this, elem );
				target.insertBefore( elem, target.firstChild );
			}
		});
	},

	before: function() {
		return this.domManip( arguments, function( elem ) {
			if ( this.parentNode ) {
				this.parentNode.insertBefore( elem, this );
			}
		});
	},

	after: function() {
		return this.domManip( arguments, function( elem ) {
			if ( this.parentNode ) {
				this.parentNode.insertBefore( elem, this.nextSibling );
			}
		});
	},

	remove: function( selector, keepData /* Internal Use Only */ ) {
		var elem,
			elems = selector ? jQuery.filter( selector, this ) : this,
			i = 0;

		for ( ; (elem = elems[i]) != null; i++ ) {
			if ( !keepData && elem.nodeType === 1 ) {
				jQuery.cleanData( getAll( elem ) );
			}

			if ( elem.parentNode ) {
				if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) {
					setGlobalEval( getAll( elem, "script" ) );
				}
				elem.parentNode.removeChild( elem );
			}
		}

		return this;
	},

	empty: function() {
		var elem,
			i = 0;

		for ( ; (elem = this[i]) != null; i++ ) {
			if ( elem.nodeType === 1 ) {

				// Prevent memory leaks
				jQuery.cleanData( getAll( elem, false ) );

				// Remove any remaining nodes
				elem.textContent = "";
			}
		}

		return this;
	},

	clone: function( dataAndEvents, deepDataAndEvents ) {
		dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
		deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;

		return this.map(function() {
			return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
		});
	},

	html: function( value ) {
		return access( this, function( value ) {
			var elem = this[ 0 ] || {},
				i = 0,
				l = this.length;

			if ( value === undefined && elem.nodeType === 1 ) {
				return elem.innerHTML;
			}

			// See if we can take a shortcut and just use innerHTML
			if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
				!wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {

				value = value.replace( rxhtmlTag, "<$1></$2>" );

				try {
					for ( ; i < l; i++ ) {
						elem = this[ i ] || {};

						// Remove element nodes and prevent memory leaks
						if ( elem.nodeType === 1 ) {
							jQuery.cleanData( getAll( elem, false ) );
							elem.innerHTML = value;
						}
					}

					elem = 0;

				// If using innerHTML throws an exception, use the fallback method
				} catch( e ) {}
			}

			if ( elem ) {
				this.empty().append( value );
			}
		}, null, value, arguments.length );
	},

	replaceWith: function() {
		var arg = arguments[ 0 ];

		// Make the changes, replacing each context element with the new content
		this.domManip( arguments, function( elem ) {
			arg = this.parentNode;

			jQuery.cleanData( getAll( this ) );

			if ( arg ) {
				arg.replaceChild( elem, this );
			}
		});

		// Force removal if there was no new content (e.g., from empty arguments)
		return arg && (arg.length || arg.nodeType) ? this : this.remove();
	},

	detach: function( selector ) {
		return this.remove( selector, true );
	},

	domManip: function( args, callback ) {

		// Flatten any nested arrays
		args = concat.apply( [], args );

		var fragment, first, scripts, hasScripts, node, doc,
			i = 0,
			l = this.length,
			set = this,
			iNoClone = l - 1,
			value = args[ 0 ],
			isFunction = jQuery.isFunction( value );

		// We can't cloneNode fragments that contain checked, in WebKit
		if ( isFunction ||
				( l > 1 && typeof value === "string" &&
					!support.checkClone && rchecked.test( value ) ) ) {
			return this.each(function( index ) {
				var self = set.eq( index );
				if ( isFunction ) {
					args[ 0 ] = value.call( this, index, self.html() );
				}
				self.domManip( args, callback );
			});
		}

		if ( l ) {
			fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this );
			first = fragment.firstChild;

			if ( fragment.childNodes.length === 1 ) {
				fragment = first;
			}

			if ( first ) {
				scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
				hasScripts = scripts.length;

				// Use the original fragment for the last item instead of the first because it can end up
				// being emptied incorrectly in certain situations (#8070).
				for ( ; i < l; i++ ) {
					node = fragment;

					if ( i !== iNoClone ) {
						node = jQuery.clone( node, true, true );

						// Keep references to cloned scripts for later restoration
						if ( hasScripts ) {
							// Support: QtWebKit
							// jQuery.merge because push.apply(_, arraylike) throws
							jQuery.merge( scripts, getAll( node, "script" ) );
						}
					}

					callback.call( this[ i ], node, i );
				}

				if ( hasScripts ) {
					doc = scripts[ scripts.length - 1 ].ownerDocument;

					// Reenable scripts
					jQuery.map( scripts, restoreScript );

					// Evaluate executable scripts on first document insertion
					for ( i = 0; i < hasScripts; i++ ) {
						node = scripts[ i ];
						if ( rscriptType.test( node.type || "" ) &&
							!data_priv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) {

							if ( node.src ) {
								// Optional AJAX dependency, but won't run scripts if not present
								if ( jQuery._evalUrl ) {
									jQuery._evalUrl( node.src );
								}
							} else {
								jQuery.globalEval( node.textContent.replace( rcleanScript, "" ) );
							}
						}
					}
				}
			}
		}

		return this;
	}
});

jQuery.each({
	appendTo: "append",
	prependTo: "prepend",
	insertBefore: "before",
	insertAfter: "after",
	replaceAll: "replaceWith"
}, function( name, original ) {
	jQuery.fn[ name ] = function( selector ) {
		var elems,
			ret = [],
			insert = jQuery( selector ),
			last = insert.length - 1,
			i = 0;

		for ( ; i <= last; i++ ) {
			elems = i === last ? this : this.clone( true );
			jQuery( insert[ i ] )[ original ]( elems );

			// Support: QtWebKit
			// .get() because push.apply(_, arraylike) throws
			push.apply( ret, elems.get() );
		}

		return this.pushStack( ret );
	};
});


var iframe,
	elemdisplay = {};

/**
 * Retrieve the actual display of a element
 * @param {String} name nodeName of the element
 * @param {Object} doc Document object
 */
// Called only from within defaultDisplay
function actualDisplay( name, doc ) {
	var style,
		elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),

		// getDefaultComputedStyle might be reliably used only on attached element
		display = window.getDefaultComputedStyle && ( style = window.getDefaultComputedStyle( elem[ 0 ] ) ) ?

			// Use of this method is a temporary fix (more like optmization) until something better comes along,
			// since it was removed from specification and supported only in FF
			style.display : jQuery.css( elem[ 0 ], "display" );

	// We don't have any data stored on the element,
	// so use "detach" method as fast way to get rid of the element
	elem.detach();

	return display;
}

/**
 * Try to determine the default display value of an element
 * @param {String} nodeName
 */
function defaultDisplay( nodeName ) {
	var doc = document,
		display = elemdisplay[ nodeName ];

	if ( !display ) {
		display = actualDisplay( nodeName, doc );

		// If the simple way fails, read from inside an iframe
		if ( display === "none" || !display ) {

			// Use the already-created iframe if possible
			iframe = (iframe || jQuery( "<iframe frameborder='0' width='0' height='0'/>" )).appendTo( doc.documentElement );

			// Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse
			doc = iframe[ 0 ].contentDocument;

			// Support: IE
			doc.write();
			doc.close();

			display = actualDisplay( nodeName, doc );
			iframe.detach();
		}

		// Store the correct default display
		elemdisplay[ nodeName ] = display;
	}

	return display;
}
var rmargin = (/^margin/);

var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );

var getStyles = function( elem ) {
		return elem.ownerDocument.defaultView.getComputedStyle( elem, null );
	};



function curCSS( elem, name, computed ) {
	var width, minWidth, maxWidth, ret,
		style = elem.style;

	computed = computed || getStyles( elem );

	// Support: IE9
	// getPropertyValue is only needed for .css('filter') in IE9, see #12537
	if ( computed ) {
		ret = computed.getPropertyValue( name ) || computed[ name ];
	}

	if ( computed ) {

		if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
			ret = jQuery.style( elem, name );
		}

		// Support: iOS < 6
		// A tribute to the "awesome hack by Dean Edwards"
		// iOS < 6 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels
		// this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
		if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {

			// Remember the original values
			width = style.width;
			minWidth = style.minWidth;
			maxWidth = style.maxWidth;

			// Put in the new values to get a computed value out
			style.minWidth = style.maxWidth = style.width = ret;
			ret = computed.width;

			// Revert the changed values
			style.width = width;
			style.minWidth = minWidth;
			style.maxWidth = maxWidth;
		}
	}

	return ret !== undefined ?
		// Support: IE
		// IE returns zIndex value as an integer.
		ret + "" :
		ret;
}


function addGetHookIf( conditionFn, hookFn ) {
	// Define the hook, we'll check on the first run if it's really needed.
	return {
		get: function() {
			if ( conditionFn() ) {
				// Hook not needed (or it's not possible to use it due to missing dependency),
				// remove it.
				// Since there are no other hooks for marginRight, remove the whole object.
				delete this.get;
				return;
			}

			// Hook needed; redefine it so that the support test is not executed again.

			return (this.get = hookFn).apply( this, arguments );
		}
	};
}


(function() {
	var pixelPositionVal, boxSizingReliableVal,
		docElem = document.documentElement,
		container = document.createElement( "div" ),
		div = document.createElement( "div" );

	if ( !div.style ) {
		return;
	}

	div.style.backgroundClip = "content-box";
	div.cloneNode( true ).style.backgroundClip = "";
	support.clearCloneStyle = div.style.backgroundClip === "content-box";

	container.style.cssText = "border:0;width:0;height:0;top:0;left:-9999px;margin-top:1px;" +
		"position:absolute";
	container.appendChild( div );

	// Executing both pixelPosition & boxSizingReliable tests require only one layout
	// so they're executed at the same time to save the second computation.
	function computePixelPositionAndBoxSizingReliable() {
		div.style.cssText =
			// Support: Firefox<29, Android 2.3
			// Vendor-prefix box-sizing
			"-webkit-box-sizing:border-box;-moz-box-sizing:border-box;" +
			"box-sizing:border-box;display:block;margin-top:1%;top:1%;" +
			"border:1px;padding:1px;width:4px;position:absolute";
		div.innerHTML = "";
		docElem.appendChild( container );

		var divStyle = window.getComputedStyle( div, null );
		pixelPositionVal = divStyle.top !== "1%";
		boxSizingReliableVal = divStyle.width === "4px";

		docElem.removeChild( container );
	}

	// Support: node.js jsdom
	// Don't assume that getComputedStyle is a property of the global object
	if ( window.getComputedStyle ) {
		jQuery.extend( support, {
			pixelPosition: function() {
				// This test is executed only once but we still do memoizing
				// since we can use the boxSizingReliable pre-computing.
				// No need to check if the test was already performed, though.
				computePixelPositionAndBoxSizingReliable();
				return pixelPositionVal;
			},
			boxSizingReliable: function() {
				if ( boxSizingReliableVal == null ) {
					computePixelPositionAndBoxSizingReliable();
				}
				return boxSizingReliableVal;
			},
			reliableMarginRight: function() {
				// Support: Android 2.3
				// Check if div with explicit width and no margin-right incorrectly
				// gets computed margin-right based on width of container. (#3333)
				// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
				// This support function is only executed once so no memoizing is needed.
				var ret,
					marginDiv = div.appendChild( document.createElement( "div" ) );

				// Reset CSS: box-sizing; display; margin; border; padding
				marginDiv.style.cssText = div.style.cssText =
					// Support: Firefox<29, Android 2.3
					// Vendor-prefix box-sizing
					"-webkit-box-sizing:content-box;-moz-box-sizing:content-box;" +
					"box-sizing:content-box;display:block;margin:0;border:0;padding:0";
				marginDiv.style.marginRight = marginDiv.style.width = "0";
				div.style.width = "1px";
				docElem.appendChild( container );

				ret = !parseFloat( window.getComputedStyle( marginDiv, null ).marginRight );

				docElem.removeChild( container );

				return ret;
			}
		});
	}
})();


// A method for quickly swapping in/out CSS properties to get correct calculations.
jQuery.swap = function( elem, options, callback, args ) {
	var ret, name,
		old = {};

	// Remember the old values, and insert the new ones
	for ( name in options ) {
		old[ name ] = elem.style[ name ];
		elem.style[ name ] = options[ name ];
	}

	ret = callback.apply( elem, args || [] );

	// Revert the old values
	for ( name in options ) {
		elem.style[ name ] = old[ name ];
	}

	return ret;
};


var
	// swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
	// see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
	rdisplayswap = /^(none|table(?!-c[ea]).+)/,
	rnumsplit = new RegExp( "^(" + pnum + ")(.*)$", "i" ),
	rrelNum = new RegExp( "^([+-])=(" + pnum + ")", "i" ),

	cssShow = { position: "absolute", visibility: "hidden", display: "block" },
	cssNormalTransform = {
		letterSpacing: "0",
		fontWeight: "400"
	},

	cssPrefixes = [ "Webkit", "O", "Moz", "ms" ];

// return a css property mapped to a potentially vendor prefixed property
function vendorPropName( style, name ) {

	// shortcut for names that are not vendor prefixed
	if ( name in style ) {
		return name;
	}

	// check for vendor prefixed names
	var capName = name[0].toUpperCase() + name.slice(1),
		origName = name,
		i = cssPrefixes.length;

	while ( i-- ) {
		name = cssPrefixes[ i ] + capName;
		if ( name in style ) {
			return name;
		}
	}

	return origName;
}

function setPositiveNumber( elem, value, subtract ) {
	var matches = rnumsplit.exec( value );
	return matches ?
		// Guard against undefined "subtract", e.g., when used as in cssHooks
		Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
		value;
}

function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
	var i = extra === ( isBorderBox ? "border" : "content" ) ?
		// If we already have the right measurement, avoid augmentation
		4 :
		// Otherwise initialize for horizontal or vertical properties
		name === "width" ? 1 : 0,

		val = 0;

	for ( ; i < 4; i += 2 ) {
		// both box models exclude margin, so add it if we want it
		if ( extra === "margin" ) {
			val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
		}

		if ( isBorderBox ) {
			// border-box includes padding, so remove it if we want content
			if ( extra === "content" ) {
				val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
			}

			// at this point, extra isn't border nor margin, so remove border
			if ( extra !== "margin" ) {
				val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
			}
		} else {
			// at this point, extra isn't content, so add padding
			val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );

			// at this point, extra isn't content nor padding, so add border
			if ( extra !== "padding" ) {
				val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
			}
		}
	}

	return val;
}

function getWidthOrHeight( elem, name, extra ) {

	// Start with offset property, which is equivalent to the border-box value
	var valueIsBorderBox = true,
		val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
		styles = getStyles( elem ),
		isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box";

	// some non-html elements return undefined for offsetWidth, so check for null/undefined
	// svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
	// MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
	if ( val <= 0 || val == null ) {
		// Fall back to computed then uncomputed css if necessary
		val = curCSS( elem, name, styles );
		if ( val < 0 || val == null ) {
			val = elem.style[ name ];
		}

		// Computed unit is not pixels. Stop here and return.
		if ( rnumnonpx.test(val) ) {
			return val;
		}

		// we need the check for style in case a browser which returns unreliable values
		// for getComputedStyle silently falls back to the reliable elem.style
		valueIsBorderBox = isBorderBox &&
			( support.boxSizingReliable() || val === elem.style[ name ] );

		// Normalize "", auto, and prepare for extra
		val = parseFloat( val ) || 0;
	}

	// use the active box-sizing model to add/subtract irrelevant styles
	return ( val +
		augmentWidthOrHeight(
			elem,
			name,
			extra || ( isBorderBox ? "border" : "content" ),
			valueIsBorderBox,
			styles
		)
	) + "px";
}

function showHide( elements, show ) {
	var display, elem, hidden,
		values = [],
		index = 0,
		length = elements.length;

	for ( ; index < length; index++ ) {
		elem = elements[ index ];
		if ( !elem.style ) {
			continue;
		}

		values[ index ] = data_priv.get( elem, "olddisplay" );
		display = elem.style.display;
		if ( show ) {
			// Reset the inline display of this element to learn if it is
			// being hidden by cascaded rules or not
			if ( !values[ index ] && display === "none" ) {
				elem.style.display = "";
			}

			// Set elements which have been overridden with display: none
			// in a stylesheet to whatever the default browser style is
			// for such an element
			if ( elem.style.display === "" && isHidden( elem ) ) {
				values[ index ] = data_priv.access( elem, "olddisplay", defaultDisplay(elem.nodeName) );
			}
		} else {
			hidden = isHidden( elem );

			if ( display !== "none" || !hidden ) {
				data_priv.set( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) );
			}
		}
	}

	// Set the display of most of the elements in a second loop
	// to avoid the constant reflow
	for ( index = 0; index < length; index++ ) {
		elem = elements[ index ];
		if ( !elem.style ) {
			continue;
		}
		if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
			elem.style.display = show ? values[ index ] || "" : "none";
		}
	}

	return elements;
}

jQuery.extend({
	// Add in style property hooks for overriding the default
	// behavior of getting and setting a style property
	cssHooks: {
		opacity: {
			get: function( elem, computed ) {
				if ( computed ) {
					// We should always get a number back from opacity
					var ret = curCSS( elem, "opacity" );
					return ret === "" ? "1" : ret;
				}
			}
		}
	},

	// Don't automatically add "px" to these possibly-unitless properties
	cssNumber: {
		"columnCount": true,
		"fillOpacity": true,
		"flexGrow": true,
		"flexShrink": true,
		"fontWeight": true,
		"lineHeight": true,
		"opacity": true,
		"order": true,
		"orphans": true,
		"widows": true,
		"zIndex": true,
		"zoom": true
	},

	// Add in properties whose names you wish to fix before
	// setting or getting the value
	cssProps: {
		// normalize float css property
		"float": "cssFloat"
	},

	// Get and set the style property on a DOM Node
	style: function( elem, name, value, extra ) {
		// Don't set styles on text and comment nodes
		if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
			return;
		}

		// Make sure that we're working with the right name
		var ret, type, hooks,
			origName = jQuery.camelCase( name ),
			style = elem.style;

		name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );

		// gets hook for the prefixed version
		// followed by the unprefixed version
		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];

		// Check if we're setting a value
		if ( value !== undefined ) {
			type = typeof value;

			// convert relative number strings (+= or -=) to relative numbers. #7345
			if ( type === "string" && (ret = rrelNum.exec( value )) ) {
				value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
				// Fixes bug #9237
				type = "number";
			}

			// Make sure that null and NaN values aren't set. See: #7116
			if ( value == null || value !== value ) {
				return;
			}

			// If a number was passed in, add 'px' to the (except for certain CSS properties)
			if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
				value += "px";
			}

			// Fixes #8908, it can be done more correctly by specifying setters in cssHooks,
			// but it would mean to define eight (for every problematic property) identical functions
			if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
				style[ name ] = "inherit";
			}

			// If a hook was provided, use that value, otherwise just set the specified value
			if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {
				style[ name ] = value;
			}

		} else {
			// If a hook was provided get the non-computed value from there
			if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
				return ret;
			}

			// Otherwise just get the value from the style object
			return style[ name ];
		}
	},

	css: function( elem, name, extra, styles ) {
		var val, num, hooks,
			origName = jQuery.camelCase( name );

		// Make sure that we're working with the right name
		name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );

		// gets hook for the prefixed version
		// followed by the unprefixed version
		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];

		// If a hook was provided get the computed value from there
		if ( hooks && "get" in hooks ) {
			val = hooks.get( elem, true, extra );
		}

		// Otherwise, if a way to get the computed value exists, use that
		if ( val === undefined ) {
			val = curCSS( elem, name, styles );
		}

		//convert "normal" to computed value
		if ( val === "normal" && name in cssNormalTransform ) {
			val = cssNormalTransform[ name ];
		}

		// Return, converting to number if forced or a qualifier was provided and val looks numeric
		if ( extra === "" || extra ) {
			num = parseFloat( val );
			return extra === true || jQuery.isNumeric( num ) ? num || 0 : val;
		}
		return val;
	}
});

jQuery.each([ "height", "width" ], function( i, name ) {
	jQuery.cssHooks[ name ] = {
		get: function( elem, computed, extra ) {
			if ( computed ) {
				// certain elements can have dimension info if we invisibly show them
				// however, it must have a current display style that would benefit from this
				return rdisplayswap.test( jQuery.css( elem, "display" ) ) && elem.offsetWidth === 0 ?
					jQuery.swap( elem, cssShow, function() {
						return getWidthOrHeight( elem, name, extra );
					}) :
					getWidthOrHeight( elem, name, extra );
			}
		},

		set: function( elem, value, extra ) {
			var styles = extra && getStyles( elem );
			return setPositiveNumber( elem, value, extra ?
				augmentWidthOrHeight(
					elem,
					name,
					extra,
					jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
					styles
				) : 0
			);
		}
	};
});

// Support: Android 2.3
jQuery.cssHooks.marginRight = addGetHookIf( support.reliableMarginRight,
	function( elem, computed ) {
		if ( computed ) {
			// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
			// Work around by temporarily setting element display to inline-block
			return jQuery.swap( elem, { "display": "inline-block" },
				curCSS, [ elem, "marginRight" ] );
		}
	}
);

// These hooks are used by animate to expand properties
jQuery.each({
	margin: "",
	padding: "",
	border: "Width"
}, function( prefix, suffix ) {
	jQuery.cssHooks[ prefix + suffix ] = {
		expand: function( value ) {
			var i = 0,
				expanded = {},

				// assumes a single number if not a string
				parts = typeof value === "string" ? value.split(" ") : [ value ];

			for ( ; i < 4; i++ ) {
				expanded[ prefix + cssExpand[ i ] + suffix ] =
					parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
			}

			return expanded;
		}
	};

	if ( !rmargin.test( prefix ) ) {
		jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
	}
});

jQuery.fn.extend({
	css: function( name, value ) {
		return access( this, function( elem, name, value ) {
			var styles, len,
				map = {},
				i = 0;

			if ( jQuery.isArray( name ) ) {
				styles = getStyles( elem );
				len = name.length;

				for ( ; i < len; i++ ) {
					map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
				}

				return map;
			}

			return value !== undefined ?
				jQuery.style( elem, name, value ) :
				jQuery.css( elem, name );
		}, name, value, arguments.length > 1 );
	},
	show: function() {
		return showHide( this, true );
	},
	hide: function() {
		return showHide( this );
	},
	toggle: function( state ) {
		if ( typeof state === "boolean" ) {
			return state ? this.show() : this.hide();
		}

		return this.each(function() {
			if ( isHidden( this ) ) {
				jQuery( this ).show();
			} else {
				jQuery( this ).hide();
			}
		});
	}
});


function Tween( elem, options, prop, end, easing ) {
	return new Tween.prototype.init( elem, options, prop, end, easing );
}
jQuery.Tween = Tween;

Tween.prototype = {
	constructor: Tween,
	init: function( elem, options, prop, end, easing, unit ) {
		this.elem = elem;
		this.prop = prop;
		this.easing = easing || "swing";
		this.options = options;
		this.start = this.now = this.cur();
		this.end = end;
		this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
	},
	cur: function() {
		var hooks = Tween.propHooks[ this.prop ];

		return hooks && hooks.get ?
			hooks.get( this ) :
			Tween.propHooks._default.get( this );
	},
	run: function( percent ) {
		var eased,
			hooks = Tween.propHooks[ this.prop ];

		if ( this.options.duration ) {
			this.pos = eased = jQuery.easing[ this.easing ](
				percent, this.options.duration * percent, 0, 1, this.options.duration
			);
		} else {
			this.pos = eased = percent;
		}
		this.now = ( this.end - this.start ) * eased + this.start;

		if ( this.options.step ) {
			this.options.step.call( this.elem, this.now, this );
		}

		if ( hooks && hooks.set ) {
			hooks.set( this );
		} else {
			Tween.propHooks._default.set( this );
		}
		return this;
	}
};

Tween.prototype.init.prototype = Tween.prototype;

Tween.propHooks = {
	_default: {
		get: function( tween ) {
			var result;

			if ( tween.elem[ tween.prop ] != null &&
				(!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
				return tween.elem[ tween.prop ];
			}

			// passing an empty string as a 3rd parameter to .css will automatically
			// attempt a parseFloat and fallback to a string if the parse fails
			// so, simple values such as "10px" are parsed to Float.
			// complex values such as "rotate(1rad)" are returned as is.
			result = jQuery.css( tween.elem, tween.prop, "" );
			// Empty strings, null, undefined and "auto" are converted to 0.
			return !result || result === "auto" ? 0 : result;
		},
		set: function( tween ) {
			// use step hook for back compat - use cssHook if its there - use .style if its
			// available and use plain properties where available
			if ( jQuery.fx.step[ tween.prop ] ) {
				jQuery.fx.step[ tween.prop ]( tween );
			} else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {
				jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
			} else {
				tween.elem[ tween.prop ] = tween.now;
			}
		}
	}
};

// Support: IE9
// Panic based approach to setting things on disconnected nodes

Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
	set: function( tween ) {
		if ( tween.elem.nodeType && tween.elem.parentNode ) {
			tween.elem[ tween.prop ] = tween.now;
		}
	}
};

jQuery.easing = {
	linear: function( p ) {
		return p;
	},
	swing: function( p ) {
		return 0.5 - Math.cos( p * Math.PI ) / 2;
	}
};

jQuery.fx = Tween.prototype.init;

// Back Compat <1.8 extension point
jQuery.fx.step = {};




var
	fxNow, timerId,
	rfxtypes = /^(?:toggle|show|hide)$/,
	rfxnum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ),
	rrun = /queueHooks$/,
	animationPrefilters = [ defaultPrefilter ],
	tweeners = {
		"*": [ function( prop, value ) {
			var tween = this.createTween( prop, value ),
				target = tween.cur(),
				parts = rfxnum.exec( value ),
				unit = parts && parts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),

				// Starting value computation is required for potential unit mismatches
				start = ( jQuery.cssNumber[ prop ] || unit !== "px" && +target ) &&
					rfxnum.exec( jQuery.css( tween.elem, prop ) ),
				scale = 1,
				maxIterations = 20;

			if ( start && start[ 3 ] !== unit ) {
				// Trust units reported by jQuery.css
				unit = unit || start[ 3 ];

				// Make sure we update the tween properties later on
				parts = parts || [];

				// Iteratively approximate from a nonzero starting point
				start = +target || 1;

				do {
					// If previous iteration zeroed out, double until we get *something*
					// Use a string for doubling factor so we don't accidentally see scale as unchanged below
					scale = scale || ".5";

					// Adjust and apply
					start = start / scale;
					jQuery.style( tween.elem, prop, start + unit );

				// Update scale, tolerating zero or NaN from tween.cur()
				// And breaking the loop if scale is unchanged or perfect, or if we've just had enough
				} while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );
			}

			// Update tween properties
			if ( parts ) {
				start = tween.start = +start || +target || 0;
				tween.unit = unit;
				// If a +=/-= token was provided, we're doing a relative animation
				tween.end = parts[ 1 ] ?
					start + ( parts[ 1 ] + 1 ) * parts[ 2 ] :
					+parts[ 2 ];
			}

			return tween;
		} ]
	};

// Animations created synchronously will run synchronously
function createFxNow() {
	setTimeout(function() {
		fxNow = undefined;
	});
	return ( fxNow = jQuery.now() );
}

// Generate parameters to create a standard animation
function genFx( type, includeWidth ) {
	var which,
		i = 0,
		attrs = { height: type };

	// if we include width, step value is 1 to do all cssExpand values,
	// if we don't include width, step value is 2 to skip over Left and Right
	includeWidth = includeWidth ? 1 : 0;
	for ( ; i < 4 ; i += 2 - includeWidth ) {
		which = cssExpand[ i ];
		attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
	}

	if ( includeWidth ) {
		attrs.opacity = attrs.width = type;
	}

	return attrs;
}

function createTween( value, prop, animation ) {
	var tween,
		collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
		index = 0,
		length = collection.length;
	for ( ; index < length; index++ ) {
		if ( (tween = collection[ index ].call( animation, prop, value )) ) {

			// we're done with this property
			return tween;
		}
	}
}

function defaultPrefilter( elem, props, opts ) {
	/* jshint validthis: true */
	var prop, value, toggle, tween, hooks, oldfire, display, checkDisplay,
		anim = this,
		orig = {},
		style = elem.style,
		hidden = elem.nodeType && isHidden( elem ),
		dataShow = data_priv.get( elem, "fxshow" );

	// handle queue: false promises
	if ( !opts.queue ) {
		hooks = jQuery._queueHooks( elem, "fx" );
		if ( hooks.unqueued == null ) {
			hooks.unqueued = 0;
			oldfire = hooks.empty.fire;
			hooks.empty.fire = function() {
				if ( !hooks.unqueued ) {
					oldfire();
				}
			};
		}
		hooks.unqueued++;

		anim.always(function() {
			// doing this makes sure that the complete handler will be called
			// before this completes
			anim.always(function() {
				hooks.unqueued--;
				if ( !jQuery.queue( elem, "fx" ).length ) {
					hooks.empty.fire();
				}
			});
		});
	}

	// height/width overflow pass
	if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
		// Make sure that nothing sneaks out
		// Record all 3 overflow attributes because IE9-10 do not
		// change the overflow attribute when overflowX and
		// overflowY are set to the same value
		opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];

		// Set display property to inline-block for height/width
		// animations on inline elements that are having width/height animated
		display = jQuery.css( elem, "display" );

		// Test default display if display is currently "none"
		checkDisplay = display === "none" ?
			data_priv.get( elem, "olddisplay" ) || defaultDisplay( elem.nodeName ) : display;

		if ( checkDisplay === "inline" && jQuery.css( elem, "float" ) === "none" ) {
			style.display = "inline-block";
		}
	}

	if ( opts.overflow ) {
		style.overflow = "hidden";
		anim.always(function() {
			style.overflow = opts.overflow[ 0 ];
			style.overflowX = opts.overflow[ 1 ];
			style.overflowY = opts.overflow[ 2 ];
		});
	}

	// show/hide pass
	for ( prop in props ) {
		value = props[ prop ];
		if ( rfxtypes.exec( value ) ) {
			delete props[ prop ];
			toggle = toggle || value === "toggle";
			if ( value === ( hidden ? "hide" : "show" ) ) {

				// If there is dataShow left over from a stopped hide or show and we are going to proceed with show, we should pretend to be hidden
				if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
					hidden = true;
				} else {
					continue;
				}
			}
			orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );

		// Any non-fx value stops us from restoring the original display value
		} else {
			display = undefined;
		}
	}

	if ( !jQuery.isEmptyObject( orig ) ) {
		if ( dataShow ) {
			if ( "hidden" in dataShow ) {
				hidden = dataShow.hidden;
			}
		} else {
			dataShow = data_priv.access( elem, "fxshow", {} );
		}

		// store state if its toggle - enables .stop().toggle() to "reverse"
		if ( toggle ) {
			dataShow.hidden = !hidden;
		}
		if ( hidden ) {
			jQuery( elem ).show();
		} else {
			anim.done(function() {
				jQuery( elem ).hide();
			});
		}
		anim.done(function() {
			var prop;

			data_priv.remove( elem, "fxshow" );
			for ( prop in orig ) {
				jQuery.style( elem, prop, orig[ prop ] );
			}
		});
		for ( prop in orig ) {
			tween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );

			if ( !( prop in dataShow ) ) {
				dataShow[ prop ] = tween.start;
				if ( hidden ) {
					tween.end = tween.start;
					tween.start = prop === "width" || prop === "height" ? 1 : 0;
				}
			}
		}

	// If this is a noop like .hide().hide(), restore an overwritten display value
	} else if ( (display === "none" ? defaultDisplay( elem.nodeName ) : display) === "inline" ) {
		style.display = display;
	}
}

function propFilter( props, specialEasing ) {
	var index, name, easing, value, hooks;

	// camelCase, specialEasing and expand cssHook pass
	for ( index in props ) {
		name = jQuery.camelCase( index );
		easing = specialEasing[ name ];
		value = props[ index ];
		if ( jQuery.isArray( value ) ) {
			easing = value[ 1 ];
			value = props[ index ] = value[ 0 ];
		}

		if ( index !== name ) {
			props[ name ] = value;
			delete props[ index ];
		}

		hooks = jQuery.cssHooks[ name ];
		if ( hooks && "expand" in hooks ) {
			value = hooks.expand( value );
			delete props[ name ];

			// not quite $.extend, this wont overwrite keys already present.
			// also - reusing 'index' from above because we have the correct "name"
			for ( index in value ) {
				if ( !( index in props ) ) {
					props[ index ] = value[ index ];
					specialEasing[ index ] = easing;
				}
			}
		} else {
			specialEasing[ name ] = easing;
		}
	}
}

function Animation( elem, properties, options ) {
	var result,
		stopped,
		index = 0,
		length = animationPrefilters.length,
		deferred = jQuery.Deferred().always( function() {
			// don't match elem in the :animated selector
			delete tick.elem;
		}),
		tick = function() {
			if ( stopped ) {
				return false;
			}
			var currentTime = fxNow || createFxNow(),
				remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
				// archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)
				temp = remaining / animation.duration || 0,
				percent = 1 - temp,
				index = 0,
				length = animation.tweens.length;

			for ( ; index < length ; index++ ) {
				animation.tweens[ index ].run( percent );
			}

			deferred.notifyWith( elem, [ animation, percent, remaining ]);

			if ( percent < 1 && length ) {
				return remaining;
			} else {
				deferred.resolveWith( elem, [ animation ] );
				return false;
			}
		},
		animation = deferred.promise({
			elem: elem,
			props: jQuery.extend( {}, properties ),
			opts: jQuery.extend( true, { specialEasing: {} }, options ),
			originalProperties: properties,
			originalOptions: options,
			startTime: fxNow || createFxNow(),
			duration: options.duration,
			tweens: [],
			createTween: function( prop, end ) {
				var tween = jQuery.Tween( elem, animation.opts, prop, end,
						animation.opts.specialEasing[ prop ] || animation.opts.easing );
				animation.tweens.push( tween );
				return tween;
			},
			stop: function( gotoEnd ) {
				var index = 0,
					// if we are going to the end, we want to run all the tweens
					// otherwise we skip this part
					length = gotoEnd ? animation.tweens.length : 0;
				if ( stopped ) {
					return this;
				}
				stopped = true;
				for ( ; index < length ; index++ ) {
					animation.tweens[ index ].run( 1 );
				}

				// resolve when we played the last frame
				// otherwise, reject
				if ( gotoEnd ) {
					deferred.resolveWith( elem, [ animation, gotoEnd ] );
				} else {
					deferred.rejectWith( elem, [ animation, gotoEnd ] );
				}
				return this;
			}
		}),
		props = animation.props;

	propFilter( props, animation.opts.specialEasing );

	for ( ; index < length ; index++ ) {
		result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
		if ( result ) {
			return result;
		}
	}

	jQuery.map( props, createTween, animation );

	if ( jQuery.isFunction( animation.opts.start ) ) {
		animation.opts.start.call( elem, animation );
	}

	jQuery.fx.timer(
		jQuery.extend( tick, {
			elem: elem,
			anim: animation,
			queue: animation.opts.queue
		})
	);

	// attach callbacks from options
	return animation.progress( animation.opts.progress )
		.done( animation.opts.done, animation.opts.complete )
		.fail( animation.opts.fail )
		.always( animation.opts.always );
}

jQuery.Animation = jQuery.extend( Animation, {

	tweener: function( props, callback ) {
		if ( jQuery.isFunction( props ) ) {
			callback = props;
			props = [ "*" ];
		} else {
			props = props.split(" ");
		}

		var prop,
			index = 0,
			length = props.length;

		for ( ; index < length ; index++ ) {
			prop = props[ index ];
			tweeners[ prop ] = tweeners[ prop ] || [];
			tweeners[ prop ].unshift( callback );
		}
	},

	prefilter: function( callback, prepend ) {
		if ( prepend ) {
			animationPrefilters.unshift( callback );
		} else {
			animationPrefilters.push( callback );
		}
	}
});

jQuery.speed = function( speed, easing, fn ) {
	var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
		complete: fn || !fn && easing ||
			jQuery.isFunction( speed ) && speed,
		duration: speed,
		easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
	};

	opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
		opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;

	// normalize opt.queue - true/undefined/null -> "fx"
	if ( opt.queue == null || opt.queue === true ) {
		opt.queue = "fx";
	}

	// Queueing
	opt.old = opt.complete;

	opt.complete = function() {
		if ( jQuery.isFunction( opt.old ) ) {
			opt.old.call( this );
		}

		if ( opt.queue ) {
			jQuery.dequeue( this, opt.queue );
		}
	};

	return opt;
};

jQuery.fn.extend({
	fadeTo: function( speed, to, easing, callback ) {

		// show any hidden elements after setting opacity to 0
		return this.filter( isHidden ).css( "opacity", 0 ).show()

			// animate to the value specified
			.end().animate({ opacity: to }, speed, easing, callback );
	},
	animate: function( prop, speed, easing, callback ) {
		var empty = jQuery.isEmptyObject( prop ),
			optall = jQuery.speed( speed, easing, callback ),
			doAnimation = function() {
				// Operate on a copy of prop so per-property easing won't be lost
				var anim = Animation( this, jQuery.extend( {}, prop ), optall );

				// Empty animations, or finishing resolves immediately
				if ( empty || data_priv.get( this, "finish" ) ) {
					anim.stop( true );
				}
			};
			doAnimation.finish = doAnimation;

		return empty || optall.queue === false ?
			this.each( doAnimation ) :
			this.queue( optall.queue, doAnimation );
	},
	stop: function( type, clearQueue, gotoEnd ) {
		var stopQueue = function( hooks ) {
			var stop = hooks.stop;
			delete hooks.stop;
			stop( gotoEnd );
		};

		if ( typeof type !== "string" ) {
			gotoEnd = clearQueue;
			clearQueue = type;
			type = undefined;
		}
		if ( clearQueue && type !== false ) {
			this.queue( type || "fx", [] );
		}

		return this.each(function() {
			var dequeue = true,
				index = type != null && type + "queueHooks",
				timers = jQuery.timers,
				data = data_priv.get( this );

			if ( index ) {
				if ( data[ index ] && data[ index ].stop ) {
					stopQueue( data[ index ] );
				}
			} else {
				for ( index in data ) {
					if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
						stopQueue( data[ index ] );
					}
				}
			}

			for ( index = timers.length; index--; ) {
				if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
					timers[ index ].anim.stop( gotoEnd );
					dequeue = false;
					timers.splice( index, 1 );
				}
			}

			// start the next in the queue if the last step wasn't forced
			// timers currently will call their complete callbacks, which will dequeue
			// but only if they were gotoEnd
			if ( dequeue || !gotoEnd ) {
				jQuery.dequeue( this, type );
			}
		});
	},
	finish: function( type ) {
		if ( type !== false ) {
			type = type || "fx";
		}
		return this.each(function() {
			var index,
				data = data_priv.get( this ),
				queue = data[ type + "queue" ],
				hooks = data[ type + "queueHooks" ],
				timers = jQuery.timers,
				length = queue ? queue.length : 0;

			// enable finishing flag on private data
			data.finish = true;

			// empty the queue first
			jQuery.queue( this, type, [] );

			if ( hooks && hooks.stop ) {
				hooks.stop.call( this, true );
			}

			// look for any active animations, and finish them
			for ( index = timers.length; index--; ) {
				if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
					timers[ index ].anim.stop( true );
					timers.splice( index, 1 );
				}
			}

			// look for any animations in the old queue and finish them
			for ( index = 0; index < length; index++ ) {
				if ( queue[ index ] && queue[ index ].finish ) {
					queue[ index ].finish.call( this );
				}
			}

			// turn off finishing flag
			delete data.finish;
		});
	}
});

jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
	var cssFn = jQuery.fn[ name ];
	jQuery.fn[ name ] = function( speed, easing, callback ) {
		return speed == null || typeof speed === "boolean" ?
			cssFn.apply( this, arguments ) :
			this.animate( genFx( name, true ), speed, easing, callback );
	};
});

// Generate shortcuts for custom animations
jQuery.each({
	slideDown: genFx("show"),
	slideUp: genFx("hide"),
	slideToggle: genFx("toggle"),
	fadeIn: { opacity: "show" },
	fadeOut: { opacity: "hide" },
	fadeToggle: { opacity: "toggle" }
}, function( name, props ) {
	jQuery.fn[ name ] = function( speed, easing, callback ) {
		return this.animate( props, speed, easing, callback );
	};
});

jQuery.timers = [];
jQuery.fx.tick = function() {
	var timer,
		i = 0,
		timers = jQuery.timers;

	fxNow = jQuery.now();

	for ( ; i < timers.length; i++ ) {
		timer = timers[ i ];
		// Checks the timer has not already been removed
		if ( !timer() && timers[ i ] === timer ) {
			timers.splice( i--, 1 );
		}
	}

	if ( !timers.length ) {
		jQuery.fx.stop();
	}
	fxNow = undefined;
};

jQuery.fx.timer = function( timer ) {
	jQuery.timers.push( timer );
	if ( timer() ) {
		jQuery.fx.start();
	} else {
		jQuery.timers.pop();
	}
};

jQuery.fx.interval = 13;

jQuery.fx.start = function() {
	if ( !timerId ) {
		timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
	}
};

jQuery.fx.stop = function() {
	clearInterval( timerId );
	timerId = null;
};

jQuery.fx.speeds = {
	slow: 600,
	fast: 200,
	// Default speed
	_default: 400
};


// Based off of the plugin by Clint Helfers, with permission.
// http://blindsignals.com/index.php/2009/07/jquery-delay/
jQuery.fn.delay = function( time, type ) {
	time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
	type = type || "fx";

	return this.queue( type, function( next, hooks ) {
		var timeout = setTimeout( next, time );
		hooks.stop = function() {
			clearTimeout( timeout );
		};
	});
};


(function() {
	var input = document.createElement( "input" ),
		select = document.createElement( "select" ),
		opt = select.appendChild( document.createElement( "option" ) );

	input.type = "checkbox";

	// Support: iOS 5.1, Android 4.x, Android 2.3
	// Check the default checkbox/radio value ("" on old WebKit; "on" elsewhere)
	support.checkOn = input.value !== "";

	// Must access the parent to make an option select properly
	// Support: IE9, IE10
	support.optSelected = opt.selected;

	// Make sure that the options inside disabled selects aren't marked as disabled
	// (WebKit marks them as disabled)
	select.disabled = true;
	support.optDisabled = !opt.disabled;

	// Check if an input maintains its value after becoming a radio
	// Support: IE9, IE10
	input = document.createElement( "input" );
	input.value = "t";
	input.type = "radio";
	support.radioValue = input.value === "t";
})();


var nodeHook, boolHook,
	attrHandle = jQuery.expr.attrHandle;

jQuery.fn.extend({
	attr: function( name, value ) {
		return access( this, jQuery.attr, name, value, arguments.length > 1 );
	},

	removeAttr: function( name ) {
		return this.each(function() {
			jQuery.removeAttr( this, name );
		});
	}
});

jQuery.extend({
	attr: function( elem, name, value ) {
		var hooks, ret,
			nType = elem.nodeType;

		// don't get/set attributes on text, comment and attribute nodes
		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
			return;
		}

		// Fallback to prop when attributes are not supported
		if ( typeof elem.getAttribute === strundefined ) {
			return jQuery.prop( elem, name, value );
		}

		// All attributes are lowercase
		// Grab necessary hook if one is defined
		if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
			name = name.toLowerCase();
			hooks = jQuery.attrHooks[ name ] ||
				( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );
		}

		if ( value !== undefined ) {

			if ( value === null ) {
				jQuery.removeAttr( elem, name );

			} else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
				return ret;

			} else {
				elem.setAttribute( name, value + "" );
				return value;
			}

		} else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
			return ret;

		} else {
			ret = jQuery.find.attr( elem, name );

			// Non-existent attributes return null, we normalize to undefined
			return ret == null ?
				undefined :
				ret;
		}
	},

	removeAttr: function( elem, value ) {
		var name, propName,
			i = 0,
			attrNames = value && value.match( rnotwhite );

		if ( attrNames && elem.nodeType === 1 ) {
			while ( (name = attrNames[i++]) ) {
				propName = jQuery.propFix[ name ] || name;

				// Boolean attributes get special treatment (#10870)
				if ( jQuery.expr.match.bool.test( name ) ) {
					// Set corresponding property to false
					elem[ propName ] = false;
				}

				elem.removeAttribute( name );
			}
		}
	},

	attrHooks: {
		type: {
			set: function( elem, value ) {
				if ( !support.radioValue && value === "radio" &&
					jQuery.nodeName( elem, "input" ) ) {
					// Setting the type on a radio button after the value resets the value in IE6-9
					// Reset value to default in case type is set after value during creation
					var val = elem.value;
					elem.setAttribute( "type", value );
					if ( val ) {
						elem.value = val;
					}
					return value;
				}
			}
		}
	}
});

// Hooks for boolean attributes
boolHook = {
	set: function( elem, value, name ) {
		if ( value === false ) {
			// Remove boolean attributes when set to false
			jQuery.removeAttr( elem, name );
		} else {
			elem.setAttribute( name, name );
		}
		return name;
	}
};
jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) {
	var getter = attrHandle[ name ] || jQuery.find.attr;

	attrHandle[ name ] = function( elem, name, isXML ) {
		var ret, handle;
		if ( !isXML ) {
			// Avoid an infinite loop by temporarily removing this function from the getter
			handle = attrHandle[ name ];
			attrHandle[ name ] = ret;
			ret = getter( elem, name, isXML ) != null ?
				name.toLowerCase() :
				null;
			attrHandle[ name ] = handle;
		}
		return ret;
	};
});




var rfocusable = /^(?:input|select|textarea|button)$/i;

jQuery.fn.extend({
	prop: function( name, value ) {
		return access( this, jQuery.prop, name, value, arguments.length > 1 );
	},

	removeProp: function( name ) {
		return this.each(function() {
			delete this[ jQuery.propFix[ name ] || name ];
		});
	}
});

jQuery.extend({
	propFix: {
		"for": "htmlFor",
		"class": "className"
	},

	prop: function( elem, name, value ) {
		var ret, hooks, notxml,
			nType = elem.nodeType;

		// don't get/set properties on text, comment and attribute nodes
		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
			return;
		}

		notxml = nType !== 1 || !jQuery.isXMLDoc( elem );

		if ( notxml ) {
			// Fix name and attach hooks
			name = jQuery.propFix[ name ] || name;
			hooks = jQuery.propHooks[ name ];
		}

		if ( value !== undefined ) {
			return hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ?
				ret :
				( elem[ name ] = value );

		} else {
			return hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ?
				ret :
				elem[ name ];
		}
	},

	propHooks: {
		tabIndex: {
			get: function( elem ) {
				return elem.hasAttribute( "tabindex" ) || rfocusable.test( elem.nodeName ) || elem.href ?
					elem.tabIndex :
					-1;
			}
		}
	}
});

// Support: IE9+
// Selectedness for an option in an optgroup can be inaccurate
if ( !support.optSelected ) {
	jQuery.propHooks.selected = {
		get: function( elem ) {
			var parent = elem.parentNode;
			if ( parent && parent.parentNode ) {
				parent.parentNode.selectedIndex;
			}
			return null;
		}
	};
}

jQuery.each([
	"tabIndex",
	"readOnly",
	"maxLength",
	"cellSpacing",
	"cellPadding",
	"rowSpan",
	"colSpan",
	"useMap",
	"frameBorder",
	"contentEditable"
], function() {
	jQuery.propFix[ this.toLowerCase() ] = this;
});




var rclass = /[\t\r\n\f]/g;

jQuery.fn.extend({
	addClass: function( value ) {
		var classes, elem, cur, clazz, j, finalValue,
			proceed = typeof value === "string" && value,
			i = 0,
			len = this.length;

		if ( jQuery.isFunction( value ) ) {
			return this.each(function( j ) {
				jQuery( this ).addClass( value.call( this, j, this.className ) );
			});
		}

		if ( proceed ) {
			// The disjunction here is for better compressibility (see removeClass)
			classes = ( value || "" ).match( rnotwhite ) || [];

			for ( ; i < len; i++ ) {
				elem = this[ i ];
				cur = elem.nodeType === 1 && ( elem.className ?
					( " " + elem.className + " " ).replace( rclass, " " ) :
					" "
				);

				if ( cur ) {
					j = 0;
					while ( (clazz = classes[j++]) ) {
						if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
							cur += clazz + " ";
						}
					}

					// only assign if different to avoid unneeded rendering.
					finalValue = jQuery.trim( cur );
					if ( elem.className !== finalValue ) {
						elem.className = finalValue;
					}
				}
			}
		}

		return this;
	},

	removeClass: function( value ) {
		var classes, elem, cur, clazz, j, finalValue,
			proceed = arguments.length === 0 || typeof value === "string" && value,
			i = 0,
			len = this.length;

		if ( jQuery.isFunction( value ) ) {
			return this.each(function( j ) {
				jQuery( this ).removeClass( value.call( this, j, this.className ) );
			});
		}
		if ( proceed ) {
			classes = ( value || "" ).match( rnotwhite ) || [];

			for ( ; i < len; i++ ) {
				elem = this[ i ];
				// This expression is here for better compressibility (see addClass)
				cur = elem.nodeType === 1 && ( elem.className ?
					( " " + elem.className + " " ).replace( rclass, " " ) :
					""
				);

				if ( cur ) {
					j = 0;
					while ( (clazz = classes[j++]) ) {
						// Remove *all* instances
						while ( cur.indexOf( " " + clazz + " " ) >= 0 ) {
							cur = cur.replace( " " + clazz + " ", " " );
						}
					}

					// only assign if different to avoid unneeded rendering.
					finalValue = value ? jQuery.trim( cur ) : "";
					if ( elem.className !== finalValue ) {
						elem.className = finalValue;
					}
				}
			}
		}

		return this;
	},

	toggleClass: function( value, stateVal ) {
		var type = typeof value;

		if ( typeof stateVal === "boolean" && type === "string" ) {
			return stateVal ? this.addClass( value ) : this.removeClass( value );
		}

		if ( jQuery.isFunction( value ) ) {
			return this.each(function( i ) {
				jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
			});
		}

		return this.each(function() {
			if ( type === "string" ) {
				// toggle individual class names
				var className,
					i = 0,
					self = jQuery( this ),
					classNames = value.match( rnotwhite ) || [];

				while ( (className = classNames[ i++ ]) ) {
					// check each className given, space separated list
					if ( self.hasClass( className ) ) {
						self.removeClass( className );
					} else {
						self.addClass( className );
					}
				}

			// Toggle whole class name
			} else if ( type === strundefined || type === "boolean" ) {
				if ( this.className ) {
					// store className if set
					data_priv.set( this, "__className__", this.className );
				}

				// If the element has a class name or if we're passed "false",
				// then remove the whole classname (if there was one, the above saved it).
				// Otherwise bring back whatever was previously saved (if anything),
				// falling back to the empty string if nothing was stored.
				this.className = this.className || value === false ? "" : data_priv.get( this, "__className__" ) || "";
			}
		});
	},

	hasClass: function( selector ) {
		var className = " " + selector + " ",
			i = 0,
			l = this.length;
		for ( ; i < l; i++ ) {
			if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
				return true;
			}
		}

		return false;
	}
});




var rreturn = /\r/g;

jQuery.fn.extend({
	val: function( value ) {
		var hooks, ret, isFunction,
			elem = this[0];

		if ( !arguments.length ) {
			if ( elem ) {
				hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];

				if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
					return ret;
				}

				ret = elem.value;

				return typeof ret === "string" ?
					// handle most common string cases
					ret.replace(rreturn, "") :
					// handle cases where value is null/undef or number
					ret == null ? "" : ret;
			}

			return;
		}

		isFunction = jQuery.isFunction( value );

		return this.each(function( i ) {
			var val;

			if ( this.nodeType !== 1 ) {
				return;
			}

			if ( isFunction ) {
				val = value.call( this, i, jQuery( this ).val() );
			} else {
				val = value;
			}

			// Treat null/undefined as ""; convert numbers to string
			if ( val == null ) {
				val = "";

			} else if ( typeof val === "number" ) {
				val += "";

			} else if ( jQuery.isArray( val ) ) {
				val = jQuery.map( val, function( value ) {
					return value == null ? "" : value + "";
				});
			}

			hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];

			// If set returns undefined, fall back to normal setting
			if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
				this.value = val;
			}
		});
	}
});

jQuery.extend({
	valHooks: {
		option: {
			get: function( elem ) {
				var val = jQuery.find.attr( elem, "value" );
				return val != null ?
					val :
					// Support: IE10-11+
					// option.text throws exceptions (#14686, #14858)
					jQuery.trim( jQuery.text( elem ) );
			}
		},
		select: {
			get: function( elem ) {
				var value, option,
					options = elem.options,
					index = elem.selectedIndex,
					one = elem.type === "select-one" || index < 0,
					values = one ? null : [],
					max = one ? index + 1 : options.length,
					i = index < 0 ?
						max :
						one ? index : 0;

				// Loop through all the selected options
				for ( ; i < max; i++ ) {
					option = options[ i ];

					// IE6-9 doesn't update selected after form reset (#2551)
					if ( ( option.selected || i === index ) &&
							// Don't return options that are disabled or in a disabled optgroup
							( support.optDisabled ? !option.disabled : option.getAttribute( "disabled" ) === null ) &&
							( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {

						// Get the specific value for the option
						value = jQuery( option ).val();

						// We don't need an array for one selects
						if ( one ) {
							return value;
						}

						// Multi-Selects return an array
						values.push( value );
					}
				}

				return values;
			},

			set: function( elem, value ) {
				var optionSet, option,
					options = elem.options,
					values = jQuery.makeArray( value ),
					i = options.length;

				while ( i-- ) {
					option = options[ i ];
					if ( (option.selected = jQuery.inArray( option.value, values ) >= 0) ) {
						optionSet = true;
					}
				}

				// force browsers to behave consistently when non-matching value is set
				if ( !optionSet ) {
					elem.selectedIndex = -1;
				}
				return values;
			}
		}
	}
});

// Radios and checkboxes getter/setter
jQuery.each([ "radio", "checkbox" ], function() {
	jQuery.valHooks[ this ] = {
		set: function( elem, value ) {
			if ( jQuery.isArray( value ) ) {
				return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
			}
		}
	};
	if ( !support.checkOn ) {
		jQuery.valHooks[ this ].get = function( elem ) {
			// Support: Webkit
			// "" is returned instead of "on" if a value isn't specified
			return elem.getAttribute("value") === null ? "on" : elem.value;
		};
	}
});




// Return jQuery for attributes-only inclusion


jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
	"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
	"change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {

	// Handle event binding
	jQuery.fn[ name ] = function( data, fn ) {
		return arguments.length > 0 ?
			this.on( name, null, data, fn ) :
			this.trigger( name );
	};
});

jQuery.fn.extend({
	hover: function( fnOver, fnOut ) {
		return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
	},

	bind: function( types, data, fn ) {
		return this.on( types, null, data, fn );
	},
	unbind: function( types, fn ) {
		return this.off( types, null, fn );
	},

	delegate: function( selector, types, data, fn ) {
		return this.on( types, selector, data, fn );
	},
	undelegate: function( selector, types, fn ) {
		// ( namespace ) or ( selector, types [, fn] )
		return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
	}
});


var nonce = jQuery.now();

var rquery = (/\?/);



// Support: Android 2.3
// Workaround failure to string-cast null input
jQuery.parseJSON = function( data ) {
	return JSON.parse( data + "" );
};


// Cross-browser xml parsing
jQuery.parseXML = function( data ) {
	var xml, tmp;
	if ( !data || typeof data !== "string" ) {
		return null;
	}

	// Support: IE9
	try {
		tmp = new DOMParser();
		xml = tmp.parseFromString( data, "text/xml" );
	} catch ( e ) {
		xml = undefined;
	}

	if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) {
		jQuery.error( "Invalid XML: " + data );
	}
	return xml;
};


var
	// Document location
	ajaxLocParts,
	ajaxLocation,

	rhash = /#.*$/,
	rts = /([?&])_=[^&]*/,
	rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg,
	// #7653, #8125, #8152: local protocol detection
	rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
	rnoContent = /^(?:GET|HEAD)$/,
	rprotocol = /^\/\//,
	rurl = /^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,

	/* Prefilters
	 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
	 * 2) These are called:
	 *    - BEFORE asking for a transport
	 *    - AFTER param serialization (s.data is a string if s.processData is true)
	 * 3) key is the dataType
	 * 4) the catchall symbol "*" can be used
	 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
	 */
	prefilters = {},

	/* Transports bindings
	 * 1) key is the dataType
	 * 2) the catchall symbol "*" can be used
	 * 3) selection will start with transport dataType and THEN go to "*" if needed
	 */
	transports = {},

	// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
	allTypes = "*/".concat("*");

// #8138, IE may throw an exception when accessing
// a field from window.location if document.domain has been set
try {
	ajaxLocation = location.href;
} catch( e ) {
	// Use the href attribute of an A element
	// since IE will modify it given document.location
	ajaxLocation = document.createElement( "a" );
	ajaxLocation.href = "";
	ajaxLocation = ajaxLocation.href;
}

// Segment location into parts
ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];

// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
function addToPrefiltersOrTransports( structure ) {

	// dataTypeExpression is optional and defaults to "*"
	return function( dataTypeExpression, func ) {

		if ( typeof dataTypeExpression !== "string" ) {
			func = dataTypeExpression;
			dataTypeExpression = "*";
		}

		var dataType,
			i = 0,
			dataTypes = dataTypeExpression.toLowerCase().match( rnotwhite ) || [];

		if ( jQuery.isFunction( func ) ) {
			// For each dataType in the dataTypeExpression
			while ( (dataType = dataTypes[i++]) ) {
				// Prepend if requested
				if ( dataType[0] === "+" ) {
					dataType = dataType.slice( 1 ) || "*";
					(structure[ dataType ] = structure[ dataType ] || []).unshift( func );

				// Otherwise append
				} else {
					(structure[ dataType ] = structure[ dataType ] || []).push( func );
				}
			}
		}
	};
}

// Base inspection function for prefilters and transports
function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {

	var inspected = {},
		seekingTransport = ( structure === transports );

	function inspect( dataType ) {
		var selected;
		inspected[ dataType ] = true;
		jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
			var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
			if ( typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) {
				options.dataTypes.unshift( dataTypeOrTransport );
				inspect( dataTypeOrTransport );
				return false;
			} else if ( seekingTransport ) {
				return !( selected = dataTypeOrTransport );
			}
		});
		return selected;
	}

	return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
}

// A special extend for ajax options
// that takes "flat" options (not to be deep extended)
// Fixes #9887
function ajaxExtend( target, src ) {
	var key, deep,
		flatOptions = jQuery.ajaxSettings.flatOptions || {};

	for ( key in src ) {
		if ( src[ key ] !== undefined ) {
			( flatOptions[ key ] ? target : ( deep || (deep = {}) ) )[ key ] = src[ key ];
		}
	}
	if ( deep ) {
		jQuery.extend( true, target, deep );
	}

	return target;
}

/* Handles responses to an ajax request:
 * - finds the right dataType (mediates between content-type and expected dataType)
 * - returns the corresponding response
 */
function ajaxHandleResponses( s, jqXHR, responses ) {

	var ct, type, finalDataType, firstDataType,
		contents = s.contents,
		dataTypes = s.dataTypes;

	// Remove auto dataType and get content-type in the process
	while ( dataTypes[ 0 ] === "*" ) {
		dataTypes.shift();
		if ( ct === undefined ) {
			ct = s.mimeType || jqXHR.getResponseHeader("Content-Type");
		}
	}

	// Check if we're dealing with a known content-type
	if ( ct ) {
		for ( type in contents ) {
			if ( contents[ type ] && contents[ type ].test( ct ) ) {
				dataTypes.unshift( type );
				break;
			}
		}
	}

	// Check to see if we have a response for the expected dataType
	if ( dataTypes[ 0 ] in responses ) {
		finalDataType = dataTypes[ 0 ];
	} else {
		// Try convertible dataTypes
		for ( type in responses ) {
			if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
				finalDataType = type;
				break;
			}
			if ( !firstDataType ) {
				firstDataType = type;
			}
		}
		// Or just use first one
		finalDataType = finalDataType || firstDataType;
	}

	// If we found a dataType
	// We add the dataType to the list if needed
	// and return the corresponding response
	if ( finalDataType ) {
		if ( finalDataType !== dataTypes[ 0 ] ) {
			dataTypes.unshift( finalDataType );
		}
		return responses[ finalDataType ];
	}
}

/* Chain conversions given the request and the original response
 * Also sets the responseXXX fields on the jqXHR instance
 */
function ajaxConvert( s, response, jqXHR, isSuccess ) {
	var conv2, current, conv, tmp, prev,
		converters = {},
		// Work with a copy of dataTypes in case we need to modify it for conversion
		dataTypes = s.dataTypes.slice();

	// Create converters map with lowercased keys
	if ( dataTypes[ 1 ] ) {
		for ( conv in s.converters ) {
			converters[ conv.toLowerCase() ] = s.converters[ conv ];
		}
	}

	current = dataTypes.shift();

	// Convert to each sequential dataType
	while ( current ) {

		if ( s.responseFields[ current ] ) {
			jqXHR[ s.responseFields[ current ] ] = response;
		}

		// Apply the dataFilter if provided
		if ( !prev && isSuccess && s.dataFilter ) {
			response = s.dataFilter( response, s.dataType );
		}

		prev = current;
		current = dataTypes.shift();

		if ( current ) {

		// There's only work to do if current dataType is non-auto
			if ( current === "*" ) {

				current = prev;

			// Convert response if prev dataType is non-auto and differs from current
			} else if ( prev !== "*" && prev !== current ) {

				// Seek a direct converter
				conv = converters[ prev + " " + current ] || converters[ "* " + current ];

				// If none found, seek a pair
				if ( !conv ) {
					for ( conv2 in converters ) {

						// If conv2 outputs current
						tmp = conv2.split( " " );
						if ( tmp[ 1 ] === current ) {

							// If prev can be converted to accepted input
							conv = converters[ prev + " " + tmp[ 0 ] ] ||
								converters[ "* " + tmp[ 0 ] ];
							if ( conv ) {
								// Condense equivalence converters
								if ( conv === true ) {
									conv = converters[ conv2 ];

								// Otherwise, insert the intermediate dataType
								} else if ( converters[ conv2 ] !== true ) {
									current = tmp[ 0 ];
									dataTypes.unshift( tmp[ 1 ] );
								}
								break;
							}
						}
					}
				}

				// Apply converter (if not an equivalence)
				if ( conv !== true ) {

					// Unless errors are allowed to bubble, catch and return them
					if ( conv && s[ "throws" ] ) {
						response = conv( response );
					} else {
						try {
							response = conv( response );
						} catch ( e ) {
							return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
						}
					}
				}
			}
		}
	}

	return { state: "success", data: response };
}

jQuery.extend({

	// Counter for holding the number of active queries
	active: 0,

	// Last-Modified header cache for next request
	lastModified: {},
	etag: {},

	ajaxSettings: {
		url: ajaxLocation,
		type: "GET",
		isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
		global: true,
		processData: true,
		async: true,
		contentType: "application/x-www-form-urlencoded; charset=UTF-8",
		/*
		timeout: 0,
		data: null,
		dataType: null,
		username: null,
		password: null,
		cache: null,
		throws: false,
		traditional: false,
		headers: {},
		*/

		accepts: {
			"*": allTypes,
			text: "text/plain",
			html: "text/html",
			xml: "application/xml, text/xml",
			json: "application/json, text/javascript"
		},

		contents: {
			xml: /xml/,
			html: /html/,
			json: /json/
		},

		responseFields: {
			xml: "responseXML",
			text: "responseText",
			json: "responseJSON"
		},

		// Data converters
		// Keys separate source (or catchall "*") and destination types with a single space
		converters: {

			// Convert anything to text
			"* text": String,

			// Text to html (true = no transformation)
			"text html": true,

			// Evaluate text as a json expression
			"text json": jQuery.parseJSON,

			// Parse text as xml
			"text xml": jQuery.parseXML
		},

		// For options that shouldn't be deep extended:
		// you can add your own custom options here if
		// and when you create one that shouldn't be
		// deep extended (see ajaxExtend)
		flatOptions: {
			url: true,
			context: true
		}
	},

	// Creates a full fledged settings object into target
	// with both ajaxSettings and settings fields.
	// If target is omitted, writes into ajaxSettings.
	ajaxSetup: function( target, settings ) {
		return settings ?

			// Building a settings object
			ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :

			// Extending ajaxSettings
			ajaxExtend( jQuery.ajaxSettings, target );
	},

	ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
	ajaxTransport: addToPrefiltersOrTransports( transports ),

	// Main method
	ajax: function( url, options ) {

		// If url is an object, simulate pre-1.5 signature
		if ( typeof url === "object" ) {
			options = url;
			url = undefined;
		}

		// Force options to be an object
		options = options || {};

		var transport,
			// URL without anti-cache param
			cacheURL,
			// Response headers
			responseHeadersString,
			responseHeaders,
			// timeout handle
			timeoutTimer,
			// Cross-domain detection vars
			parts,
			// To know if global events are to be dispatched
			fireGlobals,
			// Loop variable
			i,
			// Create the final options object
			s = jQuery.ajaxSetup( {}, options ),
			// Callbacks context
			callbackContext = s.context || s,
			// Context for global events is callbackContext if it is a DOM node or jQuery collection
			globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?
				jQuery( callbackContext ) :
				jQuery.event,
			// Deferreds
			deferred = jQuery.Deferred(),
			completeDeferred = jQuery.Callbacks("once memory"),
			// Status-dependent callbacks
			statusCode = s.statusCode || {},
			// Headers (they are sent all at once)
			requestHeaders = {},
			requestHeadersNames = {},
			// The jqXHR state
			state = 0,
			// Default abort message
			strAbort = "canceled",
			// Fake xhr
			jqXHR = {
				readyState: 0,

				// Builds headers hashtable if needed
				getResponseHeader: function( key ) {
					var match;
					if ( state === 2 ) {
						if ( !responseHeaders ) {
							responseHeaders = {};
							while ( (match = rheaders.exec( responseHeadersString )) ) {
								responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
							}
						}
						match = responseHeaders[ key.toLowerCase() ];
					}
					return match == null ? null : match;
				},

				// Raw string
				getAllResponseHeaders: function() {
					return state === 2 ? responseHeadersString : null;
				},

				// Caches the header
				setRequestHeader: function( name, value ) {
					var lname = name.toLowerCase();
					if ( !state ) {
						name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
						requestHeaders[ name ] = value;
					}
					return this;
				},

				// Overrides response content-type header
				overrideMimeType: function( type ) {
					if ( !state ) {
						s.mimeType = type;
					}
					return this;
				},

				// Status-dependent callbacks
				statusCode: function( map ) {
					var code;
					if ( map ) {
						if ( state < 2 ) {
							for ( code in map ) {
								// Lazy-add the new callback in a way that preserves old ones
								statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
							}
						} else {
							// Execute the appropriate callbacks
							jqXHR.always( map[ jqXHR.status ] );
						}
					}
					return this;
				},

				// Cancel the request
				abort: function( statusText ) {
					var finalText = statusText || strAbort;
					if ( transport ) {
						transport.abort( finalText );
					}
					done( 0, finalText );
					return this;
				}
			};

		// Attach deferreds
		deferred.promise( jqXHR ).complete = completeDeferred.add;
		jqXHR.success = jqXHR.done;
		jqXHR.error = jqXHR.fail;

		// Remove hash character (#7531: and string promotion)
		// Add protocol if not provided (prefilters might expect it)
		// Handle falsy url in the settings object (#10093: consistency with old signature)
		// We also use the url parameter if available
		s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" )
			.replace( rprotocol, ajaxLocParts[ 1 ] + "//" );

		// Alias method option to type as per ticket #12004
		s.type = options.method || options.type || s.method || s.type;

		// Extract dataTypes list
		s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( rnotwhite ) || [ "" ];

		// A cross-domain request is in order when we have a protocol:host:port mismatch
		if ( s.crossDomain == null ) {
			parts = rurl.exec( s.url.toLowerCase() );
			s.crossDomain = !!( parts &&
				( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||
					( parts[ 3 ] || ( parts[ 1 ] === "http:" ? "80" : "443" ) ) !==
						( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? "80" : "443" ) ) )
			);
		}

		// Convert data if not already a string
		if ( s.data && s.processData && typeof s.data !== "string" ) {
			s.data = jQuery.param( s.data, s.traditional );
		}

		// Apply prefilters
		inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );

		// If request was aborted inside a prefilter, stop there
		if ( state === 2 ) {
			return jqXHR;
		}

		// We can fire global events as of now if asked to
		fireGlobals = s.global;

		// Watch for a new set of requests
		if ( fireGlobals && jQuery.active++ === 0 ) {
			jQuery.event.trigger("ajaxStart");
		}

		// Uppercase the type
		s.type = s.type.toUpperCase();

		// Determine if request has content
		s.hasContent = !rnoContent.test( s.type );

		// Save the URL in case we're toying with the If-Modified-Since
		// and/or If-None-Match header later on
		cacheURL = s.url;

		// More options handling for requests with no content
		if ( !s.hasContent ) {

			// If data is available, append data to url
			if ( s.data ) {
				cacheURL = ( s.url += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data );
				// #9682: remove data so that it's not used in an eventual retry
				delete s.data;
			}

			// Add anti-cache in url if needed
			if ( s.cache === false ) {
				s.url = rts.test( cacheURL ) ?

					// If there is already a '_' parameter, set its value
					cacheURL.replace( rts, "$1_=" + nonce++ ) :

					// Otherwise add one to the end
					cacheURL + ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + nonce++;
			}
		}

		// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
		if ( s.ifModified ) {
			if ( jQuery.lastModified[ cacheURL ] ) {
				jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
			}
			if ( jQuery.etag[ cacheURL ] ) {
				jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
			}
		}

		// Set the correct header, if data is being sent
		if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
			jqXHR.setRequestHeader( "Content-Type", s.contentType );
		}

		// Set the Accepts header for the server, depending on the dataType
		jqXHR.setRequestHeader(
			"Accept",
			s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
				s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
				s.accepts[ "*" ]
		);

		// Check for headers option
		for ( i in s.headers ) {
			jqXHR.setRequestHeader( i, s.headers[ i ] );
		}

		// Allow custom headers/mimetypes and early abort
		if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
			// Abort if not done already and return
			return jqXHR.abort();
		}

		// aborting is no longer a cancellation
		strAbort = "abort";

		// Install callbacks on deferreds
		for ( i in { success: 1, error: 1, complete: 1 } ) {
			jqXHR[ i ]( s[ i ] );
		}

		// Get transport
		transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );

		// If no transport, we auto-abort
		if ( !transport ) {
			done( -1, "No Transport" );
		} else {
			jqXHR.readyState = 1;

			// Send global event
			if ( fireGlobals ) {
				globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
			}
			// Timeout
			if ( s.async && s.timeout > 0 ) {
				timeoutTimer = setTimeout(function() {
					jqXHR.abort("timeout");
				}, s.timeout );
			}

			try {
				state = 1;
				transport.send( requestHeaders, done );
			} catch ( e ) {
				// Propagate exception as error if not done
				if ( state < 2 ) {
					done( -1, e );
				// Simply rethrow otherwise
				} else {
					throw e;
				}
			}
		}

		// Callback for when everything is done
		function done( status, nativeStatusText, responses, headers ) {
			var isSuccess, success, error, response, modified,
				statusText = nativeStatusText;

			// Called once
			if ( state === 2 ) {
				return;
			}

			// State is "done" now
			state = 2;

			// Clear timeout if it exists
			if ( timeoutTimer ) {
				clearTimeout( timeoutTimer );
			}

			// Dereference transport for early garbage collection
			// (no matter how long the jqXHR object will be used)
			transport = undefined;

			// Cache response headers
			responseHeadersString = headers || "";

			// Set readyState
			jqXHR.readyState = status > 0 ? 4 : 0;

			// Determine if successful
			isSuccess = status >= 200 && status < 300 || status === 304;

			// Get response data
			if ( responses ) {
				response = ajaxHandleResponses( s, jqXHR, responses );
			}

			// Convert no matter what (that way responseXXX fields are always set)
			response = ajaxConvert( s, response, jqXHR, isSuccess );

			// If successful, handle type chaining
			if ( isSuccess ) {

				// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
				if ( s.ifModified ) {
					modified = jqXHR.getResponseHeader("Last-Modified");
					if ( modified ) {
						jQuery.lastModified[ cacheURL ] = modified;
					}
					modified = jqXHR.getResponseHeader("etag");
					if ( modified ) {
						jQuery.etag[ cacheURL ] = modified;
					}
				}

				// if no content
				if ( status === 204 || s.type === "HEAD" ) {
					statusText = "nocontent";

				// if not modified
				} else if ( status === 304 ) {
					statusText = "notmodified";

				// If we have data, let's convert it
				} else {
					statusText = response.state;
					success = response.data;
					error = response.error;
					isSuccess = !error;
				}
			} else {
				// We extract error from statusText
				// then normalize statusText and status for non-aborts
				error = statusText;
				if ( status || !statusText ) {
					statusText = "error";
					if ( status < 0 ) {
						status = 0;
					}
				}
			}

			// Set data for the fake xhr object
			jqXHR.status = status;
			jqXHR.statusText = ( nativeStatusText || statusText ) + "";

			// Success/Error
			if ( isSuccess ) {
				deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
			} else {
				deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
			}

			// Status-dependent callbacks
			jqXHR.statusCode( statusCode );
			statusCode = undefined;

			if ( fireGlobals ) {
				globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
					[ jqXHR, s, isSuccess ? success : error ] );
			}

			// Complete
			completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );

			if ( fireGlobals ) {
				globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
				// Handle the global AJAX counter
				if ( !( --jQuery.active ) ) {
					jQuery.event.trigger("ajaxStop");
				}
			}
		}

		return jqXHR;
	},

	getJSON: function( url, data, callback ) {
		return jQuery.get( url, data, callback, "json" );
	},

	getScript: function( url, callback ) {
		return jQuery.get( url, undefined, callback, "script" );
	}
});

jQuery.each( [ "get", "post" ], function( i, method ) {
	jQuery[ method ] = function( url, data, callback, type ) {
		// shift arguments if data argument was omitted
		if ( jQuery.isFunction( data ) ) {
			type = type || callback;
			callback = data;
			data = undefined;
		}

		return jQuery.ajax({
			url: url,
			type: method,
			dataType: type,
			data: data,
			success: callback
		});
	};
});

// Attach a bunch of functions for handling common AJAX events
jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete", "ajaxError", "ajaxSuccess", "ajaxSend" ], function( i, type ) {
	jQuery.fn[ type ] = function( fn ) {
		return this.on( type, fn );
	};
});


jQuery._evalUrl = function( url ) {
	return jQuery.ajax({
		url: url,
		type: "GET",
		dataType: "script",
		async: false,
		global: false,
		"throws": true
	});
};


jQuery.fn.extend({
	wrapAll: function( html ) {
		var wrap;

		if ( jQuery.isFunction( html ) ) {
			return this.each(function( i ) {
				jQuery( this ).wrapAll( html.call(this, i) );
			});
		}

		if ( this[ 0 ] ) {

			// The elements to wrap the target around
			wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );

			if ( this[ 0 ].parentNode ) {
				wrap.insertBefore( this[ 0 ] );
			}

			wrap.map(function() {
				var elem = this;

				while ( elem.firstElementChild ) {
					elem = elem.firstElementChild;
				}

				return elem;
			}).append( this );
		}

		return this;
	},

	wrapInner: function( html ) {
		if ( jQuery.isFunction( html ) ) {
			return this.each(function( i ) {
				jQuery( this ).wrapInner( html.call(this, i) );
			});
		}

		return this.each(function() {
			var self = jQuery( this ),
				contents = self.contents();

			if ( contents.length ) {
				contents.wrapAll( html );

			} else {
				self.append( html );
			}
		});
	},

	wrap: function( html ) {
		var isFunction = jQuery.isFunction( html );

		return this.each(function( i ) {
			jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
		});
	},

	unwrap: function() {
		return this.parent().each(function() {
			if ( !jQuery.nodeName( this, "body" ) ) {
				jQuery( this ).replaceWith( this.childNodes );
			}
		}).end();
	}
});


jQuery.expr.filters.hidden = function( elem ) {
	// Support: Opera <= 12.12
	// Opera reports offsetWidths and offsetHeights less than zero on some elements
	return elem.offsetWidth <= 0 && elem.offsetHeight <= 0;
};
jQuery.expr.filters.visible = function( elem ) {
	return !jQuery.expr.filters.hidden( elem );
};




var r20 = /%20/g,
	rbracket = /\[\]$/,
	rCRLF = /\r?\n/g,
	rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
	rsubmittable = /^(?:input|select|textarea|keygen)/i;

function buildParams( prefix, obj, traditional, add ) {
	var name;

	if ( jQuery.isArray( obj ) ) {
		// Serialize array item.
		jQuery.each( obj, function( i, v ) {
			if ( traditional || rbracket.test( prefix ) ) {
				// Treat each array item as a scalar.
				add( prefix, v );

			} else {
				// Item is non-scalar (array or object), encode its numeric index.
				buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
			}
		});

	} else if ( !traditional && jQuery.type( obj ) === "object" ) {
		// Serialize object item.
		for ( name in obj ) {
			buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
		}

	} else {
		// Serialize scalar item.
		add( prefix, obj );
	}
}

// Serialize an array of form elements or a set of
// key/values into a query string
jQuery.param = function( a, traditional ) {
	var prefix,
		s = [],
		add = function( key, value ) {
			// If value is a function, invoke it and return its value
			value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
			s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
		};

	// Set traditional to true for jQuery <= 1.3.2 behavior.
	if ( traditional === undefined ) {
		traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
	}

	// If an array was passed in, assume that it is an array of form elements.
	if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
		// Serialize the form elements
		jQuery.each( a, function() {
			add( this.name, this.value );
		});

	} else {
		// If traditional, encode the "old" way (the way 1.3.2 or older
		// did it), otherwise encode params recursively.
		for ( prefix in a ) {
			buildParams( prefix, a[ prefix ], traditional, add );
		}
	}

	// Return the resulting serialization
	return s.join( "&" ).replace( r20, "+" );
};

jQuery.fn.extend({
	serialize: function() {
		return jQuery.param( this.serializeArray() );
	},
	serializeArray: function() {
		return this.map(function() {
			// Can add propHook for "elements" to filter or add form elements
			var elements = jQuery.prop( this, "elements" );
			return elements ? jQuery.makeArray( elements ) : this;
		})
		.filter(function() {
			var type = this.type;

			// Use .is( ":disabled" ) so that fieldset[disabled] works
			return this.name && !jQuery( this ).is( ":disabled" ) &&
				rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
				( this.checked || !rcheckableType.test( type ) );
		})
		.map(function( i, elem ) {
			var val = jQuery( this ).val();

			return val == null ?
				null :
				jQuery.isArray( val ) ?
					jQuery.map( val, function( val ) {
						return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
					}) :
					{ name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
		}).get();
	}
});


jQuery.ajaxSettings.xhr = function() {
	try {
		return new XMLHttpRequest();
	} catch( e ) {}
};

var xhrId = 0,
	xhrCallbacks = {},
	xhrSuccessStatus = {
		// file protocol always yields status code 0, assume 200
		0: 200,
		// Support: IE9
		// #1450: sometimes IE returns 1223 when it should be 204
		1223: 204
	},
	xhrSupported = jQuery.ajaxSettings.xhr();

// Support: IE9
// Open requests must be manually aborted on unload (#5280)
if ( window.ActiveXObject ) {
	jQuery( window ).on( "unload", function() {
		for ( var key in xhrCallbacks ) {
			xhrCallbacks[ key ]();
		}
	});
}

support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
support.ajax = xhrSupported = !!xhrSupported;

jQuery.ajaxTransport(function( options ) {
	var callback;

	// Cross domain only allowed if supported through XMLHttpRequest
	if ( support.cors || xhrSupported && !options.crossDomain ) {
		return {
			send: function( headers, complete ) {
				var i,
					xhr = options.xhr(),
					id = ++xhrId;

				xhr.open( options.type, options.url, options.async, options.username, options.password );

				// Apply custom fields if provided
				if ( options.xhrFields ) {
					for ( i in options.xhrFields ) {
						xhr[ i ] = options.xhrFields[ i ];
					}
				}

				// Override mime type if needed
				if ( options.mimeType && xhr.overrideMimeType ) {
					xhr.overrideMimeType( options.mimeType );
				}

				// X-Requested-With header
				// For cross-domain requests, seeing as conditions for a preflight are
				// akin to a jigsaw puzzle, we simply never set it to be sure.
				// (it can always be set on a per-request basis or even using ajaxSetup)
				// For same-domain requests, won't change header if already provided.
				if ( !options.crossDomain && !headers["X-Requested-With"] ) {
					headers["X-Requested-With"] = "XMLHttpRequest";
				}

				// Set headers
				for ( i in headers ) {
					xhr.setRequestHeader( i, headers[ i ] );
				}

				// Callback
				callback = function( type ) {
					return function() {
						if ( callback ) {
							delete xhrCallbacks[ id ];
							callback = xhr.onload = xhr.onerror = null;

							if ( type === "abort" ) {
								xhr.abort();
							} else if ( type === "error" ) {
								complete(
									// file: protocol always yields status 0; see #8605, #14207
									xhr.status,
									xhr.statusText
								);
							} else {
								complete(
									xhrSuccessStatus[ xhr.status ] || xhr.status,
									xhr.statusText,
									// Support: IE9
									// Accessing binary-data responseText throws an exception
									// (#11426)
									typeof xhr.responseText === "string" ? {
										text: xhr.responseText
									} : undefined,
									xhr.getAllResponseHeaders()
								);
							}
						}
					};
				};

				// Listen to events
				xhr.onload = callback();
				xhr.onerror = callback("error");

				// Create the abort callback
				callback = xhrCallbacks[ id ] = callback("abort");

				try {
					// Do send the request (this may raise an exception)
					xhr.send( options.hasContent && options.data || null );
				} catch ( e ) {
					// #14683: Only rethrow if this hasn't been notified as an error yet
					if ( callback ) {
						throw e;
					}
				}
			},

			abort: function() {
				if ( callback ) {
					callback();
				}
			}
		};
	}
});




// Install script dataType
jQuery.ajaxSetup({
	accepts: {
		script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
	},
	contents: {
		script: /(?:java|ecma)script/
	},
	converters: {
		"text script": function( text ) {
			jQuery.globalEval( text );
			return text;
		}
	}
});

// Handle cache's special case and crossDomain
jQuery.ajaxPrefilter( "script", function( s ) {
	if ( s.cache === undefined ) {
		s.cache = false;
	}
	if ( s.crossDomain ) {
		s.type = "GET";
	}
});

// Bind script tag hack transport
jQuery.ajaxTransport( "script", function( s ) {
	// This transport only deals with cross domain requests
	if ( s.crossDomain ) {
		var script, callback;
		return {
			send: function( _, complete ) {
				script = jQuery("<script>").prop({
					async: true,
					charset: s.scriptCharset,
					src: s.url
				}).on(
					"load error",
					callback = function( evt ) {
						script.remove();
						callback = null;
						if ( evt ) {
							complete( evt.type === "error" ? 404 : 200, evt.type );
						}
					}
				);
				document.head.appendChild( script[ 0 ] );
			},
			abort: function() {
				if ( callback ) {
					callback();
				}
			}
		};
	}
});




var oldCallbacks = [],
	rjsonp = /(=)\?(?=&|$)|\?\?/;

// Default jsonp settings
jQuery.ajaxSetup({
	jsonp: "callback",
	jsonpCallback: function() {
		var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) );
		this[ callback ] = true;
		return callback;
	}
});

// Detect, normalize options and install callbacks for jsonp requests
jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {

	var callbackName, overwritten, responseContainer,
		jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
			"url" :
			typeof s.data === "string" && !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") && rjsonp.test( s.data ) && "data"
		);

	// Handle iff the expected data type is "jsonp" or we have a parameter to set
	if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {

		// Get callback name, remembering preexisting value associated with it
		callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
			s.jsonpCallback() :
			s.jsonpCallback;

		// Insert callback into url or form data
		if ( jsonProp ) {
			s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
		} else if ( s.jsonp !== false ) {
			s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
		}

		// Use data converter to retrieve json after script execution
		s.converters["script json"] = function() {
			if ( !responseContainer ) {
				jQuery.error( callbackName + " was not called" );
			}
			return responseContainer[ 0 ];
		};

		// force json dataType
		s.dataTypes[ 0 ] = "json";

		// Install callback
		overwritten = window[ callbackName ];
		window[ callbackName ] = function() {
			responseContainer = arguments;
		};

		// Clean-up function (fires after converters)
		jqXHR.always(function() {
			// Restore preexisting value
			window[ callbackName ] = overwritten;

			// Save back as free
			if ( s[ callbackName ] ) {
				// make sure that re-using the options doesn't screw things around
				s.jsonpCallback = originalSettings.jsonpCallback;

				// save the callback name for future use
				oldCallbacks.push( callbackName );
			}

			// Call if it was a function and we have a response
			if ( responseContainer && jQuery.isFunction( overwritten ) ) {
				overwritten( responseContainer[ 0 ] );
			}

			responseContainer = overwritten = undefined;
		});

		// Delegate to script
		return "script";
	}
});




// data: string of html
// context (optional): If specified, the fragment will be created in this context, defaults to document
// keepScripts (optional): If true, will include scripts passed in the html string
jQuery.parseHTML = function( data, context, keepScripts ) {
	if ( !data || typeof data !== "string" ) {
		return null;
	}
	if ( typeof context === "boolean" ) {
		keepScripts = context;
		context = false;
	}
	context = context || document;

	var parsed = rsingleTag.exec( data ),
		scripts = !keepScripts && [];

	// Single tag
	if ( parsed ) {
		return [ context.createElement( parsed[1] ) ];
	}

	parsed = jQuery.buildFragment( [ data ], context, scripts );

	if ( scripts && scripts.length ) {
		jQuery( scripts ).remove();
	}

	return jQuery.merge( [], parsed.childNodes );
};


// Keep a copy of the old load method
var _load = jQuery.fn.load;

/**
 * Load a url into a page
 */
jQuery.fn.load = function( url, params, callback ) {
	if ( typeof url !== "string" && _load ) {
		return _load.apply( this, arguments );
	}

	var selector, type, response,
		self = this,
		off = url.indexOf(" ");

	if ( off >= 0 ) {
		selector = jQuery.trim( url.slice( off ) );
		url = url.slice( 0, off );
	}

	// If it's a function
	if ( jQuery.isFunction( params ) ) {

		// We assume that it's the callback
		callback = params;
		params = undefined;

	// Otherwise, build a param string
	} else if ( params && typeof params === "object" ) {
		type = "POST";
	}

	// If we have elements to modify, make the request
	if ( self.length > 0 ) {
		jQuery.ajax({
			url: url,

			// if "type" variable is undefined, then "GET" method will be used
			type: type,
			dataType: "html",
			data: params
		}).done(function( responseText ) {

			// Save response for use in complete callback
			response = arguments;

			self.html( selector ?

				// If a selector was specified, locate the right elements in a dummy div
				// Exclude scripts to avoid IE 'Permission Denied' errors
				jQuery("<div>").append( jQuery.parseHTML( responseText ) ).find( selector ) :

				// Otherwise use the full result
				responseText );

		}).complete( callback && function( jqXHR, status ) {
			self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
		});
	}

	return this;
};




jQuery.expr.filters.animated = function( elem ) {
	return jQuery.grep(jQuery.timers, function( fn ) {
		return elem === fn.elem;
	}).length;
};




var docElem = window.document.documentElement;

/**
 * Gets a window from an element
 */
function getWindow( elem ) {
	return jQuery.isWindow( elem ) ? elem : elem.nodeType === 9 && elem.defaultView;
}

jQuery.offset = {
	setOffset: function( elem, options, i ) {
		var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
			position = jQuery.css( elem, "position" ),
			curElem = jQuery( elem ),
			props = {};

		// Set position first, in-case top/left are set even on static elem
		if ( position === "static" ) {
			elem.style.position = "relative";
		}

		curOffset = curElem.offset();
		curCSSTop = jQuery.css( elem, "top" );
		curCSSLeft = jQuery.css( elem, "left" );
		calculatePosition = ( position === "absolute" || position === "fixed" ) &&
			( curCSSTop + curCSSLeft ).indexOf("auto") > -1;

		// Need to be able to calculate position if either top or left is auto and position is either absolute or fixed
		if ( calculatePosition ) {
			curPosition = curElem.position();
			curTop = curPosition.top;
			curLeft = curPosition.left;

		} else {
			curTop = parseFloat( curCSSTop ) || 0;
			curLeft = parseFloat( curCSSLeft ) || 0;
		}

		if ( jQuery.isFunction( options ) ) {
			options = options.call( elem, i, curOffset );
		}

		if ( options.top != null ) {
			props.top = ( options.top - curOffset.top ) + curTop;
		}
		if ( options.left != null ) {
			props.left = ( options.left - curOffset.left ) + curLeft;
		}

		if ( "using" in options ) {
			options.using.call( elem, props );

		} else {
			curElem.css( props );
		}
	}
};

jQuery.fn.extend({
	offset: function( options ) {
		if ( arguments.length ) {
			return options === undefined ?
				this :
				this.each(function( i ) {
					jQuery.offset.setOffset( this, options, i );
				});
		}

		var docElem, win,
			elem = this[ 0 ],
			box = { top: 0, left: 0 },
			doc = elem && elem.ownerDocument;

		if ( !doc ) {
			return;
		}

		docElem = doc.documentElement;

		// Make sure it's not a disconnected DOM node
		if ( !jQuery.contains( docElem, elem ) ) {
			return box;
		}

		// If we don't have gBCR, just use 0,0 rather than error
		// BlackBerry 5, iOS 3 (original iPhone)
		if ( typeof elem.getBoundingClientRect !== strundefined ) {
			box = elem.getBoundingClientRect();
		}
		win = getWindow( doc );
		return {
			top: box.top + win.pageYOffset - docElem.clientTop,
			left: box.left + win.pageXOffset - docElem.clientLeft
		};
	},

	position: function() {
		if ( !this[ 0 ] ) {
			return;
		}

		var offsetParent, offset,
			elem = this[ 0 ],
			parentOffset = { top: 0, left: 0 };

		// Fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is its only offset parent
		if ( jQuery.css( elem, "position" ) === "fixed" ) {
			// We assume that getBoundingClientRect is available when computed position is fixed
			offset = elem.getBoundingClientRect();

		} else {
			// Get *real* offsetParent
			offsetParent = this.offsetParent();

			// Get correct offsets
			offset = this.offset();
			if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
				parentOffset = offsetParent.offset();
			}

			// Add offsetParent borders
			parentOffset.top += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true );
			parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true );
		}

		// Subtract parent offsets and element margins
		return {
			top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
			left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
		};
	},

	offsetParent: function() {
		return this.map(function() {
			var offsetParent = this.offsetParent || docElem;

			while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) && jQuery.css( offsetParent, "position" ) === "static" ) ) {
				offsetParent = offsetParent.offsetParent;
			}

			return offsetParent || docElem;
		});
	}
});

// Create scrollLeft and scrollTop methods
jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
	var top = "pageYOffset" === prop;

	jQuery.fn[ method ] = function( val ) {
		return access( this, function( elem, method, val ) {
			var win = getWindow( elem );

			if ( val === undefined ) {
				return win ? win[ prop ] : elem[ method ];
			}

			if ( win ) {
				win.scrollTo(
					!top ? val : window.pageXOffset,
					top ? val : window.pageYOffset
				);

			} else {
				elem[ method ] = val;
			}
		}, method, val, arguments.length, null );
	};
});

// Add the top/left cssHooks using jQuery.fn.position
// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
// getComputedStyle returns percent when specified for top/left/bottom/right
// rather than make the css module depend on the offset module, we just check for it here
jQuery.each( [ "top", "left" ], function( i, prop ) {
	jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,
		function( elem, computed ) {
			if ( computed ) {
				computed = curCSS( elem, prop );
				// if curCSS returns percentage, fallback to offset
				return rnumnonpx.test( computed ) ?
					jQuery( elem ).position()[ prop ] + "px" :
					computed;
			}
		}
	);
});


// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
	jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) {
		// margin is only for outerHeight, outerWidth
		jQuery.fn[ funcName ] = function( margin, value ) {
			var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
				extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );

			return access( this, function( elem, type, value ) {
				var doc;

				if ( jQuery.isWindow( elem ) ) {
					// As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
					// isn't a whole lot we can do. See pull request at this URL for discussion:
					// https://github.com/jquery/jquery/pull/764
					return elem.document.documentElement[ "client" + name ];
				}

				// Get document width or height
				if ( elem.nodeType === 9 ) {
					doc = elem.documentElement;

					// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
					// whichever is greatest
					return Math.max(
						elem.body[ "scroll" + name ], doc[ "scroll" + name ],
						elem.body[ "offset" + name ], doc[ "offset" + name ],
						doc[ "client" + name ]
					);
				}

				return value === undefined ?
					// Get width or height on the element, requesting but not forcing parseFloat
					jQuery.css( elem, type, extra ) :

					// Set width or height on the element
					jQuery.style( elem, type, value, extra );
			}, type, chainable ? margin : undefined, chainable, null );
		};
	});
});


// The number of elements contained in the matched element set
jQuery.fn.size = function() {
	return this.length;
};

jQuery.fn.andSelf = jQuery.fn.addBack;




// Register as a named AMD module, since jQuery can be concatenated with other
// files that may use define, but not via a proper concatenation script that
// understands anonymous AMD modules. A named AMD is safest and most robust
// way to register. Lowercase jquery is used because AMD module names are
// derived from file names, and jQuery is normally delivered in a lowercase
// file name. Do this after creating the global so that if an AMD module wants
// to call noConflict to hide this version of jQuery, it will work.

// Note that for maximum portability, libraries that are not jQuery should
// declare themselves as anonymous modules, and avoid setting a global if an
// AMD loader is present. jQuery is a special case. For more information, see
// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon

if ( typeof define === "function" && define.amd ) {
	define( "jquery", [], function() {
		return jQuery;
	});
}




var
	// Map over jQuery in case of overwrite
	_jQuery = window.jQuery,

	// Map over the $ in case of overwrite
	_$ = window.$;

jQuery.noConflict = function( deep ) {
	if ( window.$ === jQuery ) {
		window.$ = _$;
	}

	if ( deep && window.jQuery === jQuery ) {
		window.jQuery = _jQuery;
	}

	return jQuery;
};

// Expose jQuery and $ identifiers, even in
// AMD (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
// and CommonJS for browser emulators (#13566)
if ( typeof noGlobal === strundefined ) {
	window.jQuery = window.$ = jQuery;
}




return jQuery;

}));
;
/*! jQuery UI - v1.11.1 - 2014-08-13
* http://jqueryui.com
* Includes: core.js, widget.js, mouse.js, position.js, accordion.js, autocomplete.js, button.js, datepicker.js, dialog.js, draggable.js, droppable.js, effect.js, effect-blind.js, effect-bounce.js, effect-clip.js, effect-drop.js, effect-explode.js, effect-fade.js, effect-fold.js, effect-highlight.js, effect-puff.js, effect-pulsate.js, effect-scale.js, effect-shake.js, effect-size.js, effect-slide.js, effect-transfer.js, menu.js, progressbar.js, resizable.js, selectable.js, selectmenu.js, slider.js, sortable.js, spinner.js, tabs.js, tooltip.js
* Copyright 2014 jQuery Foundation and other contributors; Licensed MIT */

(function( factory ) {
	if ( typeof define === "function" && define.amd ) {

		// AMD. Register as an anonymous module.
		define([ "jquery" ], factory );
	} else {

		// Browser globals
		factory( jQuery );
	}
}(function( $ ) {
/*!
 * jQuery UI Core 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/category/ui-core/
 */


// $.ui might exist from components with no dependencies, e.g., $.ui.position
$.ui = $.ui || {};

$.extend( $.ui, {
	version: "1.11.1",

	keyCode: {
		BACKSPACE: 8,
		COMMA: 188,
		DELETE: 46,
		DOWN: 40,
		END: 35,
		ENTER: 13,
		ESCAPE: 27,
		HOME: 36,
		LEFT: 37,
		PAGE_DOWN: 34,
		PAGE_UP: 33,
		PERIOD: 190,
		RIGHT: 39,
		SPACE: 32,
		TAB: 9,
		UP: 38
	}
});

// plugins
$.fn.extend({
	scrollParent: function( includeHidden ) {
		var position = this.css( "position" ),
			excludeStaticParent = position === "absolute",
			overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/,
			scrollParent = this.parents().filter( function() {
				var parent = $( this );
				if ( excludeStaticParent && parent.css( "position" ) === "static" ) {
					return false;
				}
				return overflowRegex.test( parent.css( "overflow" ) + parent.css( "overflow-y" ) + parent.css( "overflow-x" ) );
			}).eq( 0 );

		return position === "fixed" || !scrollParent.length ? $( this[ 0 ].ownerDocument || document ) : scrollParent;
	},

	uniqueId: (function() {
		var uuid = 0;

		return function() {
			return this.each(function() {
				if ( !this.id ) {
					this.id = "ui-id-" + ( ++uuid );
				}
			});
		};
	})(),

	removeUniqueId: function() {
		return this.each(function() {
			if ( /^ui-id-\d+$/.test( this.id ) ) {
				$( this ).removeAttr( "id" );
			}
		});
	}
});

// selectors
function focusable( element, isTabIndexNotNaN ) {
	var map, mapName, img,
		nodeName = element.nodeName.toLowerCase();
	if ( "area" === nodeName ) {
		map = element.parentNode;
		mapName = map.name;
		if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
			return false;
		}
		img = $( "img[usemap='#" + mapName + "']" )[ 0 ];
		return !!img && visible( img );
	}
	return ( /input|select|textarea|button|object/.test( nodeName ) ?
		!element.disabled :
		"a" === nodeName ?
			element.href || isTabIndexNotNaN :
			isTabIndexNotNaN) &&
		// the element and all of its ancestors must be visible
		visible( element );
}

function visible( element ) {
	return $.expr.filters.visible( element ) &&
		!$( element ).parents().addBack().filter(function() {
			return $.css( this, "visibility" ) === "hidden";
		}).length;
}

$.extend( $.expr[ ":" ], {
	data: $.expr.createPseudo ?
		$.expr.createPseudo(function( dataName ) {
			return function( elem ) {
				return !!$.data( elem, dataName );
			};
		}) :
		// support: jQuery <1.8
		function( elem, i, match ) {
			return !!$.data( elem, match[ 3 ] );
		},

	focusable: function( element ) {
		return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
	},

	tabbable: function( element ) {
		var tabIndex = $.attr( element, "tabindex" ),
			isTabIndexNaN = isNaN( tabIndex );
		return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
	}
});

// support: jQuery <1.8
if ( !$( "<a>" ).outerWidth( 1 ).jquery ) {
	$.each( [ "Width", "Height" ], function( i, name ) {
		var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
			type = name.toLowerCase(),
			orig = {
				innerWidth: $.fn.innerWidth,
				innerHeight: $.fn.innerHeight,
				outerWidth: $.fn.outerWidth,
				outerHeight: $.fn.outerHeight
			};

		function reduce( elem, size, border, margin ) {
			$.each( side, function() {
				size -= parseFloat( $.css( elem, "padding" + this ) ) || 0;
				if ( border ) {
					size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0;
				}
				if ( margin ) {
					size -= parseFloat( $.css( elem, "margin" + this ) ) || 0;
				}
			});
			return size;
		}

		$.fn[ "inner" + name ] = function( size ) {
			if ( size === undefined ) {
				return orig[ "inner" + name ].call( this );
			}

			return this.each(function() {
				$( this ).css( type, reduce( this, size ) + "px" );
			});
		};

		$.fn[ "outer" + name] = function( size, margin ) {
			if ( typeof size !== "number" ) {
				return orig[ "outer" + name ].call( this, size );
			}

			return this.each(function() {
				$( this).css( type, reduce( this, size, true, margin ) + "px" );
			});
		};
	});
}

// support: jQuery <1.8
if ( !$.fn.addBack ) {
	$.fn.addBack = function( selector ) {
		return this.add( selector == null ?
			this.prevObject : this.prevObject.filter( selector )
		);
	};
}

// support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413)
if ( $( "<a>" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) {
	$.fn.removeData = (function( removeData ) {
		return function( key ) {
			if ( arguments.length ) {
				return removeData.call( this, $.camelCase( key ) );
			} else {
				return removeData.call( this );
			}
		};
	})( $.fn.removeData );
}

// deprecated
$.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() );

$.fn.extend({
	focus: (function( orig ) {
		return function( delay, fn ) {
			return typeof delay === "number" ?
				this.each(function() {
					var elem = this;
					setTimeout(function() {
						$( elem ).focus();
						if ( fn ) {
							fn.call( elem );
						}
					}, delay );
				}) :
				orig.apply( this, arguments );
		};
	})( $.fn.focus ),

	disableSelection: (function() {
		var eventType = "onselectstart" in document.createElement( "div" ) ?
			"selectstart" :
			"mousedown";

		return function() {
			return this.bind( eventType + ".ui-disableSelection", function( event ) {
				event.preventDefault();
			});
		};
	})(),

	enableSelection: function() {
		return this.unbind( ".ui-disableSelection" );
	},

	zIndex: function( zIndex ) {
		if ( zIndex !== undefined ) {
			return this.css( "zIndex", zIndex );
		}

		if ( this.length ) {
			var elem = $( this[ 0 ] ), position, value;
			while ( elem.length && elem[ 0 ] !== document ) {
				// Ignore z-index if position is set to a value where z-index is ignored by the browser
				// This makes behavior of this function consistent across browsers
				// WebKit always returns auto if the element is positioned
				position = elem.css( "position" );
				if ( position === "absolute" || position === "relative" || position === "fixed" ) {
					// IE returns 0 when zIndex is not specified
					// other browsers return a string
					// we ignore the case of nested elements with an explicit value of 0
					// <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
					value = parseInt( elem.css( "zIndex" ), 10 );
					if ( !isNaN( value ) && value !== 0 ) {
						return value;
					}
				}
				elem = elem.parent();
			}
		}

		return 0;
	}
});

// $.ui.plugin is deprecated. Use $.widget() extensions instead.
$.ui.plugin = {
	add: function( module, option, set ) {
		var i,
			proto = $.ui[ module ].prototype;
		for ( i in set ) {
			proto.plugins[ i ] = proto.plugins[ i ] || [];
			proto.plugins[ i ].push( [ option, set[ i ] ] );
		}
	},
	call: function( instance, name, args, allowDisconnected ) {
		var i,
			set = instance.plugins[ name ];

		if ( !set ) {
			return;
		}

		if ( !allowDisconnected && ( !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) ) {
			return;
		}

		for ( i = 0; i < set.length; i++ ) {
			if ( instance.options[ set[ i ][ 0 ] ] ) {
				set[ i ][ 1 ].apply( instance.element, args );
			}
		}
	}
};


/*!
 * jQuery UI Widget 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/jQuery.widget/
 */


var widget_uuid = 0,
	widget_slice = Array.prototype.slice;

$.cleanData = (function( orig ) {
	return function( elems ) {
		var events, elem, i;
		for ( i = 0; (elem = elems[i]) != null; i++ ) {
			try {

				// Only trigger remove when necessary to save time
				events = $._data( elem, "events" );
				if ( events && events.remove ) {
					$( elem ).triggerHandler( "remove" );
				}

			// http://bugs.jquery.com/ticket/8235
			} catch( e ) {}
		}
		orig( elems );
	};
})( $.cleanData );

$.widget = function( name, base, prototype ) {
	var fullName, existingConstructor, constructor, basePrototype,
		// proxiedPrototype allows the provided prototype to remain unmodified
		// so that it can be used as a mixin for multiple widgets (#8876)
		proxiedPrototype = {},
		namespace = name.split( "." )[ 0 ];

	name = name.split( "." )[ 1 ];
	fullName = namespace + "-" + name;

	if ( !prototype ) {
		prototype = base;
		base = $.Widget;
	}

	// create selector for plugin
	$.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
		return !!$.data( elem, fullName );
	};

	$[ namespace ] = $[ namespace ] || {};
	existingConstructor = $[ namespace ][ name ];
	constructor = $[ namespace ][ name ] = function( options, element ) {
		// allow instantiation without "new" keyword
		if ( !this._createWidget ) {
			return new constructor( options, element );
		}

		// allow instantiation without initializing for simple inheritance
		// must use "new" keyword (the code above always passes args)
		if ( arguments.length ) {
			this._createWidget( options, element );
		}
	};
	// extend with the existing constructor to carry over any static properties
	$.extend( constructor, existingConstructor, {
		version: prototype.version,
		// copy the object used to create the prototype in case we need to
		// redefine the widget later
		_proto: $.extend( {}, prototype ),
		// track widgets that inherit from this widget in case this widget is
		// redefined after a widget inherits from it
		_childConstructors: []
	});

	basePrototype = new base();
	// we need to make the options hash a property directly on the new instance
	// otherwise we'll modify the options hash on the prototype that we're
	// inheriting from
	basePrototype.options = $.widget.extend( {}, basePrototype.options );
	$.each( prototype, function( prop, value ) {
		if ( !$.isFunction( value ) ) {
			proxiedPrototype[ prop ] = value;
			return;
		}
		proxiedPrototype[ prop ] = (function() {
			var _super = function() {
					return base.prototype[ prop ].apply( this, arguments );
				},
				_superApply = function( args ) {
					return base.prototype[ prop ].apply( this, args );
				};
			return function() {
				var __super = this._super,
					__superApply = this._superApply,
					returnValue;

				this._super = _super;
				this._superApply = _superApply;

				returnValue = value.apply( this, arguments );

				this._super = __super;
				this._superApply = __superApply;

				return returnValue;
			};
		})();
	});
	constructor.prototype = $.widget.extend( basePrototype, {
		// TODO: remove support for widgetEventPrefix
		// always use the name + a colon as the prefix, e.g., draggable:start
		// don't prefix for widgets that aren't DOM-based
		widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name
	}, proxiedPrototype, {
		constructor: constructor,
		namespace: namespace,
		widgetName: name,
		widgetFullName: fullName
	});

	// If this widget is being redefined then we need to find all widgets that
	// are inheriting from it and redefine all of them so that they inherit from
	// the new version of this widget. We're essentially trying to replace one
	// level in the prototype chain.
	if ( existingConstructor ) {
		$.each( existingConstructor._childConstructors, function( i, child ) {
			var childPrototype = child.prototype;

			// redefine the child widget using the same prototype that was
			// originally used, but inherit from the new version of the base
			$.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
		});
		// remove the list of existing child constructors from the old constructor
		// so the old child constructors can be garbage collected
		delete existingConstructor._childConstructors;
	} else {
		base._childConstructors.push( constructor );
	}

	$.widget.bridge( name, constructor );

	return constructor;
};

$.widget.extend = function( target ) {
	var input = widget_slice.call( arguments, 1 ),
		inputIndex = 0,
		inputLength = input.length,
		key,
		value;
	for ( ; inputIndex < inputLength; inputIndex++ ) {
		for ( key in input[ inputIndex ] ) {
			value = input[ inputIndex ][ key ];
			if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
				// Clone objects
				if ( $.isPlainObject( value ) ) {
					target[ key ] = $.isPlainObject( target[ key ] ) ?
						$.widget.extend( {}, target[ key ], value ) :
						// Don't extend strings, arrays, etc. with objects
						$.widget.extend( {}, value );
				// Copy everything else by reference
				} else {
					target[ key ] = value;
				}
			}
		}
	}
	return target;
};

$.widget.bridge = function( name, object ) {
	var fullName = object.prototype.widgetFullName || name;
	$.fn[ name ] = function( options ) {
		var isMethodCall = typeof options === "string",
			args = widget_slice.call( arguments, 1 ),
			returnValue = this;

		// allow multiple hashes to be passed on init
		options = !isMethodCall && args.length ?
			$.widget.extend.apply( null, [ options ].concat(args) ) :
			options;

		if ( isMethodCall ) {
			this.each(function() {
				var methodValue,
					instance = $.data( this, fullName );
				if ( options === "instance" ) {
					returnValue = instance;
					return false;
				}
				if ( !instance ) {
					return $.error( "cannot call methods on " + name + " prior to initialization; " +
						"attempted to call method '" + options + "'" );
				}
				if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
					return $.error( "no such method '" + options + "' for " + name + " widget instance" );
				}
				methodValue = instance[ options ].apply( instance, args );
				if ( methodValue !== instance && methodValue !== undefined ) {
					returnValue = methodValue && methodValue.jquery ?
						returnValue.pushStack( methodValue.get() ) :
						methodValue;
					return false;
				}
			});
		} else {
			this.each(function() {
				var instance = $.data( this, fullName );
				if ( instance ) {
					instance.option( options || {} );
					if ( instance._init ) {
						instance._init();
					}
				} else {
					$.data( this, fullName, new object( options, this ) );
				}
			});
		}

		return returnValue;
	};
};

$.Widget = function( /* options, element */ ) {};
$.Widget._childConstructors = [];

$.Widget.prototype = {
	widgetName: "widget",
	widgetEventPrefix: "",
	defaultElement: "<div>",
	options: {
		disabled: false,

		// callbacks
		create: null
	},
	_createWidget: function( options, element ) {
		element = $( element || this.defaultElement || this )[ 0 ];
		this.element = $( element );
		this.uuid = widget_uuid++;
		this.eventNamespace = "." + this.widgetName + this.uuid;
		this.options = $.widget.extend( {},
			this.options,
			this._getCreateOptions(),
			options );

		this.bindings = $();
		this.hoverable = $();
		this.focusable = $();

		if ( element !== this ) {
			$.data( element, this.widgetFullName, this );
			this._on( true, this.element, {
				remove: function( event ) {
					if ( event.target === element ) {
						this.destroy();
					}
				}
			});
			this.document = $( element.style ?
				// element within the document
				element.ownerDocument :
				// element is window or document
				element.document || element );
			this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
		}

		this._create();
		this._trigger( "create", null, this._getCreateEventData() );
		this._init();
	},
	_getCreateOptions: $.noop,
	_getCreateEventData: $.noop,
	_create: $.noop,
	_init: $.noop,

	destroy: function() {
		this._destroy();
		// we can probably remove the unbind calls in 2.0
		// all event bindings should go through this._on()
		this.element
			.unbind( this.eventNamespace )
			.removeData( this.widgetFullName )
			// support: jquery <1.6.3
			// http://bugs.jquery.com/ticket/9413
			.removeData( $.camelCase( this.widgetFullName ) );
		this.widget()
			.unbind( this.eventNamespace )
			.removeAttr( "aria-disabled" )
			.removeClass(
				this.widgetFullName + "-disabled " +
				"ui-state-disabled" );

		// clean up events and states
		this.bindings.unbind( this.eventNamespace );
		this.hoverable.removeClass( "ui-state-hover" );
		this.focusable.removeClass( "ui-state-focus" );
	},
	_destroy: $.noop,

	widget: function() {
		return this.element;
	},

	option: function( key, value ) {
		var options = key,
			parts,
			curOption,
			i;

		if ( arguments.length === 0 ) {
			// don't return a reference to the internal hash
			return $.widget.extend( {}, this.options );
		}

		if ( typeof key === "string" ) {
			// handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
			options = {};
			parts = key.split( "." );
			key = parts.shift();
			if ( parts.length ) {
				curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
				for ( i = 0; i < parts.length - 1; i++ ) {
					curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
					curOption = curOption[ parts[ i ] ];
				}
				key = parts.pop();
				if ( arguments.length === 1 ) {
					return curOption[ key ] === undefined ? null : curOption[ key ];
				}
				curOption[ key ] = value;
			} else {
				if ( arguments.length === 1 ) {
					return this.options[ key ] === undefined ? null : this.options[ key ];
				}
				options[ key ] = value;
			}
		}

		this._setOptions( options );

		return this;
	},
	_setOptions: function( options ) {
		var key;

		for ( key in options ) {
			this._setOption( key, options[ key ] );
		}

		return this;
	},
	_setOption: function( key, value ) {
		this.options[ key ] = value;

		if ( key === "disabled" ) {
			this.widget()
				.toggleClass( this.widgetFullName + "-disabled", !!value );

			// If the widget is becoming disabled, then nothing is interactive
			if ( value ) {
				this.hoverable.removeClass( "ui-state-hover" );
				this.focusable.removeClass( "ui-state-focus" );
			}
		}

		return this;
	},

	enable: function() {
		return this._setOptions({ disabled: false });
	},
	disable: function() {
		return this._setOptions({ disabled: true });
	},

	_on: function( suppressDisabledCheck, element, handlers ) {
		var delegateElement,
			instance = this;

		// no suppressDisabledCheck flag, shuffle arguments
		if ( typeof suppressDisabledCheck !== "boolean" ) {
			handlers = element;
			element = suppressDisabledCheck;
			suppressDisabledCheck = false;
		}

		// no element argument, shuffle and use this.element
		if ( !handlers ) {
			handlers = element;
			element = this.element;
			delegateElement = this.widget();
		} else {
			element = delegateElement = $( element );
			this.bindings = this.bindings.add( element );
		}

		$.each( handlers, function( event, handler ) {
			function handlerProxy() {
				// allow widgets to customize the disabled handling
				// - disabled as an array instead of boolean
				// - disabled class as method for disabling individual parts
				if ( !suppressDisabledCheck &&
						( instance.options.disabled === true ||
							$( this ).hasClass( "ui-state-disabled" ) ) ) {
					return;
				}
				return ( typeof handler === "string" ? instance[ handler ] : handler )
					.apply( instance, arguments );
			}

			// copy the guid so direct unbinding works
			if ( typeof handler !== "string" ) {
				handlerProxy.guid = handler.guid =
					handler.guid || handlerProxy.guid || $.guid++;
			}

			var match = event.match( /^([\w:-]*)\s*(.*)$/ ),
				eventName = match[1] + instance.eventNamespace,
				selector = match[2];
			if ( selector ) {
				delegateElement.delegate( selector, eventName, handlerProxy );
			} else {
				element.bind( eventName, handlerProxy );
			}
		});
	},

	_off: function( element, eventName ) {
		eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace;
		element.unbind( eventName ).undelegate( eventName );
	},

	_delay: function( handler, delay ) {
		function handlerProxy() {
			return ( typeof handler === "string" ? instance[ handler ] : handler )
				.apply( instance, arguments );
		}
		var instance = this;
		return setTimeout( handlerProxy, delay || 0 );
	},

	_hoverable: function( element ) {
		this.hoverable = this.hoverable.add( element );
		this._on( element, {
			mouseenter: function( event ) {
				$( event.currentTarget ).addClass( "ui-state-hover" );
			},
			mouseleave: function( event ) {
				$( event.currentTarget ).removeClass( "ui-state-hover" );
			}
		});
	},

	_focusable: function( element ) {
		this.focusable = this.focusable.add( element );
		this._on( element, {
			focusin: function( event ) {
				$( event.currentTarget ).addClass( "ui-state-focus" );
			},
			focusout: function( event ) {
				$( event.currentTarget ).removeClass( "ui-state-focus" );
			}
		});
	},

	_trigger: function( type, event, data ) {
		var prop, orig,
			callback = this.options[ type ];

		data = data || {};
		event = $.Event( event );
		event.type = ( type === this.widgetEventPrefix ?
			type :
			this.widgetEventPrefix + type ).toLowerCase();
		// the original event may come from any element
		// so we need to reset the target on the new event
		event.target = this.element[ 0 ];

		// copy original event properties over to the new event
		orig = event.originalEvent;
		if ( orig ) {
			for ( prop in orig ) {
				if ( !( prop in event ) ) {
					event[ prop ] = orig[ prop ];
				}
			}
		}

		this.element.trigger( event, data );
		return !( $.isFunction( callback ) &&
			callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
			event.isDefaultPrevented() );
	}
};

$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
	$.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
		if ( typeof options === "string" ) {
			options = { effect: options };
		}
		var hasOptions,
			effectName = !options ?
				method :
				options === true || typeof options === "number" ?
					defaultEffect :
					options.effect || defaultEffect;
		options = options || {};
		if ( typeof options === "number" ) {
			options = { duration: options };
		}
		hasOptions = !$.isEmptyObject( options );
		options.complete = callback;
		if ( options.delay ) {
			element.delay( options.delay );
		}
		if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
			element[ method ]( options );
		} else if ( effectName !== method && element[ effectName ] ) {
			element[ effectName ]( options.duration, options.easing, callback );
		} else {
			element.queue(function( next ) {
				$( this )[ method ]();
				if ( callback ) {
					callback.call( element[ 0 ] );
				}
				next();
			});
		}
	};
});

var widget = $.widget;


/*!
 * jQuery UI Mouse 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/mouse/
 */


var mouseHandled = false;
$( document ).mouseup( function() {
	mouseHandled = false;
});

var mouse = $.widget("ui.mouse", {
	version: "1.11.1",
	options: {
		cancel: "input,textarea,button,select,option",
		distance: 1,
		delay: 0
	},
	_mouseInit: function() {
		var that = this;

		this.element
			.bind("mousedown." + this.widgetName, function(event) {
				return that._mouseDown(event);
			})
			.bind("click." + this.widgetName, function(event) {
				if (true === $.data(event.target, that.widgetName + ".preventClickEvent")) {
					$.removeData(event.target, that.widgetName + ".preventClickEvent");
					event.stopImmediatePropagation();
					return false;
				}
			});

		this.started = false;
	},

	// TODO: make sure destroying one instance of mouse doesn't mess with
	// other instances of mouse
	_mouseDestroy: function() {
		this.element.unbind("." + this.widgetName);
		if ( this._mouseMoveDelegate ) {
			this.document
				.unbind("mousemove." + this.widgetName, this._mouseMoveDelegate)
				.unbind("mouseup." + this.widgetName, this._mouseUpDelegate);
		}
	},

	_mouseDown: function(event) {
		// don't let more than one widget handle mouseStart
		if ( mouseHandled ) {
			return;
		}

		// we may have missed mouseup (out of window)
		(this._mouseStarted && this._mouseUp(event));

		this._mouseDownEvent = event;

		var that = this,
			btnIsLeft = (event.which === 1),
			// event.target.nodeName works around a bug in IE 8 with
			// disabled inputs (#7620)
			elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false);
		if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) {
			return true;
		}

		this.mouseDelayMet = !this.options.delay;
		if (!this.mouseDelayMet) {
			this._mouseDelayTimer = setTimeout(function() {
				that.mouseDelayMet = true;
			}, this.options.delay);
		}

		if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
			this._mouseStarted = (this._mouseStart(event) !== false);
			if (!this._mouseStarted) {
				event.preventDefault();
				return true;
			}
		}

		// Click event may never have fired (Gecko & Opera)
		if (true === $.data(event.target, this.widgetName + ".preventClickEvent")) {
			$.removeData(event.target, this.widgetName + ".preventClickEvent");
		}

		// these delegates are required to keep context
		this._mouseMoveDelegate = function(event) {
			return that._mouseMove(event);
		};
		this._mouseUpDelegate = function(event) {
			return that._mouseUp(event);
		};

		this.document
			.bind( "mousemove." + this.widgetName, this._mouseMoveDelegate )
			.bind( "mouseup." + this.widgetName, this._mouseUpDelegate );

		event.preventDefault();

		mouseHandled = true;
		return true;
	},

	_mouseMove: function(event) {
		// IE mouseup check - mouseup happened when mouse was out of window
		if ($.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button) {
			return this._mouseUp(event);

		// Iframe mouseup check - mouseup occurred in another document
		} else if ( !event.which ) {
			return this._mouseUp( event );
		}

		if (this._mouseStarted) {
			this._mouseDrag(event);
			return event.preventDefault();
		}

		if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
			this._mouseStarted =
				(this._mouseStart(this._mouseDownEvent, event) !== false);
			(this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event));
		}

		return !this._mouseStarted;
	},

	_mouseUp: function(event) {
		this.document
			.unbind( "mousemove." + this.widgetName, this._mouseMoveDelegate )
			.unbind( "mouseup." + this.widgetName, this._mouseUpDelegate );

		if (this._mouseStarted) {
			this._mouseStarted = false;

			if (event.target === this._mouseDownEvent.target) {
				$.data(event.target, this.widgetName + ".preventClickEvent", true);
			}

			this._mouseStop(event);
		}

		mouseHandled = false;
		return false;
	},

	_mouseDistanceMet: function(event) {
		return (Math.max(
				Math.abs(this._mouseDownEvent.pageX - event.pageX),
				Math.abs(this._mouseDownEvent.pageY - event.pageY)
			) >= this.options.distance
		);
	},

	_mouseDelayMet: function(/* event */) {
		return this.mouseDelayMet;
	},

	// These are placeholder methods, to be overriden by extending plugin
	_mouseStart: function(/* event */) {},
	_mouseDrag: function(/* event */) {},
	_mouseStop: function(/* event */) {},
	_mouseCapture: function(/* event */) { return true; }
});


/*!
 * jQuery UI Position 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/position/
 */

(function() {

$.ui = $.ui || {};

var cachedScrollbarWidth, supportsOffsetFractions,
	max = Math.max,
	abs = Math.abs,
	round = Math.round,
	rhorizontal = /left|center|right/,
	rvertical = /top|center|bottom/,
	roffset = /[\+\-]\d+(\.[\d]+)?%?/,
	rposition = /^\w+/,
	rpercent = /%$/,
	_position = $.fn.position;

function getOffsets( offsets, width, height ) {
	return [
		parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
		parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
	];
}

function parseCss( element, property ) {
	return parseInt( $.css( element, property ), 10 ) || 0;
}

function getDimensions( elem ) {
	var raw = elem[0];
	if ( raw.nodeType === 9 ) {
		return {
			width: elem.width(),
			height: elem.height(),
			offset: { top: 0, left: 0 }
		};
	}
	if ( $.isWindow( raw ) ) {
		return {
			width: elem.width(),
			height: elem.height(),
			offset: { top: elem.scrollTop(), left: elem.scrollLeft() }
		};
	}
	if ( raw.preventDefault ) {
		return {
			width: 0,
			height: 0,
			offset: { top: raw.pageY, left: raw.pageX }
		};
	}
	return {
		width: elem.outerWidth(),
		height: elem.outerHeight(),
		offset: elem.offset()
	};
}

$.position = {
	scrollbarWidth: function() {
		if ( cachedScrollbarWidth !== undefined ) {
			return cachedScrollbarWidth;
		}
		var w1, w2,
			div = $( "<div style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>" ),
			innerDiv = div.children()[0];

		$( "body" ).append( div );
		w1 = innerDiv.offsetWidth;
		div.css( "overflow", "scroll" );

		w2 = innerDiv.offsetWidth;

		if ( w1 === w2 ) {
			w2 = div[0].clientWidth;
		}

		div.remove();

		return (cachedScrollbarWidth = w1 - w2);
	},
	getScrollInfo: function( within ) {
		var overflowX = within.isWindow || within.isDocument ? "" :
				within.element.css( "overflow-x" ),
			overflowY = within.isWindow || within.isDocument ? "" :
				within.element.css( "overflow-y" ),
			hasOverflowX = overflowX === "scroll" ||
				( overflowX === "auto" && within.width < within.element[0].scrollWidth ),
			hasOverflowY = overflowY === "scroll" ||
				( overflowY === "auto" && within.height < within.element[0].scrollHeight );
		return {
			width: hasOverflowY ? $.position.scrollbarWidth() : 0,
			height: hasOverflowX ? $.position.scrollbarWidth() : 0
		};
	},
	getWithinInfo: function( element ) {
		var withinElement = $( element || window ),
			isWindow = $.isWindow( withinElement[0] ),
			isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9;
		return {
			element: withinElement,
			isWindow: isWindow,
			isDocument: isDocument,
			offset: withinElement.offset() || { left: 0, top: 0 },
			scrollLeft: withinElement.scrollLeft(),
			scrollTop: withinElement.scrollTop(),

			// support: jQuery 1.6.x
			// jQuery 1.6 doesn't support .outerWidth/Height() on documents or windows
			width: isWindow || isDocument ? withinElement.width() : withinElement.outerWidth(),
			height: isWindow || isDocument ? withinElement.height() : withinElement.outerHeight()
		};
	}
};

$.fn.position = function( options ) {
	if ( !options || !options.of ) {
		return _position.apply( this, arguments );
	}

	// make a copy, we don't want to modify arguments
	options = $.extend( {}, options );

	var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions,
		target = $( options.of ),
		within = $.position.getWithinInfo( options.within ),
		scrollInfo = $.position.getScrollInfo( within ),
		collision = ( options.collision || "flip" ).split( " " ),
		offsets = {};

	dimensions = getDimensions( target );
	if ( target[0].preventDefault ) {
		// force left top to allow flipping
		options.at = "left top";
	}
	targetWidth = dimensions.width;
	targetHeight = dimensions.height;
	targetOffset = dimensions.offset;
	// clone to reuse original targetOffset later
	basePosition = $.extend( {}, targetOffset );

	// force my and at to have valid horizontal and vertical positions
	// if a value is missing or invalid, it will be converted to center
	$.each( [ "my", "at" ], function() {
		var pos = ( options[ this ] || "" ).split( " " ),
			horizontalOffset,
			verticalOffset;

		if ( pos.length === 1) {
			pos = rhorizontal.test( pos[ 0 ] ) ?
				pos.concat( [ "center" ] ) :
				rvertical.test( pos[ 0 ] ) ?
					[ "center" ].concat( pos ) :
					[ "center", "center" ];
		}
		pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
		pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";

		// calculate offsets
		horizontalOffset = roffset.exec( pos[ 0 ] );
		verticalOffset = roffset.exec( pos[ 1 ] );
		offsets[ this ] = [
			horizontalOffset ? horizontalOffset[ 0 ] : 0,
			verticalOffset ? verticalOffset[ 0 ] : 0
		];

		// reduce to just the positions without the offsets
		options[ this ] = [
			rposition.exec( pos[ 0 ] )[ 0 ],
			rposition.exec( pos[ 1 ] )[ 0 ]
		];
	});

	// normalize collision option
	if ( collision.length === 1 ) {
		collision[ 1 ] = collision[ 0 ];
	}

	if ( options.at[ 0 ] === "right" ) {
		basePosition.left += targetWidth;
	} else if ( options.at[ 0 ] === "center" ) {
		basePosition.left += targetWidth / 2;
	}

	if ( options.at[ 1 ] === "bottom" ) {
		basePosition.top += targetHeight;
	} else if ( options.at[ 1 ] === "center" ) {
		basePosition.top += targetHeight / 2;
	}

	atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
	basePosition.left += atOffset[ 0 ];
	basePosition.top += atOffset[ 1 ];

	return this.each(function() {
		var collisionPosition, using,
			elem = $( this ),
			elemWidth = elem.outerWidth(),
			elemHeight = elem.outerHeight(),
			marginLeft = parseCss( this, "marginLeft" ),
			marginTop = parseCss( this, "marginTop" ),
			collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width,
			collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height,
			position = $.extend( {}, basePosition ),
			myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );

		if ( options.my[ 0 ] === "right" ) {
			position.left -= elemWidth;
		} else if ( options.my[ 0 ] === "center" ) {
			position.left -= elemWidth / 2;
		}

		if ( options.my[ 1 ] === "bottom" ) {
			position.top -= elemHeight;
		} else if ( options.my[ 1 ] === "center" ) {
			position.top -= elemHeight / 2;
		}

		position.left += myOffset[ 0 ];
		position.top += myOffset[ 1 ];

		// if the browser doesn't support fractions, then round for consistent results
		if ( !supportsOffsetFractions ) {
			position.left = round( position.left );
			position.top = round( position.top );
		}

		collisionPosition = {
			marginLeft: marginLeft,
			marginTop: marginTop
		};

		$.each( [ "left", "top" ], function( i, dir ) {
			if ( $.ui.position[ collision[ i ] ] ) {
				$.ui.position[ collision[ i ] ][ dir ]( position, {
					targetWidth: targetWidth,
					targetHeight: targetHeight,
					elemWidth: elemWidth,
					elemHeight: elemHeight,
					collisionPosition: collisionPosition,
					collisionWidth: collisionWidth,
					collisionHeight: collisionHeight,
					offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
					my: options.my,
					at: options.at,
					within: within,
					elem: elem
				});
			}
		});

		if ( options.using ) {
			// adds feedback as second argument to using callback, if present
			using = function( props ) {
				var left = targetOffset.left - position.left,
					right = left + targetWidth - elemWidth,
					top = targetOffset.top - position.top,
					bottom = top + targetHeight - elemHeight,
					feedback = {
						target: {
							element: target,
							left: targetOffset.left,
							top: targetOffset.top,
							width: targetWidth,
							height: targetHeight
						},
						element: {
							element: elem,
							left: position.left,
							top: position.top,
							width: elemWidth,
							height: elemHeight
						},
						horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
						vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
					};
				if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
					feedback.horizontal = "center";
				}
				if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
					feedback.vertical = "middle";
				}
				if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
					feedback.important = "horizontal";
				} else {
					feedback.important = "vertical";
				}
				options.using.call( this, props, feedback );
			};
		}

		elem.offset( $.extend( position, { using: using } ) );
	});
};

$.ui.position = {
	fit: {
		left: function( position, data ) {
			var within = data.within,
				withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
				outerWidth = within.width,
				collisionPosLeft = position.left - data.collisionPosition.marginLeft,
				overLeft = withinOffset - collisionPosLeft,
				overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
				newOverRight;

			// element is wider than within
			if ( data.collisionWidth > outerWidth ) {
				// element is initially over the left side of within
				if ( overLeft > 0 && overRight <= 0 ) {
					newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset;
					position.left += overLeft - newOverRight;
				// element is initially over right side of within
				} else if ( overRight > 0 && overLeft <= 0 ) {
					position.left = withinOffset;
				// element is initially over both left and right sides of within
				} else {
					if ( overLeft > overRight ) {
						position.left = withinOffset + outerWidth - data.collisionWidth;
					} else {
						position.left = withinOffset;
					}
				}
			// too far left -> align with left edge
			} else if ( overLeft > 0 ) {
				position.left += overLeft;
			// too far right -> align with right edge
			} else if ( overRight > 0 ) {
				position.left -= overRight;
			// adjust based on position and margin
			} else {
				position.left = max( position.left - collisionPosLeft, position.left );
			}
		},
		top: function( position, data ) {
			var within = data.within,
				withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
				outerHeight = data.within.height,
				collisionPosTop = position.top - data.collisionPosition.marginTop,
				overTop = withinOffset - collisionPosTop,
				overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
				newOverBottom;

			// element is taller than within
			if ( data.collisionHeight > outerHeight ) {
				// element is initially over the top of within
				if ( overTop > 0 && overBottom <= 0 ) {
					newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset;
					position.top += overTop - newOverBottom;
				// element is initially over bottom of within
				} else if ( overBottom > 0 && overTop <= 0 ) {
					position.top = withinOffset;
				// element is initially over both top and bottom of within
				} else {
					if ( overTop > overBottom ) {
						position.top = withinOffset + outerHeight - data.collisionHeight;
					} else {
						position.top = withinOffset;
					}
				}
			// too far up -> align with top
			} else if ( overTop > 0 ) {
				position.top += overTop;
			// too far down -> align with bottom edge
			} else if ( overBottom > 0 ) {
				position.top -= overBottom;
			// adjust based on position and margin
			} else {
				position.top = max( position.top - collisionPosTop, position.top );
			}
		}
	},
	flip: {
		left: function( position, data ) {
			var within = data.within,
				withinOffset = within.offset.left + within.scrollLeft,
				outerWidth = within.width,
				offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
				collisionPosLeft = position.left - data.collisionPosition.marginLeft,
				overLeft = collisionPosLeft - offsetLeft,
				overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
				myOffset = data.my[ 0 ] === "left" ?
					-data.elemWidth :
					data.my[ 0 ] === "right" ?
						data.elemWidth :
						0,
				atOffset = data.at[ 0 ] === "left" ?
					data.targetWidth :
					data.at[ 0 ] === "right" ?
						-data.targetWidth :
						0,
				offset = -2 * data.offset[ 0 ],
				newOverRight,
				newOverLeft;

			if ( overLeft < 0 ) {
				newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset;
				if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
					position.left += myOffset + atOffset + offset;
				}
			} else if ( overRight > 0 ) {
				newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft;
				if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
					position.left += myOffset + atOffset + offset;
				}
			}
		},
		top: function( position, data ) {
			var within = data.within,
				withinOffset = within.offset.top + within.scrollTop,
				outerHeight = within.height,
				offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
				collisionPosTop = position.top - data.collisionPosition.marginTop,
				overTop = collisionPosTop - offsetTop,
				overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
				top = data.my[ 1 ] === "top",
				myOffset = top ?
					-data.elemHeight :
					data.my[ 1 ] === "bottom" ?
						data.elemHeight :
						0,
				atOffset = data.at[ 1 ] === "top" ?
					data.targetHeight :
					data.at[ 1 ] === "bottom" ?
						-data.targetHeight :
						0,
				offset = -2 * data.offset[ 1 ],
				newOverTop,
				newOverBottom;
			if ( overTop < 0 ) {
				newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset;
				if ( ( position.top + myOffset + atOffset + offset) > overTop && ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) ) {
					position.top += myOffset + atOffset + offset;
				}
			} else if ( overBottom > 0 ) {
				newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop;
				if ( ( position.top + myOffset + atOffset + offset) > overBottom && ( newOverTop > 0 || abs( newOverTop ) < overBottom ) ) {
					position.top += myOffset + atOffset + offset;
				}
			}
		}
	},
	flipfit: {
		left: function() {
			$.ui.position.flip.left.apply( this, arguments );
			$.ui.position.fit.left.apply( this, arguments );
		},
		top: function() {
			$.ui.position.flip.top.apply( this, arguments );
			$.ui.position.fit.top.apply( this, arguments );
		}
	}
};

// fraction support test
(function() {
	var testElement, testElementParent, testElementStyle, offsetLeft, i,
		body = document.getElementsByTagName( "body" )[ 0 ],
		div = document.createElement( "div" );

	//Create a "fake body" for testing based on method used in jQuery.support
	testElement = document.createElement( body ? "div" : "body" );
	testElementStyle = {
		visibility: "hidden",
		width: 0,
		height: 0,
		border: 0,
		margin: 0,
		background: "none"
	};
	if ( body ) {
		$.extend( testElementStyle, {
			position: "absolute",
			left: "-1000px",
			top: "-1000px"
		});
	}
	for ( i in testElementStyle ) {
		testElement.style[ i ] = testElementStyle[ i ];
	}
	testElement.appendChild( div );
	testElementParent = body || document.documentElement;
	testElementParent.insertBefore( testElement, testElementParent.firstChild );

	div.style.cssText = "position: absolute; left: 10.7432222px;";

	offsetLeft = $( div ).offset().left;
	supportsOffsetFractions = offsetLeft > 10 && offsetLeft < 11;

	testElement.innerHTML = "";
	testElementParent.removeChild( testElement );
})();

})();

var position = $.ui.position;


/*!
 * jQuery UI Accordion 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/accordion/
 */


var accordion = $.widget( "ui.accordion", {
	version: "1.11.1",
	options: {
		active: 0,
		animate: {},
		collapsible: false,
		event: "click",
		header: "> li > :first-child,> :not(li):even",
		heightStyle: "auto",
		icons: {
			activeHeader: "ui-icon-triangle-1-s",
			header: "ui-icon-triangle-1-e"
		},

		// callbacks
		activate: null,
		beforeActivate: null
	},

	hideProps: {
		borderTopWidth: "hide",
		borderBottomWidth: "hide",
		paddingTop: "hide",
		paddingBottom: "hide",
		height: "hide"
	},

	showProps: {
		borderTopWidth: "show",
		borderBottomWidth: "show",
		paddingTop: "show",
		paddingBottom: "show",
		height: "show"
	},

	_create: function () {
		var options = this.options;
		this.prevShow = this.prevHide = $();
		this.element.addClass( "ui-accordion ui-widget ui-helper-reset" )
			// ARIA
			.attr( "role", "tablist" );

		// don't allow collapsible: false and active: false / null
		if ( !options.collapsible && (options.active === false || options.active == null) ) {
			options.active = 0;
		}

		this._processPanels();
		// handle negative values
		if ( options.active < 0 ) {
			options.active += this.headers.length;
		}
		this._refresh();
	},

	_getCreateEventData: function() {
		return {
			header: this.active,
			panel: !this.active.length ? $() : this.active.next()
		};
	},

	_createIcons: function() {
		var icons = this.options.icons;
		if ( icons ) {
			$( "<span>" )
				.addClass( "ui-accordion-header-icon ui-icon " + icons.header )
				.prependTo( this.headers );
			this.active.children( ".ui-accordion-header-icon" )
				.removeClass( icons.header )
				.addClass( icons.activeHeader );
			this.headers.addClass( "ui-accordion-icons" );
		}
	},

	_destroyIcons: function() {
		this.headers
			.removeClass( "ui-accordion-icons" )
			.children( ".ui-accordion-header-icon" )
				.remove();
	},

	_destroy: function() {
		var contents;

		// clean up main element
		this.element
			.removeClass( "ui-accordion ui-widget ui-helper-reset" )
			.removeAttr( "role" );

		// clean up headers
		this.headers
			.removeClass( "ui-accordion-header ui-accordion-header-active ui-state-default " +
				"ui-corner-all ui-state-active ui-state-disabled ui-corner-top" )
			.removeAttr( "role" )
			.removeAttr( "aria-expanded" )
			.removeAttr( "aria-selected" )
			.removeAttr( "aria-controls" )
			.removeAttr( "tabIndex" )
			.removeUniqueId();

		this._destroyIcons();

		// clean up content panels
		contents = this.headers.next()
			.removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom " +
				"ui-accordion-content ui-accordion-content-active ui-state-disabled" )
			.css( "display", "" )
			.removeAttr( "role" )
			.removeAttr( "aria-hidden" )
			.removeAttr( "aria-labelledby" )
			.removeUniqueId();

		if ( this.options.heightStyle !== "content" ) {
			contents.css( "height", "" );
		}
	},

	_setOption: function( key, value ) {
		if ( key === "active" ) {
			// _activate() will handle invalid values and update this.options
			this._activate( value );
			return;
		}

		if ( key === "event" ) {
			if ( this.options.event ) {
				this._off( this.headers, this.options.event );
			}
			this._setupEvents( value );
		}

		this._super( key, value );

		// setting collapsible: false while collapsed; open first panel
		if ( key === "collapsible" && !value && this.options.active === false ) {
			this._activate( 0 );
		}

		if ( key === "icons" ) {
			this._destroyIcons();
			if ( value ) {
				this._createIcons();
			}
		}

		// #5332 - opacity doesn't cascade to positioned elements in IE
		// so we need to add the disabled class to the headers and panels
		if ( key === "disabled" ) {
			this.element
				.toggleClass( "ui-state-disabled", !!value )
				.attr( "aria-disabled", value );
			this.headers.add( this.headers.next() )
				.toggleClass( "ui-state-disabled", !!value );
		}
	},

	_keydown: function( event ) {
		if ( event.altKey || event.ctrlKey ) {
			return;
		}

		var keyCode = $.ui.keyCode,
			length = this.headers.length,
			currentIndex = this.headers.index( event.target ),
			toFocus = false;

		switch ( event.keyCode ) {
			case keyCode.RIGHT:
			case keyCode.DOWN:
				toFocus = this.headers[ ( currentIndex + 1 ) % length ];
				break;
			case keyCode.LEFT:
			case keyCode.UP:
				toFocus = this.headers[ ( currentIndex - 1 + length ) % length ];
				break;
			case keyCode.SPACE:
			case keyCode.ENTER:
				this._eventHandler( event );
				break;
			case keyCode.HOME:
				toFocus = this.headers[ 0 ];
				break;
			case keyCode.END:
				toFocus = this.headers[ length - 1 ];
				break;
		}

		if ( toFocus ) {
			$( event.target ).attr( "tabIndex", -1 );
			$( toFocus ).attr( "tabIndex", 0 );
			toFocus.focus();
			event.preventDefault();
		}
	},

	_panelKeyDown: function( event ) {
		if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) {
			$( event.currentTarget ).prev().focus();
		}
	},

	refresh: function() {
		var options = this.options;
		this._processPanels();

		// was collapsed or no panel
		if ( ( options.active === false && options.collapsible === true ) || !this.headers.length ) {
			options.active = false;
			this.active = $();
		// active false only when collapsible is true
		} else if ( options.active === false ) {
			this._activate( 0 );
		// was active, but active panel is gone
		} else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
			// all remaining panel are disabled
			if ( this.headers.length === this.headers.find(".ui-state-disabled").length ) {
				options.active = false;
				this.active = $();
			// activate previous panel
			} else {
				this._activate( Math.max( 0, options.active - 1 ) );
			}
		// was active, active panel still exists
		} else {
			// make sure active index is correct
			options.active = this.headers.index( this.active );
		}

		this._destroyIcons();

		this._refresh();
	},

	_processPanels: function() {
		this.headers = this.element.find( this.options.header )
			.addClass( "ui-accordion-header ui-state-default ui-corner-all" );

		this.headers.next()
			.addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" )
			.filter( ":not(.ui-accordion-content-active)" )
			.hide();
	},

	_refresh: function() {
		var maxHeight,
			options = this.options,
			heightStyle = options.heightStyle,
			parent = this.element.parent();

		this.active = this._findActive( options.active )
			.addClass( "ui-accordion-header-active ui-state-active ui-corner-top" )
			.removeClass( "ui-corner-all" );
		this.active.next()
			.addClass( "ui-accordion-content-active" )
			.show();

		this.headers
			.attr( "role", "tab" )
			.each(function() {
				var header = $( this ),
					headerId = header.uniqueId().attr( "id" ),
					panel = header.next(),
					panelId = panel.uniqueId().attr( "id" );
				header.attr( "aria-controls", panelId );
				panel.attr( "aria-labelledby", headerId );
			})
			.next()
				.attr( "role", "tabpanel" );

		this.headers
			.not( this.active )
			.attr({
				"aria-selected": "false",
				"aria-expanded": "false",
				tabIndex: -1
			})
			.next()
				.attr({
					"aria-hidden": "true"
				})
				.hide();

		// make sure at least one header is in the tab order
		if ( !this.active.length ) {
			this.headers.eq( 0 ).attr( "tabIndex", 0 );
		} else {
			this.active.attr({
				"aria-selected": "true",
				"aria-expanded": "true",
				tabIndex: 0
			})
			.next()
				.attr({
					"aria-hidden": "false"
				});
		}

		this._createIcons();

		this._setupEvents( options.event );

		if ( heightStyle === "fill" ) {
			maxHeight = parent.height();
			this.element.siblings( ":visible" ).each(function() {
				var elem = $( this ),
					position = elem.css( "position" );

				if ( position === "absolute" || position === "fixed" ) {
					return;
				}
				maxHeight -= elem.outerHeight( true );
			});

			this.headers.each(function() {
				maxHeight -= $( this ).outerHeight( true );
			});

			this.headers.next()
				.each(function() {
					$( this ).height( Math.max( 0, maxHeight -
						$( this ).innerHeight() + $( this ).height() ) );
				})
				.css( "overflow", "auto" );
		} else if ( heightStyle === "auto" ) {
			maxHeight = 0;
			this.headers.next()
				.each(function() {
					maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() );
				})
				.height( maxHeight );
		}
	},

	_activate: function( index ) {
		var active = this._findActive( index )[ 0 ];

		// trying to activate the already active panel
		if ( active === this.active[ 0 ] ) {
			return;
		}

		// trying to collapse, simulate a click on the currently active header
		active = active || this.active[ 0 ];

		this._eventHandler({
			target: active,
			currentTarget: active,
			preventDefault: $.noop
		});
	},

	_findActive: function( selector ) {
		return typeof selector === "number" ? this.headers.eq( selector ) : $();
	},

	_setupEvents: function( event ) {
		var events = {
			keydown: "_keydown"
		};
		if ( event ) {
			$.each( event.split( " " ), function( index, eventName ) {
				events[ eventName ] = "_eventHandler";
			});
		}

		this._off( this.headers.add( this.headers.next() ) );
		this._on( this.headers, events );
		this._on( this.headers.next(), { keydown: "_panelKeyDown" });
		this._hoverable( this.headers );
		this._focusable( this.headers );
	},

	_eventHandler: function( event ) {
		var options = this.options,
			active = this.active,
			clicked = $( event.currentTarget ),
			clickedIsActive = clicked[ 0 ] === active[ 0 ],
			collapsing = clickedIsActive && options.collapsible,
			toShow = collapsing ? $() : clicked.next(),
			toHide = active.next(),
			eventData = {
				oldHeader: active,
				oldPanel: toHide,
				newHeader: collapsing ? $() : clicked,
				newPanel: toShow
			};

		event.preventDefault();

		if (
				// click on active header, but not collapsible
				( clickedIsActive && !options.collapsible ) ||
				// allow canceling activation
				( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
			return;
		}

		options.active = collapsing ? false : this.headers.index( clicked );

		// when the call to ._toggle() comes after the class changes
		// it causes a very odd bug in IE 8 (see #6720)
		this.active = clickedIsActive ? $() : clicked;
		this._toggle( eventData );

		// switch classes
		// corner classes on the previously active header stay after the animation
		active.removeClass( "ui-accordion-header-active ui-state-active" );
		if ( options.icons ) {
			active.children( ".ui-accordion-header-icon" )
				.removeClass( options.icons.activeHeader )
				.addClass( options.icons.header );
		}

		if ( !clickedIsActive ) {
			clicked
				.removeClass( "ui-corner-all" )
				.addClass( "ui-accordion-header-active ui-state-active ui-corner-top" );
			if ( options.icons ) {
				clicked.children( ".ui-accordion-header-icon" )
					.removeClass( options.icons.header )
					.addClass( options.icons.activeHeader );
			}

			clicked
				.next()
				.addClass( "ui-accordion-content-active" );
		}
	},

	_toggle: function( data ) {
		var toShow = data.newPanel,
			toHide = this.prevShow.length ? this.prevShow : data.oldPanel;

		// handle activating a panel during the animation for another activation
		this.prevShow.add( this.prevHide ).stop( true, true );
		this.prevShow = toShow;
		this.prevHide = toHide;

		if ( this.options.animate ) {
			this._animate( toShow, toHide, data );
		} else {
			toHide.hide();
			toShow.show();
			this._toggleComplete( data );
		}

		toHide.attr({
			"aria-hidden": "true"
		});
		toHide.prev().attr( "aria-selected", "false" );
		// if we're switching panels, remove the old header from the tab order
		// if we're opening from collapsed state, remove the previous header from the tab order
		// if we're collapsing, then keep the collapsing header in the tab order
		if ( toShow.length && toHide.length ) {
			toHide.prev().attr({
				"tabIndex": -1,
				"aria-expanded": "false"
			});
		} else if ( toShow.length ) {
			this.headers.filter(function() {
				return $( this ).attr( "tabIndex" ) === 0;
			})
			.attr( "tabIndex", -1 );
		}

		toShow
			.attr( "aria-hidden", "false" )
			.prev()
				.attr({
					"aria-selected": "true",
					tabIndex: 0,
					"aria-expanded": "true"
				});
	},

	_animate: function( toShow, toHide, data ) {
		var total, easing, duration,
			that = this,
			adjust = 0,
			down = toShow.length &&
				( !toHide.length || ( toShow.index() < toHide.index() ) ),
			animate = this.options.animate || {},
			options = down && animate.down || animate,
			complete = function() {
				that._toggleComplete( data );
			};

		if ( typeof options === "number" ) {
			duration = options;
		}
		if ( typeof options === "string" ) {
			easing = options;
		}
		// fall back from options to animation in case of partial down settings
		easing = easing || options.easing || animate.easing;
		duration = duration || options.duration || animate.duration;

		if ( !toHide.length ) {
			return toShow.animate( this.showProps, duration, easing, complete );
		}
		if ( !toShow.length ) {
			return toHide.animate( this.hideProps, duration, easing, complete );
		}

		total = toShow.show().outerHeight();
		toHide.animate( this.hideProps, {
			duration: duration,
			easing: easing,
			step: function( now, fx ) {
				fx.now = Math.round( now );
			}
		});
		toShow
			.hide()
			.animate( this.showProps, {
				duration: duration,
				easing: easing,
				complete: complete,
				step: function( now, fx ) {
					fx.now = Math.round( now );
					if ( fx.prop !== "height" ) {
						adjust += fx.now;
					} else if ( that.options.heightStyle !== "content" ) {
						fx.now = Math.round( total - toHide.outerHeight() - adjust );
						adjust = 0;
					}
				}
			});
	},

	_toggleComplete: function( data ) {
		var toHide = data.oldPanel;

		toHide
			.removeClass( "ui-accordion-content-active" )
			.prev()
				.removeClass( "ui-corner-top" )
				.addClass( "ui-corner-all" );

		// Work around for rendering bug in IE (#5421)
		if ( toHide.length ) {
			toHide.parent()[ 0 ].className = toHide.parent()[ 0 ].className;
		}
		this._trigger( "activate", null, data );
	}
});


/*!
 * jQuery UI Menu 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/menu/
 */


var menu = $.widget( "ui.menu", {
	version: "1.11.1",
	defaultElement: "<ul>",
	delay: 300,
	options: {
		icons: {
			submenu: "ui-icon-carat-1-e"
		},
		items: "> *",
		menus: "ul",
		position: {
			my: "left-1 top",
			at: "right top"
		},
		role: "menu",

		// callbacks
		blur: null,
		focus: null,
		select: null
	},

	_create: function() {
		this.activeMenu = this.element;

		// Flag used to prevent firing of the click handler
		// as the event bubbles up through nested menus
		this.mouseHandled = false;
		this.element
			.uniqueId()
			.addClass( "ui-menu ui-widget ui-widget-content" )
			.toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length )
			.attr({
				role: this.options.role,
				tabIndex: 0
			});

		if ( this.options.disabled ) {
			this.element
				.addClass( "ui-state-disabled" )
				.attr( "aria-disabled", "true" );
		}

		this._on({
			// Prevent focus from sticking to links inside menu after clicking
			// them (focus should always stay on UL during navigation).
			"mousedown .ui-menu-item": function( event ) {
				event.preventDefault();
			},
			"click .ui-menu-item": function( event ) {
				var target = $( event.target );
				if ( !this.mouseHandled && target.not( ".ui-state-disabled" ).length ) {
					this.select( event );

					// Only set the mouseHandled flag if the event will bubble, see #9469.
					if ( !event.isPropagationStopped() ) {
						this.mouseHandled = true;
					}

					// Open submenu on click
					if ( target.has( ".ui-menu" ).length ) {
						this.expand( event );
					} else if ( !this.element.is( ":focus" ) && $( this.document[ 0 ].activeElement ).closest( ".ui-menu" ).length ) {

						// Redirect focus to the menu
						this.element.trigger( "focus", [ true ] );

						// If the active item is on the top level, let it stay active.
						// Otherwise, blur the active item since it is no longer visible.
						if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) {
							clearTimeout( this.timer );
						}
					}
				}
			},
			"mouseenter .ui-menu-item": function( event ) {
				var target = $( event.currentTarget );
				// Remove ui-state-active class from siblings of the newly focused menu item
				// to avoid a jump caused by adjacent elements both having a class with a border
				target.siblings( ".ui-state-active" ).removeClass( "ui-state-active" );
				this.focus( event, target );
			},
			mouseleave: "collapseAll",
			"mouseleave .ui-menu": "collapseAll",
			focus: function( event, keepActiveItem ) {
				// If there's already an active item, keep it active
				// If not, activate the first item
				var item = this.active || this.element.find( this.options.items ).eq( 0 );

				if ( !keepActiveItem ) {
					this.focus( event, item );
				}
			},
			blur: function( event ) {
				this._delay(function() {
					if ( !$.contains( this.element[0], this.document[0].activeElement ) ) {
						this.collapseAll( event );
					}
				});
			},
			keydown: "_keydown"
		});

		this.refresh();

		// Clicks outside of a menu collapse any open menus
		this._on( this.document, {
			click: function( event ) {
				if ( this._closeOnDocumentClick( event ) ) {
					this.collapseAll( event );
				}

				// Reset the mouseHandled flag
				this.mouseHandled = false;
			}
		});
	},

	_destroy: function() {
		// Destroy (sub)menus
		this.element
			.removeAttr( "aria-activedescendant" )
			.find( ".ui-menu" ).addBack()
				.removeClass( "ui-menu ui-widget ui-widget-content ui-menu-icons ui-front" )
				.removeAttr( "role" )
				.removeAttr( "tabIndex" )
				.removeAttr( "aria-labelledby" )
				.removeAttr( "aria-expanded" )
				.removeAttr( "aria-hidden" )
				.removeAttr( "aria-disabled" )
				.removeUniqueId()
				.show();

		// Destroy menu items
		this.element.find( ".ui-menu-item" )
			.removeClass( "ui-menu-item" )
			.removeAttr( "role" )
			.removeAttr( "aria-disabled" )
			.removeUniqueId()
			.removeClass( "ui-state-hover" )
			.removeAttr( "tabIndex" )
			.removeAttr( "role" )
			.removeAttr( "aria-haspopup" )
			.children().each( function() {
				var elem = $( this );
				if ( elem.data( "ui-menu-submenu-carat" ) ) {
					elem.remove();
				}
			});

		// Destroy menu dividers
		this.element.find( ".ui-menu-divider" ).removeClass( "ui-menu-divider ui-widget-content" );
	},

	_keydown: function( event ) {
		var match, prev, character, skip, regex,
			preventDefault = true;

		function escape( value ) {
			return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" );
		}

		switch ( event.keyCode ) {
		case $.ui.keyCode.PAGE_UP:
			this.previousPage( event );
			break;
		case $.ui.keyCode.PAGE_DOWN:
			this.nextPage( event );
			break;
		case $.ui.keyCode.HOME:
			this._move( "first", "first", event );
			break;
		case $.ui.keyCode.END:
			this._move( "last", "last", event );
			break;
		case $.ui.keyCode.UP:
			this.previous( event );
			break;
		case $.ui.keyCode.DOWN:
			this.next( event );
			break;
		case $.ui.keyCode.LEFT:
			this.collapse( event );
			break;
		case $.ui.keyCode.RIGHT:
			if ( this.active && !this.active.is( ".ui-state-disabled" ) ) {
				this.expand( event );
			}
			break;
		case $.ui.keyCode.ENTER:
		case $.ui.keyCode.SPACE:
			this._activate( event );
			break;
		case $.ui.keyCode.ESCAPE:
			this.collapse( event );
			break;
		default:
			preventDefault = false;
			prev = this.previousFilter || "";
			character = String.fromCharCode( event.keyCode );
			skip = false;

			clearTimeout( this.filterTimer );

			if ( character === prev ) {
				skip = true;
			} else {
				character = prev + character;
			}

			regex = new RegExp( "^" + escape( character ), "i" );
			match = this.activeMenu.find( this.options.items ).filter(function() {
				return regex.test( $( this ).text() );
			});
			match = skip && match.index( this.active.next() ) !== -1 ?
				this.active.nextAll( ".ui-menu-item" ) :
				match;

			// If no matches on the current filter, reset to the last character pressed
			// to move down the menu to the first item that starts with that character
			if ( !match.length ) {
				character = String.fromCharCode( event.keyCode );
				regex = new RegExp( "^" + escape( character ), "i" );
				match = this.activeMenu.find( this.options.items ).filter(function() {
					return regex.test( $( this ).text() );
				});
			}

			if ( match.length ) {
				this.focus( event, match );
				if ( match.length > 1 ) {
					this.previousFilter = character;
					this.filterTimer = this._delay(function() {
						delete this.previousFilter;
					}, 1000 );
				} else {
					delete this.previousFilter;
				}
			} else {
				delete this.previousFilter;
			}
		}

		if ( preventDefault ) {
			event.preventDefault();
		}
	},

	_activate: function( event ) {
		if ( !this.active.is( ".ui-state-disabled" ) ) {
			if ( this.active.is( "[aria-haspopup='true']" ) ) {
				this.expand( event );
			} else {
				this.select( event );
			}
		}
	},

	refresh: function() {
		var menus, items,
			that = this,
			icon = this.options.icons.submenu,
			submenus = this.element.find( this.options.menus );

		this.element.toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length );

		// Initialize nested menus
		submenus.filter( ":not(.ui-menu)" )
			.addClass( "ui-menu ui-widget ui-widget-content ui-front" )
			.hide()
			.attr({
				role: this.options.role,
				"aria-hidden": "true",
				"aria-expanded": "false"
			})
			.each(function() {
				var menu = $( this ),
					item = menu.parent(),
					submenuCarat = $( "<span>" )
						.addClass( "ui-menu-icon ui-icon " + icon )
						.data( "ui-menu-submenu-carat", true );

				item
					.attr( "aria-haspopup", "true" )
					.prepend( submenuCarat );
				menu.attr( "aria-labelledby", item.attr( "id" ) );
			});

		menus = submenus.add( this.element );
		items = menus.find( this.options.items );

		// Initialize menu-items containing spaces and/or dashes only as dividers
		items.not( ".ui-menu-item" ).each(function() {
			var item = $( this );
			if ( that._isDivider( item ) ) {
				item.addClass( "ui-widget-content ui-menu-divider" );
			}
		});

		// Don't refresh list items that are already adapted
		items.not( ".ui-menu-item, .ui-menu-divider" )
			.addClass( "ui-menu-item" )
			.uniqueId()
			.attr({
				tabIndex: -1,
				role: this._itemRole()
			});

		// Add aria-disabled attribute to any disabled menu item
		items.filter( ".ui-state-disabled" ).attr( "aria-disabled", "true" );

		// If the active item has been removed, blur the menu
		if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
			this.blur();
		}
	},

	_itemRole: function() {
		return {
			menu: "menuitem",
			listbox: "option"
		}[ this.options.role ];
	},

	_setOption: function( key, value ) {
		if ( key === "icons" ) {
			this.element.find( ".ui-menu-icon" )
				.removeClass( this.options.icons.submenu )
				.addClass( value.submenu );
		}
		if ( key === "disabled" ) {
			this.element
				.toggleClass( "ui-state-disabled", !!value )
				.attr( "aria-disabled", value );
		}
		this._super( key, value );
	},

	focus: function( event, item ) {
		var nested, focused;
		this.blur( event, event && event.type === "focus" );

		this._scrollIntoView( item );

		this.active = item.first();
		focused = this.active.addClass( "ui-state-focus" ).removeClass( "ui-state-active" );
		// Only update aria-activedescendant if there's a role
		// otherwise we assume focus is managed elsewhere
		if ( this.options.role ) {
			this.element.attr( "aria-activedescendant", focused.attr( "id" ) );
		}

		// Highlight active parent menu item, if any
		this.active
			.parent()
			.closest( ".ui-menu-item" )
			.addClass( "ui-state-active" );

		if ( event && event.type === "keydown" ) {
			this._close();
		} else {
			this.timer = this._delay(function() {
				this._close();
			}, this.delay );
		}

		nested = item.children( ".ui-menu" );
		if ( nested.length && event && ( /^mouse/.test( event.type ) ) ) {
			this._startOpening(nested);
		}
		this.activeMenu = item.parent();

		this._trigger( "focus", event, { item: item } );
	},

	_scrollIntoView: function( item ) {
		var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight;
		if ( this._hasScroll() ) {
			borderTop = parseFloat( $.css( this.activeMenu[0], "borderTopWidth" ) ) || 0;
			paddingTop = parseFloat( $.css( this.activeMenu[0], "paddingTop" ) ) || 0;
			offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop;
			scroll = this.activeMenu.scrollTop();
			elementHeight = this.activeMenu.height();
			itemHeight = item.outerHeight();

			if ( offset < 0 ) {
				this.activeMenu.scrollTop( scroll + offset );
			} else if ( offset + itemHeight > elementHeight ) {
				this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight );
			}
		}
	},

	blur: function( event, fromFocus ) {
		if ( !fromFocus ) {
			clearTimeout( this.timer );
		}

		if ( !this.active ) {
			return;
		}

		this.active.removeClass( "ui-state-focus" );
		this.active = null;

		this._trigger( "blur", event, { item: this.active } );
	},

	_startOpening: function( submenu ) {
		clearTimeout( this.timer );

		// Don't open if already open fixes a Firefox bug that caused a .5 pixel
		// shift in the submenu position when mousing over the carat icon
		if ( submenu.attr( "aria-hidden" ) !== "true" ) {
			return;
		}

		this.timer = this._delay(function() {
			this._close();
			this._open( submenu );
		}, this.delay );
	},

	_open: function( submenu ) {
		var position = $.extend({
			of: this.active
		}, this.options.position );

		clearTimeout( this.timer );
		this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) )
			.hide()
			.attr( "aria-hidden", "true" );

		submenu
			.show()
			.removeAttr( "aria-hidden" )
			.attr( "aria-expanded", "true" )
			.position( position );
	},

	collapseAll: function( event, all ) {
		clearTimeout( this.timer );
		this.timer = this._delay(function() {
			// If we were passed an event, look for the submenu that contains the event
			var currentMenu = all ? this.element :
				$( event && event.target ).closest( this.element.find( ".ui-menu" ) );

			// If we found no valid submenu ancestor, use the main menu to close all sub menus anyway
			if ( !currentMenu.length ) {
				currentMenu = this.element;
			}

			this._close( currentMenu );

			this.blur( event );
			this.activeMenu = currentMenu;
		}, this.delay );
	},

	// With no arguments, closes the currently active menu - if nothing is active
	// it closes all menus.  If passed an argument, it will search for menus BELOW
	_close: function( startMenu ) {
		if ( !startMenu ) {
			startMenu = this.active ? this.active.parent() : this.element;
		}

		startMenu
			.find( ".ui-menu" )
				.hide()
				.attr( "aria-hidden", "true" )
				.attr( "aria-expanded", "false" )
			.end()
			.find( ".ui-state-active" ).not( ".ui-state-focus" )
				.removeClass( "ui-state-active" );
	},

	_closeOnDocumentClick: function( event ) {
		return !$( event.target ).closest( ".ui-menu" ).length;
	},

	_isDivider: function( item ) {

		// Match hyphen, em dash, en dash
		return !/[^\-\u2014\u2013\s]/.test( item.text() );
	},

	collapse: function( event ) {
		var newItem = this.active &&
			this.active.parent().closest( ".ui-menu-item", this.element );
		if ( newItem && newItem.length ) {
			this._close();
			this.focus( event, newItem );
		}
	},

	expand: function( event ) {
		var newItem = this.active &&
			this.active
				.children( ".ui-menu " )
				.find( this.options.items )
				.first();

		if ( newItem && newItem.length ) {
			this._open( newItem.parent() );

			// Delay so Firefox will not hide activedescendant change in expanding submenu from AT
			this._delay(function() {
				this.focus( event, newItem );
			});
		}
	},

	next: function( event ) {
		this._move( "next", "first", event );
	},

	previous: function( event ) {
		this._move( "prev", "last", event );
	},

	isFirstItem: function() {
		return this.active && !this.active.prevAll( ".ui-menu-item" ).length;
	},

	isLastItem: function() {
		return this.active && !this.active.nextAll( ".ui-menu-item" ).length;
	},

	_move: function( direction, filter, event ) {
		var next;
		if ( this.active ) {
			if ( direction === "first" || direction === "last" ) {
				next = this.active
					[ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" )
					.eq( -1 );
			} else {
				next = this.active
					[ direction + "All" ]( ".ui-menu-item" )
					.eq( 0 );
			}
		}
		if ( !next || !next.length || !this.active ) {
			next = this.activeMenu.find( this.options.items )[ filter ]();
		}

		this.focus( event, next );
	},

	nextPage: function( event ) {
		var item, base, height;

		if ( !this.active ) {
			this.next( event );
			return;
		}
		if ( this.isLastItem() ) {
			return;
		}
		if ( this._hasScroll() ) {
			base = this.active.offset().top;
			height = this.element.height();
			this.active.nextAll( ".ui-menu-item" ).each(function() {
				item = $( this );
				return item.offset().top - base - height < 0;
			});

			this.focus( event, item );
		} else {
			this.focus( event, this.activeMenu.find( this.options.items )
				[ !this.active ? "first" : "last" ]() );
		}
	},

	previousPage: function( event ) {
		var item, base, height;
		if ( !this.active ) {
			this.next( event );
			return;
		}
		if ( this.isFirstItem() ) {
			return;
		}
		if ( this._hasScroll() ) {
			base = this.active.offset().top;
			height = this.element.height();
			this.active.prevAll( ".ui-menu-item" ).each(function() {
				item = $( this );
				return item.offset().top - base + height > 0;
			});

			this.focus( event, item );
		} else {
			this.focus( event, this.activeMenu.find( this.options.items ).first() );
		}
	},

	_hasScroll: function() {
		return this.element.outerHeight() < this.element.prop( "scrollHeight" );
	},

	select: function( event ) {
		// TODO: It should never be possible to not have an active item at this
		// point, but the tests don't trigger mouseenter before click.
		this.active = this.active || $( event.target ).closest( ".ui-menu-item" );
		var ui = { item: this.active };
		if ( !this.active.has( ".ui-menu" ).length ) {
			this.collapseAll( event, true );
		}
		this._trigger( "select", event, ui );
	}
});


/*!
 * jQuery UI Autocomplete 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/autocomplete/
 */


$.widget( "ui.autocomplete", {
	version: "1.11.1",
	defaultElement: "<input>",
	options: {
		appendTo: null,
		autoFocus: false,
		delay: 300,
		minLength: 1,
		position: {
			my: "left top",
			at: "left bottom",
			collision: "none"
		},
		source: null,

		// callbacks
		change: null,
		close: null,
		focus: null,
		open: null,
		response: null,
		search: null,
		select: null
	},

	requestIndex: 0,
	pending: 0,

	_create: function() {
		// Some browsers only repeat keydown events, not keypress events,
		// so we use the suppressKeyPress flag to determine if we've already
		// handled the keydown event. #7269
		// Unfortunately the code for & in keypress is the same as the up arrow,
		// so we use the suppressKeyPressRepeat flag to avoid handling keypress
		// events when we know the keydown event was used to modify the
		// search term. #7799
		var suppressKeyPress, suppressKeyPressRepeat, suppressInput,
			nodeName = this.element[ 0 ].nodeName.toLowerCase(),
			isTextarea = nodeName === "textarea",
			isInput = nodeName === "input";

		this.isMultiLine =
			// Textareas are always multi-line
			isTextarea ? true :
			// Inputs are always single-line, even if inside a contentEditable element
			// IE also treats inputs as contentEditable
			isInput ? false :
			// All other element types are determined by whether or not they're contentEditable
			this.element.prop( "isContentEditable" );

		this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ];
		this.isNewMenu = true;

		this.element
			.addClass( "ui-autocomplete-input" )
			.attr( "autocomplete", "off" );

		this._on( this.element, {
			keydown: function( event ) {
				if ( this.element.prop( "readOnly" ) ) {
					suppressKeyPress = true;
					suppressInput = true;
					suppressKeyPressRepeat = true;
					return;
				}

				suppressKeyPress = false;
				suppressInput = false;
				suppressKeyPressRepeat = false;
				var keyCode = $.ui.keyCode;
				switch ( event.keyCode ) {
				case keyCode.PAGE_UP:
					suppressKeyPress = true;
					this._move( "previousPage", event );
					break;
				case keyCode.PAGE_DOWN:
					suppressKeyPress = true;
					this._move( "nextPage", event );
					break;
				case keyCode.UP:
					suppressKeyPress = true;
					this._keyEvent( "previous", event );
					break;
				case keyCode.DOWN:
					suppressKeyPress = true;
					this._keyEvent( "next", event );
					break;
				case keyCode.ENTER:
					// when menu is open and has focus
					if ( this.menu.active ) {
						// #6055 - Opera still allows the keypress to occur
						// which causes forms to submit
						suppressKeyPress = true;
						event.preventDefault();
						this.menu.select( event );
					}
					break;
				case keyCode.TAB:
					if ( this.menu.active ) {
						this.menu.select( event );
					}
					break;
				case keyCode.ESCAPE:
					if ( this.menu.element.is( ":visible" ) ) {
						if ( !this.isMultiLine ) {
							this._value( this.term );
						}
						this.close( event );
						// Different browsers have different default behavior for escape
						// Single press can mean undo or clear
						// Double press in IE means clear the whole form
						event.preventDefault();
					}
					break;
				default:
					suppressKeyPressRepeat = true;
					// search timeout should be triggered before the input value is changed
					this._searchTimeout( event );
					break;
				}
			},
			keypress: function( event ) {
				if ( suppressKeyPress ) {
					suppressKeyPress = false;
					if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
						event.preventDefault();
					}
					return;
				}
				if ( suppressKeyPressRepeat ) {
					return;
				}

				// replicate some key handlers to allow them to repeat in Firefox and Opera
				var keyCode = $.ui.keyCode;
				switch ( event.keyCode ) {
				case keyCode.PAGE_UP:
					this._move( "previousPage", event );
					break;
				case keyCode.PAGE_DOWN:
					this._move( "nextPage", event );
					break;
				case keyCode.UP:
					this._keyEvent( "previous", event );
					break;
				case keyCode.DOWN:
					this._keyEvent( "next", event );
					break;
				}
			},
			input: function( event ) {
				if ( suppressInput ) {
					suppressInput = false;
					event.preventDefault();
					return;
				}
				this._searchTimeout( event );
			},
			focus: function() {
				this.selectedItem = null;
				this.previous = this._value();
			},
			blur: function( event ) {
				if ( this.cancelBlur ) {
					delete this.cancelBlur;
					return;
				}

				clearTimeout( this.searching );
				this.close( event );
				this._change( event );
			}
		});

		this._initSource();
		this.menu = $( "<ul>" )
			.addClass( "ui-autocomplete ui-front" )
			.appendTo( this._appendTo() )
			.menu({
				// disable ARIA support, the live region takes care of that
				role: null
			})
			.hide()
			.menu( "instance" );

		this._on( this.menu.element, {
			mousedown: function( event ) {
				// prevent moving focus out of the text field
				event.preventDefault();

				// IE doesn't prevent moving focus even with event.preventDefault()
				// so we set a flag to know when we should ignore the blur event
				this.cancelBlur = true;
				this._delay(function() {
					delete this.cancelBlur;
				});

				// clicking on the scrollbar causes focus to shift to the body
				// but we can't detect a mouseup or a click immediately afterward
				// so we have to track the next mousedown and close the menu if
				// the user clicks somewhere outside of the autocomplete
				var menuElement = this.menu.element[ 0 ];
				if ( !$( event.target ).closest( ".ui-menu-item" ).length ) {
					this._delay(function() {
						var that = this;
						this.document.one( "mousedown", function( event ) {
							if ( event.target !== that.element[ 0 ] &&
									event.target !== menuElement &&
									!$.contains( menuElement, event.target ) ) {
								that.close();
							}
						});
					});
				}
			},
			menufocus: function( event, ui ) {
				var label, item;
				// support: Firefox
				// Prevent accidental activation of menu items in Firefox (#7024 #9118)
				if ( this.isNewMenu ) {
					this.isNewMenu = false;
					if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) {
						this.menu.blur();

						this.document.one( "mousemove", function() {
							$( event.target ).trigger( event.originalEvent );
						});

						return;
					}
				}

				item = ui.item.data( "ui-autocomplete-item" );
				if ( false !== this._trigger( "focus", event, { item: item } ) ) {
					// use value to match what will end up in the input, if it was a key event
					if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) {
						this._value( item.value );
					}
				}

				// Announce the value in the liveRegion
				label = ui.item.attr( "aria-label" ) || item.value;
				if ( label && $.trim( label ).length ) {
					this.liveRegion.children().hide();
					$( "<div>" ).text( label ).appendTo( this.liveRegion );
				}
			},
			menuselect: function( event, ui ) {
				var item = ui.item.data( "ui-autocomplete-item" ),
					previous = this.previous;

				// only trigger when focus was lost (click on menu)
				if ( this.element[ 0 ] !== this.document[ 0 ].activeElement ) {
					this.element.focus();
					this.previous = previous;
					// #6109 - IE triggers two focus events and the second
					// is asynchronous, so we need to reset the previous
					// term synchronously and asynchronously :-(
					this._delay(function() {
						this.previous = previous;
						this.selectedItem = item;
					});
				}

				if ( false !== this._trigger( "select", event, { item: item } ) ) {
					this._value( item.value );
				}
				// reset the term after the select event
				// this allows custom select handling to work properly
				this.term = this._value();

				this.close( event );
				this.selectedItem = item;
			}
		});

		this.liveRegion = $( "<span>", {
				role: "status",
				"aria-live": "assertive",
				"aria-relevant": "additions"
			})
			.addClass( "ui-helper-hidden-accessible" )
			.appendTo( this.document[ 0 ].body );

		// turning off autocomplete prevents the browser from remembering the
		// value when navigating through history, so we re-enable autocomplete
		// if the page is unloaded before the widget is destroyed. #7790
		this._on( this.window, {
			beforeunload: function() {
				this.element.removeAttr( "autocomplete" );
			}
		});
	},

	_destroy: function() {
		clearTimeout( this.searching );
		this.element
			.removeClass( "ui-autocomplete-input" )
			.removeAttr( "autocomplete" );
		this.menu.element.remove();
		this.liveRegion.remove();
	},

	_setOption: function( key, value ) {
		this._super( key, value );
		if ( key === "source" ) {
			this._initSource();
		}
		if ( key === "appendTo" ) {
			this.menu.element.appendTo( this._appendTo() );
		}
		if ( key === "disabled" && value && this.xhr ) {
			this.xhr.abort();
		}
	},

	_appendTo: function() {
		var element = this.options.appendTo;

		if ( element ) {
			element = element.jquery || element.nodeType ?
				$( element ) :
				this.document.find( element ).eq( 0 );
		}

		if ( !element || !element[ 0 ] ) {
			element = this.element.closest( ".ui-front" );
		}

		if ( !element.length ) {
			element = this.document[ 0 ].body;
		}

		return element;
	},

	_initSource: function() {
		var array, url,
			that = this;
		if ( $.isArray( this.options.source ) ) {
			array = this.options.source;
			this.source = function( request, response ) {
				response( $.ui.autocomplete.filter( array, request.term ) );
			};
		} else if ( typeof this.options.source === "string" ) {
			url = this.options.source;
			this.source = function( request, response ) {
				if ( that.xhr ) {
					that.xhr.abort();
				}
				that.xhr = $.ajax({
					url: url,
					data: request,
					dataType: "json",
					success: function( data ) {
						response( data );
					},
					error: function() {
						response([]);
					}
				});
			};
		} else {
			this.source = this.options.source;
		}
	},

	_searchTimeout: function( event ) {
		clearTimeout( this.searching );
		this.searching = this._delay(function() {

			// Search if the value has changed, or if the user retypes the same value (see #7434)
			var equalValues = this.term === this._value(),
				menuVisible = this.menu.element.is( ":visible" ),
				modifierKey = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;

			if ( !equalValues || ( equalValues && !menuVisible && !modifierKey ) ) {
				this.selectedItem = null;
				this.search( null, event );
			}
		}, this.options.delay );
	},

	search: function( value, event ) {
		value = value != null ? value : this._value();

		// always save the actual value, not the one passed as an argument
		this.term = this._value();

		if ( value.length < this.options.minLength ) {
			return this.close( event );
		}

		if ( this._trigger( "search", event ) === false ) {
			return;
		}

		return this._search( value );
	},

	_search: function( value ) {
		this.pending++;
		this.element.addClass( "ui-autocomplete-loading" );
		this.cancelSearch = false;

		this.source( { term: value }, this._response() );
	},

	_response: function() {
		var index = ++this.requestIndex;

		return $.proxy(function( content ) {
			if ( index === this.requestIndex ) {
				this.__response( content );
			}

			this.pending--;
			if ( !this.pending ) {
				this.element.removeClass( "ui-autocomplete-loading" );
			}
		}, this );
	},

	__response: function( content ) {
		if ( content ) {
			content = this._normalize( content );
		}
		this._trigger( "response", null, { content: content } );
		if ( !this.options.disabled && content && content.length && !this.cancelSearch ) {
			this._suggest( content );
			this._trigger( "open" );
		} else {
			// use ._close() instead of .close() so we don't cancel future searches
			this._close();
		}
	},

	close: function( event ) {
		this.cancelSearch = true;
		this._close( event );
	},

	_close: function( event ) {
		if ( this.menu.element.is( ":visible" ) ) {
			this.menu.element.hide();
			this.menu.blur();
			this.isNewMenu = true;
			this._trigger( "close", event );
		}
	},

	_change: function( event ) {
		if ( this.previous !== this._value() ) {
			this._trigger( "change", event, { item: this.selectedItem } );
		}
	},

	_normalize: function( items ) {
		// assume all items have the right format when the first item is complete
		if ( items.length && items[ 0 ].label && items[ 0 ].value ) {
			return items;
		}
		return $.map( items, function( item ) {
			if ( typeof item === "string" ) {
				return {
					label: item,
					value: item
				};
			}
			return $.extend( {}, item, {
				label: item.label || item.value,
				value: item.value || item.label
			});
		});
	},

	_suggest: function( items ) {
		var ul = this.menu.element.empty();
		this._renderMenu( ul, items );
		this.isNewMenu = true;
		this.menu.refresh();

		// size and position menu
		ul.show();
		this._resizeMenu();
		ul.position( $.extend({
			of: this.element
		}, this.options.position ) );

		if ( this.options.autoFocus ) {
			this.menu.next();
		}
	},

	_resizeMenu: function() {
		var ul = this.menu.element;
		ul.outerWidth( Math.max(
			// Firefox wraps long text (possibly a rounding bug)
			// so we add 1px to avoid the wrapping (#7513)
			ul.width( "" ).outerWidth() + 1,
			this.element.outerWidth()
		) );
	},

	_renderMenu: function( ul, items ) {
		var that = this;
		$.each( items, function( index, item ) {
			that._renderItemData( ul, item );
		});
	},

	_renderItemData: function( ul, item ) {
		return this._renderItem( ul, item ).data( "ui-autocomplete-item", item );
	},

	_renderItem: function( ul, item ) {
		return $( "<li>" ).text( item.label ).appendTo( ul );
	},

	_move: function( direction, event ) {
		if ( !this.menu.element.is( ":visible" ) ) {
			this.search( null, event );
			return;
		}
		if ( this.menu.isFirstItem() && /^previous/.test( direction ) ||
				this.menu.isLastItem() && /^next/.test( direction ) ) {

			if ( !this.isMultiLine ) {
				this._value( this.term );
			}

			this.menu.blur();
			return;
		}
		this.menu[ direction ]( event );
	},

	widget: function() {
		return this.menu.element;
	},

	_value: function() {
		return this.valueMethod.apply( this.element, arguments );
	},

	_keyEvent: function( keyEvent, event ) {
		if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
			this._move( keyEvent, event );

			// prevents moving cursor to beginning/end of the text field in some browsers
			event.preventDefault();
		}
	}
});

$.extend( $.ui.autocomplete, {
	escapeRegex: function( value ) {
		return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" );
	},
	filter: function( array, term ) {
		var matcher = new RegExp( $.ui.autocomplete.escapeRegex( term ), "i" );
		return $.grep( array, function( value ) {
			return matcher.test( value.label || value.value || value );
		});
	}
});

// live region extension, adding a `messages` option
// NOTE: This is an experimental API. We are still investigating
// a full solution for string manipulation and internationalization.
$.widget( "ui.autocomplete", $.ui.autocomplete, {
	options: {
		messages: {
			noResults: "No search results.",
			results: function( amount ) {
				return amount + ( amount > 1 ? " results are" : " result is" ) +
					" available, use up and down arrow keys to navigate.";
			}
		}
	},

	__response: function( content ) {
		var message;
		this._superApply( arguments );
		if ( this.options.disabled || this.cancelSearch ) {
			return;
		}
		if ( content && content.length ) {
			message = this.options.messages.results( content.length );
		} else {
			message = this.options.messages.noResults;
		}
		this.liveRegion.children().hide();
		$( "<div>" ).text( message ).appendTo( this.liveRegion );
	}
});

var autocomplete = $.ui.autocomplete;


/*!
 * jQuery UI Button 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/button/
 */


var lastActive,
	baseClasses = "ui-button ui-widget ui-state-default ui-corner-all",
	typeClasses = "ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only",
	formResetHandler = function() {
		var form = $( this );
		setTimeout(function() {
			form.find( ":ui-button" ).button( "refresh" );
		}, 1 );
	},
	radioGroup = function( radio ) {
		var name = radio.name,
			form = radio.form,
			radios = $( [] );
		if ( name ) {
			name = name.replace( /'/g, "\\'" );
			if ( form ) {
				radios = $( form ).find( "[name='" + name + "'][type=radio]" );
			} else {
				radios = $( "[name='" + name + "'][type=radio]", radio.ownerDocument )
					.filter(function() {
						return !this.form;
					});
			}
		}
		return radios;
	};

$.widget( "ui.button", {
	version: "1.11.1",
	defaultElement: "<button>",
	options: {
		disabled: null,
		text: true,
		label: null,
		icons: {
			primary: null,
			secondary: null
		}
	},
	_create: function() {
		this.element.closest( "form" )
			.unbind( "reset" + this.eventNamespace )
			.bind( "reset" + this.eventNamespace, formResetHandler );

		if ( typeof this.options.disabled !== "boolean" ) {
			this.options.disabled = !!this.element.prop( "disabled" );
		} else {
			this.element.prop( "disabled", this.options.disabled );
		}

		this._determineButtonType();
		this.hasTitle = !!this.buttonElement.attr( "title" );

		var that = this,
			options = this.options,
			toggleButton = this.type === "checkbox" || this.type === "radio",
			activeClass = !toggleButton ? "ui-state-active" : "";

		if ( options.label === null ) {
			options.label = (this.type === "input" ? this.buttonElement.val() : this.buttonElement.html());
		}

		this._hoverable( this.buttonElement );

		this.buttonElement
			.addClass( baseClasses )
			.attr( "role", "button" )
			.bind( "mouseenter" + this.eventNamespace, function() {
				if ( options.disabled ) {
					return;
				}
				if ( this === lastActive ) {
					$( this ).addClass( "ui-state-active" );
				}
			})
			.bind( "mouseleave" + this.eventNamespace, function() {
				if ( options.disabled ) {
					return;
				}
				$( this ).removeClass( activeClass );
			})
			.bind( "click" + this.eventNamespace, function( event ) {
				if ( options.disabled ) {
					event.preventDefault();
					event.stopImmediatePropagation();
				}
			});

		// Can't use _focusable() because the element that receives focus
		// and the element that gets the ui-state-focus class are different
		this._on({
			focus: function() {
				this.buttonElement.addClass( "ui-state-focus" );
			},
			blur: function() {
				this.buttonElement.removeClass( "ui-state-focus" );
			}
		});

		if ( toggleButton ) {
			this.element.bind( "change" + this.eventNamespace, function() {
				that.refresh();
			});
		}

		if ( this.type === "checkbox" ) {
			this.buttonElement.bind( "click" + this.eventNamespace, function() {
				if ( options.disabled ) {
					return false;
				}
			});
		} else if ( this.type === "radio" ) {
			this.buttonElement.bind( "click" + this.eventNamespace, function() {
				if ( options.disabled ) {
					return false;
				}
				$( this ).addClass( "ui-state-active" );
				that.buttonElement.attr( "aria-pressed", "true" );

				var radio = that.element[ 0 ];
				radioGroup( radio )
					.not( radio )
					.map(function() {
						return $( this ).button( "widget" )[ 0 ];
					})
					.removeClass( "ui-state-active" )
					.attr( "aria-pressed", "false" );
			});
		} else {
			this.buttonElement
				.bind( "mousedown" + this.eventNamespace, function() {
					if ( options.disabled ) {
						return false;
					}
					$( this ).addClass( "ui-state-active" );
					lastActive = this;
					that.document.one( "mouseup", function() {
						lastActive = null;
					});
				})
				.bind( "mouseup" + this.eventNamespace, function() {
					if ( options.disabled ) {
						return false;
					}
					$( this ).removeClass( "ui-state-active" );
				})
				.bind( "keydown" + this.eventNamespace, function(event) {
					if ( options.disabled ) {
						return false;
					}
					if ( event.keyCode === $.ui.keyCode.SPACE || event.keyCode === $.ui.keyCode.ENTER ) {
						$( this ).addClass( "ui-state-active" );
					}
				})
				// see #8559, we bind to blur here in case the button element loses
				// focus between keydown and keyup, it would be left in an "active" state
				.bind( "keyup" + this.eventNamespace + " blur" + this.eventNamespace, function() {
					$( this ).removeClass( "ui-state-active" );
				});

			if ( this.buttonElement.is("a") ) {
				this.buttonElement.keyup(function(event) {
					if ( event.keyCode === $.ui.keyCode.SPACE ) {
						// TODO pass through original event correctly (just as 2nd argument doesn't work)
						$( this ).click();
					}
				});
			}
		}

		this._setOption( "disabled", options.disabled );
		this._resetButton();
	},

	_determineButtonType: function() {
		var ancestor, labelSelector, checked;

		if ( this.element.is("[type=checkbox]") ) {
			this.type = "checkbox";
		} else if ( this.element.is("[type=radio]") ) {
			this.type = "radio";
		} else if ( this.element.is("input") ) {
			this.type = "input";
		} else {
			this.type = "button";
		}

		if ( this.type === "checkbox" || this.type === "radio" ) {
			// we don't search against the document in case the element
			// is disconnected from the DOM
			ancestor = this.element.parents().last();
			labelSelector = "label[for='" + this.element.attr("id") + "']";
			this.buttonElement = ancestor.find( labelSelector );
			if ( !this.buttonElement.length ) {
				ancestor = ancestor.length ? ancestor.siblings() : this.element.siblings();
				this.buttonElement = ancestor.filter( labelSelector );
				if ( !this.buttonElement.length ) {
					this.buttonElement = ancestor.find( labelSelector );
				}
			}
			this.element.addClass( "ui-helper-hidden-accessible" );

			checked = this.element.is( ":checked" );
			if ( checked ) {
				this.buttonElement.addClass( "ui-state-active" );
			}
			this.buttonElement.prop( "aria-pressed", checked );
		} else {
			this.buttonElement = this.element;
		}
	},

	widget: function() {
		return this.buttonElement;
	},

	_destroy: function() {
		this.element
			.removeClass( "ui-helper-hidden-accessible" );
		this.buttonElement
			.removeClass( baseClasses + " ui-state-active " + typeClasses )
			.removeAttr( "role" )
			.removeAttr( "aria-pressed" )
			.html( this.buttonElement.find(".ui-button-text").html() );

		if ( !this.hasTitle ) {
			this.buttonElement.removeAttr( "title" );
		}
	},

	_setOption: function( key, value ) {
		this._super( key, value );
		if ( key === "disabled" ) {
			this.widget().toggleClass( "ui-state-disabled", !!value );
			this.element.prop( "disabled", !!value );
			if ( value ) {
				if ( this.type === "checkbox" || this.type === "radio" ) {
					this.buttonElement.removeClass( "ui-state-focus" );
				} else {
					this.buttonElement.removeClass( "ui-state-focus ui-state-active" );
				}
			}
			return;
		}
		this._resetButton();
	},

	refresh: function() {
		//See #8237 & #8828
		var isDisabled = this.element.is( "input, button" ) ? this.element.is( ":disabled" ) : this.element.hasClass( "ui-button-disabled" );

		if ( isDisabled !== this.options.disabled ) {
			this._setOption( "disabled", isDisabled );
		}
		if ( this.type === "radio" ) {
			radioGroup( this.element[0] ).each(function() {
				if ( $( this ).is( ":checked" ) ) {
					$( this ).button( "widget" )
						.addClass( "ui-state-active" )
						.attr( "aria-pressed", "true" );
				} else {
					$( this ).button( "widget" )
						.removeClass( "ui-state-active" )
						.attr( "aria-pressed", "false" );
				}
			});
		} else if ( this.type === "checkbox" ) {
			if ( this.element.is( ":checked" ) ) {
				this.buttonElement
					.addClass( "ui-state-active" )
					.attr( "aria-pressed", "true" );
			} else {
				this.buttonElement
					.removeClass( "ui-state-active" )
					.attr( "aria-pressed", "false" );
			}
		}
	},

	_resetButton: function() {
		if ( this.type === "input" ) {
			if ( this.options.label ) {
				this.element.val( this.options.label );
			}
			return;
		}
		var buttonElement = this.buttonElement.removeClass( typeClasses ),
			buttonText = $( "<span></span>", this.document[0] )
				.addClass( "ui-button-text" )
				.html( this.options.label )
				.appendTo( buttonElement.empty() )
				.text(),
			icons = this.options.icons,
			multipleIcons = icons.primary && icons.secondary,
			buttonClasses = [];

		if ( icons.primary || icons.secondary ) {
			if ( this.options.text ) {
				buttonClasses.push( "ui-button-text-icon" + ( multipleIcons ? "s" : ( icons.primary ? "-primary" : "-secondary" ) ) );
			}

			if ( icons.primary ) {
				buttonElement.prepend( "<span class='ui-button-icon-primary ui-icon " + icons.primary + "'></span>" );
			}

			if ( icons.secondary ) {
				buttonElement.append( "<span class='ui-button-icon-secondary ui-icon " + icons.secondary + "'></span>" );
			}

			if ( !this.options.text ) {
				buttonClasses.push( multipleIcons ? "ui-button-icons-only" : "ui-button-icon-only" );

				if ( !this.hasTitle ) {
					buttonElement.attr( "title", $.trim( buttonText ) );
				}
			}
		} else {
			buttonClasses.push( "ui-button-text-only" );
		}
		buttonElement.addClass( buttonClasses.join( " " ) );
	}
});

$.widget( "ui.buttonset", {
	version: "1.11.1",
	options: {
		items: "button, input[type=button], input[type=submit], input[type=reset], input[type=checkbox], input[type=radio], a, :data(ui-button)"
	},

	_create: function() {
		this.element.addClass( "ui-buttonset" );
	},

	_init: function() {
		this.refresh();
	},

	_setOption: function( key, value ) {
		if ( key === "disabled" ) {
			this.buttons.button( "option", key, value );
		}

		this._super( key, value );
	},

	refresh: function() {
		var rtl = this.element.css( "direction" ) === "rtl",
			allButtons = this.element.find( this.options.items ),
			existingButtons = allButtons.filter( ":ui-button" );

		// Initialize new buttons
		allButtons.not( ":ui-button" ).button();

		// Refresh existing buttons
		existingButtons.button( "refresh" );

		this.buttons = allButtons
			.map(function() {
				return $( this ).button( "widget" )[ 0 ];
			})
				.removeClass( "ui-corner-all ui-corner-left ui-corner-right" )
				.filter( ":first" )
					.addClass( rtl ? "ui-corner-right" : "ui-corner-left" )
				.end()
				.filter( ":last" )
					.addClass( rtl ? "ui-corner-left" : "ui-corner-right" )
				.end()
			.end();
	},

	_destroy: function() {
		this.element.removeClass( "ui-buttonset" );
		this.buttons
			.map(function() {
				return $( this ).button( "widget" )[ 0 ];
			})
				.removeClass( "ui-corner-left ui-corner-right" )
			.end()
			.button( "destroy" );
	}
});

var button = $.ui.button;


/*!
 * jQuery UI Datepicker 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/datepicker/
 */


$.extend($.ui, { datepicker: { version: "1.11.1" } });

var datepicker_instActive;

function datepicker_getZindex( elem ) {
	var position, value;
	while ( elem.length && elem[ 0 ] !== document ) {
		// Ignore z-index if position is set to a value where z-index is ignored by the browser
		// This makes behavior of this function consistent across browsers
		// WebKit always returns auto if the element is positioned
		position = elem.css( "position" );
		if ( position === "absolute" || position === "relative" || position === "fixed" ) {
			// IE returns 0 when zIndex is not specified
			// other browsers return a string
			// we ignore the case of nested elements with an explicit value of 0
			// <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
			value = parseInt( elem.css( "zIndex" ), 10 );
			if ( !isNaN( value ) && value !== 0 ) {
				return value;
			}
		}
		elem = elem.parent();
	}

	return 0;
}
/* Date picker manager.
   Use the singleton instance of this class, $.datepicker, to interact with the date picker.
   Settings for (groups of) date pickers are maintained in an instance object,
   allowing multiple different settings on the same page. */

function Datepicker() {
	this._curInst = null; // The current instance in use
	this._keyEvent = false; // If the last event was a key event
	this._disabledInputs = []; // List of date picker inputs that have been disabled
	this._datepickerShowing = false; // True if the popup picker is showing , false if not
	this._inDialog = false; // True if showing within a "dialog", false if not
	this._mainDivId = "ui-datepicker-div"; // The ID of the main datepicker division
	this._inlineClass = "ui-datepicker-inline"; // The name of the inline marker class
	this._appendClass = "ui-datepicker-append"; // The name of the append marker class
	this._triggerClass = "ui-datepicker-trigger"; // The name of the trigger marker class
	this._dialogClass = "ui-datepicker-dialog"; // The name of the dialog marker class
	this._disableClass = "ui-datepicker-disabled"; // The name of the disabled covering marker class
	this._unselectableClass = "ui-datepicker-unselectable"; // The name of the unselectable cell marker class
	this._currentClass = "ui-datepicker-current-day"; // The name of the current day marker class
	this._dayOverClass = "ui-datepicker-days-cell-over"; // The name of the day hover marker class
	this.regional = []; // Available regional settings, indexed by language code
	this.regional[""] = { // Default regional settings
		closeText: "Done", // Display text for close link
		prevText: "Prev", // Display text for previous month link
		nextText: "Next", // Display text for next month link
		currentText: "Today", // Display text for current month link
		monthNames: ["January","February","March","April","May","June",
			"July","August","September","October","November","December"], // Names of months for drop-down and formatting
		monthNamesShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], // For formatting
		dayNames: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], // For formatting
		dayNamesShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], // For formatting
		dayNamesMin: ["Su","Mo","Tu","We","Th","Fr","Sa"], // Column headings for days starting at Sunday
		weekHeader: "Wk", // Column header for week of the year
		dateFormat: "mm/dd/yy", // See format options on parseDate
		firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ...
		isRTL: false, // True if right-to-left language, false if left-to-right
		showMonthAfterYear: false, // True if the year select precedes month, false for month then year
		yearSuffix: "" // Additional text to append to the year in the month headers
	};
	this._defaults = { // Global defaults for all the date picker instances
		showOn: "focus", // "focus" for popup on focus,
			// "button" for trigger button, or "both" for either
		showAnim: "fadeIn", // Name of jQuery animation for popup
		showOptions: {}, // Options for enhanced animations
		defaultDate: null, // Used when field is blank: actual date,
			// +/-number for offset from today, null for today
		appendText: "", // Display text following the input box, e.g. showing the format
		buttonText: "...", // Text for trigger button
		buttonImage: "", // URL for trigger button image
		buttonImageOnly: false, // True if the image appears alone, false if it appears on a button
		hideIfNoPrevNext: false, // True to hide next/previous month links
			// if not applicable, false to just disable them
		navigationAsDateFormat: false, // True if date formatting applied to prev/today/next links
		gotoCurrent: false, // True if today link goes back to current selection instead
		changeMonth: false, // True if month can be selected directly, false if only prev/next
		changeYear: false, // True if year can be selected directly, false if only prev/next
		yearRange: "c-10:c+10", // Range of years to display in drop-down,
			// either relative to today's year (-nn:+nn), relative to currently displayed year
			// (c-nn:c+nn), absolute (nnnn:nnnn), or a combination of the above (nnnn:-n)
		showOtherMonths: false, // True to show dates in other months, false to leave blank
		selectOtherMonths: false, // True to allow selection of dates in other months, false for unselectable
		showWeek: false, // True to show week of the year, false to not show it
		calculateWeek: this.iso8601Week, // How to calculate the week of the year,
			// takes a Date and returns the number of the week for it
		shortYearCutoff: "+10", // Short year values < this are in the current century,
			// > this are in the previous century,
			// string value starting with "+" for current year + value
		minDate: null, // The earliest selectable date, or null for no limit
		maxDate: null, // The latest selectable date, or null for no limit
		duration: "fast", // Duration of display/closure
		beforeShowDay: null, // Function that takes a date and returns an array with
			// [0] = true if selectable, false if not, [1] = custom CSS class name(s) or "",
			// [2] = cell title (optional), e.g. $.datepicker.noWeekends
		beforeShow: null, // Function that takes an input field and
			// returns a set of custom settings for the date picker
		onSelect: null, // Define a callback function when a date is selected
		onChangeMonthYear: null, // Define a callback function when the month or year is changed
		onClose: null, // Define a callback function when the datepicker is closed
		numberOfMonths: 1, // Number of months to show at a time
		showCurrentAtPos: 0, // The position in multipe months at which to show the current month (starting at 0)
		stepMonths: 1, // Number of months to step back/forward
		stepBigMonths: 12, // Number of months to step back/forward for the big links
		altField: "", // Selector for an alternate field to store selected dates into
		altFormat: "", // The date format to use for the alternate field
		constrainInput: true, // The input is constrained by the current date format
		showButtonPanel: false, // True to show button panel, false to not show it
		autoSize: false, // True to size the input for the date format, false to leave as is
		disabled: false // The initial disabled state
	};
	$.extend(this._defaults, this.regional[""]);
	this.regional.en = $.extend( true, {}, this.regional[ "" ]);
	this.regional[ "en-US" ] = $.extend( true, {}, this.regional.en );
	this.dpDiv = datepicker_bindHover($("<div id='" + this._mainDivId + "' class='ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>"));
}

$.extend(Datepicker.prototype, {
	/* Class name added to elements to indicate already configured with a date picker. */
	markerClassName: "hasDatepicker",

	//Keep track of the maximum number of rows displayed (see #7043)
	maxRows: 4,

	// TODO rename to "widget" when switching to widget factory
	_widgetDatepicker: function() {
		return this.dpDiv;
	},

	/* Override the default settings for all instances of the date picker.
	 * @param  settings  object - the new settings to use as defaults (anonymous object)
	 * @return the manager object
	 */
	setDefaults: function(settings) {
		datepicker_extendRemove(this._defaults, settings || {});
		return this;
	},

	/* Attach the date picker to a jQuery selection.
	 * @param  target	element - the target input field or division or span
	 * @param  settings  object - the new settings to use for this date picker instance (anonymous)
	 */
	_attachDatepicker: function(target, settings) {
		var nodeName, inline, inst;
		nodeName = target.nodeName.toLowerCase();
		inline = (nodeName === "div" || nodeName === "span");
		if (!target.id) {
			this.uuid += 1;
			target.id = "dp" + this.uuid;
		}
		inst = this._newInst($(target), inline);
		inst.settings = $.extend({}, settings || {});
		if (nodeName === "input") {
			this._connectDatepicker(target, inst);
		} else if (inline) {
			this._inlineDatepicker(target, inst);
		}
	},

	/* Create a new instance object. */
	_newInst: function(target, inline) {
		var id = target[0].id.replace(/([^A-Za-z0-9_\-])/g, "\\\\$1"); // escape jQuery meta chars
		return {id: id, input: target, // associated target
			selectedDay: 0, selectedMonth: 0, selectedYear: 0, // current selection
			drawMonth: 0, drawYear: 0, // month being drawn
			inline: inline, // is datepicker inline or not
			dpDiv: (!inline ? this.dpDiv : // presentation div
			datepicker_bindHover($("<div class='" + this._inlineClass + " ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>")))};
	},

	/* Attach the date picker to an input field. */
	_connectDatepicker: function(target, inst) {
		var input = $(target);
		inst.append = $([]);
		inst.trigger = $([]);
		if (input.hasClass(this.markerClassName)) {
			return;
		}
		this._attachments(input, inst);
		input.addClass(this.markerClassName).keydown(this._doKeyDown).
			keypress(this._doKeyPress).keyup(this._doKeyUp);
		this._autoSize(inst);
		$.data(target, "datepicker", inst);
		//If disabled option is true, disable the datepicker once it has been attached to the input (see ticket #5665)
		if( inst.settings.disabled ) {
			this._disableDatepicker( target );
		}
	},

	/* Make attachments based on settings. */
	_attachments: function(input, inst) {
		var showOn, buttonText, buttonImage,
			appendText = this._get(inst, "appendText"),
			isRTL = this._get(inst, "isRTL");

		if (inst.append) {
			inst.append.remove();
		}
		if (appendText) {
			inst.append = $("<span class='" + this._appendClass + "'>" + appendText + "</span>");
			input[isRTL ? "before" : "after"](inst.append);
		}

		input.unbind("focus", this._showDatepicker);

		if (inst.trigger) {
			inst.trigger.remove();
		}

		showOn = this._get(inst, "showOn");
		if (showOn === "focus" || showOn === "both") { // pop-up date picker when in the marked field
			input.focus(this._showDatepicker);
		}
		if (showOn === "button" || showOn === "both") { // pop-up date picker when button clicked
			buttonText = this._get(inst, "buttonText");
			buttonImage = this._get(inst, "buttonImage");
			inst.trigger = $(this._get(inst, "buttonImageOnly") ?
				$("<img/>").addClass(this._triggerClass).
					attr({ src: buttonImage, alt: buttonText, title: buttonText }) :
				$("<button type='button'></button>").addClass(this._triggerClass).
					html(!buttonImage ? buttonText : $("<img/>").attr(
					{ src:buttonImage, alt:buttonText, title:buttonText })));
			input[isRTL ? "before" : "after"](inst.trigger);
			inst.trigger.click(function() {
				if ($.datepicker._datepickerShowing && $.datepicker._lastInput === input[0]) {
					$.datepicker._hideDatepicker();
				} else if ($.datepicker._datepickerShowing && $.datepicker._lastInput !== input[0]) {
					$.datepicker._hideDatepicker();
					$.datepicker._showDatepicker(input[0]);
				} else {
					$.datepicker._showDatepicker(input[0]);
				}
				return false;
			});
		}
	},

	/* Apply the maximum length for the date format. */
	_autoSize: function(inst) {
		if (this._get(inst, "autoSize") && !inst.inline) {
			var findMax, max, maxI, i,
				date = new Date(2009, 12 - 1, 20), // Ensure double digits
				dateFormat = this._get(inst, "dateFormat");

			if (dateFormat.match(/[DM]/)) {
				findMax = function(names) {
					max = 0;
					maxI = 0;
					for (i = 0; i < names.length; i++) {
						if (names[i].length > max) {
							max = names[i].length;
							maxI = i;
						}
					}
					return maxI;
				};
				date.setMonth(findMax(this._get(inst, (dateFormat.match(/MM/) ?
					"monthNames" : "monthNamesShort"))));
				date.setDate(findMax(this._get(inst, (dateFormat.match(/DD/) ?
					"dayNames" : "dayNamesShort"))) + 20 - date.getDay());
			}
			inst.input.attr("size", this._formatDate(inst, date).length);
		}
	},

	/* Attach an inline date picker to a div. */
	_inlineDatepicker: function(target, inst) {
		var divSpan = $(target);
		if (divSpan.hasClass(this.markerClassName)) {
			return;
		}
		divSpan.addClass(this.markerClassName).append(inst.dpDiv);
		$.data(target, "datepicker", inst);
		this._setDate(inst, this._getDefaultDate(inst), true);
		this._updateDatepicker(inst);
		this._updateAlternate(inst);
		//If disabled option is true, disable the datepicker before showing it (see ticket #5665)
		if( inst.settings.disabled ) {
			this._disableDatepicker( target );
		}
		// Set display:block in place of inst.dpDiv.show() which won't work on disconnected elements
		// http://bugs.jqueryui.com/ticket/7552 - A Datepicker created on a detached div has zero height
		inst.dpDiv.css( "display", "block" );
	},

	/* Pop-up the date picker in a "dialog" box.
	 * @param  input element - ignored
	 * @param  date	string or Date - the initial date to display
	 * @param  onSelect  function - the function to call when a date is selected
	 * @param  settings  object - update the dialog date picker instance's settings (anonymous object)
	 * @param  pos int[2] - coordinates for the dialog's position within the screen or
	 *					event - with x/y coordinates or
	 *					leave empty for default (screen centre)
	 * @return the manager object
	 */
	_dialogDatepicker: function(input, date, onSelect, settings, pos) {
		var id, browserWidth, browserHeight, scrollX, scrollY,
			inst = this._dialogInst; // internal instance

		if (!inst) {
			this.uuid += 1;
			id = "dp" + this.uuid;
			this._dialogInput = $("<input type='text' id='" + id +
				"' style='position: absolute; top: -100px; width: 0px;'/>");
			this._dialogInput.keydown(this._doKeyDown);
			$("body").append(this._dialogInput);
			inst = this._dialogInst = this._newInst(this._dialogInput, false);
			inst.settings = {};
			$.data(this._dialogInput[0], "datepicker", inst);
		}
		datepicker_extendRemove(inst.settings, settings || {});
		date = (date && date.constructor === Date ? this._formatDate(inst, date) : date);
		this._dialogInput.val(date);

		this._pos = (pos ? (pos.length ? pos : [pos.pageX, pos.pageY]) : null);
		if (!this._pos) {
			browserWidth = document.documentElement.clientWidth;
			browserHeight = document.documentElement.clientHeight;
			scrollX = document.documentElement.scrollLeft || document.body.scrollLeft;
			scrollY = document.documentElement.scrollTop || document.body.scrollTop;
			this._pos = // should use actual width/height below
				[(browserWidth / 2) - 100 + scrollX, (browserHeight / 2) - 150 + scrollY];
		}

		// move input on screen for focus, but hidden behind dialog
		this._dialogInput.css("left", (this._pos[0] + 20) + "px").css("top", this._pos[1] + "px");
		inst.settings.onSelect = onSelect;
		this._inDialog = true;
		this.dpDiv.addClass(this._dialogClass);
		this._showDatepicker(this._dialogInput[0]);
		if ($.blockUI) {
			$.blockUI(this.dpDiv);
		}
		$.data(this._dialogInput[0], "datepicker", inst);
		return this;
	},

	/* Detach a datepicker from its control.
	 * @param  target	element - the target input field or division or span
	 */
	_destroyDatepicker: function(target) {
		var nodeName,
			$target = $(target),
			inst = $.data(target, "datepicker");

		if (!$target.hasClass(this.markerClassName)) {
			return;
		}

		nodeName = target.nodeName.toLowerCase();
		$.removeData(target, "datepicker");
		if (nodeName === "input") {
			inst.append.remove();
			inst.trigger.remove();
			$target.removeClass(this.markerClassName).
				unbind("focus", this._showDatepicker).
				unbind("keydown", this._doKeyDown).
				unbind("keypress", this._doKeyPress).
				unbind("keyup", this._doKeyUp);
		} else if (nodeName === "div" || nodeName === "span") {
			$target.removeClass(this.markerClassName).empty();
		}
	},

	/* Enable the date picker to a jQuery selection.
	 * @param  target	element - the target input field or division or span
	 */
	_enableDatepicker: function(target) {
		var nodeName, inline,
			$target = $(target),
			inst = $.data(target, "datepicker");

		if (!$target.hasClass(this.markerClassName)) {
			return;
		}

		nodeName = target.nodeName.toLowerCase();
		if (nodeName === "input") {
			target.disabled = false;
			inst.trigger.filter("button").
				each(function() { this.disabled = false; }).end().
				filter("img").css({opacity: "1.0", cursor: ""});
		} else if (nodeName === "div" || nodeName === "span") {
			inline = $target.children("." + this._inlineClass);
			inline.children().removeClass("ui-state-disabled");
			inline.find("select.ui-datepicker-month, select.ui-datepicker-year").
				prop("disabled", false);
		}
		this._disabledInputs = $.map(this._disabledInputs,
			function(value) { return (value === target ? null : value); }); // delete entry
	},

	/* Disable the date picker to a jQuery selection.
	 * @param  target	element - the target input field or division or span
	 */
	_disableDatepicker: function(target) {
		var nodeName, inline,
			$target = $(target),
			inst = $.data(target, "datepicker");

		if (!$target.hasClass(this.markerClassName)) {
			return;
		}

		nodeName = target.nodeName.toLowerCase();
		if (nodeName === "input") {
			target.disabled = true;
			inst.trigger.filter("button").
				each(function() { this.disabled = true; }).end().
				filter("img").css({opacity: "0.5", cursor: "default"});
		} else if (nodeName === "div" || nodeName === "span") {
			inline = $target.children("." + this._inlineClass);
			inline.children().addClass("ui-state-disabled");
			inline.find("select.ui-datepicker-month, select.ui-datepicker-year").
				prop("disabled", true);
		}
		this._disabledInputs = $.map(this._disabledInputs,
			function(value) { return (value === target ? null : value); }); // delete entry
		this._disabledInputs[this._disabledInputs.length] = target;
	},

	/* Is the first field in a jQuery collection disabled as a datepicker?
	 * @param  target	element - the target input field or division or span
	 * @return boolean - true if disabled, false if enabled
	 */
	_isDisabledDatepicker: function(target) {
		if (!target) {
			return false;
		}
		for (var i = 0; i < this._disabledInputs.length; i++) {
			if (this._disabledInputs[i] === target) {
				return true;
			}
		}
		return false;
	},

	/* Retrieve the instance data for the target control.
	 * @param  target  element - the target input field or division or span
	 * @return  object - the associated instance data
	 * @throws  error if a jQuery problem getting data
	 */
	_getInst: function(target) {
		try {
			return $.data(target, "datepicker");
		}
		catch (err) {
			throw "Missing instance data for this datepicker";
		}
	},

	/* Update or retrieve the settings for a date picker attached to an input field or division.
	 * @param  target  element - the target input field or division or span
	 * @param  name	object - the new settings to update or
	 *				string - the name of the setting to change or retrieve,
	 *				when retrieving also "all" for all instance settings or
	 *				"defaults" for all global defaults
	 * @param  value   any - the new value for the setting
	 *				(omit if above is an object or to retrieve a value)
	 */
	_optionDatepicker: function(target, name, value) {
		var settings, date, minDate, maxDate,
			inst = this._getInst(target);

		if (arguments.length === 2 && typeof name === "string") {
			return (name === "defaults" ? $.extend({}, $.datepicker._defaults) :
				(inst ? (name === "all" ? $.extend({}, inst.settings) :
				this._get(inst, name)) : null));
		}

		settings = name || {};
		if (typeof name === "string") {
			settings = {};
			settings[name] = value;
		}

		if (inst) {
			if (this._curInst === inst) {
				this._hideDatepicker();
			}

			date = this._getDateDatepicker(target, true);
			minDate = this._getMinMaxDate(inst, "min");
			maxDate = this._getMinMaxDate(inst, "max");
			datepicker_extendRemove(inst.settings, settings);
			// reformat the old minDate/maxDate values if dateFormat changes and a new minDate/maxDate isn't provided
			if (minDate !== null && settings.dateFormat !== undefined && settings.minDate === undefined) {
				inst.settings.minDate = this._formatDate(inst, minDate);
			}
			if (maxDate !== null && settings.dateFormat !== undefined && settings.maxDate === undefined) {
				inst.settings.maxDate = this._formatDate(inst, maxDate);
			}
			if ( "disabled" in settings ) {
				if ( settings.disabled ) {
					this._disableDatepicker(target);
				} else {
					this._enableDatepicker(target);
				}
			}
			this._attachments($(target), inst);
			this._autoSize(inst);
			this._setDate(inst, date);
			this._updateAlternate(inst);
			this._updateDatepicker(inst);
		}
	},

	// change method deprecated
	_changeDatepicker: function(target, name, value) {
		this._optionDatepicker(target, name, value);
	},

	/* Redraw the date picker attached to an input field or division.
	 * @param  target  element - the target input field or division or span
	 */
	_refreshDatepicker: function(target) {
		var inst = this._getInst(target);
		if (inst) {
			this._updateDatepicker(inst);
		}
	},

	/* Set the dates for a jQuery selection.
	 * @param  target element - the target input field or division or span
	 * @param  date	Date - the new date
	 */
	_setDateDatepicker: function(target, date) {
		var inst = this._getInst(target);
		if (inst) {
			this._setDate(inst, date);
			this._updateDatepicker(inst);
			this._updateAlternate(inst);
		}
	},

	/* Get the date(s) for the first entry in a jQuery selection.
	 * @param  target element - the target input field or division or span
	 * @param  noDefault boolean - true if no default date is to be used
	 * @return Date - the current date
	 */
	_getDateDatepicker: function(target, noDefault) {
		var inst = this._getInst(target);
		if (inst && !inst.inline) {
			this._setDateFromField(inst, noDefault);
		}
		return (inst ? this._getDate(inst) : null);
	},

	/* Handle keystrokes. */
	_doKeyDown: function(event) {
		var onSelect, dateStr, sel,
			inst = $.datepicker._getInst(event.target),
			handled = true,
			isRTL = inst.dpDiv.is(".ui-datepicker-rtl");

		inst._keyEvent = true;
		if ($.datepicker._datepickerShowing) {
			switch (event.keyCode) {
				case 9: $.datepicker._hideDatepicker();
						handled = false;
						break; // hide on tab out
				case 13: sel = $("td." + $.datepicker._dayOverClass + ":not(." +
									$.datepicker._currentClass + ")", inst.dpDiv);
						if (sel[0]) {
							$.datepicker._selectDay(event.target, inst.selectedMonth, inst.selectedYear, sel[0]);
						}

						onSelect = $.datepicker._get(inst, "onSelect");
						if (onSelect) {
							dateStr = $.datepicker._formatDate(inst);

							// trigger custom callback
							onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]);
						} else {
							$.datepicker._hideDatepicker();
						}

						return false; // don't submit the form
				case 27: $.datepicker._hideDatepicker();
						break; // hide on escape
				case 33: $.datepicker._adjustDate(event.target, (event.ctrlKey ?
							-$.datepicker._get(inst, "stepBigMonths") :
							-$.datepicker._get(inst, "stepMonths")), "M");
						break; // previous month/year on page up/+ ctrl
				case 34: $.datepicker._adjustDate(event.target, (event.ctrlKey ?
							+$.datepicker._get(inst, "stepBigMonths") :
							+$.datepicker._get(inst, "stepMonths")), "M");
						break; // next month/year on page down/+ ctrl
				case 35: if (event.ctrlKey || event.metaKey) {
							$.datepicker._clearDate(event.target);
						}
						handled = event.ctrlKey || event.metaKey;
						break; // clear on ctrl or command +end
				case 36: if (event.ctrlKey || event.metaKey) {
							$.datepicker._gotoToday(event.target);
						}
						handled = event.ctrlKey || event.metaKey;
						break; // current on ctrl or command +home
				case 37: if (event.ctrlKey || event.metaKey) {
							$.datepicker._adjustDate(event.target, (isRTL ? +1 : -1), "D");
						}
						handled = event.ctrlKey || event.metaKey;
						// -1 day on ctrl or command +left
						if (event.originalEvent.altKey) {
							$.datepicker._adjustDate(event.target, (event.ctrlKey ?
								-$.datepicker._get(inst, "stepBigMonths") :
								-$.datepicker._get(inst, "stepMonths")), "M");
						}
						// next month/year on alt +left on Mac
						break;
				case 38: if (event.ctrlKey || event.metaKey) {
							$.datepicker._adjustDate(event.target, -7, "D");
						}
						handled = event.ctrlKey || event.metaKey;
						break; // -1 week on ctrl or command +up
				case 39: if (event.ctrlKey || event.metaKey) {
							$.datepicker._adjustDate(event.target, (isRTL ? -1 : +1), "D");
						}
						handled = event.ctrlKey || event.metaKey;
						// +1 day on ctrl or command +right
						if (event.originalEvent.altKey) {
							$.datepicker._adjustDate(event.target, (event.ctrlKey ?
								+$.datepicker._get(inst, "stepBigMonths") :
								+$.datepicker._get(inst, "stepMonths")), "M");
						}
						// next month/year on alt +right
						break;
				case 40: if (event.ctrlKey || event.metaKey) {
							$.datepicker._adjustDate(event.target, +7, "D");
						}
						handled = event.ctrlKey || event.metaKey;
						break; // +1 week on ctrl or command +down
				default: handled = false;
			}
		} else if (event.keyCode === 36 && event.ctrlKey) { // display the date picker on ctrl+home
			$.datepicker._showDatepicker(this);
		} else {
			handled = false;
		}

		if (handled) {
			event.preventDefault();
			event.stopPropagation();
		}
	},

	/* Filter entered characters - based on date format. */
	_doKeyPress: function(event) {
		var chars, chr,
			inst = $.datepicker._getInst(event.target);

		if ($.datepicker._get(inst, "constrainInput")) {
			chars = $.datepicker._possibleChars($.datepicker._get(inst, "dateFormat"));
			chr = String.fromCharCode(event.charCode == null ? event.keyCode : event.charCode);
			return event.ctrlKey || event.metaKey || (chr < " " || !chars || chars.indexOf(chr) > -1);
		}
	},

	/* Synchronise manual entry and field/alternate field. */
	_doKeyUp: function(event) {
		var date,
			inst = $.datepicker._getInst(event.target);

		if (inst.input.val() !== inst.lastVal) {
			try {
				date = $.datepicker.parseDate($.datepicker._get(inst, "dateFormat"),
					(inst.input ? inst.input.val() : null),
					$.datepicker._getFormatConfig(inst));

				if (date) { // only if valid
					$.datepicker._setDateFromField(inst);
					$.datepicker._updateAlternate(inst);
					$.datepicker._updateDatepicker(inst);
				}
			}
			catch (err) {
			}
		}
		return true;
	},

	/* Pop-up the date picker for a given input field.
	 * If false returned from beforeShow event handler do not show.
	 * @param  input  element - the input field attached to the date picker or
	 *					event - if triggered by focus
	 */
	_showDatepicker: function(input) {
		input = input.target || input;
		if (input.nodeName.toLowerCase() !== "input") { // find from button/image trigger
			input = $("input", input.parentNode)[0];
		}

		if ($.datepicker._isDisabledDatepicker(input) || $.datepicker._lastInput === input) { // already here
			return;
		}

		var inst, beforeShow, beforeShowSettings, isFixed,
			offset, showAnim, duration;

		inst = $.datepicker._getInst(input);
		if ($.datepicker._curInst && $.datepicker._curInst !== inst) {
			$.datepicker._curInst.dpDiv.stop(true, true);
			if ( inst && $.datepicker._datepickerShowing ) {
				$.datepicker._hideDatepicker( $.datepicker._curInst.input[0] );
			}
		}

		beforeShow = $.datepicker._get(inst, "beforeShow");
		beforeShowSettings = beforeShow ? beforeShow.apply(input, [input, inst]) : {};
		if(beforeShowSettings === false){
			return;
		}
		datepicker_extendRemove(inst.settings, beforeShowSettings);

		inst.lastVal = null;
		$.datepicker._lastInput = input;
		$.datepicker._setDateFromField(inst);

		if ($.datepicker._inDialog) { // hide cursor
			input.value = "";
		}
		if (!$.datepicker._pos) { // position below input
			$.datepicker._pos = $.datepicker._findPos(input);
			$.datepicker._pos[1] += input.offsetHeight; // add the height
		}

		isFixed = false;
		$(input).parents().each(function() {
			isFixed |= $(this).css("position") === "fixed";
			return !isFixed;
		});

		offset = {left: $.datepicker._pos[0], top: $.datepicker._pos[1]};
		$.datepicker._pos = null;
		//to avoid flashes on Firefox
		inst.dpDiv.empty();
		// determine sizing offscreen
		inst.dpDiv.css({position: "absolute", display: "block", top: "-1000px"});
		$.datepicker._updateDatepicker(inst);
		// fix width for dynamic number of date pickers
		// and adjust position before showing
		offset = $.datepicker._checkOffset(inst, offset, isFixed);
		inst.dpDiv.css({position: ($.datepicker._inDialog && $.blockUI ?
			"static" : (isFixed ? "fixed" : "absolute")), display: "none",
			left: offset.left + "px", top: offset.top + "px"});

		if (!inst.inline) {
			showAnim = $.datepicker._get(inst, "showAnim");
			duration = $.datepicker._get(inst, "duration");
			inst.dpDiv.css( "z-index", datepicker_getZindex( $( input ) ) + 1 );
			$.datepicker._datepickerShowing = true;

			if ( $.effects && $.effects.effect[ showAnim ] ) {
				inst.dpDiv.show(showAnim, $.datepicker._get(inst, "showOptions"), duration);
			} else {
				inst.dpDiv[showAnim || "show"](showAnim ? duration : null);
			}

			if ( $.datepicker._shouldFocusInput( inst ) ) {
				inst.input.focus();
			}

			$.datepicker._curInst = inst;
		}
	},

	/* Generate the date picker content. */
	_updateDatepicker: function(inst) {
		this.maxRows = 4; //Reset the max number of rows being displayed (see #7043)
		datepicker_instActive = inst; // for delegate hover events
		inst.dpDiv.empty().append(this._generateHTML(inst));
		this._attachHandlers(inst);

		var origyearshtml,
			numMonths = this._getNumberOfMonths(inst),
			cols = numMonths[1],
			width = 17,
			activeCell = inst.dpDiv.find( "." + this._dayOverClass + " a" );

		if ( activeCell.length > 0 ) {
			datepicker_handleMouseover.apply( activeCell.get( 0 ) );
		}

		inst.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width("");
		if (cols > 1) {
			inst.dpDiv.addClass("ui-datepicker-multi-" + cols).css("width", (width * cols) + "em");
		}
		inst.dpDiv[(numMonths[0] !== 1 || numMonths[1] !== 1 ? "add" : "remove") +
			"Class"]("ui-datepicker-multi");
		inst.dpDiv[(this._get(inst, "isRTL") ? "add" : "remove") +
			"Class"]("ui-datepicker-rtl");

		if (inst === $.datepicker._curInst && $.datepicker._datepickerShowing && $.datepicker._shouldFocusInput( inst ) ) {
			inst.input.focus();
		}

		// deffered render of the years select (to avoid flashes on Firefox)
		if( inst.yearshtml ){
			origyearshtml = inst.yearshtml;
			setTimeout(function(){
				//assure that inst.yearshtml didn't change.
				if( origyearshtml === inst.yearshtml && inst.yearshtml ){
					inst.dpDiv.find("select.ui-datepicker-year:first").replaceWith(inst.yearshtml);
				}
				origyearshtml = inst.yearshtml = null;
			}, 0);
		}
	},

	// #6694 - don't focus the input if it's already focused
	// this breaks the change event in IE
	// Support: IE and jQuery <1.9
	_shouldFocusInput: function( inst ) {
		return inst.input && inst.input.is( ":visible" ) && !inst.input.is( ":disabled" ) && !inst.input.is( ":focus" );
	},

	/* Check positioning to remain on screen. */
	_checkOffset: function(inst, offset, isFixed) {
		var dpWidth = inst.dpDiv.outerWidth(),
			dpHeight = inst.dpDiv.outerHeight(),
			inputWidth = inst.input ? inst.input.outerWidth() : 0,
			inputHeight = inst.input ? inst.input.outerHeight() : 0,
			viewWidth = document.documentElement.clientWidth + (isFixed ? 0 : $(document).scrollLeft()),
			viewHeight = document.documentElement.clientHeight + (isFixed ? 0 : $(document).scrollTop());

		offset.left -= (this._get(inst, "isRTL") ? (dpWidth - inputWidth) : 0);
		offset.left -= (isFixed && offset.left === inst.input.offset().left) ? $(document).scrollLeft() : 0;
		offset.top -= (isFixed && offset.top === (inst.input.offset().top + inputHeight)) ? $(document).scrollTop() : 0;

		// now check if datepicker is showing outside window viewport - move to a better place if so.
		offset.left -= Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ?
			Math.abs(offset.left + dpWidth - viewWidth) : 0);
		offset.top -= Math.min(offset.top, (offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ?
			Math.abs(dpHeight + inputHeight) : 0);

		return offset;
	},

	/* Find an object's position on the screen. */
	_findPos: function(obj) {
		var position,
			inst = this._getInst(obj),
			isRTL = this._get(inst, "isRTL");

		while (obj && (obj.type === "hidden" || obj.nodeType !== 1 || $.expr.filters.hidden(obj))) {
			obj = obj[isRTL ? "previousSibling" : "nextSibling"];
		}

		position = $(obj).offset();
		return [position.left, position.top];
	},

	/* Hide the date picker from view.
	 * @param  input  element - the input field attached to the date picker
	 */
	_hideDatepicker: function(input) {
		var showAnim, duration, postProcess, onClose,
			inst = this._curInst;

		if (!inst || (input && inst !== $.data(input, "datepicker"))) {
			return;
		}

		if (this._datepickerShowing) {
			showAnim = this._get(inst, "showAnim");
			duration = this._get(inst, "duration");
			postProcess = function() {
				$.datepicker._tidyDialog(inst);
			};

			// DEPRECATED: after BC for 1.8.x $.effects[ showAnim ] is not needed
			if ( $.effects && ( $.effects.effect[ showAnim ] || $.effects[ showAnim ] ) ) {
				inst.dpDiv.hide(showAnim, $.datepicker._get(inst, "showOptions"), duration, postProcess);
			} else {
				inst.dpDiv[(showAnim === "slideDown" ? "slideUp" :
					(showAnim === "fadeIn" ? "fadeOut" : "hide"))]((showAnim ? duration : null), postProcess);
			}

			if (!showAnim) {
				postProcess();
			}
			this._datepickerShowing = false;

			onClose = this._get(inst, "onClose");
			if (onClose) {
				onClose.apply((inst.input ? inst.input[0] : null), [(inst.input ? inst.input.val() : ""), inst]);
			}

			this._lastInput = null;
			if (this._inDialog) {
				this._dialogInput.css({ position: "absolute", left: "0", top: "-100px" });
				if ($.blockUI) {
					$.unblockUI();
					$("body").append(this.dpDiv);
				}
			}
			this._inDialog = false;
		}
	},

	/* Tidy up after a dialog display. */
	_tidyDialog: function(inst) {
		inst.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar");
	},

	/* Close date picker if clicked elsewhere. */
	_checkExternalClick: function(event) {
		if (!$.datepicker._curInst) {
			return;
		}

		var $target = $(event.target),
			inst = $.datepicker._getInst($target[0]);

		if ( ( ( $target[0].id !== $.datepicker._mainDivId &&
				$target.parents("#" + $.datepicker._mainDivId).length === 0 &&
				!$target.hasClass($.datepicker.markerClassName) &&
				!$target.closest("." + $.datepicker._triggerClass).length &&
				$.datepicker._datepickerShowing && !($.datepicker._inDialog && $.blockUI) ) ) ||
			( $target.hasClass($.datepicker.markerClassName) && $.datepicker._curInst !== inst ) ) {
				$.datepicker._hideDatepicker();
		}
	},

	/* Adjust one of the date sub-fields. */
	_adjustDate: function(id, offset, period) {
		var target = $(id),
			inst = this._getInst(target[0]);

		if (this._isDisabledDatepicker(target[0])) {
			return;
		}
		this._adjustInstDate(inst, offset +
			(period === "M" ? this._get(inst, "showCurrentAtPos") : 0), // undo positioning
			period);
		this._updateDatepicker(inst);
	},

	/* Action for current link. */
	_gotoToday: function(id) {
		var date,
			target = $(id),
			inst = this._getInst(target[0]);

		if (this._get(inst, "gotoCurrent") && inst.currentDay) {
			inst.selectedDay = inst.currentDay;
			inst.drawMonth = inst.selectedMonth = inst.currentMonth;
			inst.drawYear = inst.selectedYear = inst.currentYear;
		} else {
			date = new Date();
			inst.selectedDay = date.getDate();
			inst.drawMonth = inst.selectedMonth = date.getMonth();
			inst.drawYear = inst.selectedYear = date.getFullYear();
		}
		this._notifyChange(inst);
		this._adjustDate(target);
	},

	/* Action for selecting a new month/year. */
	_selectMonthYear: function(id, select, period) {
		var target = $(id),
			inst = this._getInst(target[0]);

		inst["selected" + (period === "M" ? "Month" : "Year")] =
		inst["draw" + (period === "M" ? "Month" : "Year")] =
			parseInt(select.options[select.selectedIndex].value,10);

		this._notifyChange(inst);
		this._adjustDate(target);
	},

	/* Action for selecting a day. */
	_selectDay: function(id, month, year, td) {
		var inst,
			target = $(id);

		if ($(td).hasClass(this._unselectableClass) || this._isDisabledDatepicker(target[0])) {
			return;
		}

		inst = this._getInst(target[0]);
		inst.selectedDay = inst.currentDay = $("a", td).html();
		inst.selectedMonth = inst.currentMonth = month;
		inst.selectedYear = inst.currentYear = year;
		this._selectDate(id, this._formatDate(inst,
			inst.currentDay, inst.currentMonth, inst.currentYear));
	},

	/* Erase the input field and hide the date picker. */
	_clearDate: function(id) {
		var target = $(id);
		this._selectDate(target, "");
	},

	/* Update the input field with the selected date. */
	_selectDate: function(id, dateStr) {
		var onSelect,
			target = $(id),
			inst = this._getInst(target[0]);

		dateStr = (dateStr != null ? dateStr : this._formatDate(inst));
		if (inst.input) {
			inst.input.val(dateStr);
		}
		this._updateAlternate(inst);

		onSelect = this._get(inst, "onSelect");
		if (onSelect) {
			onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]);  // trigger custom callback
		} else if (inst.input) {
			inst.input.trigger("change"); // fire the change event
		}

		if (inst.inline){
			this._updateDatepicker(inst);
		} else {
			this._hideDatepicker();
			this._lastInput = inst.input[0];
			if (typeof(inst.input[0]) !== "object") {
				inst.input.focus(); // restore focus
			}
			this._lastInput = null;
		}
	},

	/* Update any alternate field to synchronise with the main field. */
	_updateAlternate: function(inst) {
		var altFormat, date, dateStr,
			altField = this._get(inst, "altField");

		if (altField) { // update alternate field too
			altFormat = this._get(inst, "altFormat") || this._get(inst, "dateFormat");
			date = this._getDate(inst);
			dateStr = this.formatDate(altFormat, date, this._getFormatConfig(inst));
			$(altField).each(function() { $(this).val(dateStr); });
		}
	},

	/* Set as beforeShowDay function to prevent selection of weekends.
	 * @param  date  Date - the date to customise
	 * @return [boolean, string] - is this date selectable?, what is its CSS class?
	 */
	noWeekends: function(date) {
		var day = date.getDay();
		return [(day > 0 && day < 6), ""];
	},

	/* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition.
	 * @param  date  Date - the date to get the week for
	 * @return  number - the number of the week within the year that contains this date
	 */
	iso8601Week: function(date) {
		var time,
			checkDate = new Date(date.getTime());

		// Find Thursday of this week starting on Monday
		checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7));

		time = checkDate.getTime();
		checkDate.setMonth(0); // Compare with Jan 1
		checkDate.setDate(1);
		return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
	},

	/* Parse a string value into a date object.
	 * See formatDate below for the possible formats.
	 *
	 * @param  format string - the expected format of the date
	 * @param  value string - the date in the above format
	 * @param  settings Object - attributes include:
	 *					shortYearCutoff  number - the cutoff year for determining the century (optional)
	 *					dayNamesShort	string[7] - abbreviated names of the days from Sunday (optional)
	 *					dayNames		string[7] - names of the days from Sunday (optional)
	 *					monthNamesShort string[12] - abbreviated names of the months (optional)
	 *					monthNames		string[12] - names of the months (optional)
	 * @return  Date - the extracted date value or null if value is blank
	 */
	parseDate: function (format, value, settings) {
		if (format == null || value == null) {
			throw "Invalid arguments";
		}

		value = (typeof value === "object" ? value.toString() : value + "");
		if (value === "") {
			return null;
		}

		var iFormat, dim, extra,
			iValue = 0,
			shortYearCutoffTemp = (settings ? settings.shortYearCutoff : null) || this._defaults.shortYearCutoff,
			shortYearCutoff = (typeof shortYearCutoffTemp !== "string" ? shortYearCutoffTemp :
				new Date().getFullYear() % 100 + parseInt(shortYearCutoffTemp, 10)),
			dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort,
			dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames,
			monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort,
			monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames,
			year = -1,
			month = -1,
			day = -1,
			doy = -1,
			literal = false,
			date,
			// Check whether a format character is doubled
			lookAhead = function(match) {
				var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match);
				if (matches) {
					iFormat++;
				}
				return matches;
			},
			// Extract a number from the string value
			getNumber = function(match) {
				var isDoubled = lookAhead(match),
					size = (match === "@" ? 14 : (match === "!" ? 20 :
					(match === "y" && isDoubled ? 4 : (match === "o" ? 3 : 2)))),
					minSize = (match === "y" ? size : 1),
					digits = new RegExp("^\\d{" + minSize + "," + size + "}"),
					num = value.substring(iValue).match(digits);
				if (!num) {
					throw "Missing number at position " + iValue;
				}
				iValue += num[0].length;
				return parseInt(num[0], 10);
			},
			// Extract a name from the string value and convert to an index
			getName = function(match, shortNames, longNames) {
				var index = -1,
					names = $.map(lookAhead(match) ? longNames : shortNames, function (v, k) {
						return [ [k, v] ];
					}).sort(function (a, b) {
						return -(a[1].length - b[1].length);
					});

				$.each(names, function (i, pair) {
					var name = pair[1];
					if (value.substr(iValue, name.length).toLowerCase() === name.toLowerCase()) {
						index = pair[0];
						iValue += name.length;
						return false;
					}
				});
				if (index !== -1) {
					return index + 1;
				} else {
					throw "Unknown name at position " + iValue;
				}
			},
			// Confirm that a literal character matches the string value
			checkLiteral = function() {
				if (value.charAt(iValue) !== format.charAt(iFormat)) {
					throw "Unexpected literal at position " + iValue;
				}
				iValue++;
			};

		for (iFormat = 0; iFormat < format.length; iFormat++) {
			if (literal) {
				if (format.charAt(iFormat) === "'" && !lookAhead("'")) {
					literal = false;
				} else {
					checkLiteral();
				}
			} else {
				switch (format.charAt(iFormat)) {
					case "d":
						day = getNumber("d");
						break;
					case "D":
						getName("D", dayNamesShort, dayNames);
						break;
					case "o":
						doy = getNumber("o");
						break;
					case "m":
						month = getNumber("m");
						break;
					case "M":
						month = getName("M", monthNamesShort, monthNames);
						break;
					case "y":
						year = getNumber("y");
						break;
					case "@":
						date = new Date(getNumber("@"));
						year = date.getFullYear();
						month = date.getMonth() + 1;
						day = date.getDate();
						break;
					case "!":
						date = new Date((getNumber("!") - this._ticksTo1970) / 10000);
						year = date.getFullYear();
						month = date.getMonth() + 1;
						day = date.getDate();
						break;
					case "'":
						if (lookAhead("'")){
							checkLiteral();
						} else {
							literal = true;
						}
						break;
					default:
						checkLiteral();
				}
			}
		}

		if (iValue < value.length){
			extra = value.substr(iValue);
			if (!/^\s+/.test(extra)) {
				throw "Extra/unparsed characters found in date: " + extra;
			}
		}

		if (year === -1) {
			year = new Date().getFullYear();
		} else if (year < 100) {
			year += new Date().getFullYear() - new Date().getFullYear() % 100 +
				(year <= shortYearCutoff ? 0 : -100);
		}

		if (doy > -1) {
			month = 1;
			day = doy;
			do {
				dim = this._getDaysInMonth(year, month - 1);
				if (day <= dim) {
					break;
				}
				month++;
				day -= dim;
			} while (true);
		}

		date = this._daylightSavingAdjust(new Date(year, month - 1, day));
		if (date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day) {
			throw "Invalid date"; // E.g. 31/02/00
		}
		return date;
	},

	/* Standard date formats. */
	ATOM: "yy-mm-dd", // RFC 3339 (ISO 8601)
	COOKIE: "D, dd M yy",
	ISO_8601: "yy-mm-dd",
	RFC_822: "D, d M y",
	RFC_850: "DD, dd-M-y",
	RFC_1036: "D, d M y",
	RFC_1123: "D, d M yy",
	RFC_2822: "D, d M yy",
	RSS: "D, d M y", // RFC 822
	TICKS: "!",
	TIMESTAMP: "@",
	W3C: "yy-mm-dd", // ISO 8601

	_ticksTo1970: (((1970 - 1) * 365 + Math.floor(1970 / 4) - Math.floor(1970 / 100) +
		Math.floor(1970 / 400)) * 24 * 60 * 60 * 10000000),

	/* Format a date object into a string value.
	 * The format can be combinations of the following:
	 * d  - day of month (no leading zero)
	 * dd - day of month (two digit)
	 * o  - day of year (no leading zeros)
	 * oo - day of year (three digit)
	 * D  - day name short
	 * DD - day name long
	 * m  - month of year (no leading zero)
	 * mm - month of year (two digit)
	 * M  - month name short
	 * MM - month name long
	 * y  - year (two digit)
	 * yy - year (four digit)
	 * @ - Unix timestamp (ms since 01/01/1970)
	 * ! - Windows ticks (100ns since 01/01/0001)
	 * "..." - literal text
	 * '' - single quote
	 *
	 * @param  format string - the desired format of the date
	 * @param  date Date - the date value to format
	 * @param  settings Object - attributes include:
	 *					dayNamesShort	string[7] - abbreviated names of the days from Sunday (optional)
	 *					dayNames		string[7] - names of the days from Sunday (optional)
	 *					monthNamesShort string[12] - abbreviated names of the months (optional)
	 *					monthNames		string[12] - names of the months (optional)
	 * @return  string - the date in the above format
	 */
	formatDate: function (format, date, settings) {
		if (!date) {
			return "";
		}

		var iFormat,
			dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort,
			dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames,
			monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort,
			monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames,
			// Check whether a format character is doubled
			lookAhead = function(match) {
				var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match);
				if (matches) {
					iFormat++;
				}
				return matches;
			},
			// Format a number, with leading zero if necessary
			formatNumber = function(match, value, len) {
				var num = "" + value;
				if (lookAhead(match)) {
					while (num.length < len) {
						num = "0" + num;
					}
				}
				return num;
			},
			// Format a name, short or long as requested
			formatName = function(match, value, shortNames, longNames) {
				return (lookAhead(match) ? longNames[value] : shortNames[value]);
			},
			output = "",
			literal = false;

		if (date) {
			for (iFormat = 0; iFormat < format.length; iFormat++) {
				if (literal) {
					if (format.charAt(iFormat) === "'" && !lookAhead("'")) {
						literal = false;
					} else {
						output += format.charAt(iFormat);
					}
				} else {
					switch (format.charAt(iFormat)) {
						case "d":
							output += formatNumber("d", date.getDate(), 2);
							break;
						case "D":
							output += formatName("D", date.getDay(), dayNamesShort, dayNames);
							break;
						case "o":
							output += formatNumber("o",
								Math.round((new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000), 3);
							break;
						case "m":
							output += formatNumber("m", date.getMonth() + 1, 2);
							break;
						case "M":
							output += formatName("M", date.getMonth(), monthNamesShort, monthNames);
							break;
						case "y":
							output += (lookAhead("y") ? date.getFullYear() :
								(date.getYear() % 100 < 10 ? "0" : "") + date.getYear() % 100);
							break;
						case "@":
							output += date.getTime();
							break;
						case "!":
							output += date.getTime() * 10000 + this._ticksTo1970;
							break;
						case "'":
							if (lookAhead("'")) {
								output += "'";
							} else {
								literal = true;
							}
							break;
						default:
							output += format.charAt(iFormat);
					}
				}
			}
		}
		return output;
	},

	/* Extract all possible characters from the date format. */
	_possibleChars: function (format) {
		var iFormat,
			chars = "",
			literal = false,
			// Check whether a format character is doubled
			lookAhead = function(match) {
				var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match);
				if (matches) {
					iFormat++;
				}
				return matches;
			};

		for (iFormat = 0; iFormat < format.length; iFormat++) {
			if (literal) {
				if (format.charAt(iFormat) === "'" && !lookAhead("'")) {
					literal = false;
				} else {
					chars += format.charAt(iFormat);
				}
			} else {
				switch (format.charAt(iFormat)) {
					case "d": case "m": case "y": case "@":
						chars += "0123456789";
						break;
					case "D": case "M":
						return null; // Accept anything
					case "'":
						if (lookAhead("'")) {
							chars += "'";
						} else {
							literal = true;
						}
						break;
					default:
						chars += format.charAt(iFormat);
				}
			}
		}
		return chars;
	},

	/* Get a setting value, defaulting if necessary. */
	_get: function(inst, name) {
		return inst.settings[name] !== undefined ?
			inst.settings[name] : this._defaults[name];
	},

	/* Parse existing date and initialise date picker. */
	_setDateFromField: function(inst, noDefault) {
		if (inst.input.val() === inst.lastVal) {
			return;
		}

		var dateFormat = this._get(inst, "dateFormat"),
			dates = inst.lastVal = inst.input ? inst.input.val() : null,
			defaultDate = this._getDefaultDate(inst),
			date = defaultDate,
			settings = this._getFormatConfig(inst);

		try {
			date = this.parseDate(dateFormat, dates, settings) || defaultDate;
		} catch (event) {
			dates = (noDefault ? "" : dates);
		}
		inst.selectedDay = date.getDate();
		inst.drawMonth = inst.selectedMonth = date.getMonth();
		inst.drawYear = inst.selectedYear = date.getFullYear();
		inst.currentDay = (dates ? date.getDate() : 0);
		inst.currentMonth = (dates ? date.getMonth() : 0);
		inst.currentYear = (dates ? date.getFullYear() : 0);
		this._adjustInstDate(inst);
	},

	/* Retrieve the default date shown on opening. */
	_getDefaultDate: function(inst) {
		return this._restrictMinMax(inst,
			this._determineDate(inst, this._get(inst, "defaultDate"), new Date()));
	},

	/* A date may be specified as an exact value or a relative one. */
	_determineDate: function(inst, date, defaultDate) {
		var offsetNumeric = function(offset) {
				var date = new Date();
				date.setDate(date.getDate() + offset);
				return date;
			},
			offsetString = function(offset) {
				try {
					return $.datepicker.parseDate($.datepicker._get(inst, "dateFormat"),
						offset, $.datepicker._getFormatConfig(inst));
				}
				catch (e) {
					// Ignore
				}

				var date = (offset.toLowerCase().match(/^c/) ?
					$.datepicker._getDate(inst) : null) || new Date(),
					year = date.getFullYear(),
					month = date.getMonth(),
					day = date.getDate(),
					pattern = /([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,
					matches = pattern.exec(offset);

				while (matches) {
					switch (matches[2] || "d") {
						case "d" : case "D" :
							day += parseInt(matches[1],10); break;
						case "w" : case "W" :
							day += parseInt(matches[1],10) * 7; break;
						case "m" : case "M" :
							month += parseInt(matches[1],10);
							day = Math.min(day, $.datepicker._getDaysInMonth(year, month));
							break;
						case "y": case "Y" :
							year += parseInt(matches[1],10);
							day = Math.min(day, $.datepicker._getDaysInMonth(year, month));
							break;
					}
					matches = pattern.exec(offset);
				}
				return new Date(year, month, day);
			},
			newDate = (date == null || date === "" ? defaultDate : (typeof date === "string" ? offsetString(date) :
				(typeof date === "number" ? (isNaN(date) ? defaultDate : offsetNumeric(date)) : new Date(date.getTime()))));

		newDate = (newDate && newDate.toString() === "Invalid Date" ? defaultDate : newDate);
		if (newDate) {
			newDate.setHours(0);
			newDate.setMinutes(0);
			newDate.setSeconds(0);
			newDate.setMilliseconds(0);
		}
		return this._daylightSavingAdjust(newDate);
	},

	/* Handle switch to/from daylight saving.
	 * Hours may be non-zero on daylight saving cut-over:
	 * > 12 when midnight changeover, but then cannot generate
	 * midnight datetime, so jump to 1AM, otherwise reset.
	 * @param  date  (Date) the date to check
	 * @return  (Date) the corrected date
	 */
	_daylightSavingAdjust: function(date) {
		if (!date) {
			return null;
		}
		date.setHours(date.getHours() > 12 ? date.getHours() + 2 : 0);
		return date;
	},

	/* Set the date(s) directly. */
	_setDate: function(inst, date, noChange) {
		var clear = !date,
			origMonth = inst.selectedMonth,
			origYear = inst.selectedYear,
			newDate = this._restrictMinMax(inst, this._determineDate(inst, date, new Date()));

		inst.selectedDay = inst.currentDay = newDate.getDate();
		inst.drawMonth = inst.selectedMonth = inst.currentMonth = newDate.getMonth();
		inst.drawYear = inst.selectedYear = inst.currentYear = newDate.getFullYear();
		if ((origMonth !== inst.selectedMonth || origYear !== inst.selectedYear) && !noChange) {
			this._notifyChange(inst);
		}
		this._adjustInstDate(inst);
		if (inst.input) {
			inst.input.val(clear ? "" : this._formatDate(inst));
		}
	},

	/* Retrieve the date(s) directly. */
	_getDate: function(inst) {
		var startDate = (!inst.currentYear || (inst.input && inst.input.val() === "") ? null :
			this._daylightSavingAdjust(new Date(
			inst.currentYear, inst.currentMonth, inst.currentDay)));
			return startDate;
	},

	/* Attach the onxxx handlers.  These are declared statically so
	 * they work with static code transformers like Caja.
	 */
	_attachHandlers: function(inst) {
		var stepMonths = this._get(inst, "stepMonths"),
			id = "#" + inst.id.replace( /\\\\/g, "\\" );
		inst.dpDiv.find("[data-handler]").map(function () {
			var handler = {
				prev: function () {
					$.datepicker._adjustDate(id, -stepMonths, "M");
				},
				next: function () {
					$.datepicker._adjustDate(id, +stepMonths, "M");
				},
				hide: function () {
					$.datepicker._hideDatepicker();
				},
				today: function () {
					$.datepicker._gotoToday(id);
				},
				selectDay: function () {
					$.datepicker._selectDay(id, +this.getAttribute("data-month"), +this.getAttribute("data-year"), this);
					return false;
				},
				selectMonth: function () {
					$.datepicker._selectMonthYear(id, this, "M");
					return false;
				},
				selectYear: function () {
					$.datepicker._selectMonthYear(id, this, "Y");
					return false;
				}
			};
			$(this).bind(this.getAttribute("data-event"), handler[this.getAttribute("data-handler")]);
		});
	},

	/* Generate the HTML for the current state of the date picker. */
	_generateHTML: function(inst) {
		var maxDraw, prevText, prev, nextText, next, currentText, gotoDate,
			controls, buttonPanel, firstDay, showWeek, dayNames, dayNamesMin,
			monthNames, monthNamesShort, beforeShowDay, showOtherMonths,
			selectOtherMonths, defaultDate, html, dow, row, group, col, selectedDate,
			cornerClass, calender, thead, day, daysInMonth, leadDays, curRows, numRows,
			printDate, dRow, tbody, daySettings, otherMonth, unselectable,
			tempDate = new Date(),
			today = this._daylightSavingAdjust(
				new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate())), // clear time
			isRTL = this._get(inst, "isRTL"),
			showButtonPanel = this._get(inst, "showButtonPanel"),
			hideIfNoPrevNext = this._get(inst, "hideIfNoPrevNext"),
			navigationAsDateFormat = this._get(inst, "navigationAsDateFormat"),
			numMonths = this._getNumberOfMonths(inst),
			showCurrentAtPos = this._get(inst, "showCurrentAtPos"),
			stepMonths = this._get(inst, "stepMonths"),
			isMultiMonth = (numMonths[0] !== 1 || numMonths[1] !== 1),
			currentDate = this._daylightSavingAdjust((!inst.currentDay ? new Date(9999, 9, 9) :
				new Date(inst.currentYear, inst.currentMonth, inst.currentDay))),
			minDate = this._getMinMaxDate(inst, "min"),
			maxDate = this._getMinMaxDate(inst, "max"),
			drawMonth = inst.drawMonth - showCurrentAtPos,
			drawYear = inst.drawYear;

		if (drawMonth < 0) {
			drawMonth += 12;
			drawYear--;
		}
		if (maxDate) {
			maxDraw = this._daylightSavingAdjust(new Date(maxDate.getFullYear(),
				maxDate.getMonth() - (numMonths[0] * numMonths[1]) + 1, maxDate.getDate()));
			maxDraw = (minDate && maxDraw < minDate ? minDate : maxDraw);
			while (this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1)) > maxDraw) {
				drawMonth--;
				if (drawMonth < 0) {
					drawMonth = 11;
					drawYear--;
				}
			}
		}
		inst.drawMonth = drawMonth;
		inst.drawYear = drawYear;

		prevText = this._get(inst, "prevText");
		prevText = (!navigationAsDateFormat ? prevText : this.formatDate(prevText,
			this._daylightSavingAdjust(new Date(drawYear, drawMonth - stepMonths, 1)),
			this._getFormatConfig(inst)));

		prev = (this._canAdjustMonth(inst, -1, drawYear, drawMonth) ?
			"<a class='ui-datepicker-prev ui-corner-all' data-handler='prev' data-event='click'" +
			" title='" + prevText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w") + "'>" + prevText + "</span></a>" :
			(hideIfNoPrevNext ? "" : "<a class='ui-datepicker-prev ui-corner-all ui-state-disabled' title='"+ prevText +"'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w") + "'>" + prevText + "</span></a>"));

		nextText = this._get(inst, "nextText");
		nextText = (!navigationAsDateFormat ? nextText : this.formatDate(nextText,
			this._daylightSavingAdjust(new Date(drawYear, drawMonth + stepMonths, 1)),
			this._getFormatConfig(inst)));

		next = (this._canAdjustMonth(inst, +1, drawYear, drawMonth) ?
			"<a class='ui-datepicker-next ui-corner-all' data-handler='next' data-event='click'" +
			" title='" + nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e") + "'>" + nextText + "</span></a>" :
			(hideIfNoPrevNext ? "" : "<a class='ui-datepicker-next ui-corner-all ui-state-disabled' title='"+ nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e") + "'>" + nextText + "</span></a>"));

		currentText = this._get(inst, "currentText");
		gotoDate = (this._get(inst, "gotoCurrent") && inst.currentDay ? currentDate : today);
		currentText = (!navigationAsDateFormat ? currentText :
			this.formatDate(currentText, gotoDate, this._getFormatConfig(inst)));

		controls = (!inst.inline ? "<button type='button' class='ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all' data-handler='hide' data-event='click'>" +
			this._get(inst, "closeText") + "</button>" : "");

		buttonPanel = (showButtonPanel) ? "<div class='ui-datepicker-buttonpane ui-widget-content'>" + (isRTL ? controls : "") +
			(this._isInRange(inst, gotoDate) ? "<button type='button' class='ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all' data-handler='today' data-event='click'" +
			">" + currentText + "</button>" : "") + (isRTL ? "" : controls) + "</div>" : "";

		firstDay = parseInt(this._get(inst, "firstDay"),10);
		firstDay = (isNaN(firstDay) ? 0 : firstDay);

		showWeek = this._get(inst, "showWeek");
		dayNames = this._get(inst, "dayNames");
		dayNamesMin = this._get(inst, "dayNamesMin");
		monthNames = this._get(inst, "monthNames");
		monthNamesShort = this._get(inst, "monthNamesShort");
		beforeShowDay = this._get(inst, "beforeShowDay");
		showOtherMonths = this._get(inst, "showOtherMonths");
		selectOtherMonths = this._get(inst, "selectOtherMonths");
		defaultDate = this._getDefaultDate(inst);
		html = "";
		dow;
		for (row = 0; row < numMonths[0]; row++) {
			group = "";
			this.maxRows = 4;
			for (col = 0; col < numMonths[1]; col++) {
				selectedDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, inst.selectedDay));
				cornerClass = " ui-corner-all";
				calender = "";
				if (isMultiMonth) {
					calender += "<div class='ui-datepicker-group";
					if (numMonths[1] > 1) {
						switch (col) {
							case 0: calender += " ui-datepicker-group-first";
								cornerClass = " ui-corner-" + (isRTL ? "right" : "left"); break;
							case numMonths[1]-1: calender += " ui-datepicker-group-last";
								cornerClass = " ui-corner-" + (isRTL ? "left" : "right"); break;
							default: calender += " ui-datepicker-group-middle"; cornerClass = ""; break;
						}
					}
					calender += "'>";
				}
				calender += "<div class='ui-datepicker-header ui-widget-header ui-helper-clearfix" + cornerClass + "'>" +
					(/all|left/.test(cornerClass) && row === 0 ? (isRTL ? next : prev) : "") +
					(/all|right/.test(cornerClass) && row === 0 ? (isRTL ? prev : next) : "") +
					this._generateMonthYearHeader(inst, drawMonth, drawYear, minDate, maxDate,
					row > 0 || col > 0, monthNames, monthNamesShort) + // draw month headers
					"</div><table class='ui-datepicker-calendar'><thead>" +
					"<tr>";
				thead = (showWeek ? "<th class='ui-datepicker-week-col'>" + this._get(inst, "weekHeader") + "</th>" : "");
				for (dow = 0; dow < 7; dow++) { // days of the week
					day = (dow + firstDay) % 7;
					thead += "<th scope='col'" + ((dow + firstDay + 6) % 7 >= 5 ? " class='ui-datepicker-week-end'" : "") + ">" +
						"<span title='" + dayNames[day] + "'>" + dayNamesMin[day] + "</span></th>";
				}
				calender += thead + "</tr></thead><tbody>";
				daysInMonth = this._getDaysInMonth(drawYear, drawMonth);
				if (drawYear === inst.selectedYear && drawMonth === inst.selectedMonth) {
					inst.selectedDay = Math.min(inst.selectedDay, daysInMonth);
				}
				leadDays = (this._getFirstDayOfMonth(drawYear, drawMonth) - firstDay + 7) % 7;
				curRows = Math.ceil((leadDays + daysInMonth) / 7); // calculate the number of rows to generate
				numRows = (isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows); //If multiple months, use the higher number of rows (see #7043)
				this.maxRows = numRows;
				printDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1 - leadDays));
				for (dRow = 0; dRow < numRows; dRow++) { // create date picker rows
					calender += "<tr>";
					tbody = (!showWeek ? "" : "<td class='ui-datepicker-week-col'>" +
						this._get(inst, "calculateWeek")(printDate) + "</td>");
					for (dow = 0; dow < 7; dow++) { // create date picker days
						daySettings = (beforeShowDay ?
							beforeShowDay.apply((inst.input ? inst.input[0] : null), [printDate]) : [true, ""]);
						otherMonth = (printDate.getMonth() !== drawMonth);
						unselectable = (otherMonth && !selectOtherMonths) || !daySettings[0] ||
							(minDate && printDate < minDate) || (maxDate && printDate > maxDate);
						tbody += "<td class='" +
							((dow + firstDay + 6) % 7 >= 5 ? " ui-datepicker-week-end" : "") + // highlight weekends
							(otherMonth ? " ui-datepicker-other-month" : "") + // highlight days from other months
							((printDate.getTime() === selectedDate.getTime() && drawMonth === inst.selectedMonth && inst._keyEvent) || // user pressed key
							(defaultDate.getTime() === printDate.getTime() && defaultDate.getTime() === selectedDate.getTime()) ?
							// or defaultDate is current printedDate and defaultDate is selectedDate
							" " + this._dayOverClass : "") + // highlight selected day
							(unselectable ? " " + this._unselectableClass + " ui-state-disabled": "") +  // highlight unselectable days
							(otherMonth && !showOtherMonths ? "" : " " + daySettings[1] + // highlight custom dates
							(printDate.getTime() === currentDate.getTime() ? " " + this._currentClass : "") + // highlight selected day
							(printDate.getTime() === today.getTime() ? " ui-datepicker-today" : "")) + "'" + // highlight today (if different)
							((!otherMonth || showOtherMonths) && daySettings[2] ? " title='" + daySettings[2].replace(/'/g, "&#39;") + "'" : "") + // cell title
							(unselectable ? "" : " data-handler='selectDay' data-event='click' data-month='" + printDate.getMonth() + "' data-year='" + printDate.getFullYear() + "'") + ">" + // actions
							(otherMonth && !showOtherMonths ? "&#xa0;" : // display for other months
							(unselectable ? "<span class='ui-state-default'>" + printDate.getDate() + "</span>" : "<a class='ui-state-default" +
							(printDate.getTime() === today.getTime() ? " ui-state-highlight" : "") +
							(printDate.getTime() === currentDate.getTime() ? " ui-state-active" : "") + // highlight selected day
							(otherMonth ? " ui-priority-secondary" : "") + // distinguish dates from other months
							"' href='#'>" + printDate.getDate() + "</a>")) + "</td>"; // display selectable date
						printDate.setDate(printDate.getDate() + 1);
						printDate = this._daylightSavingAdjust(printDate);
					}
					calender += tbody + "</tr>";
				}
				drawMonth++;
				if (drawMonth > 11) {
					drawMonth = 0;
					drawYear++;
				}
				calender += "</tbody></table>" + (isMultiMonth ? "</div>" +
							((numMonths[0] > 0 && col === numMonths[1]-1) ? "<div class='ui-datepicker-row-break'></div>" : "") : "");
				group += calender;
			}
			html += group;
		}
		html += buttonPanel;
		inst._keyEvent = false;
		return html;
	},

	/* Generate the month and year header. */
	_generateMonthYearHeader: function(inst, drawMonth, drawYear, minDate, maxDate,
			secondary, monthNames, monthNamesShort) {

		var inMinYear, inMaxYear, month, years, thisYear, determineYear, year, endYear,
			changeMonth = this._get(inst, "changeMonth"),
			changeYear = this._get(inst, "changeYear"),
			showMonthAfterYear = this._get(inst, "showMonthAfterYear"),
			html = "<div class='ui-datepicker-title'>",
			monthHtml = "";

		// month selection
		if (secondary || !changeMonth) {
			monthHtml += "<span class='ui-datepicker-month'>" + monthNames[drawMonth] + "</span>";
		} else {
			inMinYear = (minDate && minDate.getFullYear() === drawYear);
			inMaxYear = (maxDate && maxDate.getFullYear() === drawYear);
			monthHtml += "<select class='ui-datepicker-month' data-handler='selectMonth' data-event='change'>";
			for ( month = 0; month < 12; month++) {
				if ((!inMinYear || month >= minDate.getMonth()) && (!inMaxYear || month <= maxDate.getMonth())) {
					monthHtml += "<option value='" + month + "'" +
						(month === drawMonth ? " selected='selected'" : "") +
						">" + monthNamesShort[month] + "</option>";
				}
			}
			monthHtml += "</select>";
		}

		if (!showMonthAfterYear) {
			html += monthHtml + (secondary || !(changeMonth && changeYear) ? "&#xa0;" : "");
		}

		// year selection
		if ( !inst.yearshtml ) {
			inst.yearshtml = "";
			if (secondary || !changeYear) {
				html += "<span class='ui-datepicker-year'>" + drawYear + "</span>";
			} else {
				// determine range of years to display
				years = this._get(inst, "yearRange").split(":");
				thisYear = new Date().getFullYear();
				determineYear = function(value) {
					var year = (value.match(/c[+\-].*/) ? drawYear + parseInt(value.substring(1), 10) :
						(value.match(/[+\-].*/) ? thisYear + parseInt(value, 10) :
						parseInt(value, 10)));
					return (isNaN(year) ? thisYear : year);
				};
				year = determineYear(years[0]);
				endYear = Math.max(year, determineYear(years[1] || ""));
				year = (minDate ? Math.max(year, minDate.getFullYear()) : year);
				endYear = (maxDate ? Math.min(endYear, maxDate.getFullYear()) : endYear);
				inst.yearshtml += "<select class='ui-datepicker-year' data-handler='selectYear' data-event='change'>";
				for (; year <= endYear; year++) {
					inst.yearshtml += "<option value='" + year + "'" +
						(year === drawYear ? " selected='selected'" : "") +
						">" + year + "</option>";
				}
				inst.yearshtml += "</select>";

				html += inst.yearshtml;
				inst.yearshtml = null;
			}
		}

		html += this._get(inst, "yearSuffix");
		if (showMonthAfterYear) {
			html += (secondary || !(changeMonth && changeYear) ? "&#xa0;" : "") + monthHtml;
		}
		html += "</div>"; // Close datepicker_header
		return html;
	},

	/* Adjust one of the date sub-fields. */
	_adjustInstDate: function(inst, offset, period) {
		var year = inst.drawYear + (period === "Y" ? offset : 0),
			month = inst.drawMonth + (period === "M" ? offset : 0),
			day = Math.min(inst.selectedDay, this._getDaysInMonth(year, month)) + (period === "D" ? offset : 0),
			date = this._restrictMinMax(inst, this._daylightSavingAdjust(new Date(year, month, day)));

		inst.selectedDay = date.getDate();
		inst.drawMonth = inst.selectedMonth = date.getMonth();
		inst.drawYear = inst.selectedYear = date.getFullYear();
		if (period === "M" || period === "Y") {
			this._notifyChange(inst);
		}
	},

	/* Ensure a date is within any min/max bounds. */
	_restrictMinMax: function(inst, date) {
		var minDate = this._getMinMaxDate(inst, "min"),
			maxDate = this._getMinMaxDate(inst, "max"),
			newDate = (minDate && date < minDate ? minDate : date);
		return (maxDate && newDate > maxDate ? maxDate : newDate);
	},

	/* Notify change of month/year. */
	_notifyChange: function(inst) {
		var onChange = this._get(inst, "onChangeMonthYear");
		if (onChange) {
			onChange.apply((inst.input ? inst.input[0] : null),
				[inst.selectedYear, inst.selectedMonth + 1, inst]);
		}
	},

	/* Determine the number of months to show. */
	_getNumberOfMonths: function(inst) {
		var numMonths = this._get(inst, "numberOfMonths");
		return (numMonths == null ? [1, 1] : (typeof numMonths === "number" ? [1, numMonths] : numMonths));
	},

	/* Determine the current maximum date - ensure no time components are set. */
	_getMinMaxDate: function(inst, minMax) {
		return this._determineDate(inst, this._get(inst, minMax + "Date"), null);
	},

	/* Find the number of days in a given month. */
	_getDaysInMonth: function(year, month) {
		return 32 - this._daylightSavingAdjust(new Date(year, month, 32)).getDate();
	},

	/* Find the day of the week of the first of a month. */
	_getFirstDayOfMonth: function(year, month) {
		return new Date(year, month, 1).getDay();
	},

	/* Determines if we should allow a "next/prev" month display change. */
	_canAdjustMonth: function(inst, offset, curYear, curMonth) {
		var numMonths = this._getNumberOfMonths(inst),
			date = this._daylightSavingAdjust(new Date(curYear,
			curMonth + (offset < 0 ? offset : numMonths[0] * numMonths[1]), 1));

		if (offset < 0) {
			date.setDate(this._getDaysInMonth(date.getFullYear(), date.getMonth()));
		}
		return this._isInRange(inst, date);
	},

	/* Is the given date in the accepted range? */
	_isInRange: function(inst, date) {
		var yearSplit, currentYear,
			minDate = this._getMinMaxDate(inst, "min"),
			maxDate = this._getMinMaxDate(inst, "max"),
			minYear = null,
			maxYear = null,
			years = this._get(inst, "yearRange");
			if (years){
				yearSplit = years.split(":");
				currentYear = new Date().getFullYear();
				minYear = parseInt(yearSplit[0], 10);
				maxYear = parseInt(yearSplit[1], 10);
				if ( yearSplit[0].match(/[+\-].*/) ) {
					minYear += currentYear;
				}
				if ( yearSplit[1].match(/[+\-].*/) ) {
					maxYear += currentYear;
				}
			}

		return ((!minDate || date.getTime() >= minDate.getTime()) &&
			(!maxDate || date.getTime() <= maxDate.getTime()) &&
			(!minYear || date.getFullYear() >= minYear) &&
			(!maxYear || date.getFullYear() <= maxYear));
	},

	/* Provide the configuration settings for formatting/parsing. */
	_getFormatConfig: function(inst) {
		var shortYearCutoff = this._get(inst, "shortYearCutoff");
		shortYearCutoff = (typeof shortYearCutoff !== "string" ? shortYearCutoff :
			new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10));
		return {shortYearCutoff: shortYearCutoff,
			dayNamesShort: this._get(inst, "dayNamesShort"), dayNames: this._get(inst, "dayNames"),
			monthNamesShort: this._get(inst, "monthNamesShort"), monthNames: this._get(inst, "monthNames")};
	},

	/* Format the given date for display. */
	_formatDate: function(inst, day, month, year) {
		if (!day) {
			inst.currentDay = inst.selectedDay;
			inst.currentMonth = inst.selectedMonth;
			inst.currentYear = inst.selectedYear;
		}
		var date = (day ? (typeof day === "object" ? day :
			this._daylightSavingAdjust(new Date(year, month, day))) :
			this._daylightSavingAdjust(new Date(inst.currentYear, inst.currentMonth, inst.currentDay)));
		return this.formatDate(this._get(inst, "dateFormat"), date, this._getFormatConfig(inst));
	}
});

/*
 * Bind hover events for datepicker elements.
 * Done via delegate so the binding only occurs once in the lifetime of the parent div.
 * Global datepicker_instActive, set by _updateDatepicker allows the handlers to find their way back to the active picker.
 */
function datepicker_bindHover(dpDiv) {
	var selector = "button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";
	return dpDiv.delegate(selector, "mouseout", function() {
			$(this).removeClass("ui-state-hover");
			if (this.className.indexOf("ui-datepicker-prev") !== -1) {
				$(this).removeClass("ui-datepicker-prev-hover");
			}
			if (this.className.indexOf("ui-datepicker-next") !== -1) {
				$(this).removeClass("ui-datepicker-next-hover");
			}
		})
		.delegate( selector, "mouseover", datepicker_handleMouseover );
}

function datepicker_handleMouseover() {
	if (!$.datepicker._isDisabledDatepicker( datepicker_instActive.inline? datepicker_instActive.dpDiv.parent()[0] : datepicker_instActive.input[0])) {
		$(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover");
		$(this).addClass("ui-state-hover");
		if (this.className.indexOf("ui-datepicker-prev") !== -1) {
			$(this).addClass("ui-datepicker-prev-hover");
		}
		if (this.className.indexOf("ui-datepicker-next") !== -1) {
			$(this).addClass("ui-datepicker-next-hover");
		}
	}
}

/* jQuery extend now ignores nulls! */
function datepicker_extendRemove(target, props) {
	$.extend(target, props);
	for (var name in props) {
		if (props[name] == null) {
			target[name] = props[name];
		}
	}
	return target;
}

/* Invoke the datepicker functionality.
   @param  options  string - a command, optionally followed by additional parameters or
					Object - settings for attaching new datepicker functionality
   @return  jQuery object */
$.fn.datepicker = function(options){

	/* Verify an empty collection wasn't passed - Fixes #6976 */
	if ( !this.length ) {
		return this;
	}

	/* Initialise the date picker. */
	if (!$.datepicker.initialized) {
		$(document).mousedown($.datepicker._checkExternalClick);
		$.datepicker.initialized = true;
	}

	/* Append datepicker main container to body if not exist. */
	if ($("#"+$.datepicker._mainDivId).length === 0) {
		$("body").append($.datepicker.dpDiv);
	}

	var otherArgs = Array.prototype.slice.call(arguments, 1);
	if (typeof options === "string" && (options === "isDisabled" || options === "getDate" || options === "widget")) {
		return $.datepicker["_" + options + "Datepicker"].
			apply($.datepicker, [this[0]].concat(otherArgs));
	}
	if (options === "option" && arguments.length === 2 && typeof arguments[1] === "string") {
		return $.datepicker["_" + options + "Datepicker"].
			apply($.datepicker, [this[0]].concat(otherArgs));
	}
	return this.each(function() {
		typeof options === "string" ?
			$.datepicker["_" + options + "Datepicker"].
				apply($.datepicker, [this].concat(otherArgs)) :
			$.datepicker._attachDatepicker(this, options);
	});
};

$.datepicker = new Datepicker(); // singleton instance
$.datepicker.initialized = false;
$.datepicker.uuid = new Date().getTime();
$.datepicker.version = "1.11.1";

var datepicker = $.datepicker;


/*!
 * jQuery UI Draggable 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/draggable/
 */


$.widget("ui.draggable", $.ui.mouse, {
	version: "1.11.1",
	widgetEventPrefix: "drag",
	options: {
		addClasses: true,
		appendTo: "parent",
		axis: false,
		connectToSortable: false,
		containment: false,
		cursor: "auto",
		cursorAt: false,
		grid: false,
		handle: false,
		helper: "original",
		iframeFix: false,
		opacity: false,
		refreshPositions: false,
		revert: false,
		revertDuration: 500,
		scope: "default",
		scroll: true,
		scrollSensitivity: 20,
		scrollSpeed: 20,
		snap: false,
		snapMode: "both",
		snapTolerance: 20,
		stack: false,
		zIndex: false,

		// callbacks
		drag: null,
		start: null,
		stop: null
	},
	_create: function() {

		if (this.options.helper === "original" && !(/^(?:r|a|f)/).test(this.element.css("position"))) {
			this.element[0].style.position = "relative";
		}
		if (this.options.addClasses){
			this.element.addClass("ui-draggable");
		}
		if (this.options.disabled){
			this.element.addClass("ui-draggable-disabled");
		}
		this._setHandleClassName();

		this._mouseInit();
	},

	_setOption: function( key, value ) {
		this._super( key, value );
		if ( key === "handle" ) {
			this._removeHandleClassName();
			this._setHandleClassName();
		}
	},

	_destroy: function() {
		if ( ( this.helper || this.element ).is( ".ui-draggable-dragging" ) ) {
			this.destroyOnClear = true;
			return;
		}
		this.element.removeClass( "ui-draggable ui-draggable-dragging ui-draggable-disabled" );
		this._removeHandleClassName();
		this._mouseDestroy();
	},

	_mouseCapture: function(event) {

		var document = this.document[ 0 ],
			o = this.options;

		// support: IE9
		// IE9 throws an "Unspecified error" accessing document.activeElement from an <iframe>
		try {
			// Support: IE9+
			// If the <body> is blurred, IE will switch windows, see #9520
			if ( document.activeElement && document.activeElement.nodeName.toLowerCase() !== "body" ) {
				// Blur any element that currently has focus, see #4261
				$( document.activeElement ).blur();
			}
		} catch ( error ) {}

		// among others, prevent a drag on a resizable-handle
		if (this.helper || o.disabled || $(event.target).closest(".ui-resizable-handle").length > 0) {
			return false;
		}

		//Quit if we're not on a valid handle
		this.handle = this._getHandle(event);
		if (!this.handle) {
			return false;
		}

		$(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() {
			$("<div class='ui-draggable-iframeFix' style='background: #fff;'></div>")
			.css({
				width: this.offsetWidth + "px", height: this.offsetHeight + "px",
				position: "absolute", opacity: "0.001", zIndex: 1000
			})
			.css($(this).offset())
			.appendTo("body");
		});

		return true;

	},

	_mouseStart: function(event) {

		var o = this.options;

		//Create and append the visible helper
		this.helper = this._createHelper(event);

		this.helper.addClass("ui-draggable-dragging");

		//Cache the helper size
		this._cacheHelperProportions();

		//If ddmanager is used for droppables, set the global draggable
		if ($.ui.ddmanager) {
			$.ui.ddmanager.current = this;
		}

		/*
		 * - Position generation -
		 * This block generates everything position related - it's the core of draggables.
		 */

		//Cache the margins of the original element
		this._cacheMargins();

		//Store the helper's css position
		this.cssPosition = this.helper.css( "position" );
		this.scrollParent = this.helper.scrollParent( true );
		this.offsetParent = this.helper.offsetParent();
		this.offsetParentCssPosition = this.offsetParent.css( "position" );

		//The element's absolute position on the page minus margins
		this.offset = this.positionAbs = this.element.offset();
		this.offset = {
			top: this.offset.top - this.margins.top,
			left: this.offset.left - this.margins.left
		};

		//Reset scroll cache
		this.offset.scroll = false;

		$.extend(this.offset, {
			click: { //Where the click happened, relative to the element
				left: event.pageX - this.offset.left,
				top: event.pageY - this.offset.top
			},
			parent: this._getParentOffset(),
			relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
		});

		//Generate the original position
		this.originalPosition = this.position = this._generatePosition( event, false );
		this.originalPageX = event.pageX;
		this.originalPageY = event.pageY;

		//Adjust the mouse offset relative to the helper if "cursorAt" is supplied
		(o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));

		//Set a containment if given in the options
		this._setContainment();

		//Trigger event + callbacks
		if (this._trigger("start", event) === false) {
			this._clear();
			return false;
		}

		//Recache the helper size
		this._cacheHelperProportions();

		//Prepare the droppable offsets
		if ($.ui.ddmanager && !o.dropBehaviour) {
			$.ui.ddmanager.prepareOffsets(this, event);
		}

		this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position

		//If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003)
		if ( $.ui.ddmanager ) {
			$.ui.ddmanager.dragStart(this, event);
		}

		return true;
	},

	_mouseDrag: function(event, noPropagation) {
		// reset any necessary cached properties (see #5009)
		if ( this.offsetParentCssPosition === "fixed" ) {
			this.offset.parent = this._getParentOffset();
		}

		//Compute the helpers position
		this.position = this._generatePosition( event, true );
		this.positionAbs = this._convertPositionTo("absolute");

		//Call plugins and callbacks and use the resulting position if something is returned
		if (!noPropagation) {
			var ui = this._uiHash();
			if (this._trigger("drag", event, ui) === false) {
				this._mouseUp({});
				return false;
			}
			this.position = ui.position;
		}

		this.helper[ 0 ].style.left = this.position.left + "px";
		this.helper[ 0 ].style.top = this.position.top + "px";

		if ($.ui.ddmanager) {
			$.ui.ddmanager.drag(this, event);
		}

		return false;
	},

	_mouseStop: function(event) {

		//If we are using droppables, inform the manager about the drop
		var that = this,
			dropped = false;
		if ($.ui.ddmanager && !this.options.dropBehaviour) {
			dropped = $.ui.ddmanager.drop(this, event);
		}

		//if a drop comes from outside (a sortable)
		if (this.dropped) {
			dropped = this.dropped;
			this.dropped = false;
		}

		if ((this.options.revert === "invalid" && !dropped) || (this.options.revert === "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) {
			$(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() {
				if (that._trigger("stop", event) !== false) {
					that._clear();
				}
			});
		} else {
			if (this._trigger("stop", event) !== false) {
				this._clear();
			}
		}

		return false;
	},

	_mouseUp: function(event) {
		//Remove frame helpers
		$("div.ui-draggable-iframeFix").each(function() {
			this.parentNode.removeChild(this);
		});

		//If the ddmanager is used for droppables, inform the manager that dragging has stopped (see #5003)
		if ( $.ui.ddmanager ) {
			$.ui.ddmanager.dragStop(this, event);
		}

		// The interaction is over; whether or not the click resulted in a drag, focus the element
		this.element.focus();

		return $.ui.mouse.prototype._mouseUp.call(this, event);
	},

	cancel: function() {

		if (this.helper.is(".ui-draggable-dragging")) {
			this._mouseUp({});
		} else {
			this._clear();
		}

		return this;

	},

	_getHandle: function(event) {
		return this.options.handle ?
			!!$( event.target ).closest( this.element.find( this.options.handle ) ).length :
			true;
	},

	_setHandleClassName: function() {
		this.handleElement = this.options.handle ?
			this.element.find( this.options.handle ) : this.element;
		this.handleElement.addClass( "ui-draggable-handle" );
	},

	_removeHandleClassName: function() {
		this.handleElement.removeClass( "ui-draggable-handle" );
	},

	_createHelper: function(event) {

		var o = this.options,
			helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[ 0 ], [ event ])) : (o.helper === "clone" ? this.element.clone().removeAttr("id") : this.element);

		if (!helper.parents("body").length) {
			helper.appendTo((o.appendTo === "parent" ? this.element[0].parentNode : o.appendTo));
		}

		if (helper[0] !== this.element[0] && !(/(fixed|absolute)/).test(helper.css("position"))) {
			helper.css("position", "absolute");
		}

		return helper;

	},

	_adjustOffsetFromHelper: function(obj) {
		if (typeof obj === "string") {
			obj = obj.split(" ");
		}
		if ($.isArray(obj)) {
			obj = { left: +obj[0], top: +obj[1] || 0 };
		}
		if ("left" in obj) {
			this.offset.click.left = obj.left + this.margins.left;
		}
		if ("right" in obj) {
			this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
		}
		if ("top" in obj) {
			this.offset.click.top = obj.top + this.margins.top;
		}
		if ("bottom" in obj) {
			this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
		}
	},

	_isRootNode: function( element ) {
		return ( /(html|body)/i ).test( element.tagName ) || element === this.document[ 0 ];
	},

	_getParentOffset: function() {

		//Get the offsetParent and cache its position
		var po = this.offsetParent.offset(),
			document = this.document[ 0 ];

		// This is a special case where we need to modify a offset calculated on start, since the following happened:
		// 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
		// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
		//    the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
		if (this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) {
			po.left += this.scrollParent.scrollLeft();
			po.top += this.scrollParent.scrollTop();
		}

		if ( this._isRootNode( this.offsetParent[ 0 ] ) ) {
			po = { top: 0, left: 0 };
		}

		return {
			top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"), 10) || 0),
			left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"), 10) || 0)
		};

	},

	_getRelativeOffset: function() {
		if ( this.cssPosition !== "relative" ) {
			return { top: 0, left: 0 };
		}

		var p = this.element.position(),
			scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] );

		return {
			top: p.top - ( parseInt(this.helper.css( "top" ), 10) || 0 ) + ( !scrollIsRootNode ? this.scrollParent.scrollTop() : 0 ),
			left: p.left - ( parseInt(this.helper.css( "left" ), 10) || 0 ) + ( !scrollIsRootNode ? this.scrollParent.scrollLeft() : 0 )
		};

	},

	_cacheMargins: function() {
		this.margins = {
			left: (parseInt(this.element.css("marginLeft"), 10) || 0),
			top: (parseInt(this.element.css("marginTop"), 10) || 0),
			right: (parseInt(this.element.css("marginRight"), 10) || 0),
			bottom: (parseInt(this.element.css("marginBottom"), 10) || 0)
		};
	},

	_cacheHelperProportions: function() {
		this.helperProportions = {
			width: this.helper.outerWidth(),
			height: this.helper.outerHeight()
		};
	},

	_setContainment: function() {

		var over, c, ce,
			o = this.options,
			document = this.document[ 0 ];

		this.relativeContainer = null;

		if ( !o.containment ) {
			this.containment = null;
			return;
		}

		if ( o.containment === "window" ) {
			this.containment = [
				$( window ).scrollLeft() - this.offset.relative.left - this.offset.parent.left,
				$( window ).scrollTop() - this.offset.relative.top - this.offset.parent.top,
				$( window ).scrollLeft() + $( window ).width() - this.helperProportions.width - this.margins.left,
				$( window ).scrollTop() + ( $( window ).height() || document.body.parentNode.scrollHeight ) - this.helperProportions.height - this.margins.top
			];
			return;
		}

		if ( o.containment === "document") {
			this.containment = [
				0,
				0,
				$( document ).width() - this.helperProportions.width - this.margins.left,
				( $( document ).height() || document.body.parentNode.scrollHeight ) - this.helperProportions.height - this.margins.top
			];
			return;
		}

		if ( o.containment.constructor === Array ) {
			this.containment = o.containment;
			return;
		}

		if ( o.containment === "parent" ) {
			o.containment = this.helper[ 0 ].parentNode;
		}

		c = $( o.containment );
		ce = c[ 0 ];

		if ( !ce ) {
			return;
		}

		over = c.css( "overflow" ) !== "hidden";

		this.containment = [
			( parseInt( c.css( "borderLeftWidth" ), 10 ) || 0 ) + ( parseInt( c.css( "paddingLeft" ), 10 ) || 0 ),
			( parseInt( c.css( "borderTopWidth" ), 10 ) || 0 ) + ( parseInt( c.css( "paddingTop" ), 10 ) || 0 ),
			( over ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) - ( parseInt( c.css( "borderRightWidth" ), 10 ) || 0 ) - ( parseInt( c.css( "paddingRight" ), 10 ) || 0 ) - this.helperProportions.width - this.margins.left - this.margins.right,
			( over ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) - ( parseInt( c.css( "borderBottomWidth" ), 10 ) || 0 ) - ( parseInt( c.css( "paddingBottom" ), 10 ) || 0 ) - this.helperProportions.height - this.margins.top  - this.margins.bottom
		];
		this.relativeContainer = c;
	},

	_convertPositionTo: function(d, pos) {

		if (!pos) {
			pos = this.position;
		}

		var mod = d === "absolute" ? 1 : -1,
			scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] );

		return {
			top: (
				pos.top	+																// The absolute mouse position
				this.offset.relative.top * mod +										// Only for relative positioned nodes: Relative offset from element to offset parent
				this.offset.parent.top * mod -										// The offsetParent's offset without borders (offset + border)
				( ( this.cssPosition === "fixed" ? -this.offset.scroll.top : ( scrollIsRootNode ? 0 : this.offset.scroll.top ) ) * mod)
			),
			left: (
				pos.left +																// The absolute mouse position
				this.offset.relative.left * mod +										// Only for relative positioned nodes: Relative offset from element to offset parent
				this.offset.parent.left * mod	-										// The offsetParent's offset without borders (offset + border)
				( ( this.cssPosition === "fixed" ? -this.offset.scroll.left : ( scrollIsRootNode ? 0 : this.offset.scroll.left ) ) * mod)
			)
		};

	},

	_generatePosition: function( event, constrainPosition ) {

		var containment, co, top, left,
			o = this.options,
			scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] ),
			pageX = event.pageX,
			pageY = event.pageY;

		// Cache the scroll
		if ( !scrollIsRootNode || !this.offset.scroll ) {
			this.offset.scroll = {
				top: this.scrollParent.scrollTop(),
				left: this.scrollParent.scrollLeft()
			};
		}

		/*
		 * - Position constraining -
		 * Constrain the position to a mix of grid, containment.
		 */

		// If we are not dragging yet, we won't check for options
		if ( constrainPosition ) {
			if ( this.containment ) {
				if ( this.relativeContainer ){
					co = this.relativeContainer.offset();
					containment = [
						this.containment[ 0 ] + co.left,
						this.containment[ 1 ] + co.top,
						this.containment[ 2 ] + co.left,
						this.containment[ 3 ] + co.top
					];
				} else {
					containment = this.containment;
				}

				if (event.pageX - this.offset.click.left < containment[0]) {
					pageX = containment[0] + this.offset.click.left;
				}
				if (event.pageY - this.offset.click.top < containment[1]) {
					pageY = containment[1] + this.offset.click.top;
				}
				if (event.pageX - this.offset.click.left > containment[2]) {
					pageX = containment[2] + this.offset.click.left;
				}
				if (event.pageY - this.offset.click.top > containment[3]) {
					pageY = containment[3] + this.offset.click.top;
				}
			}

			if (o.grid) {
				//Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950)
				top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY;
				pageY = containment ? ((top - this.offset.click.top >= containment[1] || top - this.offset.click.top > containment[3]) ? top : ((top - this.offset.click.top >= containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;

				left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX;
				pageX = containment ? ((left - this.offset.click.left >= containment[0] || left - this.offset.click.left > containment[2]) ? left : ((left - this.offset.click.left >= containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
			}

			if ( o.axis === "y" ) {
				pageX = this.originalPageX;
			}

			if ( o.axis === "x" ) {
				pageY = this.originalPageY;
			}
		}

		return {
			top: (
				pageY -																	// The absolute mouse position
				this.offset.click.top	-												// Click offset (relative to the element)
				this.offset.relative.top -												// Only for relative positioned nodes: Relative offset from element to offset parent
				this.offset.parent.top +												// The offsetParent's offset without borders (offset + border)
				( this.cssPosition === "fixed" ? -this.offset.scroll.top : ( scrollIsRootNode ? 0 : this.offset.scroll.top ) )
			),
			left: (
				pageX -																	// The absolute mouse position
				this.offset.click.left -												// Click offset (relative to the element)
				this.offset.relative.left -												// Only for relative positioned nodes: Relative offset from element to offset parent
				this.offset.parent.left +												// The offsetParent's offset without borders (offset + border)
				( this.cssPosition === "fixed" ? -this.offset.scroll.left : ( scrollIsRootNode ? 0 : this.offset.scroll.left ) )
			)
		};

	},

	_clear: function() {
		this.helper.removeClass("ui-draggable-dragging");
		if (this.helper[0] !== this.element[0] && !this.cancelHelperRemoval) {
			this.helper.remove();
		}
		this.helper = null;
		this.cancelHelperRemoval = false;
		if ( this.destroyOnClear ) {
			this.destroy();
		}
	},

	// From now on bulk stuff - mainly helpers

	_trigger: function(type, event, ui) {
		ui = ui || this._uiHash();
		$.ui.plugin.call( this, type, [ event, ui, this ], true );
		//The absolute position has to be recalculated after plugins
		if (type === "drag") {
			this.positionAbs = this._convertPositionTo("absolute");
		}
		return $.Widget.prototype._trigger.call(this, type, event, ui);
	},

	plugins: {},

	_uiHash: function() {
		return {
			helper: this.helper,
			position: this.position,
			originalPosition: this.originalPosition,
			offset: this.positionAbs
		};
	}

});

$.ui.plugin.add("draggable", "connectToSortable", {
	start: function( event, ui, inst ) {

		var o = inst.options,
			uiSortable = $.extend({}, ui, { item: inst.element });
		inst.sortables = [];
		$(o.connectToSortable).each(function() {
			var sortable = $( this ).sortable( "instance" );
			if (sortable && !sortable.options.disabled) {
				inst.sortables.push({
					instance: sortable,
					shouldRevert: sortable.options.revert
				});
				sortable.refreshPositions();	// Call the sortable's refreshPositions at drag start to refresh the containerCache since the sortable container cache is used in drag and needs to be up to date (this will ensure it's initialised as well as being kept in step with any changes that might have happened on the page).
				sortable._trigger("activate", event, uiSortable);
			}
		});

	},
	stop: function( event, ui, inst ) {

		//If we are still over the sortable, we fake the stop event of the sortable, but also remove helper
		var uiSortable = $.extend( {}, ui, {
			item: inst.element
		});

		$.each(inst.sortables, function() {
			if (this.instance.isOver) {

				this.instance.isOver = 0;

				inst.cancelHelperRemoval = true; //Don't remove the helper in the draggable instance
				this.instance.cancelHelperRemoval = false; //Remove it in the sortable instance (so sortable plugins like revert still work)

				//The sortable revert is supported, and we have to set a temporary dropped variable on the draggable to support revert: "valid/invalid"
				if (this.shouldRevert) {
					this.instance.options.revert = this.shouldRevert;
				}

				//Trigger the stop of the sortable
				this.instance._mouseStop(event);

				this.instance.options.helper = this.instance.options._helper;

				//If the helper has been the original item, restore properties in the sortable
				if (inst.options.helper === "original") {
					this.instance.currentItem.css({ top: "auto", left: "auto" });
				}

			} else {
				this.instance.cancelHelperRemoval = false; //Remove the helper in the sortable instance
				this.instance._trigger("deactivate", event, uiSortable);
			}

		});

	},
	drag: function( event, ui, inst ) {

		var that = this;

		$.each(inst.sortables, function() {

			var innermostIntersecting = false,
				thisSortable = this;

			//Copy over some variables to allow calling the sortable's native _intersectsWith
			this.instance.positionAbs = inst.positionAbs;
			this.instance.helperProportions = inst.helperProportions;
			this.instance.offset.click = inst.offset.click;

			if (this.instance._intersectsWith(this.instance.containerCache)) {
				innermostIntersecting = true;
				$.each(inst.sortables, function() {
					this.instance.positionAbs = inst.positionAbs;
					this.instance.helperProportions = inst.helperProportions;
					this.instance.offset.click = inst.offset.click;
					if (this !== thisSortable &&
						this.instance._intersectsWith(this.instance.containerCache) &&
						$.contains(thisSortable.instance.element[0], this.instance.element[0])
					) {
						innermostIntersecting = false;
					}
					return innermostIntersecting;
				});
			}

			if (innermostIntersecting) {
				//If it intersects, we use a little isOver variable and set it once, so our move-in stuff gets fired only once
				if (!this.instance.isOver) {

					this.instance.isOver = 1;
					//Now we fake the start of dragging for the sortable instance,
					//by cloning the list group item, appending it to the sortable and using it as inst.currentItem
					//We can then fire the start event of the sortable with our passed browser event, and our own helper (so it doesn't create a new one)
					this.instance.currentItem = $(that).clone().removeAttr("id").appendTo(this.instance.element).data("ui-sortable-item", true);
					this.instance.options._helper = this.instance.options.helper; //Store helper option to later restore it
					this.instance.options.helper = function() { return ui.helper[0]; };

					event.target = this.instance.currentItem[0];
					this.instance._mouseCapture(event, true);
					this.instance._mouseStart(event, true, true);

					//Because the browser event is way off the new appended portlet, we modify a couple of variables to reflect the changes
					this.instance.offset.click.top = inst.offset.click.top;
					this.instance.offset.click.left = inst.offset.click.left;
					this.instance.offset.parent.left -= inst.offset.parent.left - this.instance.offset.parent.left;
					this.instance.offset.parent.top -= inst.offset.parent.top - this.instance.offset.parent.top;

					inst._trigger("toSortable", event);
					inst.dropped = this.instance.element; //draggable revert needs that
					//hack so receive/update callbacks work (mostly)
					inst.currentItem = inst.element;
					this.instance.fromOutside = inst;

				}

				//Provided we did all the previous steps, we can fire the drag event of the sortable on every draggable drag, when it intersects with the sortable
				if (this.instance.currentItem) {
					this.instance._mouseDrag(event);
				}

			} else {

				//If it doesn't intersect with the sortable, and it intersected before,
				//we fake the drag stop of the sortable, but make sure it doesn't remove the helper by using cancelHelperRemoval
				if (this.instance.isOver) {

					this.instance.isOver = 0;
					this.instance.cancelHelperRemoval = true;

					//Prevent reverting on this forced stop
					this.instance.options.revert = false;

					// The out event needs to be triggered independently
					this.instance._trigger("out", event, this.instance._uiHash(this.instance));

					this.instance._mouseStop(event, true);
					this.instance.options.helper = this.instance.options._helper;

					//Now we remove our currentItem, the list group clone again, and the placeholder, and animate the helper back to it's original size
					this.instance.currentItem.remove();
					if (this.instance.placeholder) {
						this.instance.placeholder.remove();
					}

					inst._trigger("fromSortable", event);
					inst.dropped = false; //draggable revert needs that
				}

			}

		});

	}
});

$.ui.plugin.add("draggable", "cursor", {
	start: function( event, ui, instance ) {
		var t = $( "body" ),
			o = instance.options;

		if (t.css("cursor")) {
			o._cursor = t.css("cursor");
		}
		t.css("cursor", o.cursor);
	},
	stop: function( event, ui, instance ) {
		var o = instance.options;
		if (o._cursor) {
			$("body").css("cursor", o._cursor);
		}
	}
});

$.ui.plugin.add("draggable", "opacity", {
	start: function( event, ui, instance ) {
		var t = $( ui.helper ),
			o = instance.options;
		if (t.css("opacity")) {
			o._opacity = t.css("opacity");
		}
		t.css("opacity", o.opacity);
	},
	stop: function( event, ui, instance ) {
		var o = instance.options;
		if (o._opacity) {
			$(ui.helper).css("opacity", o._opacity);
		}
	}
});

$.ui.plugin.add("draggable", "scroll", {
	start: function( event, ui, i ) {
		if ( !i.scrollParentNotHidden ) {
			i.scrollParentNotHidden = i.helper.scrollParent( false );
		}

		if ( i.scrollParentNotHidden[ 0 ] !== i.document[ 0 ] && i.scrollParentNotHidden[ 0 ].tagName !== "HTML" ) {
			i.overflowOffset = i.scrollParentNotHidden.offset();
		}
	},
	drag: function( event, ui, i  ) {

		var o = i.options,
			scrolled = false,
			scrollParent = i.scrollParentNotHidden[ 0 ],
			document = i.document[ 0 ];

		if ( scrollParent !== document && scrollParent.tagName !== "HTML" ) {
			if ( !o.axis || o.axis !== "x" ) {
				if ( ( i.overflowOffset.top + scrollParent.offsetHeight ) - event.pageY < o.scrollSensitivity ) {
					scrollParent.scrollTop = scrolled = scrollParent.scrollTop + o.scrollSpeed;
				} else if ( event.pageY - i.overflowOffset.top < o.scrollSensitivity ) {
					scrollParent.scrollTop = scrolled = scrollParent.scrollTop - o.scrollSpeed;
				}
			}

			if ( !o.axis || o.axis !== "y" ) {
				if ( ( i.overflowOffset.left + scrollParent.offsetWidth ) - event.pageX < o.scrollSensitivity ) {
					scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft + o.scrollSpeed;
				} else if ( event.pageX - i.overflowOffset.left < o.scrollSensitivity ) {
					scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft - o.scrollSpeed;
				}
			}

		} else {

			if (!o.axis || o.axis !== "x") {
				if (event.pageY - $(document).scrollTop() < o.scrollSensitivity) {
					scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
				} else if ($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) {
					scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
				}
			}

			if (!o.axis || o.axis !== "y") {
				if (event.pageX - $(document).scrollLeft() < o.scrollSensitivity) {
					scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
				} else if ($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) {
					scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
				}
			}

		}

		if (scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) {
			$.ui.ddmanager.prepareOffsets(i, event);
		}

	}
});

$.ui.plugin.add("draggable", "snap", {
	start: function( event, ui, i ) {

		var o = i.options;

		i.snapElements = [];

		$(o.snap.constructor !== String ? ( o.snap.items || ":data(ui-draggable)" ) : o.snap).each(function() {
			var $t = $(this),
				$o = $t.offset();
			if (this !== i.element[0]) {
				i.snapElements.push({
					item: this,
					width: $t.outerWidth(), height: $t.outerHeight(),
					top: $o.top, left: $o.left
				});
			}
		});

	},
	drag: function( event, ui, inst ) {

		var ts, bs, ls, rs, l, r, t, b, i, first,
			o = inst.options,
			d = o.snapTolerance,
			x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
			y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;

		for (i = inst.snapElements.length - 1; i >= 0; i--){

			l = inst.snapElements[i].left;
			r = l + inst.snapElements[i].width;
			t = inst.snapElements[i].top;
			b = t + inst.snapElements[i].height;

			if ( x2 < l - d || x1 > r + d || y2 < t - d || y1 > b + d || !$.contains( inst.snapElements[ i ].item.ownerDocument, inst.snapElements[ i ].item ) ) {
				if (inst.snapElements[i].snapping) {
					(inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
				}
				inst.snapElements[i].snapping = false;
				continue;
			}

			if (o.snapMode !== "inner") {
				ts = Math.abs(t - y2) <= d;
				bs = Math.abs(b - y1) <= d;
				ls = Math.abs(l - x2) <= d;
				rs = Math.abs(r - x1) <= d;
				if (ts) {
					ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
				}
				if (bs) {
					ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top;
				}
				if (ls) {
					ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left;
				}
				if (rs) {
					ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left;
				}
			}

			first = (ts || bs || ls || rs);

			if (o.snapMode !== "outer") {
				ts = Math.abs(t - y1) <= d;
				bs = Math.abs(b - y2) <= d;
				ls = Math.abs(l - x1) <= d;
				rs = Math.abs(r - x2) <= d;
				if (ts) {
					ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top;
				}
				if (bs) {
					ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
				}
				if (ls) {
					ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left;
				}
				if (rs) {
					ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left;
				}
			}

			if (!inst.snapElements[i].snapping && (ts || bs || ls || rs || first)) {
				(inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
			}
			inst.snapElements[i].snapping = (ts || bs || ls || rs || first);

		}

	}
});

$.ui.plugin.add("draggable", "stack", {
	start: function( event, ui, instance ) {
		var min,
			o = instance.options,
			group = $.makeArray($(o.stack)).sort(function(a, b) {
				return (parseInt($(a).css("zIndex"), 10) || 0) - (parseInt($(b).css("zIndex"), 10) || 0);
			});

		if (!group.length) { return; }

		min = parseInt($(group[0]).css("zIndex"), 10) || 0;
		$(group).each(function(i) {
			$(this).css("zIndex", min + i);
		});
		this.css("zIndex", (min + group.length));
	}
});

$.ui.plugin.add("draggable", "zIndex", {
	start: function( event, ui, instance ) {
		var t = $( ui.helper ),
			o = instance.options;

		if (t.css("zIndex")) {
			o._zIndex = t.css("zIndex");
		}
		t.css("zIndex", o.zIndex);
	},
	stop: function( event, ui, instance ) {
		var o = instance.options;

		if (o._zIndex) {
			$(ui.helper).css("zIndex", o._zIndex);
		}
	}
});

var draggable = $.ui.draggable;


/*!
 * jQuery UI Resizable 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/resizable/
 */


$.widget("ui.resizable", $.ui.mouse, {
	version: "1.11.1",
	widgetEventPrefix: "resize",
	options: {
		alsoResize: false,
		animate: false,
		animateDuration: "slow",
		animateEasing: "swing",
		aspectRatio: false,
		autoHide: false,
		containment: false,
		ghost: false,
		grid: false,
		handles: "e,s,se",
		helper: false,
		maxHeight: null,
		maxWidth: null,
		minHeight: 10,
		minWidth: 10,
		// See #7960
		zIndex: 90,

		// callbacks
		resize: null,
		start: null,
		stop: null
	},

	_num: function( value ) {
		return parseInt( value, 10 ) || 0;
	},

	_isNumber: function( value ) {
		return !isNaN( parseInt( value, 10 ) );
	},

	_hasScroll: function( el, a ) {

		if ( $( el ).css( "overflow" ) === "hidden") {
			return false;
		}

		var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop",
			has = false;

		if ( el[ scroll ] > 0 ) {
			return true;
		}

		// TODO: determine which cases actually cause this to happen
		// if the element doesn't have the scroll set, see if it's possible to
		// set the scroll
		el[ scroll ] = 1;
		has = ( el[ scroll ] > 0 );
		el[ scroll ] = 0;
		return has;
	},

	_create: function() {

		var n, i, handle, axis, hname,
			that = this,
			o = this.options;
		this.element.addClass("ui-resizable");

		$.extend(this, {
			_aspectRatio: !!(o.aspectRatio),
			aspectRatio: o.aspectRatio,
			originalElement: this.element,
			_proportionallyResizeElements: [],
			_helper: o.helper || o.ghost || o.animate ? o.helper || "ui-resizable-helper" : null
		});

		// Wrap the element if it cannot hold child nodes
		if (this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)) {

			this.element.wrap(
				$("<div class='ui-wrapper' style='overflow: hidden;'></div>").css({
					position: this.element.css("position"),
					width: this.element.outerWidth(),
					height: this.element.outerHeight(),
					top: this.element.css("top"),
					left: this.element.css("left")
				})
			);

			this.element = this.element.parent().data(
				"ui-resizable", this.element.resizable( "instance" )
			);

			this.elementIsWrapper = true;

			this.element.css({
				marginLeft: this.originalElement.css("marginLeft"),
				marginTop: this.originalElement.css("marginTop"),
				marginRight: this.originalElement.css("marginRight"),
				marginBottom: this.originalElement.css("marginBottom")
			});
			this.originalElement.css({
				marginLeft: 0,
				marginTop: 0,
				marginRight: 0,
				marginBottom: 0
			});
			// support: Safari
			// Prevent Safari textarea resize
			this.originalResizeStyle = this.originalElement.css("resize");
			this.originalElement.css("resize", "none");

			this._proportionallyResizeElements.push( this.originalElement.css({
				position: "static",
				zoom: 1,
				display: "block"
			}) );

			// support: IE9
			// avoid IE jump (hard set the margin)
			this.originalElement.css({ margin: this.originalElement.css("margin") });

			this._proportionallyResize();
		}

		this.handles = o.handles ||
			( !$(".ui-resizable-handle", this.element).length ?
				"e,s,se" : {
					n: ".ui-resizable-n",
					e: ".ui-resizable-e",
					s: ".ui-resizable-s",
					w: ".ui-resizable-w",
					se: ".ui-resizable-se",
					sw: ".ui-resizable-sw",
					ne: ".ui-resizable-ne",
					nw: ".ui-resizable-nw"
				} );

		if (this.handles.constructor === String) {

			if ( this.handles === "all") {
				this.handles = "n,e,s,w,se,sw,ne,nw";
			}

			n = this.handles.split(",");
			this.handles = {};

			for (i = 0; i < n.length; i++) {

				handle = $.trim(n[i]);
				hname = "ui-resizable-" + handle;
				axis = $("<div class='ui-resizable-handle " + hname + "'></div>");

				axis.css({ zIndex: o.zIndex });

				// TODO : What's going on here?
				if ("se" === handle) {
					axis.addClass("ui-icon ui-icon-gripsmall-diagonal-se");
				}

				this.handles[handle] = ".ui-resizable-" + handle;
				this.element.append(axis);
			}

		}

		this._renderAxis = function(target) {

			var i, axis, padPos, padWrapper;

			target = target || this.element;

			for (i in this.handles) {

				if (this.handles[i].constructor === String) {
					this.handles[i] = this.element.children( this.handles[ i ] ).first().show();
				}

				if (this.elementIsWrapper && this.originalElement[0].nodeName.match(/textarea|input|select|button/i)) {

					axis = $(this.handles[i], this.element);

					padWrapper = /sw|ne|nw|se|n|s/.test(i) ? axis.outerHeight() : axis.outerWidth();

					padPos = [ "padding",
						/ne|nw|n/.test(i) ? "Top" :
						/se|sw|s/.test(i) ? "Bottom" :
						/^e$/.test(i) ? "Right" : "Left" ].join("");

					target.css(padPos, padWrapper);

					this._proportionallyResize();

				}

				// TODO: What's that good for? There's not anything to be executed left
				if (!$(this.handles[i]).length) {
					continue;
				}
			}
		};

		// TODO: make renderAxis a prototype function
		this._renderAxis(this.element);

		this._handles = $(".ui-resizable-handle", this.element)
			.disableSelection();

		this._handles.mouseover(function() {
			if (!that.resizing) {
				if (this.className) {
					axis = this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);
				}
				that.axis = axis && axis[1] ? axis[1] : "se";
			}
		});

		if (o.autoHide) {
			this._handles.hide();
			$(this.element)
				.addClass("ui-resizable-autohide")
				.mouseenter(function() {
					if (o.disabled) {
						return;
					}
					$(this).removeClass("ui-resizable-autohide");
					that._handles.show();
				})
				.mouseleave(function() {
					if (o.disabled) {
						return;
					}
					if (!that.resizing) {
						$(this).addClass("ui-resizable-autohide");
						that._handles.hide();
					}
				});
		}

		this._mouseInit();

	},

	_destroy: function() {

		this._mouseDestroy();

		var wrapper,
			_destroy = function(exp) {
				$(exp)
					.removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing")
					.removeData("resizable")
					.removeData("ui-resizable")
					.unbind(".resizable")
					.find(".ui-resizable-handle")
						.remove();
			};

		// TODO: Unwrap at same DOM position
		if (this.elementIsWrapper) {
			_destroy(this.element);
			wrapper = this.element;
			this.originalElement.css({
				position: wrapper.css("position"),
				width: wrapper.outerWidth(),
				height: wrapper.outerHeight(),
				top: wrapper.css("top"),
				left: wrapper.css("left")
			}).insertAfter( wrapper );
			wrapper.remove();
		}

		this.originalElement.css("resize", this.originalResizeStyle);
		_destroy(this.originalElement);

		return this;
	},

	_mouseCapture: function(event) {
		var i, handle,
			capture = false;

		for (i in this.handles) {
			handle = $(this.handles[i])[0];
			if (handle === event.target || $.contains(handle, event.target)) {
				capture = true;
			}
		}

		return !this.options.disabled && capture;
	},

	_mouseStart: function(event) {

		var curleft, curtop, cursor,
			o = this.options,
			el = this.element;

		this.resizing = true;

		this._renderProxy();

		curleft = this._num(this.helper.css("left"));
		curtop = this._num(this.helper.css("top"));

		if (o.containment) {
			curleft += $(o.containment).scrollLeft() || 0;
			curtop += $(o.containment).scrollTop() || 0;
		}

		this.offset = this.helper.offset();
		this.position = { left: curleft, top: curtop };

		this.size = this._helper ? {
				width: this.helper.width(),
				height: this.helper.height()
			} : {
				width: el.width(),
				height: el.height()
			};

		this.originalSize = this._helper ? {
				width: el.outerWidth(),
				height: el.outerHeight()
			} : {
				width: el.width(),
				height: el.height()
			};

		this.sizeDiff = {
			width: el.outerWidth() - el.width(),
			height: el.outerHeight() - el.height()
		};

		this.originalPosition = { left: curleft, top: curtop };
		this.originalMousePosition = { left: event.pageX, top: event.pageY };

		this.aspectRatio = (typeof o.aspectRatio === "number") ?
			o.aspectRatio :
			((this.originalSize.width / this.originalSize.height) || 1);

		cursor = $(".ui-resizable-" + this.axis).css("cursor");
		$("body").css("cursor", cursor === "auto" ? this.axis + "-resize" : cursor);

		el.addClass("ui-resizable-resizing");
		this._propagate("start", event);
		return true;
	},

	_mouseDrag: function(event) {

		var data, props,
			smp = this.originalMousePosition,
			a = this.axis,
			dx = (event.pageX - smp.left) || 0,
			dy = (event.pageY - smp.top) || 0,
			trigger = this._change[a];

		this._updatePrevProperties();

		if (!trigger) {
			return false;
		}

		data = trigger.apply(this, [ event, dx, dy ]);

		this._updateVirtualBoundaries(event.shiftKey);
		if (this._aspectRatio || event.shiftKey) {
			data = this._updateRatio(data, event);
		}

		data = this._respectSize(data, event);

		this._updateCache(data);

		this._propagate("resize", event);

		props = this._applyChanges();

		if ( !this._helper && this._proportionallyResizeElements.length ) {
			this._proportionallyResize();
		}

		if ( !$.isEmptyObject( props ) ) {
			this._updatePrevProperties();
			this._trigger( "resize", event, this.ui() );
			this._applyChanges();
		}

		return false;
	},

	_mouseStop: function(event) {

		this.resizing = false;
		var pr, ista, soffseth, soffsetw, s, left, top,
			o = this.options, that = this;

		if (this._helper) {

			pr = this._proportionallyResizeElements;
			ista = pr.length && (/textarea/i).test(pr[0].nodeName);
			soffseth = ista && this._hasScroll(pr[0], "left") ? 0 : that.sizeDiff.height;
			soffsetw = ista ? 0 : that.sizeDiff.width;

			s = {
				width: (that.helper.width()  - soffsetw),
				height: (that.helper.height() - soffseth)
			};
			left = (parseInt(that.element.css("left"), 10) +
				(that.position.left - that.originalPosition.left)) || null;
			top = (parseInt(that.element.css("top"), 10) +
				(that.position.top - that.originalPosition.top)) || null;

			if (!o.animate) {
				this.element.css($.extend(s, { top: top, left: left }));
			}

			that.helper.height(that.size.height);
			that.helper.width(that.size.width);

			if (this._helper && !o.animate) {
				this._proportionallyResize();
			}
		}

		$("body").css("cursor", "auto");

		this.element.removeClass("ui-resizable-resizing");

		this._propagate("stop", event);

		if (this._helper) {
			this.helper.remove();
		}

		return false;

	},

	_updatePrevProperties: function() {
		this.prevPosition = {
			top: this.position.top,
			left: this.position.left
		};
		this.prevSize = {
			width: this.size.width,
			height: this.size.height
		};
	},

	_applyChanges: function() {
		var props = {};

		if ( this.position.top !== this.prevPosition.top ) {
			props.top = this.position.top + "px";
		}
		if ( this.position.left !== this.prevPosition.left ) {
			props.left = this.position.left + "px";
		}
		if ( this.size.width !== this.prevSize.width ) {
			props.width = this.size.width + "px";
		}
		if ( this.size.height !== this.prevSize.height ) {
			props.height = this.size.height + "px";
		}

		this.helper.css( props );

		return props;
	},

	_updateVirtualBoundaries: function(forceAspectRatio) {
		var pMinWidth, pMaxWidth, pMinHeight, pMaxHeight, b,
			o = this.options;

		b = {
			minWidth: this._isNumber(o.minWidth) ? o.minWidth : 0,
			maxWidth: this._isNumber(o.maxWidth) ? o.maxWidth : Infinity,
			minHeight: this._isNumber(o.minHeight) ? o.minHeight : 0,
			maxHeight: this._isNumber(o.maxHeight) ? o.maxHeight : Infinity
		};

		if (this._aspectRatio || forceAspectRatio) {
			pMinWidth = b.minHeight * this.aspectRatio;
			pMinHeight = b.minWidth / this.aspectRatio;
			pMaxWidth = b.maxHeight * this.aspectRatio;
			pMaxHeight = b.maxWidth / this.aspectRatio;

			if (pMinWidth > b.minWidth) {
				b.minWidth = pMinWidth;
			}
			if (pMinHeight > b.minHeight) {
				b.minHeight = pMinHeight;
			}
			if (pMaxWidth < b.maxWidth) {
				b.maxWidth = pMaxWidth;
			}
			if (pMaxHeight < b.maxHeight) {
				b.maxHeight = pMaxHeight;
			}
		}
		this._vBoundaries = b;
	},

	_updateCache: function(data) {
		this.offset = this.helper.offset();
		if (this._isNumber(data.left)) {
			this.position.left = data.left;
		}
		if (this._isNumber(data.top)) {
			this.position.top = data.top;
		}
		if (this._isNumber(data.height)) {
			this.size.height = data.height;
		}
		if (this._isNumber(data.width)) {
			this.size.width = data.width;
		}
	},

	_updateRatio: function( data ) {

		var cpos = this.position,
			csize = this.size,
			a = this.axis;

		if (this._isNumber(data.height)) {
			data.width = (data.height * this.aspectRatio);
		} else if (this._isNumber(data.width)) {
			data.height = (data.width / this.aspectRatio);
		}

		if (a === "sw") {
			data.left = cpos.left + (csize.width - data.width);
			data.top = null;
		}
		if (a === "nw") {
			data.top = cpos.top + (csize.height - data.height);
			data.left = cpos.left + (csize.width - data.width);
		}

		return data;
	},

	_respectSize: function( data ) {

		var o = this._vBoundaries,
			a = this.axis,
			ismaxw = this._isNumber(data.width) && o.maxWidth && (o.maxWidth < data.width),
			ismaxh = this._isNumber(data.height) && o.maxHeight && (o.maxHeight < data.height),
			isminw = this._isNumber(data.width) && o.minWidth && (o.minWidth > data.width),
			isminh = this._isNumber(data.height) && o.minHeight && (o.minHeight > data.height),
			dw = this.originalPosition.left + this.originalSize.width,
			dh = this.position.top + this.size.height,
			cw = /sw|nw|w/.test(a), ch = /nw|ne|n/.test(a);
		if (isminw) {
			data.width = o.minWidth;
		}
		if (isminh) {
			data.height = o.minHeight;
		}
		if (ismaxw) {
			data.width = o.maxWidth;
		}
		if (ismaxh) {
			data.height = o.maxHeight;
		}

		if (isminw && cw) {
			data.left = dw - o.minWidth;
		}
		if (ismaxw && cw) {
			data.left = dw - o.maxWidth;
		}
		if (isminh && ch) {
			data.top = dh - o.minHeight;
		}
		if (ismaxh && ch) {
			data.top = dh - o.maxHeight;
		}

		// Fixing jump error on top/left - bug #2330
		if (!data.width && !data.height && !data.left && data.top) {
			data.top = null;
		} else if (!data.width && !data.height && !data.top && data.left) {
			data.left = null;
		}

		return data;
	},

	_getPaddingPlusBorderDimensions: function( element ) {
		var i = 0,
			widths = [],
			borders = [
				element.css( "borderTopWidth" ),
				element.css( "borderRightWidth" ),
				element.css( "borderBottomWidth" ),
				element.css( "borderLeftWidth" )
			],
			paddings = [
				element.css( "paddingTop" ),
				element.css( "paddingRight" ),
				element.css( "paddingBottom" ),
				element.css( "paddingLeft" )
			];

		for ( ; i < 4; i++ ) {
			widths[ i ] = ( parseInt( borders[ i ], 10 ) || 0 );
			widths[ i ] += ( parseInt( paddings[ i ], 10 ) || 0 );
		}

		return {
			height: widths[ 0 ] + widths[ 2 ],
			width: widths[ 1 ] + widths[ 3 ]
		};
	},

	_proportionallyResize: function() {

		if (!this._proportionallyResizeElements.length) {
			return;
		}

		var prel,
			i = 0,
			element = this.helper || this.element;

		for ( ; i < this._proportionallyResizeElements.length; i++) {

			prel = this._proportionallyResizeElements[i];

			// TODO: Seems like a bug to cache this.outerDimensions
			// considering that we are in a loop.
			if (!this.outerDimensions) {
				this.outerDimensions = this._getPaddingPlusBorderDimensions( prel );
			}

			prel.css({
				height: (element.height() - this.outerDimensions.height) || 0,
				width: (element.width() - this.outerDimensions.width) || 0
			});

		}

	},

	_renderProxy: function() {

		var el = this.element, o = this.options;
		this.elementOffset = el.offset();

		if (this._helper) {

			this.helper = this.helper || $("<div style='overflow:hidden;'></div>");

			this.helper.addClass(this._helper).css({
				width: this.element.outerWidth() - 1,
				height: this.element.outerHeight() - 1,
				position: "absolute",
				left: this.elementOffset.left + "px",
				top: this.elementOffset.top + "px",
				zIndex: ++o.zIndex //TODO: Don't modify option
			});

			this.helper
				.appendTo("body")
				.disableSelection();

		} else {
			this.helper = this.element;
		}

	},

	_change: {
		e: function(event, dx) {
			return { width: this.originalSize.width + dx };
		},
		w: function(event, dx) {
			var cs = this.originalSize, sp = this.originalPosition;
			return { left: sp.left + dx, width: cs.width - dx };
		},
		n: function(event, dx, dy) {
			var cs = this.originalSize, sp = this.originalPosition;
			return { top: sp.top + dy, height: cs.height - dy };
		},
		s: function(event, dx, dy) {
			return { height: this.originalSize.height + dy };
		},
		se: function(event, dx, dy) {
			return $.extend(this._change.s.apply(this, arguments),
				this._change.e.apply(this, [ event, dx, dy ]));
		},
		sw: function(event, dx, dy) {
			return $.extend(this._change.s.apply(this, arguments),
				this._change.w.apply(this, [ event, dx, dy ]));
		},
		ne: function(event, dx, dy) {
			return $.extend(this._change.n.apply(this, arguments),
				this._change.e.apply(this, [ event, dx, dy ]));
		},
		nw: function(event, dx, dy) {
			return $.extend(this._change.n.apply(this, arguments),
				this._change.w.apply(this, [ event, dx, dy ]));
		}
	},

	_propagate: function(n, event) {
		$.ui.plugin.call(this, n, [ event, this.ui() ]);
		(n !== "resize" && this._trigger(n, event, this.ui()));
	},

	plugins: {},

	ui: function() {
		return {
			originalElement: this.originalElement,
			element: this.element,
			helper: this.helper,
			position: this.position,
			size: this.size,
			originalSize: this.originalSize,
			originalPosition: this.originalPosition
		};
	}

});

/*
 * Resizable Extensions
 */

$.ui.plugin.add("resizable", "animate", {

	stop: function( event ) {
		var that = $(this).resizable( "instance" ),
			o = that.options,
			pr = that._proportionallyResizeElements,
			ista = pr.length && (/textarea/i).test(pr[0].nodeName),
			soffseth = ista && that._hasScroll(pr[0], "left") ? 0 : that.sizeDiff.height,
			soffsetw = ista ? 0 : that.sizeDiff.width,
			style = { width: (that.size.width - soffsetw), height: (that.size.height - soffseth) },
			left = (parseInt(that.element.css("left"), 10) +
				(that.position.left - that.originalPosition.left)) || null,
			top = (parseInt(that.element.css("top"), 10) +
				(that.position.top - that.originalPosition.top)) || null;

		that.element.animate(
			$.extend(style, top && left ? { top: top, left: left } : {}), {
				duration: o.animateDuration,
				easing: o.animateEasing,
				step: function() {

					var data = {
						width: parseInt(that.element.css("width"), 10),
						height: parseInt(that.element.css("height"), 10),
						top: parseInt(that.element.css("top"), 10),
						left: parseInt(that.element.css("left"), 10)
					};

					if (pr && pr.length) {
						$(pr[0]).css({ width: data.width, height: data.height });
					}

					// propagating resize, and updating values for each animation step
					that._updateCache(data);
					that._propagate("resize", event);

				}
			}
		);
	}

});

$.ui.plugin.add( "resizable", "containment", {

	start: function() {
		var element, p, co, ch, cw, width, height,
			that = $( this ).resizable( "instance" ),
			o = that.options,
			el = that.element,
			oc = o.containment,
			ce = ( oc instanceof $ ) ? oc.get( 0 ) : ( /parent/.test( oc ) ) ? el.parent().get( 0 ) : oc;

		if ( !ce ) {
			return;
		}

		that.containerElement = $( ce );

		if ( /document/.test( oc ) || oc === document ) {
			that.containerOffset = {
				left: 0,
				top: 0
			};
			that.containerPosition = {
				left: 0,
				top: 0
			};

			that.parentData = {
				element: $( document ),
				left: 0,
				top: 0,
				width: $( document ).width(),
				height: $( document ).height() || document.body.parentNode.scrollHeight
			};
		} else {
			element = $( ce );
			p = [];
			$([ "Top", "Right", "Left", "Bottom" ]).each(function( i, name ) {
				p[ i ] = that._num( element.css( "padding" + name ) );
			});

			that.containerOffset = element.offset();
			that.containerPosition = element.position();
			that.containerSize = {
				height: ( element.innerHeight() - p[ 3 ] ),
				width: ( element.innerWidth() - p[ 1 ] )
			};

			co = that.containerOffset;
			ch = that.containerSize.height;
			cw = that.containerSize.width;
			width = ( that._hasScroll ( ce, "left" ) ? ce.scrollWidth : cw );
			height = ( that._hasScroll ( ce ) ? ce.scrollHeight : ch ) ;

			that.parentData = {
				element: ce,
				left: co.left,
				top: co.top,
				width: width,
				height: height
			};
		}
	},

	resize: function( event ) {
		var woset, hoset, isParent, isOffsetRelative,
			that = $( this ).resizable( "instance" ),
			o = that.options,
			co = that.containerOffset,
			cp = that.position,
			pRatio = that._aspectRatio || event.shiftKey,
			cop = {
				top: 0,
				left: 0
			},
			ce = that.containerElement,
			continueResize = true;

		if ( ce[ 0 ] !== document && ( /static/ ).test( ce.css( "position" ) ) ) {
			cop = co;
		}

		if ( cp.left < ( that._helper ? co.left : 0 ) ) {
			that.size.width = that.size.width +
				( that._helper ?
					( that.position.left - co.left ) :
					( that.position.left - cop.left ) );

			if ( pRatio ) {
				that.size.height = that.size.width / that.aspectRatio;
				continueResize = false;
			}
			that.position.left = o.helper ? co.left : 0;
		}

		if ( cp.top < ( that._helper ? co.top : 0 ) ) {
			that.size.height = that.size.height +
				( that._helper ?
					( that.position.top - co.top ) :
					that.position.top );

			if ( pRatio ) {
				that.size.width = that.size.height * that.aspectRatio;
				continueResize = false;
			}
			that.position.top = that._helper ? co.top : 0;
		}

		isParent = that.containerElement.get( 0 ) === that.element.parent().get( 0 );
		isOffsetRelative = /relative|absolute/.test( that.containerElement.css( "position" ) );

		if ( isParent && isOffsetRelative ) {
			that.offset.left = that.parentData.left + that.position.left;
			that.offset.top = that.parentData.top + that.position.top;
		} else {
			that.offset.left = that.element.offset().left;
			that.offset.top = that.element.offset().top;
		}

		woset = Math.abs( that.sizeDiff.width +
			(that._helper ?
				that.offset.left - cop.left :
				(that.offset.left - co.left)) );

		hoset = Math.abs( that.sizeDiff.height +
			(that._helper ?
				that.offset.top - cop.top :
				(that.offset.top - co.top)) );

		if ( woset + that.size.width >= that.parentData.width ) {
			that.size.width = that.parentData.width - woset;
			if ( pRatio ) {
				that.size.height = that.size.width / that.aspectRatio;
				continueResize = false;
			}
		}

		if ( hoset + that.size.height >= that.parentData.height ) {
			that.size.height = that.parentData.height - hoset;
			if ( pRatio ) {
				that.size.width = that.size.height * that.aspectRatio;
				continueResize = false;
			}
		}

		if ( !continueResize ){
			that.position.left = that.prevPosition.left;
			that.position.top = that.prevPosition.top;
			that.size.width = that.prevSize.width;
			that.size.height = that.prevSize.height;
		}
	},

	stop: function() {
		var that = $( this ).resizable( "instance" ),
			o = that.options,
			co = that.containerOffset,
			cop = that.containerPosition,
			ce = that.containerElement,
			helper = $( that.helper ),
			ho = helper.offset(),
			w = helper.outerWidth() - that.sizeDiff.width,
			h = helper.outerHeight() - that.sizeDiff.height;

		if ( that._helper && !o.animate && ( /relative/ ).test( ce.css( "position" ) ) ) {
			$( this ).css({
				left: ho.left - cop.left - co.left,
				width: w,
				height: h
			});
		}

		if ( that._helper && !o.animate && ( /static/ ).test( ce.css( "position" ) ) ) {
			$( this ).css({
				left: ho.left - cop.left - co.left,
				width: w,
				height: h
			});
		}
	}
});

$.ui.plugin.add("resizable", "alsoResize", {

	start: function() {
		var that = $(this).resizable( "instance" ),
			o = that.options,
			_store = function(exp) {
				$(exp).each(function() {
					var el = $(this);
					el.data("ui-resizable-alsoresize", {
						width: parseInt(el.width(), 10), height: parseInt(el.height(), 10),
						left: parseInt(el.css("left"), 10), top: parseInt(el.css("top"), 10)
					});
				});
			};

		if (typeof(o.alsoResize) === "object" && !o.alsoResize.parentNode) {
			if (o.alsoResize.length) {
				o.alsoResize = o.alsoResize[0];
				_store(o.alsoResize);
			} else {
				$.each(o.alsoResize, function(exp) {
					_store(exp);
				});
			}
		} else {
			_store(o.alsoResize);
		}
	},

	resize: function(event, ui) {
		var that = $(this).resizable( "instance" ),
			o = that.options,
			os = that.originalSize,
			op = that.originalPosition,
			delta = {
				height: (that.size.height - os.height) || 0,
				width: (that.size.width - os.width) || 0,
				top: (that.position.top - op.top) || 0,
				left: (that.position.left - op.left) || 0
			},

			_alsoResize = function(exp, c) {
				$(exp).each(function() {
					var el = $(this), start = $(this).data("ui-resizable-alsoresize"), style = {},
						css = c && c.length ?
							c :
							el.parents(ui.originalElement[0]).length ?
								[ "width", "height" ] :
								[ "width", "height", "top", "left" ];

					$.each(css, function(i, prop) {
						var sum = (start[prop] || 0) + (delta[prop] || 0);
						if (sum && sum >= 0) {
							style[prop] = sum || null;
						}
					});

					el.css(style);
				});
			};

		if (typeof(o.alsoResize) === "object" && !o.alsoResize.nodeType) {
			$.each(o.alsoResize, function(exp, c) {
				_alsoResize(exp, c);
			});
		} else {
			_alsoResize(o.alsoResize);
		}
	},

	stop: function() {
		$(this).removeData("resizable-alsoresize");
	}
});

$.ui.plugin.add("resizable", "ghost", {

	start: function() {

		var that = $(this).resizable( "instance" ), o = that.options, cs = that.size;

		that.ghost = that.originalElement.clone();
		that.ghost
			.css({
				opacity: 0.25,
				display: "block",
				position: "relative",
				height: cs.height,
				width: cs.width,
				margin: 0,
				left: 0,
				top: 0
			})
			.addClass("ui-resizable-ghost")
			.addClass(typeof o.ghost === "string" ? o.ghost : "");

		that.ghost.appendTo(that.helper);

	},

	resize: function() {
		var that = $(this).resizable( "instance" );
		if (that.ghost) {
			that.ghost.css({
				position: "relative",
				height: that.size.height,
				width: that.size.width
			});
		}
	},

	stop: function() {
		var that = $(this).resizable( "instance" );
		if (that.ghost && that.helper) {
			that.helper.get(0).removeChild(that.ghost.get(0));
		}
	}

});

$.ui.plugin.add("resizable", "grid", {

	resize: function() {
		var outerDimensions,
			that = $(this).resizable( "instance" ),
			o = that.options,
			cs = that.size,
			os = that.originalSize,
			op = that.originalPosition,
			a = that.axis,
			grid = typeof o.grid === "number" ? [ o.grid, o.grid ] : o.grid,
			gridX = (grid[0] || 1),
			gridY = (grid[1] || 1),
			ox = Math.round((cs.width - os.width) / gridX) * gridX,
			oy = Math.round((cs.height - os.height) / gridY) * gridY,
			newWidth = os.width + ox,
			newHeight = os.height + oy,
			isMaxWidth = o.maxWidth && (o.maxWidth < newWidth),
			isMaxHeight = o.maxHeight && (o.maxHeight < newHeight),
			isMinWidth = o.minWidth && (o.minWidth > newWidth),
			isMinHeight = o.minHeight && (o.minHeight > newHeight);

		o.grid = grid;

		if (isMinWidth) {
			newWidth += gridX;
		}
		if (isMinHeight) {
			newHeight += gridY;
		}
		if (isMaxWidth) {
			newWidth -= gridX;
		}
		if (isMaxHeight) {
			newHeight -= gridY;
		}

		if (/^(se|s|e)$/.test(a)) {
			that.size.width = newWidth;
			that.size.height = newHeight;
		} else if (/^(ne)$/.test(a)) {
			that.size.width = newWidth;
			that.size.height = newHeight;
			that.position.top = op.top - oy;
		} else if (/^(sw)$/.test(a)) {
			that.size.width = newWidth;
			that.size.height = newHeight;
			that.position.left = op.left - ox;
		} else {
			if ( newHeight - gridY <= 0 || newWidth - gridX <= 0) {
				outerDimensions = that._getPaddingPlusBorderDimensions( this );
			}

			if ( newHeight - gridY > 0 ) {
				that.size.height = newHeight;
				that.position.top = op.top - oy;
			} else {
				newHeight = gridY - outerDimensions.height;
				that.size.height = newHeight;
				that.position.top = op.top + os.height - newHeight;
			}
			if ( newWidth - gridX > 0 ) {
				that.size.width = newWidth;
				that.position.left = op.left - ox;
			} else {
				newWidth = gridY - outerDimensions.height;
				that.size.width = newWidth;
				that.position.left = op.left + os.width - newWidth;
			}
		}
	}

});

var resizable = $.ui.resizable;


/*!
 * jQuery UI Dialog 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/dialog/
 */


var dialog = $.widget( "ui.dialog", {
	version: "1.11.1",
	options: {
		appendTo: "body",
		autoOpen: true,
		buttons: [],
		closeOnEscape: true,
		closeText: "Close",
		dialogClass: "",
		draggable: true,
		hide: null,
		height: "auto",
		maxHeight: null,
		maxWidth: null,
		minHeight: 150,
		minWidth: 150,
		modal: false,
		position: {
			my: "center",
			at: "center",
			of: window,
			collision: "fit",
			// Ensure the titlebar is always visible
			using: function( pos ) {
				var topOffset = $( this ).css( pos ).offset().top;
				if ( topOffset < 0 ) {
					$( this ).css( "top", pos.top - topOffset );
				}
			}
		},
		resizable: true,
		show: null,
		title: null,
		width: 300,

		// callbacks
		beforeClose: null,
		close: null,
		drag: null,
		dragStart: null,
		dragStop: null,
		focus: null,
		open: null,
		resize: null,
		resizeStart: null,
		resizeStop: null
	},

	sizeRelatedOptions: {
		buttons: true,
		height: true,
		maxHeight: true,
		maxWidth: true,
		minHeight: true,
		minWidth: true,
		width: true
	},

	resizableRelatedOptions: {
		maxHeight: true,
		maxWidth: true,
		minHeight: true,
		minWidth: true
	},

	_create: function() {
		this.originalCss = {
			display: this.element[ 0 ].style.display,
			width: this.element[ 0 ].style.width,
			minHeight: this.element[ 0 ].style.minHeight,
			maxHeight: this.element[ 0 ].style.maxHeight,
			height: this.element[ 0 ].style.height
		};
		this.originalPosition = {
			parent: this.element.parent(),
			index: this.element.parent().children().index( this.element )
		};
		this.originalTitle = this.element.attr( "title" );
		this.options.title = this.options.title || this.originalTitle;

		this._createWrapper();

		this.element
			.show()
			.removeAttr( "title" )
			.addClass( "ui-dialog-content ui-widget-content" )
			.appendTo( this.uiDialog );

		this._createTitlebar();
		this._createButtonPane();

		if ( this.options.draggable && $.fn.draggable ) {
			this._makeDraggable();
		}
		if ( this.options.resizable && $.fn.resizable ) {
			this._makeResizable();
		}

		this._isOpen = false;

		this._trackFocus();
	},

	_init: function() {
		if ( this.options.autoOpen ) {
			this.open();
		}
	},

	_appendTo: function() {
		var element = this.options.appendTo;
		if ( element && (element.jquery || element.nodeType) ) {
			return $( element );
		}
		return this.document.find( element || "body" ).eq( 0 );
	},

	_destroy: function() {
		var next,
			originalPosition = this.originalPosition;

		this._destroyOverlay();

		this.element
			.removeUniqueId()
			.removeClass( "ui-dialog-content ui-widget-content" )
			.css( this.originalCss )
			// Without detaching first, the following becomes really slow
			.detach();

		this.uiDialog.stop( true, true ).remove();

		if ( this.originalTitle ) {
			this.element.attr( "title", this.originalTitle );
		}

		next = originalPosition.parent.children().eq( originalPosition.index );
		// Don't try to place the dialog next to itself (#8613)
		if ( next.length && next[ 0 ] !== this.element[ 0 ] ) {
			next.before( this.element );
		} else {
			originalPosition.parent.append( this.element );
		}
	},

	widget: function() {
		return this.uiDialog;
	},

	disable: $.noop,
	enable: $.noop,

	close: function( event ) {
		var activeElement,
			that = this;

		if ( !this._isOpen || this._trigger( "beforeClose", event ) === false ) {
			return;
		}

		this._isOpen = false;
		this._focusedElement = null;
		this._destroyOverlay();
		this._untrackInstance();

		if ( !this.opener.filter( ":focusable" ).focus().length ) {

			// support: IE9
			// IE9 throws an "Unspecified error" accessing document.activeElement from an <iframe>
			try {
				activeElement = this.document[ 0 ].activeElement;

				// Support: IE9, IE10
				// If the <body> is blurred, IE will switch windows, see #4520
				if ( activeElement && activeElement.nodeName.toLowerCase() !== "body" ) {

					// Hiding a focused element doesn't trigger blur in WebKit
					// so in case we have nothing to focus on, explicitly blur the active element
					// https://bugs.webkit.org/show_bug.cgi?id=47182
					$( activeElement ).blur();
				}
			} catch ( error ) {}
		}

		this._hide( this.uiDialog, this.options.hide, function() {
			that._trigger( "close", event );
		});
	},

	isOpen: function() {
		return this._isOpen;
	},

	moveToTop: function() {
		this._moveToTop();
	},

	_moveToTop: function( event, silent ) {
		var moved = false,
			zIndicies = this.uiDialog.siblings( ".ui-front:visible" ).map(function() {
				return +$( this ).css( "z-index" );
			}).get(),
			zIndexMax = Math.max.apply( null, zIndicies );

		if ( zIndexMax >= +this.uiDialog.css( "z-index" ) ) {
			this.uiDialog.css( "z-index", zIndexMax + 1 );
			moved = true;
		}

		if ( moved && !silent ) {
			this._trigger( "focus", event );
		}
		return moved;
	},

	open: function() {
		var that = this;
		if ( this._isOpen ) {
			if ( this._moveToTop() ) {
				this._focusTabbable();
			}
			return;
		}

		this._isOpen = true;
		this.opener = $( this.document[ 0 ].activeElement );

		this._size();
		this._position();
		this._createOverlay();
		this._moveToTop( null, true );

		// Ensure the overlay is moved to the top with the dialog, but only when
		// opening. The overlay shouldn't move after the dialog is open so that
		// modeless dialogs opened after the modal dialog stack properly.
		if ( this.overlay ) {
			this.overlay.css( "z-index", this.uiDialog.css( "z-index" ) - 1 );
		}

		this._show( this.uiDialog, this.options.show, function() {
			that._focusTabbable();
			that._trigger( "focus" );
		});

		// Track the dialog immediately upon openening in case a focus event
		// somehow occurs outside of the dialog before an element inside the
		// dialog is focused (#10152)
		this._makeFocusTarget();

		this._trigger( "open" );
	},

	_focusTabbable: function() {
		// Set focus to the first match:
		// 1. An element that was focused previously
		// 2. First element inside the dialog matching [autofocus]
		// 3. Tabbable element inside the content element
		// 4. Tabbable element inside the buttonpane
		// 5. The close button
		// 6. The dialog itself
		var hasFocus = this._focusedElement;
		if ( !hasFocus ) {
			hasFocus = this.element.find( "[autofocus]" );
		}
		if ( !hasFocus.length ) {
			hasFocus = this.element.find( ":tabbable" );
		}
		if ( !hasFocus.length ) {
			hasFocus = this.uiDialogButtonPane.find( ":tabbable" );
		}
		if ( !hasFocus.length ) {
			hasFocus = this.uiDialogTitlebarClose.filter( ":tabbable" );
		}
		if ( !hasFocus.length ) {
			hasFocus = this.uiDialog;
		}
		hasFocus.eq( 0 ).focus();
	},

	_keepFocus: function( event ) {
		function checkFocus() {
			var activeElement = this.document[0].activeElement,
				isActive = this.uiDialog[0] === activeElement ||
					$.contains( this.uiDialog[0], activeElement );
			if ( !isActive ) {
				this._focusTabbable();
			}
		}
		event.preventDefault();
		checkFocus.call( this );
		// support: IE
		// IE <= 8 doesn't prevent moving focus even with event.preventDefault()
		// so we check again later
		this._delay( checkFocus );
	},

	_createWrapper: function() {
		this.uiDialog = $("<div>")
			.addClass( "ui-dialog ui-widget ui-widget-content ui-corner-all ui-front " +
				this.options.dialogClass )
			.hide()
			.attr({
				// Setting tabIndex makes the div focusable
				tabIndex: -1,
				role: "dialog"
			})
			.appendTo( this._appendTo() );

		this._on( this.uiDialog, {
			keydown: function( event ) {
				if ( this.options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode &&
						event.keyCode === $.ui.keyCode.ESCAPE ) {
					event.preventDefault();
					this.close( event );
					return;
				}

				// prevent tabbing out of dialogs
				if ( event.keyCode !== $.ui.keyCode.TAB || event.isDefaultPrevented() ) {
					return;
				}
				var tabbables = this.uiDialog.find( ":tabbable" ),
					first = tabbables.filter( ":first" ),
					last = tabbables.filter( ":last" );

				if ( ( event.target === last[0] || event.target === this.uiDialog[0] ) && !event.shiftKey ) {
					this._delay(function() {
						first.focus();
					});
					event.preventDefault();
				} else if ( ( event.target === first[0] || event.target === this.uiDialog[0] ) && event.shiftKey ) {
					this._delay(function() {
						last.focus();
					});
					event.preventDefault();
				}
			},
			mousedown: function( event ) {
				if ( this._moveToTop( event ) ) {
					this._focusTabbable();
				}
			}
		});

		// We assume that any existing aria-describedby attribute means
		// that the dialog content is marked up properly
		// otherwise we brute force the content as the description
		if ( !this.element.find( "[aria-describedby]" ).length ) {
			this.uiDialog.attr({
				"aria-describedby": this.element.uniqueId().attr( "id" )
			});
		}
	},

	_createTitlebar: function() {
		var uiDialogTitle;

		this.uiDialogTitlebar = $( "<div>" )
			.addClass( "ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix" )
			.prependTo( this.uiDialog );
		this._on( this.uiDialogTitlebar, {
			mousedown: function( event ) {
				// Don't prevent click on close button (#8838)
				// Focusing a dialog that is partially scrolled out of view
				// causes the browser to scroll it into view, preventing the click event
				if ( !$( event.target ).closest( ".ui-dialog-titlebar-close" ) ) {
					// Dialog isn't getting focus when dragging (#8063)
					this.uiDialog.focus();
				}
			}
		});

		// support: IE
		// Use type="button" to prevent enter keypresses in textboxes from closing the
		// dialog in IE (#9312)
		this.uiDialogTitlebarClose = $( "<button type='button'></button>" )
			.button({
				label: this.options.closeText,
				icons: {
					primary: "ui-icon-closethick"
				},
				text: false
			})
			.addClass( "ui-dialog-titlebar-close" )
			.appendTo( this.uiDialogTitlebar );
		this._on( this.uiDialogTitlebarClose, {
			click: function( event ) {
				event.preventDefault();
				this.close( event );
			}
		});

		uiDialogTitle = $( "<span>" )
			.uniqueId()
			.addClass( "ui-dialog-title" )
			.prependTo( this.uiDialogTitlebar );
		this._title( uiDialogTitle );

		this.uiDialog.attr({
			"aria-labelledby": uiDialogTitle.attr( "id" )
		});
	},

	_title: function( title ) {
		if ( !this.options.title ) {
			title.html( "&#160;" );
		}
		title.text( this.options.title );
	},

	_createButtonPane: function() {
		this.uiDialogButtonPane = $( "<div>" )
			.addClass( "ui-dialog-buttonpane ui-widget-content ui-helper-clearfix" );

		this.uiButtonSet = $( "<div>" )
			.addClass( "ui-dialog-buttonset" )
			.appendTo( this.uiDialogButtonPane );

		this._createButtons();
	},

	_createButtons: function() {
		var that = this,
			buttons = this.options.buttons;

		// if we already have a button pane, remove it
		this.uiDialogButtonPane.remove();
		this.uiButtonSet.empty();

		if ( $.isEmptyObject( buttons ) || ($.isArray( buttons ) && !buttons.length) ) {
			this.uiDialog.removeClass( "ui-dialog-buttons" );
			return;
		}

		$.each( buttons, function( name, props ) {
			var click, buttonOptions;
			props = $.isFunction( props ) ?
				{ click: props, text: name } :
				props;
			// Default to a non-submitting button
			props = $.extend( { type: "button" }, props );
			// Change the context for the click callback to be the main element
			click = props.click;
			props.click = function() {
				click.apply( that.element[ 0 ], arguments );
			};
			buttonOptions = {
				icons: props.icons,
				text: props.showText
			};
			delete props.icons;
			delete props.showText;
			$( "<button></button>", props )
				.button( buttonOptions )
				.appendTo( that.uiButtonSet );
		});
		this.uiDialog.addClass( "ui-dialog-buttons" );
		this.uiDialogButtonPane.appendTo( this.uiDialog );
	},

	_makeDraggable: function() {
		var that = this,
			options = this.options;

		function filteredUi( ui ) {
			return {
				position: ui.position,
				offset: ui.offset
			};
		}

		this.uiDialog.draggable({
			cancel: ".ui-dialog-content, .ui-dialog-titlebar-close",
			handle: ".ui-dialog-titlebar",
			containment: "document",
			start: function( event, ui ) {
				$( this ).addClass( "ui-dialog-dragging" );
				that._blockFrames();
				that._trigger( "dragStart", event, filteredUi( ui ) );
			},
			drag: function( event, ui ) {
				that._trigger( "drag", event, filteredUi( ui ) );
			},
			stop: function( event, ui ) {
				var left = ui.offset.left - that.document.scrollLeft(),
					top = ui.offset.top - that.document.scrollTop();

				options.position = {
					my: "left top",
					at: "left" + (left >= 0 ? "+" : "") + left + " " +
						"top" + (top >= 0 ? "+" : "") + top,
					of: that.window
				};
				$( this ).removeClass( "ui-dialog-dragging" );
				that._unblockFrames();
				that._trigger( "dragStop", event, filteredUi( ui ) );
			}
		});
	},

	_makeResizable: function() {
		var that = this,
			options = this.options,
			handles = options.resizable,
			// .ui-resizable has position: relative defined in the stylesheet
			// but dialogs have to use absolute or fixed positioning
			position = this.uiDialog.css("position"),
			resizeHandles = typeof handles === "string" ?
				handles	:
				"n,e,s,w,se,sw,ne,nw";

		function filteredUi( ui ) {
			return {
				originalPosition: ui.originalPosition,
				originalSize: ui.originalSize,
				position: ui.position,
				size: ui.size
			};
		}

		this.uiDialog.resizable({
			cancel: ".ui-dialog-content",
			containment: "document",
			alsoResize: this.element,
			maxWidth: options.maxWidth,
			maxHeight: options.maxHeight,
			minWidth: options.minWidth,
			minHeight: this._minHeight(),
			handles: resizeHandles,
			start: function( event, ui ) {
				$( this ).addClass( "ui-dialog-resizing" );
				that._blockFrames();
				that._trigger( "resizeStart", event, filteredUi( ui ) );
			},
			resize: function( event, ui ) {
				that._trigger( "resize", event, filteredUi( ui ) );
			},
			stop: function( event, ui ) {
				var offset = that.uiDialog.offset(),
					left = offset.left - that.document.scrollLeft(),
					top = offset.top - that.document.scrollTop();

				options.height = that.uiDialog.height();
				options.width = that.uiDialog.width();
				options.position = {
					my: "left top",
					at: "left" + (left >= 0 ? "+" : "") + left + " " +
						"top" + (top >= 0 ? "+" : "") + top,
					of: that.window
				};
				$( this ).removeClass( "ui-dialog-resizing" );
				that._unblockFrames();
				that._trigger( "resizeStop", event, filteredUi( ui ) );
			}
		})
		.css( "position", position );
	},

	_trackFocus: function() {
		this._on( this.widget(), {
			focusin: function( event ) {
				this._makeFocusTarget();
				this._focusedElement = $( event.target );
			}
		});
	},

	_makeFocusTarget: function() {
		this._untrackInstance();
		this._trackingInstances().unshift( this );
	},

	_untrackInstance: function() {
		var instances = this._trackingInstances(),
			exists = $.inArray( this, instances );
		if ( exists !== -1 ) {
			instances.splice( exists, 1 );
		}
	},

	_trackingInstances: function() {
		var instances = this.document.data( "ui-dialog-instances" );
		if ( !instances ) {
			instances = [];
			this.document.data( "ui-dialog-instances", instances );
		}
		return instances;
	},

	_minHeight: function() {
		var options = this.options;

		return options.height === "auto" ?
			options.minHeight :
			Math.min( options.minHeight, options.height );
	},

	_position: function() {
		// Need to show the dialog to get the actual offset in the position plugin
		var isVisible = this.uiDialog.is( ":visible" );
		if ( !isVisible ) {
			this.uiDialog.show();
		}
		this.uiDialog.position( this.options.position );
		if ( !isVisible ) {
			this.uiDialog.hide();
		}
	},

	_setOptions: function( options ) {
		var that = this,
			resize = false,
			resizableOptions = {};

		$.each( options, function( key, value ) {
			that._setOption( key, value );

			if ( key in that.sizeRelatedOptions ) {
				resize = true;
			}
			if ( key in that.resizableRelatedOptions ) {
				resizableOptions[ key ] = value;
			}
		});

		if ( resize ) {
			this._size();
			this._position();
		}
		if ( this.uiDialog.is( ":data(ui-resizable)" ) ) {
			this.uiDialog.resizable( "option", resizableOptions );
		}
	},

	_setOption: function( key, value ) {
		var isDraggable, isResizable,
			uiDialog = this.uiDialog;

		if ( key === "dialogClass" ) {
			uiDialog
				.removeClass( this.options.dialogClass )
				.addClass( value );
		}

		if ( key === "disabled" ) {
			return;
		}

		this._super( key, value );

		if ( key === "appendTo" ) {
			this.uiDialog.appendTo( this._appendTo() );
		}

		if ( key === "buttons" ) {
			this._createButtons();
		}

		if ( key === "closeText" ) {
			this.uiDialogTitlebarClose.button({
				// Ensure that we always pass a string
				label: "" + value
			});
		}

		if ( key === "draggable" ) {
			isDraggable = uiDialog.is( ":data(ui-draggable)" );
			if ( isDraggable && !value ) {
				uiDialog.draggable( "destroy" );
			}

			if ( !isDraggable && value ) {
				this._makeDraggable();
			}
		}

		if ( key === "position" ) {
			this._position();
		}

		if ( key === "resizable" ) {
			// currently resizable, becoming non-resizable
			isResizable = uiDialog.is( ":data(ui-resizable)" );
			if ( isResizable && !value ) {
				uiDialog.resizable( "destroy" );
			}

			// currently resizable, changing handles
			if ( isResizable && typeof value === "string" ) {
				uiDialog.resizable( "option", "handles", value );
			}

			// currently non-resizable, becoming resizable
			if ( !isResizable && value !== false ) {
				this._makeResizable();
			}
		}

		if ( key === "title" ) {
			this._title( this.uiDialogTitlebar.find( ".ui-dialog-title" ) );
		}
	},

	_size: function() {
		// If the user has resized the dialog, the .ui-dialog and .ui-dialog-content
		// divs will both have width and height set, so we need to reset them
		var nonContentHeight, minContentHeight, maxContentHeight,
			options = this.options;

		// Reset content sizing
		this.element.show().css({
			width: "auto",
			minHeight: 0,
			maxHeight: "none",
			height: 0
		});

		if ( options.minWidth > options.width ) {
			options.width = options.minWidth;
		}

		// reset wrapper sizing
		// determine the height of all the non-content elements
		nonContentHeight = this.uiDialog.css({
				height: "auto",
				width: options.width
			})
			.outerHeight();
		minContentHeight = Math.max( 0, options.minHeight - nonContentHeight );
		maxContentHeight = typeof options.maxHeight === "number" ?
			Math.max( 0, options.maxHeight - nonContentHeight ) :
			"none";

		if ( options.height === "auto" ) {
			this.element.css({
				minHeight: minContentHeight,
				maxHeight: maxContentHeight,
				height: "auto"
			});
		} else {
			this.element.height( Math.max( 0, options.height - nonContentHeight ) );
		}

		if ( this.uiDialog.is( ":data(ui-resizable)" ) ) {
			this.uiDialog.resizable( "option", "minHeight", this._minHeight() );
		}
	},

	_blockFrames: function() {
		this.iframeBlocks = this.document.find( "iframe" ).map(function() {
			var iframe = $( this );

			return $( "<div>" )
				.css({
					position: "absolute",
					width: iframe.outerWidth(),
					height: iframe.outerHeight()
				})
				.appendTo( iframe.parent() )
				.offset( iframe.offset() )[0];
		});
	},

	_unblockFrames: function() {
		if ( this.iframeBlocks ) {
			this.iframeBlocks.remove();
			delete this.iframeBlocks;
		}
	},

	_allowInteraction: function( event ) {
		if ( $( event.target ).closest( ".ui-dialog" ).length ) {
			return true;
		}

		// TODO: Remove hack when datepicker implements
		// the .ui-front logic (#8989)
		return !!$( event.target ).closest( ".ui-datepicker" ).length;
	},

	_createOverlay: function() {
		if ( !this.options.modal ) {
			return;
		}

		// We use a delay in case the overlay is created from an
		// event that we're going to be cancelling (#2804)
		var isOpening = true;
		this._delay(function() {
			isOpening = false;
		});

		if ( !this.document.data( "ui-dialog-overlays" ) ) {

			// Prevent use of anchors and inputs
			// Using _on() for an event handler shared across many instances is
			// safe because the dialogs stack and must be closed in reverse order
			this._on( this.document, {
				focusin: function( event ) {
					if ( isOpening ) {
						return;
					}

					if ( !this._allowInteraction( event ) ) {
						event.preventDefault();
						this._trackingInstances()[ 0 ]._focusTabbable();
					}
				}
			});
		}

		this.overlay = $( "<div>" )
			.addClass( "ui-widget-overlay ui-front" )
			.appendTo( this._appendTo() );
		this._on( this.overlay, {
			mousedown: "_keepFocus"
		});
		this.document.data( "ui-dialog-overlays",
			(this.document.data( "ui-dialog-overlays" ) || 0) + 1 );
	},

	_destroyOverlay: function() {
		if ( !this.options.modal ) {
			return;
		}

		if ( this.overlay ) {
			var overlays = this.document.data( "ui-dialog-overlays" ) - 1;

			if ( !overlays ) {
				this.document
					.unbind( "focusin" )
					.removeData( "ui-dialog-overlays" );
			} else {
				this.document.data( "ui-dialog-overlays", overlays );
			}

			this.overlay.remove();
			this.overlay = null;
		}
	}
});


/*!
 * jQuery UI Droppable 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/droppable/
 */


$.widget( "ui.droppable", {
	version: "1.11.1",
	widgetEventPrefix: "drop",
	options: {
		accept: "*",
		activeClass: false,
		addClasses: true,
		greedy: false,
		hoverClass: false,
		scope: "default",
		tolerance: "intersect",

		// callbacks
		activate: null,
		deactivate: null,
		drop: null,
		out: null,
		over: null
	},
	_create: function() {

		var proportions,
			o = this.options,
			accept = o.accept;

		this.isover = false;
		this.isout = true;

		this.accept = $.isFunction( accept ) ? accept : function( d ) {
			return d.is( accept );
		};

		this.proportions = function( /* valueToWrite */ ) {
			if ( arguments.length ) {
				// Store the droppable's proportions
				proportions = arguments[ 0 ];
			} else {
				// Retrieve or derive the droppable's proportions
				return proportions ?
					proportions :
					proportions = {
						width: this.element[ 0 ].offsetWidth,
						height: this.element[ 0 ].offsetHeight
					};
			}
		};

		this._addToManager( o.scope );

		o.addClasses && this.element.addClass( "ui-droppable" );

	},

	_addToManager: function( scope ) {
		// Add the reference and positions to the manager
		$.ui.ddmanager.droppables[ scope ] = $.ui.ddmanager.droppables[ scope ] || [];
		$.ui.ddmanager.droppables[ scope ].push( this );
	},

	_splice: function( drop ) {
		var i = 0;
		for ( ; i < drop.length; i++ ) {
			if ( drop[ i ] === this ) {
				drop.splice( i, 1 );
			}
		}
	},

	_destroy: function() {
		var drop = $.ui.ddmanager.droppables[ this.options.scope ];

		this._splice( drop );

		this.element.removeClass( "ui-droppable ui-droppable-disabled" );
	},

	_setOption: function( key, value ) {

		if ( key === "accept" ) {
			this.accept = $.isFunction( value ) ? value : function( d ) {
				return d.is( value );
			};
		} else if ( key === "scope" ) {
			var drop = $.ui.ddmanager.droppables[ this.options.scope ];

			this._splice( drop );
			this._addToManager( value );
		}

		this._super( key, value );
	},

	_activate: function( event ) {
		var draggable = $.ui.ddmanager.current;
		if ( this.options.activeClass ) {
			this.element.addClass( this.options.activeClass );
		}
		if ( draggable ){
			this._trigger( "activate", event, this.ui( draggable ) );
		}
	},

	_deactivate: function( event ) {
		var draggable = $.ui.ddmanager.current;
		if ( this.options.activeClass ) {
			this.element.removeClass( this.options.activeClass );
		}
		if ( draggable ){
			this._trigger( "deactivate", event, this.ui( draggable ) );
		}
	},

	_over: function( event ) {

		var draggable = $.ui.ddmanager.current;

		// Bail if draggable and droppable are same element
		if ( !draggable || ( draggable.currentItem || draggable.element )[ 0 ] === this.element[ 0 ] ) {
			return;
		}

		if ( this.accept.call( this.element[ 0 ], ( draggable.currentItem || draggable.element ) ) ) {
			if ( this.options.hoverClass ) {
				this.element.addClass( this.options.hoverClass );
			}
			this._trigger( "over", event, this.ui( draggable ) );
		}

	},

	_out: function( event ) {

		var draggable = $.ui.ddmanager.current;

		// Bail if draggable and droppable are same element
		if ( !draggable || ( draggable.currentItem || draggable.element )[ 0 ] === this.element[ 0 ] ) {
			return;
		}

		if ( this.accept.call( this.element[ 0 ], ( draggable.currentItem || draggable.element ) ) ) {
			if ( this.options.hoverClass ) {
				this.element.removeClass( this.options.hoverClass );
			}
			this._trigger( "out", event, this.ui( draggable ) );
		}

	},

	_drop: function( event, custom ) {

		var draggable = custom || $.ui.ddmanager.current,
			childrenIntersection = false;

		// Bail if draggable and droppable are same element
		if ( !draggable || ( draggable.currentItem || draggable.element )[ 0 ] === this.element[ 0 ] ) {
			return false;
		}

		this.element.find( ":data(ui-droppable)" ).not( ".ui-draggable-dragging" ).each(function() {
			var inst = $( this ).droppable( "instance" );
			if (
				inst.options.greedy &&
				!inst.options.disabled &&
				inst.options.scope === draggable.options.scope &&
				inst.accept.call( inst.element[ 0 ], ( draggable.currentItem || draggable.element ) ) &&
				$.ui.intersect( draggable, $.extend( inst, { offset: inst.element.offset() } ), inst.options.tolerance, event )
			) { childrenIntersection = true; return false; }
		});
		if ( childrenIntersection ) {
			return false;
		}

		if ( this.accept.call( this.element[ 0 ], ( draggable.currentItem || draggable.element ) ) ) {
			if ( this.options.activeClass ) {
				this.element.removeClass( this.options.activeClass );
			}
			if ( this.options.hoverClass ) {
				this.element.removeClass( this.options.hoverClass );
			}
			this._trigger( "drop", event, this.ui( draggable ) );
			return this.element;
		}

		return false;

	},

	ui: function( c ) {
		return {
			draggable: ( c.currentItem || c.element ),
			helper: c.helper,
			position: c.position,
			offset: c.positionAbs
		};
	}

});

$.ui.intersect = (function() {
	function isOverAxis( x, reference, size ) {
		return ( x >= reference ) && ( x < ( reference + size ) );
	}

	return function( draggable, droppable, toleranceMode, event ) {

		if ( !droppable.offset ) {
			return false;
		}

		var x1 = ( draggable.positionAbs || draggable.position.absolute ).left,
			y1 = ( draggable.positionAbs || draggable.position.absolute ).top,
			x2 = x1 + draggable.helperProportions.width,
			y2 = y1 + draggable.helperProportions.height,
			l = droppable.offset.left,
			t = droppable.offset.top,
			r = l + droppable.proportions().width,
			b = t + droppable.proportions().height;

		switch ( toleranceMode ) {
		case "fit":
			return ( l <= x1 && x2 <= r && t <= y1 && y2 <= b );
		case "intersect":
			return ( l < x1 + ( draggable.helperProportions.width / 2 ) && // Right Half
				x2 - ( draggable.helperProportions.width / 2 ) < r && // Left Half
				t < y1 + ( draggable.helperProportions.height / 2 ) && // Bottom Half
				y2 - ( draggable.helperProportions.height / 2 ) < b ); // Top Half
		case "pointer":
			return isOverAxis( event.pageY, t, droppable.proportions().height ) && isOverAxis( event.pageX, l, droppable.proportions().width );
		case "touch":
			return (
				( y1 >= t && y1 <= b ) || // Top edge touching
				( y2 >= t && y2 <= b ) || // Bottom edge touching
				( y1 < t && y2 > b ) // Surrounded vertically
			) && (
				( x1 >= l && x1 <= r ) || // Left edge touching
				( x2 >= l && x2 <= r ) || // Right edge touching
				( x1 < l && x2 > r ) // Surrounded horizontally
			);
		default:
			return false;
		}
	};
})();

/*
	This manager tracks offsets of draggables and droppables
*/
$.ui.ddmanager = {
	current: null,
	droppables: { "default": [] },
	prepareOffsets: function( t, event ) {

		var i, j,
			m = $.ui.ddmanager.droppables[ t.options.scope ] || [],
			type = event ? event.type : null, // workaround for #2317
			list = ( t.currentItem || t.element ).find( ":data(ui-droppable)" ).addBack();

		droppablesLoop: for ( i = 0; i < m.length; i++ ) {

			// No disabled and non-accepted
			if ( m[ i ].options.disabled || ( t && !m[ i ].accept.call( m[ i ].element[ 0 ], ( t.currentItem || t.element ) ) ) ) {
				continue;
			}

			// Filter out elements in the current dragged item
			for ( j = 0; j < list.length; j++ ) {
				if ( list[ j ] === m[ i ].element[ 0 ] ) {
					m[ i ].proportions().height = 0;
					continue droppablesLoop;
				}
			}

			m[ i ].visible = m[ i ].element.css( "display" ) !== "none";
			if ( !m[ i ].visible ) {
				continue;
			}

			// Activate the droppable if used directly from draggables
			if ( type === "mousedown" ) {
				m[ i ]._activate.call( m[ i ], event );
			}

			m[ i ].offset = m[ i ].element.offset();
			m[ i ].proportions({ width: m[ i ].element[ 0 ].offsetWidth, height: m[ i ].element[ 0 ].offsetHeight });

		}

	},
	drop: function( draggable, event ) {

		var dropped = false;
		// Create a copy of the droppables in case the list changes during the drop (#9116)
		$.each( ( $.ui.ddmanager.droppables[ draggable.options.scope ] || [] ).slice(), function() {

			if ( !this.options ) {
				return;
			}
			if ( !this.options.disabled && this.visible && $.ui.intersect( draggable, this, this.options.tolerance, event ) ) {
				dropped = this._drop.call( this, event ) || dropped;
			}

			if ( !this.options.disabled && this.visible && this.accept.call( this.element[ 0 ], ( draggable.currentItem || draggable.element ) ) ) {
				this.isout = true;
				this.isover = false;
				this._deactivate.call( this, event );
			}

		});
		return dropped;

	},
	dragStart: function( draggable, event ) {
		// Listen for scrolling so that if the dragging causes scrolling the position of the droppables can be recalculated (see #5003)
		draggable.element.parentsUntil( "body" ).bind( "scroll.droppable", function() {
			if ( !draggable.options.refreshPositions ) {
				$.ui.ddmanager.prepareOffsets( draggable, event );
			}
		});
	},
	drag: function( draggable, event ) {

		// If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse.
		if ( draggable.options.refreshPositions ) {
			$.ui.ddmanager.prepareOffsets( draggable, event );
		}

		// Run through all droppables and check their positions based on specific tolerance options
		$.each( $.ui.ddmanager.droppables[ draggable.options.scope ] || [], function() {

			if ( this.options.disabled || this.greedyChild || !this.visible ) {
				return;
			}

			var parentInstance, scope, parent,
				intersects = $.ui.intersect( draggable, this, this.options.tolerance, event ),
				c = !intersects && this.isover ? "isout" : ( intersects && !this.isover ? "isover" : null );
			if ( !c ) {
				return;
			}

			if ( this.options.greedy ) {
				// find droppable parents with same scope
				scope = this.options.scope;
				parent = this.element.parents( ":data(ui-droppable)" ).filter(function() {
					return $( this ).droppable( "instance" ).options.scope === scope;
				});

				if ( parent.length ) {
					parentInstance = $( parent[ 0 ] ).droppable( "instance" );
					parentInstance.greedyChild = ( c === "isover" );
				}
			}

			// we just moved into a greedy child
			if ( parentInstance && c === "isover" ) {
				parentInstance.isover = false;
				parentInstance.isout = true;
				parentInstance._out.call( parentInstance, event );
			}

			this[ c ] = true;
			this[c === "isout" ? "isover" : "isout"] = false;
			this[c === "isover" ? "_over" : "_out"].call( this, event );

			// we just moved out of a greedy child
			if ( parentInstance && c === "isout" ) {
				parentInstance.isout = false;
				parentInstance.isover = true;
				parentInstance._over.call( parentInstance, event );
			}
		});

	},
	dragStop: function( draggable, event ) {
		draggable.element.parentsUntil( "body" ).unbind( "scroll.droppable" );
		// Call prepareOffsets one final time since IE does not fire return scroll events when overflow was caused by drag (see #5003)
		if ( !draggable.options.refreshPositions ) {
			$.ui.ddmanager.prepareOffsets( draggable, event );
		}
	}
};

var droppable = $.ui.droppable;


/*!
 * jQuery UI Effects 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/category/effects-core/
 */


var dataSpace = "ui-effects-",

	// Create a local jQuery because jQuery Color relies on it and the
	// global may not exist with AMD and a custom build (#10199)
	jQuery = $;

$.effects = {
	effect: {}
};

/*!
 * jQuery Color Animations v2.1.2
 * https://github.com/jquery/jquery-color
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * Date: Wed Jan 16 08:47:09 2013 -0600
 */
(function( jQuery, undefined ) {

	var stepHooks = "backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",

	// plusequals test for += 100 -= 100
	rplusequals = /^([\-+])=\s*(\d+\.?\d*)/,
	// a set of RE's that can match strings and generate color tuples.
	stringParsers = [ {
			re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,
			parse: function( execResult ) {
				return [
					execResult[ 1 ],
					execResult[ 2 ],
					execResult[ 3 ],
					execResult[ 4 ]
				];
			}
		}, {
			re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,
			parse: function( execResult ) {
				return [
					execResult[ 1 ] * 2.55,
					execResult[ 2 ] * 2.55,
					execResult[ 3 ] * 2.55,
					execResult[ 4 ]
				];
			}
		}, {
			// this regex ignores A-F because it's compared against an already lowercased string
			re: /#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,
			parse: function( execResult ) {
				return [
					parseInt( execResult[ 1 ], 16 ),
					parseInt( execResult[ 2 ], 16 ),
					parseInt( execResult[ 3 ], 16 )
				];
			}
		}, {
			// this regex ignores A-F because it's compared against an already lowercased string
			re: /#([a-f0-9])([a-f0-9])([a-f0-9])/,
			parse: function( execResult ) {
				return [
					parseInt( execResult[ 1 ] + execResult[ 1 ], 16 ),
					parseInt( execResult[ 2 ] + execResult[ 2 ], 16 ),
					parseInt( execResult[ 3 ] + execResult[ 3 ], 16 )
				];
			}
		}, {
			re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,
			space: "hsla",
			parse: function( execResult ) {
				return [
					execResult[ 1 ],
					execResult[ 2 ] / 100,
					execResult[ 3 ] / 100,
					execResult[ 4 ]
				];
			}
		} ],

	// jQuery.Color( )
	color = jQuery.Color = function( color, green, blue, alpha ) {
		return new jQuery.Color.fn.parse( color, green, blue, alpha );
	},
	spaces = {
		rgba: {
			props: {
				red: {
					idx: 0,
					type: "byte"
				},
				green: {
					idx: 1,
					type: "byte"
				},
				blue: {
					idx: 2,
					type: "byte"
				}
			}
		},

		hsla: {
			props: {
				hue: {
					idx: 0,
					type: "degrees"
				},
				saturation: {
					idx: 1,
					type: "percent"
				},
				lightness: {
					idx: 2,
					type: "percent"
				}
			}
		}
	},
	propTypes = {
		"byte": {
			floor: true,
			max: 255
		},
		"percent": {
			max: 1
		},
		"degrees": {
			mod: 360,
			floor: true
		}
	},
	support = color.support = {},

	// element for support tests
	supportElem = jQuery( "<p>" )[ 0 ],

	// colors = jQuery.Color.names
	colors,

	// local aliases of functions called often
	each = jQuery.each;

// determine rgba support immediately
supportElem.style.cssText = "background-color:rgba(1,1,1,.5)";
support.rgba = supportElem.style.backgroundColor.indexOf( "rgba" ) > -1;

// define cache name and alpha properties
// for rgba and hsla spaces
each( spaces, function( spaceName, space ) {
	space.cache = "_" + spaceName;
	space.props.alpha = {
		idx: 3,
		type: "percent",
		def: 1
	};
});

function clamp( value, prop, allowEmpty ) {
	var type = propTypes[ prop.type ] || {};

	if ( value == null ) {
		return (allowEmpty || !prop.def) ? null : prop.def;
	}

	// ~~ is an short way of doing floor for positive numbers
	value = type.floor ? ~~value : parseFloat( value );

	// IE will pass in empty strings as value for alpha,
	// which will hit this case
	if ( isNaN( value ) ) {
		return prop.def;
	}

	if ( type.mod ) {
		// we add mod before modding to make sure that negatives values
		// get converted properly: -10 -> 350
		return (value + type.mod) % type.mod;
	}

	// for now all property types without mod have min and max
	return 0 > value ? 0 : type.max < value ? type.max : value;
}

function stringParse( string ) {
	var inst = color(),
		rgba = inst._rgba = [];

	string = string.toLowerCase();

	each( stringParsers, function( i, parser ) {
		var parsed,
			match = parser.re.exec( string ),
			values = match && parser.parse( match ),
			spaceName = parser.space || "rgba";

		if ( values ) {
			parsed = inst[ spaceName ]( values );

			// if this was an rgba parse the assignment might happen twice
			// oh well....
			inst[ spaces[ spaceName ].cache ] = parsed[ spaces[ spaceName ].cache ];
			rgba = inst._rgba = parsed._rgba;

			// exit each( stringParsers ) here because we matched
			return false;
		}
	});

	// Found a stringParser that handled it
	if ( rgba.length ) {

		// if this came from a parsed string, force "transparent" when alpha is 0
		// chrome, (and maybe others) return "transparent" as rgba(0,0,0,0)
		if ( rgba.join() === "0,0,0,0" ) {
			jQuery.extend( rgba, colors.transparent );
		}
		return inst;
	}

	// named colors
	return colors[ string ];
}

color.fn = jQuery.extend( color.prototype, {
	parse: function( red, green, blue, alpha ) {
		if ( red === undefined ) {
			this._rgba = [ null, null, null, null ];
			return this;
		}
		if ( red.jquery || red.nodeType ) {
			red = jQuery( red ).css( green );
			green = undefined;
		}

		var inst = this,
			type = jQuery.type( red ),
			rgba = this._rgba = [];

		// more than 1 argument specified - assume ( red, green, blue, alpha )
		if ( green !== undefined ) {
			red = [ red, green, blue, alpha ];
			type = "array";
		}

		if ( type === "string" ) {
			return this.parse( stringParse( red ) || colors._default );
		}

		if ( type === "array" ) {
			each( spaces.rgba.props, function( key, prop ) {
				rgba[ prop.idx ] = clamp( red[ prop.idx ], prop );
			});
			return this;
		}

		if ( type === "object" ) {
			if ( red instanceof color ) {
				each( spaces, function( spaceName, space ) {
					if ( red[ space.cache ] ) {
						inst[ space.cache ] = red[ space.cache ].slice();
					}
				});
			} else {
				each( spaces, function( spaceName, space ) {
					var cache = space.cache;
					each( space.props, function( key, prop ) {

						// if the cache doesn't exist, and we know how to convert
						if ( !inst[ cache ] && space.to ) {

							// if the value was null, we don't need to copy it
							// if the key was alpha, we don't need to copy it either
							if ( key === "alpha" || red[ key ] == null ) {
								return;
							}
							inst[ cache ] = space.to( inst._rgba );
						}

						// this is the only case where we allow nulls for ALL properties.
						// call clamp with alwaysAllowEmpty
						inst[ cache ][ prop.idx ] = clamp( red[ key ], prop, true );
					});

					// everything defined but alpha?
					if ( inst[ cache ] && jQuery.inArray( null, inst[ cache ].slice( 0, 3 ) ) < 0 ) {
						// use the default of 1
						inst[ cache ][ 3 ] = 1;
						if ( space.from ) {
							inst._rgba = space.from( inst[ cache ] );
						}
					}
				});
			}
			return this;
		}
	},
	is: function( compare ) {
		var is = color( compare ),
			same = true,
			inst = this;

		each( spaces, function( _, space ) {
			var localCache,
				isCache = is[ space.cache ];
			if (isCache) {
				localCache = inst[ space.cache ] || space.to && space.to( inst._rgba ) || [];
				each( space.props, function( _, prop ) {
					if ( isCache[ prop.idx ] != null ) {
						same = ( isCache[ prop.idx ] === localCache[ prop.idx ] );
						return same;
					}
				});
			}
			return same;
		});
		return same;
	},
	_space: function() {
		var used = [],
			inst = this;
		each( spaces, function( spaceName, space ) {
			if ( inst[ space.cache ] ) {
				used.push( spaceName );
			}
		});
		return used.pop();
	},
	transition: function( other, distance ) {
		var end = color( other ),
			spaceName = end._space(),
			space = spaces[ spaceName ],
			startColor = this.alpha() === 0 ? color( "transparent" ) : this,
			start = startColor[ space.cache ] || space.to( startColor._rgba ),
			result = start.slice();

		end = end[ space.cache ];
		each( space.props, function( key, prop ) {
			var index = prop.idx,
				startValue = start[ index ],
				endValue = end[ index ],
				type = propTypes[ prop.type ] || {};

			// if null, don't override start value
			if ( endValue === null ) {
				return;
			}
			// if null - use end
			if ( startValue === null ) {
				result[ index ] = endValue;
			} else {
				if ( type.mod ) {
					if ( endValue - startValue > type.mod / 2 ) {
						startValue += type.mod;
					} else if ( startValue - endValue > type.mod / 2 ) {
						startValue -= type.mod;
					}
				}
				result[ index ] = clamp( ( endValue - startValue ) * distance + startValue, prop );
			}
		});
		return this[ spaceName ]( result );
	},
	blend: function( opaque ) {
		// if we are already opaque - return ourself
		if ( this._rgba[ 3 ] === 1 ) {
			return this;
		}

		var rgb = this._rgba.slice(),
			a = rgb.pop(),
			blend = color( opaque )._rgba;

		return color( jQuery.map( rgb, function( v, i ) {
			return ( 1 - a ) * blend[ i ] + a * v;
		}));
	},
	toRgbaString: function() {
		var prefix = "rgba(",
			rgba = jQuery.map( this._rgba, function( v, i ) {
				return v == null ? ( i > 2 ? 1 : 0 ) : v;
			});

		if ( rgba[ 3 ] === 1 ) {
			rgba.pop();
			prefix = "rgb(";
		}

		return prefix + rgba.join() + ")";
	},
	toHslaString: function() {
		var prefix = "hsla(",
			hsla = jQuery.map( this.hsla(), function( v, i ) {
				if ( v == null ) {
					v = i > 2 ? 1 : 0;
				}

				// catch 1 and 2
				if ( i && i < 3 ) {
					v = Math.round( v * 100 ) + "%";
				}
				return v;
			});

		if ( hsla[ 3 ] === 1 ) {
			hsla.pop();
			prefix = "hsl(";
		}
		return prefix + hsla.join() + ")";
	},
	toHexString: function( includeAlpha ) {
		var rgba = this._rgba.slice(),
			alpha = rgba.pop();

		if ( includeAlpha ) {
			rgba.push( ~~( alpha * 255 ) );
		}

		return "#" + jQuery.map( rgba, function( v ) {

			// default to 0 when nulls exist
			v = ( v || 0 ).toString( 16 );
			return v.length === 1 ? "0" + v : v;
		}).join("");
	},
	toString: function() {
		return this._rgba[ 3 ] === 0 ? "transparent" : this.toRgbaString();
	}
});
color.fn.parse.prototype = color.fn;

// hsla conversions adapted from:
// https://code.google.com/p/maashaack/source/browse/packages/graphics/trunk/src/graphics/colors/HUE2RGB.as?r=5021

function hue2rgb( p, q, h ) {
	h = ( h + 1 ) % 1;
	if ( h * 6 < 1 ) {
		return p + ( q - p ) * h * 6;
	}
	if ( h * 2 < 1) {
		return q;
	}
	if ( h * 3 < 2 ) {
		return p + ( q - p ) * ( ( 2 / 3 ) - h ) * 6;
	}
	return p;
}

spaces.hsla.to = function( rgba ) {
	if ( rgba[ 0 ] == null || rgba[ 1 ] == null || rgba[ 2 ] == null ) {
		return [ null, null, null, rgba[ 3 ] ];
	}
	var r = rgba[ 0 ] / 255,
		g = rgba[ 1 ] / 255,
		b = rgba[ 2 ] / 255,
		a = rgba[ 3 ],
		max = Math.max( r, g, b ),
		min = Math.min( r, g, b ),
		diff = max - min,
		add = max + min,
		l = add * 0.5,
		h, s;

	if ( min === max ) {
		h = 0;
	} else if ( r === max ) {
		h = ( 60 * ( g - b ) / diff ) + 360;
	} else if ( g === max ) {
		h = ( 60 * ( b - r ) / diff ) + 120;
	} else {
		h = ( 60 * ( r - g ) / diff ) + 240;
	}

	// chroma (diff) == 0 means greyscale which, by definition, saturation = 0%
	// otherwise, saturation is based on the ratio of chroma (diff) to lightness (add)
	if ( diff === 0 ) {
		s = 0;
	} else if ( l <= 0.5 ) {
		s = diff / add;
	} else {
		s = diff / ( 2 - add );
	}
	return [ Math.round(h) % 360, s, l, a == null ? 1 : a ];
};

spaces.hsla.from = function( hsla ) {
	if ( hsla[ 0 ] == null || hsla[ 1 ] == null || hsla[ 2 ] == null ) {
		return [ null, null, null, hsla[ 3 ] ];
	}
	var h = hsla[ 0 ] / 360,
		s = hsla[ 1 ],
		l = hsla[ 2 ],
		a = hsla[ 3 ],
		q = l <= 0.5 ? l * ( 1 + s ) : l + s - l * s,
		p = 2 * l - q;

	return [
		Math.round( hue2rgb( p, q, h + ( 1 / 3 ) ) * 255 ),
		Math.round( hue2rgb( p, q, h ) * 255 ),
		Math.round( hue2rgb( p, q, h - ( 1 / 3 ) ) * 255 ),
		a
	];
};

each( spaces, function( spaceName, space ) {
	var props = space.props,
		cache = space.cache,
		to = space.to,
		from = space.from;

	// makes rgba() and hsla()
	color.fn[ spaceName ] = function( value ) {

		// generate a cache for this space if it doesn't exist
		if ( to && !this[ cache ] ) {
			this[ cache ] = to( this._rgba );
		}
		if ( value === undefined ) {
			return this[ cache ].slice();
		}

		var ret,
			type = jQuery.type( value ),
			arr = ( type === "array" || type === "object" ) ? value : arguments,
			local = this[ cache ].slice();

		each( props, function( key, prop ) {
			var val = arr[ type === "object" ? key : prop.idx ];
			if ( val == null ) {
				val = local[ prop.idx ];
			}
			local[ prop.idx ] = clamp( val, prop );
		});

		if ( from ) {
			ret = color( from( local ) );
			ret[ cache ] = local;
			return ret;
		} else {
			return color( local );
		}
	};

	// makes red() green() blue() alpha() hue() saturation() lightness()
	each( props, function( key, prop ) {
		// alpha is included in more than one space
		if ( color.fn[ key ] ) {
			return;
		}
		color.fn[ key ] = function( value ) {
			var vtype = jQuery.type( value ),
				fn = ( key === "alpha" ? ( this._hsla ? "hsla" : "rgba" ) : spaceName ),
				local = this[ fn ](),
				cur = local[ prop.idx ],
				match;

			if ( vtype === "undefined" ) {
				return cur;
			}

			if ( vtype === "function" ) {
				value = value.call( this, cur );
				vtype = jQuery.type( value );
			}
			if ( value == null && prop.empty ) {
				return this;
			}
			if ( vtype === "string" ) {
				match = rplusequals.exec( value );
				if ( match ) {
					value = cur + parseFloat( match[ 2 ] ) * ( match[ 1 ] === "+" ? 1 : -1 );
				}
			}
			local[ prop.idx ] = value;
			return this[ fn ]( local );
		};
	});
});

// add cssHook and .fx.step function for each named hook.
// accept a space separated string of properties
color.hook = function( hook ) {
	var hooks = hook.split( " " );
	each( hooks, function( i, hook ) {
		jQuery.cssHooks[ hook ] = {
			set: function( elem, value ) {
				var parsed, curElem,
					backgroundColor = "";

				if ( value !== "transparent" && ( jQuery.type( value ) !== "string" || ( parsed = stringParse( value ) ) ) ) {
					value = color( parsed || value );
					if ( !support.rgba && value._rgba[ 3 ] !== 1 ) {
						curElem = hook === "backgroundColor" ? elem.parentNode : elem;
						while (
							(backgroundColor === "" || backgroundColor === "transparent") &&
							curElem && curElem.style
						) {
							try {
								backgroundColor = jQuery.css( curElem, "backgroundColor" );
								curElem = curElem.parentNode;
							} catch ( e ) {
							}
						}

						value = value.blend( backgroundColor && backgroundColor !== "transparent" ?
							backgroundColor :
							"_default" );
					}

					value = value.toRgbaString();
				}
				try {
					elem.style[ hook ] = value;
				} catch( e ) {
					// wrapped to prevent IE from throwing errors on "invalid" values like 'auto' or 'inherit'
				}
			}
		};
		jQuery.fx.step[ hook ] = function( fx ) {
			if ( !fx.colorInit ) {
				fx.start = color( fx.elem, hook );
				fx.end = color( fx.end );
				fx.colorInit = true;
			}
			jQuery.cssHooks[ hook ].set( fx.elem, fx.start.transition( fx.end, fx.pos ) );
		};
	});

};

color.hook( stepHooks );

jQuery.cssHooks.borderColor = {
	expand: function( value ) {
		var expanded = {};

		each( [ "Top", "Right", "Bottom", "Left" ], function( i, part ) {
			expanded[ "border" + part + "Color" ] = value;
		});
		return expanded;
	}
};

// Basic color names only.
// Usage of any of the other color names requires adding yourself or including
// jquery.color.svg-names.js.
colors = jQuery.Color.names = {
	// 4.1. Basic color keywords
	aqua: "#00ffff",
	black: "#000000",
	blue: "#0000ff",
	fuchsia: "#ff00ff",
	gray: "#808080",
	green: "#008000",
	lime: "#00ff00",
	maroon: "#800000",
	navy: "#000080",
	olive: "#808000",
	purple: "#800080",
	red: "#ff0000",
	silver: "#c0c0c0",
	teal: "#008080",
	white: "#ffffff",
	yellow: "#ffff00",

	// 4.2.3. "transparent" color keyword
	transparent: [ null, null, null, 0 ],

	_default: "#ffffff"
};

})( jQuery );

/******************************************************************************/
/****************************** CLASS ANIMATIONS ******************************/
/******************************************************************************/
(function() {

var classAnimationActions = [ "add", "remove", "toggle" ],
	shorthandStyles = {
		border: 1,
		borderBottom: 1,
		borderColor: 1,
		borderLeft: 1,
		borderRight: 1,
		borderTop: 1,
		borderWidth: 1,
		margin: 1,
		padding: 1
	};

$.each([ "borderLeftStyle", "borderRightStyle", "borderBottomStyle", "borderTopStyle" ], function( _, prop ) {
	$.fx.step[ prop ] = function( fx ) {
		if ( fx.end !== "none" && !fx.setAttr || fx.pos === 1 && !fx.setAttr ) {
			jQuery.style( fx.elem, prop, fx.end );
			fx.setAttr = true;
		}
	};
});

function getElementStyles( elem ) {
	var key, len,
		style = elem.ownerDocument.defaultView ?
			elem.ownerDocument.defaultView.getComputedStyle( elem, null ) :
			elem.currentStyle,
		styles = {};

	if ( style && style.length && style[ 0 ] && style[ style[ 0 ] ] ) {
		len = style.length;
		while ( len-- ) {
			key = style[ len ];
			if ( typeof style[ key ] === "string" ) {
				styles[ $.camelCase( key ) ] = style[ key ];
			}
		}
	// support: Opera, IE <9
	} else {
		for ( key in style ) {
			if ( typeof style[ key ] === "string" ) {
				styles[ key ] = style[ key ];
			}
		}
	}

	return styles;
}

function styleDifference( oldStyle, newStyle ) {
	var diff = {},
		name, value;

	for ( name in newStyle ) {
		value = newStyle[ name ];
		if ( oldStyle[ name ] !== value ) {
			if ( !shorthandStyles[ name ] ) {
				if ( $.fx.step[ name ] || !isNaN( parseFloat( value ) ) ) {
					diff[ name ] = value;
				}
			}
		}
	}

	return diff;
}

// support: jQuery <1.8
if ( !$.fn.addBack ) {
	$.fn.addBack = function( selector ) {
		return this.add( selector == null ?
			this.prevObject : this.prevObject.filter( selector )
		);
	};
}

$.effects.animateClass = function( value, duration, easing, callback ) {
	var o = $.speed( duration, easing, callback );

	return this.queue( function() {
		var animated = $( this ),
			baseClass = animated.attr( "class" ) || "",
			applyClassChange,
			allAnimations = o.children ? animated.find( "*" ).addBack() : animated;

		// map the animated objects to store the original styles.
		allAnimations = allAnimations.map(function() {
			var el = $( this );
			return {
				el: el,
				start: getElementStyles( this )
			};
		});

		// apply class change
		applyClassChange = function() {
			$.each( classAnimationActions, function(i, action) {
				if ( value[ action ] ) {
					animated[ action + "Class" ]( value[ action ] );
				}
			});
		};
		applyClassChange();

		// map all animated objects again - calculate new styles and diff
		allAnimations = allAnimations.map(function() {
			this.end = getElementStyles( this.el[ 0 ] );
			this.diff = styleDifference( this.start, this.end );
			return this;
		});

		// apply original class
		animated.attr( "class", baseClass );

		// map all animated objects again - this time collecting a promise
		allAnimations = allAnimations.map(function() {
			var styleInfo = this,
				dfd = $.Deferred(),
				opts = $.extend({}, o, {
					queue: false,
					complete: function() {
						dfd.resolve( styleInfo );
					}
				});

			this.el.animate( this.diff, opts );
			return dfd.promise();
		});

		// once all animations have completed:
		$.when.apply( $, allAnimations.get() ).done(function() {

			// set the final class
			applyClassChange();

			// for each animated element,
			// clear all css properties that were animated
			$.each( arguments, function() {
				var el = this.el;
				$.each( this.diff, function(key) {
					el.css( key, "" );
				});
			});

			// this is guarnteed to be there if you use jQuery.speed()
			// it also handles dequeuing the next anim...
			o.complete.call( animated[ 0 ] );
		});
	});
};

$.fn.extend({
	addClass: (function( orig ) {
		return function( classNames, speed, easing, callback ) {
			return speed ?
				$.effects.animateClass.call( this,
					{ add: classNames }, speed, easing, callback ) :
				orig.apply( this, arguments );
		};
	})( $.fn.addClass ),

	removeClass: (function( orig ) {
		return function( classNames, speed, easing, callback ) {
			return arguments.length > 1 ?
				$.effects.animateClass.call( this,
					{ remove: classNames }, speed, easing, callback ) :
				orig.apply( this, arguments );
		};
	})( $.fn.removeClass ),

	toggleClass: (function( orig ) {
		return function( classNames, force, speed, easing, callback ) {
			if ( typeof force === "boolean" || force === undefined ) {
				if ( !speed ) {
					// without speed parameter
					return orig.apply( this, arguments );
				} else {
					return $.effects.animateClass.call( this,
						(force ? { add: classNames } : { remove: classNames }),
						speed, easing, callback );
				}
			} else {
				// without force parameter
				return $.effects.animateClass.call( this,
					{ toggle: classNames }, force, speed, easing );
			}
		};
	})( $.fn.toggleClass ),

	switchClass: function( remove, add, speed, easing, callback) {
		return $.effects.animateClass.call( this, {
			add: add,
			remove: remove
		}, speed, easing, callback );
	}
});

})();

/******************************************************************************/
/*********************************** EFFECTS **********************************/
/******************************************************************************/

(function() {

$.extend( $.effects, {
	version: "1.11.1",

	// Saves a set of properties in a data storage
	save: function( element, set ) {
		for ( var i = 0; i < set.length; i++ ) {
			if ( set[ i ] !== null ) {
				element.data( dataSpace + set[ i ], element[ 0 ].style[ set[ i ] ] );
			}
		}
	},

	// Restores a set of previously saved properties from a data storage
	restore: function( element, set ) {
		var val, i;
		for ( i = 0; i < set.length; i++ ) {
			if ( set[ i ] !== null ) {
				val = element.data( dataSpace + set[ i ] );
				// support: jQuery 1.6.2
				// http://bugs.jquery.com/ticket/9917
				// jQuery 1.6.2 incorrectly returns undefined for any falsy value.
				// We can't differentiate between "" and 0 here, so we just assume
				// empty string since it's likely to be a more common value...
				if ( val === undefined ) {
					val = "";
				}
				element.css( set[ i ], val );
			}
		}
	},

	setMode: function( el, mode ) {
		if (mode === "toggle") {
			mode = el.is( ":hidden" ) ? "show" : "hide";
		}
		return mode;
	},

	// Translates a [top,left] array into a baseline value
	// this should be a little more flexible in the future to handle a string & hash
	getBaseline: function( origin, original ) {
		var y, x;
		switch ( origin[ 0 ] ) {
			case "top": y = 0; break;
			case "middle": y = 0.5; break;
			case "bottom": y = 1; break;
			default: y = origin[ 0 ] / original.height;
		}
		switch ( origin[ 1 ] ) {
			case "left": x = 0; break;
			case "center": x = 0.5; break;
			case "right": x = 1; break;
			default: x = origin[ 1 ] / original.width;
		}
		return {
			x: x,
			y: y
		};
	},

	// Wraps the element around a wrapper that copies position properties
	createWrapper: function( element ) {

		// if the element is already wrapped, return it
		if ( element.parent().is( ".ui-effects-wrapper" )) {
			return element.parent();
		}

		// wrap the element
		var props = {
				width: element.outerWidth(true),
				height: element.outerHeight(true),
				"float": element.css( "float" )
			},
			wrapper = $( "<div></div>" )
				.addClass( "ui-effects-wrapper" )
				.css({
					fontSize: "100%",
					background: "transparent",
					border: "none",
					margin: 0,
					padding: 0
				}),
			// Store the size in case width/height are defined in % - Fixes #5245
			size = {
				width: element.width(),
				height: element.height()
			},
			active = document.activeElement;

		// support: Firefox
		// Firefox incorrectly exposes anonymous content
		// https://bugzilla.mozilla.org/show_bug.cgi?id=561664
		try {
			active.id;
		} catch( e ) {
			active = document.body;
		}

		element.wrap( wrapper );

		// Fixes #7595 - Elements lose focus when wrapped.
		if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
			$( active ).focus();
		}

		wrapper = element.parent(); //Hotfix for jQuery 1.4 since some change in wrap() seems to actually lose the reference to the wrapped element

		// transfer positioning properties to the wrapper
		if ( element.css( "position" ) === "static" ) {
			wrapper.css({ position: "relative" });
			element.css({ position: "relative" });
		} else {
			$.extend( props, {
				position: element.css( "position" ),
				zIndex: element.css( "z-index" )
			});
			$.each([ "top", "left", "bottom", "right" ], function(i, pos) {
				props[ pos ] = element.css( pos );
				if ( isNaN( parseInt( props[ pos ], 10 ) ) ) {
					props[ pos ] = "auto";
				}
			});
			element.css({
				position: "relative",
				top: 0,
				left: 0,
				right: "auto",
				bottom: "auto"
			});
		}
		element.css(size);

		return wrapper.css( props ).show();
	},

	removeWrapper: function( element ) {
		var active = document.activeElement;

		if ( element.parent().is( ".ui-effects-wrapper" ) ) {
			element.parent().replaceWith( element );

			// Fixes #7595 - Elements lose focus when wrapped.
			if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
				$( active ).focus();
			}
		}

		return element;
	},

	setTransition: function( element, list, factor, value ) {
		value = value || {};
		$.each( list, function( i, x ) {
			var unit = element.cssUnit( x );
			if ( unit[ 0 ] > 0 ) {
				value[ x ] = unit[ 0 ] * factor + unit[ 1 ];
			}
		});
		return value;
	}
});

// return an effect options object for the given parameters:
function _normalizeArguments( effect, options, speed, callback ) {

	// allow passing all options as the first parameter
	if ( $.isPlainObject( effect ) ) {
		options = effect;
		effect = effect.effect;
	}

	// convert to an object
	effect = { effect: effect };

	// catch (effect, null, ...)
	if ( options == null ) {
		options = {};
	}

	// catch (effect, callback)
	if ( $.isFunction( options ) ) {
		callback = options;
		speed = null;
		options = {};
	}

	// catch (effect, speed, ?)
	if ( typeof options === "number" || $.fx.speeds[ options ] ) {
		callback = speed;
		speed = options;
		options = {};
	}

	// catch (effect, options, callback)
	if ( $.isFunction( speed ) ) {
		callback = speed;
		speed = null;
	}

	// add options to effect
	if ( options ) {
		$.extend( effect, options );
	}

	speed = speed || options.duration;
	effect.duration = $.fx.off ? 0 :
		typeof speed === "number" ? speed :
		speed in $.fx.speeds ? $.fx.speeds[ speed ] :
		$.fx.speeds._default;

	effect.complete = callback || options.complete;

	return effect;
}

function standardAnimationOption( option ) {
	// Valid standard speeds (nothing, number, named speed)
	if ( !option || typeof option === "number" || $.fx.speeds[ option ] ) {
		return true;
	}

	// Invalid strings - treat as "normal" speed
	if ( typeof option === "string" && !$.effects.effect[ option ] ) {
		return true;
	}

	// Complete callback
	if ( $.isFunction( option ) ) {
		return true;
	}

	// Options hash (but not naming an effect)
	if ( typeof option === "object" && !option.effect ) {
		return true;
	}

	// Didn't match any standard API
	return false;
}

$.fn.extend({
	effect: function( /* effect, options, speed, callback */ ) {
		var args = _normalizeArguments.apply( this, arguments ),
			mode = args.mode,
			queue = args.queue,
			effectMethod = $.effects.effect[ args.effect ];

		if ( $.fx.off || !effectMethod ) {
			// delegate to the original method (e.g., .show()) if possible
			if ( mode ) {
				return this[ mode ]( args.duration, args.complete );
			} else {
				return this.each( function() {
					if ( args.complete ) {
						args.complete.call( this );
					}
				});
			}
		}

		function run( next ) {
			var elem = $( this ),
				complete = args.complete,
				mode = args.mode;

			function done() {
				if ( $.isFunction( complete ) ) {
					complete.call( elem[0] );
				}
				if ( $.isFunction( next ) ) {
					next();
				}
			}

			// If the element already has the correct final state, delegate to
			// the core methods so the internal tracking of "olddisplay" works.
			if ( elem.is( ":hidden" ) ? mode === "hide" : mode === "show" ) {
				elem[ mode ]();
				done();
			} else {
				effectMethod.call( elem[0], args, done );
			}
		}

		return queue === false ? this.each( run ) : this.queue( queue || "fx", run );
	},

	show: (function( orig ) {
		return function( option ) {
			if ( standardAnimationOption( option ) ) {
				return orig.apply( this, arguments );
			} else {
				var args = _normalizeArguments.apply( this, arguments );
				args.mode = "show";
				return this.effect.call( this, args );
			}
		};
	})( $.fn.show ),

	hide: (function( orig ) {
		return function( option ) {
			if ( standardAnimationOption( option ) ) {
				return orig.apply( this, arguments );
			} else {
				var args = _normalizeArguments.apply( this, arguments );
				args.mode = "hide";
				return this.effect.call( this, args );
			}
		};
	})( $.fn.hide ),

	toggle: (function( orig ) {
		return function( option ) {
			if ( standardAnimationOption( option ) || typeof option === "boolean" ) {
				return orig.apply( this, arguments );
			} else {
				var args = _normalizeArguments.apply( this, arguments );
				args.mode = "toggle";
				return this.effect.call( this, args );
			}
		};
	})( $.fn.toggle ),

	// helper functions
	cssUnit: function(key) {
		var style = this.css( key ),
			val = [];

		$.each( [ "em", "px", "%", "pt" ], function( i, unit ) {
			if ( style.indexOf( unit ) > 0 ) {
				val = [ parseFloat( style ), unit ];
			}
		});
		return val;
	}
});

})();

/******************************************************************************/
/*********************************** EASING ***********************************/
/******************************************************************************/

(function() {

// based on easing equations from Robert Penner (http://www.robertpenner.com/easing)

var baseEasings = {};

$.each( [ "Quad", "Cubic", "Quart", "Quint", "Expo" ], function( i, name ) {
	baseEasings[ name ] = function( p ) {
		return Math.pow( p, i + 2 );
	};
});

$.extend( baseEasings, {
	Sine: function( p ) {
		return 1 - Math.cos( p * Math.PI / 2 );
	},
	Circ: function( p ) {
		return 1 - Math.sqrt( 1 - p * p );
	},
	Elastic: function( p ) {
		return p === 0 || p === 1 ? p :
			-Math.pow( 2, 8 * (p - 1) ) * Math.sin( ( (p - 1) * 80 - 7.5 ) * Math.PI / 15 );
	},
	Back: function( p ) {
		return p * p * ( 3 * p - 2 );
	},
	Bounce: function( p ) {
		var pow2,
			bounce = 4;

		while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {}
		return 1 / Math.pow( 4, 3 - bounce ) - 7.5625 * Math.pow( ( pow2 * 3 - 2 ) / 22 - p, 2 );
	}
});

$.each( baseEasings, function( name, easeIn ) {
	$.easing[ "easeIn" + name ] = easeIn;
	$.easing[ "easeOut" + name ] = function( p ) {
		return 1 - easeIn( 1 - p );
	};
	$.easing[ "easeInOut" + name ] = function( p ) {
		return p < 0.5 ?
			easeIn( p * 2 ) / 2 :
			1 - easeIn( p * -2 + 2 ) / 2;
	};
});

})();

var effect = $.effects;


/*!
 * jQuery UI Effects Blind 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/blind-effect/
 */


var effectBlind = $.effects.effect.blind = function( o, done ) {
	// Create element
	var el = $( this ),
		rvertical = /up|down|vertical/,
		rpositivemotion = /up|left|vertical|horizontal/,
		props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
		mode = $.effects.setMode( el, o.mode || "hide" ),
		direction = o.direction || "up",
		vertical = rvertical.test( direction ),
		ref = vertical ? "height" : "width",
		ref2 = vertical ? "top" : "left",
		motion = rpositivemotion.test( direction ),
		animation = {},
		show = mode === "show",
		wrapper, distance, margin;

	// if already wrapped, the wrapper's properties are my property. #6245
	if ( el.parent().is( ".ui-effects-wrapper" ) ) {
		$.effects.save( el.parent(), props );
	} else {
		$.effects.save( el, props );
	}
	el.show();
	wrapper = $.effects.createWrapper( el ).css({
		overflow: "hidden"
	});

	distance = wrapper[ ref ]();
	margin = parseFloat( wrapper.css( ref2 ) ) || 0;

	animation[ ref ] = show ? distance : 0;
	if ( !motion ) {
		el
			.css( vertical ? "bottom" : "right", 0 )
			.css( vertical ? "top" : "left", "auto" )
			.css({ position: "absolute" });

		animation[ ref2 ] = show ? margin : distance + margin;
	}

	// start at 0 if we are showing
	if ( show ) {
		wrapper.css( ref, 0 );
		if ( !motion ) {
			wrapper.css( ref2, margin + distance );
		}
	}

	// Animate
	wrapper.animate( animation, {
		duration: o.duration,
		easing: o.easing,
		queue: false,
		complete: function() {
			if ( mode === "hide" ) {
				el.hide();
			}
			$.effects.restore( el, props );
			$.effects.removeWrapper( el );
			done();
		}
	});
};


/*!
 * jQuery UI Effects Bounce 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/bounce-effect/
 */


var effectBounce = $.effects.effect.bounce = function( o, done ) {
	var el = $( this ),
		props = [ "position", "top", "bottom", "left", "right", "height", "width" ],

		// defaults:
		mode = $.effects.setMode( el, o.mode || "effect" ),
		hide = mode === "hide",
		show = mode === "show",
		direction = o.direction || "up",
		distance = o.distance,
		times = o.times || 5,

		// number of internal animations
		anims = times * 2 + ( show || hide ? 1 : 0 ),
		speed = o.duration / anims,
		easing = o.easing,

		// utility:
		ref = ( direction === "up" || direction === "down" ) ? "top" : "left",
		motion = ( direction === "up" || direction === "left" ),
		i,
		upAnim,
		downAnim,

		// we will need to re-assemble the queue to stack our animations in place
		queue = el.queue(),
		queuelen = queue.length;

	// Avoid touching opacity to prevent clearType and PNG issues in IE
	if ( show || hide ) {
		props.push( "opacity" );
	}

	$.effects.save( el, props );
	el.show();
	$.effects.createWrapper( el ); // Create Wrapper

	// default distance for the BIGGEST bounce is the outer Distance / 3
	if ( !distance ) {
		distance = el[ ref === "top" ? "outerHeight" : "outerWidth" ]() / 3;
	}

	if ( show ) {
		downAnim = { opacity: 1 };
		downAnim[ ref ] = 0;

		// if we are showing, force opacity 0 and set the initial position
		// then do the "first" animation
		el.css( "opacity", 0 )
			.css( ref, motion ? -distance * 2 : distance * 2 )
			.animate( downAnim, speed, easing );
	}

	// start at the smallest distance if we are hiding
	if ( hide ) {
		distance = distance / Math.pow( 2, times - 1 );
	}

	downAnim = {};
	downAnim[ ref ] = 0;
	// Bounces up/down/left/right then back to 0 -- times * 2 animations happen here
	for ( i = 0; i < times; i++ ) {
		upAnim = {};
		upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance;

		el.animate( upAnim, speed, easing )
			.animate( downAnim, speed, easing );

		distance = hide ? distance * 2 : distance / 2;
	}

	// Last Bounce when Hiding
	if ( hide ) {
		upAnim = { opacity: 0 };
		upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance;

		el.animate( upAnim, speed, easing );
	}

	el.queue(function() {
		if ( hide ) {
			el.hide();
		}
		$.effects.restore( el, props );
		$.effects.removeWrapper( el );
		done();
	});

	// inject all the animations we just queued to be first in line (after "inprogress")
	if ( queuelen > 1) {
		queue.splice.apply( queue,
			[ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) );
	}
	el.dequeue();

};


/*!
 * jQuery UI Effects Clip 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/clip-effect/
 */


var effectClip = $.effects.effect.clip = function( o, done ) {
	// Create element
	var el = $( this ),
		props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
		mode = $.effects.setMode( el, o.mode || "hide" ),
		show = mode === "show",
		direction = o.direction || "vertical",
		vert = direction === "vertical",
		size = vert ? "height" : "width",
		position = vert ? "top" : "left",
		animation = {},
		wrapper, animate, distance;

	// Save & Show
	$.effects.save( el, props );
	el.show();

	// Create Wrapper
	wrapper = $.effects.createWrapper( el ).css({
		overflow: "hidden"
	});
	animate = ( el[0].tagName === "IMG" ) ? wrapper : el;
	distance = animate[ size ]();

	// Shift
	if ( show ) {
		animate.css( size, 0 );
		animate.css( position, distance / 2 );
	}

	// Create Animation Object:
	animation[ size ] = show ? distance : 0;
	animation[ position ] = show ? 0 : distance / 2;

	// Animate
	animate.animate( animation, {
		queue: false,
		duration: o.duration,
		easing: o.easing,
		complete: function() {
			if ( !show ) {
				el.hide();
			}
			$.effects.restore( el, props );
			$.effects.removeWrapper( el );
			done();
		}
	});

};


/*!
 * jQuery UI Effects Drop 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/drop-effect/
 */


var effectDrop = $.effects.effect.drop = function( o, done ) {

	var el = $( this ),
		props = [ "position", "top", "bottom", "left", "right", "opacity", "height", "width" ],
		mode = $.effects.setMode( el, o.mode || "hide" ),
		show = mode === "show",
		direction = o.direction || "left",
		ref = ( direction === "up" || direction === "down" ) ? "top" : "left",
		motion = ( direction === "up" || direction === "left" ) ? "pos" : "neg",
		animation = {
			opacity: show ? 1 : 0
		},
		distance;

	// Adjust
	$.effects.save( el, props );
	el.show();
	$.effects.createWrapper( el );

	distance = o.distance || el[ ref === "top" ? "outerHeight": "outerWidth" ]( true ) / 2;

	if ( show ) {
		el
			.css( "opacity", 0 )
			.css( ref, motion === "pos" ? -distance : distance );
	}

	// Animation
	animation[ ref ] = ( show ?
		( motion === "pos" ? "+=" : "-=" ) :
		( motion === "pos" ? "-=" : "+=" ) ) +
		distance;

	// Animate
	el.animate( animation, {
		queue: false,
		duration: o.duration,
		easing: o.easing,
		complete: function() {
			if ( mode === "hide" ) {
				el.hide();
			}
			$.effects.restore( el, props );
			$.effects.removeWrapper( el );
			done();
		}
	});
};


/*!
 * jQuery UI Effects Explode 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/explode-effect/
 */


var effectExplode = $.effects.effect.explode = function( o, done ) {

	var rows = o.pieces ? Math.round( Math.sqrt( o.pieces ) ) : 3,
		cells = rows,
		el = $( this ),
		mode = $.effects.setMode( el, o.mode || "hide" ),
		show = mode === "show",

		// show and then visibility:hidden the element before calculating offset
		offset = el.show().css( "visibility", "hidden" ).offset(),

		// width and height of a piece
		width = Math.ceil( el.outerWidth() / cells ),
		height = Math.ceil( el.outerHeight() / rows ),
		pieces = [],

		// loop
		i, j, left, top, mx, my;

	// children animate complete:
	function childComplete() {
		pieces.push( this );
		if ( pieces.length === rows * cells ) {
			animComplete();
		}
	}

	// clone the element for each row and cell.
	for ( i = 0; i < rows ; i++ ) { // ===>
		top = offset.top + i * height;
		my = i - ( rows - 1 ) / 2 ;

		for ( j = 0; j < cells ; j++ ) { // |||
			left = offset.left + j * width;
			mx = j - ( cells - 1 ) / 2 ;

			// Create a clone of the now hidden main element that will be absolute positioned
			// within a wrapper div off the -left and -top equal to size of our pieces
			el
				.clone()
				.appendTo( "body" )
				.wrap( "<div></div>" )
				.css({
					position: "absolute",
					visibility: "visible",
					left: -j * width,
					top: -i * height
				})

			// select the wrapper - make it overflow: hidden and absolute positioned based on
			// where the original was located +left and +top equal to the size of pieces
				.parent()
				.addClass( "ui-effects-explode" )
				.css({
					position: "absolute",
					overflow: "hidden",
					width: width,
					height: height,
					left: left + ( show ? mx * width : 0 ),
					top: top + ( show ? my * height : 0 ),
					opacity: show ? 0 : 1
				}).animate({
					left: left + ( show ? 0 : mx * width ),
					top: top + ( show ? 0 : my * height ),
					opacity: show ? 1 : 0
				}, o.duration || 500, o.easing, childComplete );
		}
	}

	function animComplete() {
		el.css({
			visibility: "visible"
		});
		$( pieces ).remove();
		if ( !show ) {
			el.hide();
		}
		done();
	}
};


/*!
 * jQuery UI Effects Fade 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/fade-effect/
 */


var effectFade = $.effects.effect.fade = function( o, done ) {
	var el = $( this ),
		mode = $.effects.setMode( el, o.mode || "toggle" );

	el.animate({
		opacity: mode
	}, {
		queue: false,
		duration: o.duration,
		easing: o.easing,
		complete: done
	});
};


/*!
 * jQuery UI Effects Fold 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/fold-effect/
 */


var effectFold = $.effects.effect.fold = function( o, done ) {

	// Create element
	var el = $( this ),
		props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
		mode = $.effects.setMode( el, o.mode || "hide" ),
		show = mode === "show",
		hide = mode === "hide",
		size = o.size || 15,
		percent = /([0-9]+)%/.exec( size ),
		horizFirst = !!o.horizFirst,
		widthFirst = show !== horizFirst,
		ref = widthFirst ? [ "width", "height" ] : [ "height", "width" ],
		duration = o.duration / 2,
		wrapper, distance,
		animation1 = {},
		animation2 = {};

	$.effects.save( el, props );
	el.show();

	// Create Wrapper
	wrapper = $.effects.createWrapper( el ).css({
		overflow: "hidden"
	});
	distance = widthFirst ?
		[ wrapper.width(), wrapper.height() ] :
		[ wrapper.height(), wrapper.width() ];

	if ( percent ) {
		size = parseInt( percent[ 1 ], 10 ) / 100 * distance[ hide ? 0 : 1 ];
	}
	if ( show ) {
		wrapper.css( horizFirst ? {
			height: 0,
			width: size
		} : {
			height: size,
			width: 0
		});
	}

	// Animation
	animation1[ ref[ 0 ] ] = show ? distance[ 0 ] : size;
	animation2[ ref[ 1 ] ] = show ? distance[ 1 ] : 0;

	// Animate
	wrapper
		.animate( animation1, duration, o.easing )
		.animate( animation2, duration, o.easing, function() {
			if ( hide ) {
				el.hide();
			}
			$.effects.restore( el, props );
			$.effects.removeWrapper( el );
			done();
		});

};


/*!
 * jQuery UI Effects Highlight 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/highlight-effect/
 */


var effectHighlight = $.effects.effect.highlight = function( o, done ) {
	var elem = $( this ),
		props = [ "backgroundImage", "backgroundColor", "opacity" ],
		mode = $.effects.setMode( elem, o.mode || "show" ),
		animation = {
			backgroundColor: elem.css( "backgroundColor" )
		};

	if (mode === "hide") {
		animation.opacity = 0;
	}

	$.effects.save( elem, props );

	elem
		.show()
		.css({
			backgroundImage: "none",
			backgroundColor: o.color || "#ffff99"
		})
		.animate( animation, {
			queue: false,
			duration: o.duration,
			easing: o.easing,
			complete: function() {
				if ( mode === "hide" ) {
					elem.hide();
				}
				$.effects.restore( elem, props );
				done();
			}
		});
};


/*!
 * jQuery UI Effects Size 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/size-effect/
 */


var effectSize = $.effects.effect.size = function( o, done ) {

	// Create element
	var original, baseline, factor,
		el = $( this ),
		props0 = [ "position", "top", "bottom", "left", "right", "width", "height", "overflow", "opacity" ],

		// Always restore
		props1 = [ "position", "top", "bottom", "left", "right", "overflow", "opacity" ],

		// Copy for children
		props2 = [ "width", "height", "overflow" ],
		cProps = [ "fontSize" ],
		vProps = [ "borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom" ],
		hProps = [ "borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight" ],

		// Set options
		mode = $.effects.setMode( el, o.mode || "effect" ),
		restore = o.restore || mode !== "effect",
		scale = o.scale || "both",
		origin = o.origin || [ "middle", "center" ],
		position = el.css( "position" ),
		props = restore ? props0 : props1,
		zero = {
			height: 0,
			width: 0,
			outerHeight: 0,
			outerWidth: 0
		};

	if ( mode === "show" ) {
		el.show();
	}
	original = {
		height: el.height(),
		width: el.width(),
		outerHeight: el.outerHeight(),
		outerWidth: el.outerWidth()
	};

	if ( o.mode === "toggle" && mode === "show" ) {
		el.from = o.to || zero;
		el.to = o.from || original;
	} else {
		el.from = o.from || ( mode === "show" ? zero : original );
		el.to = o.to || ( mode === "hide" ? zero : original );
	}

	// Set scaling factor
	factor = {
		from: {
			y: el.from.height / original.height,
			x: el.from.width / original.width
		},
		to: {
			y: el.to.height / original.height,
			x: el.to.width / original.width
		}
	};

	// Scale the css box
	if ( scale === "box" || scale === "both" ) {

		// Vertical props scaling
		if ( factor.from.y !== factor.to.y ) {
			props = props.concat( vProps );
			el.from = $.effects.setTransition( el, vProps, factor.from.y, el.from );
			el.to = $.effects.setTransition( el, vProps, factor.to.y, el.to );
		}

		// Horizontal props scaling
		if ( factor.from.x !== factor.to.x ) {
			props = props.concat( hProps );
			el.from = $.effects.setTransition( el, hProps, factor.from.x, el.from );
			el.to = $.effects.setTransition( el, hProps, factor.to.x, el.to );
		}
	}

	// Scale the content
	if ( scale === "content" || scale === "both" ) {

		// Vertical props scaling
		if ( factor.from.y !== factor.to.y ) {
			props = props.concat( cProps ).concat( props2 );
			el.from = $.effects.setTransition( el, cProps, factor.from.y, el.from );
			el.to = $.effects.setTransition( el, cProps, factor.to.y, el.to );
		}
	}

	$.effects.save( el, props );
	el.show();
	$.effects.createWrapper( el );
	el.css( "overflow", "hidden" ).css( el.from );

	// Adjust
	if (origin) { // Calculate baseline shifts
		baseline = $.effects.getBaseline( origin, original );
		el.from.top = ( original.outerHeight - el.outerHeight() ) * baseline.y;
		el.from.left = ( original.outerWidth - el.outerWidth() ) * baseline.x;
		el.to.top = ( original.outerHeight - el.to.outerHeight ) * baseline.y;
		el.to.left = ( original.outerWidth - el.to.outerWidth ) * baseline.x;
	}
	el.css( el.from ); // set top & left

	// Animate
	if ( scale === "content" || scale === "both" ) { // Scale the children

		// Add margins/font-size
		vProps = vProps.concat([ "marginTop", "marginBottom" ]).concat(cProps);
		hProps = hProps.concat([ "marginLeft", "marginRight" ]);
		props2 = props0.concat(vProps).concat(hProps);

		el.find( "*[width]" ).each( function() {
			var child = $( this ),
				c_original = {
					height: child.height(),
					width: child.width(),
					outerHeight: child.outerHeight(),
					outerWidth: child.outerWidth()
				};
			if (restore) {
				$.effects.save(child, props2);
			}

			child.from = {
				height: c_original.height * factor.from.y,
				width: c_original.width * factor.from.x,
				outerHeight: c_original.outerHeight * factor.from.y,
				outerWidth: c_original.outerWidth * factor.from.x
			};
			child.to = {
				height: c_original.height * factor.to.y,
				width: c_original.width * factor.to.x,
				outerHeight: c_original.height * factor.to.y,
				outerWidth: c_original.width * factor.to.x
			};

			// Vertical props scaling
			if ( factor.from.y !== factor.to.y ) {
				child.from = $.effects.setTransition( child, vProps, factor.from.y, child.from );
				child.to = $.effects.setTransition( child, vProps, factor.to.y, child.to );
			}

			// Horizontal props scaling
			if ( factor.from.x !== factor.to.x ) {
				child.from = $.effects.setTransition( child, hProps, factor.from.x, child.from );
				child.to = $.effects.setTransition( child, hProps, factor.to.x, child.to );
			}

			// Animate children
			child.css( child.from );
			child.animate( child.to, o.duration, o.easing, function() {

				// Restore children
				if ( restore ) {
					$.effects.restore( child, props2 );
				}
			});
		});
	}

	// Animate
	el.animate( el.to, {
		queue: false,
		duration: o.duration,
		easing: o.easing,
		complete: function() {
			if ( el.to.opacity === 0 ) {
				el.css( "opacity", el.from.opacity );
			}
			if ( mode === "hide" ) {
				el.hide();
			}
			$.effects.restore( el, props );
			if ( !restore ) {

				// we need to calculate our new positioning based on the scaling
				if ( position === "static" ) {
					el.css({
						position: "relative",
						top: el.to.top,
						left: el.to.left
					});
				} else {
					$.each([ "top", "left" ], function( idx, pos ) {
						el.css( pos, function( _, str ) {
							var val = parseInt( str, 10 ),
								toRef = idx ? el.to.left : el.to.top;

							// if original was "auto", recalculate the new value from wrapper
							if ( str === "auto" ) {
								return toRef + "px";
							}

							return val + toRef + "px";
						});
					});
				}
			}

			$.effects.removeWrapper( el );
			done();
		}
	});

};


/*!
 * jQuery UI Effects Scale 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/scale-effect/
 */


var effectScale = $.effects.effect.scale = function( o, done ) {

	// Create element
	var el = $( this ),
		options = $.extend( true, {}, o ),
		mode = $.effects.setMode( el, o.mode || "effect" ),
		percent = parseInt( o.percent, 10 ) ||
			( parseInt( o.percent, 10 ) === 0 ? 0 : ( mode === "hide" ? 0 : 100 ) ),
		direction = o.direction || "both",
		origin = o.origin,
		original = {
			height: el.height(),
			width: el.width(),
			outerHeight: el.outerHeight(),
			outerWidth: el.outerWidth()
		},
		factor = {
			y: direction !== "horizontal" ? (percent / 100) : 1,
			x: direction !== "vertical" ? (percent / 100) : 1
		};

	// We are going to pass this effect to the size effect:
	options.effect = "size";
	options.queue = false;
	options.complete = done;

	// Set default origin and restore for show/hide
	if ( mode !== "effect" ) {
		options.origin = origin || [ "middle", "center" ];
		options.restore = true;
	}

	options.from = o.from || ( mode === "show" ? {
		height: 0,
		width: 0,
		outerHeight: 0,
		outerWidth: 0
	} : original );
	options.to = {
		height: original.height * factor.y,
		width: original.width * factor.x,
		outerHeight: original.outerHeight * factor.y,
		outerWidth: original.outerWidth * factor.x
	};

	// Fade option to support puff
	if ( options.fade ) {
		if ( mode === "show" ) {
			options.from.opacity = 0;
			options.to.opacity = 1;
		}
		if ( mode === "hide" ) {
			options.from.opacity = 1;
			options.to.opacity = 0;
		}
	}

	// Animate
	el.effect( options );

};


/*!
 * jQuery UI Effects Puff 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/puff-effect/
 */


var effectPuff = $.effects.effect.puff = function( o, done ) {
	var elem = $( this ),
		mode = $.effects.setMode( elem, o.mode || "hide" ),
		hide = mode === "hide",
		percent = parseInt( o.percent, 10 ) || 150,
		factor = percent / 100,
		original = {
			height: elem.height(),
			width: elem.width(),
			outerHeight: elem.outerHeight(),
			outerWidth: elem.outerWidth()
		};

	$.extend( o, {
		effect: "scale",
		queue: false,
		fade: true,
		mode: mode,
		complete: done,
		percent: hide ? percent : 100,
		from: hide ?
			original :
			{
				height: original.height * factor,
				width: original.width * factor,
				outerHeight: original.outerHeight * factor,
				outerWidth: original.outerWidth * factor
			}
	});

	elem.effect( o );
};


/*!
 * jQuery UI Effects Pulsate 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/pulsate-effect/
 */


var effectPulsate = $.effects.effect.pulsate = function( o, done ) {
	var elem = $( this ),
		mode = $.effects.setMode( elem, o.mode || "show" ),
		show = mode === "show",
		hide = mode === "hide",
		showhide = ( show || mode === "hide" ),

		// showing or hiding leaves of the "last" animation
		anims = ( ( o.times || 5 ) * 2 ) + ( showhide ? 1 : 0 ),
		duration = o.duration / anims,
		animateTo = 0,
		queue = elem.queue(),
		queuelen = queue.length,
		i;

	if ( show || !elem.is(":visible")) {
		elem.css( "opacity", 0 ).show();
		animateTo = 1;
	}

	// anims - 1 opacity "toggles"
	for ( i = 1; i < anims; i++ ) {
		elem.animate({
			opacity: animateTo
		}, duration, o.easing );
		animateTo = 1 - animateTo;
	}

	elem.animate({
		opacity: animateTo
	}, duration, o.easing);

	elem.queue(function() {
		if ( hide ) {
			elem.hide();
		}
		done();
	});

	// We just queued up "anims" animations, we need to put them next in the queue
	if ( queuelen > 1 ) {
		queue.splice.apply( queue,
			[ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) );
	}
	elem.dequeue();
};


/*!
 * jQuery UI Effects Shake 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/shake-effect/
 */


var effectShake = $.effects.effect.shake = function( o, done ) {

	var el = $( this ),
		props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
		mode = $.effects.setMode( el, o.mode || "effect" ),
		direction = o.direction || "left",
		distance = o.distance || 20,
		times = o.times || 3,
		anims = times * 2 + 1,
		speed = Math.round( o.duration / anims ),
		ref = (direction === "up" || direction === "down") ? "top" : "left",
		positiveMotion = (direction === "up" || direction === "left"),
		animation = {},
		animation1 = {},
		animation2 = {},
		i,

		// we will need to re-assemble the queue to stack our animations in place
		queue = el.queue(),
		queuelen = queue.length;

	$.effects.save( el, props );
	el.show();
	$.effects.createWrapper( el );

	// Animation
	animation[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance;
	animation1[ ref ] = ( positiveMotion ? "+=" : "-=" ) + distance * 2;
	animation2[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance * 2;

	// Animate
	el.animate( animation, speed, o.easing );

	// Shakes
	for ( i = 1; i < times; i++ ) {
		el.animate( animation1, speed, o.easing ).animate( animation2, speed, o.easing );
	}
	el
		.animate( animation1, speed, o.easing )
		.animate( animation, speed / 2, o.easing )
		.queue(function() {
			if ( mode === "hide" ) {
				el.hide();
			}
			$.effects.restore( el, props );
			$.effects.removeWrapper( el );
			done();
		});

	// inject all the animations we just queued to be first in line (after "inprogress")
	if ( queuelen > 1) {
		queue.splice.apply( queue,
			[ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) );
	}
	el.dequeue();

};


/*!
 * jQuery UI Effects Slide 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/slide-effect/
 */


var effectSlide = $.effects.effect.slide = function( o, done ) {

	// Create element
	var el = $( this ),
		props = [ "position", "top", "bottom", "left", "right", "width", "height" ],
		mode = $.effects.setMode( el, o.mode || "show" ),
		show = mode === "show",
		direction = o.direction || "left",
		ref = (direction === "up" || direction === "down") ? "top" : "left",
		positiveMotion = (direction === "up" || direction === "left"),
		distance,
		animation = {};

	// Adjust
	$.effects.save( el, props );
	el.show();
	distance = o.distance || el[ ref === "top" ? "outerHeight" : "outerWidth" ]( true );

	$.effects.createWrapper( el ).css({
		overflow: "hidden"
	});

	if ( show ) {
		el.css( ref, positiveMotion ? (isNaN(distance) ? "-" + distance : -distance) : distance );
	}

	// Animation
	animation[ ref ] = ( show ?
		( positiveMotion ? "+=" : "-=") :
		( positiveMotion ? "-=" : "+=")) +
		distance;

	// Animate
	el.animate( animation, {
		queue: false,
		duration: o.duration,
		easing: o.easing,
		complete: function() {
			if ( mode === "hide" ) {
				el.hide();
			}
			$.effects.restore( el, props );
			$.effects.removeWrapper( el );
			done();
		}
	});
};


/*!
 * jQuery UI Effects Transfer 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/transfer-effect/
 */


var effectTransfer = $.effects.effect.transfer = function( o, done ) {
	var elem = $( this ),
		target = $( o.to ),
		targetFixed = target.css( "position" ) === "fixed",
		body = $("body"),
		fixTop = targetFixed ? body.scrollTop() : 0,
		fixLeft = targetFixed ? body.scrollLeft() : 0,
		endPosition = target.offset(),
		animation = {
			top: endPosition.top - fixTop,
			left: endPosition.left - fixLeft,
			height: target.innerHeight(),
			width: target.innerWidth()
		},
		startPosition = elem.offset(),
		transfer = $( "<div class='ui-effects-transfer'></div>" )
			.appendTo( document.body )
			.addClass( o.className )
			.css({
				top: startPosition.top - fixTop,
				left: startPosition.left - fixLeft,
				height: elem.innerHeight(),
				width: elem.innerWidth(),
				position: targetFixed ? "fixed" : "absolute"
			})
			.animate( animation, o.duration, o.easing, function() {
				transfer.remove();
				done();
			});
};


/*!
 * jQuery UI Progressbar 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/progressbar/
 */


var progressbar = $.widget( "ui.progressbar", {
	version: "1.11.1",
	options: {
		max: 100,
		value: 0,

		change: null,
		complete: null
	},

	min: 0,

	_create: function() {
		// Constrain initial value
		this.oldValue = this.options.value = this._constrainedValue();

		this.element
			.addClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" )
			.attr({
				// Only set static values, aria-valuenow and aria-valuemax are
				// set inside _refreshValue()
				role: "progressbar",
				"aria-valuemin": this.min
			});

		this.valueDiv = $( "<div class='ui-progressbar-value ui-widget-header ui-corner-left'></div>" )
			.appendTo( this.element );

		this._refreshValue();
	},

	_destroy: function() {
		this.element
			.removeClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" )
			.removeAttr( "role" )
			.removeAttr( "aria-valuemin" )
			.removeAttr( "aria-valuemax" )
			.removeAttr( "aria-valuenow" );

		this.valueDiv.remove();
	},

	value: function( newValue ) {
		if ( newValue === undefined ) {
			return this.options.value;
		}

		this.options.value = this._constrainedValue( newValue );
		this._refreshValue();
	},

	_constrainedValue: function( newValue ) {
		if ( newValue === undefined ) {
			newValue = this.options.value;
		}

		this.indeterminate = newValue === false;

		// sanitize value
		if ( typeof newValue !== "number" ) {
			newValue = 0;
		}

		return this.indeterminate ? false :
			Math.min( this.options.max, Math.max( this.min, newValue ) );
	},

	_setOptions: function( options ) {
		// Ensure "value" option is set after other values (like max)
		var value = options.value;
		delete options.value;

		this._super( options );

		this.options.value = this._constrainedValue( value );
		this._refreshValue();
	},

	_setOption: function( key, value ) {
		if ( key === "max" ) {
			// Don't allow a max less than min
			value = Math.max( this.min, value );
		}
		if ( key === "disabled" ) {
			this.element
				.toggleClass( "ui-state-disabled", !!value )
				.attr( "aria-disabled", value );
		}
		this._super( key, value );
	},

	_percentage: function() {
		return this.indeterminate ? 100 : 100 * ( this.options.value - this.min ) / ( this.options.max - this.min );
	},

	_refreshValue: function() {
		var value = this.options.value,
			percentage = this._percentage();

		this.valueDiv
			.toggle( this.indeterminate || value > this.min )
			.toggleClass( "ui-corner-right", value === this.options.max )
			.width( percentage.toFixed(0) + "%" );

		this.element.toggleClass( "ui-progressbar-indeterminate", this.indeterminate );

		if ( this.indeterminate ) {
			this.element.removeAttr( "aria-valuenow" );
			if ( !this.overlayDiv ) {
				this.overlayDiv = $( "<div class='ui-progressbar-overlay'></div>" ).appendTo( this.valueDiv );
			}
		} else {
			this.element.attr({
				"aria-valuemax": this.options.max,
				"aria-valuenow": value
			});
			if ( this.overlayDiv ) {
				this.overlayDiv.remove();
				this.overlayDiv = null;
			}
		}

		if ( this.oldValue !== value ) {
			this.oldValue = value;
			this._trigger( "change" );
		}
		if ( value === this.options.max ) {
			this._trigger( "complete" );
		}
	}
});


/*!
 * jQuery UI Selectable 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/selectable/
 */


var selectable = $.widget("ui.selectable", $.ui.mouse, {
	version: "1.11.1",
	options: {
		appendTo: "body",
		autoRefresh: true,
		distance: 0,
		filter: "*",
		tolerance: "touch",

		// callbacks
		selected: null,
		selecting: null,
		start: null,
		stop: null,
		unselected: null,
		unselecting: null
	},
	_create: function() {
		var selectees,
			that = this;

		this.element.addClass("ui-selectable");

		this.dragged = false;

		// cache selectee children based on filter
		this.refresh = function() {
			selectees = $(that.options.filter, that.element[0]);
			selectees.addClass("ui-selectee");
			selectees.each(function() {
				var $this = $(this),
					pos = $this.offset();
				$.data(this, "selectable-item", {
					element: this,
					$element: $this,
					left: pos.left,
					top: pos.top,
					right: pos.left + $this.outerWidth(),
					bottom: pos.top + $this.outerHeight(),
					startselected: false,
					selected: $this.hasClass("ui-selected"),
					selecting: $this.hasClass("ui-selecting"),
					unselecting: $this.hasClass("ui-unselecting")
				});
			});
		};
		this.refresh();

		this.selectees = selectees.addClass("ui-selectee");

		this._mouseInit();

		this.helper = $("<div class='ui-selectable-helper'></div>");
	},

	_destroy: function() {
		this.selectees
			.removeClass("ui-selectee")
			.removeData("selectable-item");
		this.element
			.removeClass("ui-selectable ui-selectable-disabled");
		this._mouseDestroy();
	},

	_mouseStart: function(event) {
		var that = this,
			options = this.options;

		this.opos = [ event.pageX, event.pageY ];

		if (this.options.disabled) {
			return;
		}

		this.selectees = $(options.filter, this.element[0]);

		this._trigger("start", event);

		$(options.appendTo).append(this.helper);
		// position helper (lasso)
		this.helper.css({
			"left": event.pageX,
			"top": event.pageY,
			"width": 0,
			"height": 0
		});

		if (options.autoRefresh) {
			this.refresh();
		}

		this.selectees.filter(".ui-selected").each(function() {
			var selectee = $.data(this, "selectable-item");
			selectee.startselected = true;
			if (!event.metaKey && !event.ctrlKey) {
				selectee.$element.removeClass("ui-selected");
				selectee.selected = false;
				selectee.$element.addClass("ui-unselecting");
				selectee.unselecting = true;
				// selectable UNSELECTING callback
				that._trigger("unselecting", event, {
					unselecting: selectee.element
				});
			}
		});

		$(event.target).parents().addBack().each(function() {
			var doSelect,
				selectee = $.data(this, "selectable-item");
			if (selectee) {
				doSelect = (!event.metaKey && !event.ctrlKey) || !selectee.$element.hasClass("ui-selected");
				selectee.$element
					.removeClass(doSelect ? "ui-unselecting" : "ui-selected")
					.addClass(doSelect ? "ui-selecting" : "ui-unselecting");
				selectee.unselecting = !doSelect;
				selectee.selecting = doSelect;
				selectee.selected = doSelect;
				// selectable (UN)SELECTING callback
				if (doSelect) {
					that._trigger("selecting", event, {
						selecting: selectee.element
					});
				} else {
					that._trigger("unselecting", event, {
						unselecting: selectee.element
					});
				}
				return false;
			}
		});

	},

	_mouseDrag: function(event) {

		this.dragged = true;

		if (this.options.disabled) {
			return;
		}

		var tmp,
			that = this,
			options = this.options,
			x1 = this.opos[0],
			y1 = this.opos[1],
			x2 = event.pageX,
			y2 = event.pageY;

		if (x1 > x2) { tmp = x2; x2 = x1; x1 = tmp; }
		if (y1 > y2) { tmp = y2; y2 = y1; y1 = tmp; }
		this.helper.css({ left: x1, top: y1, width: x2 - x1, height: y2 - y1 });

		this.selectees.each(function() {
			var selectee = $.data(this, "selectable-item"),
				hit = false;

			//prevent helper from being selected if appendTo: selectable
			if (!selectee || selectee.element === that.element[0]) {
				return;
			}

			if (options.tolerance === "touch") {
				hit = ( !(selectee.left > x2 || selectee.right < x1 || selectee.top > y2 || selectee.bottom < y1) );
			} else if (options.tolerance === "fit") {
				hit = (selectee.left > x1 && selectee.right < x2 && selectee.top > y1 && selectee.bottom < y2);
			}

			if (hit) {
				// SELECT
				if (selectee.selected) {
					selectee.$element.removeClass("ui-selected");
					selectee.selected = false;
				}
				if (selectee.unselecting) {
					selectee.$element.removeClass("ui-unselecting");
					selectee.unselecting = false;
				}
				if (!selectee.selecting) {
					selectee.$element.addClass("ui-selecting");
					selectee.selecting = true;
					// selectable SELECTING callback
					that._trigger("selecting", event, {
						selecting: selectee.element
					});
				}
			} else {
				// UNSELECT
				if (selectee.selecting) {
					if ((event.metaKey || event.ctrlKey) && selectee.startselected) {
						selectee.$element.removeClass("ui-selecting");
						selectee.selecting = false;
						selectee.$element.addClass("ui-selected");
						selectee.selected = true;
					} else {
						selectee.$element.removeClass("ui-selecting");
						selectee.selecting = false;
						if (selectee.startselected) {
							selectee.$element.addClass("ui-unselecting");
							selectee.unselecting = true;
						}
						// selectable UNSELECTING callback
						that._trigger("unselecting", event, {
							unselecting: selectee.element
						});
					}
				}
				if (selectee.selected) {
					if (!event.metaKey && !event.ctrlKey && !selectee.startselected) {
						selectee.$element.removeClass("ui-selected");
						selectee.selected = false;

						selectee.$element.addClass("ui-unselecting");
						selectee.unselecting = true;
						// selectable UNSELECTING callback
						that._trigger("unselecting", event, {
							unselecting: selectee.element
						});
					}
				}
			}
		});

		return false;
	},

	_mouseStop: function(event) {
		var that = this;

		this.dragged = false;

		$(".ui-unselecting", this.element[0]).each(function() {
			var selectee = $.data(this, "selectable-item");
			selectee.$element.removeClass("ui-unselecting");
			selectee.unselecting = false;
			selectee.startselected = false;
			that._trigger("unselected", event, {
				unselected: selectee.element
			});
		});
		$(".ui-selecting", this.element[0]).each(function() {
			var selectee = $.data(this, "selectable-item");
			selectee.$element.removeClass("ui-selecting").addClass("ui-selected");
			selectee.selecting = false;
			selectee.selected = true;
			selectee.startselected = true;
			that._trigger("selected", event, {
				selected: selectee.element
			});
		});
		this._trigger("stop", event);

		this.helper.remove();

		return false;
	}

});


/*!
 * jQuery UI Selectmenu 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/selectmenu
 */


var selectmenu = $.widget( "ui.selectmenu", {
	version: "1.11.1",
	defaultElement: "<select>",
	options: {
		appendTo: null,
		disabled: null,
		icons: {
			button: "ui-icon-triangle-1-s"
		},
		position: {
			my: "left top",
			at: "left bottom",
			collision: "none"
		},
		width: null,

		// callbacks
		change: null,
		close: null,
		focus: null,
		open: null,
		select: null
	},

	_create: function() {
		var selectmenuId = this.element.uniqueId().attr( "id" );
		this.ids = {
			element: selectmenuId,
			button: selectmenuId + "-button",
			menu: selectmenuId + "-menu"
		};

		this._drawButton();
		this._drawMenu();

		if ( this.options.disabled ) {
			this.disable();
		}
	},

	_drawButton: function() {
		var that = this,
			tabindex = this.element.attr( "tabindex" );

		// Associate existing label with the new button
		this.label = $( "label[for='" + this.ids.element + "']" ).attr( "for", this.ids.button );
		this._on( this.label, {
			click: function( event ) {
				this.button.focus();
				event.preventDefault();
			}
		});

		// Hide original select element
		this.element.hide();

		// Create button
		this.button = $( "<span>", {
			"class": "ui-selectmenu-button ui-widget ui-state-default ui-corner-all",
			tabindex: tabindex || this.options.disabled ? -1 : 0,
			id: this.ids.button,
			role: "combobox",
			"aria-expanded": "false",
			"aria-autocomplete": "list",
			"aria-owns": this.ids.menu,
			"aria-haspopup": "true"
		})
			.insertAfter( this.element );

		$( "<span>", {
			"class": "ui-icon " + this.options.icons.button
		})
			.prependTo( this.button );

		this.buttonText = $( "<span>", {
			"class": "ui-selectmenu-text"
		})
			.appendTo( this.button );

		this._setText( this.buttonText, this.element.find( "option:selected" ).text() );
		this._resizeButton();

		this._on( this.button, this._buttonEvents );
		this.button.one( "focusin", function() {

			// Delay rendering the menu items until the button receives focus.
			// The menu may have already been rendered via a programmatic open.
			if ( !that.menuItems ) {
				that._refreshMenu();
			}
		});
		this._hoverable( this.button );
		this._focusable( this.button );
	},

	_drawMenu: function() {
		var that = this;

		// Create menu
		this.menu = $( "<ul>", {
			"aria-hidden": "true",
			"aria-labelledby": this.ids.button,
			id: this.ids.menu
		});

		// Wrap menu
		this.menuWrap = $( "<div>", {
			"class": "ui-selectmenu-menu ui-front"
		})
			.append( this.menu )
			.appendTo( this._appendTo() );

		// Initialize menu widget
		this.menuInstance = this.menu
			.menu({
				role: "listbox",
				select: function( event, ui ) {
					event.preventDefault();
					that._select( ui.item.data( "ui-selectmenu-item" ), event );
				},
				focus: function( event, ui ) {
					var item = ui.item.data( "ui-selectmenu-item" );

					// Prevent inital focus from firing and check if its a newly focused item
					if ( that.focusIndex != null && item.index !== that.focusIndex ) {
						that._trigger( "focus", event, { item: item } );
						if ( !that.isOpen ) {
							that._select( item, event );
						}
					}
					that.focusIndex = item.index;

					that.button.attr( "aria-activedescendant",
						that.menuItems.eq( item.index ).attr( "id" ) );
				}
			})
			.menu( "instance" );

		// Adjust menu styles to dropdown
		this.menu
			.addClass( "ui-corner-bottom" )
			.removeClass( "ui-corner-all" );

		// Don't close the menu on mouseleave
		this.menuInstance._off( this.menu, "mouseleave" );

		// Cancel the menu's collapseAll on document click
		this.menuInstance._closeOnDocumentClick = function() {
			return false;
		};

		// Selects often contain empty items, but never contain dividers
		this.menuInstance._isDivider = function() {
			return false;
		};
	},

	refresh: function() {
		this._refreshMenu();
		this._setText( this.buttonText, this._getSelectedItem().text() );
		if ( !this.options.width ) {
			this._resizeButton();
		}
	},

	_refreshMenu: function() {
		this.menu.empty();

		var item,
			options = this.element.find( "option" );

		if ( !options.length ) {
			return;
		}

		this._parseOptions( options );
		this._renderMenu( this.menu, this.items );

		this.menuInstance.refresh();
		this.menuItems = this.menu.find( "li" ).not( ".ui-selectmenu-optgroup" );

		item = this._getSelectedItem();

		// Update the menu to have the correct item focused
		this.menuInstance.focus( null, item );
		this._setAria( item.data( "ui-selectmenu-item" ) );

		// Set disabled state
		this._setOption( "disabled", this.element.prop( "disabled" ) );
	},

	open: function( event ) {
		if ( this.options.disabled ) {
			return;
		}

		// If this is the first time the menu is being opened, render the items
		if ( !this.menuItems ) {
			this._refreshMenu();
		} else {

			// Menu clears focus on close, reset focus to selected item
			this.menu.find( ".ui-state-focus" ).removeClass( "ui-state-focus" );
			this.menuInstance.focus( null, this._getSelectedItem() );
		}

		this.isOpen = true;
		this._toggleAttr();
		this._resizeMenu();
		this._position();

		this._on( this.document, this._documentClick );

		this._trigger( "open", event );
	},

	_position: function() {
		this.menuWrap.position( $.extend( { of: this.button }, this.options.position ) );
	},

	close: function( event ) {
		if ( !this.isOpen ) {
			return;
		}

		this.isOpen = false;
		this._toggleAttr();

		this._off( this.document );

		this._trigger( "close", event );
	},

	widget: function() {
		return this.button;
	},

	menuWidget: function() {
		return this.menu;
	},

	_renderMenu: function( ul, items ) {
		var that = this,
			currentOptgroup = "";

		$.each( items, function( index, item ) {
			if ( item.optgroup !== currentOptgroup ) {
				$( "<li>", {
					"class": "ui-selectmenu-optgroup ui-menu-divider" +
						( item.element.parent( "optgroup" ).prop( "disabled" ) ?
							" ui-state-disabled" :
							"" ),
					text: item.optgroup
				})
					.appendTo( ul );

				currentOptgroup = item.optgroup;
			}

			that._renderItemData( ul, item );
		});
	},

	_renderItemData: function( ul, item ) {
		return this._renderItem( ul, item ).data( "ui-selectmenu-item", item );
	},

	_renderItem: function( ul, item ) {
		var li = $( "<li>" );

		if ( item.disabled ) {
			li.addClass( "ui-state-disabled" );
		}
		this._setText( li, item.label );

		return li.appendTo( ul );
	},

	_setText: function( element, value ) {
		if ( value ) {
			element.text( value );
		} else {
			element.html( "&#160;" );
		}
	},

	_move: function( direction, event ) {
		var item, next,
			filter = ".ui-menu-item";

		if ( this.isOpen ) {
			item = this.menuItems.eq( this.focusIndex );
		} else {
			item = this.menuItems.eq( this.element[ 0 ].selectedIndex );
			filter += ":not(.ui-state-disabled)";
		}

		if ( direction === "first" || direction === "last" ) {
			next = item[ direction === "first" ? "prevAll" : "nextAll" ]( filter ).eq( -1 );
		} else {
			next = item[ direction + "All" ]( filter ).eq( 0 );
		}

		if ( next.length ) {
			this.menuInstance.focus( event, next );
		}
	},

	_getSelectedItem: function() {
		return this.menuItems.eq( this.element[ 0 ].selectedIndex );
	},

	_toggle: function( event ) {
		this[ this.isOpen ? "close" : "open" ]( event );
	},

	_documentClick: {
		mousedown: function( event ) {
			if ( !this.isOpen ) {
				return;
			}

			if ( !$( event.target ).closest( ".ui-selectmenu-menu, #" + this.ids.button ).length ) {
				this.close( event );
			}
		}
	},

	_buttonEvents: {

		// Prevent text selection from being reset when interacting with the selectmenu (#10144)
		mousedown: function( event ) {
			event.preventDefault();
		},

		click: "_toggle",

		keydown: function( event ) {
			var preventDefault = true;
			switch ( event.keyCode ) {
				case $.ui.keyCode.TAB:
				case $.ui.keyCode.ESCAPE:
					this.close( event );
					preventDefault = false;
					break;
				case $.ui.keyCode.ENTER:
					if ( this.isOpen ) {
						this._selectFocusedItem( event );
					}
					break;
				case $.ui.keyCode.UP:
					if ( event.altKey ) {
						this._toggle( event );
					} else {
						this._move( "prev", event );
					}
					break;
				case $.ui.keyCode.DOWN:
					if ( event.altKey ) {
						this._toggle( event );
					} else {
						this._move( "next", event );
					}
					break;
				case $.ui.keyCode.SPACE:
					if ( this.isOpen ) {
						this._selectFocusedItem( event );
					} else {
						this._toggle( event );
					}
					break;
				case $.ui.keyCode.LEFT:
					this._move( "prev", event );
					break;
				case $.ui.keyCode.RIGHT:
					this._move( "next", event );
					break;
				case $.ui.keyCode.HOME:
				case $.ui.keyCode.PAGE_UP:
					this._move( "first", event );
					break;
				case $.ui.keyCode.END:
				case $.ui.keyCode.PAGE_DOWN:
					this._move( "last", event );
					break;
				default:
					this.menu.trigger( event );
					preventDefault = false;
			}

			if ( preventDefault ) {
				event.preventDefault();
			}
		}
	},

	_selectFocusedItem: function( event ) {
		var item = this.menuItems.eq( this.focusIndex );
		if ( !item.hasClass( "ui-state-disabled" ) ) {
			this._select( item.data( "ui-selectmenu-item" ), event );
		}
	},

	_select: function( item, event ) {
		var oldIndex = this.element[ 0 ].selectedIndex;

		// Change native select element
		this.element[ 0 ].selectedIndex = item.index;
		this._setText( this.buttonText, item.label );
		this._setAria( item );
		this._trigger( "select", event, { item: item } );

		if ( item.index !== oldIndex ) {
			this._trigger( "change", event, { item: item } );
		}

		this.close( event );
	},

	_setAria: function( item ) {
		var id = this.menuItems.eq( item.index ).attr( "id" );

		this.button.attr({
			"aria-labelledby": id,
			"aria-activedescendant": id
		});
		this.menu.attr( "aria-activedescendant", id );
	},

	_setOption: function( key, value ) {
		if ( key === "icons" ) {
			this.button.find( "span.ui-icon" )
				.removeClass( this.options.icons.button )
				.addClass( value.button );
		}

		this._super( key, value );

		if ( key === "appendTo" ) {
			this.menuWrap.appendTo( this._appendTo() );
		}

		if ( key === "disabled" ) {
			this.menuInstance.option( "disabled", value );
			this.button
				.toggleClass( "ui-state-disabled", value )
				.attr( "aria-disabled", value );

			this.element.prop( "disabled", value );
			if ( value ) {
				this.button.attr( "tabindex", -1 );
				this.close();
			} else {
				this.button.attr( "tabindex", 0 );
			}
		}

		if ( key === "width" ) {
			this._resizeButton();
		}
	},

	_appendTo: function() {
		var element = this.options.appendTo;

		if ( element ) {
			element = element.jquery || element.nodeType ?
				$( element ) :
				this.document.find( element ).eq( 0 );
		}

		if ( !element || !element[ 0 ] ) {
			element = this.element.closest( ".ui-front" );
		}

		if ( !element.length ) {
			element = this.document[ 0 ].body;
		}

		return element;
	},

	_toggleAttr: function() {
		this.button
			.toggleClass( "ui-corner-top", this.isOpen )
			.toggleClass( "ui-corner-all", !this.isOpen )
			.attr( "aria-expanded", this.isOpen );
		this.menuWrap.toggleClass( "ui-selectmenu-open", this.isOpen );
		this.menu.attr( "aria-hidden", !this.isOpen );
	},

	_resizeButton: function() {
		var width = this.options.width;

		if ( !width ) {
			width = this.element.show().outerWidth();
			this.element.hide();
		}

		this.button.outerWidth( width );
	},

	_resizeMenu: function() {
		this.menu.outerWidth( Math.max(
			this.button.outerWidth(),

			// support: IE10
			// IE10 wraps long text (possibly a rounding bug)
			// so we add 1px to avoid the wrapping
			this.menu.width( "" ).outerWidth() + 1
		) );
	},

	_getCreateOptions: function() {
		return { disabled: this.element.prop( "disabled" ) };
	},

	_parseOptions: function( options ) {
		var data = [];
		options.each(function( index, item ) {
			var option = $( item ),
				optgroup = option.parent( "optgroup" );
			data.push({
				element: option,
				index: index,
				value: option.attr( "value" ),
				label: option.text(),
				optgroup: optgroup.attr( "label" ) || "",
				disabled: optgroup.prop( "disabled" ) || option.prop( "disabled" )
			});
		});
		this.items = data;
	},

	_destroy: function() {
		this.menuWrap.remove();
		this.button.remove();
		this.element.show();
		this.element.removeUniqueId();
		this.label.attr( "for", this.ids.element );
	}
});


/*!
 * jQuery UI Slider 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/slider/
 */


var slider = $.widget( "ui.slider", $.ui.mouse, {
	version: "1.11.1",
	widgetEventPrefix: "slide",

	options: {
		animate: false,
		distance: 0,
		max: 100,
		min: 0,
		orientation: "horizontal",
		range: false,
		step: 1,
		value: 0,
		values: null,

		// callbacks
		change: null,
		slide: null,
		start: null,
		stop: null
	},

	// number of pages in a slider
	// (how many times can you page up/down to go through the whole range)
	numPages: 5,

	_create: function() {
		this._keySliding = false;
		this._mouseSliding = false;
		this._animateOff = true;
		this._handleIndex = null;
		this._detectOrientation();
		this._mouseInit();

		this.element
			.addClass( "ui-slider" +
				" ui-slider-" + this.orientation +
				" ui-widget" +
				" ui-widget-content" +
				" ui-corner-all");

		this._refresh();
		this._setOption( "disabled", this.options.disabled );

		this._animateOff = false;
	},

	_refresh: function() {
		this._createRange();
		this._createHandles();
		this._setupEvents();
		this._refreshValue();
	},

	_createHandles: function() {
		var i, handleCount,
			options = this.options,
			existingHandles = this.element.find( ".ui-slider-handle" ).addClass( "ui-state-default ui-corner-all" ),
			handle = "<span class='ui-slider-handle ui-state-default ui-corner-all' tabindex='0'></span>",
			handles = [];

		handleCount = ( options.values && options.values.length ) || 1;

		if ( existingHandles.length > handleCount ) {
			existingHandles.slice( handleCount ).remove();
			existingHandles = existingHandles.slice( 0, handleCount );
		}

		for ( i = existingHandles.length; i < handleCount; i++ ) {
			handles.push( handle );
		}

		this.handles = existingHandles.add( $( handles.join( "" ) ).appendTo( this.element ) );

		this.handle = this.handles.eq( 0 );

		this.handles.each(function( i ) {
			$( this ).data( "ui-slider-handle-index", i );
		});
	},

	_createRange: function() {
		var options = this.options,
			classes = "";

		if ( options.range ) {
			if ( options.range === true ) {
				if ( !options.values ) {
					options.values = [ this._valueMin(), this._valueMin() ];
				} else if ( options.values.length && options.values.length !== 2 ) {
					options.values = [ options.values[0], options.values[0] ];
				} else if ( $.isArray( options.values ) ) {
					options.values = options.values.slice(0);
				}
			}

			if ( !this.range || !this.range.length ) {
				this.range = $( "<div></div>" )
					.appendTo( this.element );

				classes = "ui-slider-range" +
				// note: this isn't the most fittingly semantic framework class for this element,
				// but worked best visually with a variety of themes
				" ui-widget-header ui-corner-all";
			} else {
				this.range.removeClass( "ui-slider-range-min ui-slider-range-max" )
					// Handle range switching from true to min/max
					.css({
						"left": "",
						"bottom": ""
					});
			}

			this.range.addClass( classes +
				( ( options.range === "min" || options.range === "max" ) ? " ui-slider-range-" + options.range : "" ) );
		} else {
			if ( this.range ) {
				this.range.remove();
			}
			this.range = null;
		}
	},

	_setupEvents: function() {
		this._off( this.handles );
		this._on( this.handles, this._handleEvents );
		this._hoverable( this.handles );
		this._focusable( this.handles );
	},

	_destroy: function() {
		this.handles.remove();
		if ( this.range ) {
			this.range.remove();
		}

		this.element
			.removeClass( "ui-slider" +
				" ui-slider-horizontal" +
				" ui-slider-vertical" +
				" ui-widget" +
				" ui-widget-content" +
				" ui-corner-all" );

		this._mouseDestroy();
	},

	_mouseCapture: function( event ) {
		var position, normValue, distance, closestHandle, index, allowed, offset, mouseOverHandle,
			that = this,
			o = this.options;

		if ( o.disabled ) {
			return false;
		}

		this.elementSize = {
			width: this.element.outerWidth(),
			height: this.element.outerHeight()
		};
		this.elementOffset = this.element.offset();

		position = { x: event.pageX, y: event.pageY };
		normValue = this._normValueFromMouse( position );
		distance = this._valueMax() - this._valueMin() + 1;
		this.handles.each(function( i ) {
			var thisDistance = Math.abs( normValue - that.values(i) );
			if (( distance > thisDistance ) ||
				( distance === thisDistance &&
					(i === that._lastChangedValue || that.values(i) === o.min ))) {
				distance = thisDistance;
				closestHandle = $( this );
				index = i;
			}
		});

		allowed = this._start( event, index );
		if ( allowed === false ) {
			return false;
		}
		this._mouseSliding = true;

		this._handleIndex = index;

		closestHandle
			.addClass( "ui-state-active" )
			.focus();

		offset = closestHandle.offset();
		mouseOverHandle = !$( event.target ).parents().addBack().is( ".ui-slider-handle" );
		this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : {
			left: event.pageX - offset.left - ( closestHandle.width() / 2 ),
			top: event.pageY - offset.top -
				( closestHandle.height() / 2 ) -
				( parseInt( closestHandle.css("borderTopWidth"), 10 ) || 0 ) -
				( parseInt( closestHandle.css("borderBottomWidth"), 10 ) || 0) +
				( parseInt( closestHandle.css("marginTop"), 10 ) || 0)
		};

		if ( !this.handles.hasClass( "ui-state-hover" ) ) {
			this._slide( event, index, normValue );
		}
		this._animateOff = true;
		return true;
	},

	_mouseStart: function() {
		return true;
	},

	_mouseDrag: function( event ) {
		var position = { x: event.pageX, y: event.pageY },
			normValue = this._normValueFromMouse( position );

		this._slide( event, this._handleIndex, normValue );

		return false;
	},

	_mouseStop: function( event ) {
		this.handles.removeClass( "ui-state-active" );
		this._mouseSliding = false;

		this._stop( event, this._handleIndex );
		this._change( event, this._handleIndex );

		this._handleIndex = null;
		this._clickOffset = null;
		this._animateOff = false;

		return false;
	},

	_detectOrientation: function() {
		this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal";
	},

	_normValueFromMouse: function( position ) {
		var pixelTotal,
			pixelMouse,
			percentMouse,
			valueTotal,
			valueMouse;

		if ( this.orientation === "horizontal" ) {
			pixelTotal = this.elementSize.width;
			pixelMouse = position.x - this.elementOffset.left - ( this._clickOffset ? this._clickOffset.left : 0 );
		} else {
			pixelTotal = this.elementSize.height;
			pixelMouse = position.y - this.elementOffset.top - ( this._clickOffset ? this._clickOffset.top : 0 );
		}

		percentMouse = ( pixelMouse / pixelTotal );
		if ( percentMouse > 1 ) {
			percentMouse = 1;
		}
		if ( percentMouse < 0 ) {
			percentMouse = 0;
		}
		if ( this.orientation === "vertical" ) {
			percentMouse = 1 - percentMouse;
		}

		valueTotal = this._valueMax() - this._valueMin();
		valueMouse = this._valueMin() + percentMouse * valueTotal;

		return this._trimAlignValue( valueMouse );
	},

	_start: function( event, index ) {
		var uiHash = {
			handle: this.handles[ index ],
			value: this.value()
		};
		if ( this.options.values && this.options.values.length ) {
			uiHash.value = this.values( index );
			uiHash.values = this.values();
		}
		return this._trigger( "start", event, uiHash );
	},

	_slide: function( event, index, newVal ) {
		var otherVal,
			newValues,
			allowed;

		if ( this.options.values && this.options.values.length ) {
			otherVal = this.values( index ? 0 : 1 );

			if ( ( this.options.values.length === 2 && this.options.range === true ) &&
					( ( index === 0 && newVal > otherVal) || ( index === 1 && newVal < otherVal ) )
				) {
				newVal = otherVal;
			}

			if ( newVal !== this.values( index ) ) {
				newValues = this.values();
				newValues[ index ] = newVal;
				// A slide can be canceled by returning false from the slide callback
				allowed = this._trigger( "slide", event, {
					handle: this.handles[ index ],
					value: newVal,
					values: newValues
				} );
				otherVal = this.values( index ? 0 : 1 );
				if ( allowed !== false ) {
					this.values( index, newVal );
				}
			}
		} else {
			if ( newVal !== this.value() ) {
				// A slide can be canceled by returning false from the slide callback
				allowed = this._trigger( "slide", event, {
					handle: this.handles[ index ],
					value: newVal
				} );
				if ( allowed !== false ) {
					this.value( newVal );
				}
			}
		}
	},

	_stop: function( event, index ) {
		var uiHash = {
			handle: this.handles[ index ],
			value: this.value()
		};
		if ( this.options.values && this.options.values.length ) {
			uiHash.value = this.values( index );
			uiHash.values = this.values();
		}

		this._trigger( "stop", event, uiHash );
	},

	_change: function( event, index ) {
		if ( !this._keySliding && !this._mouseSliding ) {
			var uiHash = {
				handle: this.handles[ index ],
				value: this.value()
			};
			if ( this.options.values && this.options.values.length ) {
				uiHash.value = this.values( index );
				uiHash.values = this.values();
			}

			//store the last changed value index for reference when handles overlap
			this._lastChangedValue = index;

			this._trigger( "change", event, uiHash );
		}
	},

	value: function( newValue ) {
		if ( arguments.length ) {
			this.options.value = this._trimAlignValue( newValue );
			this._refreshValue();
			this._change( null, 0 );
			return;
		}

		return this._value();
	},

	values: function( index, newValue ) {
		var vals,
			newValues,
			i;

		if ( arguments.length > 1 ) {
			this.options.values[ index ] = this._trimAlignValue( newValue );
			this._refreshValue();
			this._change( null, index );
			return;
		}

		if ( arguments.length ) {
			if ( $.isArray( arguments[ 0 ] ) ) {
				vals = this.options.values;
				newValues = arguments[ 0 ];
				for ( i = 0; i < vals.length; i += 1 ) {
					vals[ i ] = this._trimAlignValue( newValues[ i ] );
					this._change( null, i );
				}
				this._refreshValue();
			} else {
				if ( this.options.values && this.options.values.length ) {
					return this._values( index );
				} else {
					return this.value();
				}
			}
		} else {
			return this._values();
		}
	},

	_setOption: function( key, value ) {
		var i,
			valsLength = 0;

		if ( key === "range" && this.options.range === true ) {
			if ( value === "min" ) {
				this.options.value = this._values( 0 );
				this.options.values = null;
			} else if ( value === "max" ) {
				this.options.value = this._values( this.options.values.length - 1 );
				this.options.values = null;
			}
		}

		if ( $.isArray( this.options.values ) ) {
			valsLength = this.options.values.length;
		}

		if ( key === "disabled" ) {
			this.element.toggleClass( "ui-state-disabled", !!value );
		}

		this._super( key, value );

		switch ( key ) {
			case "orientation":
				this._detectOrientation();
				this.element
					.removeClass( "ui-slider-horizontal ui-slider-vertical" )
					.addClass( "ui-slider-" + this.orientation );
				this._refreshValue();

				// Reset positioning from previous orientation
				this.handles.css( value === "horizontal" ? "bottom" : "left", "" );
				break;
			case "value":
				this._animateOff = true;
				this._refreshValue();
				this._change( null, 0 );
				this._animateOff = false;
				break;
			case "values":
				this._animateOff = true;
				this._refreshValue();
				for ( i = 0; i < valsLength; i += 1 ) {
					this._change( null, i );
				}
				this._animateOff = false;
				break;
			case "min":
			case "max":
				this._animateOff = true;
				this._refreshValue();
				this._animateOff = false;
				break;
			case "range":
				this._animateOff = true;
				this._refresh();
				this._animateOff = false;
				break;
		}
	},

	//internal value getter
	// _value() returns value trimmed by min and max, aligned by step
	_value: function() {
		var val = this.options.value;
		val = this._trimAlignValue( val );

		return val;
	},

	//internal values getter
	// _values() returns array of values trimmed by min and max, aligned by step
	// _values( index ) returns single value trimmed by min and max, aligned by step
	_values: function( index ) {
		var val,
			vals,
			i;

		if ( arguments.length ) {
			val = this.options.values[ index ];
			val = this._trimAlignValue( val );

			return val;
		} else if ( this.options.values && this.options.values.length ) {
			// .slice() creates a copy of the array
			// this copy gets trimmed by min and max and then returned
			vals = this.options.values.slice();
			for ( i = 0; i < vals.length; i+= 1) {
				vals[ i ] = this._trimAlignValue( vals[ i ] );
			}

			return vals;
		} else {
			return [];
		}
	},

	// returns the step-aligned value that val is closest to, between (inclusive) min and max
	_trimAlignValue: function( val ) {
		if ( val <= this._valueMin() ) {
			return this._valueMin();
		}
		if ( val >= this._valueMax() ) {
			return this._valueMax();
		}
		var step = ( this.options.step > 0 ) ? this.options.step : 1,
			valModStep = (val - this._valueMin()) % step,
			alignValue = val - valModStep;

		if ( Math.abs(valModStep) * 2 >= step ) {
			alignValue += ( valModStep > 0 ) ? step : ( -step );
		}

		// Since JavaScript has problems with large floats, round
		// the final value to 5 digits after the decimal point (see #4124)
		return parseFloat( alignValue.toFixed(5) );
	},

	_valueMin: function() {
		return this.options.min;
	},

	_valueMax: function() {
		return this.options.max;
	},

	_refreshValue: function() {
		var lastValPercent, valPercent, value, valueMin, valueMax,
			oRange = this.options.range,
			o = this.options,
			that = this,
			animate = ( !this._animateOff ) ? o.animate : false,
			_set = {};

		if ( this.options.values && this.options.values.length ) {
			this.handles.each(function( i ) {
				valPercent = ( that.values(i) - that._valueMin() ) / ( that._valueMax() - that._valueMin() ) * 100;
				_set[ that.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
				$( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
				if ( that.options.range === true ) {
					if ( that.orientation === "horizontal" ) {
						if ( i === 0 ) {
							that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { left: valPercent + "%" }, o.animate );
						}
						if ( i === 1 ) {
							that.range[ animate ? "animate" : "css" ]( { width: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
						}
					} else {
						if ( i === 0 ) {
							that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { bottom: ( valPercent ) + "%" }, o.animate );
						}
						if ( i === 1 ) {
							that.range[ animate ? "animate" : "css" ]( { height: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
						}
					}
				}
				lastValPercent = valPercent;
			});
		} else {
			value = this.value();
			valueMin = this._valueMin();
			valueMax = this._valueMax();
			valPercent = ( valueMax !== valueMin ) ?
					( value - valueMin ) / ( valueMax - valueMin ) * 100 :
					0;
			_set[ this.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
			this.handle.stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );

			if ( oRange === "min" && this.orientation === "horizontal" ) {
				this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { width: valPercent + "%" }, o.animate );
			}
			if ( oRange === "max" && this.orientation === "horizontal" ) {
				this.range[ animate ? "animate" : "css" ]( { width: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
			}
			if ( oRange === "min" && this.orientation === "vertical" ) {
				this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { height: valPercent + "%" }, o.animate );
			}
			if ( oRange === "max" && this.orientation === "vertical" ) {
				this.range[ animate ? "animate" : "css" ]( { height: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
			}
		}
	},

	_handleEvents: {
		keydown: function( event ) {
			var allowed, curVal, newVal, step,
				index = $( event.target ).data( "ui-slider-handle-index" );

			switch ( event.keyCode ) {
				case $.ui.keyCode.HOME:
				case $.ui.keyCode.END:
				case $.ui.keyCode.PAGE_UP:
				case $.ui.keyCode.PAGE_DOWN:
				case $.ui.keyCode.UP:
				case $.ui.keyCode.RIGHT:
				case $.ui.keyCode.DOWN:
				case $.ui.keyCode.LEFT:
					event.preventDefault();
					if ( !this._keySliding ) {
						this._keySliding = true;
						$( event.target ).addClass( "ui-state-active" );
						allowed = this._start( event, index );
						if ( allowed === false ) {
							return;
						}
					}
					break;
			}

			step = this.options.step;
			if ( this.options.values && this.options.values.length ) {
				curVal = newVal = this.values( index );
			} else {
				curVal = newVal = this.value();
			}

			switch ( event.keyCode ) {
				case $.ui.keyCode.HOME:
					newVal = this._valueMin();
					break;
				case $.ui.keyCode.END:
					newVal = this._valueMax();
					break;
				case $.ui.keyCode.PAGE_UP:
					newVal = this._trimAlignValue(
						curVal + ( ( this._valueMax() - this._valueMin() ) / this.numPages )
					);
					break;
				case $.ui.keyCode.PAGE_DOWN:
					newVal = this._trimAlignValue(
						curVal - ( (this._valueMax() - this._valueMin()) / this.numPages ) );
					break;
				case $.ui.keyCode.UP:
				case $.ui.keyCode.RIGHT:
					if ( curVal === this._valueMax() ) {
						return;
					}
					newVal = this._trimAlignValue( curVal + step );
					break;
				case $.ui.keyCode.DOWN:
				case $.ui.keyCode.LEFT:
					if ( curVal === this._valueMin() ) {
						return;
					}
					newVal = this._trimAlignValue( curVal - step );
					break;
			}

			this._slide( event, index, newVal );
		},
		keyup: function( event ) {
			var index = $( event.target ).data( "ui-slider-handle-index" );

			if ( this._keySliding ) {
				this._keySliding = false;
				this._stop( event, index );
				this._change( event, index );
				$( event.target ).removeClass( "ui-state-active" );
			}
		}
	}
});


/*!
 * jQuery UI Sortable 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/sortable/
 */


var sortable = $.widget("ui.sortable", $.ui.mouse, {
	version: "1.11.1",
	widgetEventPrefix: "sort",
	ready: false,
	options: {
		appendTo: "parent",
		axis: false,
		connectWith: false,
		containment: false,
		cursor: "auto",
		cursorAt: false,
		dropOnEmpty: true,
		forcePlaceholderSize: false,
		forceHelperSize: false,
		grid: false,
		handle: false,
		helper: "original",
		items: "> *",
		opacity: false,
		placeholder: false,
		revert: false,
		scroll: true,
		scrollSensitivity: 20,
		scrollSpeed: 20,
		scope: "default",
		tolerance: "intersect",
		zIndex: 1000,

		// callbacks
		activate: null,
		beforeStop: null,
		change: null,
		deactivate: null,
		out: null,
		over: null,
		receive: null,
		remove: null,
		sort: null,
		start: null,
		stop: null,
		update: null
	},

	_isOverAxis: function( x, reference, size ) {
		return ( x >= reference ) && ( x < ( reference + size ) );
	},

	_isFloating: function( item ) {
		return (/left|right/).test(item.css("float")) || (/inline|table-cell/).test(item.css("display"));
	},

	_create: function() {

		var o = this.options;
		this.containerCache = {};
		this.element.addClass("ui-sortable");

		//Get the items
		this.refresh();

		//Let's determine if the items are being displayed horizontally
		this.floating = this.items.length ? o.axis === "x" || this._isFloating(this.items[0].item) : false;

		//Let's determine the parent's offset
		this.offset = this.element.offset();

		//Initialize mouse events for interaction
		this._mouseInit();

		this._setHandleClassName();

		//We're ready to go
		this.ready = true;

	},

	_setOption: function( key, value ) {
		this._super( key, value );

		if ( key === "handle" ) {
			this._setHandleClassName();
		}
	},

	_setHandleClassName: function() {
		this.element.find( ".ui-sortable-handle" ).removeClass( "ui-sortable-handle" );
		$.each( this.items, function() {
			( this.instance.options.handle ?
				this.item.find( this.instance.options.handle ) : this.item )
				.addClass( "ui-sortable-handle" );
		});
	},

	_destroy: function() {
		this.element
			.removeClass( "ui-sortable ui-sortable-disabled" )
			.find( ".ui-sortable-handle" )
				.removeClass( "ui-sortable-handle" );
		this._mouseDestroy();

		for ( var i = this.items.length - 1; i >= 0; i-- ) {
			this.items[i].item.removeData(this.widgetName + "-item");
		}

		return this;
	},

	_mouseCapture: function(event, overrideHandle) {
		var currentItem = null,
			validHandle = false,
			that = this;

		if (this.reverting) {
			return false;
		}

		if(this.options.disabled || this.options.type === "static") {
			return false;
		}

		//We have to refresh the items data once first
		this._refreshItems(event);

		//Find out if the clicked node (or one of its parents) is a actual item in this.items
		$(event.target).parents().each(function() {
			if($.data(this, that.widgetName + "-item") === that) {
				currentItem = $(this);
				return false;
			}
		});
		if($.data(event.target, that.widgetName + "-item") === that) {
			currentItem = $(event.target);
		}

		if(!currentItem) {
			return false;
		}
		if(this.options.handle && !overrideHandle) {
			$(this.options.handle, currentItem).find("*").addBack().each(function() {
				if(this === event.target) {
					validHandle = true;
				}
			});
			if(!validHandle) {
				return false;
			}
		}

		this.currentItem = currentItem;
		this._removeCurrentsFromItems();
		return true;

	},

	_mouseStart: function(event, overrideHandle, noActivation) {

		var i, body,
			o = this.options;

		this.currentContainer = this;

		//We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture
		this.refreshPositions();

		//Create and append the visible helper
		this.helper = this._createHelper(event);

		//Cache the helper size
		this._cacheHelperProportions();

		/*
		 * - Position generation -
		 * This block generates everything position related - it's the core of draggables.
		 */

		//Cache the margins of the original element
		this._cacheMargins();

		//Get the next scrolling parent
		this.scrollParent = this.helper.scrollParent();

		//The element's absolute position on the page minus margins
		this.offset = this.currentItem.offset();
		this.offset = {
			top: this.offset.top - this.margins.top,
			left: this.offset.left - this.margins.left
		};

		$.extend(this.offset, {
			click: { //Where the click happened, relative to the element
				left: event.pageX - this.offset.left,
				top: event.pageY - this.offset.top
			},
			parent: this._getParentOffset(),
			relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
		});

		// Only after we got the offset, we can change the helper's position to absolute
		// TODO: Still need to figure out a way to make relative sorting possible
		this.helper.css("position", "absolute");
		this.cssPosition = this.helper.css("position");

		//Generate the original position
		this.originalPosition = this._generatePosition(event);
		this.originalPageX = event.pageX;
		this.originalPageY = event.pageY;

		//Adjust the mouse offset relative to the helper if "cursorAt" is supplied
		(o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));

		//Cache the former DOM position
		this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] };

		//If the helper is not the original, hide the original so it's not playing any role during the drag, won't cause anything bad this way
		if(this.helper[0] !== this.currentItem[0]) {
			this.currentItem.hide();
		}

		//Create the placeholder
		this._createPlaceholder();

		//Set a containment if given in the options
		if(o.containment) {
			this._setContainment();
		}

		if( o.cursor && o.cursor !== "auto" ) { // cursor option
			body = this.document.find( "body" );

			// support: IE
			this.storedCursor = body.css( "cursor" );
			body.css( "cursor", o.cursor );

			this.storedStylesheet = $( "<style>*{ cursor: "+o.cursor+" !important; }</style>" ).appendTo( body );
		}

		if(o.opacity) { // opacity option
			if (this.helper.css("opacity")) {
				this._storedOpacity = this.helper.css("opacity");
			}
			this.helper.css("opacity", o.opacity);
		}

		if(o.zIndex) { // zIndex option
			if (this.helper.css("zIndex")) {
				this._storedZIndex = this.helper.css("zIndex");
			}
			this.helper.css("zIndex", o.zIndex);
		}

		//Prepare scrolling
		if(this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") {
			this.overflowOffset = this.scrollParent.offset();
		}

		//Call callbacks
		this._trigger("start", event, this._uiHash());

		//Recache the helper size
		if(!this._preserveHelperProportions) {
			this._cacheHelperProportions();
		}


		//Post "activate" events to possible containers
		if( !noActivation ) {
			for ( i = this.containers.length - 1; i >= 0; i-- ) {
				this.containers[ i ]._trigger( "activate", event, this._uiHash( this ) );
			}
		}

		//Prepare possible droppables
		if($.ui.ddmanager) {
			$.ui.ddmanager.current = this;
		}

		if ($.ui.ddmanager && !o.dropBehaviour) {
			$.ui.ddmanager.prepareOffsets(this, event);
		}

		this.dragging = true;

		this.helper.addClass("ui-sortable-helper");
		this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position
		return true;

	},

	_mouseDrag: function(event) {
		var i, item, itemElement, intersection,
			o = this.options,
			scrolled = false;

		//Compute the helpers position
		this.position = this._generatePosition(event);
		this.positionAbs = this._convertPositionTo("absolute");

		if (!this.lastPositionAbs) {
			this.lastPositionAbs = this.positionAbs;
		}

		//Do scrolling
		if(this.options.scroll) {
			if(this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") {

				if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) {
					this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
				} else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity) {
					this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;
				}

				if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) {
					this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
				} else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity) {
					this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;
				}

			} else {

				if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) {
					scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
				} else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) {
					scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
				}

				if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) {
					scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
				} else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) {
					scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
				}

			}

			if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) {
				$.ui.ddmanager.prepareOffsets(this, event);
			}
		}

		//Regenerate the absolute position used for position checks
		this.positionAbs = this._convertPositionTo("absolute");

		//Set the helper position
		if(!this.options.axis || this.options.axis !== "y") {
			this.helper[0].style.left = this.position.left+"px";
		}
		if(!this.options.axis || this.options.axis !== "x") {
			this.helper[0].style.top = this.position.top+"px";
		}

		//Rearrange
		for (i = this.items.length - 1; i >= 0; i--) {

			//Cache variables and intersection, continue if no intersection
			item = this.items[i];
			itemElement = item.item[0];
			intersection = this._intersectsWithPointer(item);
			if (!intersection) {
				continue;
			}

			// Only put the placeholder inside the current Container, skip all
			// items from other containers. This works because when moving
			// an item from one container to another the
			// currentContainer is switched before the placeholder is moved.
			//
			// Without this, moving items in "sub-sortables" can cause
			// the placeholder to jitter between the outer and inner container.
			if (item.instance !== this.currentContainer) {
				continue;
			}

			// cannot intersect with itself
			// no useless actions that have been done before
			// no action if the item moved is the parent of the item checked
			if (itemElement !== this.currentItem[0] &&
				this.placeholder[intersection === 1 ? "next" : "prev"]()[0] !== itemElement &&
				!$.contains(this.placeholder[0], itemElement) &&
				(this.options.type === "semi-dynamic" ? !$.contains(this.element[0], itemElement) : true)
			) {

				this.direction = intersection === 1 ? "down" : "up";

				if (this.options.tolerance === "pointer" || this._intersectsWithSides(item)) {
					this._rearrange(event, item);
				} else {
					break;
				}

				this._trigger("change", event, this._uiHash());
				break;
			}
		}

		//Post events to containers
		this._contactContainers(event);

		//Interconnect with droppables
		if($.ui.ddmanager) {
			$.ui.ddmanager.drag(this, event);
		}

		//Call callbacks
		this._trigger("sort", event, this._uiHash());

		this.lastPositionAbs = this.positionAbs;
		return false;

	},

	_mouseStop: function(event, noPropagation) {

		if(!event) {
			return;
		}

		//If we are using droppables, inform the manager about the drop
		if ($.ui.ddmanager && !this.options.dropBehaviour) {
			$.ui.ddmanager.drop(this, event);
		}

		if(this.options.revert) {
			var that = this,
				cur = this.placeholder.offset(),
				axis = this.options.axis,
				animation = {};

			if ( !axis || axis === "x" ) {
				animation.left = cur.left - this.offset.parent.left - this.margins.left + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollLeft);
			}
			if ( !axis || axis === "y" ) {
				animation.top = cur.top - this.offset.parent.top - this.margins.top + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollTop);
			}
			this.reverting = true;
			$(this.helper).animate( animation, parseInt(this.options.revert, 10) || 500, function() {
				that._clear(event);
			});
		} else {
			this._clear(event, noPropagation);
		}

		return false;

	},

	cancel: function() {

		if(this.dragging) {

			this._mouseUp({ target: null });

			if(this.options.helper === "original") {
				this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
			} else {
				this.currentItem.show();
			}

			//Post deactivating events to containers
			for (var i = this.containers.length - 1; i >= 0; i--){
				this.containers[i]._trigger("deactivate", null, this._uiHash(this));
				if(this.containers[i].containerCache.over) {
					this.containers[i]._trigger("out", null, this._uiHash(this));
					this.containers[i].containerCache.over = 0;
				}
			}

		}

		if (this.placeholder) {
			//$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
			if(this.placeholder[0].parentNode) {
				this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
			}
			if(this.options.helper !== "original" && this.helper && this.helper[0].parentNode) {
				this.helper.remove();
			}

			$.extend(this, {
				helper: null,
				dragging: false,
				reverting: false,
				_noFinalSort: null
			});

			if(this.domPosition.prev) {
				$(this.domPosition.prev).after(this.currentItem);
			} else {
				$(this.domPosition.parent).prepend(this.currentItem);
			}
		}

		return this;

	},

	serialize: function(o) {

		var items = this._getItemsAsjQuery(o && o.connected),
			str = [];
		o = o || {};

		$(items).each(function() {
			var res = ($(o.item || this).attr(o.attribute || "id") || "").match(o.expression || (/(.+)[\-=_](.+)/));
			if (res) {
				str.push((o.key || res[1]+"[]")+"="+(o.key && o.expression ? res[1] : res[2]));
			}
		});

		if(!str.length && o.key) {
			str.push(o.key + "=");
		}

		return str.join("&");

	},

	toArray: function(o) {

		var items = this._getItemsAsjQuery(o && o.connected),
			ret = [];

		o = o || {};

		items.each(function() { ret.push($(o.item || this).attr(o.attribute || "id") || ""); });
		return ret;

	},

	/* Be careful with the following core functions */
	_intersectsWith: function(item) {

		var x1 = this.positionAbs.left,
			x2 = x1 + this.helperProportions.width,
			y1 = this.positionAbs.top,
			y2 = y1 + this.helperProportions.height,
			l = item.left,
			r = l + item.width,
			t = item.top,
			b = t + item.height,
			dyClick = this.offset.click.top,
			dxClick = this.offset.click.left,
			isOverElementHeight = ( this.options.axis === "x" ) || ( ( y1 + dyClick ) > t && ( y1 + dyClick ) < b ),
			isOverElementWidth = ( this.options.axis === "y" ) || ( ( x1 + dxClick ) > l && ( x1 + dxClick ) < r ),
			isOverElement = isOverElementHeight && isOverElementWidth;

		if ( this.options.tolerance === "pointer" ||
			this.options.forcePointerForContainers ||
			(this.options.tolerance !== "pointer" && this.helperProportions[this.floating ? "width" : "height"] > item[this.floating ? "width" : "height"])
		) {
			return isOverElement;
		} else {

			return (l < x1 + (this.helperProportions.width / 2) && // Right Half
				x2 - (this.helperProportions.width / 2) < r && // Left Half
				t < y1 + (this.helperProportions.height / 2) && // Bottom Half
				y2 - (this.helperProportions.height / 2) < b ); // Top Half

		}
	},

	_intersectsWithPointer: function(item) {

		var isOverElementHeight = (this.options.axis === "x") || this._isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height),
			isOverElementWidth = (this.options.axis === "y") || this._isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width),
			isOverElement = isOverElementHeight && isOverElementWidth,
			verticalDirection = this._getDragVerticalDirection(),
			horizontalDirection = this._getDragHorizontalDirection();

		if (!isOverElement) {
			return false;
		}

		return this.floating ?
			( ((horizontalDirection && horizontalDirection === "right") || verticalDirection === "down") ? 2 : 1 )
			: ( verticalDirection && (verticalDirection === "down" ? 2 : 1) );

	},

	_intersectsWithSides: function(item) {

		var isOverBottomHalf = this._isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height),
			isOverRightHalf = this._isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width),
			verticalDirection = this._getDragVerticalDirection(),
			horizontalDirection = this._getDragHorizontalDirection();

		if (this.floating && horizontalDirection) {
			return ((horizontalDirection === "right" && isOverRightHalf) || (horizontalDirection === "left" && !isOverRightHalf));
		} else {
			return verticalDirection && ((verticalDirection === "down" && isOverBottomHalf) || (verticalDirection === "up" && !isOverBottomHalf));
		}

	},

	_getDragVerticalDirection: function() {
		var delta = this.positionAbs.top - this.lastPositionAbs.top;
		return delta !== 0 && (delta > 0 ? "down" : "up");
	},

	_getDragHorizontalDirection: function() {
		var delta = this.positionAbs.left - this.lastPositionAbs.left;
		return delta !== 0 && (delta > 0 ? "right" : "left");
	},

	refresh: function(event) {
		this._refreshItems(event);
		this._setHandleClassName();
		this.refreshPositions();
		return this;
	},

	_connectWith: function() {
		var options = this.options;
		return options.connectWith.constructor === String ? [options.connectWith] : options.connectWith;
	},

	_getItemsAsjQuery: function(connected) {

		var i, j, cur, inst,
			items = [],
			queries = [],
			connectWith = this._connectWith();

		if(connectWith && connected) {
			for (i = connectWith.length - 1; i >= 0; i--){
				cur = $(connectWith[i]);
				for ( j = cur.length - 1; j >= 0; j--){
					inst = $.data(cur[j], this.widgetFullName);
					if(inst && inst !== this && !inst.options.disabled) {
						queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), inst]);
					}
				}
			}
		}

		queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), this]);

		function addItems() {
			items.push( this );
		}
		for (i = queries.length - 1; i >= 0; i--){
			queries[i][0].each( addItems );
		}

		return $(items);

	},

	_removeCurrentsFromItems: function() {

		var list = this.currentItem.find(":data(" + this.widgetName + "-item)");

		this.items = $.grep(this.items, function (item) {
			for (var j=0; j < list.length; j++) {
				if(list[j] === item.item[0]) {
					return false;
				}
			}
			return true;
		});

	},

	_refreshItems: function(event) {

		this.items = [];
		this.containers = [this];

		var i, j, cur, inst, targetData, _queries, item, queriesLength,
			items = this.items,
			queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]],
			connectWith = this._connectWith();

		if(connectWith && this.ready) { //Shouldn't be run the first time through due to massive slow-down
			for (i = connectWith.length - 1; i >= 0; i--){
				cur = $(connectWith[i]);
				for (j = cur.length - 1; j >= 0; j--){
					inst = $.data(cur[j], this.widgetFullName);
					if(inst && inst !== this && !inst.options.disabled) {
						queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]);
						this.containers.push(inst);
					}
				}
			}
		}

		for (i = queries.length - 1; i >= 0; i--) {
			targetData = queries[i][1];
			_queries = queries[i][0];

			for (j=0, queriesLength = _queries.length; j < queriesLength; j++) {
				item = $(_queries[j]);

				item.data(this.widgetName + "-item", targetData); // Data for target checking (mouse manager)

				items.push({
					item: item,
					instance: targetData,
					width: 0, height: 0,
					left: 0, top: 0
				});
			}
		}

	},

	refreshPositions: function(fast) {

		//This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change
		if(this.offsetParent && this.helper) {
			this.offset.parent = this._getParentOffset();
		}

		var i, item, t, p;

		for (i = this.items.length - 1; i >= 0; i--){
			item = this.items[i];

			//We ignore calculating positions of all connected containers when we're not over them
			if(item.instance !== this.currentContainer && this.currentContainer && item.item[0] !== this.currentItem[0]) {
				continue;
			}

			t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item;

			if (!fast) {
				item.width = t.outerWidth();
				item.height = t.outerHeight();
			}

			p = t.offset();
			item.left = p.left;
			item.top = p.top;
		}

		if(this.options.custom && this.options.custom.refreshContainers) {
			this.options.custom.refreshContainers.call(this);
		} else {
			for (i = this.containers.length - 1; i >= 0; i--){
				p = this.containers[i].element.offset();
				this.containers[i].containerCache.left = p.left;
				this.containers[i].containerCache.top = p.top;
				this.containers[i].containerCache.width = this.containers[i].element.outerWidth();
				this.containers[i].containerCache.height = this.containers[i].element.outerHeight();
			}
		}

		return this;
	},

	_createPlaceholder: function(that) {
		that = that || this;
		var className,
			o = that.options;

		if(!o.placeholder || o.placeholder.constructor === String) {
			className = o.placeholder;
			o.placeholder = {
				element: function() {

					var nodeName = that.currentItem[0].nodeName.toLowerCase(),
						element = $( "<" + nodeName + ">", that.document[0] )
							.addClass(className || that.currentItem[0].className+" ui-sortable-placeholder")
							.removeClass("ui-sortable-helper");

					if ( nodeName === "tr" ) {
						that.currentItem.children().each(function() {
							$( "<td>&#160;</td>", that.document[0] )
								.attr( "colspan", $( this ).attr( "colspan" ) || 1 )
								.appendTo( element );
						});
					} else if ( nodeName === "img" ) {
						element.attr( "src", that.currentItem.attr( "src" ) );
					}

					if ( !className ) {
						element.css( "visibility", "hidden" );
					}

					return element;
				},
				update: function(container, p) {

					// 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that
					// 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified
					if(className && !o.forcePlaceholderSize) {
						return;
					}

					//If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item
					if(!p.height()) { p.height(that.currentItem.innerHeight() - parseInt(that.currentItem.css("paddingTop")||0, 10) - parseInt(that.currentItem.css("paddingBottom")||0, 10)); }
					if(!p.width()) { p.width(that.currentItem.innerWidth() - parseInt(that.currentItem.css("paddingLeft")||0, 10) - parseInt(that.currentItem.css("paddingRight")||0, 10)); }
				}
			};
		}

		//Create the placeholder
		that.placeholder = $(o.placeholder.element.call(that.element, that.currentItem));

		//Append it after the actual current item
		that.currentItem.after(that.placeholder);

		//Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)
		o.placeholder.update(that, that.placeholder);

	},

	_contactContainers: function(event) {
		var i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, cur, nearBottom, floating, axis,
			innermostContainer = null,
			innermostIndex = null;

		// get innermost container that intersects with item
		for (i = this.containers.length - 1; i >= 0; i--) {

			// never consider a container that's located within the item itself
			if($.contains(this.currentItem[0], this.containers[i].element[0])) {
				continue;
			}

			if(this._intersectsWith(this.containers[i].containerCache)) {

				// if we've already found a container and it's more "inner" than this, then continue
				if(innermostContainer && $.contains(this.containers[i].element[0], innermostContainer.element[0])) {
					continue;
				}

				innermostContainer = this.containers[i];
				innermostIndex = i;

			} else {
				// container doesn't intersect. trigger "out" event if necessary
				if(this.containers[i].containerCache.over) {
					this.containers[i]._trigger("out", event, this._uiHash(this));
					this.containers[i].containerCache.over = 0;
				}
			}

		}

		// if no intersecting containers found, return
		if(!innermostContainer) {
			return;
		}

		// move the item into the container if it's not there already
		if(this.containers.length === 1) {
			if (!this.containers[innermostIndex].containerCache.over) {
				this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
				this.containers[innermostIndex].containerCache.over = 1;
			}
		} else {

			//When entering a new container, we will find the item with the least distance and append our item near it
			dist = 10000;
			itemWithLeastDistance = null;
			floating = innermostContainer.floating || this._isFloating(this.currentItem);
			posProperty = floating ? "left" : "top";
			sizeProperty = floating ? "width" : "height";
			axis = floating ? "clientX" : "clientY";

			for (j = this.items.length - 1; j >= 0; j--) {
				if(!$.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) {
					continue;
				}
				if(this.items[j].item[0] === this.currentItem[0]) {
					continue;
				}

				cur = this.items[j].item.offset()[posProperty];
				nearBottom = false;
				if ( event[ axis ] - cur > this.items[ j ][ sizeProperty ] / 2 ) {
					nearBottom = true;
				}

				if ( Math.abs( event[ axis ] - cur ) < dist ) {
					dist = Math.abs( event[ axis ] - cur );
					itemWithLeastDistance = this.items[ j ];
					this.direction = nearBottom ? "up": "down";
				}
			}

			//Check if dropOnEmpty is enabled
			if(!itemWithLeastDistance && !this.options.dropOnEmpty) {
				return;
			}

			if(this.currentContainer === this.containers[innermostIndex]) {
				return;
			}

			itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true);
			this._trigger("change", event, this._uiHash());
			this.containers[innermostIndex]._trigger("change", event, this._uiHash(this));
			this.currentContainer = this.containers[innermostIndex];

			//Update the placeholder
			this.options.placeholder.update(this.currentContainer, this.placeholder);

			this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
			this.containers[innermostIndex].containerCache.over = 1;
		}


	},

	_createHelper: function(event) {

		var o = this.options,
			helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper === "clone" ? this.currentItem.clone() : this.currentItem);

		//Add the helper to the DOM if that didn't happen already
		if(!helper.parents("body").length) {
			$(o.appendTo !== "parent" ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]);
		}

		if(helper[0] === this.currentItem[0]) {
			this._storedCSS = { width: this.currentItem[0].style.width, height: this.currentItem[0].style.height, position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left") };
		}

		if(!helper[0].style.width || o.forceHelperSize) {
			helper.width(this.currentItem.width());
		}
		if(!helper[0].style.height || o.forceHelperSize) {
			helper.height(this.currentItem.height());
		}

		return helper;

	},

	_adjustOffsetFromHelper: function(obj) {
		if (typeof obj === "string") {
			obj = obj.split(" ");
		}
		if ($.isArray(obj)) {
			obj = {left: +obj[0], top: +obj[1] || 0};
		}
		if ("left" in obj) {
			this.offset.click.left = obj.left + this.margins.left;
		}
		if ("right" in obj) {
			this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
		}
		if ("top" in obj) {
			this.offset.click.top = obj.top + this.margins.top;
		}
		if ("bottom" in obj) {
			this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
		}
	},

	_getParentOffset: function() {


		//Get the offsetParent and cache its position
		this.offsetParent = this.helper.offsetParent();
		var po = this.offsetParent.offset();

		// This is a special case where we need to modify a offset calculated on start, since the following happened:
		// 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
		// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
		//    the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
		if(this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) {
			po.left += this.scrollParent.scrollLeft();
			po.top += this.scrollParent.scrollTop();
		}

		// This needs to be actually done for all browsers, since pageX/pageY includes this information
		// with an ugly IE fix
		if( this.offsetParent[0] === document.body || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) {
			po = { top: 0, left: 0 };
		}

		return {
			top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
			left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
		};

	},

	_getRelativeOffset: function() {

		if(this.cssPosition === "relative") {
			var p = this.currentItem.position();
			return {
				top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
				left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
			};
		} else {
			return { top: 0, left: 0 };
		}

	},

	_cacheMargins: function() {
		this.margins = {
			left: (parseInt(this.currentItem.css("marginLeft"),10) || 0),
			top: (parseInt(this.currentItem.css("marginTop"),10) || 0)
		};
	},

	_cacheHelperProportions: function() {
		this.helperProportions = {
			width: this.helper.outerWidth(),
			height: this.helper.outerHeight()
		};
	},

	_setContainment: function() {

		var ce, co, over,
			o = this.options;
		if(o.containment === "parent") {
			o.containment = this.helper[0].parentNode;
		}
		if(o.containment === "document" || o.containment === "window") {
			this.containment = [
				0 - this.offset.relative.left - this.offset.parent.left,
				0 - this.offset.relative.top - this.offset.parent.top,
				$(o.containment === "document" ? document : window).width() - this.helperProportions.width - this.margins.left,
				($(o.containment === "document" ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
			];
		}

		if(!(/^(document|window|parent)$/).test(o.containment)) {
			ce = $(o.containment)[0];
			co = $(o.containment).offset();
			over = ($(ce).css("overflow") !== "hidden");

			this.containment = [
				co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left,
				co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top,
				co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left,
				co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top
			];
		}

	},

	_convertPositionTo: function(d, pos) {

		if(!pos) {
			pos = this.position;
		}
		var mod = d === "absolute" ? 1 : -1,
			scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent,
			scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);

		return {
			top: (
				pos.top	+																// The absolute mouse position
				this.offset.relative.top * mod +										// Only for relative positioned nodes: Relative offset from element to offset parent
				this.offset.parent.top * mod -											// The offsetParent's offset without borders (offset + border)
				( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
			),
			left: (
				pos.left +																// The absolute mouse position
				this.offset.relative.left * mod +										// Only for relative positioned nodes: Relative offset from element to offset parent
				this.offset.parent.left * mod	-										// The offsetParent's offset without borders (offset + border)
				( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
			)
		};

	},

	_generatePosition: function(event) {

		var top, left,
			o = this.options,
			pageX = event.pageX,
			pageY = event.pageY,
			scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);

		// This is another very weird special case that only happens for relative elements:
		// 1. If the css position is relative
		// 2. and the scroll parent is the document or similar to the offset parent
		// we have to refresh the relative offset during the scroll so there are no jumps
		if(this.cssPosition === "relative" && !(this.scrollParent[0] !== document && this.scrollParent[0] !== this.offsetParent[0])) {
			this.offset.relative = this._getRelativeOffset();
		}

		/*
		 * - Position constraining -
		 * Constrain the position to a mix of grid, containment.
		 */

		if(this.originalPosition) { //If we are not dragging yet, we won't check for options

			if(this.containment) {
				if(event.pageX - this.offset.click.left < this.containment[0]) {
					pageX = this.containment[0] + this.offset.click.left;
				}
				if(event.pageY - this.offset.click.top < this.containment[1]) {
					pageY = this.containment[1] + this.offset.click.top;
				}
				if(event.pageX - this.offset.click.left > this.containment[2]) {
					pageX = this.containment[2] + this.offset.click.left;
				}
				if(event.pageY - this.offset.click.top > this.containment[3]) {
					pageY = this.containment[3] + this.offset.click.top;
				}
			}

			if(o.grid) {
				top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1];
				pageY = this.containment ? ( (top - this.offset.click.top >= this.containment[1] && top - this.offset.click.top <= this.containment[3]) ? top : ((top - this.offset.click.top >= this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;

				left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0];
				pageX = this.containment ? ( (left - this.offset.click.left >= this.containment[0] && left - this.offset.click.left <= this.containment[2]) ? left : ((left - this.offset.click.left >= this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
			}

		}

		return {
			top: (
				pageY -																// The absolute mouse position
				this.offset.click.top -													// Click offset (relative to the element)
				this.offset.relative.top	-											// Only for relative positioned nodes: Relative offset from element to offset parent
				this.offset.parent.top +												// The offsetParent's offset without borders (offset + border)
				( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
			),
			left: (
				pageX -																// The absolute mouse position
				this.offset.click.left -												// Click offset (relative to the element)
				this.offset.relative.left	-											// Only for relative positioned nodes: Relative offset from element to offset parent
				this.offset.parent.left +												// The offsetParent's offset without borders (offset + border)
				( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
			)
		};

	},

	_rearrange: function(event, i, a, hardRefresh) {

		a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction === "down" ? i.item[0] : i.item[0].nextSibling));

		//Various things done here to improve the performance:
		// 1. we create a setTimeout, that calls refreshPositions
		// 2. on the instance, we have a counter variable, that get's higher after every append
		// 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same
		// 4. this lets only the last addition to the timeout stack through
		this.counter = this.counter ? ++this.counter : 1;
		var counter = this.counter;

		this._delay(function() {
			if(counter === this.counter) {
				this.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove
			}
		});

	},

	_clear: function(event, noPropagation) {

		this.reverting = false;
		// We delay all events that have to be triggered to after the point where the placeholder has been removed and
		// everything else normalized again
		var i,
			delayedTriggers = [];

		// We first have to update the dom position of the actual currentItem
		// Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088)
		if(!this._noFinalSort && this.currentItem.parent().length) {
			this.placeholder.before(this.currentItem);
		}
		this._noFinalSort = null;

		if(this.helper[0] === this.currentItem[0]) {
			for(i in this._storedCSS) {
				if(this._storedCSS[i] === "auto" || this._storedCSS[i] === "static") {
					this._storedCSS[i] = "";
				}
			}
			this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
		} else {
			this.currentItem.show();
		}

		if(this.fromOutside && !noPropagation) {
			delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); });
		}
		if((this.fromOutside || this.domPosition.prev !== this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent !== this.currentItem.parent()[0]) && !noPropagation) {
			delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed
		}

		// Check if the items Container has Changed and trigger appropriate
		// events.
		if (this !== this.currentContainer) {
			if(!noPropagation) {
				delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); });
				delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); };  }).call(this, this.currentContainer));
				delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this));  }; }).call(this, this.currentContainer));
			}
		}


		//Post events to containers
		function delayEvent( type, instance, container ) {
			return function( event ) {
				container._trigger( type, event, instance._uiHash( instance ) );
			};
		}
		for (i = this.containers.length - 1; i >= 0; i--){
			if (!noPropagation) {
				delayedTriggers.push( delayEvent( "deactivate", this, this.containers[ i ] ) );
			}
			if(this.containers[i].containerCache.over) {
				delayedTriggers.push( delayEvent( "out", this, this.containers[ i ] ) );
				this.containers[i].containerCache.over = 0;
			}
		}

		//Do what was originally in plugins
		if ( this.storedCursor ) {
			this.document.find( "body" ).css( "cursor", this.storedCursor );
			this.storedStylesheet.remove();
		}
		if(this._storedOpacity) {
			this.helper.css("opacity", this._storedOpacity);
		}
		if(this._storedZIndex) {
			this.helper.css("zIndex", this._storedZIndex === "auto" ? "" : this._storedZIndex);
		}

		this.dragging = false;
		if(this.cancelHelperRemoval) {
			if(!noPropagation) {
				this._trigger("beforeStop", event, this._uiHash());
				for (i=0; i < delayedTriggers.length; i++) {
					delayedTriggers[i].call(this, event);
				} //Trigger all delayed events
				this._trigger("stop", event, this._uiHash());
			}

			this.fromOutside = false;
			return false;
		}

		if(!noPropagation) {
			this._trigger("beforeStop", event, this._uiHash());
		}

		//$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
		this.placeholder[0].parentNode.removeChild(this.placeholder[0]);

		if(this.helper[0] !== this.currentItem[0]) {
			this.helper.remove();
		}
		this.helper = null;

		if(!noPropagation) {
			for (i=0; i < delayedTriggers.length; i++) {
				delayedTriggers[i].call(this, event);
			} //Trigger all delayed events
			this._trigger("stop", event, this._uiHash());
		}

		this.fromOutside = false;
		return true;

	},

	_trigger: function() {
		if ($.Widget.prototype._trigger.apply(this, arguments) === false) {
			this.cancel();
		}
	},

	_uiHash: function(_inst) {
		var inst = _inst || this;
		return {
			helper: inst.helper,
			placeholder: inst.placeholder || $([]),
			position: inst.position,
			originalPosition: inst.originalPosition,
			offset: inst.positionAbs,
			item: inst.currentItem,
			sender: _inst ? _inst.element : null
		};
	}

});


/*!
 * jQuery UI Spinner 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/spinner/
 */


function spinner_modifier( fn ) {
	return function() {
		var previous = this.element.val();
		fn.apply( this, arguments );
		this._refresh();
		if ( previous !== this.element.val() ) {
			this._trigger( "change" );
		}
	};
}

var spinner = $.widget( "ui.spinner", {
	version: "1.11.1",
	defaultElement: "<input>",
	widgetEventPrefix: "spin",
	options: {
		culture: null,
		icons: {
			down: "ui-icon-triangle-1-s",
			up: "ui-icon-triangle-1-n"
		},
		incremental: true,
		max: null,
		min: null,
		numberFormat: null,
		page: 10,
		step: 1,

		change: null,
		spin: null,
		start: null,
		stop: null
	},

	_create: function() {
		// handle string values that need to be parsed
		this._setOption( "max", this.options.max );
		this._setOption( "min", this.options.min );
		this._setOption( "step", this.options.step );

		// Only format if there is a value, prevents the field from being marked
		// as invalid in Firefox, see #9573.
		if ( this.value() !== "" ) {
			// Format the value, but don't constrain.
			this._value( this.element.val(), true );
		}

		this._draw();
		this._on( this._events );
		this._refresh();

		// turning off autocomplete prevents the browser from remembering the
		// value when navigating through history, so we re-enable autocomplete
		// if the page is unloaded before the widget is destroyed. #7790
		this._on( this.window, {
			beforeunload: function() {
				this.element.removeAttr( "autocomplete" );
			}
		});
	},

	_getCreateOptions: function() {
		var options = {},
			element = this.element;

		$.each( [ "min", "max", "step" ], function( i, option ) {
			var value = element.attr( option );
			if ( value !== undefined && value.length ) {
				options[ option ] = value;
			}
		});

		return options;
	},

	_events: {
		keydown: function( event ) {
			if ( this._start( event ) && this._keydown( event ) ) {
				event.preventDefault();
			}
		},
		keyup: "_stop",
		focus: function() {
			this.previous = this.element.val();
		},
		blur: function( event ) {
			if ( this.cancelBlur ) {
				delete this.cancelBlur;
				return;
			}

			this._stop();
			this._refresh();
			if ( this.previous !== this.element.val() ) {
				this._trigger( "change", event );
			}
		},
		mousewheel: function( event, delta ) {
			if ( !delta ) {
				return;
			}
			if ( !this.spinning && !this._start( event ) ) {
				return false;
			}

			this._spin( (delta > 0 ? 1 : -1) * this.options.step, event );
			clearTimeout( this.mousewheelTimer );
			this.mousewheelTimer = this._delay(function() {
				if ( this.spinning ) {
					this._stop( event );
				}
			}, 100 );
			event.preventDefault();
		},
		"mousedown .ui-spinner-button": function( event ) {
			var previous;

			// We never want the buttons to have focus; whenever the user is
			// interacting with the spinner, the focus should be on the input.
			// If the input is focused then this.previous is properly set from
			// when the input first received focus. If the input is not focused
			// then we need to set this.previous based on the value before spinning.
			previous = this.element[0] === this.document[0].activeElement ?
				this.previous : this.element.val();
			function checkFocus() {
				var isActive = this.element[0] === this.document[0].activeElement;
				if ( !isActive ) {
					this.element.focus();
					this.previous = previous;
					// support: IE
					// IE sets focus asynchronously, so we need to check if focus
					// moved off of the input because the user clicked on the button.
					this._delay(function() {
						this.previous = previous;
					});
				}
			}

			// ensure focus is on (or stays on) the text field
			event.preventDefault();
			checkFocus.call( this );

			// support: IE
			// IE doesn't prevent moving focus even with event.preventDefault()
			// so we set a flag to know when we should ignore the blur event
			// and check (again) if focus moved off of the input.
			this.cancelBlur = true;
			this._delay(function() {
				delete this.cancelBlur;
				checkFocus.call( this );
			});

			if ( this._start( event ) === false ) {
				return;
			}

			this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event );
		},
		"mouseup .ui-spinner-button": "_stop",
		"mouseenter .ui-spinner-button": function( event ) {
			// button will add ui-state-active if mouse was down while mouseleave and kept down
			if ( !$( event.currentTarget ).hasClass( "ui-state-active" ) ) {
				return;
			}

			if ( this._start( event ) === false ) {
				return false;
			}
			this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event );
		},
		// TODO: do we really want to consider this a stop?
		// shouldn't we just stop the repeater and wait until mouseup before
		// we trigger the stop event?
		"mouseleave .ui-spinner-button": "_stop"
	},

	_draw: function() {
		var uiSpinner = this.uiSpinner = this.element
			.addClass( "ui-spinner-input" )
			.attr( "autocomplete", "off" )
			.wrap( this._uiSpinnerHtml() )
			.parent()
				// add buttons
				.append( this._buttonHtml() );

		this.element.attr( "role", "spinbutton" );

		// button bindings
		this.buttons = uiSpinner.find( ".ui-spinner-button" )
			.attr( "tabIndex", -1 )
			.button()
			.removeClass( "ui-corner-all" );

		// IE 6 doesn't understand height: 50% for the buttons
		// unless the wrapper has an explicit height
		if ( this.buttons.height() > Math.ceil( uiSpinner.height() * 0.5 ) &&
				uiSpinner.height() > 0 ) {
			uiSpinner.height( uiSpinner.height() );
		}

		// disable spinner if element was already disabled
		if ( this.options.disabled ) {
			this.disable();
		}
	},

	_keydown: function( event ) {
		var options = this.options,
			keyCode = $.ui.keyCode;

		switch ( event.keyCode ) {
		case keyCode.UP:
			this._repeat( null, 1, event );
			return true;
		case keyCode.DOWN:
			this._repeat( null, -1, event );
			return true;
		case keyCode.PAGE_UP:
			this._repeat( null, options.page, event );
			return true;
		case keyCode.PAGE_DOWN:
			this._repeat( null, -options.page, event );
			return true;
		}

		return false;
	},

	_uiSpinnerHtml: function() {
		return "<span class='ui-spinner ui-widget ui-widget-content ui-corner-all'></span>";
	},

	_buttonHtml: function() {
		return "" +
			"<a class='ui-spinner-button ui-spinner-up ui-corner-tr'>" +
				"<span class='ui-icon " + this.options.icons.up + "'>&#9650;</span>" +
			"</a>" +
			"<a class='ui-spinner-button ui-spinner-down ui-corner-br'>" +
				"<span class='ui-icon " + this.options.icons.down + "'>&#9660;</span>" +
			"</a>";
	},

	_start: function( event ) {
		if ( !this.spinning && this._trigger( "start", event ) === false ) {
			return false;
		}

		if ( !this.counter ) {
			this.counter = 1;
		}
		this.spinning = true;
		return true;
	},

	_repeat: function( i, steps, event ) {
		i = i || 500;

		clearTimeout( this.timer );
		this.timer = this._delay(function() {
			this._repeat( 40, steps, event );
		}, i );

		this._spin( steps * this.options.step, event );
	},

	_spin: function( step, event ) {
		var value = this.value() || 0;

		if ( !this.counter ) {
			this.counter = 1;
		}

		value = this._adjustValue( value + step * this._increment( this.counter ) );

		if ( !this.spinning || this._trigger( "spin", event, { value: value } ) !== false) {
			this._value( value );
			this.counter++;
		}
	},

	_increment: function( i ) {
		var incremental = this.options.incremental;

		if ( incremental ) {
			return $.isFunction( incremental ) ?
				incremental( i ) :
				Math.floor( i * i * i / 50000 - i * i / 500 + 17 * i / 200 + 1 );
		}

		return 1;
	},

	_precision: function() {
		var precision = this._precisionOf( this.options.step );
		if ( this.options.min !== null ) {
			precision = Math.max( precision, this._precisionOf( this.options.min ) );
		}
		return precision;
	},

	_precisionOf: function( num ) {
		var str = num.toString(),
			decimal = str.indexOf( "." );
		return decimal === -1 ? 0 : str.length - decimal - 1;
	},

	_adjustValue: function( value ) {
		var base, aboveMin,
			options = this.options;

		// make sure we're at a valid step
		// - find out where we are relative to the base (min or 0)
		base = options.min !== null ? options.min : 0;
		aboveMin = value - base;
		// - round to the nearest step
		aboveMin = Math.round(aboveMin / options.step) * options.step;
		// - rounding is based on 0, so adjust back to our base
		value = base + aboveMin;

		// fix precision from bad JS floating point math
		value = parseFloat( value.toFixed( this._precision() ) );

		// clamp the value
		if ( options.max !== null && value > options.max) {
			return options.max;
		}
		if ( options.min !== null && value < options.min ) {
			return options.min;
		}

		return value;
	},

	_stop: function( event ) {
		if ( !this.spinning ) {
			return;
		}

		clearTimeout( this.timer );
		clearTimeout( this.mousewheelTimer );
		this.counter = 0;
		this.spinning = false;
		this._trigger( "stop", event );
	},

	_setOption: function( key, value ) {
		if ( key === "culture" || key === "numberFormat" ) {
			var prevValue = this._parse( this.element.val() );
			this.options[ key ] = value;
			this.element.val( this._format( prevValue ) );
			return;
		}

		if ( key === "max" || key === "min" || key === "step" ) {
			if ( typeof value === "string" ) {
				value = this._parse( value );
			}
		}
		if ( key === "icons" ) {
			this.buttons.first().find( ".ui-icon" )
				.removeClass( this.options.icons.up )
				.addClass( value.up );
			this.buttons.last().find( ".ui-icon" )
				.removeClass( this.options.icons.down )
				.addClass( value.down );
		}

		this._super( key, value );

		if ( key === "disabled" ) {
			this.widget().toggleClass( "ui-state-disabled", !!value );
			this.element.prop( "disabled", !!value );
			this.buttons.button( value ? "disable" : "enable" );
		}
	},

	_setOptions: spinner_modifier(function( options ) {
		this._super( options );
	}),

	_parse: function( val ) {
		if ( typeof val === "string" && val !== "" ) {
			val = window.Globalize && this.options.numberFormat ?
				Globalize.parseFloat( val, 10, this.options.culture ) : +val;
		}
		return val === "" || isNaN( val ) ? null : val;
	},

	_format: function( value ) {
		if ( value === "" ) {
			return "";
		}
		return window.Globalize && this.options.numberFormat ?
			Globalize.format( value, this.options.numberFormat, this.options.culture ) :
			value;
	},

	_refresh: function() {
		this.element.attr({
			"aria-valuemin": this.options.min,
			"aria-valuemax": this.options.max,
			// TODO: what should we do with values that can't be parsed?
			"aria-valuenow": this._parse( this.element.val() )
		});
	},

	isValid: function() {
		var value = this.value();

		// null is invalid
		if ( value === null ) {
			return false;
		}

		// if value gets adjusted, it's invalid
		return value === this._adjustValue( value );
	},

	// update the value without triggering change
	_value: function( value, allowAny ) {
		var parsed;
		if ( value !== "" ) {
			parsed = this._parse( value );
			if ( parsed !== null ) {
				if ( !allowAny ) {
					parsed = this._adjustValue( parsed );
				}
				value = this._format( parsed );
			}
		}
		this.element.val( value );
		this._refresh();
	},

	_destroy: function() {
		this.element
			.removeClass( "ui-spinner-input" )
			.prop( "disabled", false )
			.removeAttr( "autocomplete" )
			.removeAttr( "role" )
			.removeAttr( "aria-valuemin" )
			.removeAttr( "aria-valuemax" )
			.removeAttr( "aria-valuenow" );
		this.uiSpinner.replaceWith( this.element );
	},

	stepUp: spinner_modifier(function( steps ) {
		this._stepUp( steps );
	}),
	_stepUp: function( steps ) {
		if ( this._start() ) {
			this._spin( (steps || 1) * this.options.step );
			this._stop();
		}
	},

	stepDown: spinner_modifier(function( steps ) {
		this._stepDown( steps );
	}),
	_stepDown: function( steps ) {
		if ( this._start() ) {
			this._spin( (steps || 1) * -this.options.step );
			this._stop();
		}
	},

	pageUp: spinner_modifier(function( pages ) {
		this._stepUp( (pages || 1) * this.options.page );
	}),

	pageDown: spinner_modifier(function( pages ) {
		this._stepDown( (pages || 1) * this.options.page );
	}),

	value: function( newVal ) {
		if ( !arguments.length ) {
			return this._parse( this.element.val() );
		}
		spinner_modifier( this._value ).call( this, newVal );
	},

	widget: function() {
		return this.uiSpinner;
	}
});


/*!
 * jQuery UI Tabs 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/tabs/
 */


var tabs = $.widget( "ui.tabs", {
	version: "1.11.1",
	delay: 300,
	options: {
		active: null,
		collapsible: false,
		event: "click",
		heightStyle: "content",
		hide: null,
		show: null,

		// callbacks
		activate: null,
		beforeActivate: null,
		beforeLoad: null,
		load: null
	},

	_isLocal: (function() {
		var rhash = /#.*$/;

		return function( anchor ) {
			var anchorUrl, locationUrl;

			// support: IE7
			// IE7 doesn't normalize the href property when set via script (#9317)
			anchor = anchor.cloneNode( false );

			anchorUrl = anchor.href.replace( rhash, "" );
			locationUrl = location.href.replace( rhash, "" );

			// decoding may throw an error if the URL isn't UTF-8 (#9518)
			try {
				anchorUrl = decodeURIComponent( anchorUrl );
			} catch ( error ) {}
			try {
				locationUrl = decodeURIComponent( locationUrl );
			} catch ( error ) {}

			return anchor.hash.length > 1 && anchorUrl === locationUrl;
		};
	})(),

	_create: function() {
		var that = this,
			options = this.options;

		this.running = false;

		this.element
			.addClass( "ui-tabs ui-widget ui-widget-content ui-corner-all" )
			.toggleClass( "ui-tabs-collapsible", options.collapsible );

		this._processTabs();
		options.active = this._initialActive();

		// Take disabling tabs via class attribute from HTML
		// into account and update option properly.
		if ( $.isArray( options.disabled ) ) {
			options.disabled = $.unique( options.disabled.concat(
				$.map( this.tabs.filter( ".ui-state-disabled" ), function( li ) {
					return that.tabs.index( li );
				})
			) ).sort();
		}

		// check for length avoids error when initializing empty list
		if ( this.options.active !== false && this.anchors.length ) {
			this.active = this._findActive( options.active );
		} else {
			this.active = $();
		}

		this._refresh();

		if ( this.active.length ) {
			this.load( options.active );
		}
	},

	_initialActive: function() {
		var active = this.options.active,
			collapsible = this.options.collapsible,
			locationHash = location.hash.substring( 1 );

		if ( active === null ) {
			// check the fragment identifier in the URL
			if ( locationHash ) {
				this.tabs.each(function( i, tab ) {
					if ( $( tab ).attr( "aria-controls" ) === locationHash ) {
						active = i;
						return false;
					}
				});
			}

			// check for a tab marked active via a class
			if ( active === null ) {
				active = this.tabs.index( this.tabs.filter( ".ui-tabs-active" ) );
			}

			// no active tab, set to false
			if ( active === null || active === -1 ) {
				active = this.tabs.length ? 0 : false;
			}
		}

		// handle numbers: negative, out of range
		if ( active !== false ) {
			active = this.tabs.index( this.tabs.eq( active ) );
			if ( active === -1 ) {
				active = collapsible ? false : 0;
			}
		}

		// don't allow collapsible: false and active: false
		if ( !collapsible && active === false && this.anchors.length ) {
			active = 0;
		}

		return active;
	},

	_getCreateEventData: function() {
		return {
			tab: this.active,
			panel: !this.active.length ? $() : this._getPanelForTab( this.active )
		};
	},

	_tabKeydown: function( event ) {
		var focusedTab = $( this.document[0].activeElement ).closest( "li" ),
			selectedIndex = this.tabs.index( focusedTab ),
			goingForward = true;

		if ( this._handlePageNav( event ) ) {
			return;
		}

		switch ( event.keyCode ) {
			case $.ui.keyCode.RIGHT:
			case $.ui.keyCode.DOWN:
				selectedIndex++;
				break;
			case $.ui.keyCode.UP:
			case $.ui.keyCode.LEFT:
				goingForward = false;
				selectedIndex--;
				break;
			case $.ui.keyCode.END:
				selectedIndex = this.anchors.length - 1;
				break;
			case $.ui.keyCode.HOME:
				selectedIndex = 0;
				break;
			case $.ui.keyCode.SPACE:
				// Activate only, no collapsing
				event.preventDefault();
				clearTimeout( this.activating );
				this._activate( selectedIndex );
				return;
			case $.ui.keyCode.ENTER:
				// Toggle (cancel delayed activation, allow collapsing)
				event.preventDefault();
				clearTimeout( this.activating );
				// Determine if we should collapse or activate
				this._activate( selectedIndex === this.options.active ? false : selectedIndex );
				return;
			default:
				return;
		}

		// Focus the appropriate tab, based on which key was pressed
		event.preventDefault();
		clearTimeout( this.activating );
		selectedIndex = this._focusNextTab( selectedIndex, goingForward );

		// Navigating with control key will prevent automatic activation
		if ( !event.ctrlKey ) {
			// Update aria-selected immediately so that AT think the tab is already selected.
			// Otherwise AT may confuse the user by stating that they need to activate the tab,
			// but the tab will already be activated by the time the announcement finishes.
			focusedTab.attr( "aria-selected", "false" );
			this.tabs.eq( selectedIndex ).attr( "aria-selected", "true" );

			this.activating = this._delay(function() {
				this.option( "active", selectedIndex );
			}, this.delay );
		}
	},

	_panelKeydown: function( event ) {
		if ( this._handlePageNav( event ) ) {
			return;
		}

		// Ctrl+up moves focus to the current tab
		if ( event.ctrlKey && event.keyCode === $.ui.keyCode.UP ) {
			event.preventDefault();
			this.active.focus();
		}
	},

	// Alt+page up/down moves focus to the previous/next tab (and activates)
	_handlePageNav: function( event ) {
		if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_UP ) {
			this._activate( this._focusNextTab( this.options.active - 1, false ) );
			return true;
		}
		if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_DOWN ) {
			this._activate( this._focusNextTab( this.options.active + 1, true ) );
			return true;
		}
	},

	_findNextTab: function( index, goingForward ) {
		var lastTabIndex = this.tabs.length - 1;

		function constrain() {
			if ( index > lastTabIndex ) {
				index = 0;
			}
			if ( index < 0 ) {
				index = lastTabIndex;
			}
			return index;
		}

		while ( $.inArray( constrain(), this.options.disabled ) !== -1 ) {
			index = goingForward ? index + 1 : index - 1;
		}

		return index;
	},

	_focusNextTab: function( index, goingForward ) {
		index = this._findNextTab( index, goingForward );
		this.tabs.eq( index ).focus();
		return index;
	},

	_setOption: function( key, value ) {
		if ( key === "active" ) {
			// _activate() will handle invalid values and update this.options
			this._activate( value );
			return;
		}

		if ( key === "disabled" ) {
			// don't use the widget factory's disabled handling
			this._setupDisabled( value );
			return;
		}

		this._super( key, value);

		if ( key === "collapsible" ) {
			this.element.toggleClass( "ui-tabs-collapsible", value );
			// Setting collapsible: false while collapsed; open first panel
			if ( !value && this.options.active === false ) {
				this._activate( 0 );
			}
		}

		if ( key === "event" ) {
			this._setupEvents( value );
		}

		if ( key === "heightStyle" ) {
			this._setupHeightStyle( value );
		}
	},

	_sanitizeSelector: function( hash ) {
		return hash ? hash.replace( /[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g, "\\$&" ) : "";
	},

	refresh: function() {
		var options = this.options,
			lis = this.tablist.children( ":has(a[href])" );

		// get disabled tabs from class attribute from HTML
		// this will get converted to a boolean if needed in _refresh()
		options.disabled = $.map( lis.filter( ".ui-state-disabled" ), function( tab ) {
			return lis.index( tab );
		});

		this._processTabs();

		// was collapsed or no tabs
		if ( options.active === false || !this.anchors.length ) {
			options.active = false;
			this.active = $();
		// was active, but active tab is gone
		} else if ( this.active.length && !$.contains( this.tablist[ 0 ], this.active[ 0 ] ) ) {
			// all remaining tabs are disabled
			if ( this.tabs.length === options.disabled.length ) {
				options.active = false;
				this.active = $();
			// activate previous tab
			} else {
				this._activate( this._findNextTab( Math.max( 0, options.active - 1 ), false ) );
			}
		// was active, active tab still exists
		} else {
			// make sure active index is correct
			options.active = this.tabs.index( this.active );
		}

		this._refresh();
	},

	_refresh: function() {
		this._setupDisabled( this.options.disabled );
		this._setupEvents( this.options.event );
		this._setupHeightStyle( this.options.heightStyle );

		this.tabs.not( this.active ).attr({
			"aria-selected": "false",
			"aria-expanded": "false",
			tabIndex: -1
		});
		this.panels.not( this._getPanelForTab( this.active ) )
			.hide()
			.attr({
				"aria-hidden": "true"
			});

		// Make sure one tab is in the tab order
		if ( !this.active.length ) {
			this.tabs.eq( 0 ).attr( "tabIndex", 0 );
		} else {
			this.active
				.addClass( "ui-tabs-active ui-state-active" )
				.attr({
					"aria-selected": "true",
					"aria-expanded": "true",
					tabIndex: 0
				});
			this._getPanelForTab( this.active )
				.show()
				.attr({
					"aria-hidden": "false"
				});
		}
	},

	_processTabs: function() {
		var that = this;

		this.tablist = this._getList()
			.addClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
			.attr( "role", "tablist" )

			// Prevent users from focusing disabled tabs via click
			.delegate( "> li", "mousedown" + this.eventNamespace, function( event ) {
				if ( $( this ).is( ".ui-state-disabled" ) ) {
					event.preventDefault();
				}
			})

			// support: IE <9
			// Preventing the default action in mousedown doesn't prevent IE
			// from focusing the element, so if the anchor gets focused, blur.
			// We don't have to worry about focusing the previously focused
			// element since clicking on a non-focusable element should focus
			// the body anyway.
			.delegate( ".ui-tabs-anchor", "focus" + this.eventNamespace, function() {
				if ( $( this ).closest( "li" ).is( ".ui-state-disabled" ) ) {
					this.blur();
				}
			});

		this.tabs = this.tablist.find( "> li:has(a[href])" )
			.addClass( "ui-state-default ui-corner-top" )
			.attr({
				role: "tab",
				tabIndex: -1
			});

		this.anchors = this.tabs.map(function() {
				return $( "a", this )[ 0 ];
			})
			.addClass( "ui-tabs-anchor" )
			.attr({
				role: "presentation",
				tabIndex: -1
			});

		this.panels = $();

		this.anchors.each(function( i, anchor ) {
			var selector, panel, panelId,
				anchorId = $( anchor ).uniqueId().attr( "id" ),
				tab = $( anchor ).closest( "li" ),
				originalAriaControls = tab.attr( "aria-controls" );

			// inline tab
			if ( that._isLocal( anchor ) ) {
				selector = anchor.hash;
				panelId = selector.substring( 1 );
				panel = that.element.find( that._sanitizeSelector( selector ) );
			// remote tab
			} else {
				// If the tab doesn't already have aria-controls,
				// generate an id by using a throw-away element
				panelId = tab.attr( "aria-controls" ) || $( {} ).uniqueId()[ 0 ].id;
				selector = "#" + panelId;
				panel = that.element.find( selector );
				if ( !panel.length ) {
					panel = that._createPanel( panelId );
					panel.insertAfter( that.panels[ i - 1 ] || that.tablist );
				}
				panel.attr( "aria-live", "polite" );
			}

			if ( panel.length) {
				that.panels = that.panels.add( panel );
			}
			if ( originalAriaControls ) {
				tab.data( "ui-tabs-aria-controls", originalAriaControls );
			}
			tab.attr({
				"aria-controls": panelId,
				"aria-labelledby": anchorId
			});
			panel.attr( "aria-labelledby", anchorId );
		});

		this.panels
			.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
			.attr( "role", "tabpanel" );
	},

	// allow overriding how to find the list for rare usage scenarios (#7715)
	_getList: function() {
		return this.tablist || this.element.find( "ol,ul" ).eq( 0 );
	},

	_createPanel: function( id ) {
		return $( "<div>" )
			.attr( "id", id )
			.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
			.data( "ui-tabs-destroy", true );
	},

	_setupDisabled: function( disabled ) {
		if ( $.isArray( disabled ) ) {
			if ( !disabled.length ) {
				disabled = false;
			} else if ( disabled.length === this.anchors.length ) {
				disabled = true;
			}
		}

		// disable tabs
		for ( var i = 0, li; ( li = this.tabs[ i ] ); i++ ) {
			if ( disabled === true || $.inArray( i, disabled ) !== -1 ) {
				$( li )
					.addClass( "ui-state-disabled" )
					.attr( "aria-disabled", "true" );
			} else {
				$( li )
					.removeClass( "ui-state-disabled" )
					.removeAttr( "aria-disabled" );
			}
		}

		this.options.disabled = disabled;
	},

	_setupEvents: function( event ) {
		var events = {};
		if ( event ) {
			$.each( event.split(" "), function( index, eventName ) {
				events[ eventName ] = "_eventHandler";
			});
		}

		this._off( this.anchors.add( this.tabs ).add( this.panels ) );
		// Always prevent the default action, even when disabled
		this._on( true, this.anchors, {
			click: function( event ) {
				event.preventDefault();
			}
		});
		this._on( this.anchors, events );
		this._on( this.tabs, { keydown: "_tabKeydown" } );
		this._on( this.panels, { keydown: "_panelKeydown" } );

		this._focusable( this.tabs );
		this._hoverable( this.tabs );
	},

	_setupHeightStyle: function( heightStyle ) {
		var maxHeight,
			parent = this.element.parent();

		if ( heightStyle === "fill" ) {
			maxHeight = parent.height();
			maxHeight -= this.element.outerHeight() - this.element.height();

			this.element.siblings( ":visible" ).each(function() {
				var elem = $( this ),
					position = elem.css( "position" );

				if ( position === "absolute" || position === "fixed" ) {
					return;
				}
				maxHeight -= elem.outerHeight( true );
			});

			this.element.children().not( this.panels ).each(function() {
				maxHeight -= $( this ).outerHeight( true );
			});

			this.panels.each(function() {
				$( this ).height( Math.max( 0, maxHeight -
					$( this ).innerHeight() + $( this ).height() ) );
			})
			.css( "overflow", "auto" );
		} else if ( heightStyle === "auto" ) {
			maxHeight = 0;
			this.panels.each(function() {
				maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() );
			}).height( maxHeight );
		}
	},

	_eventHandler: function( event ) {
		var options = this.options,
			active = this.active,
			anchor = $( event.currentTarget ),
			tab = anchor.closest( "li" ),
			clickedIsActive = tab[ 0 ] === active[ 0 ],
			collapsing = clickedIsActive && options.collapsible,
			toShow = collapsing ? $() : this._getPanelForTab( tab ),
			toHide = !active.length ? $() : this._getPanelForTab( active ),
			eventData = {
				oldTab: active,
				oldPanel: toHide,
				newTab: collapsing ? $() : tab,
				newPanel: toShow
			};

		event.preventDefault();

		if ( tab.hasClass( "ui-state-disabled" ) ||
				// tab is already loading
				tab.hasClass( "ui-tabs-loading" ) ||
				// can't switch durning an animation
				this.running ||
				// click on active header, but not collapsible
				( clickedIsActive && !options.collapsible ) ||
				// allow canceling activation
				( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
			return;
		}

		options.active = collapsing ? false : this.tabs.index( tab );

		this.active = clickedIsActive ? $() : tab;
		if ( this.xhr ) {
			this.xhr.abort();
		}

		if ( !toHide.length && !toShow.length ) {
			$.error( "jQuery UI Tabs: Mismatching fragment identifier." );
		}

		if ( toShow.length ) {
			this.load( this.tabs.index( tab ), event );
		}
		this._toggle( event, eventData );
	},

	// handles show/hide for selecting tabs
	_toggle: function( event, eventData ) {
		var that = this,
			toShow = eventData.newPanel,
			toHide = eventData.oldPanel;

		this.running = true;

		function complete() {
			that.running = false;
			that._trigger( "activate", event, eventData );
		}

		function show() {
			eventData.newTab.closest( "li" ).addClass( "ui-tabs-active ui-state-active" );

			if ( toShow.length && that.options.show ) {
				that._show( toShow, that.options.show, complete );
			} else {
				toShow.show();
				complete();
			}
		}

		// start out by hiding, then showing, then completing
		if ( toHide.length && this.options.hide ) {
			this._hide( toHide, this.options.hide, function() {
				eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
				show();
			});
		} else {
			eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
			toHide.hide();
			show();
		}

		toHide.attr( "aria-hidden", "true" );
		eventData.oldTab.attr({
			"aria-selected": "false",
			"aria-expanded": "false"
		});
		// If we're switching tabs, remove the old tab from the tab order.
		// If we're opening from collapsed state, remove the previous tab from the tab order.
		// If we're collapsing, then keep the collapsing tab in the tab order.
		if ( toShow.length && toHide.length ) {
			eventData.oldTab.attr( "tabIndex", -1 );
		} else if ( toShow.length ) {
			this.tabs.filter(function() {
				return $( this ).attr( "tabIndex" ) === 0;
			})
			.attr( "tabIndex", -1 );
		}

		toShow.attr( "aria-hidden", "false" );
		eventData.newTab.attr({
			"aria-selected": "true",
			"aria-expanded": "true",
			tabIndex: 0
		});
	},

	_activate: function( index ) {
		var anchor,
			active = this._findActive( index );

		// trying to activate the already active panel
		if ( active[ 0 ] === this.active[ 0 ] ) {
			return;
		}

		// trying to collapse, simulate a click on the current active header
		if ( !active.length ) {
			active = this.active;
		}

		anchor = active.find( ".ui-tabs-anchor" )[ 0 ];
		this._eventHandler({
			target: anchor,
			currentTarget: anchor,
			preventDefault: $.noop
		});
	},

	_findActive: function( index ) {
		return index === false ? $() : this.tabs.eq( index );
	},

	_getIndex: function( index ) {
		// meta-function to give users option to provide a href string instead of a numerical index.
		if ( typeof index === "string" ) {
			index = this.anchors.index( this.anchors.filter( "[href$='" + index + "']" ) );
		}

		return index;
	},

	_destroy: function() {
		if ( this.xhr ) {
			this.xhr.abort();
		}

		this.element.removeClass( "ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible" );

		this.tablist
			.removeClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
			.removeAttr( "role" );

		this.anchors
			.removeClass( "ui-tabs-anchor" )
			.removeAttr( "role" )
			.removeAttr( "tabIndex" )
			.removeUniqueId();

		this.tablist.unbind( this.eventNamespace );

		this.tabs.add( this.panels ).each(function() {
			if ( $.data( this, "ui-tabs-destroy" ) ) {
				$( this ).remove();
			} else {
				$( this )
					.removeClass( "ui-state-default ui-state-active ui-state-disabled " +
						"ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel" )
					.removeAttr( "tabIndex" )
					.removeAttr( "aria-live" )
					.removeAttr( "aria-busy" )
					.removeAttr( "aria-selected" )
					.removeAttr( "aria-labelledby" )
					.removeAttr( "aria-hidden" )
					.removeAttr( "aria-expanded" )
					.removeAttr( "role" );
			}
		});

		this.tabs.each(function() {
			var li = $( this ),
				prev = li.data( "ui-tabs-aria-controls" );
			if ( prev ) {
				li
					.attr( "aria-controls", prev )
					.removeData( "ui-tabs-aria-controls" );
			} else {
				li.removeAttr( "aria-controls" );
			}
		});

		this.panels.show();

		if ( this.options.heightStyle !== "content" ) {
			this.panels.css( "height", "" );
		}
	},

	enable: function( index ) {
		var disabled = this.options.disabled;
		if ( disabled === false ) {
			return;
		}

		if ( index === undefined ) {
			disabled = false;
		} else {
			index = this._getIndex( index );
			if ( $.isArray( disabled ) ) {
				disabled = $.map( disabled, function( num ) {
					return num !== index ? num : null;
				});
			} else {
				disabled = $.map( this.tabs, function( li, num ) {
					return num !== index ? num : null;
				});
			}
		}
		this._setupDisabled( disabled );
	},

	disable: function( index ) {
		var disabled = this.options.disabled;
		if ( disabled === true ) {
			return;
		}

		if ( index === undefined ) {
			disabled = true;
		} else {
			index = this._getIndex( index );
			if ( $.inArray( index, disabled ) !== -1 ) {
				return;
			}
			if ( $.isArray( disabled ) ) {
				disabled = $.merge( [ index ], disabled ).sort();
			} else {
				disabled = [ index ];
			}
		}
		this._setupDisabled( disabled );
	},

	load: function( index, event ) {
		index = this._getIndex( index );
		var that = this,
			tab = this.tabs.eq( index ),
			anchor = tab.find( ".ui-tabs-anchor" ),
			panel = this._getPanelForTab( tab ),
			eventData = {
				tab: tab,
				panel: panel
			};

		// not remote
		if ( this._isLocal( anchor[ 0 ] ) ) {
			return;
		}

		this.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) );

		// support: jQuery <1.8
		// jQuery <1.8 returns false if the request is canceled in beforeSend,
		// but as of 1.8, $.ajax() always returns a jqXHR object.
		if ( this.xhr && this.xhr.statusText !== "canceled" ) {
			tab.addClass( "ui-tabs-loading" );
			panel.attr( "aria-busy", "true" );

			this.xhr
				.success(function( response ) {
					// support: jQuery <1.8
					// http://bugs.jquery.com/ticket/11778
					setTimeout(function() {
						panel.html( response );
						that._trigger( "load", event, eventData );
					}, 1 );
				})
				.complete(function( jqXHR, status ) {
					// support: jQuery <1.8
					// http://bugs.jquery.com/ticket/11778
					setTimeout(function() {
						if ( status === "abort" ) {
							that.panels.stop( false, true );
						}

						tab.removeClass( "ui-tabs-loading" );
						panel.removeAttr( "aria-busy" );

						if ( jqXHR === that.xhr ) {
							delete that.xhr;
						}
					}, 1 );
				});
		}
	},

	_ajaxSettings: function( anchor, event, eventData ) {
		var that = this;
		return {
			url: anchor.attr( "href" ),
			beforeSend: function( jqXHR, settings ) {
				return that._trigger( "beforeLoad", event,
					$.extend( { jqXHR: jqXHR, ajaxSettings: settings }, eventData ) );
			}
		};
	},

	_getPanelForTab: function( tab ) {
		var id = $( tab ).attr( "aria-controls" );
		return this.element.find( this._sanitizeSelector( "#" + id ) );
	}
});


/*!
 * jQuery UI Tooltip 1.11.1
 * http://jqueryui.com
 *
 * Copyright 2014 jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/tooltip/
 */


var tooltip = $.widget( "ui.tooltip", {
	version: "1.11.1",
	options: {
		content: function() {
			// support: IE<9, Opera in jQuery <1.7
			// .text() can't accept undefined, so coerce to a string
			var title = $( this ).attr( "title" ) || "";
			// Escape title, since we're going from an attribute to raw HTML
			return $( "<a>" ).text( title ).html();
		},
		hide: true,
		// Disabled elements have inconsistent behavior across browsers (#8661)
		items: "[title]:not([disabled])",
		position: {
			my: "left top+15",
			at: "left bottom",
			collision: "flipfit flip"
		},
		show: true,
		tooltipClass: null,
		track: false,

		// callbacks
		close: null,
		open: null
	},

	_addDescribedBy: function( elem, id ) {
		var describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ );
		describedby.push( id );
		elem
			.data( "ui-tooltip-id", id )
			.attr( "aria-describedby", $.trim( describedby.join( " " ) ) );
	},

	_removeDescribedBy: function( elem ) {
		var id = elem.data( "ui-tooltip-id" ),
			describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ ),
			index = $.inArray( id, describedby );

		if ( index !== -1 ) {
			describedby.splice( index, 1 );
		}

		elem.removeData( "ui-tooltip-id" );
		describedby = $.trim( describedby.join( " " ) );
		if ( describedby ) {
			elem.attr( "aria-describedby", describedby );
		} else {
			elem.removeAttr( "aria-describedby" );
		}
	},

	_create: function() {
		this._on({
			mouseover: "open",
			focusin: "open"
		});

		// IDs of generated tooltips, needed for destroy
		this.tooltips = {};
		// IDs of parent tooltips where we removed the title attribute
		this.parents = {};

		if ( this.options.disabled ) {
			this._disable();
		}

		// Append the aria-live region so tooltips announce correctly
		this.liveRegion = $( "<div>" )
			.attr({
				role: "log",
				"aria-live": "assertive",
				"aria-relevant": "additions"
			})
			.addClass( "ui-helper-hidden-accessible" )
			.appendTo( this.document[ 0 ].body );
	},

	_setOption: function( key, value ) {
		var that = this;

		if ( key === "disabled" ) {
			this[ value ? "_disable" : "_enable" ]();
			this.options[ key ] = value;
			// disable element style changes
			return;
		}

		this._super( key, value );

		if ( key === "content" ) {
			$.each( this.tooltips, function( id, element ) {
				that._updateContent( element );
			});
		}
	},

	_disable: function() {
		var that = this;

		// close open tooltips
		$.each( this.tooltips, function( id, element ) {
			var event = $.Event( "blur" );
			event.target = event.currentTarget = element[0];
			that.close( event, true );
		});

		// remove title attributes to prevent native tooltips
		this.element.find( this.options.items ).addBack().each(function() {
			var element = $( this );
			if ( element.is( "[title]" ) ) {
				element
					.data( "ui-tooltip-title", element.attr( "title" ) )
					.removeAttr( "title" );
			}
		});
	},

	_enable: function() {
		// restore title attributes
		this.element.find( this.options.items ).addBack().each(function() {
			var element = $( this );
			if ( element.data( "ui-tooltip-title" ) ) {
				element.attr( "title", element.data( "ui-tooltip-title" ) );
			}
		});
	},

	open: function( event ) {
		var that = this,
			target = $( event ? event.target : this.element )
				// we need closest here due to mouseover bubbling,
				// but always pointing at the same event target
				.closest( this.options.items );

		// No element to show a tooltip for or the tooltip is already open
		if ( !target.length || target.data( "ui-tooltip-id" ) ) {
			return;
		}

		if ( target.attr( "title" ) ) {
			target.data( "ui-tooltip-title", target.attr( "title" ) );
		}

		target.data( "ui-tooltip-open", true );

		// kill parent tooltips, custom or native, for hover
		if ( event && event.type === "mouseover" ) {
			target.parents().each(function() {
				var parent = $( this ),
					blurEvent;
				if ( parent.data( "ui-tooltip-open" ) ) {
					blurEvent = $.Event( "blur" );
					blurEvent.target = blurEvent.currentTarget = this;
					that.close( blurEvent, true );
				}
				if ( parent.attr( "title" ) ) {
					parent.uniqueId();
					that.parents[ this.id ] = {
						element: this,
						title: parent.attr( "title" )
					};
					parent.attr( "title", "" );
				}
			});
		}

		this._updateContent( target, event );
	},

	_updateContent: function( target, event ) {
		var content,
			contentOption = this.options.content,
			that = this,
			eventType = event ? event.type : null;

		if ( typeof contentOption === "string" ) {
			return this._open( event, target, contentOption );
		}

		content = contentOption.call( target[0], function( response ) {
			// ignore async response if tooltip was closed already
			if ( !target.data( "ui-tooltip-open" ) ) {
				return;
			}
			// IE may instantly serve a cached response for ajax requests
			// delay this call to _open so the other call to _open runs first
			that._delay(function() {
				// jQuery creates a special event for focusin when it doesn't
				// exist natively. To improve performance, the native event
				// object is reused and the type is changed. Therefore, we can't
				// rely on the type being correct after the event finished
				// bubbling, so we set it back to the previous value. (#8740)
				if ( event ) {
					event.type = eventType;
				}
				this._open( event, target, response );
			});
		});
		if ( content ) {
			this._open( event, target, content );
		}
	},

	_open: function( event, target, content ) {
		var tooltip, events, delayedShow, a11yContent,
			positionOption = $.extend( {}, this.options.position );

		if ( !content ) {
			return;
		}

		// Content can be updated multiple times. If the tooltip already
		// exists, then just update the content and bail.
		tooltip = this._find( target );
		if ( tooltip.length ) {
			tooltip.find( ".ui-tooltip-content" ).html( content );
			return;
		}

		// if we have a title, clear it to prevent the native tooltip
		// we have to check first to avoid defining a title if none exists
		// (we don't want to cause an element to start matching [title])
		//
		// We use removeAttr only for key events, to allow IE to export the correct
		// accessible attributes. For mouse events, set to empty string to avoid
		// native tooltip showing up (happens only when removing inside mouseover).
		if ( target.is( "[title]" ) ) {
			if ( event && event.type === "mouseover" ) {
				target.attr( "title", "" );
			} else {
				target.removeAttr( "title" );
			}
		}

		tooltip = this._tooltip( target );
		this._addDescribedBy( target, tooltip.attr( "id" ) );
		tooltip.find( ".ui-tooltip-content" ).html( content );

		// Support: Voiceover on OS X, JAWS on IE <= 9
		// JAWS announces deletions even when aria-relevant="additions"
		// Voiceover will sometimes re-read the entire log region's contents from the beginning
		this.liveRegion.children().hide();
		if ( content.clone ) {
			a11yContent = content.clone();
			a11yContent.removeAttr( "id" ).find( "[id]" ).removeAttr( "id" );
		} else {
			a11yContent = content;
		}
		$( "<div>" ).html( a11yContent ).appendTo( this.liveRegion );

		function position( event ) {
			positionOption.of = event;
			if ( tooltip.is( ":hidden" ) ) {
				return;
			}
			tooltip.position( positionOption );
		}
		if ( this.options.track && event && /^mouse/.test( event.type ) ) {
			this._on( this.document, {
				mousemove: position
			});
			// trigger once to override element-relative positioning
			position( event );
		} else {
			tooltip.position( $.extend({
				of: target
			}, this.options.position ) );
		}

		this.hiding = false;
		this.closing = false;
		tooltip.hide();

		this._show( tooltip, this.options.show );
		// Handle tracking tooltips that are shown with a delay (#8644). As soon
		// as the tooltip is visible, position the tooltip using the most recent
		// event.
		if ( this.options.show && this.options.show.delay ) {
			delayedShow = this.delayedShow = setInterval(function() {
				if ( tooltip.is( ":visible" ) ) {
					position( positionOption.of );
					clearInterval( delayedShow );
				}
			}, $.fx.interval );
		}

		this._trigger( "open", event, { tooltip: tooltip } );

		events = {
			keyup: function( event ) {
				if ( event.keyCode === $.ui.keyCode.ESCAPE ) {
					var fakeEvent = $.Event(event);
					fakeEvent.currentTarget = target[0];
					this.close( fakeEvent, true );
				}
			}
		};

		// Only bind remove handler for delegated targets. Non-delegated
		// tooltips will handle this in destroy.
		if ( target[ 0 ] !== this.element[ 0 ] ) {
			events.remove = function() {
				this._removeTooltip( tooltip );
			};
		}

		if ( !event || event.type === "mouseover" ) {
			events.mouseleave = "close";
		}
		if ( !event || event.type === "focusin" ) {
			events.focusout = "close";
		}
		this._on( true, target, events );
	},

	close: function( event ) {
		var that = this,
			target = $( event ? event.currentTarget : this.element ),
			tooltip = this._find( target );

		// disabling closes the tooltip, so we need to track when we're closing
		// to avoid an infinite loop in case the tooltip becomes disabled on close
		if ( this.closing ) {
			return;
		}

		// Clear the interval for delayed tracking tooltips
		clearInterval( this.delayedShow );

		// only set title if we had one before (see comment in _open())
		// If the title attribute has changed since open(), don't restore
		if ( target.data( "ui-tooltip-title" ) && !target.attr( "title" ) ) {
			target.attr( "title", target.data( "ui-tooltip-title" ) );
		}

		this._removeDescribedBy( target );

		this.hiding = true;
		tooltip.stop( true );
		this._hide( tooltip, this.options.hide, function() {
			that._removeTooltip( $( this ) );
			this.hiding = false;
			this.closing = false;
		});

		target.removeData( "ui-tooltip-open" );
		this._off( target, "mouseleave focusout keyup" );

		// Remove 'remove' binding only on delegated targets
		if ( target[ 0 ] !== this.element[ 0 ] ) {
			this._off( target, "remove" );
		}
		this._off( this.document, "mousemove" );

		if ( event && event.type === "mouseleave" ) {
			$.each( this.parents, function( id, parent ) {
				$( parent.element ).attr( "title", parent.title );
				delete that.parents[ id ];
			});
		}

		this.closing = true;
		this._trigger( "close", event, { tooltip: tooltip } );
		if ( !this.hiding ) {
			this.closing = false;
		}
	},

	_tooltip: function( element ) {
		var tooltip = $( "<div>" )
				.attr( "role", "tooltip" )
				.addClass( "ui-tooltip ui-widget ui-corner-all ui-widget-content " +
					( this.options.tooltipClass || "" ) ),
			id = tooltip.uniqueId().attr( "id" );

		$( "<div>" )
			.addClass( "ui-tooltip-content" )
			.appendTo( tooltip );

		tooltip.appendTo( this.document[0].body );
		this.tooltips[ id ] = element;
		return tooltip;
	},

	_find: function( target ) {
		var id = target.data( "ui-tooltip-id" );
		return id ? $( "#" + id ) : $();
	},

	_removeTooltip: function( tooltip ) {
		tooltip.remove();
		delete this.tooltips[ tooltip.attr( "id" ) ];
	},

	_destroy: function() {
		var that = this;

		// close open tooltips
		$.each( this.tooltips, function( id, element ) {
			// Delegate to close method to handle common cleanup
			var event = $.Event( "blur" );
			event.target = event.currentTarget = element[0];
			that.close( event, true );

			// Remove immediately; destroying an open tooltip doesn't use the
			// hide animation
			$( "#" + id ).remove();

			// Restore the title
			if ( element.data( "ui-tooltip-title" ) ) {
				// If the title attribute has changed since open(), don't restore
				if ( !element.attr( "title" ) ) {
					element.attr( "title", element.data( "ui-tooltip-title" ) );
				}
				element.removeData( "ui-tooltip-title" );
			}
		});
		this.liveRegion.remove();
	}
});



}));;
/*!

 handlebars v1.3.0

Copyright (C) 2011 by Yehuda Katz

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

@license
*/
/* exported Handlebars */
var Handlebars = (function() {
// handlebars/safe-string.js
var __module4__ = (function() {
  "use strict";
  var __exports__;
  // Build out our basic SafeString type
  function SafeString(string) {
    this.string = string;
  }

  SafeString.prototype.toString = function() {
    return "" + this.string;
  };

  __exports__ = SafeString;
  return __exports__;
})();

// handlebars/utils.js
var __module3__ = (function(__dependency1__) {
  "use strict";
  var __exports__ = {};
  /*jshint -W004 */
  var SafeString = __dependency1__;

  var escape = {
    "&": "&amp;",
    "<": "&lt;",
    ">": "&gt;",
    '"': "&quot;",
    "'": "&#x27;",
    "`": "&#x60;"
  };

  var badChars = /[&<>"'`]/g;
  var possible = /[&<>"'`]/;

  function escapeChar(chr) {
    return escape[chr] || "&amp;";
  }

  function extend(obj, value) {
    for(var key in value) {
      if(Object.prototype.hasOwnProperty.call(value, key)) {
        obj[key] = value[key];
      }
    }
  }

  __exports__.extend = extend;var toString = Object.prototype.toString;
  __exports__.toString = toString;
  // Sourced from lodash
  // https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
  var isFunction = function(value) {
    return typeof value === 'function';
  };
  // fallback for older versions of Chrome and Safari
  if (isFunction(/x/)) {
    isFunction = function(value) {
      return typeof value === 'function' && toString.call(value) === '[object Function]';
    };
  }
  var isFunction;
  __exports__.isFunction = isFunction;
  var isArray = Array.isArray || function(value) {
    return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false;
  };
  __exports__.isArray = isArray;

  function escapeExpression(string) {
    // don't escape SafeStrings, since they're already safe
    if (string instanceof SafeString) {
      return string.toString();
    } else if (!string && string !== 0) {
      return "";
    }

    // Force a string conversion as this will be done by the append regardless and
    // the regex test will do this transparently behind the scenes, causing issues if
    // an object's to string has escaped characters in it.
    string = "" + string;

    if(!possible.test(string)) { return string; }
    return string.replace(badChars, escapeChar);
  }

  __exports__.escapeExpression = escapeExpression;function isEmpty(value) {
    if (!value && value !== 0) {
      return true;
    } else if (isArray(value) && value.length === 0) {
      return true;
    } else {
      return false;
    }
  }

  __exports__.isEmpty = isEmpty;
  return __exports__;
})(__module4__);

// handlebars/exception.js
var __module5__ = (function() {
  "use strict";
  var __exports__;

  var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];

  function Exception(message, node) {
    var line;
    if (node && node.firstLine) {
      line = node.firstLine;

      message += ' - ' + line + ':' + node.firstColumn;
    }

    var tmp = Error.prototype.constructor.call(this, message);

    // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
    for (var idx = 0; idx < errorProps.length; idx++) {
      this[errorProps[idx]] = tmp[errorProps[idx]];
    }

    if (line) {
      this.lineNumber = line;
      this.column = node.firstColumn;
    }
  }

  Exception.prototype = new Error();

  __exports__ = Exception;
  return __exports__;
})();

// handlebars/base.js
var __module2__ = (function(__dependency1__, __dependency2__) {
  "use strict";
  var __exports__ = {};
  var Utils = __dependency1__;
  var Exception = __dependency2__;

  var VERSION = "1.3.0";
  __exports__.VERSION = VERSION;var COMPILER_REVISION = 4;
  __exports__.COMPILER_REVISION = COMPILER_REVISION;
  var REVISION_CHANGES = {
    1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
    2: '== 1.0.0-rc.3',
    3: '== 1.0.0-rc.4',
    4: '>= 1.0.0'
  };
  __exports__.REVISION_CHANGES = REVISION_CHANGES;
  var isArray = Utils.isArray,
      isFunction = Utils.isFunction,
      toString = Utils.toString,
      objectType = '[object Object]';

  function HandlebarsEnvironment(helpers, partials) {
    this.helpers = helpers || {};
    this.partials = partials || {};

    registerDefaultHelpers(this);
  }

  __exports__.HandlebarsEnvironment = HandlebarsEnvironment;HandlebarsEnvironment.prototype = {
    constructor: HandlebarsEnvironment,

    logger: logger,
    log: log,

    registerHelper: function(name, fn, inverse) {
      if (toString.call(name) === objectType) {
        if (inverse || fn) { throw new Exception('Arg not supported with multiple helpers'); }
        Utils.extend(this.helpers, name);
      } else {
        if (inverse) { fn.not = inverse; }
        this.helpers[name] = fn;
      }
    },

    registerPartial: function(name, str) {
      if (toString.call(name) === objectType) {
        Utils.extend(this.partials,  name);
      } else {
        this.partials[name] = str;
      }
    }
  };

  function registerDefaultHelpers(instance) {
    instance.registerHelper('helperMissing', function(arg) {
      if(arguments.length === 2) {
        return undefined;
      } else {
        throw new Exception("Missing helper: '" + arg + "'");
      }
    });

    instance.registerHelper('blockHelperMissing', function(context, options) {
      var inverse = options.inverse || function() {}, fn = options.fn;

      if (isFunction(context)) { context = context.call(this); }

      if(context === true) {
        return fn(this);
      } else if(context === false || context == null) {
        return inverse(this);
      } else if (isArray(context)) {
        if(context.length > 0) {
          return instance.helpers.each(context, options);
        } else {
          return inverse(this);
        }
      } else {
        return fn(context);
      }
    });

    instance.registerHelper('each', function(context, options) {
      var fn = options.fn, inverse = options.inverse;
      var i = 0, ret = "", data;

      if (isFunction(context)) { context = context.call(this); }

      if (options.data) {
        data = createFrame(options.data);
      }

      if(context && typeof context === 'object') {
        if (isArray(context)) {
          for(var j = context.length; i<j; i++) {
            if (data) {
              data.index = i;
              data.first = (i === 0);
              data.last  = (i === (context.length-1));
            }
            ret = ret + fn(context[i], { data: data });
          }
        } else {
          for(var key in context) {
            if(context.hasOwnProperty(key)) {
              if(data) { 
                data.key = key; 
                data.index = i;
                data.first = (i === 0);
              }
              ret = ret + fn(context[key], {data: data});
              i++;
            }
          }
        }
      }

      if(i === 0){
        ret = inverse(this);
      }

      return ret;
    });

    instance.registerHelper('if', function(conditional, options) {
      if (isFunction(conditional)) { conditional = conditional.call(this); }

      // Default behavior is to render the positive path if the value is truthy and not empty.
      // The `includeZero` option may be set to treat the condtional as purely not empty based on the
      // behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative.
      if ((!options.hash.includeZero && !conditional) || Utils.isEmpty(conditional)) {
        return options.inverse(this);
      } else {
        return options.fn(this);
      }
    });

    instance.registerHelper('unless', function(conditional, options) {
      return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn, hash: options.hash});
    });

    instance.registerHelper('with', function(context, options) {
      if (isFunction(context)) { context = context.call(this); }

      if (!Utils.isEmpty(context)) return options.fn(context);
    });

    instance.registerHelper('log', function(context, options) {
      var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
      instance.log(level, context);
    });
  }

  var logger = {
    methodMap: { 0: 'debug', 1: 'info', 2: 'warn', 3: 'error' },

    // State enum
    DEBUG: 0,
    INFO: 1,
    WARN: 2,
    ERROR: 3,
    level: 3,

    // can be overridden in the host environment
    log: function(level, obj) {
      if (logger.level <= level) {
        var method = logger.methodMap[level];
        if (typeof console !== 'undefined' && console[method]) {
          console[method].call(console, obj);
        }
      }
    }
  };
  __exports__.logger = logger;
  function log(level, obj) { logger.log(level, obj); }

  __exports__.log = log;var createFrame = function(object) {
    var obj = {};
    Utils.extend(obj, object);
    return obj;
  };
  __exports__.createFrame = createFrame;
  return __exports__;
})(__module3__, __module5__);

// handlebars/runtime.js
var __module6__ = (function(__dependency1__, __dependency2__, __dependency3__) {
  "use strict";
  var __exports__ = {};
  var Utils = __dependency1__;
  var Exception = __dependency2__;
  var COMPILER_REVISION = __dependency3__.COMPILER_REVISION;
  var REVISION_CHANGES = __dependency3__.REVISION_CHANGES;

  function checkRevision(compilerInfo) {
    var compilerRevision = compilerInfo && compilerInfo[0] || 1,
        currentRevision = COMPILER_REVISION;

    if (compilerRevision !== currentRevision) {
      if (compilerRevision < currentRevision) {
        var runtimeVersions = REVISION_CHANGES[currentRevision],
            compilerVersions = REVISION_CHANGES[compilerRevision];
        throw new Exception("Template was precompiled with an older version of Handlebars than the current runtime. "+
              "Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").");
      } else {
        // Use the embedded version info since the runtime doesn't know about this revision yet
        throw new Exception("Template was precompiled with a newer version of Handlebars than the current runtime. "+
              "Please update your runtime to a newer version ("+compilerInfo[1]+").");
      }
    }
  }

  __exports__.checkRevision = checkRevision;// TODO: Remove this line and break up compilePartial

  function template(templateSpec, env) {
    if (!env) {
      throw new Exception("No environment passed to template");
    }

    // Note: Using env.VM references rather than local var references throughout this section to allow
    // for external users to override these as psuedo-supported APIs.
    var invokePartialWrapper = function(partial, name, context, helpers, partials, data) {
      var result = env.VM.invokePartial.apply(this, arguments);
      if (result != null) { return result; }

      if (env.compile) {
        var options = { helpers: helpers, partials: partials, data: data };
        partials[name] = env.compile(partial, { data: data !== undefined }, env);
        return partials[name](context, options);
      } else {
        throw new Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
      }
    };

    // Just add water
    var container = {
      escapeExpression: Utils.escapeExpression,
      invokePartial: invokePartialWrapper,
      programs: [],
      program: function(i, fn, data) {
        var programWrapper = this.programs[i];
        if(data) {
          programWrapper = program(i, fn, data);
        } else if (!programWrapper) {
          programWrapper = this.programs[i] = program(i, fn);
        }
        return programWrapper;
      },
      merge: function(param, common) {
        var ret = param || common;

        if (param && common && (param !== common)) {
          ret = {};
          Utils.extend(ret, common);
          Utils.extend(ret, param);
        }
        return ret;
      },
      programWithDepth: env.VM.programWithDepth,
      noop: env.VM.noop,
      compilerInfo: null
    };

    return function(context, options) {
      options = options || {};
      var namespace = options.partial ? options : env,
          helpers,
          partials;

      if (!options.partial) {
        helpers = options.helpers;
        partials = options.partials;
      }
      var result = templateSpec.call(
            container,
            namespace, context,
            helpers,
            partials,
            options.data);

      if (!options.partial) {
        env.VM.checkRevision(container.compilerInfo);
      }

      return result;
    };
  }

  __exports__.template = template;function programWithDepth(i, fn, data /*, $depth */) {
    var args = Array.prototype.slice.call(arguments, 3);

    var prog = function(context, options) {
      options = options || {};

      return fn.apply(this, [context, options.data || data].concat(args));
    };
    prog.program = i;
    prog.depth = args.length;
    return prog;
  }

  __exports__.programWithDepth = programWithDepth;function program(i, fn, data) {
    var prog = function(context, options) {
      options = options || {};

      return fn(context, options.data || data);
    };
    prog.program = i;
    prog.depth = 0;
    return prog;
  }

  __exports__.program = program;function invokePartial(partial, name, context, helpers, partials, data) {
    var options = { partial: true, helpers: helpers, partials: partials, data: data };

    if(partial === undefined) {
      throw new Exception("The partial " + name + " could not be found");
    } else if(partial instanceof Function) {
      return partial(context, options);
    }
  }

  __exports__.invokePartial = invokePartial;function noop() { return ""; }

  __exports__.noop = noop;
  return __exports__;
})(__module3__, __module5__, __module2__);

// handlebars.runtime.js
var __module1__ = (function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__) {
  "use strict";
  var __exports__;
  /*globals Handlebars: true */
  var base = __dependency1__;

  // Each of these augment the Handlebars object. No need to setup here.
  // (This is done to easily share code between commonjs and browse envs)
  var SafeString = __dependency2__;
  var Exception = __dependency3__;
  var Utils = __dependency4__;
  var runtime = __dependency5__;

  // For compatibility and usage outside of module systems, make the Handlebars object a namespace
  var create = function() {
    var hb = new base.HandlebarsEnvironment();

    Utils.extend(hb, base);
    hb.SafeString = SafeString;
    hb.Exception = Exception;
    hb.Utils = Utils;

    hb.VM = runtime;
    hb.template = function(spec) {
      return runtime.template(spec, hb);
    };

    return hb;
  };

  var Handlebars = create();
  Handlebars.create = create;

  __exports__ = Handlebars;
  return __exports__;
})(__module2__, __module4__, __module5__, __module3__, __module6__);

// handlebars/compiler/ast.js
var __module7__ = (function(__dependency1__) {
  "use strict";
  var __exports__;
  var Exception = __dependency1__;

  function LocationInfo(locInfo){
    locInfo = locInfo || {};
    this.firstLine   = locInfo.first_line;
    this.firstColumn = locInfo.first_column;
    this.lastColumn  = locInfo.last_column;
    this.lastLine    = locInfo.last_line;
  }

  var AST = {
    ProgramNode: function(statements, inverseStrip, inverse, locInfo) {
      var inverseLocationInfo, firstInverseNode;
      if (arguments.length === 3) {
        locInfo = inverse;
        inverse = null;
      } else if (arguments.length === 2) {
        locInfo = inverseStrip;
        inverseStrip = null;
      }

      LocationInfo.call(this, locInfo);
      this.type = "program";
      this.statements = statements;
      this.strip = {};

      if(inverse) {
        firstInverseNode = inverse[0];
        if (firstInverseNode) {
          inverseLocationInfo = {
            first_line: firstInverseNode.firstLine,
            last_line: firstInverseNode.lastLine,
            last_column: firstInverseNode.lastColumn,
            first_column: firstInverseNode.firstColumn
          };
          this.inverse = new AST.ProgramNode(inverse, inverseStrip, inverseLocationInfo);
        } else {
          this.inverse = new AST.ProgramNode(inverse, inverseStrip);
        }
        this.strip.right = inverseStrip.left;
      } else if (inverseStrip) {
        this.strip.left = inverseStrip.right;
      }
    },

    MustacheNode: function(rawParams, hash, open, strip, locInfo) {
      LocationInfo.call(this, locInfo);
      this.type = "mustache";
      this.strip = strip;

      // Open may be a string parsed from the parser or a passed boolean flag
      if (open != null && open.charAt) {
        // Must use charAt to support IE pre-10
        var escapeFlag = open.charAt(3) || open.charAt(2);
        this.escaped = escapeFlag !== '{' && escapeFlag !== '&';
      } else {
        this.escaped = !!open;
      }

      if (rawParams instanceof AST.SexprNode) {
        this.sexpr = rawParams;
      } else {
        // Support old AST API
        this.sexpr = new AST.SexprNode(rawParams, hash);
      }

      this.sexpr.isRoot = true;

      // Support old AST API that stored this info in MustacheNode
      this.id = this.sexpr.id;
      this.params = this.sexpr.params;
      this.hash = this.sexpr.hash;
      this.eligibleHelper = this.sexpr.eligibleHelper;
      this.isHelper = this.sexpr.isHelper;
    },

    SexprNode: function(rawParams, hash, locInfo) {
      LocationInfo.call(this, locInfo);

      this.type = "sexpr";
      this.hash = hash;

      var id = this.id = rawParams[0];
      var params = this.params = rawParams.slice(1);

      // a mustache is an eligible helper if:
      // * its id is simple (a single part, not `this` or `..`)
      var eligibleHelper = this.eligibleHelper = id.isSimple;

      // a mustache is definitely a helper if:
      // * it is an eligible helper, and
      // * it has at least one parameter or hash segment
      this.isHelper = eligibleHelper && (params.length || hash);

      // if a mustache is an eligible helper but not a definite
      // helper, it is ambiguous, and will be resolved in a later
      // pass or at runtime.
    },

    PartialNode: function(partialName, context, strip, locInfo) {
      LocationInfo.call(this, locInfo);
      this.type         = "partial";
      this.partialName  = partialName;
      this.context      = context;
      this.strip = strip;
    },

    BlockNode: function(mustache, program, inverse, close, locInfo) {
      LocationInfo.call(this, locInfo);

      if(mustache.sexpr.id.original !== close.path.original) {
        throw new Exception(mustache.sexpr.id.original + " doesn't match " + close.path.original, this);
      }

      this.type = 'block';
      this.mustache = mustache;
      this.program  = program;
      this.inverse  = inverse;

      this.strip = {
        left: mustache.strip.left,
        right: close.strip.right
      };

      (program || inverse).strip.left = mustache.strip.right;
      (inverse || program).strip.right = close.strip.left;

      if (inverse && !program) {
        this.isInverse = true;
      }
    },

    ContentNode: function(string, locInfo) {
      LocationInfo.call(this, locInfo);
      this.type = "content";
      this.string = string;
    },

    HashNode: function(pairs, locInfo) {
      LocationInfo.call(this, locInfo);
      this.type = "hash";
      this.pairs = pairs;
    },

    IdNode: function(parts, locInfo) {
      LocationInfo.call(this, locInfo);
      this.type = "ID";

      var original = "",
          dig = [],
          depth = 0;

      for(var i=0,l=parts.length; i<l; i++) {
        var part = parts[i].part;
        original += (parts[i].separator || '') + part;

        if (part === ".." || part === "." || part === "this") {
          if (dig.length > 0) {
            throw new Exception("Invalid path: " + original, this);
          } else if (part === "..") {
            depth++;
          } else {
            this.isScoped = true;
          }
        } else {
          dig.push(part);
        }
      }

      this.original = original;
      this.parts    = dig;
      this.string   = dig.join('.');
      this.depth    = depth;

      // an ID is simple if it only has one part, and that part is not
      // `..` or `this`.
      this.isSimple = parts.length === 1 && !this.isScoped && depth === 0;

      this.stringModeValue = this.string;
    },

    PartialNameNode: function(name, locInfo) {
      LocationInfo.call(this, locInfo);
      this.type = "PARTIAL_NAME";
      this.name = name.original;
    },

    DataNode: function(id, locInfo) {
      LocationInfo.call(this, locInfo);
      this.type = "DATA";
      this.id = id;
    },

    StringNode: function(string, locInfo) {
      LocationInfo.call(this, locInfo);
      this.type = "STRING";
      this.original =
        this.string =
        this.stringModeValue = string;
    },

    IntegerNode: function(integer, locInfo) {
      LocationInfo.call(this, locInfo);
      this.type = "INTEGER";
      this.original =
        this.integer = integer;
      this.stringModeValue = Number(integer);
    },

    BooleanNode: function(bool, locInfo) {
      LocationInfo.call(this, locInfo);
      this.type = "BOOLEAN";
      this.bool = bool;
      this.stringModeValue = bool === "true";
    },

    CommentNode: function(comment, locInfo) {
      LocationInfo.call(this, locInfo);
      this.type = "comment";
      this.comment = comment;
    }
  };

  // Must be exported as an object rather than the root of the module as the jison lexer
  // most modify the object to operate properly.
  __exports__ = AST;
  return __exports__;
})(__module5__);

// handlebars/compiler/parser.js
var __module9__ = (function() {
  "use strict";
  var __exports__;
  /* jshint ignore:start */
  /* Jison generated parser */
  var handlebars = (function(){
  var parser = {trace: function trace() { },
  yy: {},
  symbols_: {"error":2,"root":3,"statements":4,"EOF":5,"program":6,"simpleInverse":7,"statement":8,"openInverse":9,"closeBlock":10,"openBlock":11,"mustache":12,"partial":13,"CONTENT":14,"COMMENT":15,"OPEN_BLOCK":16,"sexpr":17,"CLOSE":18,"OPEN_INVERSE":19,"OPEN_ENDBLOCK":20,"path":21,"OPEN":22,"OPEN_UNESCAPED":23,"CLOSE_UNESCAPED":24,"OPEN_PARTIAL":25,"partialName":26,"partial_option0":27,"sexpr_repetition0":28,"sexpr_option0":29,"dataName":30,"param":31,"STRING":32,"INTEGER":33,"BOOLEAN":34,"OPEN_SEXPR":35,"CLOSE_SEXPR":36,"hash":37,"hash_repetition_plus0":38,"hashSegment":39,"ID":40,"EQUALS":41,"DATA":42,"pathSegments":43,"SEP":44,"$accept":0,"$end":1},
  terminals_: {2:"error",5:"EOF",14:"CONTENT",15:"COMMENT",16:"OPEN_BLOCK",18:"CLOSE",19:"OPEN_INVERSE",20:"OPEN_ENDBLOCK",22:"OPEN",23:"OPEN_UNESCAPED",24:"CLOSE_UNESCAPED",25:"OPEN_PARTIAL",32:"STRING",33:"INTEGER",34:"BOOLEAN",35:"OPEN_SEXPR",36:"CLOSE_SEXPR",40:"ID",41:"EQUALS",42:"DATA",44:"SEP"},
  productions_: [0,[3,2],[3,1],[6,2],[6,3],[6,2],[6,1],[6,1],[6,0],[4,1],[4,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,4],[7,2],[17,3],[17,1],[31,1],[31,1],[31,1],[31,1],[31,1],[31,3],[37,1],[39,3],[26,1],[26,1],[26,1],[30,2],[21,1],[43,3],[43,1],[27,0],[27,1],[28,0],[28,2],[29,0],[29,1],[38,1],[38,2]],
  performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) {

  var $0 = $$.length - 1;
  switch (yystate) {
  case 1: return new yy.ProgramNode($$[$0-1], this._$); 
  break;
  case 2: return new yy.ProgramNode([], this._$); 
  break;
  case 3:this.$ = new yy.ProgramNode([], $$[$0-1], $$[$0], this._$);
  break;
  case 4:this.$ = new yy.ProgramNode($$[$0-2], $$[$0-1], $$[$0], this._$);
  break;
  case 5:this.$ = new yy.ProgramNode($$[$0-1], $$[$0], [], this._$);
  break;
  case 6:this.$ = new yy.ProgramNode($$[$0], this._$);
  break;
  case 7:this.$ = new yy.ProgramNode([], this._$);
  break;
  case 8:this.$ = new yy.ProgramNode([], this._$);
  break;
  case 9:this.$ = [$$[$0]];
  break;
  case 10: $$[$0-1].push($$[$0]); this.$ = $$[$0-1]; 
  break;
  case 11:this.$ = new yy.BlockNode($$[$0-2], $$[$0-1].inverse, $$[$0-1], $$[$0], this._$);
  break;
  case 12:this.$ = new yy.BlockNode($$[$0-2], $$[$0-1], $$[$0-1].inverse, $$[$0], this._$);
  break;
  case 13:this.$ = $$[$0];
  break;
  case 14:this.$ = $$[$0];
  break;
  case 15:this.$ = new yy.ContentNode($$[$0], this._$);
  break;
  case 16:this.$ = new yy.CommentNode($$[$0], this._$);
  break;
  case 17:this.$ = new yy.MustacheNode($$[$0-1], null, $$[$0-2], stripFlags($$[$0-2], $$[$0]), this._$);
  break;
  case 18:this.$ = new yy.MustacheNode($$[$0-1], null, $$[$0-2], stripFlags($$[$0-2], $$[$0]), this._$);
  break;
  case 19:this.$ = {path: $$[$0-1], strip: stripFlags($$[$0-2], $$[$0])};
  break;
  case 20:this.$ = new yy.MustacheNode($$[$0-1], null, $$[$0-2], stripFlags($$[$0-2], $$[$0]), this._$);
  break;
  case 21:this.$ = new yy.MustacheNode($$[$0-1], null, $$[$0-2], stripFlags($$[$0-2], $$[$0]), this._$);
  break;
  case 22:this.$ = new yy.PartialNode($$[$0-2], $$[$0-1], stripFlags($$[$0-3], $$[$0]), this._$);
  break;
  case 23:this.$ = stripFlags($$[$0-1], $$[$0]);
  break;
  case 24:this.$ = new yy.SexprNode([$$[$0-2]].concat($$[$0-1]), $$[$0], this._$);
  break;
  case 25:this.$ = new yy.SexprNode([$$[$0]], null, this._$);
  break;
  case 26:this.$ = $$[$0];
  break;
  case 27:this.$ = new yy.StringNode($$[$0], this._$);
  break;
  case 28:this.$ = new yy.IntegerNode($$[$0], this._$);
  break;
  case 29:this.$ = new yy.BooleanNode($$[$0], this._$);
  break;
  case 30:this.$ = $$[$0];
  break;
  case 31:$$[$0-1].isHelper = true; this.$ = $$[$0-1];
  break;
  case 32:this.$ = new yy.HashNode($$[$0], this._$);
  break;
  case 33:this.$ = [$$[$0-2], $$[$0]];
  break;
  case 34:this.$ = new yy.PartialNameNode($$[$0], this._$);
  break;
  case 35:this.$ = new yy.PartialNameNode(new yy.StringNode($$[$0], this._$), this._$);
  break;
  case 36:this.$ = new yy.PartialNameNode(new yy.IntegerNode($$[$0], this._$));
  break;
  case 37:this.$ = new yy.DataNode($$[$0], this._$);
  break;
  case 38:this.$ = new yy.IdNode($$[$0], this._$);
  break;
  case 39: $$[$0-2].push({part: $$[$0], separator: $$[$0-1]}); this.$ = $$[$0-2]; 
  break;
  case 40:this.$ = [{part: $$[$0]}];
  break;
  case 43:this.$ = [];
  break;
  case 44:$$[$0-1].push($$[$0]);
  break;
  case 47:this.$ = [$$[$0]];
  break;
  case 48:$$[$0-1].push($$[$0]);
  break;
  }
  },
  table: [{3:1,4:2,5:[1,3],8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],22:[1,13],23:[1,14],25:[1,15]},{1:[3]},{5:[1,16],8:17,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],22:[1,13],23:[1,14],25:[1,15]},{1:[2,2]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],25:[2,9]},{4:20,6:18,7:19,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,21],20:[2,8],22:[1,13],23:[1,14],25:[1,15]},{4:20,6:22,7:19,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,21],20:[2,8],22:[1,13],23:[1,14],25:[1,15]},{5:[2,13],14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],25:[2,13]},{5:[2,14],14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],25:[2,14]},{5:[2,15],14:[2,15],15:[2,15],16:[2,15],19:[2,15],20:[2,15],22:[2,15],23:[2,15],25:[2,15]},{5:[2,16],14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],25:[2,16]},{17:23,21:24,30:25,40:[1,28],42:[1,27],43:26},{17:29,21:24,30:25,40:[1,28],42:[1,27],43:26},{17:30,21:24,30:25,40:[1,28],42:[1,27],43:26},{17:31,21:24,30:25,40:[1,28],42:[1,27],43:26},{21:33,26:32,32:[1,34],33:[1,35],40:[1,28],43:26},{1:[2,1]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],25:[2,10]},{10:36,20:[1,37]},{4:38,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,7],22:[1,13],23:[1,14],25:[1,15]},{7:39,8:17,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,21],20:[2,6],22:[1,13],23:[1,14],25:[1,15]},{17:23,18:[1,40],21:24,30:25,40:[1,28],42:[1,27],43:26},{10:41,20:[1,37]},{18:[1,42]},{18:[2,43],24:[2,43],28:43,32:[2,43],33:[2,43],34:[2,43],35:[2,43],36:[2,43],40:[2,43],42:[2,43]},{18:[2,25],24:[2,25],36:[2,25]},{18:[2,38],24:[2,38],32:[2,38],33:[2,38],34:[2,38],35:[2,38],36:[2,38],40:[2,38],42:[2,38],44:[1,44]},{21:45,40:[1,28],43:26},{18:[2,40],24:[2,40],32:[2,40],33:[2,40],34:[2,40],35:[2,40],36:[2,40],40:[2,40],42:[2,40],44:[2,40]},{18:[1,46]},{18:[1,47]},{24:[1,48]},{18:[2,41],21:50,27:49,40:[1,28],43:26},{18:[2,34],40:[2,34]},{18:[2,35],40:[2,35]},{18:[2,36],40:[2,36]},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],25:[2,11]},{21:51,40:[1,28],43:26},{8:17,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,3],22:[1,13],23:[1,14],25:[1,15]},{4:52,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,5],22:[1,13],23:[1,14],25:[1,15]},{14:[2,23],15:[2,23],16:[2,23],19:[2,23],20:[2,23],22:[2,23],23:[2,23],25:[2,23]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],25:[2,12]},{14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],25:[2,18]},{18:[2,45],21:56,24:[2,45],29:53,30:60,31:54,32:[1,57],33:[1,58],34:[1,59],35:[1,61],36:[2,45],37:55,38:62,39:63,40:[1,64],42:[1,27],43:26},{40:[1,65]},{18:[2,37],24:[2,37],32:[2,37],33:[2,37],34:[2,37],35:[2,37],36:[2,37],40:[2,37],42:[2,37]},{14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],25:[2,17]},{5:[2,20],14:[2,20],15:[2,20],16:[2,20],19:[2,20],20:[2,20],22:[2,20],23:[2,20],25:[2,20]},{5:[2,21],14:[2,21],15:[2,21],16:[2,21],19:[2,21],20:[2,21],22:[2,21],23:[2,21],25:[2,21]},{18:[1,66]},{18:[2,42]},{18:[1,67]},{8:17,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,4],22:[1,13],23:[1,14],25:[1,15]},{18:[2,24],24:[2,24],36:[2,24]},{18:[2,44],24:[2,44],32:[2,44],33:[2,44],34:[2,44],35:[2,44],36:[2,44],40:[2,44],42:[2,44]},{18:[2,46],24:[2,46],36:[2,46]},{18:[2,26],24:[2,26],32:[2,26],33:[2,26],34:[2,26],35:[2,26],36:[2,26],40:[2,26],42:[2,26]},{18:[2,27],24:[2,27],32:[2,27],33:[2,27],34:[2,27],35:[2,27],36:[2,27],40:[2,27],42:[2,27]},{18:[2,28],24:[2,28],32:[2,28],33:[2,28],34:[2,28],35:[2,28],36:[2,28],40:[2,28],42:[2,28]},{18:[2,29],24:[2,29],32:[2,29],33:[2,29],34:[2,29],35:[2,29],36:[2,29],40:[2,29],42:[2,29]},{18:[2,30],24:[2,30],32:[2,30],33:[2,30],34:[2,30],35:[2,30],36:[2,30],40:[2,30],42:[2,30]},{17:68,21:24,30:25,40:[1,28],42:[1,27],43:26},{18:[2,32],24:[2,32],36:[2,32],39:69,40:[1,70]},{18:[2,47],24:[2,47],36:[2,47],40:[2,47]},{18:[2,40],24:[2,40],32:[2,40],33:[2,40],34:[2,40],35:[2,40],36:[2,40],40:[2,40],41:[1,71],42:[2,40],44:[2,40]},{18:[2,39],24:[2,39],32:[2,39],33:[2,39],34:[2,39],35:[2,39],36:[2,39],40:[2,39],42:[2,39],44:[2,39]},{5:[2,22],14:[2,22],15:[2,22],16:[2,22],19:[2,22],20:[2,22],22:[2,22],23:[2,22],25:[2,22]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],25:[2,19]},{36:[1,72]},{18:[2,48],24:[2,48],36:[2,48],40:[2,48]},{41:[1,71]},{21:56,30:60,31:73,32:[1,57],33:[1,58],34:[1,59],35:[1,61],40:[1,28],42:[1,27],43:26},{18:[2,31],24:[2,31],32:[2,31],33:[2,31],34:[2,31],35:[2,31],36:[2,31],40:[2,31],42:[2,31]},{18:[2,33],24:[2,33],36:[2,33],40:[2,33]}],
  defaultActions: {3:[2,2],16:[2,1],50:[2,42]},
  parseError: function parseError(str, hash) {
      throw new Error(str);
  },
  parse: function parse(input) {
      var self = this, stack = [0], vstack = [null], lstack = [], table = this.table, yytext = "", yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1;
      this.lexer.setInput(input);
      this.lexer.yy = this.yy;
      this.yy.lexer = this.lexer;
      this.yy.parser = this;
      if (typeof this.lexer.yylloc == "undefined")
          this.lexer.yylloc = {};
      var yyloc = this.lexer.yylloc;
      lstack.push(yyloc);
      var ranges = this.lexer.options && this.lexer.options.ranges;
      if (typeof this.yy.parseError === "function")
          this.parseError = this.yy.parseError;
      function popStack(n) {
          stack.length = stack.length - 2 * n;
          vstack.length = vstack.length - n;
          lstack.length = lstack.length - n;
      }
      function lex() {
          var token;
          token = self.lexer.lex() || 1;
          if (typeof token !== "number") {
              token = self.symbols_[token] || token;
          }
          return token;
      }
      var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;
      while (true) {
          state = stack[stack.length - 1];
          if (this.defaultActions[state]) {
              action = this.defaultActions[state];
          } else {
              if (symbol === null || typeof symbol == "undefined") {
                  symbol = lex();
              }
              action = table[state] && table[state][symbol];
          }
          if (typeof action === "undefined" || !action.length || !action[0]) {
              var errStr = "";
              if (!recovering) {
                  expected = [];
                  for (p in table[state])
                      if (this.terminals_[p] && p > 2) {
                          expected.push("'" + this.terminals_[p] + "'");
                      }
                  if (this.lexer.showPosition) {
                      errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'";
                  } else {
                      errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'");
                  }
                  this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
              }
          }
          if (action[0] instanceof Array && action.length > 1) {
              throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol);
          }
          switch (action[0]) {
          case 1:
              stack.push(symbol);
              vstack.push(this.lexer.yytext);
              lstack.push(this.lexer.yylloc);
              stack.push(action[1]);
              symbol = null;
              if (!preErrorSymbol) {
                  yyleng = this.lexer.yyleng;
                  yytext = this.lexer.yytext;
                  yylineno = this.lexer.yylineno;
                  yyloc = this.lexer.yylloc;
                  if (recovering > 0)
                      recovering--;
              } else {
                  symbol = preErrorSymbol;
                  preErrorSymbol = null;
              }
              break;
          case 2:
              len = this.productions_[action[1]][1];
              yyval.$ = vstack[vstack.length - len];
              yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column};
              if (ranges) {
                  yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]];
              }
              r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
              if (typeof r !== "undefined") {
                  return r;
              }
              if (len) {
                  stack = stack.slice(0, -1 * len * 2);
                  vstack = vstack.slice(0, -1 * len);
                  lstack = lstack.slice(0, -1 * len);
              }
              stack.push(this.productions_[action[1]][0]);
              vstack.push(yyval.$);
              lstack.push(yyval._$);
              newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
              stack.push(newState);
              break;
          case 3:
              return true;
          }
      }
      return true;
  }
  };


  function stripFlags(open, close) {
    return {
      left: open.charAt(2) === '~',
      right: close.charAt(0) === '~' || close.charAt(1) === '~'
    };
  }

  /* Jison generated lexer */
  var lexer = (function(){
  var lexer = ({EOF:1,
  parseError:function parseError(str, hash) {
          if (this.yy.parser) {
              this.yy.parser.parseError(str, hash);
          } else {
              throw new Error(str);
          }
      },
  setInput:function (input) {
          this._input = input;
          this._more = this._less = this.done = false;
          this.yylineno = this.yyleng = 0;
          this.yytext = this.matched = this.match = '';
          this.conditionStack = ['INITIAL'];
          this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0};
          if (this.options.ranges) this.yylloc.range = [0,0];
          this.offset = 0;
          return this;
      },
  input:function () {
          var ch = this._input[0];
          this.yytext += ch;
          this.yyleng++;
          this.offset++;
          this.match += ch;
          this.matched += ch;
          var lines = ch.match(/(?:\r\n?|\n).*/g);
          if (lines) {
              this.yylineno++;
              this.yylloc.last_line++;
          } else {
              this.yylloc.last_column++;
          }
          if (this.options.ranges) this.yylloc.range[1]++;

          this._input = this._input.slice(1);
          return ch;
      },
  unput:function (ch) {
          var len = ch.length;
          var lines = ch.split(/(?:\r\n?|\n)/g);

          this._input = ch + this._input;
          this.yytext = this.yytext.substr(0, this.yytext.length-len-1);
          //this.yyleng -= len;
          this.offset -= len;
          var oldLines = this.match.split(/(?:\r\n?|\n)/g);
          this.match = this.match.substr(0, this.match.length-1);
          this.matched = this.matched.substr(0, this.matched.length-1);

          if (lines.length-1) this.yylineno -= lines.length-1;
          var r = this.yylloc.range;

          this.yylloc = {first_line: this.yylloc.first_line,
            last_line: this.yylineno+1,
            first_column: this.yylloc.first_column,
            last_column: lines ?
                (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length:
                this.yylloc.first_column - len
            };

          if (this.options.ranges) {
              this.yylloc.range = [r[0], r[0] + this.yyleng - len];
          }
          return this;
      },
  more:function () {
          this._more = true;
          return this;
      },
  less:function (n) {
          this.unput(this.match.slice(n));
      },
  pastInput:function () {
          var past = this.matched.substr(0, this.matched.length - this.match.length);
          return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
      },
  upcomingInput:function () {
          var next = this.match;
          if (next.length < 20) {
              next += this._input.substr(0, 20-next.length);
          }
          return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, "");
      },
  showPosition:function () {
          var pre = this.pastInput();
          var c = new Array(pre.length + 1).join("-");
          return pre + this.upcomingInput() + "\n" + c+"^";
      },
  next:function () {
          if (this.done) {
              return this.EOF;
          }
          if (!this._input) this.done = true;

          var token,
              match,
              tempMatch,
              index,
              col,
              lines;
          if (!this._more) {
              this.yytext = '';
              this.match = '';
          }
          var rules = this._currentRules();
          for (var i=0;i < rules.length; i++) {
              tempMatch = this._input.match(this.rules[rules[i]]);
              if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
                  match = tempMatch;
                  index = i;
                  if (!this.options.flex) break;
              }
          }
          if (match) {
              lines = match[0].match(/(?:\r\n?|\n).*/g);
              if (lines) this.yylineno += lines.length;
              this.yylloc = {first_line: this.yylloc.last_line,
                             last_line: this.yylineno+1,
                             first_column: this.yylloc.last_column,
                             last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length};
              this.yytext += match[0];
              this.match += match[0];
              this.matches = match;
              this.yyleng = this.yytext.length;
              if (this.options.ranges) {
                  this.yylloc.range = [this.offset, this.offset += this.yyleng];
              }
              this._more = false;
              this._input = this._input.slice(match[0].length);
              this.matched += match[0];
              token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]);
              if (this.done && this._input) this.done = false;
              if (token) return token;
              else return;
          }
          if (this._input === "") {
              return this.EOF;
          } else {
              return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(),
                      {text: "", token: null, line: this.yylineno});
          }
      },
  lex:function lex() {
          var r = this.next();
          if (typeof r !== 'undefined') {
              return r;
          } else {
              return this.lex();
          }
      },
  begin:function begin(condition) {
          this.conditionStack.push(condition);
      },
  popState:function popState() {
          return this.conditionStack.pop();
      },
  _currentRules:function _currentRules() {
          return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;
      },
  topState:function () {
          return this.conditionStack[this.conditionStack.length-2];
      },
  pushState:function begin(condition) {
          this.begin(condition);
      }});
  lexer.options = {};
  lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {


  function strip(start, end) {
    return yy_.yytext = yy_.yytext.substr(start, yy_.yyleng-end);
  }


  var YYSTATE=YY_START
  switch($avoiding_name_collisions) {
  case 0:
                                     if(yy_.yytext.slice(-2) === "\\\\") {
                                       strip(0,1);
                                       this.begin("mu");
                                     } else if(yy_.yytext.slice(-1) === "\\") {
                                       strip(0,1);
                                       this.begin("emu");
                                     } else {
                                       this.begin("mu");
                                     }
                                     if(yy_.yytext) return 14;
                                   
  break;
  case 1:return 14;
  break;
  case 2:
                                     this.popState();
                                     return 14;
                                   
  break;
  case 3:strip(0,4); this.popState(); return 15;
  break;
  case 4:return 35;
  break;
  case 5:return 36;
  break;
  case 6:return 25;
  break;
  case 7:return 16;
  break;
  case 8:return 20;
  break;
  case 9:return 19;
  break;
  case 10:return 19;
  break;
  case 11:return 23;
  break;
  case 12:return 22;
  break;
  case 13:this.popState(); this.begin('com');
  break;
  case 14:strip(3,5); this.popState(); return 15;
  break;
  case 15:return 22;
  break;
  case 16:return 41;
  break;
  case 17:return 40;
  break;
  case 18:return 40;
  break;
  case 19:return 44;
  break;
  case 20:// ignore whitespace
  break;
  case 21:this.popState(); return 24;
  break;
  case 22:this.popState(); return 18;
  break;
  case 23:yy_.yytext = strip(1,2).replace(/\\"/g,'"'); return 32;
  break;
  case 24:yy_.yytext = strip(1,2).replace(/\\'/g,"'"); return 32;
  break;
  case 25:return 42;
  break;
  case 26:return 34;
  break;
  case 27:return 34;
  break;
  case 28:return 33;
  break;
  case 29:return 40;
  break;
  case 30:yy_.yytext = strip(1,2); return 40;
  break;
  case 31:return 'INVALID';
  break;
  case 32:return 5;
  break;
  }
  };
  lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|\\\{\{|\\\\\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\()/,/^(?:\))/,/^(?:\{\{(~)?>)/,/^(?:\{\{(~)?#)/,/^(?:\{\{(~)?\/)/,/^(?:\{\{(~)?\^)/,/^(?:\{\{(~)?\s*else\b)/,/^(?:\{\{(~)?\{)/,/^(?:\{\{(~)?&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{(~)?)/,/^(?:=)/,/^(?:\.\.)/,/^(?:\.(?=([=~}\s\/.)])))/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}(~)?\}\})/,/^(?:(~)?\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=([~}\s)])))/,/^(?:false(?=([~}\s)])))/,/^(?:-?[0-9]+(?=([~}\s)])))/,/^(?:([^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=([=~}\s\/.)]))))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:$)/];
  lexer.conditions = {"mu":{"rules":[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"com":{"rules":[3],"inclusive":false},"INITIAL":{"rules":[0,1,32],"inclusive":true}};
  return lexer;})()
  parser.lexer = lexer;
  function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser;
  return new Parser;
  })();__exports__ = handlebars;
  /* jshint ignore:end */
  return __exports__;
})();

// handlebars/compiler/base.js
var __module8__ = (function(__dependency1__, __dependency2__) {
  "use strict";
  var __exports__ = {};
  var parser = __dependency1__;
  var AST = __dependency2__;

  __exports__.parser = parser;

  function parse(input) {
    // Just return if an already-compile AST was passed in.
    if(input.constructor === AST.ProgramNode) { return input; }

    parser.yy = AST;
    return parser.parse(input);
  }

  __exports__.parse = parse;
  return __exports__;
})(__module9__, __module7__);

// handlebars/compiler/compiler.js
var __module10__ = (function(__dependency1__) {
  "use strict";
  var __exports__ = {};
  var Exception = __dependency1__;

  function Compiler() {}

  __exports__.Compiler = Compiler;// the foundHelper register will disambiguate helper lookup from finding a
  // function in a context. This is necessary for mustache compatibility, which
  // requires that context functions in blocks are evaluated by blockHelperMissing,
  // and then proceed as if the resulting value was provided to blockHelperMissing.

  Compiler.prototype = {
    compiler: Compiler,

    disassemble: function() {
      var opcodes = this.opcodes, opcode, out = [], params, param;

      for (var i=0, l=opcodes.length; i<l; i++) {
        opcode = opcodes[i];

        if (opcode.opcode === 'DECLARE') {
          out.push("DECLARE " + opcode.name + "=" + opcode.value);
        } else {
          params = [];
          for (var j=0; j<opcode.args.length; j++) {
            param = opcode.args[j];
            if (typeof param === "string") {
              param = "\"" + param.replace("\n", "\\n") + "\"";
            }
            params.push(param);
          }
          out.push(opcode.opcode + " " + params.join(" "));
        }
      }

      return out.join("\n");
    },

    equals: function(other) {
      var len = this.opcodes.length;
      if (other.opcodes.length !== len) {
        return false;
      }

      for (var i = 0; i < len; i++) {
        var opcode = this.opcodes[i],
            otherOpcode = other.opcodes[i];
        if (opcode.opcode !== otherOpcode.opcode || opcode.args.length !== otherOpcode.args.length) {
          return false;
        }
        for (var j = 0; j < opcode.args.length; j++) {
          if (opcode.args[j] !== otherOpcode.args[j]) {
            return false;
          }
        }
      }

      len = this.children.length;
      if (other.children.length !== len) {
        return false;
      }
      for (i = 0; i < len; i++) {
        if (!this.children[i].equals(other.children[i])) {
          return false;
        }
      }

      return true;
    },

    guid: 0,

    compile: function(program, options) {
      this.opcodes = [];
      this.children = [];
      this.depths = {list: []};
      this.options = options;

      // These changes will propagate to the other compiler components
      var knownHelpers = this.options.knownHelpers;
      this.options.knownHelpers = {
        'helperMissing': true,
        'blockHelperMissing': true,
        'each': true,
        'if': true,
        'unless': true,
        'with': true,
        'log': true
      };
      if (knownHelpers) {
        for (var name in knownHelpers) {
          this.options.knownHelpers[name] = knownHelpers[name];
        }
      }

      return this.accept(program);
    },

    accept: function(node) {
      var strip = node.strip || {},
          ret;
      if (strip.left) {
        this.opcode('strip');
      }

      ret = this[node.type](node);

      if (strip.right) {
        this.opcode('strip');
      }

      return ret;
    },

    program: function(program) {
      var statements = program.statements;

      for(var i=0, l=statements.length; i<l; i++) {
        this.accept(statements[i]);
      }
      this.isSimple = l === 1;

      this.depths.list = this.depths.list.sort(function(a, b) {
        return a - b;
      });

      return this;
    },

    compileProgram: function(program) {
      var result = new this.compiler().compile(program, this.options);
      var guid = this.guid++, depth;

      this.usePartial = this.usePartial || result.usePartial;

      this.children[guid] = result;

      for(var i=0, l=result.depths.list.length; i<l; i++) {
        depth = result.depths.list[i];

        if(depth < 2) { continue; }
        else { this.addDepth(depth - 1); }
      }

      return guid;
    },

    block: function(block) {
      var mustache = block.mustache,
          program = block.program,
          inverse = block.inverse;

      if (program) {
        program = this.compileProgram(program);
      }

      if (inverse) {
        inverse = this.compileProgram(inverse);
      }

      var sexpr = mustache.sexpr;
      var type = this.classifySexpr(sexpr);

      if (type === "helper") {
        this.helperSexpr(sexpr, program, inverse);
      } else if (type === "simple") {
        this.simpleSexpr(sexpr);

        // now that the simple mustache is resolved, we need to
        // evaluate it by executing `blockHelperMissing`
        this.opcode('pushProgram', program);
        this.opcode('pushProgram', inverse);
        this.opcode('emptyHash');
        this.opcode('blockValue');
      } else {
        this.ambiguousSexpr(sexpr, program, inverse);

        // now that the simple mustache is resolved, we need to
        // evaluate it by executing `blockHelperMissing`
        this.opcode('pushProgram', program);
        this.opcode('pushProgram', inverse);
        this.opcode('emptyHash');
        this.opcode('ambiguousBlockValue');
      }

      this.opcode('append');
    },

    hash: function(hash) {
      var pairs = hash.pairs, pair, val;

      this.opcode('pushHash');

      for(var i=0, l=pairs.length; i<l; i++) {
        pair = pairs[i];
        val  = pair[1];

        if (this.options.stringParams) {
          if(val.depth) {
            this.addDepth(val.depth);
          }
          this.opcode('getContext', val.depth || 0);
          this.opcode('pushStringParam', val.stringModeValue, val.type);

          if (val.type === 'sexpr') {
            // Subexpressions get evaluated and passed in
            // in string params mode.
            this.sexpr(val);
          }
        } else {
          this.accept(val);
        }

        this.opcode('assignToHash', pair[0]);
      }
      this.opcode('popHash');
    },

    partial: function(partial) {
      var partialName = partial.partialName;
      this.usePartial = true;

      if(partial.context) {
        this.ID(partial.context);
      } else {
        this.opcode('push', 'depth0');
      }

      this.opcode('invokePartial', partialName.name);
      this.opcode('append');
    },

    content: function(content) {
      this.opcode('appendContent', content.string);
    },

    mustache: function(mustache) {
      this.sexpr(mustache.sexpr);

      if(mustache.escaped && !this.options.noEscape) {
        this.opcode('appendEscaped');
      } else {
        this.opcode('append');
      }
    },

    ambiguousSexpr: function(sexpr, program, inverse) {
      var id = sexpr.id,
          name = id.parts[0],
          isBlock = program != null || inverse != null;

      this.opcode('getContext', id.depth);

      this.opcode('pushProgram', program);
      this.opcode('pushProgram', inverse);

      this.opcode('invokeAmbiguous', name, isBlock);
    },

    simpleSexpr: function(sexpr) {
      var id = sexpr.id;

      if (id.type === 'DATA') {
        this.DATA(id);
      } else if (id.parts.length) {
        this.ID(id);
      } else {
        // Simplified ID for `this`
        this.addDepth(id.depth);
        this.opcode('getContext', id.depth);
        this.opcode('pushContext');
      }

      this.opcode('resolvePossibleLambda');
    },

    helperSexpr: function(sexpr, program, inverse) {
      var params = this.setupFullMustacheParams(sexpr, program, inverse),
          name = sexpr.id.parts[0];

      if (this.options.knownHelpers[name]) {
        this.opcode('invokeKnownHelper', params.length, name);
      } else if (this.options.knownHelpersOnly) {
        throw new Exception("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
      } else {
        this.opcode('invokeHelper', params.length, name, sexpr.isRoot);
      }
    },

    sexpr: function(sexpr) {
      var type = this.classifySexpr(sexpr);

      if (type === "simple") {
        this.simpleSexpr(sexpr);
      } else if (type === "helper") {
        this.helperSexpr(sexpr);
      } else {
        this.ambiguousSexpr(sexpr);
      }
    },

    ID: function(id) {
      this.addDepth(id.depth);
      this.opcode('getContext', id.depth);

      var name = id.parts[0];
      if (!name) {
        this.opcode('pushContext');
      } else {
        this.opcode('lookupOnContext', id.parts[0]);
      }

      for(var i=1, l=id.parts.length; i<l; i++) {
        this.opcode('lookup', id.parts[i]);
      }
    },

    DATA: function(data) {
      this.options.data = true;
      if (data.id.isScoped || data.id.depth) {
        throw new Exception('Scoped data references are not supported: ' + data.original, data);
      }

      this.opcode('lookupData');
      var parts = data.id.parts;
      for(var i=0, l=parts.length; i<l; i++) {
        this.opcode('lookup', parts[i]);
      }
    },

    STRING: function(string) {
      this.opcode('pushString', string.string);
    },

    INTEGER: function(integer) {
      this.opcode('pushLiteral', integer.integer);
    },

    BOOLEAN: function(bool) {
      this.opcode('pushLiteral', bool.bool);
    },

    comment: function() {},

    // HELPERS
    opcode: function(name) {
      this.opcodes.push({ opcode: name, args: [].slice.call(arguments, 1) });
    },

    declare: function(name, value) {
      this.opcodes.push({ opcode: 'DECLARE', name: name, value: value });
    },

    addDepth: function(depth) {
      if(depth === 0) { return; }

      if(!this.depths[depth]) {
        this.depths[depth] = true;
        this.depths.list.push(depth);
      }
    },

    classifySexpr: function(sexpr) {
      var isHelper   = sexpr.isHelper;
      var isEligible = sexpr.eligibleHelper;
      var options    = this.options;

      // if ambiguous, we can possibly resolve the ambiguity now
      if (isEligible && !isHelper) {
        var name = sexpr.id.parts[0];

        if (options.knownHelpers[name]) {
          isHelper = true;
        } else if (options.knownHelpersOnly) {
          isEligible = false;
        }
      }

      if (isHelper) { return "helper"; }
      else if (isEligible) { return "ambiguous"; }
      else { return "simple"; }
    },

    pushParams: function(params) {
      var i = params.length, param;

      while(i--) {
        param = params[i];

        if(this.options.stringParams) {
          if(param.depth) {
            this.addDepth(param.depth);
          }

          this.opcode('getContext', param.depth || 0);
          this.opcode('pushStringParam', param.stringModeValue, param.type);

          if (param.type === 'sexpr') {
            // Subexpressions get evaluated and passed in
            // in string params mode.
            this.sexpr(param);
          }
        } else {
          this[param.type](param);
        }
      }
    },

    setupFullMustacheParams: function(sexpr, program, inverse) {
      var params = sexpr.params;
      this.pushParams(params);

      this.opcode('pushProgram', program);
      this.opcode('pushProgram', inverse);

      if (sexpr.hash) {
        this.hash(sexpr.hash);
      } else {
        this.opcode('emptyHash');
      }

      return params;
    }
  };

  function precompile(input, options, env) {
    if (input == null || (typeof input !== 'string' && input.constructor !== env.AST.ProgramNode)) {
      throw new Exception("You must pass a string or Handlebars AST to Handlebars.precompile. You passed " + input);
    }

    options = options || {};
    if (!('data' in options)) {
      options.data = true;
    }

    var ast = env.parse(input);
    var environment = new env.Compiler().compile(ast, options);
    return new env.JavaScriptCompiler().compile(environment, options);
  }

  __exports__.precompile = precompile;function compile(input, options, env) {
    if (input == null || (typeof input !== 'string' && input.constructor !== env.AST.ProgramNode)) {
      throw new Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input);
    }

    options = options || {};

    if (!('data' in options)) {
      options.data = true;
    }

    var compiled;

    function compileInput() {
      var ast = env.parse(input);
      var environment = new env.Compiler().compile(ast, options);
      var templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true);
      return env.template(templateSpec);
    }

    // Template is only compiled on first use and cached after that point.
    return function(context, options) {
      if (!compiled) {
        compiled = compileInput();
      }
      return compiled.call(this, context, options);
    };
  }

  __exports__.compile = compile;
  return __exports__;
})(__module5__);

// handlebars/compiler/javascript-compiler.js
var __module11__ = (function(__dependency1__, __dependency2__) {
  "use strict";
  var __exports__;
  var COMPILER_REVISION = __dependency1__.COMPILER_REVISION;
  var REVISION_CHANGES = __dependency1__.REVISION_CHANGES;
  var log = __dependency1__.log;
  var Exception = __dependency2__;

  function Literal(value) {
    this.value = value;
  }

  function JavaScriptCompiler() {}

  JavaScriptCompiler.prototype = {
    // PUBLIC API: You can override these methods in a subclass to provide
    // alternative compiled forms for name lookup and buffering semantics
    nameLookup: function(parent, name /* , type*/) {
      var wrap,
          ret;
      if (parent.indexOf('depth') === 0) {
        wrap = true;
      }

      if (/^[0-9]+$/.test(name)) {
        ret = parent + "[" + name + "]";
      } else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
        ret = parent + "." + name;
      }
      else {
        ret = parent + "['" + name + "']";
      }

      if (wrap) {
        return '(' + parent + ' && ' + ret + ')';
      } else {
        return ret;
      }
    },

    compilerInfo: function() {
      var revision = COMPILER_REVISION,
          versions = REVISION_CHANGES[revision];
      return "this.compilerInfo = ["+revision+",'"+versions+"'];\n";
    },

    appendToBuffer: function(string) {
      if (this.environment.isSimple) {
        return "return " + string + ";";
      } else {
        return {
          appendToBuffer: true,
          content: string,
          toString: function() { return "buffer += " + string + ";"; }
        };
      }
    },

    initializeBuffer: function() {
      return this.quotedString("");
    },

    namespace: "Handlebars",
    // END PUBLIC API

    compile: function(environment, options, context, asObject) {
      this.environment = environment;
      this.options = options || {};

      log('debug', this.environment.disassemble() + "\n\n");

      this.name = this.environment.name;
      this.isChild = !!context;
      this.context = context || {
        programs: [],
        environments: [],
        aliases: { }
      };

      this.preamble();

      this.stackSlot = 0;
      this.stackVars = [];
      this.registers = { list: [] };
      this.hashes = [];
      this.compileStack = [];
      this.inlineStack = [];

      this.compileChildren(environment, options);

      var opcodes = environment.opcodes, opcode;

      this.i = 0;

      for(var l=opcodes.length; this.i<l; this.i++) {
        opcode = opcodes[this.i];

        if(opcode.opcode === 'DECLARE') {
          this[opcode.name] = opcode.value;
        } else {
          this[opcode.opcode].apply(this, opcode.args);
        }

        // Reset the stripNext flag if it was not set by this operation.
        if (opcode.opcode !== this.stripNext) {
          this.stripNext = false;
        }
      }

      // Flush any trailing content that might be pending.
      this.pushSource('');

      if (this.stackSlot || this.inlineStack.length || this.compileStack.length) {
        throw new Exception('Compile completed with content left on stack');
      }

      return this.createFunctionContext(asObject);
    },

    preamble: function() {
      var out = [];

      if (!this.isChild) {
        var namespace = this.namespace;

        var copies = "helpers = this.merge(helpers, " + namespace + ".helpers);";
        if (this.environment.usePartial) { copies = copies + " partials = this.merge(partials, " + namespace + ".partials);"; }
        if (this.options.data) { copies = copies + " data = data || {};"; }
        out.push(copies);
      } else {
        out.push('');
      }

      if (!this.environment.isSimple) {
        out.push(", buffer = " + this.initializeBuffer());
      } else {
        out.push("");
      }

      // track the last context pushed into place to allow skipping the
      // getContext opcode when it would be a noop
      this.lastContext = 0;
      this.source = out;
    },

    createFunctionContext: function(asObject) {
      var locals = this.stackVars.concat(this.registers.list);

      if(locals.length > 0) {
        this.source[1] = this.source[1] + ", " + locals.join(", ");
      }

      // Generate minimizer alias mappings
      if (!this.isChild) {
        for (var alias in this.context.aliases) {
          if (this.context.aliases.hasOwnProperty(alias)) {
            this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
          }
        }
      }

      if (this.source[1]) {
        this.source[1] = "var " + this.source[1].substring(2) + ";";
      }

      // Merge children
      if (!this.isChild) {
        this.source[1] += '\n' + this.context.programs.join('\n') + '\n';
      }

      if (!this.environment.isSimple) {
        this.pushSource("return buffer;");
      }

      var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"];

      for(var i=0, l=this.environment.depths.list.length; i<l; i++) {
        params.push("depth" + this.environment.depths.list[i]);
      }

      // Perform a second pass over the output to merge content when possible
      var source = this.mergeSource();

      if (!this.isChild) {
        source = this.compilerInfo()+source;
      }

      if (asObject) {
        params.push(source);

        return Function.apply(this, params);
      } else {
        var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n  ' + source + '}';
        log('debug', functionSource + "\n\n");
        return functionSource;
      }
    },
    mergeSource: function() {
      // WARN: We are not handling the case where buffer is still populated as the source should
      // not have buffer append operations as their final action.
      var source = '',
          buffer;
      for (var i = 0, len = this.source.length; i < len; i++) {
        var line = this.source[i];
        if (line.appendToBuffer) {
          if (buffer) {
            buffer = buffer + '\n    + ' + line.content;
          } else {
            buffer = line.content;
          }
        } else {
          if (buffer) {
            source += 'buffer += ' + buffer + ';\n  ';
            buffer = undefined;
          }
          source += line + '\n  ';
        }
      }
      return source;
    },

    // [blockValue]
    //
    // On stack, before: hash, inverse, program, value
    // On stack, after: return value of blockHelperMissing
    //
    // The purpose of this opcode is to take a block of the form
    // `{{#foo}}...{{/foo}}`, resolve the value of `foo`, and
    // replace it on the stack with the result of properly
    // invoking blockHelperMissing.
    blockValue: function() {
      this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';

      var params = ["depth0"];
      this.setupParams(0, params);

      this.replaceStack(function(current) {
        params.splice(1, 0, current);
        return "blockHelperMissing.call(" + params.join(", ") + ")";
      });
    },

    // [ambiguousBlockValue]
    //
    // On stack, before: hash, inverse, program, value
    // Compiler value, before: lastHelper=value of last found helper, if any
    // On stack, after, if no lastHelper: same as [blockValue]
    // On stack, after, if lastHelper: value
    ambiguousBlockValue: function() {
      this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';

      var params = ["depth0"];
      this.setupParams(0, params);

      var current = this.topStack();
      params.splice(1, 0, current);

      this.pushSource("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }");
    },

    // [appendContent]
    //
    // On stack, before: ...
    // On stack, after: ...
    //
    // Appends the string value of `content` to the current buffer
    appendContent: function(content) {
      if (this.pendingContent) {
        content = this.pendingContent + content;
      }
      if (this.stripNext) {
        content = content.replace(/^\s+/, '');
      }

      this.pendingContent = content;
    },

    // [strip]
    //
    // On stack, before: ...
    // On stack, after: ...
    //
    // Removes any trailing whitespace from the prior content node and flags
    // the next operation for stripping if it is a content node.
    strip: function() {
      if (this.pendingContent) {
        this.pendingContent = this.pendingContent.replace(/\s+$/, '');
      }
      this.stripNext = 'strip';
    },

    // [append]
    //
    // On stack, before: value, ...
    // On stack, after: ...
    //
    // Coerces `value` to a String and appends it to the current buffer.
    //
    // If `value` is truthy, or 0, it is coerced into a string and appended
    // Otherwise, the empty string is appended
    append: function() {
      // Force anything that is inlined onto the stack so we don't have duplication
      // when we examine local
      this.flushInline();
      var local = this.popStack();
      this.pushSource("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
      if (this.environment.isSimple) {
        this.pushSource("else { " + this.appendToBuffer("''") + " }");
      }
    },

    // [appendEscaped]
    //
    // On stack, before: value, ...
    // On stack, after: ...
    //
    // Escape `value` and append it to the buffer
    appendEscaped: function() {
      this.context.aliases.escapeExpression = 'this.escapeExpression';

      this.pushSource(this.appendToBuffer("escapeExpression(" + this.popStack() + ")"));
    },

    // [getContext]
    //
    // On stack, before: ...
    // On stack, after: ...
    // Compiler value, after: lastContext=depth
    //
    // Set the value of the `lastContext` compiler value to the depth
    getContext: function(depth) {
      if(this.lastContext !== depth) {
        this.lastContext = depth;
      }
    },

    // [lookupOnContext]
    //
    // On stack, before: ...
    // On stack, after: currentContext[name], ...
    //
    // Looks up the value of `name` on the current context and pushes
    // it onto the stack.
    lookupOnContext: function(name) {
      this.push(this.nameLookup('depth' + this.lastContext, name, 'context'));
    },

    // [pushContext]
    //
    // On stack, before: ...
    // On stack, after: currentContext, ...
    //
    // Pushes the value of the current context onto the stack.
    pushContext: function() {
      this.pushStackLiteral('depth' + this.lastContext);
    },

    // [resolvePossibleLambda]
    //
    // On stack, before: value, ...
    // On stack, after: resolved value, ...
    //
    // If the `value` is a lambda, replace it on the stack by
    // the return value of the lambda
    resolvePossibleLambda: function() {
      this.context.aliases.functionType = '"function"';

      this.replaceStack(function(current) {
        return "typeof " + current + " === functionType ? " + current + ".apply(depth0) : " + current;
      });
    },

    // [lookup]
    //
    // On stack, before: value, ...
    // On stack, after: value[name], ...
    //
    // Replace the value on the stack with the result of looking
    // up `name` on `value`
    lookup: function(name) {
      this.replaceStack(function(current) {
        return current + " == null || " + current + " === false ? " + current + " : " + this.nameLookup(current, name, 'context');
      });
    },

    // [lookupData]
    //
    // On stack, before: ...
    // On stack, after: data, ...
    //
    // Push the data lookup operator
    lookupData: function() {
      this.pushStackLiteral('data');
    },

    // [pushStringParam]
    //
    // On stack, before: ...
    // On stack, after: string, currentContext, ...
    //
    // This opcode is designed for use in string mode, which
    // provides the string value of a parameter along with its
    // depth rather than resolving it immediately.
    pushStringParam: function(string, type) {
      this.pushStackLiteral('depth' + this.lastContext);

      this.pushString(type);

      // If it's a subexpression, the string result
      // will be pushed after this opcode.
      if (type !== 'sexpr') {
        if (typeof string === 'string') {
          this.pushString(string);
        } else {
          this.pushStackLiteral(string);
        }
      }
    },

    emptyHash: function() {
      this.pushStackLiteral('{}');

      if (this.options.stringParams) {
        this.push('{}'); // hashContexts
        this.push('{}'); // hashTypes
      }
    },
    pushHash: function() {
      if (this.hash) {
        this.hashes.push(this.hash);
      }
      this.hash = {values: [], types: [], contexts: []};
    },
    popHash: function() {
      var hash = this.hash;
      this.hash = this.hashes.pop();

      if (this.options.stringParams) {
        this.push('{' + hash.contexts.join(',') + '}');
        this.push('{' + hash.types.join(',') + '}');
      }

      this.push('{\n    ' + hash.values.join(',\n    ') + '\n  }');
    },

    // [pushString]
    //
    // On stack, before: ...
    // On stack, after: quotedString(string), ...
    //
    // Push a quoted version of `string` onto the stack
    pushString: function(string) {
      this.pushStackLiteral(this.quotedString(string));
    },

    // [push]
    //
    // On stack, before: ...
    // On stack, after: expr, ...
    //
    // Push an expression onto the stack
    push: function(expr) {
      this.inlineStack.push(expr);
      return expr;
    },

    // [pushLiteral]
    //
    // On stack, before: ...
    // On stack, after: value, ...
    //
    // Pushes a value onto the stack. This operation prevents
    // the compiler from creating a temporary variable to hold
    // it.
    pushLiteral: function(value) {
      this.pushStackLiteral(value);
    },

    // [pushProgram]
    //
    // On stack, before: ...
    // On stack, after: program(guid), ...
    //
    // Push a program expression onto the stack. This takes
    // a compile-time guid and converts it into a runtime-accessible
    // expression.
    pushProgram: function(guid) {
      if (guid != null) {
        this.pushStackLiteral(this.programExpression(guid));
      } else {
        this.pushStackLiteral(null);
      }
    },

    // [invokeHelper]
    //
    // On stack, before: hash, inverse, program, params..., ...
    // On stack, after: result of helper invocation
    //
    // Pops off the helper's parameters, invokes the helper,
    // and pushes the helper's return value onto the stack.
    //
    // If the helper is not found, `helperMissing` is called.
    invokeHelper: function(paramSize, name, isRoot) {
      this.context.aliases.helperMissing = 'helpers.helperMissing';
      this.useRegister('helper');

      var helper = this.lastHelper = this.setupHelper(paramSize, name, true);
      var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');

      var lookup = 'helper = ' + helper.name + ' || ' + nonHelper;
      if (helper.paramsInit) {
        lookup += ',' + helper.paramsInit;
      }

      this.push(
        '('
          + lookup
          + ',helper '
            + '? helper.call(' + helper.callParams + ') '
            + ': helperMissing.call(' + helper.helperMissingParams + '))');

      // Always flush subexpressions. This is both to prevent the compounding size issue that
      // occurs when the code has to be duplicated for inlining and also to prevent errors
      // due to the incorrect options object being passed due to the shared register.
      if (!isRoot) {
        this.flushInline();
      }
    },

    // [invokeKnownHelper]
    //
    // On stack, before: hash, inverse, program, params..., ...
    // On stack, after: result of helper invocation
    //
    // This operation is used when the helper is known to exist,
    // so a `helperMissing` fallback is not required.
    invokeKnownHelper: function(paramSize, name) {
      var helper = this.setupHelper(paramSize, name);
      this.push(helper.name + ".call(" + helper.callParams + ")");
    },

    // [invokeAmbiguous]
    //
    // On stack, before: hash, inverse, program, params..., ...
    // On stack, after: result of disambiguation
    //
    // This operation is used when an expression like `{{foo}}`
    // is provided, but we don't know at compile-time whether it
    // is a helper or a path.
    //
    // This operation emits more code than the other options,
    // and can be avoided by passing the `knownHelpers` and
    // `knownHelpersOnly` flags at compile-time.
    invokeAmbiguous: function(name, helperCall) {
      this.context.aliases.functionType = '"function"';
      this.useRegister('helper');

      this.emptyHash();
      var helper = this.setupHelper(0, name, helperCall);

      var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');

      var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
      var nextStack = this.nextStack();

      if (helper.paramsInit) {
        this.pushSource(helper.paramsInit);
      }
      this.pushSource('if (helper = ' + helperName + ') { ' + nextStack + ' = helper.call(' + helper.callParams + '); }');
      this.pushSource('else { helper = ' + nonHelper + '; ' + nextStack + ' = typeof helper === functionType ? helper.call(' + helper.callParams + ') : helper; }');
    },

    // [invokePartial]
    //
    // On stack, before: context, ...
    // On stack after: result of partial invocation
    //
    // This operation pops off a context, invokes a partial with that context,
    // and pushes the result of the invocation back.
    invokePartial: function(name) {
      var params = [this.nameLookup('partials', name, 'partial'), "'" + name + "'", this.popStack(), "helpers", "partials"];

      if (this.options.data) {
        params.push("data");
      }

      this.context.aliases.self = "this";
      this.push("self.invokePartial(" + params.join(", ") + ")");
    },

    // [assignToHash]
    //
    // On stack, before: value, hash, ...
    // On stack, after: hash, ...
    //
    // Pops a value and hash off the stack, assigns `hash[key] = value`
    // and pushes the hash back onto the stack.
    assignToHash: function(key) {
      var value = this.popStack(),
          context,
          type;

      if (this.options.stringParams) {
        type = this.popStack();
        context = this.popStack();
      }

      var hash = this.hash;
      if (context) {
        hash.contexts.push("'" + key + "': " + context);
      }
      if (type) {
        hash.types.push("'" + key + "': " + type);
      }
      hash.values.push("'" + key + "': (" + value + ")");
    },

    // HELPERS

    compiler: JavaScriptCompiler,

    compileChildren: function(environment, options) {
      var children = environment.children, child, compiler;

      for(var i=0, l=children.length; i<l; i++) {
        child = children[i];
        compiler = new this.compiler();

        var index = this.matchExistingProgram(child);

        if (index == null) {
          this.context.programs.push('');     // Placeholder to prevent name conflicts for nested children
          index = this.context.programs.length;
          child.index = index;
          child.name = 'program' + index;
          this.context.programs[index] = compiler.compile(child, options, this.context);
          this.context.environments[index] = child;
        } else {
          child.index = index;
          child.name = 'program' + index;
        }
      }
    },
    matchExistingProgram: function(child) {
      for (var i = 0, len = this.context.environments.length; i < len; i++) {
        var environment = this.context.environments[i];
        if (environment && environment.equals(child)) {
          return i;
        }
      }
    },

    programExpression: function(guid) {
      this.context.aliases.self = "this";

      if(guid == null) {
        return "self.noop";
      }

      var child = this.environment.children[guid],
          depths = child.depths.list, depth;

      var programParams = [child.index, child.name, "data"];

      for(var i=0, l = depths.length; i<l; i++) {
        depth = depths[i];

        if(depth === 1) { programParams.push("depth0"); }
        else { programParams.push("depth" + (depth - 1)); }
      }

      return (depths.length === 0 ? "self.program(" : "self.programWithDepth(") + programParams.join(", ") + ")";
    },

    register: function(name, val) {
      this.useRegister(name);
      this.pushSource(name + " = " + val + ";");
    },

    useRegister: function(name) {
      if(!this.registers[name]) {
        this.registers[name] = true;
        this.registers.list.push(name);
      }
    },

    pushStackLiteral: function(item) {
      return this.push(new Literal(item));
    },

    pushSource: function(source) {
      if (this.pendingContent) {
        this.source.push(this.appendToBuffer(this.quotedString(this.pendingContent)));
        this.pendingContent = undefined;
      }

      if (source) {
        this.source.push(source);
      }
    },

    pushStack: function(item) {
      this.flushInline();

      var stack = this.incrStack();
      if (item) {
        this.pushSource(stack + " = " + item + ";");
      }
      this.compileStack.push(stack);
      return stack;
    },

    replaceStack: function(callback) {
      var prefix = '',
          inline = this.isInline(),
          stack,
          createdStack,
          usedLiteral;

      // If we are currently inline then we want to merge the inline statement into the
      // replacement statement via ','
      if (inline) {
        var top = this.popStack(true);

        if (top instanceof Literal) {
          // Literals do not need to be inlined
          stack = top.value;
          usedLiteral = true;
        } else {
          // Get or create the current stack name for use by the inline
          createdStack = !this.stackSlot;
          var name = !createdStack ? this.topStackName() : this.incrStack();

          prefix = '(' + this.push(name) + ' = ' + top + '),';
          stack = this.topStack();
        }
      } else {
        stack = this.topStack();
      }

      var item = callback.call(this, stack);

      if (inline) {
        if (!usedLiteral) {
          this.popStack();
        }
        if (createdStack) {
          this.stackSlot--;
        }
        this.push('(' + prefix + item + ')');
      } else {
        // Prevent modification of the context depth variable. Through replaceStack
        if (!/^stack/.test(stack)) {
          stack = this.nextStack();
        }

        this.pushSource(stack + " = (" + prefix + item + ");");
      }
      return stack;
    },

    nextStack: function() {
      return this.pushStack();
    },

    incrStack: function() {
      this.stackSlot++;
      if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
      return this.topStackName();
    },
    topStackName: function() {
      return "stack" + this.stackSlot;
    },
    flushInline: function() {
      var inlineStack = this.inlineStack;
      if (inlineStack.length) {
        this.inlineStack = [];
        for (var i = 0, len = inlineStack.length; i < len; i++) {
          var entry = inlineStack[i];
          if (entry instanceof Literal) {
            this.compileStack.push(entry);
          } else {
            this.pushStack(entry);
          }
        }
      }
    },
    isInline: function() {
      return this.inlineStack.length;
    },

    popStack: function(wrapped) {
      var inline = this.isInline(),
          item = (inline ? this.inlineStack : this.compileStack).pop();

      if (!wrapped && (item instanceof Literal)) {
        return item.value;
      } else {
        if (!inline) {
          if (!this.stackSlot) {
            throw new Exception('Invalid stack pop');
          }
          this.stackSlot--;
        }
        return item;
      }
    },

    topStack: function(wrapped) {
      var stack = (this.isInline() ? this.inlineStack : this.compileStack),
          item = stack[stack.length - 1];

      if (!wrapped && (item instanceof Literal)) {
        return item.value;
      } else {
        return item;
      }
    },

    quotedString: function(str) {
      return '"' + str
        .replace(/\\/g, '\\\\')
        .replace(/"/g, '\\"')
        .replace(/\n/g, '\\n')
        .replace(/\r/g, '\\r')
        .replace(/\u2028/g, '\\u2028')   // Per Ecma-262 7.3 + 7.8.4
        .replace(/\u2029/g, '\\u2029') + '"';
    },

    setupHelper: function(paramSize, name, missingParams) {
      var params = [],
          paramsInit = this.setupParams(paramSize, params, missingParams);
      var foundHelper = this.nameLookup('helpers', name, 'helper');

      return {
        params: params,
        paramsInit: paramsInit,
        name: foundHelper,
        callParams: ["depth0"].concat(params).join(", "),
        helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ")
      };
    },

    setupOptions: function(paramSize, params) {
      var options = [], contexts = [], types = [], param, inverse, program;

      options.push("hash:" + this.popStack());

      if (this.options.stringParams) {
        options.push("hashTypes:" + this.popStack());
        options.push("hashContexts:" + this.popStack());
      }

      inverse = this.popStack();
      program = this.popStack();

      // Avoid setting fn and inverse if neither are set. This allows
      // helpers to do a check for `if (options.fn)`
      if (program || inverse) {
        if (!program) {
          this.context.aliases.self = "this";
          program = "self.noop";
        }

        if (!inverse) {
          this.context.aliases.self = "this";
          inverse = "self.noop";
        }

        options.push("inverse:" + inverse);
        options.push("fn:" + program);
      }

      for(var i=0; i<paramSize; i++) {
        param = this.popStack();
        params.push(param);

        if(this.options.stringParams) {
          types.push(this.popStack());
          contexts.push(this.popStack());
        }
      }

      if (this.options.stringParams) {
        options.push("contexts:[" + contexts.join(",") + "]");
        options.push("types:[" + types.join(",") + "]");
      }

      if(this.options.data) {
        options.push("data:data");
      }

      return options;
    },

    // the params and contexts arguments are passed in arrays
    // to fill in
    setupParams: function(paramSize, params, useRegister) {
      var options = '{' + this.setupOptions(paramSize, params).join(',') + '}';

      if (useRegister) {
        this.useRegister('options');
        params.push('options');
        return 'options=' + options;
      } else {
        params.push(options);
        return '';
      }
    }
  };

  var reservedWords = (
    "break else new var" +
    " case finally return void" +
    " catch for switch while" +
    " continue function this with" +
    " default if throw" +
    " delete in try" +
    " do instanceof typeof" +
    " abstract enum int short" +
    " boolean export interface static" +
    " byte extends long super" +
    " char final native synchronized" +
    " class float package throws" +
    " const goto private transient" +
    " debugger implements protected volatile" +
    " double import public let yield"
  ).split(" ");

  var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};

  for(var i=0, l=reservedWords.length; i<l; i++) {
    compilerWords[reservedWords[i]] = true;
  }

  JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
    if(!JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name)) {
      return true;
    }
    return false;
  };

  __exports__ = JavaScriptCompiler;
  return __exports__;
})(__module2__, __module5__);

// handlebars.js
var __module0__ = (function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__) {
  "use strict";
  var __exports__;
  /*globals Handlebars: true */
  var Handlebars = __dependency1__;

  // Compiler imports
  var AST = __dependency2__;
  var Parser = __dependency3__.parser;
  var parse = __dependency3__.parse;
  var Compiler = __dependency4__.Compiler;
  var compile = __dependency4__.compile;
  var precompile = __dependency4__.precompile;
  var JavaScriptCompiler = __dependency5__;

  var _create = Handlebars.create;
  var create = function() {
    var hb = _create();

    hb.compile = function(input, options) {
      return compile(input, options, hb);
    };
    hb.precompile = function (input, options) {
      return precompile(input, options, hb);
    };

    hb.AST = AST;
    hb.Compiler = Compiler;
    hb.JavaScriptCompiler = JavaScriptCompiler;
    hb.Parser = Parser;
    hb.parse = parse;

    return hb;
  };

  Handlebars = create();
  Handlebars.create = create;

  __exports__ = Handlebars;
  return __exports__;
})(__module1__, __module7__, __module8__, __module10__, __module11__);

  return __module0__;
})();
;
/**
 * jQuery.Ruler v1.1
 * Add Photoshop-like rulers and mouse position to a container element using jQuery.
 * http://ruler.hilliuse.com
 * 
 * Dual licensed under the MIT and GPL licenses.
 * Copyright 2013 Hillius Ettinoffe http://hilliuse.com
 */
;(function( $ ){

	$.fn.ruler = function(options) {
	
		var defaults = {
			vRuleSize: 18,
			hRuleSize: 18
		};//defaults
		var settings = $.extend({},defaults,options);
		
		var hRule = '<div class="ruler hRule"></div>',
				vRule = '<div class="ruler vRule"></div>',
				corner = '<div class="ruler corner"></div>',
				vMouse = '<div class="vMouse"></div>',
				hMouse = '<div class="hMouse"></div>',
				mousePosBox = '<div class="mousePosBox">x: 50%, y: 50%</div>';
		
		
		
		return this.each(function() {
			var $this = $(this);
			
			// Attach rulers
			
			// Should not need 1 min padding-top of 1px but it does
			// will figure it out some other time

			if (settings.hRuleSize >= 0) {				
			    $(hRule).height(settings.hRuleSize).prependTo($('.site-wrapper'));//$this);
			}
			
			if (settings.vRuleSize >= 0) {
			    $(vRule).width(settings.vRuleSize).height($this.outerHeight()).prependTo($('.site-wrapper'));
			}

			if (settings.hRuleSize >= 0 && settings.vRuleSize >= 0) {
			    $(corner).appendTo($('.site-wrapper'));
			}
	
			
			var $hRule = $('.hRule');
			var $vRule = $('.vRule');
		
			// Horizontal ruler ticks
			var tickLabelPos = settings.vRuleSize;
			var newTickLabel = "";
			while ( tickLabelPos <= $hRule.width() - 18 ) {
				if ((( tickLabelPos - settings.vRuleSize ) %50 ) == 0 ) {
					newTickLabel = "<div class='tickLabel'>" + ( tickLabelPos - settings.vRuleSize ) + "</div>";
					$(newTickLabel).css( "left", tickLabelPos+"px" ).appendTo($hRule);
				} else if ((( tickLabelPos - settings.vRuleSize ) %10 ) == 0 ) {
					newTickLabel = "<div class='tickMajor'></div>";
					$(newTickLabel).css("left",tickLabelPos+"px").appendTo($hRule);
				}
				//else if (((tickLabelPos - settings.vRuleSize) % 5) == 0) {
				//	newTickLabel = "<div class='tickMinor'></div>";
				//	$(newTickLabel).css( "left", tickLabelPos+"px" ).appendTo($hRule);
				//}
				tickLabelPos = (tickLabelPos + 10);				
			}//hz ticks

			// Vertical ruler ticks
			tickLabelPos = 0;
			newTickLabel = "";
			while (tickLabelPos <= $vRule.height()) {
				if ((( tickLabelPos - 0 ) %50 ) == 0) {
					newTickLabel = "<div class='tickLabel'><span>" + ( tickLabelPos - 0 ) + "</span></div>";
					$(newTickLabel).css( "top", tickLabelPos+"px" ).appendTo($vRule);
				} else if (((tickLabelPos - 0)%10) == 0) {
					newTickLabel = "<div class='tickMajor'></div>";
					$(newTickLabel).css( "top", tickLabelPos+"px" ).appendTo($vRule);
				}
				//else if (((tickLabelPos - 0) % 5) == 0) {
				//	newTickLabel = "<div class='tickMinor'></div>";
				//	$(newTickLabel).css( "top", tickLabelPos+"px" ).appendTo($vRule);
				//}
				tickLabelPos = ( tickLabelPos + 10 );				
			}//vert ticks			
			
		});//each		
		
	};//ruler
})( jQuery );;
/*
* Jssor 18.0
* http://www.jssor.com/
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
* 
* TERMS OF USE - Jssor
* 
* Copyright 2014 Jssor
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
* 
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

var $JssorDebug$ = new function () {

    this.$DebugMode = true;

    // Methods

    this.$Log = function (msg, important) {
        var console = window.console || {};
        var debug = this.$DebugMode;

        if (debug && console.log) {
            console.log(msg);
        } else if (debug && important) {
            alert(msg);
        }
    };

    this.$Error = function (msg, e) {
        var console = window.console || {};
        var debug = this.$DebugMode;

        if (debug && console.error) {
            console.error(msg);
        } else if (debug) {
            alert(msg);
        }

        if (debug) {
            // since we're debugging, fail fast by crashing
            throw e || new Error(msg);
        }
    };

    this.$Fail = function (msg) {
        throw new Error(msg);
    };

    this.$Assert = function (value, msg) {
        var debug = this.$DebugMode;
        if (debug) {
            if (!value)
                throw new Error("Assert failed " + msg || "");
        }
    };

    this.$Trace = function (msg) {
        var console = window.console || {};
        var debug = this.$DebugMode;

        if (debug && console.log) {
            console.log(msg);
        }
    };

    this.$Execute = function (func) {
        var debug = this.$DebugMode;
        if (debug)
            func();
    };

    this.$LiveStamp = function (obj, id) {
        var debug = this.$DebugMode;
        if (debug) {
            var stamp = document.createElement("DIV");
            stamp.setAttribute("id", id);

            obj.$Live = stamp;
        }
    };

    this.$C_AbstractMethod = function () {
        ///	<summary>
        ///		Tells compiler the method is abstract, it should be implemented by subclass.
        ///	</summary>

        throw new Error("The method is abstract, it should be implemented by subclass.");
    };

    function C_AbstractClass (instance) {
        ///	<summary>
        ///		Tells compiler the class is abstract, it should be implemented by subclass.
        ///	</summary>

        if(instance.constructor === C_AbstractClass.caller)
            throw new Error("Cannot create instance of an abstract class.");
    }

    this.$C_AbstractClass = C_AbstractClass;
};

//$JssorEasing$
var $JssorEasing$ = window.$JssorEasing$ = {
    $EaseLinear: function (t) {
        return t;
    },
    $EaseGoBack: function (t) {
        return 1 - Math.abs((t *= 2) - 1);
    },
    $EaseSwing: function (t) {
        return -Math.cos(t * Math.PI) / 2 + .5;
    },
    $EaseInQuad: function (t) {
        return t * t;
    },
    $EaseOutQuad: function (t) {
        return -t * (t - 2);
    },
    $EaseInOutQuad: function (t) {
        return (t *= 2) < 1 ? 1 / 2 * t * t : -1 / 2 * (--t * (t - 2) - 1);
    },
    $EaseInCubic: function (t) {
        return t * t * t;
    },
    $EaseOutCubic: function (t) {
        return (t -= 1) * t * t + 1;
    },
    $EaseInOutCubic: function (t) {
        return (t *= 2) < 1 ? 1 / 2 * t * t * t : 1 / 2 * ((t -= 2) * t * t + 2);
    },
    $EaseInQuart: function (t) {
        return t * t * t * t;
    },
    $EaseOutQuart: function (t) {
        return -((t -= 1) * t * t * t - 1);
    },
    $EaseInOutQuart: function (t) {
        return (t *= 2) < 1 ? 1 / 2 * t * t * t * t : -1 / 2 * ((t -= 2) * t * t * t - 2);
    },
    $EaseInQuint: function (t) {
        return t * t * t * t * t;
    },
    $EaseOutQuint: function (t) {
        return (t -= 1) * t * t * t * t + 1;
    },
    $EaseInOutQuint: function (t) {
        return (t *= 2) < 1 ? 1 / 2 * t * t * t * t * t : 1 / 2 * ((t -= 2) * t * t * t * t + 2);
    },
    $EaseInSine: function (t) {
        return 1 - Math.cos(t * Math.PI / 2);
    },
    $EaseOutSine: function (t) {
        return Math.sin(t * Math.PI / 2);
    },
    $EaseInOutSine: function (t) {
        return -1 / 2 * (Math.cos(Math.PI * t) - 1);
    },
    $EaseInExpo: function (t) {
        return t == 0 ? 0 : Math.pow(2, 10 * (t - 1));
    },
    $EaseOutExpo: function (t) {
        return t == 1 ? 1 : -Math.pow(2, -10 * t) + 1;
    },
    $EaseInOutExpo: function (t) {
        return t == 0 || t == 1 ? t : (t *= 2) < 1 ? 1 / 2 * Math.pow(2, 10 * (t - 1)) : 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
    },
    $EaseInCirc: function (t) {
        return -(Math.sqrt(1 - t * t) - 1);
    },
    $EaseOutCirc: function (t) {
        return Math.sqrt(1 - (t -= 1) * t);
    },
    $EaseInOutCirc: function (t) {
        return (t *= 2) < 1 ? -1 / 2 * (Math.sqrt(1 - t * t) - 1) : 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);
    },
    $EaseInElastic: function (t) {
        if (!t || t == 1)
            return t;
        var p = .3, s = .075;
        return -(Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * 2 * Math.PI / p));
    },
    $EaseOutElastic: function (t) {
        if (!t || t == 1)
            return t;
        var p = .3, s = .075;
        return Math.pow(2, -10 * t) * Math.sin((t - s) * 2 * Math.PI / p) + 1;
    },
    $EaseInOutElastic: function (t) {
        if (!t || t == 1)
            return t;
        var p = .45, s = .1125;
        return (t *= 2) < 1 ? -.5 * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * 2 * Math.PI / p) : Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * 2 * Math.PI / p) * .5 + 1;
    },
    $EaseInBack: function (t) {
        var s = 1.70158;
        return t * t * ((s + 1) * t - s);
    },
    $EaseOutBack: function (t) {
        var s = 1.70158;
        return (t -= 1) * t * ((s + 1) * t + s) + 1;
    },
    $EaseInOutBack: function (t) {
        var s = 1.70158;
        return (t *= 2) < 1 ? 1 / 2 * t * t * (((s *= 1.525) + 1) * t - s) : 1 / 2 * ((t -= 2) * t * (((s *= 1.525) + 1) * t + s) + 2);
    },
    $EaseInBounce: function (t) {
        return 1 - $JssorEasing$.$EaseOutBounce(1 - t)
    },
    $EaseOutBounce: function (t) {
        return t < 1 / 2.75 ? 7.5625 * t * t : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .75 : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .9375 : 7.5625 * (t -= 2.625 / 2.75) * t + .984375;
    },
    $EaseInOutBounce: function (t) {
        return t < 1 / 2 ? $JssorEasing$.$EaseInBounce(t * 2) * .5 : $JssorEasing$.$EaseOutBounce(t * 2 - 1) * .5 + .5;
    },
    $EaseInWave: function (t) {
        return 1 - Math.cos(t * Math.PI * 2)
    },
    $EaseOutWave: function (t) {
        return Math.sin(t * Math.PI * 2);
    },
    $EaseOutJump: function (t) {
        return 1 - (((t *= 2) < 1) ? (t = 1 - t) * t * t : (t -= 1) * t * t);
    },
    $EaseInJump: function (t) {
        return ((t *= 2) < 1) ? t * t * t : (t = 2 - t) * t * t;
    }
};

var $JssorDirection$ = window.$JssorDirection$ = {
    $TO_LEFT: 0x0001,
    $TO_RIGHT: 0x0002,
    $TO_TOP: 0x0004,
    $TO_BOTTOM: 0x0008,
    $HORIZONTAL: 0x0003,
    $VERTICAL: 0x000C,
    $LEFTRIGHT: 0x0003,
    $TOPBOTOM: 0x000C,
    $TOPLEFT: 0x0005,
    $TOPRIGHT: 0x0006,
    $BOTTOMLEFT: 0x0009,
    $BOTTOMRIGHT: 0x000A,
    $AROUND: 0x000F,

    $GetDirectionHorizontal: function (direction) {
        return direction & 0x0003;
    },
    $GetDirectionVertical: function (direction) {
        return direction & 0x000C;
    },
    $ChessHorizontal: function (direction) {
        return (~direction & 0x0003) + (direction & 0x000C);
    },
    $ChessVertical: function (direction) {
        return (~direction & 0x000C) + (direction & 0x0003);
    },
    $IsToLeft: function (direction) {
        return (direction & 0x0003) == 0x0001;
    },
    $IsToRight: function (direction) {
        return (direction & 0x0003) == 0x0002;
    },
    $IsToTop: function (direction) {
        return (direction & 0x000C) == 0x0004;
    },
    $IsToBottom: function (direction) {
        return (direction & 0x000C) == 0x0008;
    },
    $IsHorizontal: function (direction) {
        return (direction & 0x0003) > 0;
    },
    $IsVertical: function (direction) {
        return (direction & 0x000C) > 0;
    }
};

var $JssorKeyCode$ = {
    $BACKSPACE: 8,
    $COMMA: 188,
    $DELETE: 46,
    $DOWN: 40,
    $END: 35,
    $ENTER: 13,
    $ESCAPE: 27,
    $HOME: 36,
    $LEFT: 37,
    $NUMPAD_ADD: 107,
    $NUMPAD_DECIMAL: 110,
    $NUMPAD_DIVIDE: 111,
    $NUMPAD_ENTER: 108,
    $NUMPAD_MULTIPLY: 106,
    $NUMPAD_SUBTRACT: 109,
    $PAGE_DOWN: 34,
    $PAGE_UP: 33,
    $PERIOD: 190,
    $RIGHT: 39,
    $SPACE: 32,
    $TAB: 9,
    $UP: 38
};

var $JssorAlignment$ = {
    $TopLeft: 0x11,
    $TopCenter: 0x12,
    $TopRight: 0x14,
    $MiddleLeft: 0x21,
    $MiddleCenter: 0x22,
    $MiddleRight: 0x24,
    $BottomLeft: 0x41,
    $BottomCenter: 0x42,
    $BottomRight: 0x44,

    $IsTop: function (aligment) {
        return aligment & 0x10 > 0;
    },
    $IsMiddle: function (alignment) {
        return alignment & 0x20 > 0;
    },
    $IsBottom: function (alignment) {
        return alignment & 0x40 > 0;
    },
    $IsLeft: function (alignment) {
        return alignment & 0x01 > 0;
    },
    $IsCenter: function (alignment) {
        return alignment & 0x02 > 0;
    },
    $IsRight: function (alignment) {
        return alignment & 0x04 > 0;
    }
};

var $JssorMatrix$;

var $JssorAnimator$;

// $Jssor$ is a static class, so make it singleton instance
var $Jssor$ = window.$Jssor$ = new function () {
    // Fields
    var _This = this;

    var REGEX_WHITESPACE_GLOBAL = /\S+/g;

    var ROWSER_UNKNOWN = 0;
    var BROWSER_IE = 1;
    var BROWSER_FIREFOX = 2;
    var BROWSER_FIREFOX = 3;
    var BROWSER_CHROME = 4;
    var BROWSER_OPERA = 5;

    //var arrActiveX = ["Msxml2.XMLHTTP", "Msxml3.XMLHTTP", "Microsoft.XMLHTTP"];

    var browser = 0;
    var browserRuntimeVersion = 0;
    var browserEngineVersion = 0;
    var browserJavascriptVersion = 0;
    var webkitVersion = 0;

    var app = navigator.appName;
    var ver = navigator.appVersion;
    var ua = navigator.userAgent;

    var _DocElmt = document.documentElement;
    var _TransformProperty;

    function DetectBrowser() {
        if (!browser) {
            if (app == "Microsoft Internet Explorer" &&
                !!window.attachEvent && !!window.ActiveXObject) {

                var ieOffset = ua.indexOf("MSIE");
                browser = BROWSER_IE;
                browserEngineVersion = ParseFloat(ua.substring(ieOffset + 5, ua.indexOf(";", ieOffset)));

                //check IE javascript version
                /*@cc_on
                browserJavascriptVersion = @_jscript_version;
                @*/

                // update: for intranet sites and compat view list sites, IE sends
                // an IE7 User-Agent to the server to be interoperable, and even if
                // the page requests a later IE version, IE will still report the
                // IE7 UA to JS. we should be robust to self
                //var docMode = document.documentMode;
                //if (typeof docMode !== "undefined") {
                //    browserRuntimeVersion = docMode;
                //}

                browserRuntimeVersion = document.documentMode || browserEngineVersion;

            }
            else if (app == "Netscape" && !!window.addEventListener) {

                var ffOffset = ua.indexOf("Firefox");
                var saOffset = ua.indexOf("Safari");
                var chOffset = ua.indexOf("Chrome");
                var webkitOffset = ua.indexOf("AppleWebKit");

                if (ffOffset >= 0) {
                    browser = BROWSER_FIREFOX;
                    browserRuntimeVersion = ParseFloat(ua.substring(ffOffset + 8));
                }
                else if (saOffset >= 0) {
                    var slash = ua.substring(0, saOffset).lastIndexOf("/");
                    browser = (chOffset >= 0) ? BROWSER_CHROME : BROWSER_FIREFOX;
                    browserRuntimeVersion = ParseFloat(ua.substring(slash + 1, saOffset));
                }

                if (webkitOffset >= 0)
                    webkitVersion = ParseFloat(ua.substring(webkitOffset + 12));
            }
            else {
                var match = /(opera)(?:.*version|)[ \/]([\w.]+)/i.exec(ua);
                if (match) {
                    browser = BROWSER_OPERA;
                    browserRuntimeVersion = ParseFloat(match[2]);
                }
            }
        }
    }

    function IsBrowserIE() {
        DetectBrowser();
        return browser == BROWSER_IE;
    }

    function IsBrowserIeQuirks() {
        return IsBrowserIE() && (browserRuntimeVersion < 6 || document.compatMode == "BackCompat");   //Composite to "CSS1Compat"
    }

    function IsBrowserFireFox() {
        DetectBrowser();
        return browser == BROWSER_FIREFOX;
    }

    function IsBrowserSafari() {
        DetectBrowser();
        return browser == BROWSER_FIREFOX;
    }

    function IsBrowserChrome() {
        DetectBrowser();
        return browser == BROWSER_CHROME;
    }

    function IsBrowserOpera() {
        DetectBrowser();
        return browser == BROWSER_OPERA;
    }

    function IsBrowserBadTransform() {
        return IsBrowserSafari() && (webkitVersion > 534) && (webkitVersion < 535);
    }

    function IsBrowserIe9Earlier() {
        return IsBrowserIE() && browserRuntimeVersion < 9; 
    }

    function GetTransformProperty(elmt) {

        if (!_TransformProperty) {
            // Note that in some versions of IE9 it is critical that
            // msTransform appear in this list before MozTransform

            each(['transform', 'WebkitTransform', 'msTransform', 'MozTransform', 'OTransform'], function (property) {
                if (elmt.style[property] != undefined) {
                    _TransformProperty = property;
                    return true;
                }
            });

            _TransformProperty = _TransformProperty || "transform";
        }

        return _TransformProperty;
    }

    // Helpers
    function getOffsetParent(elmt, isFixed) {
        // IE and Opera "fixed" position elements don't have offset parents.
        // regardless, if it's fixed, its offset parent is the body.
        if (isFixed && elmt != document.body) {
            return document.body;
        } else {
            return elmt.offsetParent;
        }
    }

    function toString(obj) {
        return Object.prototype.toString.call(obj);
    }

    // [[Class]] -> type pairs
    var class2type;

    function each(object, callback) {
        if (toString(object) == "[object Array]") {
            for (var i = 0; i < object.length; i++) {
                if (callback(object[i], i, object)) {
                    return true;
                }
            }
        }
        else {
            for (var name in object) {
                if (callback(object[name], name, object)) {
                    return true;
                }
            }
        }
    }

    function GetClass2Type() {
        if (!class2type) {
            class2type = {};
            each(["Boolean", "Number", "String", "Function", "Array", "Date", "RegExp", "Object"], function (name) {
                class2type["[object " + name + "]"] = name.toLowerCase();
            });
        }

        return class2type;
    }

    function type(obj) {
        return obj == null ? String(obj) : GetClass2Type()[toString(obj)] || "object";
    }

    function isPlainObject(obj) {
        // Must be an Object.
        // Because of IE, we also have to check the presence of the constructor property.
        // Make sure that DOM nodes and window objects don't pass through, as well
        if (!obj || type(obj) !== "object" || obj.nodeType || _This.$IsWindow(obj)) {
            return false;
        }

        var hasOwn = Object.prototype.hasOwnProperty;

        try {
            // Not own constructor property must be Object
            if (obj.constructor &&
				!hasOwn.call(obj, "constructor") &&
				!hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {
                return false;
            }
        } catch (e) {
            // IE8,9 Will throw exceptions on certain host objects #9897
            return false;
        }

        // Own properties are enumerated firstly, so to speed up,
        // if last one is own, then all properties are own.

        var key;
        for (key in obj) { }

        return key === undefined || hasOwn.call(obj, key);
    }

    function Point(x, y) {
        return { x: x, y: y };
    }

    function Delay(code, delay) {
        setTimeout(code, delay || 0);
    }

    function RemoveByReg(str, reg) {
        var m = reg.exec(str);

        if (m) {
            var header = str.substr(0, m.index);
            var tailer = str.substr(m.lastIndex + 1, str.length - (m.lastIndex + 1));
            str = header + tailer;
        }

        return str;
    }

    function BuildNewCss(oldCss, removeRegs, replaceValue) {
        var css = (!oldCss || oldCss == "inherit") ? "" : oldCss;

        each(removeRegs, function (removeReg) {
            var m = removeReg.exec(css);

            if (m) {
                var header = css.substr(0, m.index);
                var tailer = css.substr(m.lastIndex + 1, css.length - (m.lastIndex + 1));
                css = header + tailer;
            }
        });

        css = replaceValue + (css.indexOf(" ") != 0 ? " " : "") + css;

        return css;
    }

    function SetStyleFilterIE(elmt, value) {
        if (browserRuntimeVersion < 9) {
            elmt.style.filter = value;
        }
    }

    function SetStyleMatrixIE(elmt, matrix, offset) {
        //matrix is not for ie9+ running in ie8- mode
        if (browserJavascriptVersion < 9) {
            var oldFilterValue = elmt.style.filter;
            var matrixReg = new RegExp(/[\s]*progid:DXImageTransform\.Microsoft\.Matrix\([^\)]*\)/g);
            var matrixValue = matrix ? "progid:DXImageTransform.Microsoft.Matrix(" + "M11=" + matrix[0][0] + ", M12=" + matrix[0][1] + ", M21=" + matrix[1][0] + ", M22=" + matrix[1][1] + ", SizingMethod='auto expand')" : "";

            var newFilterValue = BuildNewCss(oldFilterValue, [matrixReg], matrixValue);

            SetStyleFilterIE(elmt, newFilterValue);

            _This.$CssMarginTop(elmt, offset.y);
            _This.$CssMarginLeft(elmt, offset.x);
        }
    }

    // Methods

    _This.$IsBrowserIE = IsBrowserIE;

    _This.$IsBrowserIeQuirks = IsBrowserIeQuirks;

    _This.$IsBrowserFireFox = IsBrowserFireFox;

    _This.$IsBrowserSafari = IsBrowserSafari;

    _This.$IsBrowserChrome = IsBrowserChrome;

    _This.$IsBrowserOpera = IsBrowserOpera;

    _This.$IsBrowserBadTransform = IsBrowserBadTransform;

    _This.$IsBrowserIe9Earlier = IsBrowserIe9Earlier;

    _This.$BrowserVersion = function () {
        return browserRuntimeVersion;
    };

    _This.$BrowserEngineVersion = function () {
        return browserEngineVersion || browserRuntimeVersion;
    };

    _This.$WebKitVersion = function () {
        DetectBrowser();

        return webkitVersion;
    };

    _This.$Delay = Delay;

    _This.$Inherit = function (instance, baseClass) {
        baseClass.apply(instance, [].slice.call(arguments, 2));
        return Extend({}, instance);
    };

    function Construct(instance, constructor) {
        instance.constructor === Construct.caller && instance.$Construct && instance.$Construct();
    }

    _This.$Construct = Construct;

    _This.$GetElement = function (elmt) {
        if (_This.$IsString(elmt)) {
            elmt = document.getElementById(elmt);
        }

        return elmt;
    };

    function GetEvent(event) {
        return event || window.event;
    }

    _This.$GetEvent = GetEvent;

    _This.$EventSrc = function (event) {
        event = GetEvent(event);
        return event.target || event.srcElement || document;
    };

    _This.$EventDst = function (event) {
        event = GetEvent(event);
        return event.relatedTarget || event.toElement;
    };

    _This.$MousePosition = function (event) {
        event = GetEvent(event);
        var body = document.body;

        return {
            x: event.pageX || event.clientX + (_DocElmt.scrollLeft || body.scrollLeft || 0) - (_DocElmt.clientLeft || body.clientLeft || 0) || 0,
            y: event.pageY || event.clientY + (_DocElmt.scrollTop || body.scrollTop || 0) - (_DocElmt.clientTop || body.clientTop || 0) || 0
        };
    };

    _This.$PageScroll = function () {
        var body = document.body;

        return {
            x: (window.pageXOffset || _DocElmt.scrollLeft || body.scrollLeft || 0) - (_DocElmt.clientLeft || body.clientLeft || 0),
            y: (window.pageYOffset || _DocElmt.scrollTop || body.scrollTop || 0) - (_DocElmt.clientTop || body.clientTop || 0)
        };
    };

    _This.$WindowSize = function () {
        var body = document.body;

        return {
            x: body.clientWidth || _DocElmt.clientWidth,
            y: body.clientHeight || _DocElmt.clientHeight
        };
    };

    //_This.$GetElementPosition = function (elmt) {
    //    elmt = _This.$GetElement(elmt);
    //    var result = Point();

    //    // technique from:
    //    // http://www.quirksmode.org/js/findpos.html
    //    // with special check for "fixed" elements.

    //    while (elmt) {
    //        result.x += elmt.offsetLeft;
    //        result.y += elmt.offsetTop;

    //        var isFixed = _This.$GetElementStyle(elmt).position == "fixed";

    //        if (isFixed) {
    //            result = result.$Plus(_This.$PageScroll(window));
    //        }

    //        elmt = getOffsetParent(elmt, isFixed);
    //    }

    //    return result;
    //};

    //_This.$GetMouseScroll = function (event) {
    //    event = GetEvent(event);
    //    var delta = 0; // default value

    //    // technique from:
    //    // http://blog.paranoidferret.com/index.php/2007/10/31/javascript-tutorial-the-scroll-wheel/

    //    if (typeof (event.wheelDelta) == "number") {
    //        delta = event.wheelDelta;
    //    } else if (typeof (event.detail) == "number") {
    //        delta = event.detail * -1;
    //    } else {
    //        $JssorDebug$.$Fail("Unknown event mouse scroll, no known technique.");
    //    }

    //    // normalize value to [-1, 1]
    //    return delta ? delta / Math.abs(delta) : 0;
    //};

    //_This.$MakeAjaxRequest = function (url, callback) {
    //    var async = typeof (callback) == "function";
    //    var req = null;

    //    if (async) {
    //        var actual = callback;
    //        var callback = function () {
    //            Delay($Jssor$.$CreateCallback(null, actual, req), 1);
    //        };
    //    }

    //    if (window.ActiveXObject) {
    //        for (var i = 0; i < arrActiveX.length; i++) {
    //            try {
    //                req = new ActiveXObject(arrActiveX[i]);
    //                break;
    //            } catch (e) {
    //                continue;
    //            }
    //        }
    //    } else if (window.XMLHttpRequest) {
    //        req = new XMLHttpRequest();
    //    }

    //    if (!req) {
    //        $JssorDebug$.$Fail("Browser doesn't support XMLHttpRequest.");
    //    }

    //    if (async) {
    //        req.onreadystatechange = function () {
    //            if (req.readyState == 4) {
    //                // prevent memory leaks by breaking circular reference now
    //                req.onreadystatechange = new Function();
    //                callback();
    //            }
    //        };
    //    }

    //    try {
    //        req.open("GET", url, async);
    //        req.send(null);
    //    } catch (e) {
    //        $JssorDebug$.$Log(e.name + " while making AJAX request: " + e.message);

    //        req.onreadystatechange = null;
    //        req = null;

    //        if (async) {
    //            callback();
    //        }
    //    }

    //    return async ? null : req;
    //};

    //_This.$ParseXml = function (string) {
    //    var xmlDoc = null;

    //    if (window.ActiveXObject) {
    //        try {
    //            xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
    //            xmlDoc.async = false;
    //            xmlDoc.loadXML(string);
    //        } catch (e) {
    //            $JssorDebug$.$Log(e.name + " while parsing XML (ActiveX): " + e.message);
    //        }
    //    } else if (window.DOMParser) {
    //        try {
    //            var parser = new DOMParser();
    //            xmlDoc = parser.parseFromString(string, "text/xml");
    //        } catch (e) {
    //            $JssorDebug$.$Log(e.name + " while parsing XML (DOMParser): " + e.message);
    //        }
    //    } else {
    //        $JssorDebug$.$Fail("Browser doesn't support XML DOM.");
    //    }

    //    return xmlDoc;
    //};

    function Css(elmt, name, value) {
        ///	<summary>
        ///		access css
        ///     $Jssor$.$Css(elmt, name);         //get css value
        ///     $Jssor$.$Css(elmt, name, value);  //set css value
        ///	</summary>
        ///	<param name="elmt" type="HTMLElement">
        ///		the element to access css
        ///	</param>
        ///	<param name="name" type="String">
        ///		the name of css property
        ///	</param>
        ///	<param name="value" optional="true">
        ///		the value to set
        ///	</param>
        if (value != undefined) {
            elmt.style[name] = value;
        }
        else {
            var style = elmt.currentStyle || elmt.style;
            value = style[name];

            if (value == "" && window.getComputedStyle) {
                style = elmt.ownerDocument.defaultView.getComputedStyle(elmt, null);

                style && (value = style.getPropertyValue(name) || style[name]);
            }

            return value;
        }
    }

    function CssN(elmt, name, value, isDimensional) {
        ///	<summary>
        ///		access css as numeric
        ///     $Jssor$.$CssN(elmt, name);         //get css value
        ///     $Jssor$.$CssN(elmt, name, value);  //set css value
        ///	</summary>
        ///	<param name="elmt" type="HTMLElement">
        ///		the element to access css
        ///	</param>
        ///	<param name="name" type="String">
        ///		the name of css property
        ///	</param>
        ///	<param name="value" type="Number" optional="true">
        ///		the value to set
        ///	</param>
        if (value != undefined) {
            isDimensional && (value += "px");
            Css(elmt, name, value);
        }
        else {
            return ParseFloat(Css(elmt, name));
        }
    }

    function CssP(elmt, name, value) {
        ///	<summary>
        ///		access css in pixel as numeric, like 'top', 'left', 'width', 'height'
        ///     $Jssor$.$CssP(elmt, name);         //get css value
        ///     $Jssor$.$CssP(elmt, name, value);  //set css value
        ///	</summary>
        ///	<param name="elmt" type="HTMLElement">
        ///		the element to access css
        ///	</param>
        ///	<param name="name" type="String">
        ///		the name of css property
        ///	</param>
        ///	<param name="value" type="Number" optional="true">
        ///		the value to set
        ///	</param>
        return CssN(elmt, name, value, true);
    }

    function CssProxy(name, numericOrDimension) {
        ///	<summary>
        ///		create proxy to access css, CssProxy(name[, numericOrDimension]);
        ///	</summary>
        ///	<param name="elmt" type="HTMLElement">
        ///		the element to access css
        ///	</param>
        ///	<param name="numericOrDimension" type="Number" optional="true">
        ///		not set: access original css, 1: access css as numeric, 2: access css in pixel as numeric
        ///	</param>
        var isDimensional = numericOrDimension & 2;
        var cssAccessor = numericOrDimension ? CssN : Css;
        return function (elmt, value) {
            return cssAccessor(elmt, name, value, isDimensional);
        };
    }

    function GetStyleOpacity(elmt) {
        if (IsBrowserIE() && browserEngineVersion < 9) {
            var match = /opacity=([^)]*)/.exec(elmt.style.filter || "");
            return match ? (ParseFloat(match[1]) / 100) : 1;
        }
        else
            return ParseFloat(elmt.style.opacity || "1");
    }

    function SetStyleOpacity(elmt, opacity, ie9EarlierForce) {

        if (IsBrowserIE() && browserEngineVersion < 9) {
            //var filterName = "filter"; // browserEngineVersion < 8 ? "filter" : "-ms-filter";
            var finalFilter = elmt.style.filter || "";

            // for CSS filter browsers (IE), remove alpha filter if it's unnecessary.
            // update: doing _This always since IE9 beta seems to have broken the
            // behavior if we rely on the programmatic filters collection.
            var alphaReg = new RegExp(/[\s]*alpha\([^\)]*\)/g);

            // important: note the lazy star! _This protects against
            // multiple filters; we don't want to delete the other ones.
            // update: also trimming extra whitespace around filter.

            var ieOpacity = Math.round(100 * opacity);
            var alphaFilter = "";
            if (ieOpacity < 100 || ie9EarlierForce) {
                alphaFilter = "alpha(opacity=" + ieOpacity + ") ";
                //elmt.style["-ms-filter"] = "progid:DXImageTransform.Microsoft.Alpha(opacity=" + ieOpacity + ") ";
            }

            var newFilterValue = BuildNewCss(finalFilter, [alphaReg], alphaFilter);

            SetStyleFilterIE(elmt, newFilterValue);
        }

            //if (!IsBrowserIE() || browserEngineVersion >= 9) 
        else {
            elmt.style.opacity = opacity == 1 ? "" : Math.round(opacity * 100) / 100;
        }
    }

    function SetStyleTransformInternal(elmt, transform) {
        var rotate = transform.$Rotate || 0;
        var scale = transform.$Scale == undefined ? 1 : transform.$Scale;

        if (IsBrowserIe9Earlier()) {
            var matrix = _This.$CreateMatrix(rotate / 180 * Math.PI, scale, scale);
            SetStyleMatrixIE(elmt, (!rotate && scale == 1) ? null : matrix, _This.$GetMatrixOffset(matrix, transform.$OriginalWidth, transform.$OriginalHeight));
        }
        else {
            //rotate(15deg) scale(.5) translateZ(0)
            var transformProperty = GetTransformProperty(elmt);
            if (transformProperty) {
                var transformValue = "rotate(" + rotate % 360 + "deg) scale(" + scale + ")";

                //needed for touch device, no need for desktop device
                if (IsBrowserChrome() && webkitVersion > 535 && "ontouchstart" in window)
                    transformValue += " perspective(2000px)";

                elmt.style[transformProperty] = transformValue;
            }
        }
    }

    _This.$SetStyleTransform = function (elmt, transform) {
        if (IsBrowserBadTransform()) {
            Delay(_This.$CreateCallback(null, SetStyleTransformInternal, elmt, transform));
        }
        else {
            SetStyleTransformInternal(elmt, transform);
        }
    };

    _This.$SetStyleTransformOrigin = function (elmt, transformOrigin) {
        var transformProperty = GetTransformProperty(elmt);

        if (transformProperty)
            elmt.style[transformProperty + "Origin"] = transformOrigin;
    };

    _This.$CssScale = function (elmt, scale) {

        if (IsBrowserIE() && browserEngineVersion < 9 || (browserEngineVersion < 10 && IsBrowserIeQuirks())) {
            elmt.style.zoom = (scale == 1) ? "" : scale;
        }
        else {
            var transformProperty = GetTransformProperty(elmt);

            if (transformProperty) {
                //rotate(15deg) scale(.5)
                var transformValue = "scale(" + scale + ")";

                var oldTransformValue = elmt.style[transformProperty];
                var scaleReg = new RegExp(/[\s]*scale\(.*?\)/g);

                var newTransformValue = BuildNewCss(oldTransformValue, [scaleReg], transformValue);

                elmt.style[transformProperty] = newTransformValue;
            }
        }
    };

    _This.$EnableHWA = function (elmt) {
        if (!elmt.style[GetTransformProperty(elmt)] || elmt.style[GetTransformProperty(elmt)] == "none")
            elmt.style[GetTransformProperty(elmt)] = "perspective(2000px)";
    };

    _This.$DisableHWA = function (elmt) {
        //if (force || elmt.style[GetTransformProperty(elmt)] == "perspective(2000px)")
        elmt.style[GetTransformProperty(elmt)] = "none";
    };

    var ie8OffsetWidth = 0;
    var ie8OffsetHeight = 0;
    //var ie8WindowResizeCallbackHandlers;
    //var ie8LastVerticalScrollbar;
    //var toggleInfo = "";

    //function Ie8WindowResizeFilter(window, handler) {

    //    var trigger = true;

    //    var checkElement = (IsBrowserIeQuirks() ? window.document.body : window.document.documentElement);
    //    if (checkElement) {
    //        //check vertical bar
    //        //var hasVerticalBar = checkElement.scrollHeight > checkElement.clientHeight;
    //        //var verticalBarToggle = hasVerticalBar != ie8LastVerticalScrollbar;
    //        //ie8LastVerticalScrollbar = hasVerticalBar;

    //        var widthChange = checkElement.offsetWidth - ie8OffsetWidth;
    //        var heightChange = checkElement.offsetHeight - ie8OffsetHeight;
    //        if (widthChange || heightChange) {

    //            ie8OffsetWidth += widthChange;
    //            ie8OffsetHeight += heightChange;
    //        }
    //        else
    //            trigger = false;
    //    }

    //    trigger && handler();
    //}

    //_This.$OnWindowResize = function (window, handler) {

    //    if (IsBrowserIE() && browserEngineVersion < 9) {
    //        if (!ie8WindowResizeCallbackHandlers) {
    //            ie8WindowResizeCallbackHandlers = [handler];
    //            handler = _This.$CreateCallback(null, Ie8WindowResizeFilter, window);
    //        }
    //        else {
    //            ie8WindowResizeCallbackHandlers.push(handler);
    //            return;
    //        }
    //    }

    //    _This.$AddEvent(window, "resize", handler);
    //};

    _This.$WindowResizeFilter = function (window, handler) {
        return IsBrowserIe9Earlier() ? function () {

            var trigger = true;

            var checkElement = (IsBrowserIeQuirks() ? window.document.body : window.document.documentElement);
            if (checkElement) {
                //check vertical bar
                //var hasVerticalBar = checkElement.scrollHeight > checkElement.clientHeight;
                //var verticalBarToggle = hasVerticalBar != ie8LastVerticalScrollbar;
                //ie8LastVerticalScrollbar = hasVerticalBar;

                var widthChange = checkElement.offsetWidth - ie8OffsetWidth;
                var heightChange = checkElement.offsetHeight - ie8OffsetHeight;
                if (widthChange || heightChange) {
                    ie8OffsetWidth += widthChange;
                    ie8OffsetHeight += heightChange;
                }
                else
                    trigger = false;
            }

            trigger && handler();

        } : handler;
    };

    _This.$MouseOverOutFilter = function (handler, target) {
        ///	<param name="target" type="HTMLDomElement">
        ///		The target element to detect mouse over/out events. (for ie < 9 compatibility)
        ///	</param>

        $JssorDebug$.$Execute(function () {
            if (!target) {
                throw new Error("Null reference, parameter \"target\".");
            }
        });

        return function (event) {
            event = GetEvent(event);

            var eventName = event.type;
            var related = event.relatedTarget || (eventName == "mouseout" ? event.toElement : event.fromElement);

            if (!related || (related !== target && !_This.$IsChild(target, related))) {
                handler(event);
            }
        };
    };

    _This.$AddEvent = function (elmt, eventName, handler, useCapture) {
        elmt = _This.$GetElement(elmt);

        // technique from:
        // http://blog.paranoidferret.com/index.php/2007/08/10/javascript-working-with-events/

        if (elmt.addEventListener) {
            if (eventName == "mousewheel") {
                elmt.addEventListener("DOMMouseScroll", handler, useCapture);
            }
            // we are still going to add the mousewheel -- not a mistake!
            // _This is for opera, since it uses onmousewheel but needs addEventListener.
            elmt.addEventListener(eventName, handler, useCapture);
        }
        else if (elmt.attachEvent) {
            elmt.attachEvent("on" + eventName, handler);
            if (useCapture && elmt.setCapture) {
                elmt.setCapture();
            }
        }

        $JssorDebug$.$Execute(function () {
            if (!elmt.addEventListener && !elmt.attachEvent) {
                $JssorDebug$.$Fail("Unable to attach event handler, no known technique.");
            }
        });

    };

    _This.$RemoveEvent = function (elmt, eventName, handler, useCapture) {
        elmt = _This.$GetElement(elmt);

        // technique from:
        // http://blog.paranoidferret.com/index.php/2007/08/10/javascript-working-with-events/

        if (elmt.removeEventListener) {
            if (eventName == "mousewheel") {
                elmt.removeEventListener("DOMMouseScroll", handler, useCapture);
            }
            // we are still going to remove the mousewheel -- not a mistake!
            // _This is for opera, since it uses onmousewheel but needs removeEventListener.
            elmt.removeEventListener(eventName, handler, useCapture);
        }
        else if (elmt.detachEvent) {
            elmt.detachEvent("on" + eventName, handler);
            if (useCapture && elmt.releaseCapture) {
                elmt.releaseCapture();
            }
        }
    };

    _This.$FireEvent = function (elmt, eventName) {
        //var document = elmt.document;

        $JssorDebug$.$Execute(function () {
            if (!document.createEvent && !document.createEventObject) {
                $JssorDebug$.$Fail("Unable to fire event, no known technique.");
            }

            if (!elmt.dispatchEvent && !elmt.fireEvent) {
                $JssorDebug$.$Fail("Unable to fire event, no known technique.");
            }
        });

        var evento;

        if (document.createEvent) {
            evento = document.createEvent("HTMLEvents");
            evento.initEvent(eventName, false, false);
            elmt.dispatchEvent(evento);
        }
        else {
            var ieEventName = "on" + eventName;
            evento = document.createEventObject();
            //event.eventType = ieEventName;
            //event.eventName = ieEventName;

            elmt.fireEvent(ieEventName, evento);
        }
    };

    _This.$AddEventBrowserMouseUp = function (handler, userCapture) {
        _This.$AddEvent((IsBrowserIe9Earlier()) ? document : window, "mouseup", handler, userCapture);
    };

    _This.$RemoveEventBrowserMouseUp = function (handler, userCapture) {
        _This.$RemoveEvent((IsBrowserIe9Earlier()) ? document : window, "mouseup", handler, userCapture);
    };

    //_This.$AddEventBrowserMouseDown = function (handler, userCapture) {
    //    _This.$AddEvent((IsBrowserIe9Earlier()) ? document : window, "mousedown", handler, userCapture);
    //};

    //_This.$RemoveEventBrowserMouseDown = function (handler, userCapture) {
    //    _This.$RemoveEvent((IsBrowserIe9Earlier()) ? document : window, "mousedown", handler, userCapture);
    //};

    _This.$CancelEvent = function (event) {
        event = GetEvent(event);

        // technique from:
        // http://blog.paranoidferret.com/index.php/2007/08/10/javascript-working-with-events/

        if (event.preventDefault) {
            event.preventDefault();     // W3C for preventing default
        }

        event.cancel = true;            // legacy for preventing default
        event.returnValue = false;      // IE for preventing default
    };

    _This.$StopEvent = function (event) {
        event = GetEvent(event);

        // technique from:
        // http://blog.paranoidferret.com/index.php/2007/08/10/javascript-working-with-events/

        if (event.stopPropagation) {
            event.stopPropagation();    // W3C for stopping propagation
        }

        event.cancelBubble = true;      // IE for stopping propagation
    };

    _This.$CreateCallback = function (object, method) {
        // create callback args
        var initialArgs = [].slice.call(arguments, 2);

        // create closure to apply method
        var callback = function () {
            // concatenate new args, but make a copy of initialArgs first
            var args = initialArgs.concat([].slice.call(arguments, 0));

            return method.apply(object, args);
        };

        //$JssorDebug$.$LiveStamp(callback, "callback_" + ($Jssor$.$GetNow() & 0xFFFFFF));

        return callback;
    };

    var _Freeer;
    _This.$FreeElement = function (elmt) {
        if (!_Freeer)
            _Freeer = _This.$CreateDiv();

        if (elmt) {
            $Jssor$.$AppendChild(_Freeer, elmt);
            $Jssor$.$ClearInnerHtml(_Freeer);
        }
    };

    _This.$InnerText = function (elmt, text) {
        if (text == undefined)
            return elmt.textContent || elmt.innerText;

        var textNode = document.createTextNode(text);
        _This.$ClearInnerHtml(elmt);
        elmt.appendChild(textNode);
    };
    
    _This.$InnerHtml = function (elmt, html) {
        if (html == undefined)
            return elmt.innerHTML;

        elmt.innerHTML = html;
    };

    _This.$GetClientRect = function (elmt) {
        var rect = elmt.getBoundingClientRect();

        return { x: rect.left, y: rect.top, w: rect.right - rect.left, h: rect.bottom - rect.top };
    };

    _This.$ClearInnerHtml = function (elmt) {
        elmt.innerHTML = "";
    };

    _This.$EncodeHtml = function (text) {
        var div = _This.$CreateDiv();
        _This.$InnerText(div, text);
        return _This.$InnerHtml(div);
    };

    _This.$DecodeHtml = function (html) {
        var div = _This.$CreateDiv();
        _This.$InnerHtml(div, html);
        return _This.$InnerText(div);
    };

    _This.$SelectElement = function (elmt) {
        var userSelection;
        if (window.getSelection) {
            //W3C default
            userSelection = window.getSelection();
        }
        var theRange = null;
        if (document.createRange) {
            theRange = document.createRange();
            theRange.selectNode(elmt);
        }
        else {
            theRange = document.body.createTextRange();
            theRange.moveToElementText(elmt);
            theRange.select();
        }
        //set user selection
        if (userSelection)
            userSelection.addRange(theRange);
    };

    _This.$DeselectElements = function () {
        if (document.selection) {
            document.selection.empty();
        } else if (window.getSelection) {
            window.getSelection().removeAllRanges();
        }
    };

    _This.$Children = function (elmt) {
        var children = [];

        for (var tmpEl = elmt.firstChild; tmpEl; tmpEl = tmpEl.nextSibling) {
            if (tmpEl.nodeType == 1) {
                children.push(tmpEl);
            }
        }

        return children;
    };

    function FindChild(elmt, attrValue, noDeep, attrName) {
        attrName = attrName || "u";

        for (elmt = elmt ? elmt.firstChild : null; elmt; elmt = elmt.nextSibling) {
            if (elmt.nodeType == 1) {
                if (AttributeEx(elmt, attrName) == attrValue)
                    return elmt;

                if (!noDeep) {
                    var childRet = FindChild(elmt, attrValue, noDeep, attrName);
                    if (childRet)
                        return childRet;
                }
            }
        }
    }

    _This.$FindChild = FindChild;

    function FindChildren(elmt, attrValue, noDeep, attrName) {
        attrName = attrName || "u";

        var ret = [];

        for (elmt = elmt ? elmt.firstChild : null; elmt; elmt = elmt.nextSibling) {
            if (elmt.nodeType == 1) {
                if (AttributeEx(elmt, attrName) == attrValue)
                    ret.push(elmt);

                if (!noDeep) {
                    var childRet = FindChildren(elmt, attrValue, noDeep, attrName);
                    if (childRet.length)
                        ret = ret.concat(childRet);
                }
            }
        }

        return ret;
    }

    _This.$FindChildren = FindChildren;

    function FindChildByTag(elmt, tagName, noDeep) {

        for (elmt = elmt ? elmt.firstChild : null; elmt; elmt = elmt.nextSibling) {
            if (elmt.nodeType == 1) {
                if (elmt.tagName == tagName)
                    return elmt;

                if (!noDeep) {
                    var childRet = FindChildByTag(elmt, tagName, noDeep);
                    if (childRet)
                        return childRet;
                }
            }
        }
    }

    _This.$FindChildByTag = FindChildByTag;

    function FindChildrenByTag(elmt, tagName, noDeep) {
        var ret = [];

        for (elmt = elmt ? elmt.firstChild : null; elmt; elmt = elmt.nextSibling) {
            if (elmt.nodeType == 1) {
                if (!tagName || elmt.tagName == tagName)
                    ret.push(elmt);

                if (!noDeep) {
                    var childRet = FindChildrenByTag(elmt, tagName, noDeep);
                    if (childRet.length)
                        ret = ret.concat(childRet);
                }
            }
        }

        return ret;
    }

    _This.$FindChildrenByTag = FindChildrenByTag;

    _This.$GetElementsByTag = function (elmt, tagName) {
        return elmt.getElementsByTagName(tagName);
    };

    function Extend(target) {
        for (var i = 1; i < arguments.length; i++) {

            var options = arguments[i];

            // Only deal with non-null/undefined values
            if (options) {
                // Extend the base object
                for (var name in options) {
                    target[name] = options[name];
                }
            }
        }

        // Return the modified object
        return target;
    }

    _This.$Extend = Extend;

    function Unextend(target, options) {
        $JssorDebug$.$Assert(options);

        var unextended = {};

        // Extend the base object
        for (var name in target) {
            if (target[name] != options[name]) {
                unextended[name] = target[name];
            }
        }

        // Return the modified object
        return unextended;
    }

    _This.$Unextend = Unextend;

    _This.$IsUndefined = function (obj) {
        return type(obj) == "undefined";
    };

    _This.$IsFunction = function (obj) {
        return type(obj) == "function";
    };

    _This.$IsArray = function (obj) {
        return type(obj) == "array";
    };

    _This.$IsString = function (obj) {
        return type(obj) == "string";
    };

    _This.$IsNumeric = function (obj) {
        return !isNaN(ParseFloat(obj)) && isFinite(obj);
    };

    _This.$IsWindow = function (obj) {
        return obj && obj == obj.window;
    };

    _This.$Type = type;

    // args is for internal usage only
    _This.$Each = each;

    _This.$IsPlainObject = isPlainObject;

    function CreateElement(tagName) {
        return document.createElement(tagName);
    }

    _This.$CreateElement = CreateElement;

    _This.$CreateDiv = function () {
        return CreateElement("DIV", document);
    };

    _This.$CreateSpan = function () {
        return CreateElement("SPAN", document);
    };

    _This.$EmptyFunction = function () { };

    function Attribute(elmt, name, value) {
        if (value == undefined)
            return elmt.getAttribute(name);

        elmt.setAttribute(name, value);
    }

    function AttributeEx(elmt, name) {
        return Attribute(elmt, name) || Attribute(elmt, "data-" + name);
    }

    _This.$Attribute = Attribute;
    _This.$AttributeEx = AttributeEx;

    function ClassName(elmt, className) {
        if (className == undefined)
            return elmt.className;

        elmt.className = className;
    }

    _This.$ClassName = ClassName;

    function ToHash(array) {
        var hash = {};

        each(array, function (item) {
            hash[item] = item;
        });

        return hash;
    }

    _This.$ToHash = ToHash;

    function Join(separator, strings) {
        ///	<param name="separator" type="String">
        ///		The element to show the dialog around
        ///	</param>
        ///	<param name="strings" type="Array" value="['1']">
        ///		The element to show the dialog around
        ///	</param>

        var joined = "";

        each(strings, function (str) {
            joined && (joined += separator);
            joined += str;
        });

        return joined;
    }

    _This.$Join = Join;

    _This.$AddClass = function (elmt, className) {
        var newClassName = ClassName(elmt) + " " + className;
        ClassName(elmt, Join(" ", ToHash(newClassName.match(REGEX_WHITESPACE_GLOBAL))));
    };

    _This.$RemoveClass = function (elmt, className) {
        ClassName(elmt, Join(" ", _This.$Unextend(ToHash(ClassName(elmt).match(REGEX_WHITESPACE_GLOBAL)), ToHash(className.match(REGEX_WHITESPACE_GLOBAL)))));
    };

    _This.$ParentNode = function (elmt) {
        return elmt.parentNode;
    };

    _This.$HideElement = function (elmt) {
        _This.$CssDisplay(elmt, "none");
    };

    _This.$EnableElement = function (elmt, notEnable) {
        if (notEnable) {
            _This.$Attribute(elmt, "disabled", true);
        }
        else {
            _This.$RemoveAttribute(elmt, "disabled");
        }
    };

    _This.$HideElements = function (elmts) {
        for (var i = 0; i < elmts.length; i++) {
            _This.$HideElement(elmts[i]);
        }
    };

    _This.$ShowElement = function (elmt, hide) {
        _This.$CssDisplay(elmt, hide ? "none" : "");
    };

    _This.$ShowElements = function (elmts, hide) {
        for (var i = 0; i < elmts.length; i++) {
            _This.$ShowElement(elmts[i], hide);
        }
    };

    _This.$RemoveAttribute = function (elmt, attrbuteName) {
        elmt.removeAttribute(attrbuteName);
    };

    _This.$CanClearClip = function () {
        return IsBrowserIE() && browserRuntimeVersion < 10;
    };

    _This.$SetStyleClip = function (elmt, clip) {
        if (clip) {
            elmt.style.clip = "rect(" + Math.round(clip.$Top) + "px " + Math.round(clip.$Right) + "px " + Math.round(clip.$Bottom) + "px " + Math.round(clip.$Left) + "px)";
        }
        else {
            var cssText = elmt.style.cssText;
            var clipRegs = [
                new RegExp(/[\s]*clip: rect\(.*?\)[;]?/i),
                new RegExp(/[\s]*cliptop: .*?[;]?/i),
                new RegExp(/[\s]*clipright: .*?[;]?/i),
                new RegExp(/[\s]*clipbottom: .*?[;]?/i),
                new RegExp(/[\s]*clipleft: .*?[;]?/i)
            ];

            var newCssText = BuildNewCss(cssText, clipRegs, "");

            $Jssor$.$CssCssText(elmt, newCssText);
        }
    };

    _This.$GetNow = function () {
        return new Date().getTime();
    };

    _This.$AppendChild = function (elmt, child) {
        elmt.appendChild(child);
    };

    _This.$AppendChildren = function (elmt, children) {
        each(children, function (child) {
            _This.$AppendChild(elmt, child);
        });
    };

    _This.$InsertBefore = function (elmt, child, refObject) {
        elmt.insertBefore(child, refObject);
    };

    _This.$InsertAdjacentHtml = function (elmt, where, text) {
        elmt.insertAdjacentHTML(where, text);
    };

    _This.$RemoveChild = function (elmt, child) {
        elmt.removeChild(child);
    };

    _This.$RemoveChildren = function (elmt, children) {
        each(children, function (child) {
            _This.$RemoveChild(elmt, child);
        });
    };

    _This.$ClearChildren = function (elmt) {
        _This.$RemoveChildren(elmt, _This.$Children(elmt));
    };

    _This.$ParseInt = function (str, radix) {
        return parseInt(str, radix || 10);
    };

    function ParseFloat(str) {
        return parseFloat(str);
    }

    _This.$ParseFloat = ParseFloat;

    _This.$IsChild = function (elmtA, elmtB) {
        var body = document.body;
        while (elmtB && elmtA != elmtB && body != elmtB) {
            try {
                elmtB = elmtB.parentNode;
            } catch (e) {
                // Firefox sometimes fires events for XUL elements, which throws
                // a "permission denied" error. so this is not a child.
                return false;
            }
        }
        return elmtA == elmtB;
    };

    function CloneNode(elmt, noDeep) {
        return elmt.cloneNode(!noDeep);
    }

    _This.$CloneNode = CloneNode;

    function TranslateTransition(transition) {
        if (transition) {
            var flyDirection = transition.$FlyDirection;

            if (flyDirection & 1) {
                transition.x = transition.$ScaleHorizontal || 1;
            }
            if (flyDirection & 2) {
                transition.x = -transition.$ScaleHorizontal || -1;
            }
            if (flyDirection & 4) {
                transition.y = transition.$ScaleVertical || 1;
            }
            if (flyDirection & 8) {
                transition.y = -transition.$ScaleVertical || -1;
            }

            if (transition.$Rotate == true)
                transition.$Rotate = 1;

            TranslateTransition(transition.$Brother);
        }
    }

    _This.$TranslateTransitions = function (transitions) {
        ///	<summary>
        ///		For backward compatibility only.
        ///	</summary>
        if (transitions) {
            for (var i = 0; i < transitions.length; i++) {
                TranslateTransition(transitions[i]);
            }
            for (var name in transitions) {
                TranslateTransition(transitions[name]);
            }
        }
    };

    //function ImageLoader() {
    //    var _ThisImageLoader = this;
    //    var _BaseImageLoader = _This.$Inherit(_ThisImageLoader, $JssorObject$);

    //    var _ImageLoading = 1;
    //    var _MainImageSrc;
    //    var _MainImage;
    //    var _CompleteCallback;
    //    var _MainImageAbort;

    //    function LoadCompleteCallback(image, abort) {
    //        _ImageLoading--;

    //        if (image) {
    //            _This.$RemoveEvent(image, "load");
    //            _This.$RemoveEvent(image, "abort");
    //            _This.$RemoveEvent(image, "error");

    //            if (_MainImageSrc == image.src) {
    //                _MainImage = image;
    //                _MainImageAbort = abort;
    //            }
    //        }

    //        _CompleteCallback && _CompleteCallback(_MainImage, _MainImageAbort);
    //    }

    //    function LoadImage(src) {
    //        _ImageLoading++;

    //        if (IsBrowserOpera() && browserRuntimeVersion < 11.6 || !src) {
    //            LoadImageCallback(callback, null, !src);
    //        }
    //        else {
    //            var image = new Image();

    //            _This.$AddEvent(image, "load", _This.$CreateCallback(null, LoadImageCallback, image, false));

    //            var abortHandler = _This.$CreateCallback(null, LoadImageCallback, image, true);
    //            _This.$AddEvent(image, "abort", abortHandler);
    //            _This.$AddEvent(image, "error", abortHandler);

    //            image.src = src;
    //        }
    //    }

    //    _ThisImageLoader.$LoadImage = function (src, callback) {
    //        _MainImageSrc = src;
    //        _CompleteCallback = callback;

    //        LoadImage(src);
    //        LoadComplete();
    //    };

    //    _ThisImageLoader.$LoadImages = function (imageElmts, mainImageElmt, callback) {
    //        mainImageElmt && (_MainImageSrc = mainImageElmt.src);
    //        _CompleteCallback = callback;

    //        each(imageElmts, function (imageElmt) {
    //            LoadImage(imageElmt.src);
    //        });
    //        LoadComplete();
    //    };
    //}

    _This.$LoadImage = function (src, callback) {
        var image = new Image();

        function LoadImageCompleteHandler(abort) {
            _This.$RemoveEvent(image, "load", LoadImageCompleteHandler);
            _This.$RemoveEvent(image, "abort", ErrorOrAbortHandler);
            _This.$RemoveEvent(image, "error", ErrorOrAbortHandler);

            if (callback)
                callback(image, abort);
        }

        function ErrorOrAbortHandler() {
            LoadImageCompleteHandler(true);
        }

        if (IsBrowserOpera() && browserRuntimeVersion < 11.6 || !src) {
            LoadImageCompleteHandler(!src);
        }
        else {

            _This.$AddEvent(image, "load", LoadImageCompleteHandler);
            _This.$AddEvent(image, "abort", ErrorOrAbortHandler);
            _This.$AddEvent(image, "error", ErrorOrAbortHandler);
            
            image.src = src;
        }
    };

    _This.$LoadImages = function (imageElmts, mainImageElmt, callback) {

        var _ImageLoading = imageElmts.length + 1;

        function LoadImageCompleteEventHandler(image, abort) {
            _ImageLoading--;
            if (mainImageElmt && image && image.src == mainImageElmt.src)
                mainImageElmt = image;
            !_ImageLoading && callback && callback(mainImageElmt);
        }

        each(imageElmts, function (imageElmt) {
            _This.$LoadImage(imageElmt.src, LoadImageCompleteEventHandler);
        });

        LoadImageCompleteEventHandler();
    };

    _This.$BuildElement = function (template, tagName, replacer, createCopy) {
        if (createCopy)
            template = CloneNode(template);

        var templateHolders = $Jssor$.$GetElementsByTag(template, tagName);
        for (var j = templateHolders.length -1; j > -1; j--) {
            var templateHolder = templateHolders[j];
            var replaceItem = CloneNode(replacer);
            ClassName(replaceItem, ClassName(templateHolder));
            $Jssor$.$CssCssText(replaceItem, templateHolder.style.cssText);

            var thumbnailPlaceHolderParent = $Jssor$.$ParentNode(templateHolder);
            $Jssor$.$InsertBefore(thumbnailPlaceHolderParent, replaceItem, templateHolder);
            $Jssor$.$RemoveChild(thumbnailPlaceHolderParent, templateHolder);
        }

        return template;
    };

    var _MouseDownButtons;
    function JssorButtonEx(elmt) {
        var _Self = this;

        var _OriginClassName;

        var _IsMouseDown;   //class name 'dn'
        var _IsActive;      //class name 'av'
        var _IsDisabled;    //class name 'ds'

        function Highlight() {
            var className = _OriginClassName;

            if (_IsDisabled) {
                className += 'ds';
            }
            else if (_IsMouseDown) {
                className += 'dn';
            }
            else if (_IsActive) {
                className += "av";
            }

            ClassName(elmt, className);
        }

        function OnMouseDown(event) {
            if (_IsDisabled) {
                _This.$CancelEvent(event);
            }
            else {
                _MouseDownButtons.push(_Self);

                _IsMouseDown = true;

                Highlight();
            }
        }

        _Self.$MouseUp = function () {
            ///	<summary>
            ///		Internal member function, do not use it.
            ///	</summary>
            ///	<private />

            _IsMouseDown = false;

            Highlight();
        };

        _Self.$Activate = function (activate) {
            if (activate != undefined) {
                _IsActive = activate;

                Highlight();
            }
            else {
                return _IsActive;
            }
        };

        _Self.$Enable = function (enable) {
            if (enable != undefined) {
                _IsDisabled = !enable;

                Highlight();
            }
            else {
                return !_IsDisabled;
            }
        };

        //JssorButtonEx Constructor
        {
            elmt = _This.$GetElement(elmt);

            if (!_MouseDownButtons) {
                _This.$AddEventBrowserMouseUp(function () {
                    var oldMouseDownButtons = _MouseDownButtons;
                    _MouseDownButtons = [];

                    each(oldMouseDownButtons, function (button) {
                        button.$MouseUp();
                    });
                });

                _MouseDownButtons = [];
            }

            _OriginClassName = ClassName(elmt);

            $Jssor$.$AddEvent(elmt, "mousedown", OnMouseDown);
        }
    }

    _This.$Buttonize = function (elmt) {
        return new JssorButtonEx(elmt);
    };

    _This.$Css = Css;
    _This.$CssN = CssN;
    _This.$CssP = CssP;

    _This.$CssOverflow = CssProxy("overflow");

    _This.$CssTop = CssProxy("top", 2);
    _This.$CssLeft = CssProxy("left", 2);
    _This.$CssWidth = CssProxy("width", 2);
    _This.$CssHeight = CssProxy("height", 2);
    _This.$CssMarginLeft = CssProxy("marginLeft", 2);
    _This.$CssMarginTop = CssProxy("marginTop", 2);
    _This.$CssPosition = CssProxy("position");
    _This.$CssDisplay = CssProxy("display");
    _This.$CssZIndex = CssProxy("zIndex", 1);
    _This.$CssFloat = function (elmt, float) {
        return Css(elmt, IsBrowserIE() ? "styleFloat" : "cssFloat", float);
    };
    _This.$CssOpacity = function (elmt, opacity, ie9EarlierForce) {
        if (opacity != undefined) {
            SetStyleOpacity(elmt, opacity, ie9EarlierForce);
        }
        else {
            return GetStyleOpacity(elmt);
        }
    };

    _This.$CssCssText = function (elmt, text) {
        if (text != undefined) {
            elmt.style.cssText = text;
        }
        else {
            return elmt.style.cssText;
        }
    };

    var _StyleGetter = {
        $Opacity: _This.$CssOpacity,
        $Top: _This.$CssTop,
        $Left: _This.$CssLeft,
        $Width: _This.$CssWidth,
        $Height: _This.$CssHeight,
        $Position: _This.$CssPosition,
        $Display: _This.$CssDisplay,
        $ZIndex: _This.$CssZIndex
    };

    var _StyleSetterReserved;

    function StyleSetter() {
        if (!_StyleSetterReserved) {
            _StyleSetterReserved = Extend({
                $MarginTop: _This.$CssMarginTop,
                $MarginLeft: _This.$CssMarginLeft,
                $Clip: _This.$SetStyleClip,
                $Transform: _This.$SetStyleTransform
            }, _StyleGetter);
        }
        return _StyleSetterReserved;
    }

    function StyleSetterEx() {
        StyleSetter();

        //For Compression Only
        _StyleSetterReserved.$Transform = _StyleSetterReserved.$Transform;

        return _StyleSetterReserved;
    }

    _This.$StyleSetter = StyleSetter;

    _This.$StyleSetterEx = StyleSetterEx;

    _This.$GetStyles = function (elmt, originStyles) {
        StyleSetter();

        var styles = {};

        each(originStyles, function (value, key) {
            if (_StyleGetter[key]) {
                styles[key] = _StyleGetter[key](elmt);
            }
        });

        return styles;
    };

    _This.$SetStyles = function (elmt, styles) {
        var styleSetter = StyleSetter();

        each(styles, function (value, key) {
            styleSetter[key] && styleSetter[key](elmt, value);
        });
    };

    _This.$SetStylesEx = function (elmt, styles) {
        StyleSetterEx();

        _This.$SetStyles(elmt, styles);
    };

    $JssorMatrix$ = new function () {
        var _ThisMatrix = this;

        function Multiply(ma, mb) {
            var acs = ma[0].length;
            var rows = ma.length;
            var cols = mb[0].length;

            var matrix = [];

            for (var r = 0; r < rows; r++) {
                var row = matrix[r] = [];
                for (var c = 0; c < cols; c++) {
                    var unitValue = 0;

                    for (var ac = 0; ac < acs; ac++) {
                        unitValue += ma[r][ac] * mb[ac][c];
                    }

                    row[c] = unitValue;
                }
            }

            return matrix;
        }

        _ThisMatrix.$ScaleX = function (matrix, sx) {
            return _ThisMatrix.$ScaleXY(matrix, sx, 0);
        };

        _ThisMatrix.$ScaleY = function (matrix, sy) {
            return _ThisMatrix.$ScaleXY(matrix, 0, sy);
        };

        _ThisMatrix.$ScaleXY = function (matrix, sx, sy) {
            return Multiply(matrix, [[sx, 0], [0, sy]]);
        };

        _ThisMatrix.$TransformPoint = function (matrix, p) {
            var pMatrix = Multiply(matrix, [[p.x], [p.y]]);

            return Point(pMatrix[0][0], pMatrix[1][0]);
        };
    };

    _This.$CreateMatrix = function (alpha, scaleX, scaleY) {
        var cos = Math.cos(alpha);
        var sin = Math.sin(alpha);
        //var r11 = cos;
        //var r21 = sin;
        //var r12 = -sin;
        //var r22 = cos;

        //var m11 = cos * scaleX;
        //var m12 = -sin * scaleY;
        //var m21 = sin * scaleX;
        //var m22 = cos * scaleY;

        return [[cos * scaleX, -sin * scaleY], [sin * scaleX, cos * scaleY]];
    };

    _This.$GetMatrixOffset = function (matrix, width, height) {
        var p1 = $JssorMatrix$.$TransformPoint(matrix, Point(-width / 2, -height / 2));
        var p2 = $JssorMatrix$.$TransformPoint(matrix, Point(width / 2, -height / 2));
        var p3 = $JssorMatrix$.$TransformPoint(matrix, Point(width / 2, height / 2));
        var p4 = $JssorMatrix$.$TransformPoint(matrix, Point(-width / 2, height / 2));

        return Point(Math.min(p1.x, p2.x, p3.x, p4.x) + width / 2, Math.min(p1.y, p2.y, p3.y, p4.y) + height / 2);
    };

    _This.$Transform = function (fromStyles, toStyles, interPosition, easings, durings, rounds, options) {

        var currentStyles = toStyles;

        if (fromStyles) {
            currentStyles = {};

            for (var key in toStyles) {
                var round = rounds[key] || 1;
                var during = durings[key] || [0, 1];
                var propertyInterPosition = (interPosition - during[0]) / during[1];
                propertyInterPosition = Math.min(Math.max(propertyInterPosition, 0), 1);
                propertyInterPosition = propertyInterPosition * round;
                var floorPosition = Math.floor(propertyInterPosition);
                if (propertyInterPosition != floorPosition)
                    propertyInterPosition -= floorPosition;

                var easing = easings[key] || easings.$Default;
                var easingValue = easing(propertyInterPosition);
                var currentPropertyValue;
                var value = fromStyles[key];
                var toValue = toStyles[key];

                if ($Jssor$.$IsNumeric(toValue)) {
                    currentPropertyValue = value + (toValue - value) * easingValue;
                }
                else {
                    currentPropertyValue = $Jssor$.$Extend({ $Offset: {} }, fromStyles[key]);

                    $Jssor$.$Each(toValue.$Offset, function (rectX, n) {
                        var offsetValue = rectX * easingValue;
                        currentPropertyValue.$Offset[n] = offsetValue;
                        currentPropertyValue[n] += offsetValue;
                    });
                }
                currentStyles[key] = currentPropertyValue;
            }

            if (fromStyles.$Zoom) {
                currentStyles.$Transform = { $Rotate: currentStyles.$Rotate || 0, $Scale: currentStyles.$Zoom, $OriginalWidth: options.$OriginalWidth, $OriginalHeight: options.$OriginalHeight };
            }
        }

        if (toStyles.$Clip && options.$Move) {
            var styleFrameNClipOffset = currentStyles.$Clip.$Offset;

            var offsetY = (styleFrameNClipOffset.$Top || 0) + (styleFrameNClipOffset.$Bottom || 0);
            var offsetX = (styleFrameNClipOffset.$Left || 0) + (styleFrameNClipOffset.$Right || 0);

            currentStyles.$Left = (currentStyles.$Left || 0) + offsetX;
            currentStyles.$Top = (currentStyles.$Top || 0) + offsetY;
            currentStyles.$Clip.$Left -= offsetX;
            currentStyles.$Clip.$Right -= offsetX;
            currentStyles.$Clip.$Top -= offsetY;
            currentStyles.$Clip.$Bottom -= offsetY;
        }

        if (currentStyles.$Clip && $Jssor$.$CanClearClip() && !currentStyles.$Clip.$Top && !currentStyles.$Clip.$Left && (currentStyles.$Clip.$Right == options.$OriginalWidth) && (currentStyles.$Clip.$Bottom == options.$OriginalHeight))
            currentStyles.$Clip = null;

        return currentStyles;
    };
};

//$JssorObject$
var $JssorObject$ = window.$JssorObject$ = function () {
    var _ThisObject = this;
    // Fields

    var _Listeners = []; // dictionary of eventName --> array of handlers
    var _Listenees = [];

    // Private Methods
    function AddListener(eventName, handler) {

        $JssorDebug$.$Execute(function () {
            if (eventName == undefined || eventName == null)
                throw new Error("param 'eventName' is null or empty.");

            if (typeof (handler) != "function") {
                throw "param 'handler' must be a function.";
            }

            $Jssor$.$Each(_Listeners, function (listener) {
                if (listener.$EventName == eventName && listener.$Handler === handler) {
                    throw new Error("The handler listened to the event already, cannot listen to the same event of the same object with the same handler twice.");
                }
            });
        });

        _Listeners.push({ $EventName: eventName, $Handler: handler });
    }

    function RemoveListener(eventName, handler) {

        $JssorDebug$.$Execute(function () {
            if (eventName == undefined || eventName == null)
                throw new Error("param 'eventName' is null or empty.");

            if (typeof (handler) != "function") {
                throw "param 'handler' must be a function.";
            }
        });

        $Jssor$.$Each(_Listeners, function (listener, index) {
            if (listener.$EventName == eventName && listener.$Handler === handler) {
                _Listeners.splice(index, 1);
            }
        });
    }

    function ClearListeners() {
        _Listeners = [];
    }

    function ClearListenees() {

        $Jssor$.$Each(_Listenees, function (listenee) {
            $Jssor$.$RemoveEvent(listenee.$Obj, listenee.$EventName, listenee.$Handler);
        });

        _Listenees = [];
    }

    //Protected Methods
    _ThisObject.$Listen = function (obj, eventName, handler, useCapture) {

        $JssorDebug$.$Execute(function () {
            if (!obj)
                throw new Error("param 'obj' is null or empty.");

            if (eventName == undefined || eventName == null)
                throw new Error("param 'eventName' is null or empty.");

            if (typeof (handler) != "function") {
                throw "param 'handler' must be a function.";
            }

            $Jssor$.$Each(_Listenees, function (listenee) {
                if (listenee.$Obj === obj && listenee.$EventName == eventName && listenee.$Handler === handler) {
                    throw new Error("The handler listened to the event already, cannot listen to the same event of the same object with the same handler twice.");
                }
            });
        });

        $Jssor$.$AddEvent(obj, eventName, handler, useCapture);
        _Listenees.push({ $Obj: obj, $EventName: eventName, $Handler: handler });
    };

    _ThisObject.$Unlisten = function (obj, eventName, handler) {

        $JssorDebug$.$Execute(function () {
            if (!obj)
                throw new Error("param 'obj' is null or empty.");

            if (eventName == undefined || eventName == null)
                throw new Error("param 'eventName' is null or empty.");

            if (typeof (handler) != "function") {
                throw "param 'handler' must be a function.";
            }
        });

        $Jssor$.$Each(_Listenees, function (listenee, index) {
            if (listenee.$Obj === obj && listenee.$EventName == eventName && listenee.$Handler === handler) {
                $Jssor$.$RemoveEvent(obj, eventName, handler);
                _Listenees.splice(index, 1);
            }
        });
    };

    _ThisObject.$UnlistenAll = ClearListenees;

    // Public Methods
    _ThisObject.$On = _ThisObject.addEventListener = AddListener;

    _ThisObject.$Off = _ThisObject.removeEventListener = RemoveListener;

    _ThisObject.$TriggerEvent = function (eventName) {

        var args = [].slice.call(arguments, 1);

        $Jssor$.$Each(_Listeners, function (listener) {
            try {
                listener.$EventName == eventName && listener.$Handler.apply(window, args);
            } catch (e) {
                // handler threw an error, ignore, go on to next one
                $JssorDebug$.$Error(e.name + " while executing " + eventName +
                        " handler: " + e.message, e);
            }
        });
    };

    _ThisObject.$Destroy = function () {
        ClearListenees();
        ClearListeners();

        for (var name in _ThisObject)
            delete _ThisObject[name];
    };

    $JssorDebug$.$C_AbstractClass(_ThisObject);
};

$JssorAnimator$ = function (delay, duration, options, elmt, fromStyles, toStyles) {
    delay = delay || 0;

    var _ThisAnimator = this;
    var _AutoPlay;
    var _Hiden;
    var _CombineMode;
    var _PlayToPosition;
    var _PlayDirection;
    var _NoStop;
    var _TimeStampLastFrame = 0;

    var _SubEasings;
    var _SubRounds;
    var _SubDurings;
    var _Callback;

    var _Position_Current = 0;
    var _Position_Display = 0;
    var _Hooked;

    var _Position_InnerBegin = delay;
    var _Position_InnerEnd = delay + duration;
    var _Position_OuterBegin;
    var _Position_OuterEnd;
    var _LoopLength;

    var _NestedAnimators = [];
    var _StyleSetter;

    function GetPositionRange(position, begin, end) {
        var range = 0;

        if (position < begin)
            range = -1;

        else if (position > end)
            range = 1;

        return range;
    }

    function GetInnerPositionRange(position) {
        return GetPositionRange(position, _Position_InnerBegin, _Position_InnerEnd);
    }

    function GetOuterPositionRange(position) {
        return GetPositionRange(position, _Position_OuterBegin, _Position_OuterEnd);
    }

    function Shift(offset) {
        _Position_OuterBegin += offset;
        _Position_OuterEnd += offset;
        _Position_InnerBegin += offset;
        _Position_InnerEnd += offset;

        _Position_Current += offset;
        _Position_Display += offset;

        $Jssor$.$Each(_NestedAnimators, function (animator) {
            animator, animator.$Shift(offset);
        });
    }

    function Locate(position, relative) {
        var offset = position - _Position_OuterBegin + delay * relative;

        Shift(offset);

        //$JssorDebug$.$Execute(function () {
        //    _ThisAnimator.$Position_InnerBegin = _Position_InnerBegin;
        //    _ThisAnimator.$Position_InnerEnd = _Position_InnerEnd;
        //    _ThisAnimator.$Position_OuterBegin = _Position_OuterBegin;
        //    _ThisAnimator.$Position_OuterEnd = _Position_OuterEnd;
        //});

        return _Position_OuterEnd;
    }

    function GoToPosition(positionOuter, force) {
        var trimedPositionOuter = positionOuter;

        if (_LoopLength && (trimedPositionOuter >= _Position_OuterEnd || trimedPositionOuter <= _Position_OuterBegin)) {
            trimedPositionOuter = ((trimedPositionOuter - _Position_OuterBegin) % _LoopLength + _LoopLength) % _LoopLength + _Position_OuterBegin;
        }

        if (!_Hooked || _NoStop || force || _Position_Current != trimedPositionOuter) {

            var positionToDisplay = Math.min(trimedPositionOuter, _Position_OuterEnd);
            positionToDisplay = Math.max(positionToDisplay, _Position_OuterBegin);

            if (!_Hooked || _NoStop || force || positionToDisplay != _Position_Display) {
                if (toStyles) {

                    var interPosition = (positionToDisplay - _Position_InnerBegin) / (duration || 1);

                    //if (options.$Optimize && $Jssor$.$IsBrowserChrome() && duration) {
                    //    interPosition = Math.round(interPosition / 8 * duration) * 8 / duration;
                    //}

                    if (options.$Reverse)
                        interPosition = 1 - interPosition;

                    var currentStyles = $Jssor$.$Transform(fromStyles, toStyles, interPosition, _SubEasings, _SubDurings, _SubRounds, options);

                    $Jssor$.$Each(currentStyles, function (value, key) {
                        _StyleSetter[key] && _StyleSetter[key](elmt, value);
                    });
                }

                _ThisAnimator.$OnInnerOffsetChange(_Position_Display - _Position_InnerBegin, positionToDisplay - _Position_InnerBegin);
            }

            _Position_Display = positionToDisplay;

            $Jssor$.$Each(_NestedAnimators, function (animator, i) {
                var nestedAnimator = positionOuter < _Position_Current ? _NestedAnimators[_NestedAnimators.length - i - 1] : animator;
                nestedAnimator.$GoToPosition(positionOuter, force);
            });

            var positionOld = _Position_Current;
            var positionNew = positionOuter;

            _Position_Current = trimedPositionOuter;
            _Hooked = true;

            _ThisAnimator.$OnPositionChange(positionOld, positionNew);
        }
    }

    function Join(animator, combineMode) {
        ///	<summary>
        ///		Combine another animator as nested animator
        ///	</summary>
        ///	<param name="animator" type="$JssorAnimator$">
        ///		An instance of $JssorAnimator$
        ///	</param>
        ///	<param name="combineMode" type="int">
        ///		0: parallel - place the animator parallel to this animator.
        ///		1: chain - chain the animator at the _Position_InnerEnd of this animator.
        ///	</param>
        $JssorDebug$.$Execute(function () {
            if (combineMode !== 0 && combineMode !== 1)
                $JssorDebug$.$Fail("Argument out of range, the value of 'combineMode' should be either 0 or 1.");
        });

        if (combineMode)
            animator.$Locate(_Position_OuterEnd, 1);

        _Position_OuterEnd = Math.max(_Position_OuterEnd, animator.$GetPosition_OuterEnd());
        _NestedAnimators.push(animator);
    }

    var RequestAnimationFrame = window.requestAnimationFrame
    || window.webkitRequestAnimationFrame
    || window.mozRequestAnimationFrame
    || window.msRequestAnimationFrame;

    if ($Jssor$.$IsBrowserSafari() && $Jssor$.$BrowserVersion() < 7) {
        RequestAnimationFrame = null;

        $JssorDebug$.$Log("Custom animation frame for safari before 7.");
    }

    RequestAnimationFrame = RequestAnimationFrame || function (callback) {
        $Jssor$.$Delay(callback, options.$Interval);
    };

    function ShowFrame() {
        if (_AutoPlay) {
            var now = $Jssor$.$GetNow();
            var timeOffset = Math.min(now - _TimeStampLastFrame, options.$IntervalMax);
            var timePosition = _Position_Current + timeOffset * _PlayDirection;
            _TimeStampLastFrame = now;

            if (timePosition * _PlayDirection >= _PlayToPosition * _PlayDirection)
                timePosition = _PlayToPosition;

            GoToPosition(timePosition);

            if (!_NoStop && timePosition * _PlayDirection >= _PlayToPosition * _PlayDirection) {
                Stop(_Callback);
            }
            else {
                RequestAnimationFrame(ShowFrame);
            }
        }
    }

    function PlayToPosition(toPosition, callback, noStop) {
        if (!_AutoPlay) {
            _AutoPlay = true;
            _NoStop = noStop
            _Callback = callback;
            toPosition = Math.max(toPosition, _Position_OuterBegin);
            toPosition = Math.min(toPosition, _Position_OuterEnd);
            _PlayToPosition = toPosition;
            _PlayDirection = _PlayToPosition < _Position_Current ? -1 : 1;
            _ThisAnimator.$OnStart();
            _TimeStampLastFrame = $Jssor$.$GetNow();
            RequestAnimationFrame(ShowFrame);
        }
    }

    function Stop(callback) {
        if (_AutoPlay) {
            _NoStop = _AutoPlay = _Callback = false;
            _ThisAnimator.$OnStop();

            if (callback)
                callback();
        }
    }

    _ThisAnimator.$Play = function (positionLength, callback, noStop) {
        PlayToPosition(positionLength ? _Position_Current + positionLength : _Position_OuterEnd, callback, noStop);
    };

    _ThisAnimator.$PlayToPosition = PlayToPosition;

    _ThisAnimator.$PlayToBegin = function (callback, noStop) {
        PlayToPosition(_Position_OuterBegin, callback, noStop);
    };

    _ThisAnimator.$PlayToEnd = function (callback, noStop) {
        PlayToPosition(_Position_OuterEnd, callback, noStop);
    };

    _ThisAnimator.$Stop = Stop;

    _ThisAnimator.$Continue = function (toPosition) {
        PlayToPosition(toPosition);
    };

    _ThisAnimator.$GetPosition = function () {
        return _Position_Current;
    };

    _ThisAnimator.$GetPlayToPosition = function () {
        return _PlayToPosition;
    };

    _ThisAnimator.$GetPosition_Display = function () {
        return _Position_Display;
    };

    _ThisAnimator.$GoToPosition = GoToPosition;

    _ThisAnimator.$GoToBegin = function () {
        GoToPosition(_Position_OuterBegin, true);
    };

    _ThisAnimator.$GoToEnd = function () {
        GoToPosition(_Position_OuterEnd, true);
    };

    _ThisAnimator.$Move = function (offset) {
        GoToPosition(_Position_Current + offset);
    };

    _ThisAnimator.$CombineMode = function () {
        return _CombineMode;
    };

    _ThisAnimator.$GetDuration = function () {
        return duration;
    };

    _ThisAnimator.$IsPlaying = function () {
        return _AutoPlay;
    };

    _ThisAnimator.$IsOnTheWay = function () {
        return _Position_Current > _Position_InnerBegin && _Position_Current <= _Position_InnerEnd;
    };

    _ThisAnimator.$SetLoopLength = function (length) {
        _LoopLength = length;
    };

    _ThisAnimator.$Locate = Locate;

    _ThisAnimator.$Shift = Shift;

    _ThisAnimator.$Join = Join;

    _ThisAnimator.$Combine = function (animator) {
        ///	<summary>
        ///		Combine another animator parallel to this animator
        ///	</summary>
        ///	<param name="animator" type="$JssorAnimator$">
        ///		An instance of $JssorAnimator$
        ///	</param>
        Join(animator, 0);
    };

    _ThisAnimator.$Chain = function (animator) {
        ///	<summary>
        ///		Chain another animator at the _Position_InnerEnd of this animator
        ///	</summary>
        ///	<param name="animator" type="$JssorAnimator$">
        ///		An instance of $JssorAnimator$
        ///	</param>
        Join(animator, 1);
    };

    _ThisAnimator.$GetPosition_InnerBegin = function () {
        ///	<summary>
        ///		Internal member function, do not use it.
        ///	</summary>
        ///	<private />
        ///	<returns type="int" />
        return _Position_InnerBegin;
    };

    _ThisAnimator.$GetPosition_InnerEnd = function () {
        ///	<summary>
        ///		Internal member function, do not use it.
        ///	</summary>
        ///	<private />
        ///	<returns type="int" />
        return _Position_InnerEnd;
    };

    _ThisAnimator.$GetPosition_OuterBegin = function () {
        ///	<summary>
        ///		Internal member function, do not use it.
        ///	</summary>
        ///	<private />
        ///	<returns type="int" />
        return _Position_OuterBegin;
    };

    _ThisAnimator.$GetPosition_OuterEnd = function () {
        ///	<summary>
        ///		Internal member function, do not use it.
        ///	</summary>
        ///	<private />
        ///	<returns type="int" />
        return _Position_OuterEnd;
    };

    _ThisAnimator.$OnPositionChange = _ThisAnimator.$OnStart = _ThisAnimator.$OnStop = _ThisAnimator.$OnInnerOffsetChange = $Jssor$.$EmptyFunction;
    _ThisAnimator.$Version = $Jssor$.$GetNow();

    //Constructor  1
    {
        options = $Jssor$.$Extend({
            $Interval: 16,
            $IntervalMax: 50
        }, options);

        //Sodo statement, for development time intellisence only
        $JssorDebug$.$Execute(function () {
            options = $Jssor$.$Extend({
                $LoopLength: undefined,
                $Setter: undefined,
                $Easing: undefined
            }, options);
        });

        _LoopLength = options.$LoopLength;

        _StyleSetter = $Jssor$.$Extend({}, $Jssor$.$StyleSetter(), options.$Setter);

        _Position_OuterBegin = _Position_InnerBegin = delay;
        _Position_OuterEnd = _Position_InnerEnd = delay + duration;

        _SubRounds = options.$Round || {};
        _SubDurings = options.$During || {};
        _SubEasings = $Jssor$.$Extend({ $Default: $Jssor$.$IsFunction(options.$Easing) && options.$Easing || $JssorEasing$.$EaseSwing }, options.$Easing);
    }
};

function $JssorPlayerClass$() {

    var _ThisPlayer = this;
    var _PlayerControllers = [];

    function PlayerController(playerElement) {
        var _SelfPlayerController = this;
        var _PlayerInstance;
        var _PlayerInstantces = [];

        function OnPlayerInstanceDataAvailable(event) {
            var srcElement = $Jssor$.$EventSrc(event);
            _PlayerInstance = srcElement.pInstance;

            $Jssor$.$RemoveEvent(srcElement, "dataavailable", OnPlayerInstanceDataAvailable);
            $Jssor$.$Each(_PlayerInstantces, function (playerInstance) {
                if (playerInstance != _PlayerInstance) {
                    playerInstance.$Remove();
                }
            });

            playerElement.pTagName = _PlayerInstance.tagName;
            _PlayerInstantces = null;
        }

        function HandlePlayerInstance(playerInstanceElement) {
            var playerHandler;

            if (!playerInstanceElement.pInstance) {
                var playerHandlerAttribute = $Jssor$.$AttributeEx(playerInstanceElement, "pHandler");

                if ($JssorPlayer$[playerHandlerAttribute]) {
                    $Jssor$.$AddEvent(playerInstanceElement, "dataavailable", OnPlayerInstanceDataAvailable);
                    playerHandler = new $JssorPlayer$[playerHandlerAttribute](playerElement, playerInstanceElement);
                    _PlayerInstantces.push(playerHandler);

                    $JssorDebug$.$Execute(function () {
                        if ($Jssor$.$Type(playerHandler.$Remove) != "function") {
                            $JssorDebug$.$Fail("'pRemove' interface not implemented for player handler '" + playerHandlerAttribute + "'.");
                        }
                    });
                }
            }

            return playerHandler;
        }

        _SelfPlayerController.$InitPlayerController = function () {
            if (!playerElement.pInstance && !HandlePlayerInstance(playerElement)) {

                var playerInstanceElements = $Jssor$.$Children(playerElement);

                $Jssor$.$Each(playerInstanceElements, function (playerInstanceElement) {
                    HandlePlayerInstance(playerInstanceElement);
                });
            }
        };
    }

    _ThisPlayer.$EVT_SWITCH = 21;

    _ThisPlayer.$FetchPlayers = function (elmt) {
        elmt = elmt || document.body;

        var playerElements = $Jssor$.$FindChildren(elmt, "player");

        $Jssor$.$Each(playerElements, function (playerElement) {
            if (!_PlayerControllers[playerElement.pId]) {
                playerElement.pId = _PlayerControllers.length;
                _PlayerControllers.push(new PlayerController(playerElement));
            }
            var playerController = _PlayerControllers[playerElement.pId];
            playerController.$InitPlayerController();
        });
    };
};


var $JssorSlider$;
var $JssorSlideshowFormations$ = window.$JssorSlideshowFormations$ = {};
var $JssorSlideshowRunner$;

new function () {

    var COLUMN_INCREASE = 0;
    var COLUMN_DECREASE = 1;
    var ROW_INCREASE = 2;
    var ROW_DECREASE = 3;

    var DIRECTION_HORIZONTAL = 0x0003;
    var DIRECTION_VERTICAL = 0x000C;

    var TO_LEFT = 0x0001;
    var TO_RIGHT = 0x0002;
    var TO_TOP = 0x0004;
    var TO_BOTTOM = 0x0008;

    var FROM_LEFT = 0x0100;
    var FROM_TOP = 0x0200;
    var FROM_RIGHT = 0x0400;
    var FROM_BOTTOM = 0x0800;

    var ASSEMBLY_BOTTOM_LEFT = FROM_BOTTOM + TO_LEFT;
    var ASSEMBLY_BOTTOM_RIGHT = FROM_BOTTOM + TO_RIGHT;
    var ASSEMBLY_TOP_LEFT = FROM_TOP + TO_LEFT;
    var ASSEMBLY_TOP_RIGHT = FROM_TOP + TO_RIGHT;
    var ASSEMBLY_LEFT_TOP = FROM_LEFT + TO_TOP;
    var ASSEMBLY_LEFT_BOTTOM = FROM_LEFT + TO_BOTTOM;
    var ASSEMBLY_RIGHT_TOP = FROM_RIGHT + TO_TOP;
    var ASSEMBLY_RIGHT_BOTTOM = FROM_RIGHT + TO_BOTTOM;

    function isToLeft(roadValue) {
        return (roadValue & TO_LEFT) == TO_LEFT;
    }

    function isToRight(roadValue) {
        return (roadValue & TO_RIGHT) == TO_RIGHT;
    }

    function isToTop(roadValue) {
        return (roadValue & TO_TOP) == TO_TOP;
    }

    function isToBottom(roadValue) {
        return (roadValue & TO_BOTTOM) == TO_BOTTOM;
    }

    function PushFormationOrder(arr, order, formationItem) {
        formationItem.push(order);
        arr[order] = arr[order] || [];
        arr[order].push(formationItem);
    }

    $JssorSlideshowFormations$.$FormationStraight = function (transition) {
        var cols = transition.$Cols;
        var rows = transition.$Rows;
        var formationDirection = transition.$Assembly;
        var count = transition.$Count;
        var a = [];
        var i = 0;
        var col = 0;
        var r = 0;
        var cl = cols - 1;
        var rl = rows - 1;
        var il = count - 1;
        var cr;
        var order;
        for (r = 0; r < rows; r++) {
            for (col = 0; col < cols; col++) {
                cr = r + ',' + col;
                switch (formationDirection) {
                    case ASSEMBLY_BOTTOM_LEFT:
                        order = il - (col * rows + (rl - r));
                        break;
                    case ASSEMBLY_RIGHT_TOP:
                        order = il - (r * cols + (cl - col));
                        break;
                    case ASSEMBLY_TOP_LEFT:
                        order = il - (col * rows + r);
                    case ASSEMBLY_LEFT_TOP:
                        order = il - (r * cols + col);
                        break;
                    case ASSEMBLY_BOTTOM_RIGHT:
                        order = col * rows + r;
                        break;
                    case ASSEMBLY_LEFT_BOTTOM:
                        order = r * cols + (cl - col);
                        break;
                    case ASSEMBLY_TOP_RIGHT:
                        order = col * rows + (rl - r);
                        break;
                    default:
                        order = r * cols + col;
                        break; //ASSEMBLY_RIGHT_BOTTOM
                }
                PushFormationOrder(a, order, [r, col]);
            }
        }

        return a;
    };

    $JssorSlideshowFormations$.$FormationSwirl = function (transition) {
        var cols = transition.$Cols;
        var rows = transition.$Rows;
        var formationDirection = transition.$Assembly;
        var count = transition.$Count;
        var a = [];
        var hit = [];
        var i = 0;
        var col = 0;
        var r = 0;
        var cl = cols - 1;
        var rl = rows - 1;
        var il = count - 1;
        var cr;
        var courses;
        var course = 0;
        switch (formationDirection) {
            case ASSEMBLY_BOTTOM_LEFT:
                col = cl;
                r = 0;
                courses = [ROW_INCREASE, COLUMN_DECREASE, ROW_DECREASE, COLUMN_INCREASE];
                break;
            case ASSEMBLY_RIGHT_TOP:
                col = 0;
                r = rl;
                courses = [COLUMN_INCREASE, ROW_DECREASE, COLUMN_DECREASE, ROW_INCREASE];
                break;
            case ASSEMBLY_TOP_LEFT:
                col = cl;
                r = rl;
                courses = [ROW_DECREASE, COLUMN_DECREASE, ROW_INCREASE, COLUMN_INCREASE];
                break;
            case ASSEMBLY_LEFT_TOP:
                col = cl;
                r = rl;
                courses = [COLUMN_DECREASE, ROW_DECREASE, COLUMN_INCREASE, ROW_INCREASE];
                break;
            case ASSEMBLY_BOTTOM_RIGHT:
                col = 0;
                r = 0;
                courses = [ROW_INCREASE, COLUMN_INCREASE, ROW_DECREASE, COLUMN_DECREASE];
                break;
            case ASSEMBLY_LEFT_BOTTOM:
                col = cl;
                r = 0;
                courses = [COLUMN_DECREASE, ROW_INCREASE, COLUMN_INCREASE, ROW_DECREASE];
                break;
            case ASSEMBLY_TOP_RIGHT:
                col = 0;
                r = rl;
                courses = [ROW_DECREASE, COLUMN_INCREASE, ROW_INCREASE, COLUMN_DECREASE];
                break;
            default:
                col = 0;
                r = 0;
                courses = [COLUMN_INCREASE, ROW_INCREASE, COLUMN_DECREASE, ROW_DECREASE];
                break; //ASSEMBLY_RIGHT_BOTTOM
        }
        i = 0;
        while (i < count) {
            cr = r + ',' + col;
            if (col >= 0 && col < cols && r >= 0 && r < rows && !hit[cr]) {
                //a[cr] = i++;
                hit[cr] = true;
                PushFormationOrder(a, i++, [r, col]);
            }
            else {
                switch (courses[course++ % courses.length]) {
                    case COLUMN_INCREASE:
                        col--;
                        break;
                    case ROW_INCREASE:
                        r--;
                        break;
                    case COLUMN_DECREASE:
                        col++;
                        break;
                    case ROW_DECREASE:
                        r++;
                        break;
                }
            }

            switch (courses[course % courses.length]) {
                case COLUMN_INCREASE:
                    col++;
                    break;
                case ROW_INCREASE:
                    r++;
                    break;
                case COLUMN_DECREASE:
                    col--;
                    break;
                case ROW_DECREASE:
                    r--;
                    break;
            }
        }
        return a;
    };

    $JssorSlideshowFormations$.$FormationZigZag = function (transition) {
        var cols = transition.$Cols;
        var rows = transition.$Rows;
        var formationDirection = transition.$Assembly;
        var count = transition.$Count;
        var a = [];
        var i = 0;
        var col = 0;
        var r = 0;
        var cl = cols - 1;
        var rl = rows - 1;
        var il = count - 1;
        var cr;
        var courses;
        var course = 0;
        switch (formationDirection) {
            case ASSEMBLY_BOTTOM_LEFT:
                col = cl;
                r = 0;
                courses = [ROW_INCREASE, COLUMN_DECREASE, ROW_DECREASE, COLUMN_DECREASE];
                break;
            case ASSEMBLY_RIGHT_TOP:
                col = 0;
                r = rl;
                courses = [COLUMN_INCREASE, ROW_DECREASE, COLUMN_DECREASE, ROW_DECREASE];
                break;
            case ASSEMBLY_TOP_LEFT:
                col = cl;
                r = rl;
                courses = [ROW_DECREASE, COLUMN_DECREASE, ROW_INCREASE, COLUMN_DECREASE];
                break;
            case ASSEMBLY_LEFT_TOP:
                col = cl;
                r = rl;
                courses = [COLUMN_DECREASE, ROW_DECREASE, COLUMN_INCREASE, ROW_DECREASE];
                break;
            case ASSEMBLY_BOTTOM_RIGHT:
                col = 0;
                r = 0;
                courses = [ROW_INCREASE, COLUMN_INCREASE, ROW_DECREASE, COLUMN_INCREASE];
                break;
            case ASSEMBLY_LEFT_BOTTOM:
                col = cl;
                r = 0;
                courses = [COLUMN_DECREASE, ROW_INCREASE, COLUMN_INCREASE, ROW_INCREASE];
                break;
            case ASSEMBLY_TOP_RIGHT:
                col = 0;
                r = rl;
                courses = [ROW_DECREASE, COLUMN_INCREASE, ROW_INCREASE, COLUMN_INCREASE];
                break;
            default:
                col = 0;
                r = 0;
                courses = [COLUMN_INCREASE, ROW_INCREASE, COLUMN_DECREASE, ROW_INCREASE];
                break; //ASSEMBLY_RIGHT_BOTTOM
        }
        i = 0;
        while (i < count) {
            cr = r + ',' + col;
            if (col >= 0 && col < cols && r >= 0 && r < rows && typeof (a[cr]) == 'undefined') {
                PushFormationOrder(a, i++, [r, col]);
                //a[cr] = i++;
                switch (courses[course % courses.length]) {
                    case COLUMN_INCREASE:
                        col++;
                        break;
                    case ROW_INCREASE:
                        r++;
                        break;
                    case COLUMN_DECREASE:
                        col--;
                        break;
                    case ROW_DECREASE:
                        r--;
                        break;
                }
            }
            else {
                switch (courses[course++ % courses.length]) {
                    case COLUMN_INCREASE:
                        col--;
                        break;
                    case ROW_INCREASE:
                        r--;
                        break;
                    case COLUMN_DECREASE:
                        col++;
                        break;
                    case ROW_DECREASE:
                        r++;
                        break;
                }
                switch (courses[course++ % courses.length]) {
                    case COLUMN_INCREASE:
                        col++;
                        break;
                    case ROW_INCREASE:
                        r++;
                        break;
                    case COLUMN_DECREASE:
                        col--;
                        break;
                    case ROW_DECREASE:
                        r--;
                        break;
                }
            }
        }
        return a;
    };

    $JssorSlideshowFormations$.$FormationStraightStairs = function (transition) {
        var cols = transition.$Cols;
        var rows = transition.$Rows;
        var formationDirection = transition.$Assembly;
        var count = transition.$Count;
        var a = [];
        var i = 0;
        var col = 0;
        var r = 0;
        var cl = cols - 1;
        var rl = rows - 1;
        var il = count - 1;
        var cr;
        switch (formationDirection) {
            case ASSEMBLY_BOTTOM_LEFT:
            case ASSEMBLY_TOP_RIGHT:
            case ASSEMBLY_TOP_LEFT:
            case ASSEMBLY_BOTTOM_RIGHT:
                var C = 0;
                var R = 0;
                break;
            case ASSEMBLY_LEFT_BOTTOM:
            case ASSEMBLY_RIGHT_TOP:
            case ASSEMBLY_LEFT_TOP:
            case ASSEMBLY_RIGHT_BOTTOM:
                var C = cl;
                var R = 0;
                break;
            default:
                formationDirection = ASSEMBLY_RIGHT_BOTTOM;
                var C = cl;
                var R = 0;
                break;
        }
        col = C;
        r = R;
        while (i < count) {
            cr = r + ',' + col;
            if (isToTop(formationDirection) || isToRight(formationDirection)) {
                PushFormationOrder(a, il - i++, [r, col]);
                //a[cr] = il - i++;
            }
            else {
                PushFormationOrder(a, i++, [r, col]);
                //a[cr] = i++;
            }
            switch (formationDirection) {
                case ASSEMBLY_BOTTOM_LEFT:
                case ASSEMBLY_TOP_RIGHT:
                    col--;
                    r++;
                    break;
                case ASSEMBLY_TOP_LEFT:
                case ASSEMBLY_BOTTOM_RIGHT:
                    col++;
                    r--;
                    break;
                case ASSEMBLY_LEFT_BOTTOM:
                case ASSEMBLY_RIGHT_TOP:
                    col--;
                    r--;
                    break;
                case ASSEMBLY_RIGHT_BOTTOM:
                case ASSEMBLY_LEFT_TOP:
                default:
                    col++;
                    r++;
                    break;
            }
            if (col < 0 || r < 0 || col > cl || r > rl) {
                switch (formationDirection) {
                    case ASSEMBLY_BOTTOM_LEFT:
                    case ASSEMBLY_TOP_RIGHT:
                        C++;
                        break;
                    case ASSEMBLY_LEFT_BOTTOM:
                    case ASSEMBLY_RIGHT_TOP:
                    case ASSEMBLY_TOP_LEFT:
                    case ASSEMBLY_BOTTOM_RIGHT:
                        R++;
                        break;
                    case ASSEMBLY_RIGHT_BOTTOM:
                    case ASSEMBLY_LEFT_TOP:
                    default:
                        C--;
                        break;
                }
                if (C < 0 || R < 0 || C > cl || R > rl) {
                    switch (formationDirection) {
                        case ASSEMBLY_BOTTOM_LEFT:
                        case ASSEMBLY_TOP_RIGHT:
                            C = cl;
                            R++;
                            break;
                        case ASSEMBLY_TOP_LEFT:
                        case ASSEMBLY_BOTTOM_RIGHT:
                            R = rl;
                            C++;
                            break;
                        case ASSEMBLY_LEFT_BOTTOM:
                        case ASSEMBLY_RIGHT_TOP: R = rl; C--;
                            break;
                        case ASSEMBLY_RIGHT_BOTTOM:
                        case ASSEMBLY_LEFT_TOP:
                        default:
                            C = 0;
                            R++;
                            break;
                    }
                    if (R > rl)
                        R = rl;
                    else if (R < 0)
                        R = 0;
                    else if (C > cl)
                        C = cl;
                    else if (C < 0)
                        C = 0;
                }
                r = R;
                col = C;
            }
        }
        return a;
    };

    $JssorSlideshowFormations$.$FormationSquare = function (transition) {
        var cols = transition.$Cols || 1;
        var rows = transition.$Rows || 1;
        var arr = [];
        var i = 0;
        var col;
        var r;
        var dc;
        var dr;
        var cr;
        dc = cols < rows ? (rows - cols) / 2 : 0;
        dr = cols > rows ? (cols - rows) / 2 : 0;
        cr = Math.round(Math.max(cols / 2, rows / 2)) + 1;
        for (col = 0; col < cols; col++) {
            for (r = 0; r < rows; r++)
                PushFormationOrder(arr, cr - Math.min(col + 1 + dc, r + 1 + dr, cols - col + dc, rows - r + dr), [r, col]);
        }
        return arr;
    };

    $JssorSlideshowFormations$.$FormationRectangle = function (transition) {
        var cols = transition.$Cols || 1;
        var rows = transition.$Rows || 1;
        var arr = [];
        var i = 0;
        var col;
        var r;
        var cr;
        cr = Math.round(Math.min(cols / 2, rows / 2)) + 1;
        for (col = 0; col < cols; col++) {
            for (r = 0; r < rows; r++)
                PushFormationOrder(arr, cr - Math.min(col + 1, r + 1, cols - col, rows - r), [r, col]);
        }
        return arr;
    };

    $JssorSlideshowFormations$.$FormationRandom = function (transition) {
        var a = [];
        var r, col, i;
        for (r = 0; r < transition.$Rows; r++) {
            for (col = 0; col < transition.$Cols; col++)
                PushFormationOrder(a, Math.ceil(100000 * Math.random()) % 13, [r, col]);
        }

        return a;
    };

    $JssorSlideshowFormations$.$FormationCircle = function (transition) {
        var cols = transition.$Cols || 1;
        var rows = transition.$Rows || 1;
        var arr = [];
        var i = 0;
        var col;
        var r;
        var hc = cols / 2 - 0.5;
        var hr = rows / 2 - 0.5;
        for (col = 0; col < cols; col++) {
            for (r = 0; r < rows; r++)
                PushFormationOrder(arr, Math.round(Math.sqrt(Math.pow(col - hc, 2) + Math.pow(r - hr, 2))), [r, col]);
        }
        return arr;
    };

    $JssorSlideshowFormations$.$FormationCross = function (transition) {
        var cols = transition.$Cols || 1;
        var rows = transition.$Rows || 1;
        var arr = [];
        var i = 0;
        var col;
        var r;
        var hc = cols / 2 - 0.5;
        var hr = rows / 2 - 0.5;
        for (col = 0; col < cols; col++) {
            for (r = 0; r < rows; r++)
                PushFormationOrder(arr, Math.round(Math.min(Math.abs(col - hc), Math.abs(r - hr))), [r, col]);
        }
        return arr;
    };

    $JssorSlideshowFormations$.$FormationRectangleCross = function (transition) {
        var cols = transition.$Cols || 1;
        var rows = transition.$Rows || 1;
        var arr = [];
        var i = 0;
        var col;
        var r;
        var hc = cols / 2 - 0.5;
        var hr = rows / 2 - 0.5;
        var cr = Math.max(hc, hr) + 1;
        for (col = 0; col < cols; col++) {
            for (r = 0; r < rows; r++)
                PushFormationOrder(arr, Math.round(cr - Math.max(hc - Math.abs(col - hc), hr - Math.abs(r - hr))) - 1, [r, col]);
        }
        return arr;
    };

    function GetFormation(transition) {

        var formationInstance = transition.$Formation(transition);

        return transition.$Reverse ? formationInstance.reverse() : formationInstance;

    } //GetFormation

    //var _PrototypeTransitions = [];
    function EnsureTransitionInstance(options, slideshowInterval) {

        var _SlideshowTransition = {
            $Interval: slideshowInterval,  //Delay to play next frame
            $Duration: 1, //Duration to finish the entire transition
            $Delay: 0,  //Delay to assembly blocks
            $Cols: 1,   //Number of columns
            $Rows: 1,   //Number of rows
            $Opacity: 0,   //Fade block or not
            $Zoom: 0,   //Zoom block or not
            $Clip: 0,   //Clip block or not
            $Move: false,   //Move block or not
            $SlideOut: false,   //Slide the previous slide out to display next slide instead
            //$FlyDirection: 0,   //Specify fly transform with direction
            $Reverse: false,    //Reverse the assembly or not
            $Formation: $JssorSlideshowFormations$.$FormationRandom,    //Shape that assembly blocks as
            $Assembly: ASSEMBLY_RIGHT_BOTTOM,   //The way to assembly blocks
            $ChessMode: { $Column: 0, $Row: 0 },    //Chess move or fly direction
            $Easing: $JssorEasing$.$EaseSwing,  //Specify variation of speed during transition
            $Round: {},
            $Blocks: [],
            $During: {}
        };

        $Jssor$.$Extend(_SlideshowTransition, options);

        _SlideshowTransition.$Count = _SlideshowTransition.$Cols * _SlideshowTransition.$Rows;
        if ($Jssor$.$IsFunction(_SlideshowTransition.$Easing))
            _SlideshowTransition.$Easing = { $Default: _SlideshowTransition.$Easing };

        _SlideshowTransition.$FramesCount = Math.ceil(_SlideshowTransition.$Duration / _SlideshowTransition.$Interval);
        _SlideshowTransition.$EasingInstance = GetEasing(_SlideshowTransition);

        _SlideshowTransition.$GetBlocks = function (width, height) {
            width /= _SlideshowTransition.$Cols;
            height /= _SlideshowTransition.$Rows;
            var wh = width + 'x' + height;
            if (!_SlideshowTransition.$Blocks[wh]) {
                _SlideshowTransition.$Blocks[wh] = { $Width: width, $Height: height };
                for (var col = 0; col < _SlideshowTransition.$Cols; col++) {
                    for (var r = 0; r < _SlideshowTransition.$Rows; r++)
                        _SlideshowTransition.$Blocks[wh][r + ',' + col] = { $Top: r * height, $Right: col * width + width, $Bottom: r * height + height, $Left: col * width };
                }
            }

            return _SlideshowTransition.$Blocks[wh];
        };

        if (_SlideshowTransition.$Brother) {
            _SlideshowTransition.$Brother = EnsureTransitionInstance(_SlideshowTransition.$Brother, slideshowInterval);
            _SlideshowTransition.$SlideOut = true;
        }

        return _SlideshowTransition;
    }

    function GetEasing(transition) {
        var easing = transition.$Easing;
        if (!easing.$Default)
            easing.$Default = $JssorEasing$.$EaseSwing;

        var duration = transition.$FramesCount;

        var cache = easing.$Cache;
        if (!cache) {
            var enumerator = $Jssor$.$Extend({}, transition.$Easing, transition.$Round);
            cache = easing.$Cache = {};

            $Jssor$.$Each(enumerator, function (v, easingName) {
                var easingFunction = easing[easingName] || easing.$Default;
                var round = transition.$Round[easingName] || 1;

                if (!$Jssor$.$IsArray(easingFunction.$Cache))
                    easingFunction.$Cache = [];

                var easingFunctionCache = easingFunction.$Cache[duration] = easingFunction.$Cache[duration] || [];

                if (!easingFunctionCache[round]) {
                    easingFunctionCache[round] = [0];
                    for (var t = 1; t <= duration; t++) {
                        var tRound = t / duration * round;
                        var tRoundFloor = Math.floor(tRound);
                        if (tRound != tRoundFloor)
                            tRound -= tRoundFloor;
                        easingFunctionCache[round][t] = easingFunction(tRound);
                    }
                }

                cache[easingName] = easingFunctionCache;

            });
        }

        return cache;
    } //GetEasing

    //Formation Definition -------

    function JssorSlideshowPlayer(slideContainer, slideElement, slideTransition, beginTime, slideContainerWidth, slideContainerHeight) {
        var _Self = this;

        var _Block;
        var _StartStylesArr = {};
        var _AnimationStylesArrs = {};
        var _AnimationBlockItems = [];
        var _StyleStart;
        var _StyleEnd;
        var _StyleDif;
        var _ChessModeColumn = slideTransition.$ChessMode.$Column || 0;
        var _ChessModeRow = slideTransition.$ChessMode.$Row || 0;

        var _Blocks = slideTransition.$GetBlocks(slideContainerWidth, slideContainerHeight);
        var _FormationInstance = GetFormation(slideTransition);
        var _MaxOrder = _FormationInstance.length - 1;
        var _Period = slideTransition.$Duration + slideTransition.$Delay * _MaxOrder;
        var _EndTime = beginTime + _Period;

        var _SlideOut = slideTransition.$SlideOut;
        var _IsIn;

        _EndTime += $Jssor$.$IsBrowserChrome() ? 260 : 50;

        _Self.$EndTime = _EndTime;

        _Self.$ShowFrame = function (time) {
            time -= beginTime;

            var isIn = time < _Period;

            if (isIn || _IsIn) {
                _IsIn = isIn;

                if (!_SlideOut)
                    time = _Period - time;

                var frameIndex = Math.ceil(time / slideTransition.$Interval);

                $Jssor$.$Each(_AnimationStylesArrs, function (value, index) {

                    var itemFrameIndex = Math.max(frameIndex, value.$Min);
                    itemFrameIndex = Math.min(itemFrameIndex, value.length - 1);

                    if (value.$LastFrameIndex != itemFrameIndex) {
                        if (!value.$LastFrameIndex && !_SlideOut) {
                            $Jssor$.$ShowElement(_AnimationBlockItems[index]);
                        }
                        else if (itemFrameIndex == value.$Max && _SlideOut) {
                            $Jssor$.$HideElement(_AnimationBlockItems[index]);
                        }
                        value.$LastFrameIndex = itemFrameIndex;
                        $Jssor$.$SetStylesEx(_AnimationBlockItems[index], value[itemFrameIndex]);
                    }
                });
            }
        };

        function DisableHWA(elmt) {
            $Jssor$.$DisableHWA(elmt);

            var children = $Jssor$.$Children(elmt);

            $Jssor$.$Each(children, function (child) {
                DisableHWA(child);
            });
        }

        //constructor
        {
            slideElement = $Jssor$.$CloneNode(slideElement);
            DisableHWA(slideElement);
            if ($Jssor$.$IsBrowserIe9Earlier()) {
                var hasImage = !slideElement["no-image"];
                var slideChildElements = $Jssor$.$FindChildrenByTag(slideElement);
                $Jssor$.$Each(slideChildElements, function (slideChildElement) {
                    if (hasImage || slideChildElement["jssor-slider"])
                        $Jssor$.$CssOpacity(slideChildElement, $Jssor$.$CssOpacity(slideChildElement), true);
                });
            }

            $Jssor$.$Each(_FormationInstance, function (formationItems, order) {
                $Jssor$.$Each(formationItems, function (formationItem) {
                    var row = formationItem[0];
                    var col = formationItem[1];
                    {
                        var columnRow = row + ',' + col;

                        var chessHorizontal = false;
                        var chessVertical = false;
                        var chessRotate = false;

                        if (_ChessModeColumn && col % 2) {
                            if ($JssorDirection$.$IsHorizontal(_ChessModeColumn)) {
                                chessHorizontal = !chessHorizontal;
                            }
                            if ($JssorDirection$.$IsVertical(_ChessModeColumn)) {
                                chessVertical = !chessVertical;
                            }

                            if (_ChessModeColumn & 16)
                                chessRotate = !chessRotate;
                        }

                        if (_ChessModeRow && row % 2) {
                            if ($JssorDirection$.$IsHorizontal(_ChessModeRow)) {
                                chessHorizontal = !chessHorizontal;
                            }
                            if ($JssorDirection$.$IsVertical(_ChessModeRow)) {
                                chessVertical = !chessVertical;
                            }
                            if (_ChessModeRow & 16)
                                chessRotate = !chessRotate;
                        }

                        slideTransition.$Top = slideTransition.$Top || (slideTransition.$Clip & 4);
                        slideTransition.$Bottom = slideTransition.$Bottom || (slideTransition.$Clip & 8);
                        slideTransition.$Left = slideTransition.$Left || (slideTransition.$Clip & 1);
                        slideTransition.$Right = slideTransition.$Right || (slideTransition.$Clip & 2);

                        var topBenchmark = chessVertical ? slideTransition.$Bottom : slideTransition.$Top;
                        var bottomBenchmark = chessVertical ? slideTransition.$Top : slideTransition.$Bottom;
                        var leftBenchmark = chessHorizontal ? slideTransition.$Right : slideTransition.$Left;
                        var rightBenchmark = chessHorizontal ? slideTransition.$Left : slideTransition.$Right;

                        //$JssorDebug$.$Execute(function () {
                        //    topBenchmark = bottomBenchmark = leftBenchmark = rightBenchmark = false;
                        //});

                        slideTransition.$Clip = topBenchmark || bottomBenchmark || leftBenchmark || rightBenchmark;

                        _StyleDif = {};
                        _StyleEnd = { $Top: 0, $Left: 0, $Opacity: 1, $Width: slideContainerWidth, $Height: slideContainerHeight };
                        _StyleStart = $Jssor$.$Extend({}, _StyleEnd);
                        _Block = $Jssor$.$Extend({}, _Blocks[columnRow]);

                        if (slideTransition.$Opacity) {
                            _StyleEnd.$Opacity = 2 - slideTransition.$Opacity;
                        }

                        if (slideTransition.$ZIndex) {
                            _StyleEnd.$ZIndex = slideTransition.$ZIndex;
                            _StyleStart.$ZIndex = 0;
                        }

                        var allowClip = slideTransition.$Cols * slideTransition.$Rows > 1 || slideTransition.$Clip;

                        if (slideTransition.$Zoom || slideTransition.$Rotate) {
                            var allowRotate = true;
                            if ($Jssor$.$IsBrowserIE() && $Jssor$.$BrowserEngineVersion() < 9) {
                                if (slideTransition.$Cols * slideTransition.$Rows > 1)
                                    allowRotate = false;
                                else
                                    allowClip = false;
                            }

                            if (allowRotate) {
                                _StyleEnd.$Zoom = slideTransition.$Zoom ? slideTransition.$Zoom - 1 : 1;
                                _StyleStart.$Zoom = 1;

                                if ($Jssor$.$IsBrowserIe9Earlier() || $Jssor$.$IsBrowserOpera())
                                    _StyleEnd.$Zoom = Math.min(_StyleEnd.$Zoom, 2);

                                var rotate = slideTransition.$Rotate;

                                _StyleEnd.$Rotate = rotate * 360 * ((chessRotate) ? -1 : 1);
                                _StyleStart.$Rotate = 0;
                            }
                        }

                        if (allowClip) {
                            if (slideTransition.$Clip) {
                                var clipScale = slideTransition.$ScaleClip || 1;
                                var blockOffset = _Block.$Offset = {};
                                if (topBenchmark && bottomBenchmark) {
                                    blockOffset.$Top = _Blocks.$Height / 2 * clipScale;
                                    blockOffset.$Bottom = -blockOffset.$Top;
                                }
                                else if (topBenchmark) {
                                    blockOffset.$Bottom = -_Blocks.$Height * clipScale;
                                }
                                else if (bottomBenchmark) {
                                    blockOffset.$Top = _Blocks.$Height * clipScale;
                                }

                                if (leftBenchmark && rightBenchmark) {
                                    blockOffset.$Left = _Blocks.$Width / 2 * clipScale;
                                    blockOffset.$Right = -blockOffset.$Left;
                                }
                                else if (leftBenchmark) {
                                    blockOffset.$Right = -_Blocks.$Width * clipScale;
                                }
                                else if (rightBenchmark) {
                                    blockOffset.$Left = _Blocks.$Width * clipScale;
                                }
                            }

                            _StyleDif.$Clip = _Block;
                            _StyleStart.$Clip = _Blocks[columnRow];
                        }

                        //fly
                        {
                            var chessHor = chessHorizontal ? 1 : -1;
                            var chessVer = chessVertical ? 1 : -1;

                            if (slideTransition.x)
                                _StyleEnd.$Left += slideContainerWidth * slideTransition.x * chessHor;

                            if (slideTransition.y)
                                _StyleEnd.$Top += slideContainerHeight * slideTransition.y * chessVer;
                        }

                        $Jssor$.$Each(_StyleEnd, function (propertyEnd, property) {
                            if ($Jssor$.$IsNumeric(propertyEnd)) {
                                if (propertyEnd != _StyleStart[property]) {
                                    _StyleDif[property] = propertyEnd - _StyleStart[property];
                                }
                            }
                        });

                        _StartStylesArr[columnRow] = _SlideOut ? _StyleStart : _StyleEnd;

                        var animationStylesArr = [];
                        var virtualFrameCount = Math.round(order * slideTransition.$Delay / slideTransition.$Interval);
                        _AnimationStylesArrs[columnRow] = new Array(virtualFrameCount);
                        _AnimationStylesArrs[columnRow].$Min = virtualFrameCount;

                        var framesCount = slideTransition.$FramesCount;
                        for (var frameN = 0; frameN <= framesCount; frameN++) {
                            var styleFrameN = {};

                            $Jssor$.$Each(_StyleDif, function (propertyDiff, property) {
                                var propertyEasings = slideTransition.$EasingInstance[property] || slideTransition.$EasingInstance.$Default;
                                var propertyEasingArray = propertyEasings[slideTransition.$Round[property] || 1];

                                var propertyDuring = slideTransition.$During[property] || [0, 1];
                                var propertyFrameN = (frameN / framesCount - propertyDuring[0]) / propertyDuring[1] * framesCount;
                                propertyFrameN = Math.round(Math.min(framesCount, Math.max(propertyFrameN, 0)));

                                var propertyEasingValue = propertyEasingArray[propertyFrameN];

                                if ($Jssor$.$IsNumeric(propertyDiff)) {
                                    styleFrameN[property] = _StyleStart[property] + propertyDiff * propertyEasingValue;
                                }
                                else {
                                    var value = styleFrameN[property] = $Jssor$.$Extend({}, _StyleStart[property]);
                                    value.$Offset = [];
                                    $Jssor$.$Each(propertyDiff.$Offset, function (rectX, n) {
                                        var offsetValue = rectX * propertyEasingValue;
                                        value.$Offset[n] = offsetValue;
                                        value[n] += offsetValue;
                                    });
                                }
                            });

                            if (_StyleStart.$Zoom) {
                                styleFrameN.$Transform = { $Rotate: styleFrameN.$Rotate || 0, $Scale: styleFrameN.$Zoom, $OriginalWidth: slideContainerWidth, $OriginalHeight: slideContainerHeight };
                            }
                            if (styleFrameN.$Clip && slideTransition.$Move) {
                                var styleFrameNClipOffset = styleFrameN.$Clip.$Offset;
                                var offsetY = (styleFrameNClipOffset.$Top || 0) + (styleFrameNClipOffset.$Bottom || 0);
                                var offsetX = (styleFrameNClipOffset.$Left || 0) + (styleFrameNClipOffset.$Right || 0);

                                styleFrameN.$Left = (styleFrameN.$Left || 0) + offsetX;
                                styleFrameN.$Top = (styleFrameN.$Top || 0) + offsetY;
                                styleFrameN.$Clip.$Left -= offsetX;
                                styleFrameN.$Clip.$Right -= offsetX;
                                styleFrameN.$Clip.$Top -= offsetY;
                                styleFrameN.$Clip.$Bottom -= offsetY;
                            }

                            styleFrameN.$ZIndex = styleFrameN.$ZIndex || 1;

                            _AnimationStylesArrs[columnRow].push(styleFrameN);
                        }

                    } //for
                });
            });

            _FormationInstance.reverse();
            $Jssor$.$Each(_FormationInstance, function (formationItems) {
                $Jssor$.$Each(formationItems, function (formationItem) {
                    var row = formationItem[0];
                    var col = formationItem[1];

                    var columnRow = row + ',' + col;

                    var image = slideElement;
                    if (col || row)
                        image = $Jssor$.$CloneNode(slideElement);

                    $Jssor$.$SetStyles(image, _StartStylesArr[columnRow]);
                    $Jssor$.$CssOverflow(image, "hidden");

                    $Jssor$.$CssPosition(image, "absolute");
                    slideContainer.$AddClipElement(image);
                    _AnimationBlockItems[columnRow] = image;
                    $Jssor$.$ShowElement(image, !_SlideOut);
                });
            });
        }
    }

    //JssorSlideshowRunner++++++++
    var _SlideshowRunnerCount = 1;
    $JssorSlideshowRunner$ = window.$JssorSlideshowRunner$ = function (slideContainer, slideContainerWidth, slideContainerHeight, slideshowOptions, handleTouchEventOnly) {

        var _SelfSlideshowRunner = this;

        //var _State = 0; //-1 fullfill, 0 clean, 1 initializing, 2 stay, 3 playing
        var _EndTime;

        var _SliderFrameCount;

        var _SlideshowPlayerBelow;
        var _SlideshowPlayerAbove;

        var _PrevItem;
        var _SlideItem;

        var _TransitionIndex = 0;
        var _TransitionsOrder = slideshowOptions.$TransitionsOrder;

        var _SlideshowTransition;

        var _SlideshowPerformance = 8;

        function SlideshowProcessor() {
            var _SelfSlideshowProcessor = this;
            var _CurrentTime = 0;

            $JssorAnimator$.call(_SelfSlideshowProcessor, 0, _EndTime);

            _SelfSlideshowProcessor.$OnPositionChange = function (oldPosition, newPosition) {
                if ((newPosition - _CurrentTime) > _SlideshowPerformance) {
                    _CurrentTime = newPosition;

                    _SlideshowPlayerAbove && _SlideshowPlayerAbove.$ShowFrame(newPosition);
                    _SlideshowPlayerBelow && _SlideshowPlayerBelow.$ShowFrame(newPosition);
                }
            };

            _SelfSlideshowProcessor.$Transition = _SlideshowTransition;
        }

        //member functions
        _SelfSlideshowRunner.$GetTransition = function (slideCount) {
            var n = 0;

            var transitions = slideshowOptions.$Transitions;

            var transitionCount = transitions.length;

            if (_TransitionsOrder) { /*Sequence*/
                //if (transitionCount > slideCount && ($Jssor$.$IsBrowserChrome() || $Jssor$.$IsBrowserSafari() || $Jssor$.$IsBrowserFireFox())) {
                //    transitionCount -= transitionCount % slideCount;
                //}
                n = _TransitionIndex++ % transitionCount;
            }
            else { /*Random*/
                n = Math.floor(Math.random() * transitionCount);
            }

            transitions[n] && (transitions[n].$Index = n);

            return transitions[n];
        };

        _SelfSlideshowRunner.$Initialize = function (slideIndex, prevIndex, slideItem, prevItem, slideshowTransition) {
            $JssorDebug$.$Execute(function () {
                if (_SlideshowPlayerBelow) {
                    $JssorDebug$.$Fail("slideshow runner has not been cleared.");
                }
            });

            _SlideshowTransition = slideshowTransition;

            slideshowTransition = EnsureTransitionInstance(slideshowTransition, _SlideshowPerformance);

            _SlideItem = slideItem;
            _PrevItem = prevItem;

            var prevSlideElement = prevItem.$Item;
            var currentSlideElement = slideItem.$Item;
            prevSlideElement["no-image"] = !prevItem.$Image;
            currentSlideElement["no-image"] = !slideItem.$Image;

            var slideElementAbove = prevSlideElement;
            var slideElementBelow = currentSlideElement;

            var slideTransitionAbove = slideshowTransition;
            var slideTransitionBelow = slideshowTransition.$Brother || EnsureTransitionInstance({}, _SlideshowPerformance);

            if (!slideshowTransition.$SlideOut) {
                slideElementAbove = currentSlideElement;
                slideElementBelow = prevSlideElement;
            }

            var shift = slideTransitionBelow.$Shift || 0;

            _SlideshowPlayerBelow = new JssorSlideshowPlayer(slideContainer, slideElementBelow, slideTransitionBelow, Math.max(shift - slideTransitionBelow.$Interval, 0), slideContainerWidth, slideContainerHeight);
            _SlideshowPlayerAbove = new JssorSlideshowPlayer(slideContainer, slideElementAbove, slideTransitionAbove, Math.max(slideTransitionBelow.$Interval - shift, 0), slideContainerWidth, slideContainerHeight);

            _SlideshowPlayerBelow.$ShowFrame(0);
            _SlideshowPlayerAbove.$ShowFrame(0);

            _EndTime = Math.max(_SlideshowPlayerBelow.$EndTime, _SlideshowPlayerAbove.$EndTime);

            _SelfSlideshowRunner.$Index = slideIndex;
        };

        _SelfSlideshowRunner.$Clear = function () {
            slideContainer.$Clear();
            _SlideshowPlayerBelow = null;
            _SlideshowPlayerAbove = null;
        };

        _SelfSlideshowRunner.$GetProcessor = function () {
            var slideshowProcessor = null;

            if (_SlideshowPlayerAbove)
                slideshowProcessor = new SlideshowProcessor();

            return slideshowProcessor;
        };

        //Constructor
        {
            if ($Jssor$.$IsBrowserIe9Earlier() || $Jssor$.$IsBrowserOpera() || (handleTouchEventOnly && $Jssor$.$WebKitVersion() < 537)) {
                _SlideshowPerformance = 16;
            }

            $JssorObject$.call(_SelfSlideshowRunner);
            $JssorAnimator$.call(_SelfSlideshowRunner, -10000000, 10000000);

            $JssorDebug$.$LiveStamp(_SelfSlideshowRunner, "slideshow_runner_" + _SlideshowRunnerCount++);
        }
    };
    //JssorSlideshowRunner--------

    //JssorSlider
    function JssorSlider(elmt, options) {
        var _SelfSlider = this;

        //private classes
        function Conveyor() {
            var _SelfConveyor = this;
            $JssorAnimator$.call(_SelfConveyor, -100000000, 200000000);

            _SelfConveyor.$GetCurrentSlideInfo = function () {
                var positionDisplay = _SelfConveyor.$GetPosition_Display();
                var virtualIndex = Math.floor(positionDisplay);
                var slideIndex = GetRealIndex(virtualIndex);
                var slidePosition = positionDisplay - Math.floor(positionDisplay);

                return { $Index: slideIndex, $VirtualIndex: virtualIndex, $Position: slidePosition };
            };

            _SelfConveyor.$OnPositionChange = function (oldPosition, newPosition) {

                var index = Math.floor(newPosition);
                if (index != newPosition && newPosition > oldPosition)
                    index++;

                ResetNavigator(index, true);

                _SelfSlider.$TriggerEvent(JssorSlider.$EVT_POSITION_CHANGE, GetRealIndex(newPosition), GetRealIndex(oldPosition), newPosition, oldPosition);
            };
        }

        //Carousel
        function Carousel() {
            var _SelfCarousel = this;

            $JssorAnimator$.call(_SelfCarousel, 0, 0, { $LoopLength: _SlideCount });

            //Carousel Constructor
            {
                $Jssor$.$Each(_SlideItems, function (slideItem) {
                    (_Loop & 1) && slideItem.$SetLoopLength(_SlideCount);
                    _SelfCarousel.$Chain(slideItem);
                    slideItem.$Shift(_ParkingPosition / _StepLength);
                });
            }
        }
        //Carousel

        //Slideshow
        function Slideshow() {
            var _SelfSlideshow = this;
            var _Wrapper = _SlideContainer.$Elmt;

            $JssorAnimator$.call(_SelfSlideshow, -1, 2, { $Easing: $JssorEasing$.$EaseLinear, $Setter: { $Position: SetPosition }, $LoopLength: _SlideCount }, _Wrapper, { $Position: 1 }, { $Position: -1 });

            _SelfSlideshow.$Wrapper = _Wrapper;

            //Slideshow Constructor
            {
                $JssorDebug$.$Execute(function () {
                    $Jssor$.$Attribute(_SlideContainer.$Elmt, "debug-id", "slide_container");
                });
            }
        }
        //Slideshow

        //CarouselPlayer
        function CarouselPlayer(carousel, slideshow) {
            var _SelfCarouselPlayer = this;
            var _FromPosition;
            var _ToPosition;
            var _Duration;
            var _StandBy;
            var _StandByPosition;

            $JssorAnimator$.call(_SelfCarouselPlayer, -100000000, 200000000, { $IntervalMax: 100 });

            _SelfCarouselPlayer.$OnStart = function () {
                _IsSliding = true;
                _LoadingTicket = null;

                //EVT_SWIPE_START
                _SelfSlider.$TriggerEvent(JssorSlider.$EVT_SWIPE_START, GetRealIndex(_Conveyor.$GetPosition()), _Conveyor.$GetPosition());
            };

            _SelfCarouselPlayer.$OnStop = function () {

                _IsSliding = false;
                _StandBy = false;

                var currentSlideInfo = _Conveyor.$GetCurrentSlideInfo();

                //EVT_SWIPE_END
                _SelfSlider.$TriggerEvent(JssorSlider.$EVT_SWIPE_END, GetRealIndex(_Conveyor.$GetPosition()), _Conveyor.$GetPosition());

                if (!currentSlideInfo.$Position) {
                    OnPark(currentSlideInfo.$VirtualIndex, _CurrentSlideIndex);
                }
            };

            _SelfCarouselPlayer.$OnPositionChange = function (oldPosition, newPosition) {

                var toPosition;

                if (_StandBy)
                    toPosition = _StandByPosition;
                else {
                    toPosition = _ToPosition;

                    if (_Duration) {
                        var interPosition = newPosition / _Duration;
                        //if ($Jssor$.$IsBrowserChrome() || $Jssor$.$IsBrowserFireFox()) {
                        //    Math.round(interPosition * 8 / _Duration) / 8 * _Duration;

                        //    if ($Jssor$.$BrowserVersion() < 38)
                        //        interPosition = parseFloat(interPosition.toFixed(4));
                        //}
                        toPosition = _Options.$SlideEasing(interPosition) * (_ToPosition - _FromPosition) + _FromPosition;
                    }
                }

                _Conveyor.$GoToPosition(toPosition);
            };

            _SelfCarouselPlayer.$PlayCarousel = function (fromPosition, toPosition, duration, callback) {
                $JssorDebug$.$Execute(function () {
                    if (_SelfCarouselPlayer.$IsPlaying())
                        $JssorDebug$.$Fail("The carousel is already playing.");
                });

                _FromPosition = fromPosition;
                _ToPosition = toPosition;
                _Duration = duration;

                _Conveyor.$GoToPosition(fromPosition);
                _SelfCarouselPlayer.$GoToPosition(0);

                _SelfCarouselPlayer.$PlayToPosition(duration, callback);
            };

            _SelfCarouselPlayer.$StandBy = function (standByPosition) {
                _StandBy = true;
                _StandByPosition = standByPosition;
                _SelfCarouselPlayer.$Play(standByPosition, null, true);
            };

            _SelfCarouselPlayer.$SetStandByPosition = function (standByPosition) {
                _StandByPosition = standByPosition;
            };

            _SelfCarouselPlayer.$MoveCarouselTo = function (position) {
                _Conveyor.$GoToPosition(position);
            };

            //CarouselPlayer Constructor
            {
                _Conveyor = new Conveyor();

                _Conveyor.$Combine(carousel);
                _Conveyor.$Combine(slideshow);
            }
        }
        //CarouselPlayer

        //SlideContainer
        function SlideContainer() {
            var _Self = this;
            var elmt = CreatePanel();

            $Jssor$.$CssZIndex(elmt, 0);
            $Jssor$.$Css(elmt, "pointerEvents", "none");

            _Self.$Elmt = elmt;

            _Self.$AddClipElement = function (clipElement) {
                $Jssor$.$AppendChild(elmt, clipElement);
                $Jssor$.$ShowElement(elmt);
            };

            _Self.$Clear = function () {
                $Jssor$.$HideElement(elmt);
                $Jssor$.$ClearInnerHtml(elmt);
            };
        }
        //SlideContainer

        //SlideItem
        function SlideItem(slideElmt, slideIndex) {

            var _SelfSlideItem = this;

            var _CaptionSliderIn;
            var _CaptionSliderOut;
            var _CaptionSliderCurrent;
            var _IsCaptionSliderPlayingWhenDragStart;

            var _Wrapper;
            var _BaseElement = slideElmt;

            var _LoadingScreen;

            var _ImageItem;
            var _ImageElmts = [];
            var _LinkItemOrigin;
            var _LinkItem;
            var _ImageLoading;
            var _ImageLoaded;
            var _ImageLazyLoading;
            var _ContentRefreshed;

            var _Processor;

            var _PlayerInstanceElement;
            var _PlayerInstance;

            var _SequenceNumber;    //for debug only

            $JssorAnimator$.call(_SelfSlideItem, -_DisplayPieces, _DisplayPieces + 1, { $SlideItemAnimator: true });

            function ResetCaptionSlider(fresh) {
                _CaptionSliderOut && _CaptionSliderOut.$Revert();
                _CaptionSliderIn && _CaptionSliderIn.$Revert();

                RefreshContent(slideElmt, fresh);
                _ContentRefreshed = true;

                _CaptionSliderIn = new _CaptionSliderOptions.$Class(slideElmt, _CaptionSliderOptions, 1);
                $JssorDebug$.$LiveStamp(_CaptionSliderIn, "caption_slider_" + _CaptionSliderCount + "_in");
                _CaptionSliderOut = new _CaptionSliderOptions.$Class(slideElmt, _CaptionSliderOptions);
                $JssorDebug$.$LiveStamp(_CaptionSliderOut, "caption_slider_" + _CaptionSliderCount + "_out");

                $JssorDebug$.$Execute(function () {
                    _CaptionSliderCount++;
                });

                _CaptionSliderOut.$GoToBegin();
                _CaptionSliderIn.$GoToBegin();
            }

            function EnsureCaptionSliderVersion() {
                if (_CaptionSliderIn.$Version < _CaptionSliderOptions.$Version) {
                    ResetCaptionSlider();
                }
            }

            //event handling begin
            function LoadImageCompleteEventHandler(completeCallback, loadingScreen, image) {
                if (!_ImageLoaded) {
                    _ImageLoaded = true;

                    if (_ImageItem && image) {
                        var imageWidth = image.width;
                        var imageHeight = image.height;
                        var fillWidth = imageWidth;
                        var fillHeight = imageHeight;

                        if (imageWidth && imageHeight && _Options.$FillMode) {

                            //0 stretch, 1 contain (keep aspect ratio and put all inside slide), 2 cover (keep aspect ratio and cover whole slide), 4 actual size, 5 contain for large image, actual size for small image, default value is 0
                            if (_Options.$FillMode & 3 && (!(_Options.$FillMode & 4) || imageWidth > _SlideWidth || imageHeight > _SlideHeight)) {
                                var fitHeight = false;
                                var ratio = _SlideWidth / _SlideHeight * imageHeight / imageWidth;

                                if (_Options.$FillMode & 1) {
                                    fitHeight = (ratio > 1);
                                }
                                else if (_Options.$FillMode & 2) {
                                    fitHeight = (ratio < 1);
                                }
                                fillWidth = fitHeight ? imageWidth * _SlideHeight / imageHeight : _SlideWidth;
                                fillHeight = fitHeight ? _SlideHeight : imageHeight * _SlideWidth / imageWidth;
                            }

                            $Jssor$.$CssWidth(_ImageItem, fillWidth);
                            $Jssor$.$CssHeight(_ImageItem, fillHeight);
                            $Jssor$.$CssTop(_ImageItem, (_SlideHeight - fillHeight) / 2);
                            $Jssor$.$CssLeft(_ImageItem, (_SlideWidth - fillWidth) / 2);
                        }

                        $Jssor$.$CssPosition(_ImageItem, "absolute");

                        _SelfSlider.$TriggerEvent(JssorSlider.$EVT_LOAD_END, slideItem);
                    }
                }

                $Jssor$.$HideElement(loadingScreen);
                completeCallback && completeCallback(_SelfSlideItem);
            }

            function LoadSlideshowImageCompleteEventHandler(nextIndex, nextItem, slideshowTransition, loadingTicket) {
                if (loadingTicket == _LoadingTicket && _CurrentSlideIndex == slideIndex && _AutoPlay) {
                    if (!_Frozen) {
                        var nextRealIndex = GetRealIndex(nextIndex);
                        _SlideshowRunner.$Initialize(nextRealIndex, slideIndex, nextItem, _SelfSlideItem, slideshowTransition);
                        nextItem.$HideContentForSlideshow();
                        _Slideshow.$Locate(nextRealIndex, 1);
                        _Slideshow.$GoToPosition(nextRealIndex);
                        _CarouselPlayer.$PlayCarousel(nextIndex, nextIndex, 0);
                    }
                }
            }

            function SlideReadyEventHandler(loadingTicket) {
                if (loadingTicket == _LoadingTicket && _CurrentSlideIndex == slideIndex) {

                    if (!_Processor) {
                        var slideshowProcessor = null;
                        if (_SlideshowRunner) {
                            if (_SlideshowRunner.$Index == slideIndex)
                                slideshowProcessor = _SlideshowRunner.$GetProcessor();
                            else
                                _SlideshowRunner.$Clear();
                        }

                        EnsureCaptionSliderVersion();

                        _Processor = new Processor(slideElmt, slideIndex, slideshowProcessor, _SelfSlideItem.$GetCaptionSliderIn(), _SelfSlideItem.$GetCaptionSliderOut());
                        _Processor.$SetPlayer(_PlayerInstance);
                    }

                    !_Processor.$IsPlaying() && _Processor.$Replay();
                }
            }

            function ParkEventHandler(currentIndex, previousIndex, manualActivate) {
                if (currentIndex == slideIndex) {

                    if (currentIndex != previousIndex)
                        _SlideItems[previousIndex] && _SlideItems[previousIndex].$ParkOut();
                    else
                        !manualActivate && _Processor && _Processor.$AdjustIdleOnPark();

                    _PlayerInstance && _PlayerInstance.$Enable();

                    //park in
                    var loadingTicket = _LoadingTicket = $Jssor$.$GetNow();
                    _SelfSlideItem.$LoadImage($Jssor$.$CreateCallback(null, SlideReadyEventHandler, loadingTicket));
                }
                else {
                    var distance = Math.abs(slideIndex - currentIndex);
                    var loadRange = _DisplayPieces + _Options.$LazyLoading;
                    if (!_ImageLazyLoading || distance <= loadRange || _SlideCount - distance <= loadRange) {
                        _SelfSlideItem.$LoadImage();
                    }
                }
            }

            function SwipeStartEventHandler() {
                if (_CurrentSlideIndex == slideIndex && _Processor) {
                    _Processor.$Stop();
                    _PlayerInstance && _PlayerInstance.$Quit();
                    _PlayerInstance && _PlayerInstance.$Disable();
                    _Processor.$OpenSlideshowPanel();
                }
            }

            function FreezeEventHandler() {
                if (_CurrentSlideIndex == slideIndex && _Processor) {
                    _Processor.$Stop();
                }
            }

            function LinkClickEventHandler(event) {
                if (_LastDragSucceded) {
                    $Jssor$.$CancelEvent(event);
                }
                else {
                    _SelfSlider.$TriggerEvent(JssorSlider.$EVT_CLICK, slideIndex, event);
                }
            }

            function PlayerAvailableEventHandler() {
                _PlayerInstance = _PlayerInstanceElement.pInstance;
                _Processor && _Processor.$SetPlayer(_PlayerInstance);
            }

            _SelfSlideItem.$LoadImage = function (completeCallback, loadingScreen) {
                loadingScreen = loadingScreen || _LoadingScreen;

                if (_ImageElmts.length && !_ImageLoaded) {

                    $Jssor$.$ShowElement(loadingScreen);

                    if (!_ImageLoading) {
                        _ImageLoading = true;
                        _SelfSlider.$TriggerEvent(JssorSlider.$EVT_LOAD_START);

                        $Jssor$.$Each(_ImageElmts, function (imageElmt) {

                            if (!imageElmt.src) {
                                imageElmt.src = $Jssor$.$AttributeEx(imageElmt, "src2");
                                $Jssor$.$CssDisplay(imageElmt, imageElmt["display-origin"]);
                            }
                        });
                    }
                    $Jssor$.$LoadImages(_ImageElmts, _ImageItem, $Jssor$.$CreateCallback(null, LoadImageCompleteEventHandler, completeCallback, loadingScreen));
                }
                else {
                    LoadImageCompleteEventHandler(completeCallback, loadingScreen);
                }
            };

            _SelfSlideItem.$GoForNextSlide = function () {
                if (_SlideshowRunner) {
                    var slideshowTransition = _SlideshowRunner.$GetTransition(_SlideCount);

                    if (slideshowTransition) {
                        var loadingTicket = _LoadingTicket = $Jssor$.$GetNow();

                        var nextIndex = slideIndex + _PlayReverse;
                        var nextItem = _SlideItems[GetRealIndex(nextIndex)];
                        return nextItem.$LoadImage($Jssor$.$CreateCallback(null, LoadSlideshowImageCompleteEventHandler, nextIndex, nextItem, slideshowTransition, loadingTicket), _LoadingScreen);
                    }
                }

                PlayTo(_CurrentSlideIndex + _Options.$AutoPlaySteps * _PlayReverse);
            };

            _SelfSlideItem.$TryActivate = function () {
                ParkEventHandler(slideIndex, slideIndex, true);
            };

            _SelfSlideItem.$ParkOut = function () {
                //park out
                _PlayerInstance && _PlayerInstance.$Quit();
                _PlayerInstance && _PlayerInstance.$Disable();
                _SelfSlideItem.$UnhideContentForSlideshow();
                _Processor && _Processor.$Abort();
                _Processor = null;
                ResetCaptionSlider();
            };

            //for debug only
            _SelfSlideItem.$StampSlideItemElements = function (stamp) {
                stamp = _SequenceNumber + "_" + stamp;

                $JssorDebug$.$Execute(function () {
                    if (_ImageItem)
                        $Jssor$.$Attribute(_ImageItem, "debug-id", stamp + "_slide_item_image_id");

                    $Jssor$.$Attribute(slideElmt, "debug-id", stamp + "_slide_item_item_id");
                });

                $JssorDebug$.$Execute(function () {
                    $Jssor$.$Attribute(_Wrapper, "debug-id", stamp + "_slide_item_wrapper_id");
                });

                $JssorDebug$.$Execute(function () {
                    $Jssor$.$Attribute(_LoadingScreen, "debug-id", stamp + "_loading_container_id");
                });
            };

            _SelfSlideItem.$HideContentForSlideshow = function () {
                $Jssor$.$HideElement(slideElmt);
            };

            _SelfSlideItem.$UnhideContentForSlideshow = function () {
                $Jssor$.$ShowElement(slideElmt);
            };

            _SelfSlideItem.$EnablePlayer = function () {
                _PlayerInstance && _PlayerInstance.$Enable();
            };

            function RefreshContent(elmt, fresh, level) {
                if (elmt["jssor-slider"])
                    return;

                level = level || 0;

                if (!_ContentRefreshed) {
                    if (elmt.tagName == "IMG") {
                        _ImageElmts.push(elmt);

                        if (!elmt.src) {
                            _ImageLazyLoading = true;
                            elmt["display-origin"] = $Jssor$.$CssDisplay(elmt);
                            $Jssor$.$HideElement(elmt);
                        }
                    }
                    if ($Jssor$.$IsBrowserIe9Earlier()) {
                        $Jssor$.$CssZIndex(elmt, ($Jssor$.$CssZIndex(elmt) || 0) + 1);
                    }
                    if (_Options.$HWA && $Jssor$.$WebKitVersion()) {
                        if (!_IsTouchDevice || $Jssor$.$WebKitVersion() < 534 || (!_SlideshowEnabled && !$Jssor$.$IsBrowserChrome())) {
                            $Jssor$.$EnableHWA(elmt);
                        }
                    }
                }

                var childElements = $Jssor$.$Children(elmt);

                $Jssor$.$Each(childElements, function (childElement, i) {

                    var uAttribute = $Jssor$.$AttributeEx(childElement, "u");
                    if (uAttribute == "player" && !_PlayerInstanceElement) {
                        _PlayerInstanceElement = childElement;
                        if (_PlayerInstanceElement.pInstance) {
                            PlayerAvailableEventHandler();
                        }
                        else {
                            $Jssor$.$AddEvent(_PlayerInstanceElement, "dataavailable", PlayerAvailableEventHandler);
                        }
                    }

                    if (uAttribute == "caption") {
                        if (!$Jssor$.$IsBrowserIE() && !fresh) {
                            var captionElement = $Jssor$.$CloneNode(childElement);
                            $Jssor$.$InsertBefore(elmt, captionElement, childElement);
                            $Jssor$.$RemoveChild(elmt, childElement);
                            childElement = captionElement;

                            fresh = true;
                        }
                    }
                    else if (!_ContentRefreshed && !level && !_ImageItem && $Jssor$.$AttributeEx(childElement, "u") == "image") {
                        _ImageItem = childElement;

                        if (_ImageItem) {
                            if (_ImageItem.tagName == "A") {
                                _LinkItemOrigin = _ImageItem;
                                $Jssor$.$SetStyles(_LinkItemOrigin, _StyleDef);

                                _LinkItem = $Jssor$.$CloneNode(_ImageItem, true);
                                //cancel click event on <A> element when a drag of slide succeeded
                                $Jssor$.$AddEvent(_LinkItem, "click", LinkClickEventHandler);

                                $Jssor$.$SetStyles(_LinkItem, _StyleDef);
                                $Jssor$.$CssDisplay(_LinkItem, "block");
                                $Jssor$.$CssOpacity(_LinkItem, 0);
                                $Jssor$.$Css(_LinkItem, "backgroundColor", "#000");

                                _ImageItem = $Jssor$.$FindChildByTag(_ImageItem, "IMG");

                                $JssorDebug$.$Execute(function () {
                                    if (!_ImageItem) {
                                        $JssorDebug$.$Error("slide html code definition error, no 'IMG' found in a 'image with link' slide.\r\n" + elmt.outerHTML);
                                    }
                                });
                            }
                            _ImageItem.border = 0;

                            $Jssor$.$SetStyles(_ImageItem, _StyleDef);
                        }
                    }

                    RefreshContent(childElement, fresh, level + 1);
                });
            }

            _SelfSlideItem.$OnInnerOffsetChange = function (oldOffset, newOffset) {
                var slidePosition = _DisplayPieces - newOffset;

                SetPosition(_Wrapper, slidePosition);

                //following lines are for future usage, not ready yet
                //if (!_IsDragging || !_IsCaptionSliderPlayingWhenDragStart) {
                //    var _DealWithParallax;
                //    if (IsCurrentSlideIndex(slideIndex)) {
                //        if (_CaptionSliderOptions.$PlayOutMode == 2)
                //            _DealWithParallax = true;
                //    }
                //    else {
                //        if (!_CaptionSliderOptions.$PlayInMode) {
                //            //PlayInMode: 0 none
                //            _CaptionSliderIn.$GoToEnd();
                //        }
                //        //else if (_CaptionSliderOptions.$PlayInMode == 1) {
                //        //    //PlayInMode: 1 chain
                //        //    _CaptionSliderIn.$GoToBegin();
                //        //}
                //        else if (_CaptionSliderOptions.$PlayInMode == 2) {
                //            //PlayInMode: 2 parallel
                //            _DealWithParallax = true;
                //        }
                //    }

                //    if (_DealWithParallax) {
                //        _CaptionSliderIn.$GoToPosition((_CaptionSliderIn.$GetPosition_OuterEnd() - _CaptionSliderIn.$GetPosition_OuterBegin()) * Math.abs(newOffset - 1) * .8 + _CaptionSliderIn.$GetPosition_OuterBegin());
                //    }
                //}
            };

            _SelfSlideItem.$GetCaptionSliderIn = function () {
                return _CaptionSliderIn;
            };

            _SelfSlideItem.$GetCaptionSliderOut = function () {
                return _CaptionSliderOut;
            };

            _SelfSlideItem.$Index = slideIndex;

            $JssorObject$.call(_SelfSlideItem);

            //SlideItem Constructor
            {

                var thumb = $Jssor$.$FindChild(slideElmt, "thumb", true);
                if (thumb) {
                    _SelfSlideItem.$Thumb = $Jssor$.$CloneNode(thumb);
                    $Jssor$.$RemoveAttribute(thumb, "id");
                    $Jssor$.$HideElement(thumb);
                }
                $Jssor$.$ShowElement(slideElmt);

                _LoadingScreen = $Jssor$.$CloneNode(_LoadingContainer);
                $Jssor$.$CssZIndex(_LoadingScreen, 1000);

                //cancel click event on <A> element when a drag of slide succeeded
                $Jssor$.$AddEvent(slideElmt, "click", LinkClickEventHandler);

                ResetCaptionSlider(true);

                _SelfSlideItem.$Image = _ImageItem;
                _SelfSlideItem.$Link = _LinkItem;

                _SelfSlideItem.$Item = slideElmt;

                _SelfSlideItem.$Wrapper = _Wrapper = slideElmt;
                $Jssor$.$AppendChild(_Wrapper, _LoadingScreen);

                _SelfSlider.$On(203, ParkEventHandler);
                _SelfSlider.$On(28, FreezeEventHandler);
                _SelfSlider.$On(24, SwipeStartEventHandler);

                $JssorDebug$.$Execute(function () {
                    _SequenceNumber = _SlideItemCreatedCount++;
                });

                $JssorDebug$.$Execute(function () {
                    $Jssor$.$Attribute(_Wrapper, "debug-id", "slide-" + slideIndex);
                });
            }
        }
        //SlideItem

        //Processor
        function Processor(slideElmt, slideIndex, slideshowProcessor, captionSliderIn, captionSliderOut) {

            var _SelfProcessor = this;

            var _ProgressBegin = 0;
            var _SlideshowBegin = 0;
            var _SlideshowEnd;
            var _CaptionInBegin;
            var _IdleBegin;
            var _IdleEnd;
            var _ProgressEnd;

            var _IsSlideshowRunning;
            var _IsRollingBack;

            var _PlayerInstance;
            var _IsPlayerOnService;

            var slideItem = _SlideItems[slideIndex];

            $JssorAnimator$.call(_SelfProcessor, 0, 0);

            function UpdateLink() {

                $Jssor$.$ClearChildren(_LinkContainer);

                if (_ShowLink && _IsSlideshowRunning && slideItem.$Link) {
                    $Jssor$.$AppendChild(_LinkContainer, slideItem.$Link);
                }

                $Jssor$.$ShowElement(_LinkContainer, !_IsSlideshowRunning && slideItem.$Image);
            }

            function ProcessCompleteEventHandler() {

                if (_IsRollingBack) {
                    _IsRollingBack = false;
                    _SelfSlider.$TriggerEvent(JssorSlider.$EVT_ROLLBACK_END, slideIndex, _IdleEnd, _ProgressBegin, _IdleBegin, _IdleEnd, _ProgressEnd);
                    _SelfProcessor.$GoToPosition(_IdleBegin);
                }

                _SelfProcessor.$Replay();
            }

            function PlayerSwitchEventHandler(isOnService) {
                _IsPlayerOnService = isOnService;

                _SelfProcessor.$Stop();
                _SelfProcessor.$Replay();
            }

            _SelfProcessor.$Replay = function () {

                var currentPosition = _SelfProcessor.$GetPosition_Display();

                if (!_IsDragging && !_IsSliding && !_IsPlayerOnService && _CurrentSlideIndex == slideIndex) {

                    if (!currentPosition) {
                        if (_SlideshowEnd && !_IsSlideshowRunning) {
                            _IsSlideshowRunning = true;

                            _SelfProcessor.$OpenSlideshowPanel(true);

                            _SelfSlider.$TriggerEvent(JssorSlider.$EVT_SLIDESHOW_START, slideIndex, _ProgressBegin, _SlideshowBegin, _SlideshowEnd, _ProgressEnd);
                        }

                        UpdateLink();
                    }

                    var toPosition;
                    var stateEvent = JssorSlider.$EVT_STATE_CHANGE;

                    if (currentPosition != _ProgressEnd) {
                        if (currentPosition == _IdleEnd) {
                            toPosition = _ProgressEnd;
                        }
                        else if (currentPosition == _IdleBegin) {
                            toPosition = _IdleEnd;
                        }
                        else if (!currentPosition) {
                            toPosition = _IdleBegin;
                        }
                        else if (currentPosition > _IdleEnd) {
                            _IsRollingBack = true;
                            toPosition = _IdleEnd;
                            stateEvent = JssorSlider.$EVT_ROLLBACK_START;
                        }
                        else {
                            //continue from break (by drag or lock)
                            toPosition = _SelfProcessor.$GetPlayToPosition();
                        }
                    }

                    //$JssorDebug$.$Execute(function () {
                    //    if (currentPosition == _ProgressEnd) {
                    //        debugger;
                    //    }
                    //});

                    _SelfSlider.$TriggerEvent(stateEvent, slideIndex, currentPosition, _ProgressBegin, _IdleBegin, _IdleEnd, _ProgressEnd);

                    var allowAutoPlay = _AutoPlay && (!_HoverToPause || _NotOnHover);

                    if (currentPosition == _ProgressEnd) {
                        (_IdleEnd != _ProgressEnd && !(_HoverToPause & 12) || allowAutoPlay) && slideItem.$GoForNextSlide();
                    }
                    else if (allowAutoPlay || currentPosition != _IdleEnd) {
                        _SelfProcessor.$PlayToPosition(toPosition, ProcessCompleteEventHandler);
                    }
                }
            };

            _SelfProcessor.$AdjustIdleOnPark = function () {
                if (_IdleEnd == _ProgressEnd && _IdleEnd == _SelfProcessor.$GetPosition_Display())
                    _SelfProcessor.$GoToPosition(_IdleBegin);
            };

            _SelfProcessor.$Abort = function () {
                _SlideshowRunner && _SlideshowRunner.$Index == slideIndex && _SlideshowRunner.$Clear();

                var currentPosition = _SelfProcessor.$GetPosition_Display();
                if (currentPosition < _ProgressEnd) {
                    _SelfSlider.$TriggerEvent(JssorSlider.$EVT_STATE_CHANGE, slideIndex, -currentPosition -1, _ProgressBegin, _IdleBegin, _IdleEnd, _ProgressEnd);
                }
            };

            _SelfProcessor.$OpenSlideshowPanel = function (open) {
                if (slideshowProcessor) {
                    $Jssor$.$CssOverflow(_SlideshowPanel, open && slideshowProcessor.$Transition.$Outside ? "" : "hidden");
                }
            };

            _SelfProcessor.$OnInnerOffsetChange = function (oldPosition, newPosition) {

                if (_IsSlideshowRunning && newPosition >= _SlideshowEnd) {
                    _IsSlideshowRunning = false;
                    UpdateLink();
                    slideItem.$UnhideContentForSlideshow();
                    _SlideshowRunner.$Clear();

                    _SelfSlider.$TriggerEvent(JssorSlider.$EVT_SLIDESHOW_END, slideIndex, _ProgressBegin, _SlideshowBegin, _SlideshowEnd, _ProgressEnd);
                }

                _SelfSlider.$TriggerEvent(JssorSlider.$EVT_PROGRESS_CHANGE, slideIndex, newPosition, _ProgressBegin, _IdleBegin, _IdleEnd, _ProgressEnd);
            };

            _SelfProcessor.$SetPlayer = function (playerInstance) {
                if (playerInstance && !_PlayerInstance) {
                    _PlayerInstance = playerInstance;

                    playerInstance.$On($JssorPlayer$.$EVT_SWITCH, PlayerSwitchEventHandler);
                }
            };

            //Processor Constructor
            {
                if (slideshowProcessor) {
                    _SelfProcessor.$Chain(slideshowProcessor);
                }

                _SlideshowEnd = _SelfProcessor.$GetPosition_OuterEnd();
                _CaptionInBegin = _SelfProcessor.$GetPosition_OuterEnd();
                _SelfProcessor.$Chain(captionSliderIn);
                _IdleBegin = captionSliderIn.$GetPosition_OuterEnd();
                _IdleEnd = _IdleBegin + ($Jssor$.$ParseFloat($Jssor$.$AttributeEx(slideElmt, "idle")) || _Options.$AutoPlayInterval);

                captionSliderOut.$Shift(_IdleEnd);
                _SelfProcessor.$Combine(captionSliderOut);
                _ProgressEnd = _SelfProcessor.$GetPosition_OuterEnd();
            }
        }
        //Processor
        //private classes

        function SetPosition(elmt, position) {
            var orientation = _DragOrientation > 0 ? _DragOrientation : _PlayOrientation;
            var x = _StepLengthX * position * (orientation & 1);
            var y = _StepLengthY * position * ((orientation >> 1) & 1);

            if ($Jssor$.$IsBrowserChrome() && $Jssor$.$BrowserVersion() < 38) {
                x = x.toFixed(3);
                y = y.toFixed(3);
            }
            else {
                x = Math.round(x);
                y = Math.round(y);
            }

            if ($Jssor$.$IsBrowserIE() && $Jssor$.$BrowserVersion() >= 10 && $Jssor$.$BrowserVersion() < 11) {
                elmt.style.msTransform = "translate(" + x + "px, " + y + "px)";
            }
            else if ($Jssor$.$IsBrowserChrome() && $Jssor$.$BrowserVersion() >= 30 && $Jssor$.$BrowserVersion() < 34) {
                elmt.style.WebkitTransition = "transform 0s";
                elmt.style.WebkitTransform = "translate3d(" + x + "px, " + y + "px, 0px) perspective(2000px)";
            }
            else {
                $Jssor$.$CssLeft(elmt, x);
                $Jssor$.$CssTop(elmt, y);
            }
        }

        //Event handling begin

        function OnMouseDown(event) {
            var tagName = $Jssor$.$EventSrc(event).tagName;
            if (!_DragOrientationRegistered && tagName != "INPUT" && tagName != "TEXTAREA" && tagName != "SELECT" && RegisterDrag()) {
                OnDragStart(event);
            }
        }

        function RecordFreezePoint() {

            _CarouselPlaying_OnFreeze = _IsSliding;
            _PlayToPosition_OnFreeze = _CarouselPlayer.$GetPlayToPosition();
            _Position_OnFreeze = _Conveyor.$GetPosition();

        }

        function Freeze() {

            RecordFreezePoint();

            if (_IsDragging || !_NotOnHover && (_HoverToPause & 12)) {
                _CarouselPlayer.$Stop();

                _SelfSlider.$TriggerEvent(JssorSlider.$EVT_FREEZE);
            }

        }

        function Unfreeze(byDrag) {

            byDrag && RecordFreezePoint();

            if (!_IsDragging && (_NotOnHover || !(_HoverToPause & 12)) && !_CarouselPlayer.$IsPlaying()) {

                var currentPosition = _Conveyor.$GetPosition();
                var toPosition = Math.ceil(_Position_OnFreeze);

                if (byDrag && Math.abs(_DragOffsetTotal) >= _Options.$MinDragOffsetToSlide) {
                    toPosition = Math.ceil(currentPosition);
                    toPosition += _DragIndexAdjust;
                }

                if (!(_Loop & 1)) {
                    toPosition = Math.min(_SlideCount - _DisplayPieces, Math.max(toPosition, 0));
                }

                var t = Math.abs(toPosition - currentPosition);
                t = 1 - Math.pow(1 - t, 5);

                if (!_LastDragSucceded && _CarouselPlaying_OnFreeze) {
                    _CarouselPlayer.$Continue(_PlayToPosition_OnFreeze);
                }
                else if (currentPosition == toPosition) {
                    _CurrentSlideItem.$EnablePlayer();
                    _CurrentSlideItem.$TryActivate();
                }
                else {

                    _CarouselPlayer.$PlayCarousel(currentPosition, toPosition, t * _SlideDuration);
                }
            }
        }

        function OnDragStart(event) {

            _IsDragging = true;
            _DragInvalid = false;
            _LoadingTicket = null;

            $Jssor$.$AddEvent(document, _MoveEvent, OnDragMove);

            _LastTimeMoveByDrag = $Jssor$.$GetNow() - 50;

            _LastDragSucceded = 0;
            Freeze();

            if (!_CarouselPlaying_OnFreeze)
                _DragOrientation = 0;

            if (_HandleTouchEventOnly) {
                var touchPoint = event.touches[0];
                _DragStartMouseX = touchPoint.clientX;
                _DragStartMouseY = touchPoint.clientY;
            }
            else {
                var mousePoint = $Jssor$.$MousePosition(event);

                _DragStartMouseX = mousePoint.x;
                _DragStartMouseY = mousePoint.y;

                $Jssor$.$CancelEvent(event);
            }

            _DragOffsetTotal = 0;
            _DragOffsetLastTime = 0;
            _DragIndexAdjust = 0;

            //Trigger EVT_DRAGSTART
            _SelfSlider.$TriggerEvent(JssorSlider.$EVT_DRAG_START, GetRealIndex(_Position_OnFreeze), _Position_OnFreeze, event);
        }

        function OnDragMove(event) {
            if (_IsDragging && (!$Jssor$.$IsBrowserIe9Earlier() || event.button)) {
                var actionPoint;

                if (_HandleTouchEventOnly) {
                    var touches = event.touches;
                    if (touches && touches.length > 0) {
                        actionPoint = { x: touches[0].clientX, y: touches[0].clientY };
                    }
                }
                else {
                    actionPoint = $Jssor$.$MousePosition(event);
                }

                if (actionPoint) {
                    var distanceX = actionPoint.x - _DragStartMouseX;
                    var distanceY = actionPoint.y - _DragStartMouseY;


                    if (Math.floor(_Position_OnFreeze) != _Position_OnFreeze)
                        _DragOrientation = _DragOrientation || (_PlayOrientation & _DragOrientationRegistered);

                    if ((distanceX || distanceY) && !_DragOrientation) {
                        if (_DragOrientationRegistered == 3) {
                            if (Math.abs(distanceY) > Math.abs(distanceX)) {
                                _DragOrientation = 2;
                            }
                            else
                                _DragOrientation = 1;
                        }
                        else {
                            _DragOrientation = _DragOrientationRegistered;
                        }

                        if (_IsTouchDevice && _DragOrientation == 1 && Math.abs(distanceY) - Math.abs(distanceX) > 3) {
                            _DragInvalid = true;
                        }
                    }

                    if (_DragOrientation) {
                        var distance = distanceY;
                        var stepLength = _StepLengthY;

                        if (_DragOrientation == 1) {
                            distance = distanceX;
                            stepLength = _StepLengthX;
                        }

                        if (!(_Loop & 1)) {
                            if (distance > 0) {
                                var normalDistance = stepLength * _CurrentSlideIndex;
                                var sqrtDistance = distance - normalDistance;
                                if (sqrtDistance > 0) {
                                    distance = normalDistance + Math.sqrt(sqrtDistance) * 5;
                                }
                            }

                            if (distance < 0) {
                                var normalDistance = stepLength * (_SlideCount - _DisplayPieces - _CurrentSlideIndex);
                                var sqrtDistance = -distance - normalDistance;

                                if (sqrtDistance > 0) {
                                    distance = -normalDistance - Math.sqrt(sqrtDistance) * 5;
                                }
                            }
                        }

                        if (_DragOffsetTotal - _DragOffsetLastTime < -2) {
                            _DragIndexAdjust = 0;
                        }
                        else if (_DragOffsetTotal - _DragOffsetLastTime > 2) {
                            _DragIndexAdjust = -1;
                        }

                        _DragOffsetLastTime = _DragOffsetTotal;
                        _DragOffsetTotal = distance;
                        _PositionToGoByDrag = _Position_OnFreeze - _DragOffsetTotal / stepLength / (_ScaleRatio || 1);

                        if (_DragOffsetTotal && _DragOrientation && !_DragInvalid) {
                            $Jssor$.$CancelEvent(event);
                            if (!_IsSliding) {
                                _CarouselPlayer.$StandBy(_PositionToGoByDrag);
                            }
                            else
                                _CarouselPlayer.$SetStandByPosition(_PositionToGoByDrag);
                        }
                        else if ($Jssor$.$IsBrowserIe9Earlier()) {
                            $Jssor$.$CancelEvent(event);
                        }
                    }
                }
            }
            else {
                OnDragEnd(event);
            }
        }

        function OnDragEnd(event) {
            UnregisterDrag();

            if (_IsDragging) {

                _IsDragging = false;

                _LastTimeMoveByDrag = $Jssor$.$GetNow();

                $Jssor$.$RemoveEvent(document, _MoveEvent, OnDragMove);

                _LastDragSucceded = _DragOffsetTotal;

                _LastDragSucceded && $Jssor$.$CancelEvent(event);

                _CarouselPlayer.$Stop();

                var currentPosition = _Conveyor.$GetPosition();

                //Trigger EVT_DRAG_END
                _SelfSlider.$TriggerEvent(JssorSlider.$EVT_DRAG_END, GetRealIndex(currentPosition), currentPosition, GetRealIndex(_Position_OnFreeze), _Position_OnFreeze, event);

                Unfreeze(true);
            }
        }
        //Event handling end

        function SetCurrentSlideIndex(index) {
            _PrevSlideItem = _SlideItems[_CurrentSlideIndex];
            _PreviousSlideIndex = _CurrentSlideIndex;
            _CurrentSlideIndex = GetRealIndex(index);
            _CurrentSlideItem = _SlideItems[_CurrentSlideIndex];
            ResetNavigator(index);
            return _CurrentSlideIndex;
        }

        function OnPark(slideIndex, prevIndex) {
            _DragOrientation = 0;

            SetCurrentSlideIndex(slideIndex);

            //Trigger EVT_PARK
            _SelfSlider.$TriggerEvent(JssorSlider.$EVT_PARK, GetRealIndex(slideIndex), prevIndex);
        }

        function ResetNavigator(index, temp) {
            _TempSlideIndex = index;
            $Jssor$.$Each(_Navigators, function (navigator) {
                navigator.$SetCurrentIndex(GetRealIndex(index), index, temp);
            });
        }

        function RegisterDrag() {
            var dragRegistry = JssorSlider.$DragRegistry || 0;
            var dragOrientation = _DragEnabled;
            if (_IsTouchDevice)
                (dragOrientation & 1) && (dragOrientation &= 1);
            JssorSlider.$DragRegistry |= dragOrientation;

            return (_DragOrientationRegistered = dragOrientation & ~dragRegistry);
        }

        function UnregisterDrag() {
            if (_DragOrientationRegistered) {
                JssorSlider.$DragRegistry &= ~_DragEnabled;
                _DragOrientationRegistered = 0;
            }
        }

        function CreatePanel() {
            var div = $Jssor$.$CreateDiv();

            $Jssor$.$SetStyles(div, _StyleDef);
            $Jssor$.$CssPosition(div, "absolute");

            return div;
        }

        function GetRealIndex(index) {
            return (index % _SlideCount + _SlideCount) % _SlideCount;
        }

        function IsCurrentSlideIndex(index) {
            return GetRealIndex(index) == _CurrentSlideIndex;
        }

        function IsPreviousSlideIndex(index) {
            return GetRealIndex(index) == _PreviousSlideIndex;
        }

        //Navigation Request Handler
        function NavigationClickHandler(index, relative) {
            if (relative) {
                if (!_Loop) {
                    //Stop at threshold
                    index = Math.min(Math.max(index + _TempSlideIndex, 0), _SlideCount - _DisplayPieces);
                    relative = false;
                }
                else if (_Loop & 2) {
                    //Rewind
                    index = GetRealIndex(index + _TempSlideIndex);
                    relative = false;
                }
            }
            PlayTo(index, _Options.$SlideDuration, relative);
        }

        function ShowNavigators() {
            $Jssor$.$Each(_Navigators, function (navigator) {
                navigator.$Show(navigator.$Options.$ChanceToShow <= _NotOnHover);
            });
        }

        function MainContainerMouseLeaveEventHandler() {
            if (!_NotOnHover) {

                //$JssorDebug$.$Log("mouseleave");

                _NotOnHover = 1;

                ShowNavigators();

                if (!_IsDragging) {
                    (_HoverToPause & 12) && Unfreeze();
                    (_HoverToPause & 3) && _SlideItems[_CurrentSlideIndex].$TryActivate();
                }
            }
        }

        function MainContainerMouseEnterEventHandler() {

            if (_NotOnHover) {

                //$JssorDebug$.$Log("mouseenter");

                _NotOnHover = 0;

                ShowNavigators();

                _IsDragging || !(_HoverToPause & 12) || Freeze();
            }
        }

        function AdjustSlidesContainerSize() {
            _StyleDef = { $Width: _SlideWidth, $Height: _SlideHeight, $Top: 0, $Left: 0 };

            $Jssor$.$Each(_SlideElmts, function (slideElmt, i) {

                $Jssor$.$SetStyles(slideElmt, _StyleDef);
                $Jssor$.$CssPosition(slideElmt, "absolute");
                $Jssor$.$CssOverflow(slideElmt, "hidden");

                $Jssor$.$HideElement(slideElmt);
            });

            $Jssor$.$SetStyles(_LoadingContainer, _StyleDef);
        }

        function PlayToOffset(offset, slideDuration) {
            PlayTo(offset, slideDuration, true);
        }

        function PlayTo(slideIndex, slideDuration, relative) {
            ///	<summary>
            ///		PlayTo( slideIndex [, slideDuration] ); //Play slider to position 'slideIndex' within a period calculated base on 'slideDuration'.
            ///	</summary>
            ///	<param name="slideIndex" type="Number">
            ///		slide slideIndex or position will be playing to
            ///	</param>
            ///	<param name="slideDuration" type="Number" optional="true">
            ///		base slide duration in milliseconds to calculate the whole duration to complete this play request.
            ///	    default value is '$SlideDuration' value which is specified when initialize the slider.
            ///	</param>
            /// http://msdn.microsoft.com/en-us/library/vstudio/bb385682.aspx
            /// http://msdn.microsoft.com/en-us/library/vstudio/hh542720.aspx
            if (_CarouselEnabled && (!_IsDragging || _Options.$NaviQuitDrag)) {
                _IsSliding = true;
                _IsDragging = false;
                _CarouselPlayer.$Stop();

                {
                    //Slide Duration
                    if (slideDuration == undefined)
                        slideDuration = _SlideDuration;

                    var positionDisplay = _Carousel.$GetPosition_Display();
                    var positionTo = slideIndex;
                    if (relative) {
                        positionTo = positionDisplay + slideIndex;
                        if (slideIndex > 0)
                            positionTo = Math.ceil(positionTo);
                        else
                            positionTo = Math.floor(positionTo);
                    }


                    if (!(_Loop & 1)) {
                        positionTo = GetRealIndex(positionTo);
                        positionTo = Math.max(0, Math.min(positionTo, _SlideCount - _DisplayPieces));
                    }

                    var positionOffset = (positionTo - positionDisplay) % _SlideCount;
                    positionTo = positionDisplay + positionOffset;

                    var duration = positionDisplay == positionTo ? 0 : slideDuration * Math.abs(positionOffset);
                    duration = Math.min(duration, slideDuration * _DisplayPieces * 1.5);

                    _CarouselPlayer.$PlayCarousel(positionDisplay, positionTo, duration || 1);
                }
            }
        }

        //private functions

        //member functions

        _SelfSlider.$PlayTo = PlayTo;

        _SelfSlider.$GoTo = function (slideIndex) {
            ///	<summary>
            ///		instance.$GoTo( slideIndex );   //Go to the specifed slide immediately with no play.
            ///	</summary>
            PlayTo(slideIndex, 1);
        };

        _SelfSlider.$Next = function () {
            ///	<summary>
            ///		instance.$Next();   //Play the slider to next slide.
            ///	</summary>
            PlayToOffset(1);
        };

        _SelfSlider.$Prev = function () {
            ///	<summary>
            ///		instance.$Prev();   //Play the slider to previous slide.
            ///	</summary>
            PlayToOffset(-1);
        };

        _SelfSlider.$Pause = function () {
            ///	<summary>
            ///		instance.$Pause();   //Pause the slider, prevent it from auto playing.
            ///	</summary>
            _AutoPlay = false;
        };

        _SelfSlider.$Play = function () {
            ///	<summary>
            ///		instance.$Play();   //Start auto play if the slider is currently paused.
            ///	</summary>
            if (!_AutoPlay) {
                _AutoPlay = true;
                _SlideItems[_CurrentSlideIndex] && _SlideItems[_CurrentSlideIndex].$TryActivate();
            }
        };

        _SelfSlider.$SetSlideshowTransitions = function (transitions) {
            ///	<summary>
            ///		instance.$SetSlideshowTransitions( transitions );   //Reset slideshow transitions for the slider.
            ///	</summary>
            $JssorDebug$.$Execute(function () {
                if (!transitions || !transitions.length) {
                    $JssorDebug$.$Error("Can not set slideshow transitions, no transitions specified.");
                }
            });

            $Jssor$.$TranslateTransitions(transitions);    //for old transition compatibility
            _Options.$SlideshowOptions.$Transitions = transitions;
        };

        _SelfSlider.$SetCaptionTransitions = function (transitions) {
            ///	<summary>
            ///		instance.$SetCaptionTransitions( transitions );   //Reset caption transitions for the slider.
            ///	</summary>
            $JssorDebug$.$Execute(function () {
                if (!transitions || !transitions.length) {
                    $JssorDebug$.$Error("Can not set caption transitions, no transitions specified");
                }
            });

            $Jssor$.$TranslateTransitions(transitions);    //for old transition compatibility
            _CaptionSliderOptions.$CaptionTransitions = transitions;
            _CaptionSliderOptions.$Version = $Jssor$.$GetNow();
        };

        _SelfSlider.$SlidesCount = function () {
            ///	<summary>
            ///		instance.$SlidesCount();   //Retrieve slides count of the slider.
            ///	</summary>
            return _SlideElmts.length;
        };

        _SelfSlider.$CurrentIndex = function () {
            ///	<summary>
            ///		instance.$CurrentIndex();   //Retrieve current slide index of the slider.
            ///	</summary>
            return _CurrentSlideIndex;
        };

        _SelfSlider.$IsAutoPlaying = function () {
            ///	<summary>
            ///		instance.$IsAutoPlaying();   //Retrieve auto play status of the slider.
            ///	</summary>
            return _AutoPlay;
        };

        _SelfSlider.$IsDragging = function () {
            ///	<summary>
            ///		instance.$IsDragging();   //Retrieve drag status of the slider.
            ///	</summary>
            return _IsDragging;
        };

        _SelfSlider.$IsSliding = function () {
            ///	<summary>
            ///		instance.$IsSliding();   //Retrieve right<-->left sliding status of the slider.
            ///	</summary>
            return _IsSliding;
        };

        _SelfSlider.$IsMouseOver = function () {
            ///	<summary>
            ///		instance.$IsMouseOver();   //Retrieve mouse over status of the slider.
            ///	</summary>
            return !_NotOnHover;
        };

        _SelfSlider.$LastDragSucceded = function () {
            ///	<summary>
            ///		instance.$IsLastDragSucceded();   //Retrieve last drag succeded status, returns 0 if failed, returns drag offset if succeded
            ///	</summary>
            return _LastDragSucceded;
        };

        function OriginalWidth() {
            ///	<summary>
            ///		instance.$OriginalWidth();   //Retrieve original width of the slider.
            ///	</summary>
            return $Jssor$.$CssWidth(_ScaleWrapper || elmt);
        }

        function OriginalHeight() {
            ///	<summary>
            ///		instance.$OriginalHeight();   //Retrieve original height of the slider.
            ///	</summary>
            return $Jssor$.$CssHeight(_ScaleWrapper || elmt);
        }

        _SelfSlider.$OriginalWidth = _SelfSlider.$GetOriginalWidth = OriginalWidth;

        _SelfSlider.$OriginalHeight = _SelfSlider.$GetOriginalHeight = OriginalHeight;

        function Scale(dimension, isHeight) {
            ///	<summary>
            ///		instance.$ScaleWidth();   //Retrieve scaled dimension the slider currently displays.
            ///		instance.$ScaleWidth( dimension );   //Scale the slider to new width and keep aspect ratio.
            ///	</summary>

            if (dimension == undefined)
                return $Jssor$.$CssWidth(elmt);

            $JssorDebug$.$Execute(function () {
                if (!dimension || dimension < 0) {
                    $JssorDebug$.$Fail("'$ScaleWidth' error, 'dimension' should be positive value.");
                }
            });

            if (!_ScaleWrapper) {
                $JssorDebug$.$Execute(function () {
                    var originalWidthStr = $Jssor$.$Css(elmt, "width");
                    var originalHeightStr = $Jssor$.$Css(elmt, "height");
                    var originalWidth = $Jssor$.$CssP(elmt, "width");
                    var originalHeight = $Jssor$.$CssP(elmt, "height");

                    if (!originalWidthStr) {
                        $JssorDebug$.$Fail("Cannot scale jssor slider, 'dimension' of 'outer container' not specified. Please specify 'dimension' in pixel. e.g. 'dimension: 600px;'");
                    }

                    if (!originalHeightStr) {
                        $JssorDebug$.$Fail("Cannot scale jssor slider, 'height' of 'outer container' not specified. Please specify 'height' in pixel. e.g. 'height: 300px;'");
                    }

                    if (originalWidthStr.indexOf('%') != -1) {
                        $JssorDebug$.$Fail("Cannot scale jssor slider, 'dimension' of 'outer container' not valid. Please specify 'dimension' in pixel. e.g. 'dimension: 600px;'");
                    }

                    if (originalHeightStr.indexOf('%') != -1) {
                        $JssorDebug$.$Fail("Cannot scale jssor slider, 'height' of 'outer container' not valid. Please specify 'height' in pixel. e.g. 'height: 300px;'");
                    }

                    if (!originalWidth) {
                        $JssorDebug$.$Fail("Cannot scale jssor slider, 'dimension' of 'outer container' not valid. 'dimension' of 'outer container' should be positive number. e.g. 'dimension: 600px;'");
                    }

                    if (!originalHeight) {
                        $JssorDebug$.$Fail("Cannot scale jssor slider, 'height' of 'outer container' not valid. 'height' of 'outer container' should be positive number. e.g. 'height: 300px;'");
                    }
                });

                var innerWrapper = $Jssor$.$CreateDiv(document);
                $Jssor$.$CssCssText(innerWrapper, $Jssor$.$CssCssText(elmt));
                $Jssor$.$ClassName(innerWrapper, $Jssor$.$ClassName(elmt));

                $Jssor$.$CssPosition(innerWrapper, "relative");
                $Jssor$.$CssTop(innerWrapper, 0);
                $Jssor$.$CssLeft(innerWrapper, 0);
                $Jssor$.$CssOverflow(innerWrapper, "visible");

                _ScaleWrapper = $Jssor$.$CreateDiv(document);

                $Jssor$.$CssPosition(_ScaleWrapper, "absolute");
                $Jssor$.$CssTop(_ScaleWrapper, 0);
                $Jssor$.$CssLeft(_ScaleWrapper, 0);
                $Jssor$.$CssWidth(_ScaleWrapper, $Jssor$.$CssWidth(elmt));
                $Jssor$.$CssHeight(_ScaleWrapper, $Jssor$.$CssHeight(elmt));
                $Jssor$.$SetStyleTransformOrigin(_ScaleWrapper, "0 0");

                $Jssor$.$AppendChild(_ScaleWrapper, innerWrapper);

                var children = $Jssor$.$Children(elmt);
                $Jssor$.$AppendChild(elmt, _ScaleWrapper);

                $Jssor$.$Css(elmt, "backgroundImage", "");

                var noMoveElmts = {
                    "navigator": _BulletNavigatorOptions && _BulletNavigatorOptions.$Scale == false,
                    "arrowleft": _ArrowNavigatorOptions && _ArrowNavigatorOptions.$Scale == false,
                    "arrowright": _ArrowNavigatorOptions && _ArrowNavigatorOptions.$Scale == false,
                    "thumbnavigator": _ThumbnailNavigatorOptions && _ThumbnailNavigatorOptions.$Scale == false,
                    "thumbwrapper": _ThumbnailNavigatorOptions && _ThumbnailNavigatorOptions.$Scale == false
                };

                $Jssor$.$Each(children, function (child) {
                    $Jssor$.$AppendChild(noMoveElmts[$Jssor$.$AttributeEx(child, "u")] ? elmt : innerWrapper, child);
                });

                $Jssor$.$ShowElement(innerWrapper);
                $Jssor$.$ShowElement(_ScaleWrapper);
            }

            $JssorDebug$.$Execute(function () {
                if (!_InitialScrollWidth) {
                    _InitialScrollWidth = _SelfSlider.$Elmt.scrollWidth;
                }
            });

            _ScaleRatio = dimension /  (isHeight? $Jssor$.$CssHeight : $Jssor$.$CssWidth)(_ScaleWrapper);
            $Jssor$.$CssScale(_ScaleWrapper, _ScaleRatio);

            var scaleWidth = isHeight ? (_ScaleRatio * OriginalWidth()) : dimension;
            var scaleHeight = isHeight ? dimension : (_ScaleRatio * OriginalHeight());

            $Jssor$.$CssWidth(elmt, scaleWidth);
            $Jssor$.$CssHeight(elmt, scaleHeight);

            $Jssor$.$Each(_Navigators, function (navigator) {
                navigator.$Relocate(scaleWidth, scaleHeight);
            });
        }

        _SelfSlider.$ScaleHeight = _SelfSlider.$GetScaleHeight = function (height) {
            ///	<summary>
            ///		instance.$ScaleHeight();   //Retrieve scaled height the slider currently displays.
            ///		instance.$ScaleHeight( dimension );   //Scale the slider to new height and keep aspect ratio.
            ///	</summary>

            if (height == undefined)
                return $Jssor$.$CssHeight(elmt);

            Scale(height, true);
        };

        _SelfSlider.$ScaleWidth = _SelfSlider.$SetScaleWidth = _SelfSlider.$GetScaleWidth = Scale;

        _SelfSlider.$GetVirtualIndex = function (index) {
            var parkingIndex = Math.ceil(GetRealIndex(_ParkingPosition / _StepLength));
            var displayIndex = GetRealIndex(index - _CurrentSlideIndex + parkingIndex);

            if (displayIndex > _DisplayPieces) {
                if (index - _CurrentSlideIndex > _SlideCount / 2)
                    index -= _SlideCount;
                else if (index - _CurrentSlideIndex <= -_SlideCount / 2)
                    index += _SlideCount;
            }
            else {
                index = _CurrentSlideIndex + displayIndex - parkingIndex;
            }

            return index;
        };

        //member functions

        $JssorObject$.call(_SelfSlider);

        $JssorDebug$.$Execute(function () {
            var outerContainerElmt = $Jssor$.$GetElement(elmt);
            if (!outerContainerElmt)
                $JssorDebug$.$Fail("Outer container '" + elmt + "' not found.");
        });

        //initialize member variables
        _SelfSlider.$Elmt = elmt = $Jssor$.$GetElement(elmt);
        //initialize member variables

        var _InitialScrollWidth;    //for debug only
        var _CaptionSliderCount = 1;    //for debug only

        var _Options = $Jssor$.$Extend({
            $FillMode: 0,                   //[Optional] The way to fill image in slide, 0 stretch, 1 contain (keep aspect ratio and put all inside slide), 2 cover (keep aspect ratio and cover whole slide), 4 actual size, 5 contain for large image, actual size for small image, default value is 0
            $LazyLoading: 1,                //[Optional] For image with  lazy loading format (<IMG src2="url" .../>), by default it will be loaded only when the slide comes.
            //But an integer value (maybe 0, 1, 2 or 3) indicates that how far of nearby slides should be loaded immediately as well, default value is 1.
            $StartIndex: 0,                 //[Optional] Index of slide to display when initialize, default value is 0
            $AutoPlay: false,               //[Optional] Whether to auto play, default value is false
            $Loop: 1,                       //[Optional] Enable loop(circular) of carousel or not, 0: stop, 1: loop, 2 rewind, default value is 1
            $HWA: true,                     //[Optional] Enable hardware acceleration or not, default value is true
            $NaviQuitDrag: true,
            $AutoPlaySteps: 1,              //[Optional] Steps to go of every play (this options applys only when slideshow disabled), default value is 1
            $AutoPlayInterval: 3000,        //[Optional] Interval to play next slide since the previous stopped if a slideshow is auto playing, default value is 3000
            $PauseOnHover: 1,               //[Optional] Whether to pause when mouse over if a slider is auto playing, 0 no pause, 1 pause for desktop, 2 pause for touch device, 3 pause for desktop and touch device, 4 freeze for desktop, 8 freeze for touch device, 12 freeze for desktop and touch device, default value is 1

            $SlideDuration: 500,            //[Optional] Specifies default duration (swipe) for slide in milliseconds, default value is 400
            $SlideEasing: $JssorEasing$.$EaseOutQuad,   //[Optional] Specifies easing for right to left animation, default value is $JssorEasing$.$EaseOutQuad
            $MinDragOffsetToSlide: 20,      //[Optional] Minimum drag offset that trigger slide, default value is 20
            $SlideSpacing: 0, 				//[Optional] Space between each slide in pixels, default value is 0
            $DisplayPieces: 1,              //[Optional] Number of pieces to display (the slideshow would be disabled if the value is set to greater than 1), default value is 1
            $ParkingPosition: 0,            //[Optional] The offset position to park slide (this options applys only when slideshow disabled), default value is 0.
            $UISearchMode: 1,               //[Optional] The way (0 parellel, 1 recursive, default value is recursive) to search UI components (slides container, loading screen, navigator container, arrow navigator container, thumbnail navigator container etc.
            $PlayOrientation: 1,            //[Optional] Orientation to play slide (for auto play, navigation), 1 horizental, 2 vertical, 5 horizental reverse, 6 vertical reverse, default value is 1
            $DragOrientation: 1             //[Optional] Orientation to drag slide, 0 no drag, 1 horizental, 2 vertical, 3 both, default value is 1 (Note that the $DragOrientation should be the same as $PlayOrientation when $DisplayPieces is greater than 1, or parking position is not 0)

        }, options);

        //Sodo statement for development time intellisence only
        $JssorDebug$.$Execute(function () {
            _Options = $Jssor$.$Extend({
                $ArrowKeyNavigation: undefined,
                $SlideWidth: undefined,
                $SlideHeight: undefined,
                $SlideshowOptions: undefined,
                $CaptionSliderOptions: undefined,
                $BulletNavigatorOptions: undefined,
                $ArrowNavigatorOptions: undefined,
                $ThumbnailNavigatorOptions: undefined
            },
            _Options);
        });

        var _PlayOrientation = _Options.$PlayOrientation & 3;
        var _PlayReverse = (_Options.$PlayOrientation & 4) / -4 || 1;

        var _SlideshowOptions = _Options.$SlideshowOptions;
        var _CaptionSliderOptions = $Jssor$.$Extend({ $Class: $JssorCaptionSliderBase$, $PlayInMode: 1, $PlayOutMode: 1 }, _Options.$CaptionSliderOptions);
        $Jssor$.$TranslateTransitions(_CaptionSliderOptions.$CaptionTransitions); //for old transition compatibility
        var _BulletNavigatorOptions = _Options.$BulletNavigatorOptions;
        var _ArrowNavigatorOptions = _Options.$ArrowNavigatorOptions;
        var _ThumbnailNavigatorOptions = _Options.$ThumbnailNavigatorOptions;

        $JssorDebug$.$Execute(function () {
            if (_SlideshowOptions && !_SlideshowOptions.$Class) {
                $JssorDebug$.$Fail("Option $SlideshowOptions error, class not specified.");
            }
        });

        $JssorDebug$.$Execute(function () {
            if (_Options.$CaptionSliderOptions && !_Options.$CaptionSliderOptions.$Class) {
                $JssorDebug$.$Fail("Option $CaptionSliderOptions error, class not specified.");
            }
        });

        $JssorDebug$.$Execute(function () {
            if (_BulletNavigatorOptions && !_BulletNavigatorOptions.$Class) {
                $JssorDebug$.$Fail("Option $BulletNavigatorOptions error, class not specified.");
            }
        });

        $JssorDebug$.$Execute(function () {
            if (_ArrowNavigatorOptions && !_ArrowNavigatorOptions.$Class) {
                $JssorDebug$.$Fail("Option $ArrowNavigatorOptions error, class not specified.");
            }
        });

        $JssorDebug$.$Execute(function () {
            if (_ThumbnailNavigatorOptions && !_ThumbnailNavigatorOptions.$Class) {
                $JssorDebug$.$Fail("Option $ThumbnailNavigatorOptions error, class not specified.");
            }
        });

        var _UISearchNoDeep = !_Options.$UISearchMode;
        var _ScaleWrapper;
        var _SlidesContainer = $Jssor$.$FindChild(elmt, "slides", _UISearchNoDeep);
        var _LoadingContainer = $Jssor$.$FindChild(elmt, "loading", _UISearchNoDeep) || $Jssor$.$CreateDiv(document);

        var _BulletNavigatorContainer = $Jssor$.$FindChild(elmt, "navigator", _UISearchNoDeep);

        var _ArrowLeft = $Jssor$.$FindChild(elmt, "arrowleft", _UISearchNoDeep);
        var _ArrowRight = $Jssor$.$FindChild(elmt, "arrowright", _UISearchNoDeep);

        var _ThumbnailNavigatorContainer = $Jssor$.$FindChild(elmt, "thumbnavigator", _UISearchNoDeep);

        $JssorDebug$.$Execute(function () {
            //if (_BulletNavigatorOptions && !_BulletNavigatorContainer) {
            //    throw new Error("$BulletNavigatorOptions specified but bullet navigator container (<div u=\"navigator\" ...) not defined.");
            //}
            if (_BulletNavigatorContainer && !_BulletNavigatorOptions) {
                throw new Error("Bullet navigator container defined but $BulletNavigatorOptions not specified.");
            }

            //if (_ArrowNavigatorOptions) {
            //    if (!_ArrowLeft) {
            //        throw new Error("$ArrowNavigatorOptions specified, but arrowleft (<span u=\"arrowleft\" ...) not defined.");
            //    }

            //    if (!_ArrowRight) {
            //        throw new Error("$ArrowNavigatorOptions specified, but arrowright (<span u=\"arrowright\" ...) not defined.");
            //    }
            //}

            if ((_ArrowLeft || _ArrowRight) && !_ArrowNavigatorOptions) {
                throw new Error("arrowleft or arrowright defined, but $ArrowNavigatorOptions not specified.");
            }

            //if (_ThumbnailNavigatorOptions && !_ThumbnailNavigatorContainer) {
            //    throw new Error("$ThumbnailNavigatorOptions specified, but thumbnail navigator container (<div u=\"thumbnavigator\" ...) not defined.");
            //}

            if (_ThumbnailNavigatorContainer && !_ThumbnailNavigatorOptions) {
                throw new Error("Thumbnail navigator container defined, but $ThumbnailNavigatorOptions not specified.");
            }
        });

        var _SlidesContainerWidth = $Jssor$.$CssWidth(_SlidesContainer);
        var _SlidesContainerHeight = $Jssor$.$CssHeight(_SlidesContainer);

        $JssorDebug$.$Execute(function () {
            if (isNaN(_SlidesContainerWidth))
                $JssorDebug$.$Fail("Width of slides container wrong specification, it should be specified in pixel (like style='width: 600px;').");

            if (_SlidesContainerWidth == undefined)
                $JssorDebug$.$Fail("Width of slides container not specified, it should be specified in pixel (like style='width: 600px;').");

            if (isNaN(_SlidesContainerHeight))
                $JssorDebug$.$Fail("Height of slides container wrong specification, it should be specified in pixel (like style='height: 300px;').");

            if (_SlidesContainerHeight == undefined)
                $JssorDebug$.$Fail("Height of slides container not specified, it should be specified in pixel (like style='height: 300px;').");

            var slidesContainerOverflow = $Jssor$.$CssOverflow(_SlidesContainer);
            var slidesContainerOverflowX = $Jssor$.$Css(_SlidesContainer, "overflowX");
            var slidesContainerOverflowY = $Jssor$.$Css(_SlidesContainer, "overflowY");
            if (slidesContainerOverflow != "hidden" && (slidesContainerOverflowX != "hidden" || slidesContainerOverflowY != "hidden"))
                $JssorDebug$.$Fail("Overflow of slides container wrong specification, it should be specified as 'hidden' (style='overflow:hidden;').");

            //var slidesContainerTop = $Jssor$.$CssTop(_SlidesContainer);
            //var slidesContainerLeft = $Jssor$.$CssLeft(_SlidesContainer);

            //if (isNaN(slidesContainerTop))
            //    $JssorDebug$.$Fail("Top of slides container wrong specification, it should be specified in pixel (like style='top: 0px;').");

            //if (slidesContainerTop == undefined)
            //    $JssorDebug$.$Fail("Top of slides container not specified, it should be specified in pixel (like style='top: 0px;').");

            //if (isNaN(slidesContainerLeft))
            //    $JssorDebug$.$Fail("Left of slides container wrong specification, it should be specified in pixel (like style='left: 0px;').");

            //if (slidesContainerLeft == undefined)
            //    $JssorDebug$.$Fail("Left of slides container not specified, it should be specified in pixel (like style='left: 0px;').");
        });

        $JssorDebug$.$Execute(function () {
            if (!$Jssor$.$IsNumeric(_Options.$DisplayPieces))
                $JssorDebug$.$Fail("Option $DisplayPieces error, it should be a numeric value and greater than or equal to 1.");

            if (_Options.$DisplayPieces < 1)
                $JssorDebug$.$Fail("Option $DisplayPieces error, it should be greater than or equal to 1.");

            if (_Options.$DisplayPieces > 1 && _Options.$DragOrientation && _Options.$DragOrientation != _PlayOrientation)
                $JssorDebug$.$Fail("Option $DragOrientation error, it should be 0 or the same of $PlayOrientation when $DisplayPieces is greater than 1.");

            if (!$Jssor$.$IsNumeric(_Options.$ParkingPosition))
                $JssorDebug$.$Fail("Option $ParkingPosition error, it should be a numeric value.");

            if (_Options.$ParkingPosition && _Options.$DragOrientation && _Options.$DragOrientation != _PlayOrientation)
                $JssorDebug$.$Fail("Option $DragOrientation error, it should be 0 or the same of $PlayOrientation when $ParkingPosition is not equal to 0.");
        });

        var _StyleDef;

        var _SlideElmts = [];

        {
            var slideElmts = $Jssor$.$Children(_SlidesContainer);
            $Jssor$.$Each(slideElmts, function (slideElmt) {
                if (slideElmt.tagName == "DIV" && !$Jssor$.$AttributeEx(slideElmt, "u")) {
                    _SlideElmts.push(slideElmt);
                }
            });
        }

        $JssorDebug$.$Execute(function () {
            if (_SlideElmts.length < 1) {
                $JssorDebug$.$Error("Slides html code definition error, there must be at least 1 slide to initialize a slider.");
            }
        });

        var _SlideItemCreatedCount = 0; //for debug only
        var _SlideItemReleasedCount = 0;    //for debug only

        var _PreviousSlideIndex;
        var _CurrentSlideIndex = -1;
        var _TempSlideIndex;
        var _PrevSlideItem;
        var _CurrentSlideItem;
        var _SlideCount = _SlideElmts.length;

        var _SlideWidth = _Options.$SlideWidth || _SlidesContainerWidth;
        var _SlideHeight = _Options.$SlideHeight || _SlidesContainerHeight;

        var _SlideSpacing = _Options.$SlideSpacing;
        var _StepLengthX = _SlideWidth + _SlideSpacing;
        var _StepLengthY = _SlideHeight + _SlideSpacing;
        var _StepLength = (_PlayOrientation & 1) ? _StepLengthX : _StepLengthY;
        var _DisplayPieces = Math.min(_Options.$DisplayPieces, _SlideCount);

        var _SlideshowPanel;
        var _CurrentBoardIndex = 0;
        var _DragOrientation;
        var _DragOrientationRegistered;
        var _DragInvalid;

        var _HandleTouchEventOnly;
        var _IsTouchDevice;

        var _Navigators = [];
        var _BulletNavigator;
        var _ArrowNavigator;
        var _ThumbnailNavigator;

        var _ShowLink;

        var _Frozen;
        var _AutoPlay;
        var _AutoPlaySteps = _Options.$AutoPlaySteps;
        var _HoverToPause = _Options.$PauseOnHover;
        var _AutoPlayInterval = _Options.$AutoPlayInterval;
        var _SlideDuration = _Options.$SlideDuration;

        var _SlideshowRunnerClass;
        var _TransitionsOrder;

        var _SlideshowEnabled;
        var _ParkingPosition;
        var _CarouselEnabled = _DisplayPieces < _SlideCount;
        var _Loop = _CarouselEnabled ? _Options.$Loop : 0;

        var _DragEnabled;
        var _LastDragSucceded;

        var _NotOnHover = 1;   //0 Hovering, 1 Not hovering

        //Variable Definition
        var _IsSliding;
        var _IsDragging;
        var _LoadingTicket;

        //The X position of mouse/touch when a drag start
        var _DragStartMouseX = 0;
        //The Y position of mouse/touch when a drag start
        var _DragStartMouseY = 0;
        var _DragOffsetTotal;
        var _DragOffsetLastTime;
        var _DragIndexAdjust;

        var _Carousel;
        var _Conveyor;
        var _Slideshow;
        var _CarouselPlayer;
        var _SlideContainer = new SlideContainer();
        var _ScaleRatio;

        //$JssorSlider$ Constructor
        {
            _AutoPlay = _Options.$AutoPlay;
            _SelfSlider.$Options = options;

            AdjustSlidesContainerSize();

            elmt["jssor-slider"] = true;

            //_SlideshowPanel = CreatePanel();
            //$Jssor$.$CssZIndex(elmt, $Jssor$.$CssZIndex(elmt));
            //$Jssor$.$CssLeft(_SlideshowPanel, $Jssor$.$CssLeft(_SlidesContainer));
            //$Jssor$.$CssZIndex(_SlidesContainer, $Jssor$.$CssZIndex(_SlidesContainer));
            //$Jssor$.$CssTop(_SlideshowPanel, $Jssor$.$CssTop(_SlidesContainer));
            $Jssor$.$CssZIndex(_SlidesContainer, $Jssor$.$CssZIndex(_SlidesContainer) || 0);
            $Jssor$.$CssPosition(_SlidesContainer, "absolute");
            _SlideshowPanel = $Jssor$.$CloneNode(_SlidesContainer);
            $Jssor$.$InsertBefore($Jssor$.$ParentNode(_SlidesContainer), _SlideshowPanel, _SlidesContainer);

            if (_SlideshowOptions) {
                _ShowLink = _SlideshowOptions.$ShowLink;
                _SlideshowRunnerClass = _SlideshowOptions.$Class;

                $JssorDebug$.$Execute(function () {
                    if (!_SlideshowOptions.$Transitions || !_SlideshowOptions.$Transitions.length) {
                        $JssorDebug$.$Error("Invalid '$SlideshowOptions', no '$Transitions' specified.");
                    }
                });

                $Jssor$.$TranslateTransitions(_SlideshowOptions.$Transitions); //for old transition compatibility

                _SlideshowEnabled = _DisplayPieces == 1 && _SlideCount > 1 && _SlideshowRunnerClass && (!$Jssor$.$IsBrowserIE() || $Jssor$.$BrowserVersion() >= 8);
            }

            _ParkingPosition = (_SlideshowEnabled || _DisplayPieces >= _SlideCount || !(_Loop & 1)) ? 0 : _Options.$ParkingPosition;

            _DragEnabled = ((_DisplayPieces > 1 || _ParkingPosition) ? _PlayOrientation : -1) & _Options.$DragOrientation;

            //SlideBoard
            var _SlideboardElmt = _SlidesContainer;
            var _SlideItems = [];

            var _SlideshowRunner;
            var _LinkContainer;

            var _DownEvent = "mousedown";
            var _MoveEvent = "mousemove";
            var _UpEvent = "mouseup";
            var _CancelEvent;

            var _LastTimeMoveByDrag;
            var _Position_OnFreeze;
            var _CarouselPlaying_OnFreeze;
            var _PlayToPosition_OnFreeze;
            var _PositionToGoByDrag;

            //SlideBoard Constructor
            {
                var msPrefix;
                if (window.navigator.pointerEnabled || (msPrefix = window.navigator.msPointerEnabled)) {
                    _IsTouchDevice = true;

                    _DownEvent = msPrefix ? "MSPointerDown" : "pointerdown";
                    _MoveEvent = msPrefix ? "MSPointerMove" : "pointermove";
                    _UpEvent = msPrefix ? "MSPointerUp" : "pointerup";
                    _CancelEvent = msPrefix ? "MSPointerCancel" : "pointercancel";

                    if (_DragEnabled) {
                        var touchAction = "auto";
                        if (_DragEnabled == 2) {
                            touchAction = "pan-x";
                        }
                        else if (_DragEnabled) {
                            touchAction = "pan-y";
                        }

                        $Jssor$.$Css(_SlideboardElmt, msPrefix ? "msTouchAction" : "touchAction", touchAction);
                    }
                }
                else if ("ontouchstart" in window || "createTouch" in document) {
                    _HandleTouchEventOnly = true;
                    _IsTouchDevice = true;

                    _DownEvent = "touchstart";
                    _MoveEvent = "touchmove";
                    _UpEvent = "touchend";
                    _CancelEvent = "touchcancel";
                }

                _Slideshow = new Slideshow();

                if (_SlideshowEnabled)
                    _SlideshowRunner = new _SlideshowRunnerClass(_SlideContainer, _SlideWidth, _SlideHeight, _SlideshowOptions, _HandleTouchEventOnly);

                $Jssor$.$AppendChild(_SlideshowPanel, _Slideshow.$Wrapper);
                $Jssor$.$CssOverflow(_SlidesContainer, "hidden");

                //link container
                {
                    _LinkContainer = CreatePanel();
                    $Jssor$.$Css(_LinkContainer, "backgroundColor", "#000");
                    $Jssor$.$CssOpacity(_LinkContainer, 0);
                    $Jssor$.$InsertBefore(_SlideboardElmt, _LinkContainer, _SlideboardElmt.firstChild);
                }

                for (var i = 0; i < _SlideElmts.length; i++) {
                    var slideElmt = _SlideElmts[i];
                    var slideItem = new SlideItem(slideElmt, i);
                    _SlideItems.push(slideItem);
                }

                $Jssor$.$HideElement(_LoadingContainer);

                $JssorDebug$.$Execute(function () {
                    $Jssor$.$Attribute(_LoadingContainer, "debug-id", "loading-container");
                });

                _Carousel = new Carousel()
                _CarouselPlayer = new CarouselPlayer(_Carousel, _Slideshow);

                $JssorDebug$.$Execute(function () {
                    $Jssor$.$Attribute(_SlideboardElmt, "debug-id", "slide-board");
                });

                if (_DragEnabled) {
                    $Jssor$.$AddEvent(_SlidesContainer, _DownEvent, OnMouseDown);
                    $Jssor$.$AddEvent(document, _UpEvent, OnDragEnd);
                    _CancelEvent && $Jssor$.$AddEvent(document, _CancelEvent, OnDragEnd);
                }
            }
            //SlideBoard

            _HoverToPause &= (_IsTouchDevice ? 10 : 5);

            //Bullet Navigator
            if (_BulletNavigatorContainer && _BulletNavigatorOptions) {
                _BulletNavigator = new _BulletNavigatorOptions.$Class(_BulletNavigatorContainer, _BulletNavigatorOptions, OriginalWidth(), OriginalHeight());
                _Navigators.push(_BulletNavigator);
            }

            //Arrow Navigator
            if (_ArrowNavigatorOptions && _ArrowLeft && _ArrowRight) {
                _ArrowNavigator = new _ArrowNavigatorOptions.$Class(_ArrowLeft, _ArrowRight, _ArrowNavigatorOptions, OriginalWidth(), OriginalHeight());
                _Navigators.push(_ArrowNavigator);
            }

            //Thumbnail Navigator
            if (_ThumbnailNavigatorContainer && _ThumbnailNavigatorOptions) {
                _ThumbnailNavigatorOptions.$StartIndex = _Options.$StartIndex;
                _ThumbnailNavigator = new _ThumbnailNavigatorOptions.$Class(_ThumbnailNavigatorContainer, _ThumbnailNavigatorOptions);
                _Navigators.push(_ThumbnailNavigator);
            }

            $Jssor$.$Each(_Navigators, function (navigator) {
                navigator.$Reset(_SlideCount, _SlideItems, _LoadingContainer);
                navigator.$On($JssorNavigatorEvents$.$NAVIGATIONREQUEST, NavigationClickHandler);
            });

            Scale(OriginalWidth());

            $Jssor$.$AddEvent(elmt, "mouseout", $Jssor$.$MouseOverOutFilter(MainContainerMouseLeaveEventHandler, elmt));
            $Jssor$.$AddEvent(elmt, "mouseover", $Jssor$.$MouseOverOutFilter(MainContainerMouseEnterEventHandler, elmt));

            ShowNavigators();

            //Keyboard Navigation
            if (_Options.$ArrowKeyNavigation) {
                $Jssor$.$AddEvent(document, "keydown", function (e) {
                    if (e.keyCode == $JssorKeyCode$.$LEFT) {
                        //Arrow Left
                        PlayToOffset(-1);
                    }
                    else if (e.keyCode == $JssorKeyCode$.$RIGHT) {
                        //Arrow Right
                        PlayToOffset(1);
                    }
                });
            }

            var startPosition = _Options.$StartIndex;
            if (!(_Loop & 1)) {
                startPosition = Math.max(0, Math.min(startPosition, _SlideCount - _DisplayPieces));
            }
            _CarouselPlayer.$PlayCarousel(startPosition, startPosition, 0);
        }
    }
    //Jssor Slider

    //JssorSlider.$ASSEMBLY_BOTTOM_LEFT = ASSEMBLY_BOTTOM_LEFT;
    //JssorSlider.$ASSEMBLY_BOTTOM_RIGHT = ASSEMBLY_BOTTOM_RIGHT;
    //JssorSlider.$ASSEMBLY_TOP_LEFT = ASSEMBLY_TOP_LEFT;
    //JssorSlider.$ASSEMBLY_TOP_RIGHT = ASSEMBLY_TOP_RIGHT;
    //JssorSlider.$ASSEMBLY_LEFT_TOP = ASSEMBLY_LEFT_TOP;
    //JssorSlider.$ASSEMBLY_LEFT_BOTTOM = ASSEMBLY_LEFT_BOTTOM;
    //JssorSlider.$ASSEMBLY_RIGHT_TOP = ASSEMBLY_RIGHT_TOP;
    //JssorSlider.$ASSEMBLY_RIGHT_BOTTOM = ASSEMBLY_RIGHT_BOTTOM;

    JssorSlider.$EVT_CLICK = 21;
    JssorSlider.$EVT_DRAG_START = 22;
    JssorSlider.$EVT_DRAG_END = 23;
    JssorSlider.$EVT_SWIPE_START = 24;
    JssorSlider.$EVT_SWIPE_END = 25;

    JssorSlider.$EVT_LOAD_START = 26;
    JssorSlider.$EVT_LOAD_END = 27;
    JssorSlider.$EVT_FREEZE = 28;

    JssorSlider.$EVT_POSITION_CHANGE = 202;
    JssorSlider.$EVT_PARK = 203;

    JssorSlider.$EVT_SLIDESHOW_START = 206;
    JssorSlider.$EVT_SLIDESHOW_END = 207;

    JssorSlider.$EVT_PROGRESS_CHANGE = 208;
    JssorSlider.$EVT_STATE_CHANGE = 209;
    JssorSlider.$EVT_ROLLBACK_START = 210;
    JssorSlider.$EVT_ROLLBACK_END = 211;

    window.$JssorSlider$ = $JssorSlider$ = JssorSlider;

    //(function ($) {
    //    jQuery.fn.jssorSlider = function (options) {
    //        return this.each(function () {
    //            return $(this).data('jssorSlider') || $(this).data('jssorSlider', new JssorSlider(this, options));
    //        });
    //    };
    //})(jQuery);

    //window.jQuery && (jQuery.fn.jssorSlider = function (options) {
    //    return this.each(function () {
    //        return jQuery(this).data('jssorSlider') || jQuery(this).data('jssorSlider', new JssorSlider(this, options));
    //    });
    //});
};

//$JssorBulletNavigator$
var $JssorNavigatorEvents$ = {
    $NAVIGATIONREQUEST: 1,
    $INDEXCHANGE: 2,
    $RESET: 3
};

var $JssorBulletNavigator$ = window.$JssorBulletNavigator$ = function (elmt, options, containerWidth, containerHeight) {
    var self = this;
    $JssorObject$.call(self);

    elmt = $Jssor$.$GetElement(elmt);

    var _Count;
    var _Length;
    var _Width;
    var _Height;
    var _CurrentIndex;
    var _CurrentInnerIndex = 0;
    var _Options;
    var _Steps;
    var _Lanes;
    var _SpacingX;
    var _SpacingY;
    var _Orientation;
    var _ItemPrototype;
    var _PrototypeWidth;
    var _PrototypeHeight;

    var _ButtonElements = [];
    var _Buttons = [];

    function Highlight(index) {
        if (index != -1)
            _Buttons[index].$Activate(index == _CurrentInnerIndex);
    }

    function OnNavigationRequest(index) {
        self.$TriggerEvent($JssorNavigatorEvents$.$NAVIGATIONREQUEST, index * _Steps);
    }

    self.$Elmt = elmt;
    self.$GetCurrentIndex = function () {
        return _CurrentIndex;
    };

    self.$SetCurrentIndex = function (index) {
        if (index != _CurrentIndex) {
            var lastInnerIndex = _CurrentInnerIndex;
            var innerIndex = Math.floor(index / _Steps);
            _CurrentInnerIndex = innerIndex;
            _CurrentIndex = index;

            Highlight(lastInnerIndex);
            Highlight(innerIndex);

            //self.$TriggerEvent($JssorNavigatorEvents$.$INDEXCHANGE, index);
        }
    };

    self.$Show = function (hide) {
        $Jssor$.$ShowElement(elmt, hide);
    };

    var _Located;
    self.$Relocate = function (containerWidth, containerHeight) {
        if (!_Located || _Options.$Scale == false) {
            if (_Options.$AutoCenter & 1) {
                $Jssor$.$CssLeft(elmt, (containerWidth - _Width) / 2);
            }
            if (_Options.$AutoCenter & 2) {
                $Jssor$.$CssTop(elmt, (containerHeight - _Height) / 2);
            }

            _Located = true;
        }
    };

    var _Initialized;
    self.$Reset = function (length) {
        if (!_Initialized) {
            _Length = length;
            _Count = Math.ceil(length / _Steps);
            _CurrentInnerIndex = 0;

            var itemOffsetX = _PrototypeWidth + _SpacingX;
            var itemOffsetY = _PrototypeHeight + _SpacingY;

            var maxIndex = Math.ceil(_Count / _Lanes) - 1;

            _Width = _PrototypeWidth + itemOffsetX * (!_Orientation ? maxIndex : _Lanes - 1);
            _Height = _PrototypeHeight + itemOffsetY * (_Orientation ? maxIndex : _Lanes - 1);

            $Jssor$.$CssWidth(elmt, _Width);
            $Jssor$.$CssHeight(elmt, _Height);

            for (var buttonIndex = 0; buttonIndex < _Count; buttonIndex++) {

                var numberDiv = $Jssor$.$CreateSpan();
                $Jssor$.$InnerText(numberDiv, buttonIndex + 1);

                var div = $Jssor$.$BuildElement(_ItemPrototype, "numbertemplate", numberDiv, true);
                $Jssor$.$CssPosition(div, "absolute");

                var columnIndex = buttonIndex % (maxIndex + 1);
                $Jssor$.$CssLeft(div, !_Orientation ? itemOffsetX * columnIndex : buttonIndex % _Lanes * itemOffsetX);
                $Jssor$.$CssTop(div, _Orientation ? itemOffsetY * columnIndex : Math.floor(buttonIndex / (maxIndex + 1)) * itemOffsetY);

                $Jssor$.$AppendChild(elmt, div);
                _ButtonElements[buttonIndex] = div;

                if (_Options.$ActionMode & 1)
                    $Jssor$.$AddEvent(div, "click", $Jssor$.$CreateCallback(null, OnNavigationRequest, buttonIndex));

                if (_Options.$ActionMode & 2)
                    $Jssor$.$AddEvent(div, "mouseover", $Jssor$.$MouseOverOutFilter($Jssor$.$CreateCallback(null, OnNavigationRequest, buttonIndex), div));

                _Buttons[buttonIndex] = $Jssor$.$Buttonize(div);
            }

            //self.$TriggerEvent($JssorNavigatorEvents$.$RESET);
            _Initialized = true;
        }
    };

    //JssorBulletNavigator Constructor
    {
        self.$Options = _Options = $Jssor$.$Extend({
            $SpacingX: 0,
            $SpacingY: 0,
            $Orientation: 1,
            $ActionMode: 1
        }, options);

        //Sodo statement for development time intellisence only
        $JssorDebug$.$Execute(function () {
            _Options = $Jssor$.$Extend({
                $Steps: undefined,
                $Lanes: undefined
            }, _Options);
        });

        _ItemPrototype = $Jssor$.$FindChild(elmt, "prototype");

        $JssorDebug$.$Execute(function () {
            if (!_ItemPrototype)
                $JssorDebug$.$Fail("Navigator item prototype not defined.");

            if (isNaN($Jssor$.$CssWidth(_ItemPrototype))) {
                $JssorDebug$.$Fail("Width of 'navigator item prototype' not specified.");
            }

            if (isNaN($Jssor$.$CssHeight(_ItemPrototype))) {
                $JssorDebug$.$Fail("Height of 'navigator item prototype' not specified.");
            }
        });

        _PrototypeWidth = $Jssor$.$CssWidth(_ItemPrototype);
        _PrototypeHeight = $Jssor$.$CssHeight(_ItemPrototype);

        $Jssor$.$RemoveChild(elmt, _ItemPrototype);

        _Steps = _Options.$Steps || 1;
        _Lanes = _Options.$Lanes || 1;
        _SpacingX = _Options.$SpacingX;
        _SpacingY = _Options.$SpacingY;
        _Orientation = _Options.$Orientation - 1;
    }
};

var $JssorArrowNavigator$ = window.$JssorArrowNavigator$ = function (arrowLeft, arrowRight, options, containerWidth, containerHeight) {
    var self = this;
    $JssorObject$.call(self);

    $JssorDebug$.$Execute(function () {

        if (!arrowLeft)
            $JssorDebug$.$Fail("Option '$ArrowNavigatorOptions' spepcified, but UI 'arrowleft' not defined. Define 'arrowleft' to enable direct navigation, or remove option '$ArrowNavigatorOptions' to disable direct navigation.");

        if (!arrowRight)
            $JssorDebug$.$Fail("Option '$ArrowNavigatorOptions' spepcified, but UI 'arrowright' not defined. Define 'arrowright' to enable direct navigation, or remove option '$ArrowNavigatorOptions' to disable direct navigation.");

        if (isNaN($Jssor$.$CssWidth(arrowLeft))) {
            $JssorDebug$.$Fail("Width of 'arrow left' not specified.");
        }

        if (isNaN($Jssor$.$CssWidth(arrowRight))) {
            $JssorDebug$.$Fail("Width of 'arrow right' not specified.");
        }

        if (isNaN($Jssor$.$CssHeight(arrowLeft))) {
            $JssorDebug$.$Fail("Height of 'arrow left' not specified.");
        }

        if (isNaN($Jssor$.$CssHeight(arrowRight))) {
            $JssorDebug$.$Fail("Height of 'arrow right' not specified.");
        }
    });

    var _Length;
    var _CurrentIndex;
    var _Options;
    var _Steps;
    var _ArrowWidth = $Jssor$.$CssWidth(arrowLeft);
    var _ArrowHeight = $Jssor$.$CssHeight(arrowLeft);

    function OnNavigationRequest(steps) {
        self.$TriggerEvent($JssorNavigatorEvents$.$NAVIGATIONREQUEST, steps, true);
    }

    self.$GetCurrentIndex = function () {
        return _CurrentIndex;
    };

    self.$SetCurrentIndex = function (index, virtualIndex, temp) {
        if (temp) {
            _CurrentIndex = virtualIndex;
        }
        else {
            _CurrentIndex = index;
        }
        //self.$TriggerEvent($JssorNavigatorEvents$.$INDEXCHANGE, index);
    };

    self.$Show = function (hide) {
        $Jssor$.$ShowElement(arrowLeft, hide);
        $Jssor$.$ShowElement(arrowRight, hide);
    };

    var _Located;
    self.$Relocate = function (conainerWidth, containerHeight) {
        if (!_Located || _Options.$Scale == false) {

            if (_Options.$AutoCenter & 1) {
                $Jssor$.$CssLeft(arrowLeft, (containerWidth - _ArrowWidth) / 2);
                $Jssor$.$CssLeft(arrowRight, (containerWidth - _ArrowWidth) / 2);
            }

            if (_Options.$AutoCenter & 2) {
                $Jssor$.$CssTop(arrowLeft, (containerHeight - _ArrowHeight) / 2);
                $Jssor$.$CssTop(arrowRight, (containerHeight - _ArrowHeight) / 2);
            }

            _Located = true;
        }
    };

    var _Initialized;
    self.$Reset = function (length) {
        _Length = length;
        _CurrentIndex = 0;

        if (!_Initialized) {

            $Jssor$.$AddEvent(arrowLeft, "click", $Jssor$.$CreateCallback(null, OnNavigationRequest, -_Steps));
            $Jssor$.$AddEvent(arrowRight, "click", $Jssor$.$CreateCallback(null, OnNavigationRequest, _Steps));

            $Jssor$.$Buttonize(arrowLeft);
            $Jssor$.$Buttonize(arrowRight);

            _Initialized = true;
        }

        //self.$TriggerEvent($JssorNavigatorEvents$.$RESET);
    };

    //JssorArrowNavigator Constructor
    {
        self.$Options = _Options = $Jssor$.$Extend({
            $Steps: 1
        }, options);

        _Steps = _Options.$Steps;
    }
};

//$JssorThumbnailNavigator$
var $JssorThumbnailNavigator$ = window.$JssorThumbnailNavigator$ = function (elmt, options) {
    var _Self = this;
    var _Length;
    var _Count;
    var _CurrentIndex;
    var _Options;
    var _NavigationItems = [];

    var _Width;
    var _Height;
    var _Lanes;
    var _SpacingX;
    var _SpacingY;
    var _PrototypeWidth;
    var _PrototypeHeight;
    var _DisplayPieces;

    var _Slider;
    var _CurrentMouseOverIndex = -1;

    var _SlidesContainer;
    var _ThumbnailPrototype;

    $JssorObject$.call(_Self);
    elmt = $Jssor$.$GetElement(elmt);

    function NavigationItem(item, index) {
        var self = this;
        var _Wrapper;
        var _Button;
        var _Thumbnail;

        function Highlight(mouseStatus) {
            _Button.$Activate(_CurrentIndex == index);
        }

        function OnNavigationRequest(event) {
            if (!_Slider.$LastDragSucceded()) {
                var tail = _Lanes - index % _Lanes;
                var slideVirtualIndex = _Slider.$GetVirtualIndex((index + tail) / _Lanes - 1);
                var itemVirtualIndex = slideVirtualIndex * _Lanes + _Lanes - tail;
                _Self.$TriggerEvent($JssorNavigatorEvents$.$NAVIGATIONREQUEST, itemVirtualIndex);
            }

            //$JssorDebug$.$Log("navigation request");
        }

        $JssorDebug$.$Execute(function () {
            self.$Wrapper = undefined;
        });

        self.$Index = index;

        self.$Highlight = Highlight;

        //NavigationItem Constructor
        {
            _Thumbnail = item.$Thumb || item.$Image || $Jssor$.$CreateDiv();
            self.$Wrapper = _Wrapper = $Jssor$.$BuildElement(_ThumbnailPrototype, "thumbnailtemplate", _Thumbnail, true);

            _Button = $Jssor$.$Buttonize(_Wrapper);
            if (_Options.$ActionMode & 1)
                $Jssor$.$AddEvent(_Wrapper, "click", OnNavigationRequest);
            if (_Options.$ActionMode & 2)
                $Jssor$.$AddEvent(_Wrapper, "mouseover", $Jssor$.$MouseOverOutFilter(OnNavigationRequest, _Wrapper));
        }
    }

    _Self.$GetCurrentIndex = function () {
        return _CurrentIndex;
    };

    _Self.$SetCurrentIndex = function (index, virtualIndex, temp) {
        var oldIndex = _CurrentIndex;
        _CurrentIndex = index;
        if (oldIndex != -1)
            _NavigationItems[oldIndex].$Highlight();
        _NavigationItems[index].$Highlight();

        if (!temp) {
            _Slider.$PlayTo(_Slider.$GetVirtualIndex(Math.floor(virtualIndex / _Lanes)));
        }
    };

    _Self.$Show = function (hide) {
        $Jssor$.$ShowElement(elmt, hide);
    };

    _Self.$Relocate = $Jssor$.$EmptyFunction;

    var _Initialized;
    _Self.$Reset = function (length, items, loadingContainer) {
        if (!_Initialized) {
            _Length = length;
            _Count = Math.ceil(_Length / _Lanes);
            _CurrentIndex = -1;
            _DisplayPieces = Math.min(_DisplayPieces, items.length);

            var horizontal = _Options.$Orientation & 1;

            var slideWidth = _PrototypeWidth + (_PrototypeWidth + _SpacingX) * (_Lanes - 1) * (1 - horizontal);
            var slideHeight = _PrototypeHeight + (_PrototypeHeight + _SpacingY) * (_Lanes - 1) * horizontal;

            var slidesContainerWidth = slideWidth + (slideWidth + _SpacingX) * (_DisplayPieces - 1) * horizontal;
            var slidesContainerHeight = slideHeight + (slideHeight + _SpacingY) * (_DisplayPieces - 1) * (1 - horizontal);

            $Jssor$.$CssPosition(_SlidesContainer, "absolute");
            $Jssor$.$CssOverflow(_SlidesContainer, "hidden");
            if (_Options.$AutoCenter & 1) {
                $Jssor$.$CssLeft(_SlidesContainer, (_Width - slidesContainerWidth) / 2);
            }
            if (_Options.$AutoCenter & 2) {
                $Jssor$.$CssTop(_SlidesContainer, (_Height - slidesContainerHeight) / 2);
            }
            //$JssorDebug$.$Execute(function () {
            //    if (!_Options.$AutoCenter) {
            //        var slidesContainerTop = $Jssor$.$CssTop(_SlidesContainer);
            //        var slidesContainerLeft = $Jssor$.$CssLeft(_SlidesContainer);

            //        if (isNaN(slidesContainerTop)) {
            //            $JssorDebug$.$Fail("Position 'top' wrong specification of thumbnail navigator slides container (<div u=\"thumbnavigator\">...<div u=\"slides\">), \r\nwhen option $ThumbnailNavigatorOptions.$AutoCenter set to 0, it should be specified in pixel (like <div u=\"slides\" style=\"top: 0px;\">)");
            //        }

            //        if (isNaN(slidesContainerLeft)) {
            //            $JssorDebug$.$Fail("Position 'left' wrong specification of thumbnail navigator slides container (<div u=\"thumbnavigator\">...<div u=\"slides\">), \r\nwhen option $ThumbnailNavigatorOptions.$AutoCenter set to 0, it should be specified in pixel (like <div u=\"slides\" style=\"left: 0px;\">)");
            //        }
            //    }
            //});
            $Jssor$.$CssWidth(_SlidesContainer, slidesContainerWidth);
            $Jssor$.$CssHeight(_SlidesContainer, slidesContainerHeight);

            var slideItemElmts = [];
            $Jssor$.$Each(items, function (item, index) {
                var navigationItem = new NavigationItem(item, index);
                var navigationItemWrapper = navigationItem.$Wrapper;

                var columnIndex = Math.floor(index / _Lanes);
                var laneIndex = index % _Lanes;

                $Jssor$.$CssLeft(navigationItemWrapper, (_PrototypeWidth + _SpacingX) * laneIndex * (1 - horizontal));
                $Jssor$.$CssTop(navigationItemWrapper, (_PrototypeHeight + _SpacingY) * laneIndex * horizontal);

                if (!slideItemElmts[columnIndex]) {
                    slideItemElmts[columnIndex] = $Jssor$.$CreateDiv();
                    $Jssor$.$AppendChild(_SlidesContainer, slideItemElmts[columnIndex]);
                }

                $Jssor$.$AppendChild(slideItemElmts[columnIndex], navigationItemWrapper);

                _NavigationItems.push(navigationItem);
            });

            var thumbnailSliderOptions = $Jssor$.$Extend({
                $HWA: false,
                $AutoPlay: false,
                $NaviQuitDrag: false,
                $SlideWidth: slideWidth,
                $SlideHeight: slideHeight,
                $SlideSpacing: _SpacingX * horizontal + _SpacingY * (1 - horizontal),
                $MinDragOffsetToSlide: 12,
                $SlideDuration: 200,
                $PauseOnHover: 1,
                $PlayOrientation: _Options.$Orientation,
                $DragOrientation: _Options.$DisableDrag ? 0 : _Options.$Orientation
            }, _Options);

            _Slider = new $JssorSlider$(elmt, thumbnailSliderOptions);

            _Initialized = true;
        }

        //_Self.$TriggerEvent($JssorNavigatorEvents$.$RESET);
    };

    //JssorThumbnailNavigator Constructor
    {
        _Self.$Options = _Options = $Jssor$.$Extend({
            $SpacingX: 3,
            $SpacingY: 3,
            $DisplayPieces: 1,
            $Orientation: 1,
            $AutoCenter: 3,
            $ActionMode: 1
        }, options);

        //Sodo statement for development time intellisence only
        $JssorDebug$.$Execute(function () {
            _Options = $Jssor$.$Extend({
                $Lanes: undefined,
                $Width: undefined,
                $Height: undefined
            }, _Options);
        });

        _Width = $Jssor$.$CssWidth(elmt);
        _Height = $Jssor$.$CssHeight(elmt);

        $JssorDebug$.$Execute(function () {
            if (!_Width)
                $JssorDebug$.$Fail("width of 'thumbnavigator' container not specified.");
            if (!_Height)
                $JssorDebug$.$Fail("height of 'thumbnavigator' container not specified.");
        });

        _SlidesContainer = $Jssor$.$FindChild(elmt, "slides", true);
        _ThumbnailPrototype = $Jssor$.$FindChild(_SlidesContainer, "prototype");

        $JssorDebug$.$Execute(function () {
            if (!_ThumbnailPrototype)
                $JssorDebug$.$Fail("prototype of 'thumbnavigator' not defined.");
        });

        _PrototypeWidth = $Jssor$.$CssWidth(_ThumbnailPrototype);
        _PrototypeHeight = $Jssor$.$CssHeight(_ThumbnailPrototype);

        $Jssor$.$RemoveChild(_SlidesContainer, _ThumbnailPrototype);

        _Lanes = _Options.$Lanes || 1;
        _SpacingX = _Options.$SpacingX;
        _SpacingY = _Options.$SpacingY;
        _DisplayPieces = _Options.$DisplayPieces;
    }
};

//$JssorCaptionSliderBase$
function $JssorCaptionSliderBase$() {
    $JssorAnimator$.call(this, 0, 0);
    this.$Revert = $Jssor$.$EmptyFunction;
}

var $JssorCaptionSlider$ = window.$JssorCaptionSlider$ = function (container, captionSlideOptions, playIn) {
    $JssorDebug$.$Execute(function () {
        if (!captionSlideOptions.$CaptionTransitions) {
            $JssorDebug$.$Error("'$CaptionSliderOptions' option error, '$CaptionSliderOptions.$CaptionTransitions' not specified.");
        }
        //else if (!$Jssor$.$IsArray(captionSlideOptions.$CaptionTransitions)) {
        //    $JssorDebug$.$Error("'$CaptionSliderOptions' option error, '$CaptionSliderOptions.$CaptionTransitions' is not an array.");
        //}
    });

    var _Self = this;
    var _ImmediateOutCaptionHanger;
    var _PlayMode = playIn ? captionSlideOptions.$PlayInMode : captionSlideOptions.$PlayOutMode;

    var _CaptionTransitions = captionSlideOptions.$CaptionTransitions;
    var _CaptionTuningFetcher = { $Transition: "t", $Delay: "d", $Duration: "du", x: "x", y: "y", $Rotate: "r", $Zoom: "z", $Opacity: "f", $BeginTime: "b" };
    var _CaptionTuningTransfer = {
        $Default: function (value, tuningValue) {
            if (!isNaN(tuningValue.$Value))
                value = tuningValue.$Value;
            else
                value *= tuningValue.$Percent;

            return value;
        },
        $Opacity: function (value, tuningValue) {
            return this.$Default(value - 1, tuningValue);
        }
    };
    _CaptionTuningTransfer.$Zoom = _CaptionTuningTransfer.$Opacity;

    $JssorAnimator$.call(_Self, 0, 0);

    function GetCaptionItems(element, level) {

        var itemsToPlay = [];
        var lastTransitionName;
        var namedTransitions = [];
        var namedTransitionOrders = [];

        //$JssorDebug$.$Execute(function () {

        //    var debugInfoElement = $Jssor$.$GetElement("debugInfo");

        //    if (debugInfoElement && playIn) {

        //        var text = $Jssor.$InnerHtml(debugInfoElement) + "<br>";

        //        $Jssor$.$InnerHtml(debugInfoElement, text);
        //    }
        //});

        function FetchRawTransition(captionElmt, index) {
            var rawTransition = {};

            $Jssor$.$Each(_CaptionTuningFetcher, function (fetchAttribute, fetchProperty) {
                var attributeValue = $Jssor$.$AttributeEx(captionElmt, fetchAttribute + (index || ""));
                if (attributeValue) {
                    var propertyValue = {};

                    if (fetchAttribute == "t") {
                        //if (($Jssor$.$IsBrowserChrome() || $Jssor$.$IsBrowserSafari() || $Jssor$.$IsBrowserFireFox()) && attributeValue == "*") {
                        //    attributeValue = Math.floor(Math.random() * captionSlideOptions.$CaptionTransitions.length);
                        //    $Jssor$.$Attribute(captionElmt, fetchAttribute + (index || ""), attributeValue);
                        //}

                        propertyValue.$Value = attributeValue;
                    }
                    else if (attributeValue.indexOf("%") + 1)
                        propertyValue.$Percent = $Jssor$.$ParseFloat(attributeValue) / 100;
                    else
                        propertyValue.$Value = $Jssor$.$ParseFloat(attributeValue);

                    rawTransition[fetchProperty] = propertyValue;
                }
            });

            return rawTransition;
        }

        function GetRandomTransition() {
            return _CaptionTransitions[Math.floor(Math.random() * _CaptionTransitions.length)];
        }

        function EvaluateCaptionTransition(transitionName) {

            var transition;

            if (transitionName == "*") {
                transition = GetRandomTransition();
            }
            else if (transitionName) {

                //indexed transition allowed, just the same as named transition
                var tempTransition = _CaptionTransitions[$Jssor$.$ParseInt(transitionName)] || _CaptionTransitions[transitionName];

                if ($Jssor$.$IsArray(tempTransition)) {
                    if (transitionName != lastTransitionName) {
                        lastTransitionName = transitionName;
                        namedTransitionOrders[transitionName] = 0;

                        namedTransitions[transitionName] = tempTransition[Math.floor(Math.random() * tempTransition.length)];
                    }
                    else {
                        namedTransitionOrders[transitionName]++;
                    }

                    tempTransition = namedTransitions[transitionName];

                    if ($Jssor$.$IsArray(tempTransition)) {
                        tempTransition = tempTransition.length && tempTransition[namedTransitionOrders[transitionName] % tempTransition.length];

                        if ($Jssor$.$IsArray(tempTransition)) {
                            //got transition from array level 3, random for all captions
                            tempTransition = tempTransition[Math.floor(Math.random() * tempTransition.length)];
                        }
                        //else {
                        //    //got transition from array level 2, in sequence for all adjacent captions with same name specified
                        //    transition = tempTransition;
                        //}
                    }
                    //else {
                    //    //got transition from array level 1, random but same for all adjacent captions with same name specified
                    //    transition = tempTransition;
                    //}
                }
                //else {
                //    //got transition directly from a simple transition object
                //    transition = tempTransition;
                //}

                transition = tempTransition;

                if ($Jssor$.$IsString(transition))
                    transition = EvaluateCaptionTransition(transition);
            }

            return transition;
        }

        var captionElmts = $Jssor$.$Children(element);
        $Jssor$.$Each(captionElmts, function (captionElmt, i) {

            var transitionsWithTuning = [];
            transitionsWithTuning.$Elmt = captionElmt;
            var isCaption = $Jssor$.$AttributeEx(captionElmt, "u") == "caption";

            $Jssor$.$Each(playIn ? [0, 3] : [2], function (j, k) {

                if (isCaption) {
                    var transition;
                    var rawTransition;

                    if (j != 2 || !$Jssor$.$AttributeEx(captionElmt, "t3")) {
                        rawTransition = FetchRawTransition(captionElmt, j);

                        if (j == 2 && !rawTransition.$Transition) {
                            rawTransition.$Delay = rawTransition.$Delay || { $Value: 0 };
                            rawTransition = $Jssor$.$Extend(FetchRawTransition(captionElmt, 0), rawTransition);
                        }
                    }

                    if (rawTransition && rawTransition.$Transition) {

                        transition = EvaluateCaptionTransition(rawTransition.$Transition.$Value);

                        if (transition) {

                            //var transitionWithTuning = $Jssor$.$Extend({ $Delay: 0, $ScaleHorizontal: 1, $ScaleVertical: 1 }, transition);
                            var transitionWithTuning = $Jssor$.$Extend({ $Delay: 0 }, transition);

                            $Jssor$.$Each(rawTransition, function (rawPropertyValue, propertyName) {
                                var tuningPropertyValue = (_CaptionTuningTransfer[propertyName] || _CaptionTuningTransfer.$Default).apply(_CaptionTuningTransfer, [transitionWithTuning[propertyName], rawTransition[propertyName]]);
                                if (!isNaN(tuningPropertyValue))
                                    transitionWithTuning[propertyName] = tuningPropertyValue;
                            });

                            if (!k) {
                                if (rawTransition.$BeginTime)
                                    transitionWithTuning.$BeginTime = rawTransition.$BeginTime.$Value || 0;
                                else if ((_PlayMode) & 2)
                                    transitionWithTuning.$BeginTime = 0;
                            }
                        }
                    }

                    transitionsWithTuning.push(transitionWithTuning);
                }

                if ((level % 2) && !k) {
                    //transitionsWithTuning.$Children = GetCaptionItems(captionElmt, lastTransitionName, [].concat(namedTransitions), [].concat(namedTransitionOrders), level + 1);
                    transitionsWithTuning.$Children = GetCaptionItems(captionElmt, level + 1);
                }
            });

            itemsToPlay.push(transitionsWithTuning);
        });

        return itemsToPlay;
    }

    function CreateAnimator(item, transition, immediateOut) {

        var animatorOptions = {
            $Easing: transition.$Easing,
            $Round: transition.$Round,
            $During: transition.$During,
            $Reverse: playIn && !immediateOut,
            $Optimize: true
        };

        $JssorDebug$.$Execute(function () {
            animatorOptions.$CaptionAnimator = true;
        });

        var captionItem = item;
        var captionParent = $Jssor$.$ParentNode(item);

        var captionItemWidth = $Jssor$.$CssWidth(captionItem);
        var captionItemHeight = $Jssor$.$CssHeight(captionItem);
        var captionParentWidth = $Jssor$.$CssWidth(captionParent);
        var captionParentHeight = $Jssor$.$CssHeight(captionParent);

        var toStyles = {};
        var fromStyles = {};
        var scaleClip = transition.$ScaleClip || 1;

        //Opacity
        if (transition.$Opacity) {
            toStyles.$Opacity = 2 - transition.$Opacity;
        }

        animatorOptions.$OriginalWidth = captionItemWidth;
        animatorOptions.$OriginalHeight = captionItemHeight;

        //Transform
        if (transition.$Zoom || transition.$Rotate) {
            toStyles.$Zoom = transition.$Zoom ? transition.$Zoom - 1 : 1;

            if ($Jssor$.$IsBrowserIe9Earlier() || $Jssor$.$IsBrowserOpera())
                toStyles.$Zoom = Math.min(toStyles.$Zoom, 2);

            fromStyles.$Zoom = 1;

            var rotate = transition.$Rotate || 0;

            toStyles.$Rotate = rotate * 360;
            fromStyles.$Rotate = 0;
        }
            //Clip
        else if (transition.$Clip) {
            var fromStyleClip = { $Top: 0, $Right: captionItemWidth, $Bottom: captionItemHeight, $Left: 0 };
            var toStyleClip = $Jssor$.$Extend({}, fromStyleClip);

            var blockOffset = toStyleClip.$Offset = {};

            var topBenchmark = transition.$Clip & 4;
            var bottomBenchmark = transition.$Clip & 8;
            var leftBenchmark = transition.$Clip & 1;
            var rightBenchmark = transition.$Clip & 2;

            if (topBenchmark && bottomBenchmark) {
                blockOffset.$Top = captionItemHeight / 2 * scaleClip;
                blockOffset.$Bottom = -blockOffset.$Top;
            }
            else if (topBenchmark)
                blockOffset.$Bottom = -captionItemHeight * scaleClip;
            else if (bottomBenchmark)
                blockOffset.$Top = captionItemHeight * scaleClip;

            if (leftBenchmark && rightBenchmark) {
                blockOffset.$Left = captionItemWidth / 2 * scaleClip;
                blockOffset.$Right = -blockOffset.$Left;
            }
            else if (leftBenchmark)
                blockOffset.$Right = -captionItemWidth * scaleClip;
            else if (rightBenchmark)
                blockOffset.$Left = captionItemWidth * scaleClip;

            animatorOptions.$Move = transition.$Move;
            toStyles.$Clip = toStyleClip;
            fromStyles.$Clip = fromStyleClip;
        }

        //Fly
        {
            var toLeft = 0;
            var toTop = 0;

            if (transition.x)
                toLeft -= captionParentWidth * transition.x;

            if (transition.y)
                toTop -= captionParentHeight * transition.y;

            if (toLeft || toTop || animatorOptions.$Move) {
                toStyles.$Left = toLeft + $Jssor$.$CssLeft(captionItem);
                toStyles.$Top = toTop + $Jssor$.$CssTop(captionItem);
            }
        }

        //duration
        var duration = transition.$Duration;

        fromStyles = $Jssor$.$Extend(fromStyles, $Jssor$.$GetStyles(captionItem, toStyles));

        animatorOptions.$Setter = $Jssor$.$StyleSetterEx();

        return new $JssorAnimator$(transition.$Delay, duration, animatorOptions, captionItem, fromStyles, toStyles);
    }

    function CreateAnimators(streamLineLength, captionItems) {

        $Jssor$.$Each(captionItems, function (captionItem, i) {

            $JssorDebug$.$Execute(function () {
                if (captionItem.length) {
                    var top = $Jssor$.$CssTop(captionItem.$Elmt);
                    var left = $Jssor$.$CssLeft(captionItem.$Elmt);
                    var width = $Jssor$.$CssWidth(captionItem.$Elmt);
                    var height = $Jssor$.$CssHeight(captionItem.$Elmt);

                    var error = null;

                    if (isNaN(top))
                        error = "Style 'top' for caption not specified. Please always specify caption like 'position: absolute; top: ...px; left: ...px; width: ...px; height: ...px;'.";
                    else if (isNaN(left))
                        error = "Style 'left' not specified. Please always specify caption like 'position: absolute; top: ...px; left: ...px; width: ...px; height: ...px;'.";
                    else if (isNaN(width))
                        error = "Style 'width' not specified. Please always specify caption like 'position: absolute; top: ...px; left: ...px; width: ...px; height: ...px;'.";
                    else if (isNaN(height))
                        error = "Style 'height' not specified. Please always specify caption like 'position: absolute; top: ...px; left: ...px; width: ...px; height: ...px;'.";

                    if (error)
                        $JssorDebug$.$Error("Caption " + (i + 1) + " definition error, \r\n" + error + "\r\n" + captionItem.$Elmt.outerHTML);
                }
            });

            var animator;
            var captionElmt = captionItem.$Elmt;
            var transition = captionItem[0];
            var transition3 = captionItem[1];

            if (transition) {

                animator = CreateAnimator(captionElmt, transition);
                streamLineLength = animator.$Locate(transition.$BeginTime == undefined ? streamLineLength : transition.$BeginTime, 1);
            }

            streamLineLength = CreateAnimators(streamLineLength, captionItem.$Children);

            if (transition3) {
                var animator3 = CreateAnimator(captionElmt, transition3, 1);
                animator3.$Locate(streamLineLength, 1);
                _Self.$Combine(animator3);
                _ImmediateOutCaptionHanger.$Combine(animator3);
            }

            if (animator)
                _Self.$Combine(animator);
        });

        return streamLineLength;
    }

    _Self.$Revert = function () {
        _Self.$GoToPosition(_Self.$GetPosition_OuterEnd() * (playIn || 0));
        _ImmediateOutCaptionHanger.$GoToBegin();
    };

    {
        _ImmediateOutCaptionHanger = new $JssorAnimator$(0, 0);


        CreateAnimators(0, _PlayMode ? GetCaptionItems(container, 1) : []);
    }
};

;
// Knockout JavaScript library v3.0.0
// (c) Steven Sanderson - http://knockoutjs.com/
// License: MIT (http://www.opensource.org/licenses/mit-license.php)

(function() {(function(q){var y=this||(0,eval)("this"),w=y.document,K=y.navigator,u=y.jQuery,B=y.JSON;(function(q){"function"===typeof require&&"object"===typeof exports&&"object"===typeof module?q(module.exports||exports):"function"===typeof define&&define.amd?define(["exports"],q):q(y.ko={})})(function(F){function G(a,c){return null===a||typeof a in N?a===c:!1}function H(b,c,d,e){a.d[b]={init:function(b){a.a.f.set(b,L,{});return{controlsDescendantBindings:!0}},update:function(b,h,k,m,f){k=a.a.f.get(b,L);h=a.a.c(h());
m=!d!==!h;var p=!k.ob;if(p||c||m!==k.Db)p&&(k.ob=a.a.Ya(a.e.childNodes(b),!0)),m?(p||a.e.S(b,a.a.Ya(k.ob)),a.Ta(e?e(f,h):f,b)):a.e.Z(b),k.Db=m}};a.g.Y[b]=!1;a.e.P[b]=!0}var a="undefined"!==typeof F?F:{};a.b=function(b,c){for(var d=b.split("."),e=a,g=0;g<d.length-1;g++)e=e[d[g]];e[d[d.length-1]]=c};a.s=function(a,c,d){a[c]=d};a.version="3.0.0";a.b("version",a.version);a.a=function(){function b(a,b){for(var f in a)a.hasOwnProperty(f)&&b(f,a[f])}function c(k,b){if("input"!==a.a.v(k)||!k.type||"click"!=
b.toLowerCase())return!1;var f=k.type;return"checkbox"==f||"radio"==f}var d={},e={};d[K&&/Firefox\/2/i.test(K.userAgent)?"KeyboardEvent":"UIEvents"]=["keyup","keydown","keypress"];d.MouseEvents="click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave".split(" ");b(d,function(a,b){if(b.length)for(var f=0,c=b.length;f<c;f++)e[b[f]]=a});var g={propertychange:!0},h=w&&function(){for(var a=3,b=w.createElement("div"),f=b.getElementsByTagName("i");b.innerHTML="\x3c!--[if gt IE "+
++a+"]><i></i><![endif]--\x3e",f[0];);return 4<a?a:q}();return{$a:["authenticity_token",/^__RequestVerificationToken(_.*)?$/],n:function(a,b){for(var f=0,c=a.length;f<c;f++)b(a[f])},l:function(a,b){if("function"==typeof Array.prototype.indexOf)return Array.prototype.indexOf.call(a,b);for(var f=0,c=a.length;f<c;f++)if(a[f]===b)return f;return-1},Ua:function(a,b,f){for(var c=0,d=a.length;c<d;c++)if(b.call(f,a[c]))return a[c];return null},ia:function(b,c){var f=a.a.l(b,c);0<=f&&b.splice(f,1)},Va:function(b){b=
b||[];for(var c=[],f=0,d=b.length;f<d;f++)0>a.a.l(c,b[f])&&c.push(b[f]);return c},ha:function(a,b){a=a||[];for(var f=[],c=0,d=a.length;c<d;c++)f.push(b(a[c]));return f},ga:function(a,b){a=a||[];for(var f=[],c=0,d=a.length;c<d;c++)b(a[c])&&f.push(a[c]);return f},X:function(a,b){if(b instanceof Array)a.push.apply(a,b);else for(var f=0,c=b.length;f<c;f++)a.push(b[f]);return a},V:function(b,c,f){var d=a.a.l(a.a.Ha(b),c);0>d?f&&b.push(c):f||b.splice(d,1)},extend:function(a,b){if(b)for(var f in b)b.hasOwnProperty(f)&&
(a[f]=b[f]);return a},K:b,Da:function(a,b){if(!a)return a;var f={},c;for(c in a)a.hasOwnProperty(c)&&(f[c]=b(a[c],c,a));return f},wa:function(b){for(;b.firstChild;)a.removeNode(b.firstChild)},Vb:function(b){b=a.a.Q(b);for(var c=w.createElement("div"),f=0,d=b.length;f<d;f++)c.appendChild(a.L(b[f]));return c},Ya:function(b,c){for(var f=0,d=b.length,e=[];f<d;f++){var g=b[f].cloneNode(!0);e.push(c?a.L(g):g)}return e},S:function(b,c){a.a.wa(b);if(c)for(var f=0,d=c.length;f<d;f++)b.appendChild(c[f])},nb:function(b,
c){var f=b.nodeType?[b]:b;if(0<f.length){for(var d=f[0],e=d.parentNode,g=0,n=c.length;g<n;g++)e.insertBefore(c[g],d);g=0;for(n=f.length;g<n;g++)a.removeNode(f[g])}},$:function(a,b){if(a.length){for(b=8===b.nodeType&&b.parentNode||b;a.length&&a[0].parentNode!==b;)a.splice(0,1);if(1<a.length){var f=a[0],c=a[a.length-1];for(a.length=0;f!==c;)if(a.push(f),f=f.nextSibling,!f)return;a.push(c)}}return a},qb:function(a,b){7>h?a.setAttribute("selected",b):a.selected=b},la:function(a){return null===a||a===
q?"":a.trim?a.trim():a.toString().replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")},ec:function(b,c){for(var f=[],d=(b||"").split(c),e=0,g=d.length;e<g;e++){var n=a.a.la(d[e]);""!==n&&f.push(n)}return f},ac:function(a,b){a=a||"";return b.length>a.length?!1:a.substring(0,b.length)===b},Gb:function(a,b){if(a===b)return!0;if(11===a.nodeType)return!1;if(b.contains)return b.contains(3===a.nodeType?a.parentNode:a);if(b.compareDocumentPosition)return 16==(b.compareDocumentPosition(a)&16);for(;a&&a!=b;)a=a.parentNode;
return!!a},va:function(b){return a.a.Gb(b,b.ownerDocument.documentElement)},Ra:function(b){return!!a.a.Ua(b,a.a.va)},v:function(a){return a&&a.tagName&&a.tagName.toLowerCase()},r:function(b,d,f){var e=h&&g[d];if(e||"undefined"==typeof u)if(e||"function"!=typeof b.addEventListener)if("undefined"!=typeof b.attachEvent){var s=function(a){f.call(b,a)},l="on"+d;b.attachEvent(l,s);a.a.C.ea(b,function(){b.detachEvent(l,s)})}else throw Error("Browser doesn't support addEventListener or attachEvent");else b.addEventListener(d,
f,!1);else{if(c(b,d)){var n=f;f=function(a,b){var f=this.checked;b&&(this.checked=!0!==b.Ab);n.call(this,a);this.checked=f}}u(b).bind(d,f)}},da:function(a,b){if(!a||!a.nodeType)throw Error("element must be a DOM node when calling triggerEvent");if("undefined"!=typeof u){var f=[];c(a,b)&&f.push({Ab:a.checked});u(a).trigger(b,f)}else if("function"==typeof w.createEvent)if("function"==typeof a.dispatchEvent)f=w.createEvent(e[b]||"HTMLEvents"),f.initEvent(b,!0,!0,y,0,0,0,0,0,!1,!1,!1,!1,0,a),a.dispatchEvent(f);
else throw Error("The supplied element doesn't support dispatchEvent");else if("undefined"!=typeof a.fireEvent)c(a,b)&&(a.checked=!0!==a.checked),a.fireEvent("on"+b);else throw Error("Browser doesn't support triggering events");},c:function(b){return a.M(b)?b():b},Ha:function(b){return a.M(b)?b.t():b},ma:function(b,c,f){if(c){var d=/\S+/g,e=b.className.match(d)||[];a.a.n(c.match(d),function(b){a.a.V(e,b,f)});b.className=e.join(" ")}},Ma:function(b,c){var f=a.a.c(c);if(null===f||f===q)f="";var d=a.e.firstChild(b);
!d||3!=d.nodeType||a.e.nextSibling(d)?a.e.S(b,[w.createTextNode(f)]):d.data=f;a.a.Jb(b)},pb:function(a,b){a.name=b;if(7>=h)try{a.mergeAttributes(w.createElement("<input name='"+a.name+"'/>"),!1)}catch(f){}},Jb:function(a){9<=h&&(a=1==a.nodeType?a:a.parentNode,a.style&&(a.style.zoom=a.style.zoom))},Hb:function(a){if(h){var b=a.style.width;a.style.width=0;a.style.width=b}},Zb:function(b,c){b=a.a.c(b);c=a.a.c(c);for(var f=[],d=b;d<=c;d++)f.push(d);return f},Q:function(a){for(var b=[],c=0,d=a.length;c<
d;c++)b.push(a[c]);return b},cc:6===h,dc:7===h,ja:h,ab:function(b,c){for(var f=a.a.Q(b.getElementsByTagName("input")).concat(a.a.Q(b.getElementsByTagName("textarea"))),d="string"==typeof c?function(a){return a.name===c}:function(a){return c.test(a.name)},e=[],g=f.length-1;0<=g;g--)d(f[g])&&e.push(f[g]);return e},Wb:function(b){return"string"==typeof b&&(b=a.a.la(b))?B&&B.parse?B.parse(b):(new Function("return "+b))():null},Na:function(b,c,f){if(!B||!B.stringify)throw Error("Cannot find JSON.stringify(). Some browsers (e.g., IE < 8) don't support it natively, but you can overcome this by adding a script reference to json2.js, downloadable from http://www.json.org/json2.js");
return B.stringify(a.a.c(b),c,f)},Xb:function(c,d,f){f=f||{};var e=f.params||{},g=f.includeFields||this.$a,h=c;if("object"==typeof c&&"form"===a.a.v(c))for(var h=c.action,n=g.length-1;0<=n;n--)for(var r=a.a.ab(c,g[n]),v=r.length-1;0<=v;v--)e[r[v].name]=r[v].value;d=a.a.c(d);var t=w.createElement("form");t.style.display="none";t.action=h;t.method="post";for(var E in d)c=w.createElement("input"),c.name=E,c.value=a.a.Na(a.a.c(d[E])),t.appendChild(c);b(e,function(a,b){var c=w.createElement("input");c.name=
a;c.value=b;t.appendChild(c)});w.body.appendChild(t);f.submitter?f.submitter(t):t.submit();setTimeout(function(){t.parentNode.removeChild(t)},0)}}}();a.b("utils",a.a);a.b("utils.arrayForEach",a.a.n);a.b("utils.arrayFirst",a.a.Ua);a.b("utils.arrayFilter",a.a.ga);a.b("utils.arrayGetDistinctValues",a.a.Va);a.b("utils.arrayIndexOf",a.a.l);a.b("utils.arrayMap",a.a.ha);a.b("utils.arrayPushAll",a.a.X);a.b("utils.arrayRemoveItem",a.a.ia);a.b("utils.extend",a.a.extend);a.b("utils.fieldsIncludedWithJsonPost",
a.a.$a);a.b("utils.getFormFields",a.a.ab);a.b("utils.peekObservable",a.a.Ha);a.b("utils.postJson",a.a.Xb);a.b("utils.parseJson",a.a.Wb);a.b("utils.registerEventHandler",a.a.r);a.b("utils.stringifyJson",a.a.Na);a.b("utils.range",a.a.Zb);a.b("utils.toggleDomNodeCssClass",a.a.ma);a.b("utils.triggerEvent",a.a.da);a.b("utils.unwrapObservable",a.a.c);a.b("utils.objectForEach",a.a.K);a.b("utils.addOrRemoveItem",a.a.V);a.b("unwrap",a.a.c);Function.prototype.bind||(Function.prototype.bind=function(a){var c=
this,d=Array.prototype.slice.call(arguments);a=d.shift();return function(){return c.apply(a,d.concat(Array.prototype.slice.call(arguments)))}});a.a.f=new function(){function a(b,h){var k=b[d];if(!k||"null"===k||!e[k]){if(!h)return q;k=b[d]="ko"+c++;e[k]={}}return e[k]}var c=0,d="__ko__"+(new Date).getTime(),e={};return{get:function(c,d){var e=a(c,!1);return e===q?q:e[d]},set:function(c,d,e){if(e!==q||a(c,!1)!==q)a(c,!0)[d]=e},clear:function(a){var b=a[d];return b?(delete e[b],a[d]=null,!0):!1},D:function(){return c++ +
d}}};a.b("utils.domData",a.a.f);a.b("utils.domData.clear",a.a.f.clear);a.a.C=new function(){function b(b,c){var e=a.a.f.get(b,d);e===q&&c&&(e=[],a.a.f.set(b,d,e));return e}function c(d){var e=b(d,!1);if(e)for(var e=e.slice(0),m=0;m<e.length;m++)e[m](d);a.a.f.clear(d);"function"==typeof u&&"function"==typeof u.cleanData&&u.cleanData([d]);if(g[d.nodeType])for(e=d.firstChild;d=e;)e=d.nextSibling,8===d.nodeType&&c(d)}var d=a.a.f.D(),e={1:!0,8:!0,9:!0},g={1:!0,9:!0};return{ea:function(a,c){if("function"!=
typeof c)throw Error("Callback must be a function");b(a,!0).push(c)},mb:function(c,e){var g=b(c,!1);g&&(a.a.ia(g,e),0==g.length&&a.a.f.set(c,d,q))},L:function(b){if(e[b.nodeType]&&(c(b),g[b.nodeType])){var d=[];a.a.X(d,b.getElementsByTagName("*"));for(var m=0,f=d.length;m<f;m++)c(d[m])}return b},removeNode:function(b){a.L(b);b.parentNode&&b.parentNode.removeChild(b)}}};a.L=a.a.C.L;a.removeNode=a.a.C.removeNode;a.b("cleanNode",a.L);a.b("removeNode",a.removeNode);a.b("utils.domNodeDisposal",a.a.C);
a.b("utils.domNodeDisposal.addDisposeCallback",a.a.C.ea);a.b("utils.domNodeDisposal.removeDisposeCallback",a.a.C.mb);(function(){a.a.Fa=function(b){var c;if("undefined"!=typeof u)if(u.parseHTML)c=u.parseHTML(b)||[];else{if((c=u.clean([b]))&&c[0]){for(b=c[0];b.parentNode&&11!==b.parentNode.nodeType;)b=b.parentNode;b.parentNode&&b.parentNode.removeChild(b)}}else{var d=a.a.la(b).toLowerCase();c=w.createElement("div");d=d.match(/^<(thead|tbody|tfoot)/)&&[1,"<table>","</table>"]||!d.indexOf("<tr")&&[2,
"<table><tbody>","</tbody></table>"]||(!d.indexOf("<td")||!d.indexOf("<th"))&&[3,"<table><tbody><tr>","</tr></tbody></table>"]||[0,"",""];b="ignored<div>"+d[1]+b+d[2]+"</div>";for("function"==typeof y.innerShiv?c.appendChild(y.innerShiv(b)):c.innerHTML=b;d[0]--;)c=c.lastChild;c=a.a.Q(c.lastChild.childNodes)}return c};a.a.Ka=function(b,c){a.a.wa(b);c=a.a.c(c);if(null!==c&&c!==q)if("string"!=typeof c&&(c=c.toString()),"undefined"!=typeof u)u(b).html(c);else for(var d=a.a.Fa(c),e=0;e<d.length;e++)b.appendChild(d[e])}})();
a.b("utils.parseHtmlFragment",a.a.Fa);a.b("utils.setHtml",a.a.Ka);a.u=function(){function b(c,e){if(c)if(8==c.nodeType){var g=a.u.jb(c.nodeValue);null!=g&&e.push({Fb:c,Tb:g})}else if(1==c.nodeType)for(var g=0,h=c.childNodes,k=h.length;g<k;g++)b(h[g],e)}var c={};return{Ca:function(a){if("function"!=typeof a)throw Error("You can only pass a function to ko.memoization.memoize()");var b=(4294967296*(1+Math.random())|0).toString(16).substring(1)+(4294967296*(1+Math.random())|0).toString(16).substring(1);
c[b]=a;return"\x3c!--[ko_memo:"+b+"]--\x3e"},ub:function(a,b){var g=c[a];if(g===q)throw Error("Couldn't find any memo with ID "+a+". Perhaps it's already been unmemoized.");try{return g.apply(null,b||[]),!0}finally{delete c[a]}},vb:function(c,e){var g=[];b(c,g);for(var h=0,k=g.length;h<k;h++){var m=g[h].Fb,f=[m];e&&a.a.X(f,e);a.u.ub(g[h].Tb,f);m.nodeValue="";m.parentNode&&m.parentNode.removeChild(m)}},jb:function(a){return(a=a.match(/^\[ko_memo\:(.*?)\]$/))?a[1]:null}}}();a.b("memoization",a.u);a.b("memoization.memoize",
a.u.Ca);a.b("memoization.unmemoize",a.u.ub);a.b("memoization.parseMemoText",a.u.jb);a.b("memoization.unmemoizeDomNodeAndDescendants",a.u.vb);a.xa={throttle:function(b,c){b.throttleEvaluation=c;var d=null;return a.h({read:b,write:function(a){clearTimeout(d);d=setTimeout(function(){b(a)},c)}})},notify:function(a,c){a.equalityComparer="always"==c?null:G}};var N={undefined:1,"boolean":1,number:1,string:1};a.b("extenders",a.xa);a.sb=function(b,c,d){this.target=b;this.qa=c;this.Eb=d;a.s(this,"dispose",
this.B)};a.sb.prototype.B=function(){this.Qb=!0;this.Eb()};a.ca=function(){this.F={};a.a.extend(this,a.ca.fn);a.s(this,"subscribe",this.T);a.s(this,"extend",this.extend);a.s(this,"getSubscriptionsCount",this.Lb)};var I="change";a.ca.fn={T:function(b,c,d){d=d||I;var e=new a.sb(this,c?b.bind(c):b,function(){a.a.ia(this.F[d],e)}.bind(this));this.F[d]||(this.F[d]=[]);this.F[d].push(e);return e},notifySubscribers:function(b,c){c=c||I;if(this.cb(c))try{a.i.Wa();for(var d=this.F[c].slice(0),e=0,g;g=d[e];++e)g&&
!0!==g.Qb&&g.qa(b)}finally{a.i.end()}},cb:function(a){return this.F[a]&&this.F[a].length},Lb:function(){var b=0;a.a.K(this.F,function(a,d){b+=d.length});return b},extend:function(b){var c=this;b&&a.a.K(b,function(b,e){var g=a.xa[b];"function"==typeof g&&(c=g(c,e)||c)});return c}};a.fb=function(a){return null!=a&&"function"==typeof a.T&&"function"==typeof a.notifySubscribers};a.b("subscribable",a.ca);a.b("isSubscribable",a.fb);a.i=function(){var b=[];return{Wa:function(a){b.push(a&&{qa:a,Za:[]})},
end:function(){b.pop()},lb:function(c){if(!a.fb(c))throw Error("Only subscribable things can act as dependencies");if(0<b.length){var d=b[b.length-1];!d||0<=a.a.l(d.Za,c)||(d.Za.push(c),d.qa(c))}},p:function(a,d,e){try{return b.push(null),a.apply(d,e||[])}finally{b.pop()}}}}();a.q=function(b){function c(){if(0<arguments.length)return c.equalityComparer&&c.equalityComparer(d,arguments[0])||(c.O(),d=arguments[0],c.N()),this;a.i.lb(c);return d}var d=b;a.ca.call(c);c.t=function(){return d};c.N=function(){c.notifySubscribers(d)};
c.O=function(){c.notifySubscribers(d,"beforeChange")};a.a.extend(c,a.q.fn);a.s(c,"peek",c.t);a.s(c,"valueHasMutated",c.N);a.s(c,"valueWillMutate",c.O);return c};a.q.fn={equalityComparer:G};var C=a.q.Yb="__ko_proto__";a.q.fn[C]=a.q;a.ya=function(b,c){return null===b||b===q||b[C]===q?!1:b[C]===c?!0:a.ya(b[C],c)};a.M=function(b){return a.ya(b,a.q)};a.gb=function(b){return"function"==typeof b&&b[C]===a.q||"function"==typeof b&&b[C]===a.h&&b.Nb?!0:!1};a.b("observable",a.q);a.b("isObservable",a.M);a.b("isWriteableObservable",
a.gb);a.ba=function(b){b=b||[];if("object"!=typeof b||!("length"in b))throw Error("The argument passed when initializing an observable array must be an array, or null, or undefined.");b=a.q(b);a.a.extend(b,a.ba.fn);return b.extend({trackArrayChanges:!0})};a.ba.fn={remove:function(b){for(var c=this.t(),d=[],e="function"!=typeof b||a.M(b)?function(a){return a===b}:b,g=0;g<c.length;g++){var h=c[g];e(h)&&(0===d.length&&this.O(),d.push(h),c.splice(g,1),g--)}d.length&&this.N();return d},removeAll:function(b){if(b===
q){var c=this.t(),d=c.slice(0);this.O();c.splice(0,c.length);this.N();return d}return b?this.remove(function(c){return 0<=a.a.l(b,c)}):[]},destroy:function(b){var c=this.t(),d="function"!=typeof b||a.M(b)?function(a){return a===b}:b;this.O();for(var e=c.length-1;0<=e;e--)d(c[e])&&(c[e]._destroy=!0);this.N()},destroyAll:function(b){return b===q?this.destroy(function(){return!0}):b?this.destroy(function(c){return 0<=a.a.l(b,c)}):[]},indexOf:function(b){var c=this();return a.a.l(c,b)},replace:function(a,
c){var d=this.indexOf(a);0<=d&&(this.O(),this.t()[d]=c,this.N())}};a.a.n("pop push reverse shift sort splice unshift".split(" "),function(b){a.ba.fn[b]=function(){var a=this.t();this.O();this.Xa(a,b,arguments);a=a[b].apply(a,arguments);this.N();return a}});a.a.n(["slice"],function(b){a.ba.fn[b]=function(){var a=this();return a[b].apply(a,arguments)}});a.b("observableArray",a.ba);var J="arrayChange";a.xa.trackArrayChanges=function(b){function c(){if(!d){d=!0;var c=b.notifySubscribers;b.notifySubscribers=
function(a,b){b&&b!==I||++g;return c.apply(this,arguments)};var m=[].concat(b.t()||[]);e=null;b.T(function(c){c=[].concat(c||[]);if(b.cb(J)){var d;if(!e||1<g)e=a.a.ra(m,c,{sparse:!0});d=e;d.length&&b.notifySubscribers(d,J)}m=c;e=null;g=0})}}if(!b.Xa){var d=!1,e=null,g=0,h=b.T;b.T=b.subscribe=function(a,b,f){f===J&&c();return h.apply(this,arguments)};b.Xa=function(a,b,c){function p(a,b,c){h.push({status:a,value:b,index:c})}if(d&&!g){var h=[],l=a.length,n=c.length,r=0;switch(b){case "push":r=l;case "unshift":for(b=
0;b<n;b++)p("added",c[b],r+b);break;case "pop":r=l-1;case "shift":l&&p("deleted",a[r],r);break;case "splice":b=Math.min(Math.max(0,0>c[0]?l+c[0]:c[0]),l);for(var l=1===n?l:Math.min(b+(c[1]||0),l),n=b+n-2,r=Math.max(l,n),v=2;b<r;++b,++v)b<l&&p("deleted",a[b],b),b<n&&p("added",c[v],b);break;default:return}e=h}}}};a.h=function(b,c,d){function e(){a.a.n(z,function(a){a.B()});z=[]}function g(){var a=k.throttleEvaluation;a&&0<=a?(clearTimeout(x),x=setTimeout(h,a)):h()}function h(){if(!s){if(E&&E()){if(!l){D();
p=!0;return}}else l=!1;s=!0;try{var b=a.a.ha(z,function(a){return a.target});a.i.Wa(function(c){var d;0<=(d=a.a.l(b,c))?b[d]=q:z.push(c.T(g))});for(var d=c?n.call(c):n(),e=b.length-1;0<=e;e--)b[e]&&z.splice(e,1)[0].B();p=!0;k.equalityComparer&&k.equalityComparer(f,d)||(k.notifySubscribers(f,"beforeChange"),f=d,k.notifySubscribers(f))}finally{a.i.end(),s=!1}z.length||D()}}function k(){if(0<arguments.length){if("function"===typeof r)r.apply(c,arguments);else throw Error("Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.");
return this}p||h();a.i.lb(k);return f}function m(){return!p||0<z.length}var f,p=!1,s=!1,l=!1,n=b;n&&"object"==typeof n?(d=n,n=d.read):(d=d||{},n||(n=d.read));if("function"!=typeof n)throw Error("Pass a function that returns the value of the ko.computed");var r=d.write,v=d.disposeWhenNodeIsRemoved||d.I||null,t=d.disposeWhen||d.ua,E=t,D=e,z=[],x=null;c||(c=d.owner);k.t=function(){p||h();return f};k.Kb=function(){return z.length};k.Nb="function"===typeof d.write;k.B=function(){D()};k.aa=m;a.ca.call(k);
a.a.extend(k,a.h.fn);a.s(k,"peek",k.t);a.s(k,"dispose",k.B);a.s(k,"isActive",k.aa);a.s(k,"getDependenciesCount",k.Kb);v&&(l=!0,v.nodeType&&(E=function(){return!a.a.va(v)||t&&t()}));!0!==d.deferEvaluation&&h();v&&m()&&(D=function(){a.a.C.mb(v,D);e()},a.a.C.ea(v,D));return k};a.Pb=function(b){return a.ya(b,a.h)};F=a.q.Yb;a.h[F]=a.q;a.h.fn={equalityComparer:G};a.h.fn[F]=a.h;a.b("dependentObservable",a.h);a.b("computed",a.h);a.b("isComputed",a.Pb);(function(){function b(a,g,h){h=h||new d;a=g(a);if("object"!=
typeof a||null===a||a===q||a instanceof Date||a instanceof String||a instanceof Number||a instanceof Boolean)return a;var k=a instanceof Array?[]:{};h.save(a,k);c(a,function(c){var d=g(a[c]);switch(typeof d){case "boolean":case "number":case "string":case "function":k[c]=d;break;case "object":case "undefined":var p=h.get(d);k[c]=p!==q?p:b(d,g,h)}});return k}function c(a,b){if(a instanceof Array){for(var c=0;c<a.length;c++)b(c);"function"==typeof a.toJSON&&b("toJSON")}else for(c in a)b(c)}function d(){this.keys=
[];this.Qa=[]}a.tb=function(c){if(0==arguments.length)throw Error("When calling ko.toJS, pass the object you want to convert.");return b(c,function(b){for(var c=0;a.M(b)&&10>c;c++)b=b();return b})};a.toJSON=function(b,c,d){b=a.tb(b);return a.a.Na(b,c,d)};d.prototype={save:function(b,c){var d=a.a.l(this.keys,b);0<=d?this.Qa[d]=c:(this.keys.push(b),this.Qa.push(c))},get:function(b){b=a.a.l(this.keys,b);return 0<=b?this.Qa[b]:q}}})();a.b("toJS",a.tb);a.b("toJSON",a.toJSON);(function(){a.k={o:function(b){switch(a.a.v(b)){case "option":return!0===
b.__ko__hasDomDataOptionValue__?a.a.f.get(b,a.d.options.Ea):7>=a.a.ja?b.getAttributeNode("value")&&b.getAttributeNode("value").specified?b.value:b.text:b.value;case "select":return 0<=b.selectedIndex?a.k.o(b.options[b.selectedIndex]):q;default:return b.value}},na:function(b,c){switch(a.a.v(b)){case "option":switch(typeof c){case "string":a.a.f.set(b,a.d.options.Ea,q);"__ko__hasDomDataOptionValue__"in b&&delete b.__ko__hasDomDataOptionValue__;b.value=c;break;default:a.a.f.set(b,a.d.options.Ea,c),b.__ko__hasDomDataOptionValue__=
!0,b.value="number"===typeof c?c:""}break;case "select":""===c&&(c=q);if(null===c||c===q)b.selectedIndex=-1;for(var d=b.options.length-1;0<=d;d--)if(a.k.o(b.options[d])==c){b.selectedIndex=d;break}1<b.size||-1!==b.selectedIndex||(b.selectedIndex=0);break;default:if(null===c||c===q)c="";b.value=c}}}})();a.b("selectExtensions",a.k);a.b("selectExtensions.readValue",a.k.o);a.b("selectExtensions.writeValue",a.k.na);a.g=function(){function b(b){b=a.a.la(b);123===b.charCodeAt(0)&&(b=b.slice(1,-1));var c=
[],d=b.match(e),k,l,n=0;if(d){d.push(",");for(var r=0,v;v=d[r];++r){var t=v.charCodeAt(0);if(44===t){if(0>=n){k&&c.push(l?{key:k,value:l.join("")}:{unknown:k});k=l=n=0;continue}}else if(58===t){if(!l)continue}else if(47===t&&r&&1<v.length)(t=d[r-1].match(g))&&!h[t[0]]&&(b=b.substr(b.indexOf(v)+1),d=b.match(e),d.push(","),r=-1,v="/");else if(40===t||123===t||91===t)++n;else if(41===t||125===t||93===t)--n;else if(!k&&!l){k=34===t||39===t?v.slice(1,-1):v;continue}l?l.push(v):l=[v]}}return c}var c=["true",
"false","null","undefined"],d=/^(?:[$_a-z][$\w]*|(.+)(\.\s*[$_a-z][$\w]*|\[.+\]))$/i,e=RegExp("\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'|/(?:[^/\\\\]|\\\\.)*/w*|[^\\s:,/][^,\"'{}()/:[\\]]*[^\\s,\"'{}()/:[\\]]|[^\\s]","g"),g=/[\])"'A-Za-z0-9_$]+$/,h={"in":1,"return":1,"typeof":1},k={};return{Y:[],U:k,Ga:b,ka:function(e,f){function g(b,f){var e,r=a.getBindingHandler(b);if(r&&r.preprocess?f=r.preprocess(f,b,g):1){if(r=k[b])e=f,0<=a.a.l(c,e)?e=!1:(r=e.match(d),e=null===r?!1:r[1]?"Object("+r[1]+")"+
r[2]:e),r=e;r&&l.push("'"+b+"':function(_z){"+e+"=_z}");n&&(f="function(){return "+f+" }");h.push("'"+b+"':"+f)}}f=f||{};var h=[],l=[],n=f.valueAccessors,r="string"===typeof e?b(e):e;a.a.n(r,function(a){g(a.key||a.unknown,a.value)});l.length&&g("_ko_property_writers","{"+l.join(",")+"}");return h.join(",")},Sb:function(a,b){for(var c=0;c<a.length;c++)if(a[c].key==b)return!0;return!1},oa:function(b,c,d,e,k){if(b&&a.M(b))!a.gb(b)||k&&b.t()===e||b(e);else if((b=c.get("_ko_property_writers"))&&b[d])b[d](e)}}}();
a.b("expressionRewriting",a.g);a.b("expressionRewriting.bindingRewriteValidators",a.g.Y);a.b("expressionRewriting.parseObjectLiteral",a.g.Ga);a.b("expressionRewriting.preProcessBindings",a.g.ka);a.b("expressionRewriting._twoWayBindings",a.g.U);a.b("jsonExpressionRewriting",a.g);a.b("jsonExpressionRewriting.insertPropertyAccessorsIntoJson",a.g.ka);(function(){function b(a){return 8==a.nodeType&&h.test(g?a.text:a.nodeValue)}function c(a){return 8==a.nodeType&&k.test(g?a.text:a.nodeValue)}function d(a,
d){for(var e=a,k=1,n=[];e=e.nextSibling;){if(c(e)&&(k--,0===k))return n;n.push(e);b(e)&&k++}if(!d)throw Error("Cannot find closing comment tag to match: "+a.nodeValue);return null}function e(a,b){var c=d(a,b);return c?0<c.length?c[c.length-1].nextSibling:a.nextSibling:null}var g=w&&"\x3c!--test--\x3e"===w.createComment("test").text,h=g?/^\x3c!--\s*ko(?:\s+([\s\S]+))?\s*--\x3e$/:/^\s*ko(?:\s+([\s\S]+))?\s*$/,k=g?/^\x3c!--\s*\/ko\s*--\x3e$/:/^\s*\/ko\s*$/,m={ul:!0,ol:!0};a.e={P:{},childNodes:function(a){return b(a)?
d(a):a.childNodes},Z:function(c){if(b(c)){c=a.e.childNodes(c);for(var d=0,e=c.length;d<e;d++)a.removeNode(c[d])}else a.a.wa(c)},S:function(c,d){if(b(c)){a.e.Z(c);for(var e=c.nextSibling,k=0,n=d.length;k<n;k++)e.parentNode.insertBefore(d[k],e)}else a.a.S(c,d)},kb:function(a,c){b(a)?a.parentNode.insertBefore(c,a.nextSibling):a.firstChild?a.insertBefore(c,a.firstChild):a.appendChild(c)},eb:function(c,d,e){e?b(c)?c.parentNode.insertBefore(d,e.nextSibling):e.nextSibling?c.insertBefore(d,e.nextSibling):
c.appendChild(d):a.e.kb(c,d)},firstChild:function(a){return b(a)?!a.nextSibling||c(a.nextSibling)?null:a.nextSibling:a.firstChild},nextSibling:function(a){b(a)&&(a=e(a));return a.nextSibling&&c(a.nextSibling)?null:a.nextSibling},Mb:b,bc:function(a){return(a=(g?a.text:a.nodeValue).match(h))?a[1]:null},ib:function(d){if(m[a.a.v(d)]){var k=d.firstChild;if(k){do if(1===k.nodeType){var g;g=k.firstChild;var h=null;if(g){do if(h)h.push(g);else if(b(g)){var n=e(g,!0);n?g=n:h=[g]}else c(g)&&(h=[g]);while(g=
g.nextSibling)}if(g=h)for(h=k.nextSibling,n=0;n<g.length;n++)h?d.insertBefore(g[n],h):d.appendChild(g[n])}while(k=k.nextSibling)}}}}})();a.b("virtualElements",a.e);a.b("virtualElements.allowedBindings",a.e.P);a.b("virtualElements.emptyNode",a.e.Z);a.b("virtualElements.insertAfter",a.e.eb);a.b("virtualElements.prepend",a.e.kb);a.b("virtualElements.setDomNodeChildren",a.e.S);(function(){a.H=function(){this.zb={}};a.a.extend(a.H.prototype,{nodeHasBindings:function(b){switch(b.nodeType){case 1:return null!=
b.getAttribute("data-bind");case 8:return a.e.Mb(b);default:return!1}},getBindings:function(a,c){var d=this.getBindingsString(a,c);return d?this.parseBindingsString(d,c,a):null},getBindingAccessors:function(a,c){var d=this.getBindingsString(a,c);return d?this.parseBindingsString(d,c,a,{valueAccessors:!0}):null},getBindingsString:function(b){switch(b.nodeType){case 1:return b.getAttribute("data-bind");case 8:return a.e.bc(b);default:return null}},parseBindingsString:function(b,c,d,e){try{var g=this.zb,
h=b+(e&&e.valueAccessors||""),k;if(!(k=g[h])){var m,f="with($context){with($data||{}){return{"+a.g.ka(b,e)+"}}}";m=new Function("$context","$element",f);k=g[h]=m}return k(c,d)}catch(p){throw p.message="Unable to parse bindings.\nBindings value: "+b+"\nMessage: "+p.message,p;}}});a.H.instance=new a.H})();a.b("bindingProvider",a.H);(function(){function b(a){return function(){return a}}function c(a){return a()}function d(b){return a.a.Da(a.i.p(b),function(a,c){return function(){return b()[c]}})}function e(a,
b){return d(this.getBindings.bind(this,a,b))}function g(b,c,d){var f,e=a.e.firstChild(c),k=a.H.instance,g=k.preprocessNode;if(g){for(;f=e;)e=a.e.nextSibling(f),g.call(k,f);e=a.e.firstChild(c)}for(;f=e;)e=a.e.nextSibling(f),h(b,f,d)}function h(b,c,d){var f=!0,e=1===c.nodeType;e&&a.e.ib(c);if(e&&d||a.H.instance.nodeHasBindings(c))f=m(c,null,b,d).shouldBindDescendants;f&&!p[a.a.v(c)]&&g(b,c,!e)}function k(b){var c=[],d={},f=[];a.a.K(b,function D(e){if(!d[e]){var k=a.getBindingHandler(e);k&&(k.after&&
(f.push(e),a.a.n(k.after,function(c){if(b[c]){if(-1!==a.a.l(f,c))throw Error("Cannot combine the following bindings, because they have a cyclic dependency: "+f.join(", "));D(c)}}),f.pop()),c.push({key:e,bb:k}));d[e]=!0}});return c}function m(b,d,f,g){var h=a.a.f.get(b,s);if(!d){if(h)throw Error("You cannot apply bindings multiple times to the same element.");a.a.f.set(b,s,!0)}!h&&g&&a.rb(b,f);var m;if(d&&"function"!==typeof d)m=d;else{var p=a.H.instance,l=p.getBindingAccessors||e;if(d||f.A){var A=
a.h(function(){(m=d?d(f,b):l.call(p,b,f))&&f.A&&f.A();return m},null,{I:b});m&&A.aa()||(A=null)}else m=a.i.p(l,p,[b,f])}var u;if(m){var w=A?function(a){return function(){return c(A()[a])}}:function(a){return m[a]},y=function(){return a.a.Da(A?A():m,c)};y.get=function(a){return m[a]&&c(w(a))};y.has=function(a){return a in m};g=k(m);a.a.n(g,function(c){var d=c.bb.init,e=c.bb.update,k=c.key;if(8===b.nodeType&&!a.e.P[k])throw Error("The binding '"+k+"' cannot be used with virtual elements");try{"function"==
typeof d&&a.i.p(function(){var a=d(b,w(k),y,f.$data,f);if(a&&a.controlsDescendantBindings){if(u!==q)throw Error("Multiple bindings ("+u+" and "+k+") are trying to control descendant bindings of the same element. You cannot use these bindings together on the same element.");u=k}}),"function"==typeof e&&a.h(function(){e(b,w(k),y,f.$data,f)},null,{I:b})}catch(g){throw g.message='Unable to process binding "'+k+": "+m[k]+'"\nMessage: '+g.message,g;}})}return{shouldBindDescendants:u===q}}function f(b){return b&&
b instanceof a.G?b:new a.G(b)}a.d={};var p={script:!0};a.getBindingHandler=function(b){return a.d[b]};a.G=function(b,c,d,f){var e=this,k="function"==typeof b,g,h=a.h(function(){var g=k?b():b;c?(c.A&&c.A(),a.a.extend(e,c),h&&(e.A=h)):(e.$parents=[],e.$root=g,e.ko=a);e.$rawData=b;e.$data=g;d&&(e[d]=g);f&&f(e,c,g);return e.$data},null,{ua:function(){return g&&!a.a.Ra(g)},I:!0});h.aa()&&(e.A=h,h.equalityComparer=null,g=[],h.wb=function(b){g.push(b);a.a.C.ea(b,function(b){a.a.ia(g,b);g.length||(h.B(),
e.A=h=q)})})};a.G.prototype.createChildContext=function(b,c,d){return new a.G(b,this,c,function(a,b){a.$parentContext=b;a.$parent=b.$data;a.$parents=(b.$parents||[]).slice(0);a.$parents.unshift(a.$parent);d&&d(a)})};a.G.prototype.extend=function(b){return new a.G(this.$rawData,this,null,function(c){a.a.extend(c,"function"==typeof b?b():b)})};var s=a.a.f.D(),l=a.a.f.D();a.rb=function(b,c){if(2==arguments.length)a.a.f.set(b,l,c),c.A&&c.A.wb(b);else return a.a.f.get(b,l)};a.pa=function(b,c,d){1===b.nodeType&&
a.e.ib(b);return m(b,c,f(d),!0)};a.xb=function(c,e,k){k=f(k);return a.pa(c,"function"===typeof e?d(e.bind(null,k,c)):a.a.Da(e,b),k)};a.Ta=function(a,b){1!==b.nodeType&&8!==b.nodeType||g(f(a),b,!0)};a.Sa=function(a,b){if(b&&1!==b.nodeType&&8!==b.nodeType)throw Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node");b=b||y.document.body;h(f(a),b,!0)};a.ta=function(b){switch(b.nodeType){case 1:case 8:var c=a.rb(b);if(c)return c;if(b.parentNode)return a.ta(b.parentNode)}return q};
a.Cb=function(b){return(b=a.ta(b))?b.$data:q};a.b("bindingHandlers",a.d);a.b("applyBindings",a.Sa);a.b("applyBindingsToDescendants",a.Ta);a.b("applyBindingAccessorsToNode",a.pa);a.b("applyBindingsToNode",a.xb);a.b("contextFor",a.ta);a.b("dataFor",a.Cb)})();var M={"class":"className","for":"htmlFor"};a.d.attr={update:function(b,c){var d=a.a.c(c())||{};a.a.K(d,function(c,d){d=a.a.c(d);var h=!1===d||null===d||d===q;h&&b.removeAttribute(c);8>=a.a.ja&&c in M?(c=M[c],h?b.removeAttribute(c):b[c]=d):h||b.setAttribute(c,
d.toString());"name"===c&&a.a.pb(b,h?"":d.toString())})}};(function(){a.d.checked={after:["value","attr"],init:function(b,c,d){function e(){return d.has("checkedValue")?a.a.c(d.get("checkedValue")):b.value}function g(){var k=b.checked,g=s?e():k;if(l&&(!m||k)){var h=a.i.p(c);f?p!==g?(k&&(a.a.V(h,g,!0),a.a.V(h,p,!1)),p=g):a.a.V(h,g,k):a.g.oa(h,d,"checked",g,!0)}}function h(){var d=a.a.c(c());b.checked=f?0<=a.a.l(d,e()):k?d:e()===d}var k="checkbox"==b.type,m="radio"==b.type;if(k||m){var f=k&&a.a.c(c())instanceof
Array,p=f?e():q,s=m||f,l=!1;m&&!b.name&&a.d.uniqueName.init(b,function(){return!0});a.h(g,null,{I:b});a.a.r(b,"click",g);a.h(h,null,{I:b});l=!0}}};a.g.U.checked=!0;a.d.checkedValue={update:function(b,c){b.value=a.a.c(c())}}})();a.d.css={update:function(b,c){var d=a.a.c(c());"object"==typeof d?a.a.K(d,function(c,d){d=a.a.c(d);a.a.ma(b,c,d)}):(d=String(d||""),a.a.ma(b,b.__ko__cssValue,!1),b.__ko__cssValue=d,a.a.ma(b,d,!0))}};a.d.enable={update:function(b,c){var d=a.a.c(c());d&&b.disabled?b.removeAttribute("disabled"):
d||b.disabled||(b.disabled=!0)}};a.d.disable={update:function(b,c){a.d.enable.update(b,function(){return!a.a.c(c())})}};a.d.event={init:function(b,c,d,e,g){var h=c()||{};a.a.K(h,function(k){"string"==typeof k&&a.a.r(b,k,function(b){var f,h=c()[k];if(h){try{var s=a.a.Q(arguments);e=g.$data;s.unshift(e);f=h.apply(e,s)}finally{!0!==f&&(b.preventDefault?b.preventDefault():b.returnValue=!1)}!1===d.get(k+"Bubble")&&(b.cancelBubble=!0,b.stopPropagation&&b.stopPropagation())}})})}};a.d.foreach={hb:function(b){return function(){var c=
b(),d=a.a.Ha(c);if(!d||"number"==typeof d.length)return{foreach:c,templateEngine:a.J.Aa};a.a.c(c);return{foreach:d.data,as:d.as,includeDestroyed:d.includeDestroyed,afterAdd:d.afterAdd,beforeRemove:d.beforeRemove,afterRender:d.afterRender,beforeMove:d.beforeMove,afterMove:d.afterMove,templateEngine:a.J.Aa}}},init:function(b,c){return a.d.template.init(b,a.d.foreach.hb(c))},update:function(b,c,d,e,g){return a.d.template.update(b,a.d.foreach.hb(c),d,e,g)}};a.g.Y.foreach=!1;a.e.P.foreach=!0;a.d.hasfocus=
{init:function(b,c,d){function e(e){b.__ko_hasfocusUpdating=!0;var g=b.ownerDocument;if("activeElement"in g){var f;try{f=g.activeElement}catch(h){f=g.body}e=f===b}g=c();a.g.oa(g,d,"hasfocus",e,!0);b.__ko_hasfocusLastValue=e;b.__ko_hasfocusUpdating=!1}var g=e.bind(null,!0),h=e.bind(null,!1);a.a.r(b,"focus",g);a.a.r(b,"focusin",g);a.a.r(b,"blur",h);a.a.r(b,"focusout",h)},update:function(b,c){var d=!!a.a.c(c());b.__ko_hasfocusUpdating||b.__ko_hasfocusLastValue===d||(d?b.focus():b.blur(),a.i.p(a.a.da,
null,[b,d?"focusin":"focusout"]))}};a.g.U.hasfocus=!0;a.d.hasFocus=a.d.hasfocus;a.g.U.hasFocus=!0;a.d.html={init:function(){return{controlsDescendantBindings:!0}},update:function(b,c){a.a.Ka(b,c())}};var L=a.a.f.D();H("if");H("ifnot",!1,!0);H("with",!0,!1,function(a,c){return a.createChildContext(c)});a.d.options={init:function(b){if("select"!==a.a.v(b))throw Error("options binding applies only to SELECT elements");for(;0<b.length;)b.remove(0);return{controlsDescendantBindings:!0}},update:function(b,
c,d){function e(){return a.a.ga(b.options,function(a){return a.selected})}function g(a,b,c){var d=typeof b;return"function"==d?b(a):"string"==d?a[b]:c}function h(c,d){if(p.length){var f=0<=a.a.l(p,a.k.o(d[0]));a.a.qb(d[0],f);l&&!f&&a.i.p(a.a.da,null,[b,"change"])}}var k=0!=b.length&&b.multiple?b.scrollTop:null;c=a.a.c(c());var m=d.get("optionsIncludeDestroyed"),f={},p;p=b.multiple?a.a.ha(e(),a.k.o):0<=b.selectedIndex?[a.k.o(b.options[b.selectedIndex])]:[];if(c){"undefined"==typeof c.length&&(c=[c]);
var s=a.a.ga(c,function(b){return m||b===q||null===b||!a.a.c(b._destroy)});d.has("optionsCaption")&&(c=a.a.c(d.get("optionsCaption")),null!==c&&c!==q&&s.unshift(f))}else c=[];var l=!1;c=h;d.has("optionsAfterRender")&&(c=function(b,c){h(0,c);a.i.p(d.get("optionsAfterRender"),null,[c[0],b!==f?b:q])});a.a.Ja(b,s,function(b,c,e){e.length&&(p=e[0].selected?[a.k.o(e[0])]:[],l=!0);c=w.createElement("option");b===f?(a.a.Ma(c,d.get("optionsCaption")),a.k.na(c,q)):(e=g(b,d.get("optionsValue"),b),a.k.na(c,a.a.c(e)),
b=g(b,d.get("optionsText"),e),a.a.Ma(c,b));return[c]},null,c);(b.multiple?p.length&&e().length<p.length:p.length&&0<=b.selectedIndex?a.k.o(b.options[b.selectedIndex])!==p[0]:p.length||0<=b.selectedIndex)&&a.i.p(a.a.da,null,[b,"change"]);a.a.Hb(b);k&&20<Math.abs(k-b.scrollTop)&&(b.scrollTop=k)}};a.d.options.Ea=a.a.f.D();a.d.selectedOptions={after:["options","foreach"],init:function(b,c,d){a.a.r(b,"change",function(){var e=c(),g=[];a.a.n(b.getElementsByTagName("option"),function(b){b.selected&&g.push(a.k.o(b))});
a.g.oa(e,d,"selectedOptions",g)})},update:function(b,c){if("select"!=a.a.v(b))throw Error("values binding applies only to SELECT elements");var d=a.a.c(c());d&&"number"==typeof d.length&&a.a.n(b.getElementsByTagName("option"),function(b){var c=0<=a.a.l(d,a.k.o(b));a.a.qb(b,c)})}};a.g.U.selectedOptions=!0;a.d.style={update:function(b,c){var d=a.a.c(c()||{});a.a.K(d,function(c,d){d=a.a.c(d);b.style[c]=d||""})}};a.d.submit={init:function(b,c,d,e,g){if("function"!=typeof c())throw Error("The value for a submit binding must be a function");
a.a.r(b,"submit",function(a){var d,e=c();try{d=e.call(g.$data,b)}finally{!0!==d&&(a.preventDefault?a.preventDefault():a.returnValue=!1)}})}};a.d.text={init:function(){return{controlsDescendantBindings:!0}},update:function(b,c){a.a.Ma(b,c())}};a.e.P.text=!0;a.d.uniqueName={init:function(b,c){if(c()){var d="ko_unique_"+ ++a.d.uniqueName.Bb;a.a.pb(b,d)}}};a.d.uniqueName.Bb=0;a.d.value={after:["options","foreach"],init:function(b,c,d){function e(){k=!1;var e=c(),f=a.k.o(b);a.g.oa(e,d,"value",f)}var g=
["change"],h=d.get("valueUpdate"),k=!1;h&&("string"==typeof h&&(h=[h]),a.a.X(g,h),g=a.a.Va(g));!a.a.ja||"input"!=b.tagName.toLowerCase()||"text"!=b.type||"off"==b.autocomplete||b.form&&"off"==b.form.autocomplete||-1!=a.a.l(g,"propertychange")||(a.a.r(b,"propertychange",function(){k=!0}),a.a.r(b,"blur",function(){k&&e()}));a.a.n(g,function(c){var d=e;a.a.ac(c,"after")&&(d=function(){setTimeout(e,0)},c=c.substring(5));a.a.r(b,c,d)})},update:function(b,c){var d="select"===a.a.v(b),e=a.a.c(c()),g=a.k.o(b);
e!==g&&(g=function(){a.k.na(b,e)},g(),d&&(e!==a.k.o(b)?a.i.p(a.a.da,null,[b,"change"]):setTimeout(g,0)))}};a.g.U.value=!0;a.d.visible={update:function(b,c){var d=a.a.c(c()),e="none"!=b.style.display;d&&!e?b.style.display="":!d&&e&&(b.style.display="none")}};(function(b){a.d[b]={init:function(c,d,e,g,h){return a.d.event.init.call(this,c,function(){var a={};a[b]=d();return a},e,g,h)}}})("click");a.w=function(){};a.w.prototype.renderTemplateSource=function(){throw Error("Override renderTemplateSource");
};a.w.prototype.createJavaScriptEvaluatorBlock=function(){throw Error("Override createJavaScriptEvaluatorBlock");};a.w.prototype.makeTemplateSource=function(b,c){if("string"==typeof b){c=c||w;var d=c.getElementById(b);if(!d)throw Error("Cannot find template with ID "+b);return new a.m.j(d)}if(1==b.nodeType||8==b.nodeType)return new a.m.W(b);throw Error("Unknown template type: "+b);};a.w.prototype.renderTemplate=function(a,c,d,e){a=this.makeTemplateSource(a,e);return this.renderTemplateSource(a,c,
d)};a.w.prototype.isTemplateRewritten=function(a,c){return!1===this.allowTemplateRewriting?!0:this.makeTemplateSource(a,c).data("isRewritten")};a.w.prototype.rewriteTemplate=function(a,c,d){a=this.makeTemplateSource(a,d);c=c(a.text());a.text(c);a.data("isRewritten",!0)};a.b("templateEngine",a.w);a.Oa=function(){function b(b,c,d,k){b=a.g.Ga(b);for(var m=a.g.Y,f=0;f<b.length;f++){var p=b[f].key;if(m.hasOwnProperty(p)){var s=m[p];if("function"===typeof s){if(p=s(b[f].value))throw Error(p);}else if(!s)throw Error("This template engine does not support the '"+
p+"' binding within its templates");}}d="ko.__tr_ambtns(function($context,$element){return(function(){return{ "+a.g.ka(b,{valueAccessors:!0})+" } })()},'"+d.toLowerCase()+"')";return k.createJavaScriptEvaluatorBlock(d)+c}var c=/(<([a-z]+\d*)(?:\s+(?!data-bind\s*=\s*)[a-z0-9\-]+(?:=(?:\"[^\"]*\"|\'[^\']*\'))?)*\s+)data-bind\s*=\s*(["'])([\s\S]*?)\3/gi,d=/\x3c!--\s*ko\b\s*([\s\S]*?)\s*--\x3e/g;return{Ib:function(b,c,d){c.isTemplateRewritten(b,d)||c.rewriteTemplate(b,function(b){return a.Oa.Ub(b,c)},
d)},Ub:function(a,g){return a.replace(c,function(a,c,d,f,e){return b(e,c,d,g)}).replace(d,function(a,c){return b(c,"\x3c!-- ko --\x3e","#comment",g)})},yb:function(b,c){return a.u.Ca(function(d,k){var m=d.nextSibling;m&&m.nodeName.toLowerCase()===c&&a.pa(m,b,k)})}}}();a.b("__tr_ambtns",a.Oa.yb);(function(){a.m={};a.m.j=function(a){this.j=a};a.m.j.prototype.text=function(){var b=a.a.v(this.j),b="script"===b?"text":"textarea"===b?"value":"innerHTML";if(0==arguments.length)return this.j[b];var c=arguments[0];
"innerHTML"===b?a.a.Ka(this.j,c):this.j[b]=c};var b=a.a.f.D()+"_";a.m.j.prototype.data=function(c){if(1===arguments.length)return a.a.f.get(this.j,b+c);a.a.f.set(this.j,b+c,arguments[1])};var c=a.a.f.D();a.m.W=function(a){this.j=a};a.m.W.prototype=new a.m.j;a.m.W.prototype.text=function(){if(0==arguments.length){var b=a.a.f.get(this.j,c)||{};b.Pa===q&&b.sa&&(b.Pa=b.sa.innerHTML);return b.Pa}a.a.f.set(this.j,c,{Pa:arguments[0]})};a.m.j.prototype.nodes=function(){if(0==arguments.length)return(a.a.f.get(this.j,
c)||{}).sa;a.a.f.set(this.j,c,{sa:arguments[0]})};a.b("templateSources",a.m);a.b("templateSources.domElement",a.m.j);a.b("templateSources.anonymousTemplate",a.m.W)})();(function(){function b(b,c,d){var e;for(c=a.e.nextSibling(c);b&&(e=b)!==c;)b=a.e.nextSibling(e),d(e,b)}function c(c,d){if(c.length){var f=c[0],e=c[c.length-1],g=f.parentNode,h=a.H.instance,n=h.preprocessNode;if(n){b(f,e,function(a,b){var c=a.previousSibling,d=n.call(h,a);d&&(a===f&&(f=d[0]||b),a===e&&(e=d[d.length-1]||c))});c.length=
0;if(!f)return;f===e?c.push(f):(c.push(f,e),a.a.$(c,g))}b(f,e,function(b){1!==b.nodeType&&8!==b.nodeType||a.Sa(d,b)});b(f,e,function(b){1!==b.nodeType&&8!==b.nodeType||a.u.vb(b,[d])});a.a.$(c,g)}}function d(a){return a.nodeType?a:0<a.length?a[0]:null}function e(b,e,f,h,s){s=s||{};var l=b&&d(b),l=l&&l.ownerDocument,n=s.templateEngine||g;a.Oa.Ib(f,n,l);f=n.renderTemplate(f,h,s,l);if("number"!=typeof f.length||0<f.length&&"number"!=typeof f[0].nodeType)throw Error("Template engine must return an array of DOM nodes");
l=!1;switch(e){case "replaceChildren":a.e.S(b,f);l=!0;break;case "replaceNode":a.a.nb(b,f);l=!0;break;case "ignoreTargetNode":break;default:throw Error("Unknown renderMode: "+e);}l&&(c(f,h),s.afterRender&&a.i.p(s.afterRender,null,[f,h.$data]));return f}var g;a.La=function(b){if(b!=q&&!(b instanceof a.w))throw Error("templateEngine must inherit from ko.templateEngine");g=b};a.Ia=function(b,c,f,h,s){f=f||{};if((f.templateEngine||g)==q)throw Error("Set a template engine before calling renderTemplate");
s=s||"replaceChildren";if(h){var l=d(h);return a.h(function(){var g=c&&c instanceof a.G?c:new a.G(a.a.c(c)),r="function"==typeof b?b(g.$data,g):b,g=e(h,s,r,g,f);"replaceNode"==s&&(h=g,l=d(h))},null,{ua:function(){return!l||!a.a.va(l)},I:l&&"replaceNode"==s?l.parentNode:l})}return a.u.Ca(function(d){a.Ia(b,c,f,d,"replaceNode")})};a.$b=function(b,d,f,g,h){function l(a,b){c(b,r);f.afterRender&&f.afterRender(b,a)}function n(a,c){r=h.createChildContext(a,f.as,function(a){a.$index=c});var d="function"==
typeof b?b(a,r):b;return e(null,"ignoreTargetNode",d,r,f)}var r;return a.h(function(){var b=a.a.c(d)||[];"undefined"==typeof b.length&&(b=[b]);b=a.a.ga(b,function(b){return f.includeDestroyed||b===q||null===b||!a.a.c(b._destroy)});a.i.p(a.a.Ja,null,[g,b,n,f,l])},null,{I:g})};var h=a.a.f.D();a.d.template={init:function(b,c){var d=a.a.c(c());"string"==typeof d||d.name?a.e.Z(b):(d=a.e.childNodes(b),d=a.a.Vb(d),(new a.m.W(b)).nodes(d));return{controlsDescendantBindings:!0}},update:function(b,c,d,e,g){c=
a.a.c(c());d={};e=!0;var l,n=null;"string"!=typeof c&&(d=c,c=a.a.c(d.name),"if"in d&&(e=a.a.c(d["if"])),e&&"ifnot"in d&&(e=!a.a.c(d.ifnot)),l=a.a.c(d.data));"foreach"in d?n=a.$b(c||b,e&&d.foreach||[],d,b,g):e?(g="data"in d?g.createChildContext(l,d.as):g,n=a.Ia(c||b,g,d,b)):a.e.Z(b);g=n;(l=a.a.f.get(b,h))&&"function"==typeof l.B&&l.B();a.a.f.set(b,h,g&&g.aa()?g:q)}};a.g.Y.template=function(b){b=a.g.Ga(b);return 1==b.length&&b[0].unknown||a.g.Sb(b,"name")?null:"This template engine does not support anonymous templates nested within its templates"};
a.e.P.template=!0})();a.b("setTemplateEngine",a.La);a.b("renderTemplate",a.Ia);a.a.ra=function(){function a(b,d,e,g,h){var k=Math.min,m=Math.max,f=[],p,q=b.length,l,n=d.length,r=n-q||1,v=q+n+1,t,u,w;for(p=0;p<=q;p++)for(u=t,f.push(t=[]),w=k(n,p+r),l=m(0,p-1);l<=w;l++)t[l]=l?p?b[p-1]===d[l-1]?u[l-1]:k(u[l]||v,t[l-1]||v)+1:l+1:p+1;k=[];m=[];r=[];p=q;for(l=n;p||l;)n=f[p][l]-1,l&&n===f[p][l-1]?m.push(k[k.length]={status:e,value:d[--l],index:l}):p&&n===f[p-1][l]?r.push(k[k.length]={status:g,value:b[--p],
index:p}):(--l,--p,h.sparse||k.push({status:"retained",value:d[l]}));if(m.length&&r.length){b=10*q;var z;for(d=e=0;(h.dontLimitMoves||d<b)&&(z=m[e]);e++){for(g=0;f=r[g];g++)if(z.value===f.value){z.moved=f.index;f.moved=z.index;r.splice(g,1);d=g=0;break}d+=g}}return k.reverse()}return function(c,d,e){e="boolean"===typeof e?{dontLimitMoves:e}:e||{};c=c||[];d=d||[];return c.length<=d.length?a(c,d,"added","deleted",e):a(d,c,"deleted","added",e)}}();a.b("utils.compareArrays",a.a.ra);(function(){function b(b,
c,g,h,k){var m=[],f=a.h(function(){var f=c(g,k,a.a.$(m,b))||[];0<m.length&&(a.a.nb(m,f),h&&a.i.p(h,null,[g,f,k]));m.splice(0,m.length);a.a.X(m,f)},null,{I:b,ua:function(){return!a.a.Ra(m)}});return{R:m,h:f.aa()?f:q}}var c=a.a.f.D();a.a.Ja=function(d,e,g,h,k){function m(b,c){x=s[c];t!==c&&(z[b]=x);x.za(t++);a.a.$(x.R,d);r.push(x);w.push(x)}function f(b,c){if(b)for(var d=0,e=c.length;d<e;d++)c[d]&&a.a.n(c[d].R,function(a){b(a,d,c[d].fa)})}e=e||[];h=h||{};var p=a.a.f.get(d,c)===q,s=a.a.f.get(d,c)||[],
l=a.a.ha(s,function(a){return a.fa}),n=a.a.ra(l,e,h.dontLimitMoves),r=[],v=0,t=0,u=[],w=[];e=[];for(var z=[],l=[],x,A=0,y,B;y=n[A];A++)switch(B=y.moved,y.status){case "deleted":B===q&&(x=s[v],x.h&&x.h.B(),u.push.apply(u,a.a.$(x.R,d)),h.beforeRemove&&(e[A]=x,w.push(x)));v++;break;case "retained":m(A,v++);break;case "added":B!==q?m(A,B):(x={fa:y.value,za:a.q(t++)},r.push(x),w.push(x),p||(l[A]=x))}f(h.beforeMove,z);a.a.n(u,h.beforeRemove?a.L:a.removeNode);for(var A=0,p=a.e.firstChild(d),C;x=w[A];A++){x.R||
a.a.extend(x,b(d,g,x.fa,k,x.za));for(v=0;n=x.R[v];p=n.nextSibling,C=n,v++)n!==p&&a.e.eb(d,n,C);!x.Ob&&k&&(k(x.fa,x.R,x.za),x.Ob=!0)}f(h.beforeRemove,e);f(h.afterMove,z);f(h.afterAdd,l);a.a.f.set(d,c,r)}})();a.b("utils.setDomNodeChildrenFromArrayMapping",a.a.Ja);a.J=function(){this.allowTemplateRewriting=!1};a.J.prototype=new a.w;a.J.prototype.renderTemplateSource=function(b){var c=(9>a.a.ja?0:b.nodes)?b.nodes():null;if(c)return a.a.Q(c.cloneNode(!0).childNodes);b=b.text();return a.a.Fa(b)};a.J.Aa=
new a.J;a.La(a.J.Aa);a.b("nativeTemplateEngine",a.J);(function(){a.Ba=function(){var a=this.Rb=function(){if("undefined"==typeof u||!u.tmpl)return 0;try{if(0<=u.tmpl.tag.tmpl.open.toString().indexOf("__"))return 2}catch(a){}return 1}();this.renderTemplateSource=function(b,e,g){g=g||{};if(2>a)throw Error("Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later.");var h=b.data("precompiled");h||(h=b.text()||"",h=u.template(null,"{{ko_with $item.koBindingContext}}"+h+
"{{/ko_with}}"),b.data("precompiled",h));b=[e.$data];e=u.extend({koBindingContext:e},g.templateOptions);e=u.tmpl(h,b,e);e.appendTo(w.createElement("div"));u.fragments={};return e};this.createJavaScriptEvaluatorBlock=function(a){return"{{ko_code ((function() { return "+a+" })()) }}"};this.addTemplate=function(a,b){w.write("<script type='text/html' id='"+a+"'>"+b+"\x3c/script>")};0<a&&(u.tmpl.tag.ko_code={open:"__.push($1 || '');"},u.tmpl.tag.ko_with={open:"with($1) {",close:"} "})};a.Ba.prototype=
new a.w;var b=new a.Ba;0<b.Rb&&a.La(b);a.b("jqueryTmplTemplateEngine",a.Ba)})()})})();})();
;
/**
 * Copyright (c) 2011-2014 Felix Gnass
 * Licensed under the MIT license
 * http://spin.js.org/
 *
 * Example:
    var opts = {
      lines: 12             // The number of lines to draw
    , length: 7             // The length of each line
    , width: 5              // The line thickness
    , radius: 10            // The radius of the inner circle
    , scale: 1.0            // Scales overall size of the spinner
    , corners: 1            // Roundness (0..1)
    , color: '#000'         // #rgb or #rrggbb
    , opacity: 1/4          // Opacity of the lines
    , rotate: 0             // Rotation offset
    , direction: 1          // 1: clockwise, -1: counterclockwise
    , speed: 1              // Rounds per second
    , trail: 100            // Afterglow percentage
    , fps: 20               // Frames per second when using setTimeout()
    , zIndex: 2e9           // Use a high z-index by default
    , className: 'spinner'  // CSS class to assign to the element
    , top: '50%'            // center vertically
    , left: '50%'           // center horizontally
    , shadow: false         // Whether to render a shadow
    , hwaccel: false        // Whether to use hardware acceleration (might be buggy)
    , position: 'absolute'  // Element positioning
    }
    var target = document.getElementById('foo')
    var spinner = new Spinner(opts).spin(target)
 */
;(function (root, factory) {

  /* CommonJS */
  if (typeof module == 'object' && module.exports) module.exports = factory()

  /* AMD module */
  else if (typeof define == 'function' && define.amd) define(factory)

  /* Browser global */
  else root.Spinner = factory()
}(this, function () {
  "use strict"

  var prefixes = ['webkit', 'Moz', 'ms', 'O'] /* Vendor prefixes */
    , animations = {} /* Animation rules keyed by their name */
    , useCssAnimations /* Whether to use CSS animations or setTimeout */
    , sheet /* A stylesheet to hold the @keyframe or VML rules. */

  /**
   * Utility function to create elements. If no tag name is given,
   * a DIV is created. Optionally properties can be passed.
   */
  function createEl (tag, prop) {
    var el = document.createElement(tag || 'div')
      , n

    for (n in prop) el[n] = prop[n]
    return el
  }

  /**
   * Appends children and returns the parent.
   */
  function ins (parent /* child1, child2, ...*/) {
    for (var i = 1, n = arguments.length; i < n; i++) {
      parent.appendChild(arguments[i])
    }

    return parent
  }

  /**
   * Creates an opacity keyframe animation rule and returns its name.
   * Since most mobile Webkits have timing issues with animation-delay,
   * we create separate rules for each line/segment.
   */
  function addAnimation (alpha, trail, i, lines) {
    var name = ['opacity', trail, ~~(alpha * 100), i, lines].join('-')
      , start = 0.01 + i/lines * 100
      , z = Math.max(1 - (1-alpha) / trail * (100-start), alpha)
      , prefix = useCssAnimations.substring(0, useCssAnimations.indexOf('Animation')).toLowerCase()
      , pre = prefix && '-' + prefix + '-' || ''

    if (!animations[name]) {
      sheet.insertRule(
        '@' + pre + 'keyframes ' + name + '{' +
        '0%{opacity:' + z + '}' +
        start + '%{opacity:' + alpha + '}' +
        (start+0.01) + '%{opacity:1}' +
        (start+trail) % 100 + '%{opacity:' + alpha + '}' +
        '100%{opacity:' + z + '}' +
        '}', sheet.cssRules.length)

      animations[name] = 1
    }

    return name
  }

  /**
   * Tries various vendor prefixes and returns the first supported property.
   */
  function vendor (el, prop) {
    var s = el.style
      , pp
      , i

    prop = prop.charAt(0).toUpperCase() + prop.slice(1)
    if (s[prop] !== undefined) return prop
    for (i = 0; i < prefixes.length; i++) {
      pp = prefixes[i]+prop
      if (s[pp] !== undefined) return pp
    }
  }

  /**
   * Sets multiple style properties at once.
   */
  function css (el, prop) {
    for (var n in prop) {
      el.style[vendor(el, n) || n] = prop[n]
    }

    return el
  }

  /**
   * Fills in default values.
   */
  function merge (obj) {
    for (var i = 1; i < arguments.length; i++) {
      var def = arguments[i]
      for (var n in def) {
        if (obj[n] === undefined) obj[n] = def[n]
      }
    }
    return obj
  }

  /**
   * Returns the line color from the given string or array.
   */
  function getColor (color, idx) {
    return typeof color == 'string' ? color : color[idx % color.length]
  }

  // Built-in defaults

  var defaults = {
    lines: 12             // The number of lines to draw
  , length: 7             // The length of each line
  , width: 5              // The line thickness
  , radius: 10            // The radius of the inner circle
  , scale: 1.0            // Scales overall size of the spinner
  , corners: 1            // Roundness (0..1)
  , color: '#000'         // #rgb or #rrggbb
  , opacity: 1/4          // Opacity of the lines
  , rotate: 0             // Rotation offset
  , direction: 1          // 1: clockwise, -1: counterclockwise
  , speed: 1              // Rounds per second
  , trail: 100            // Afterglow percentage
  , fps: 20               // Frames per second when using setTimeout()
  , zIndex: 2e9           // Use a high z-index by default
  , className: 'spinner'  // CSS class to assign to the element
  , top: '50%'            // center vertically
  , left: '50%'           // center horizontally
  , shadow: false         // Whether to render a shadow
  , hwaccel: false        // Whether to use hardware acceleration (might be buggy)
  , position: 'absolute'  // Element positioning
  }

  /** The constructor */
  function Spinner (o) {
    this.opts = merge(o || {}, Spinner.defaults, defaults)
  }

  // Global defaults that override the built-ins:
  Spinner.defaults = {}

  merge(Spinner.prototype, {
    /**
     * Adds the spinner to the given target element. If this instance is already
     * spinning, it is automatically removed from its previous target b calling
     * stop() internally.
     */
    spin: function (target) {
      this.stop()

      var self = this
        , o = self.opts
        , el = self.el = createEl(null, {className: o.className})

      css(el, {
        position: o.position
      , width: 0
      , zIndex: o.zIndex
      , left: o.left
      , top: o.top
      })

      if (target) {
        target.insertBefore(el, target.firstChild || null)
      }

      el.setAttribute('role', 'progressbar')
      self.lines(el, self.opts)

      if (!useCssAnimations) {
        // No CSS animation support, use setTimeout() instead
        var i = 0
          , start = (o.lines - 1) * (1 - o.direction) / 2
          , alpha
          , fps = o.fps
          , f = fps / o.speed
          , ostep = (1 - o.opacity) / (f * o.trail / 100)
          , astep = f / o.lines

        ;(function anim () {
          i++
          for (var j = 0; j < o.lines; j++) {
            alpha = Math.max(1 - (i + (o.lines - j) * astep) % f * ostep, o.opacity)

            self.opacity(el, j * o.direction + start, alpha, o)
          }
          self.timeout = self.el && setTimeout(anim, ~~(1000 / fps))
        })()
      }
      return self
    }

    /**
     * Stops and removes the Spinner.
     */
  , stop: function () {
      var el = this.el
      if (el) {
        clearTimeout(this.timeout)
        if (el.parentNode) el.parentNode.removeChild(el)
        this.el = undefined
      }
      return this
    }

    /**
     * Internal method that draws the individual lines. Will be overwritten
     * in VML fallback mode below.
     */
  , lines: function (el, o) {
      var i = 0
        , start = (o.lines - 1) * (1 - o.direction) / 2
        , seg

      function fill (color, shadow) {
        return css(createEl(), {
          position: 'absolute'
        , width: o.scale * (o.length + o.width) + 'px'
        , height: o.scale * o.width + 'px'
        , background: color
        , boxShadow: shadow
        , transformOrigin: 'left'
        , transform: 'rotate(' + ~~(360/o.lines*i + o.rotate) + 'deg) translate(' + o.scale*o.radius + 'px' + ',0)'
        , borderRadius: (o.corners * o.scale * o.width >> 1) + 'px'
        })
      }

      for (; i < o.lines; i++) {
        seg = css(createEl(), {
          position: 'absolute'
        , top: 1 + ~(o.scale * o.width / 2) + 'px'
        , transform: o.hwaccel ? 'translate3d(0,0,0)' : ''
        , opacity: o.opacity
        , animation: useCssAnimations && addAnimation(o.opacity, o.trail, start + i * o.direction, o.lines) + ' ' + 1 / o.speed + 's linear infinite'
        })

        if (o.shadow) ins(seg, css(fill('#000', '0 0 4px #000'), {top: '2px'}))
        ins(el, ins(seg, fill(getColor(o.color, i), '0 0 1px rgba(0,0,0,.1)')))
      }
      return el
    }

    /**
     * Internal method that adjusts the opacity of a single line.
     * Will be overwritten in VML fallback mode below.
     */
  , opacity: function (el, i, val) {
      if (i < el.childNodes.length) el.childNodes[i].style.opacity = val
    }

  })


  function initVML () {

    /* Utility function to create a VML tag */
    function vml (tag, attr) {
      return createEl('<' + tag + ' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">', attr)
    }

    // No CSS transforms but VML support, add a CSS rule for VML elements:
    sheet.addRule('.spin-vml', 'behavior:url(#default#VML)')

    Spinner.prototype.lines = function (el, o) {
      var r = o.scale * (o.length + o.width)
        , s = o.scale * 2 * r

      function grp () {
        return css(
          vml('group', {
            coordsize: s + ' ' + s
          , coordorigin: -r + ' ' + -r
          })
        , { width: s, height: s }
        )
      }

      var margin = -(o.width + o.length) * o.scale * 2 + 'px'
        , g = css(grp(), {position: 'absolute', top: margin, left: margin})
        , i

      function seg (i, dx, filter) {
        ins(
          g
        , ins(
            css(grp(), {rotation: 360 / o.lines * i + 'deg', left: ~~dx})
          , ins(
              css(
                vml('roundrect', {arcsize: o.corners})
              , { width: r
                , height: o.scale * o.width
                , left: o.scale * o.radius
                , top: -o.scale * o.width >> 1
                , filter: filter
                }
              )
            , vml('fill', {color: getColor(o.color, i), opacity: o.opacity})
            , vml('stroke', {opacity: 0}) // transparent stroke to fix color bleeding upon opacity change
            )
          )
        )
      }

      if (o.shadow)
        for (i = 1; i <= o.lines; i++) {
          seg(i, -2, 'progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)')
        }

      for (i = 1; i <= o.lines; i++) seg(i)
      return ins(el, g)
    }

    Spinner.prototype.opacity = function (el, i, val, o) {
      var c = el.firstChild
      o = o.shadow && o.lines || 0
      if (c && i + o < c.childNodes.length) {
        c = c.childNodes[i + o]; c = c && c.firstChild; c = c && c.firstChild
        if (c) c.opacity = val
      }
    }
  }

  if (typeof document !== 'undefined') {
    sheet = (function () {
      var el = createEl('style', {type : 'text/css'})
      ins(document.getElementsByTagName('head')[0], el)
      return el.sheet || el.styleSheet
    }())

    var probe = css(createEl('group'), {behavior: 'url(#default#VML)'})

    if (!vendor(probe, 'transform') && probe.adj) initVML()
    else useCssAnimations = vendor(probe, 'animation')
  }

  return Spinner

}));;
/**
 * @license Knockout.Punches
 * Enhanced binding syntaxes for Knockout 3+
 * (c) Michael Best
 * License: MIT (http://www.opensource.org/licenses/mit-license.php)
 * Version 0.4.1
 */
(function (factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['knockout'], factory);
    } else {
        // Browser globals
        factory(ko);
    }
}(function(ko) {

// Add a preprocess function to a binding handler.
function addBindingPreprocessor(bindingKeyOrHandler, preprocessFn) {
    return chainPreprocessor(getOrCreateHandler(bindingKeyOrHandler), 'preprocess', preprocessFn);
}

// These utility functions are separated out because they're also used by
// preprocessBindingProperty

// Get the binding handler or create a new, empty one
function getOrCreateHandler(bindingKeyOrHandler) {
    return typeof bindingKeyOrHandler === 'object' ? bindingKeyOrHandler :
        (ko.getBindingHandler(bindingKeyOrHandler) || (ko.bindingHandlers[bindingKeyOrHandler] = {}));
}
// Add a preprocess function
function chainPreprocessor(obj, prop, fn) {
    if (obj[prop]) {
        // If the handler already has a preprocess function, chain the new
        // one after the existing one. If the previous function in the chain
        // returns a falsy value (to remove the binding), the chain ends. This
        // method allows each function to modify and return the binding value.
        var previousFn = obj[prop];
        obj[prop] = function(value, binding, addBinding) {
            value = previousFn.call(this, value, binding, addBinding);
            if (value)
                return fn.call(this, value, binding, addBinding);
        };
    } else {
        obj[prop] = fn;
    }
    return obj;
}

// Add a preprocessNode function to the binding provider. If a
// function already exists, chain the new one after it. This calls
// each function in the chain until one modifies the node. This
// method allows only one function to modify the node.
function addNodePreprocessor(preprocessFn) {
    var provider = ko.bindingProvider.instance;
    if (provider.preprocessNode) {
        var previousPreprocessFn = provider.preprocessNode;
        provider.preprocessNode = function(node) {
            var newNodes = previousPreprocessFn.call(this, node);
            if (!newNodes)
                newNodes = preprocessFn.call(this, node);
            return newNodes;
        };
    } else {
        provider.preprocessNode = preprocessFn;
    }
}

function addBindingHandlerCreator(matchRegex, callbackFn) {
    var oldGetHandler = ko.getBindingHandler;
    ko.getBindingHandler = function(bindingKey) {
        var match;
        return oldGetHandler(bindingKey) || ((match = bindingKey.match(matchRegex)) && callbackFn(match, bindingKey));
    };
}

// Create shortcuts to commonly used ko functions
var ko_unwrap = ko.unwrap;

// Create "punches" object and export utility functions
var ko_punches = ko.punches = {
    utils: {
        addBindingPreprocessor: addBindingPreprocessor,
        addNodePreprocessor: addNodePreprocessor,
        addBindingHandlerCreator: addBindingHandlerCreator,

        // previous names retained for backwards compitibility
        setBindingPreprocessor: addBindingPreprocessor,
        setNodePreprocessor: addNodePreprocessor
    }
};

ko_punches.enableAll = function () {
    // Enable interpolation markup
    enableInterpolationMarkup();
    enableAttributeInterpolationMarkup();

    // Enable auto-namspacing of attr, css, event, and style
    enableAutoNamespacedSyntax('attr');
    enableAutoNamespacedSyntax('css');
    enableAutoNamespacedSyntax('event');
    enableAutoNamespacedSyntax('style');

    // Enable filter syntax for text, html, and attr
    enableTextFilter('text');
    enableTextFilter('html');
    setDefaultNamespacedBindingPreprocessor('attr', filterPreprocessor);

    // Enable wrapped callbacks for click, submit, event, optionsAfterRender, and template options
    enableWrappedCallback('click');
    enableWrappedCallback('submit');
    enableWrappedCallback('optionsAfterRender');
    setDefaultNamespacedBindingPreprocessor('event', wrappedCallbackPreprocessor);
    setBindingPropertyPreprocessor('template', 'beforeRemove', wrappedCallbackPreprocessor);
    setBindingPropertyPreprocessor('template', 'afterAdd', wrappedCallbackPreprocessor);
    setBindingPropertyPreprocessor('template', 'afterRender', wrappedCallbackPreprocessor);
};
// Convert input in the form of `expression | filter1 | filter2:arg1:arg2` to a function call format
// with filters accessed as ko.filters.filter1, etc.
function filterPreprocessor(input) {
    // Check if the input contains any | characters; if not, just return
    if (input.indexOf('|') === -1)
        return input;

    // Split the input into tokens, in which | and : are individual tokens, quoted strings are ignored, and all tokens are space-trimmed
    var tokens = input.match(/"([^"\\]|\\.)*"|'([^'\\]|\\.)*'|\|\||[|:]|[^\s|:"'][^|:"']*[^\s|:"']|[^\s|:"']/g);
    if (tokens && tokens.length > 1) {
        // Append a line so that we don't need a separate code block to deal with the last item
        tokens.push('|');
        input = tokens[0];
        var lastToken, token, inFilters = false, nextIsFilter = false;
        for (var i = 1, token; token = tokens[i]; ++i) {
            if (token === '|') {
                if (inFilters) {
                    if (lastToken === ':')
                        input += "undefined";
                    input += ')';
                }
                nextIsFilter = true;
                inFilters = true;
            } else {
                if (nextIsFilter) {
                    input = "ko.filters['" + token + "'](" + input;
                } else if (inFilters && token === ':') {
                    if (lastToken === ':')
                        input += "undefined";
                    input += ",";
                } else {
                    input += token;
                }
                nextIsFilter = false;
            }
            lastToken = token;
        }
    }
    return input;
}

// Set the filter preprocessor for a specific binding
function enableTextFilter(bindingKeyOrHandler) {
    addBindingPreprocessor(bindingKeyOrHandler, filterPreprocessor);
}

var filters = {};

// Convert value to uppercase
filters.uppercase = function(value) {
    return String.prototype.toUpperCase.call(ko_unwrap(value));
};

// Convert value to lowercase
filters.lowercase = function(value) {
    return String.prototype.toLowerCase.call(ko_unwrap(value));
};

// Return default value if the input value is empty or null
filters['default'] = function (value, defaultValue) {
    value = ko_unwrap(value);
    if (typeof value === "function") {
        return value;
    }
    if (typeof value === "string") {
        return trim(value) === '' ? defaultValue : value;
    }
    return value == null || value.length == 0 ? defaultValue : value;
};

// Return the value with the search string replaced with the replacement string
filters.replace = function(value, search, replace) {
    return String.prototype.replace.call(ko_unwrap(value), search, replace);
};

filters.fit = function(value, length, replacement, trimWhere) {
    value = ko_unwrap(value);
    if (length && ('' + value).length > length) {
        replacement = '' + (replacement || '...');
        length = length - replacement.length;
        value = '' + value;
        switch (trimWhere) {
            case 'left':
                return replacement + value.slice(-length);
            case 'middle':
                var leftLen = Math.ceil(length / 2);
                return value.substr(0, leftLen) + replacement + value.slice(leftLen-length);
            default:
                return value.substr(0, length) + replacement;
        }
    } else {
        return value;
    }
};

// Convert a model object to JSON
filters.json = function(rootObject, space, replacer) {     // replacer and space are optional
    return ko.toJSON(rootObject, replacer, space);
};

// Format a number using the browser's toLocaleString
filters.number = function(value) {
    return (+ko_unwrap(value)).toLocaleString();
};

// Export the filters object for general access
ko.filters = filters;

// Export the preprocessor functions
ko_punches.textFilter = {
    preprocessor: filterPreprocessor,
    enableForBinding: enableTextFilter
};
// Support dynamically-created, namespaced bindings. The binding key syntax is
// "namespace.binding". Within a certain namespace, we can dynamically create the
// handler for any binding. This is particularly useful for bindings that work
// the same way, but just set a different named value, such as for element
// attributes or CSS classes.
var namespacedBindingMatch = /([^\.]+)\.(.+)/, namespaceDivider = '.';
addBindingHandlerCreator(namespacedBindingMatch, function (match, bindingKey) {
    var namespace = match[1],
        namespaceHandler = ko.bindingHandlers[namespace];
    if (namespaceHandler) {
        var bindingName = match[2],
            handlerFn = namespaceHandler.getNamespacedHandler || defaultGetNamespacedHandler,
            handler = handlerFn.call(namespaceHandler, bindingName, namespace, bindingKey);
        ko.bindingHandlers[bindingKey] = handler;
        return handler;
    }
});

// Knockout's built-in bindings "attr", "event", "css" and "style" include the idea of
// namespaces, representing it using a single binding that takes an object map of names
// to values. This default handler translates a binding of "namespacedName: value"
// to "namespace: {name: value}" to automatically support those built-in bindings.
function defaultGetNamespacedHandler(name, namespace, namespacedName) {
    var handler = ko.utils.extend({}, this);
    function setHandlerFunction(funcName) {
        if (handler[funcName]) {
            handler[funcName] = function(element, valueAccessor) {
                function subValueAccessor() {
                    var result = {};
                    result[name] = valueAccessor();
                    return result;
                }
                var args = Array.prototype.slice.call(arguments, 0);
                args[1] = subValueAccessor;
                return ko.bindingHandlers[namespace][funcName].apply(this, args);
            };
        }
    }
    // Set new init and update functions that wrap the originals
    setHandlerFunction('init');
    setHandlerFunction('update');
    // Clear any preprocess function since preprocessing of the new binding would need to be different
    if (handler.preprocess)
        handler.preprocess = null;
    if (ko.virtualElements.allowedBindings[namespace])
        ko.virtualElements.allowedBindings[namespacedName] = true;
    return handler;
}

// Sets a preprocess function for every generated namespace.x binding. This can
// be called multiple times for the same binding, and the preprocess functions will
// be chained. If the binding has a custom getNamespacedHandler method, make sure that
// it's set before this function is used.
function setDefaultNamespacedBindingPreprocessor(namespace, preprocessFn) {
    var handler = ko.getBindingHandler(namespace);
    if (handler) {
        var previousHandlerFn = handler.getNamespacedHandler || defaultGetNamespacedHandler;
        handler.getNamespacedHandler = function() {
            return addBindingPreprocessor(previousHandlerFn.apply(this, arguments), preprocessFn);
        };
    }
}

function autoNamespacedPreprocessor(value, binding, addBinding) {
    if (value.charAt(0) !== "{")
        return value;

    // Handle two-level binding specified as "binding: {key: value}" by parsing inner
    // object and converting to "binding.key: value"
    var subBindings = ko.expressionRewriting.parseObjectLiteral(value);
    ko.utils.arrayForEach(subBindings, function(keyValue) {
        addBinding(binding + namespaceDivider + keyValue.key, keyValue.value);
    });
}

// Set the namespaced preprocessor for a specific binding
function enableAutoNamespacedSyntax(bindingKeyOrHandler) {
    addBindingPreprocessor(bindingKeyOrHandler, autoNamespacedPreprocessor);
}

// Export the preprocessor functions
ko_punches.namespacedBinding = {
    defaultGetHandler: defaultGetNamespacedHandler,
    setDefaultBindingPreprocessor: setDefaultNamespacedBindingPreprocessor,
    preprocessor: autoNamespacedPreprocessor,
    enableForBinding: enableAutoNamespacedSyntax
};
// Wrap a callback function in an anonymous function so that it is called with the appropriate
// "this" value.
function wrappedCallbackPreprocessor(val) {
    // Matches either an isolated identifier or something ending with a property accessor
    if (/^([$_a-z][$\w]*|.+(\.\s*[$_a-z][$\w]*|\[.+\]))$/i.test(val)) {
        return 'function(_x,_y,_z){return(' + val + ')(_x,_y,_z);}';
    } else {
        return val;
    }
}

// Set the wrappedCallback preprocessor for a specific binding
function enableWrappedCallback(bindingKeyOrHandler) {
    addBindingPreprocessor(bindingKeyOrHandler, wrappedCallbackPreprocessor);
}

// Export the preprocessor functions
ko_punches.wrappedCallback = {
    preprocessor: wrappedCallbackPreprocessor,
    enableForBinding: enableWrappedCallback
};
// Attach a preprocess function to a specific property of a binding. This allows you to
// preprocess binding "options" using the same preprocess functions that work for bindings.
function setBindingPropertyPreprocessor(bindingKeyOrHandler, property, preprocessFn) {
    var handler = getOrCreateHandler(bindingKeyOrHandler);
    if (!handler._propertyPreprocessors) {
        // Initialize the binding preprocessor
        chainPreprocessor(handler, 'preprocess', propertyPreprocessor);
        handler._propertyPreprocessors = {};
    }
    // Add the property preprocess function
    chainPreprocessor(handler._propertyPreprocessors, property, preprocessFn);
}

// In order to preprocess a binding property, we have to preprocess the binding itself.
// This preprocess function splits up the binding value and runs each property's preprocess
// function if it's set.
function propertyPreprocessor(value, binding, addBinding) {
    if (value.charAt(0) !== "{")
        return value;

    var subBindings = ko.expressionRewriting.parseObjectLiteral(value),
        resultStrings = [],
        propertyPreprocessors = this._propertyPreprocessors || {};
    ko.utils.arrayForEach(subBindings, function(keyValue) {
        var prop = keyValue.key, propVal = keyValue.value;
        if (propertyPreprocessors[prop]) {
            propVal = propertyPreprocessors[prop](propVal, prop, addBinding);
        }
        if (propVal) {
            resultStrings.push("'" + prop + "':" + propVal);
        }
    });
    return "{" + resultStrings.join(",") + "}";
}

// Export the preprocessor functions
ko_punches.preprocessBindingProperty = {
    setPreprocessor: setBindingPropertyPreprocessor
};
// Wrap an expression in an anonymous function so that it is called when the event happens
function makeExpressionCallbackPreprocessor(args) {
    return function expressionCallbackPreprocessor(val) {
        return 'function('+args+'){return(' + val + ');}';
    };
}

var eventExpressionPreprocessor = makeExpressionCallbackPreprocessor("$data,$event");

// Set the expressionCallback preprocessor for a specific binding
function enableExpressionCallback(bindingKeyOrHandler, args) {
    var args = Array.prototype.slice.call(arguments, 1).join();
    addBindingPreprocessor(bindingKeyOrHandler, makeExpressionCallbackPreprocessor(args));
}

// Export the preprocessor functions
ko_punches.expressionCallback = {
    makePreprocessor: makeExpressionCallbackPreprocessor,
    eventPreprocessor: eventExpressionPreprocessor,
    enableForBinding: enableExpressionCallback
};

// Create an "on" namespace for events to use the expression method
ko.bindingHandlers.on = {
    getNamespacedHandler: function(eventName) {
        var handler = ko.getBindingHandler('event' + namespaceDivider + eventName);
        return addBindingPreprocessor(handler, eventExpressionPreprocessor);
    }
};
// Performance comparison at http://jsperf.com/markup-interpolation-comparison
function parseInterpolationMarkup(textToParse, outerTextCallback, expressionCallback) {
    function innerParse(text) {
        var innerMatch = text.match(/^([\s\S]*)}}([\s\S]*?)\{\{([\s\S]*)$/);
        if (innerMatch) {
            innerParse(innerMatch[1]);
            outerTextCallback(innerMatch[2]);
            expressionCallback(innerMatch[3]);
        } else {
            expressionCallback(text);
        }
    }
    var outerMatch = textToParse.match(/^([\s\S]*?)\{\{([\s\S]*)}}([\s\S]*)$/);
    if (outerMatch) {
        outerTextCallback(outerMatch[1]);
        innerParse(outerMatch[2]);
        outerTextCallback(outerMatch[3]);
    }
}

function trim(string) {
    return string == null ? '' :
        string.trim ?
            string.trim() :
            string.toString().replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
}

function interpolationMarkupPreprocessor(node) {
    // only needs to work with text nodes
    if (node.nodeType === 3 && node.nodeValue && node.nodeValue.indexOf('{{') !== -1 && (node.parentNode || {}).nodeName != "TEXTAREA") {
        var nodes = [];
        function addTextNode(text) {
            if (text)
                nodes.push(document.createTextNode(text));
        }
        function wrapExpr(expressionText) {
            if (expressionText)
                nodes.push.apply(nodes, ko_punches_interpolationMarkup.wrapExpression(trim(expressionText), node));
        }
        parseInterpolationMarkup(node.nodeValue, addTextNode, wrapExpr)

        if (nodes.length) {
            if (node.parentNode) {
                for (var i = 0, n = nodes.length, parent = node.parentNode; i < n; ++i) {
                    parent.insertBefore(nodes[i], node);
                }
                parent.removeChild(node);
            }
            return nodes;
        }
    }
}

if (!ko.virtualElements.allowedBindings.html) {
    // Virtual html binding
    // SO Question: http://stackoverflow.com/a/15348139
    var overridden = ko.bindingHandlers.html.update;
    ko.bindingHandlers.html.update = function (element, valueAccessor) {
        if (element.nodeType === 8) {
            var html = ko_unwrap(valueAccessor());
            if (html != null) {
                var parsedNodes = ko.utils.parseHtmlFragment('' + html);
                ko.virtualElements.setDomNodeChildren(element, parsedNodes);
            } else {
                ko.virtualElements.emptyNode(element);
            }
        } else {
            overridden(element, valueAccessor);
        }
    };
    ko.virtualElements.allowedBindings.html = true;
}

function wrapExpression(expressionText, node) {
    var ownerDocument = node ? node.ownerDocument : document,
        closeComment = ownerDocument.createComment("/ko"),
        firstChar = expressionText[0];

    if (firstChar === '#') {
        return [ ownerDocument.createComment("ko " + expressionText.slice(1)) ];
    } else if (firstChar === '/') {
        return [ closeComment ];
    } else if (firstChar === '{' && expressionText[expressionText.length - 1] === '}') {
        return [ ownerDocument.createComment("ko html:" + expressionText.slice(1, -1)), closeComment ];
    } else {
        return [ ownerDocument.createComment("ko text:" + expressionText), closeComment ];
    }
};

function enableInterpolationMarkup() {
    addNodePreprocessor(interpolationMarkupPreprocessor);
}

// Export the preprocessor functions
var ko_punches_interpolationMarkup = ko_punches.interpolationMarkup = {
    preprocessor: interpolationMarkupPreprocessor,
    enable: enableInterpolationMarkup,
    wrapExpression: wrapExpression
};


var dataBind = 'data-bind';
function attributeInterpolationMarkerPreprocessor(node) {
    if (node.nodeType === 1 && node.attributes.length) {
        var dataBindAttribute = node.getAttribute(dataBind);
        for (var attrs = ko.utils.arrayPushAll([], node.attributes), n = attrs.length, i = 0; i < n; ++i) {
            var attr = attrs[i];
            if (attr.specified && attr.name != dataBind && attr.value.indexOf('{{') !== -1) {
                var parts = [], attrValue = '';
                function addText(text) {
                    if (text)
                        parts.push('"' + text.replace(/"/g, '\\"') + '"');
                }
                function addExpr(expressionText) {
                    if (expressionText) {
                        attrValue = expressionText;
                        parts.push('ko.unwrap(' + expressionText + ')');
                    }
                }
                parseInterpolationMarkup(attr.value, addText, addExpr);

                if (parts.length > 1) {
                    attrValue = '""+' + parts.join('+');
                }

                if (attrValue) {
                    var attrName = attr.name.toLowerCase();
                    var attrBinding = ko_punches_attributeInterpolationMarkup.attributeBinding(attrName, attrValue, node) || attributeBinding(attrName, attrValue, node);
                    if (!dataBindAttribute) {
                        dataBindAttribute = attrBinding
                    } else {
                        dataBindAttribute += ',' + attrBinding;
                    }
                    node.setAttribute(dataBind, dataBindAttribute);
                    // Using removeAttribute instead of removeAttributeNode because IE clears the
                    // class if you use removeAttributeNode to remove the id.
                    node.removeAttribute(attr.name);
                }
            }
        }
    }
}

function attributeBinding(name, value, node) {
    if (ko.getBindingHandler(name)) {
        return name + ':' + value;
    } else {
        return 'attr.' + name + ':' + value;
    }
}

function enableAttributeInterpolationMarkup() {
    addNodePreprocessor(attributeInterpolationMarkerPreprocessor);
}

var ko_punches_attributeInterpolationMarkup = ko_punches.attributeInterpolationMarkup = {
    preprocessor: attributeInterpolationMarkerPreprocessor,
    enable: enableAttributeInterpolationMarkup,
    attributeBinding: attributeBinding
};

    return ko_punches;
}));
;
/*!
 * Bootstrap v3.2.0 (http://getbootstrap.com)
 * Copyright 2011-2014 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 */

/*!
 * Generated using the Bootstrap Customizer (http://getbootstrap.com/customize/?id=3602c3078fae9c951243)
 * Config saved to config.json and https://gist.github.com/3602c3078fae9c951243
 */
if (typeof jQuery === "undefined") { throw new Error("Bootstrap's JavaScript requires jQuery") }

/* ========================================================================
 * Bootstrap: tooltip.js v3.2.0
 * http://getbootstrap.com/javascript/#tooltip
 * Inspired by the original jQuery.tipsy by Jason Frame
 * ========================================================================
 * Copyright 2011-2014 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */


+function ($) {
  'use strict';

  // TOOLTIP PUBLIC CLASS DEFINITION
  // ===============================

  var Tooltip = function (element, options) {
    this.type       =
    this.options    =
    this.enabled    =
    this.timeout    =
    this.hoverState =
    this.$element   = null

    this.init('tooltip', element, options)
  }

  Tooltip.VERSION  = '3.2.0'

  Tooltip.DEFAULTS = {
    animation: true,
    placement: 'top',
    selector: false,
    template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
    trigger: 'hover focus',
    title: '',
    delay: 0,
    html: false,
    container: false,
    viewport: {
      selector: 'body',
      padding: 0
    }
  }

  Tooltip.prototype.init = function (type, element, options) {
    this.enabled   = true
    this.type      = type
    this.$element  = $(element)
    this.options   = this.getOptions(options)
    this.$viewport = this.options.viewport && $(this.options.viewport.selector || this.options.viewport)

    var triggers = this.options.trigger.split(' ')

    for (var i = triggers.length; i--;) {
      var trigger = triggers[i]

      if (trigger == 'click') {
        this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
      } else if (trigger != 'manual') {
        var eventIn  = trigger == 'hover' ? 'mouseenter' : 'focusin'
        var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'

        this.$element.on(eventIn  + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
        this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
      }
    }

    this.options.selector ?
      (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
      this.fixTitle()
  }

  Tooltip.prototype.getDefaults = function () {
    return Tooltip.DEFAULTS
  }

  Tooltip.prototype.getOptions = function (options) {
    options = $.extend({}, this.getDefaults(), this.$element.data(), options)

    if (options.delay && typeof options.delay == 'number') {
      options.delay = {
        show: options.delay,
        hide: options.delay
      }
    }

    return options
  }

  Tooltip.prototype.getDelegateOptions = function () {
    var options  = {}
    var defaults = this.getDefaults()

    this._options && $.each(this._options, function (key, value) {
      if (defaults[key] != value) options[key] = value
    })

    return options
  }

  Tooltip.prototype.enter = function (obj) {
    var self = obj instanceof this.constructor ?
      obj : $(obj.currentTarget).data('bs.' + this.type)

    if (!self) {
      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
      $(obj.currentTarget).data('bs.' + this.type, self)
    }

    clearTimeout(self.timeout)

    self.hoverState = 'in'

    if (!self.options.delay || !self.options.delay.show) return self.show()

    self.timeout = setTimeout(function () {
      if (self.hoverState == 'in') self.show()
    }, self.options.delay.show)
  }

  Tooltip.prototype.leave = function (obj) {
    var self = obj instanceof this.constructor ?
      obj : $(obj.currentTarget).data('bs.' + this.type)

    if (!self) {
      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
      $(obj.currentTarget).data('bs.' + this.type, self)
    }

    clearTimeout(self.timeout)

    self.hoverState = 'out'

    if (!self.options.delay || !self.options.delay.hide) return self.hide()

    self.timeout = setTimeout(function () {
      if (self.hoverState == 'out') self.hide()
    }, self.options.delay.hide)
  }

  Tooltip.prototype.show = function () {
    var e = $.Event('show.bs.' + this.type)

    if (this.hasContent() && this.enabled) {
      this.$element.trigger(e)

      var inDom = $.contains(document.documentElement, this.$element[0])
      if (e.isDefaultPrevented() || !inDom) return
      var that = this

      var $tip = this.tip()

      var tipId = this.getUID(this.type)

      this.setContent()
      $tip.attr('id', tipId)
      this.$element.attr('aria-describedby', tipId)

      if (this.options.animation) $tip.addClass('fade')

      var placement = typeof this.options.placement == 'function' ?
        this.options.placement.call(this, $tip[0], this.$element[0]) :
        this.options.placement

      var autoToken = /\s?auto?\s?/i
      var autoPlace = autoToken.test(placement)
      if (autoPlace) placement = placement.replace(autoToken, '') || 'top'

      $tip
        .detach()
        .css({ top: 0, left: 0, display: 'block' })
        .addClass(placement)
        .data('bs.' + this.type, this)

      this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)

      var pos = this.getPosition()
      var actualWidth  = $tip[0].offsetWidth
      var actualHeight = $tip[0].offsetHeight

      if (autoPlace) {
        var orgPlacement = placement
        var $parent      = this.$element.parent()
        var parentDim    = this.getPosition($parent)

        placement = placement == 'bottom' && pos.top   + pos.height       + actualHeight - parentDim.scroll > parentDim.height ? 'top'    :
                    placement == 'top'    && pos.top   - parentDim.scroll - actualHeight < 0                                   ? 'bottom' :
                    placement == 'right'  && pos.right + actualWidth      > parentDim.width                                    ? 'left'   :
                    placement == 'left'   && pos.left  - actualWidth      < parentDim.left                                     ? 'right'  :
                    placement

        $tip
          .removeClass(orgPlacement)
          .addClass(placement)
      }

      var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
      this.applyPlacement(calculatedOffset, placement)

      var complete = function () {
        that.$element.trigger('shown.bs.' + that.type)
        that.hoverState = null
      }

      $.support.transition && this.$tip.hasClass('fade') ?
        $tip
          .one('bsTransitionEnd', complete)
          .emulateTransitionEnd(150) :
        complete()
    }
  }

  Tooltip.prototype.applyPlacement = function (offset, placement) {
    var $tip   = this.tip()
    var width  = $tip[0].offsetWidth
    var height = $tip[0].offsetHeight

    // manually read margins because getBoundingClientRect includes difference
    var marginTop = parseInt($tip.css('margin-top'), 10)
    var marginLeft = parseInt($tip.css('margin-left'), 10)

    // we must check for NaN for ie 8/9
    if (isNaN(marginTop))  marginTop  = 0
    if (isNaN(marginLeft)) marginLeft = 0

    offset.top  = offset.top  + marginTop
    offset.left = offset.left + marginLeft

    // $.fn.offset doesn't round pixel values
    // so we use setOffset directly with our own function B-0
    $.offset.setOffset($tip[0], $.extend({
      using: function (props) {
        $tip.css({
          top: Math.round(props.top),
          left: Math.round(props.left)
        })
      }
    }, offset), 0)

    $tip.addClass('in')

    // check to see if placing tip in new offset caused the tip to resize itself
    var actualWidth  = $tip[0].offsetWidth
    var actualHeight = $tip[0].offsetHeight

    if (placement == 'top' && actualHeight != height) {
      offset.top = offset.top + height - actualHeight
    }

    var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)
    if (delta.left) offset.left += delta.left
    //reworked implementation with removing delta top
    else offset.top += delta.top
    //else offset.top

    var arrowDelta          = delta.left ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
    var arrowPosition       = delta.left ? 'left'        : 'top'
    var arrowOffsetPosition = delta.left ? 'offsetWidth' : 'offsetHeight'
    $tip.offset(offset)
    this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], arrowPosition)
  }

  Tooltip.prototype.replaceArrow = function (delta, dimension, position) {
    //reworked arrow positioning implementation to fixed
    //this.arrow().css(position, delta ? (50 * (1 - delta / dimension) + '%') : '')
      this.arrow().css(position, '')
  }

  Tooltip.prototype.setContent = function () {
    var $tip  = this.tip()
    var title = this.getTitle()

    $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
    $tip.removeClass('fade in top bottom left right')
  }

  Tooltip.prototype.hide = function () {
    var that = this
    var $tip = this.tip()
    var e    = $.Event('hide.bs.' + this.type)

    this.$element.removeAttr('aria-describedby')

    function complete() {
      if (that.hoverState != 'in') $tip.detach()
      that.$element.trigger('hidden.bs.' + that.type)
    }

    this.$element.trigger(e)

    if (e.isDefaultPrevented()) return

    $tip.removeClass('in')

    $.support.transition && this.$tip.hasClass('fade') ?
      $tip
        .one('bsTransitionEnd', complete)
        .emulateTransitionEnd(150) :
      complete()

    this.hoverState = null

    return this
  }

  Tooltip.prototype.fixTitle = function () {
    var $e = this.$element
    if ($e.attr('title') || typeof ($e.attr('data-original-title')) != 'string') {
      $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
    }
  }

  Tooltip.prototype.hasContent = function () {
    return this.getTitle()
  }

  Tooltip.prototype.getPosition = function ($element) {
    $element   = $element || this.$element
    var el     = $element[0]
    var isBody = el.tagName == 'BODY'
    return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : null, {
      scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop(),
      width:  isBody ? $(window).width()  : $element.outerWidth(),
      height: isBody ? $(window).height() : $element.outerHeight()
    }, isBody ? { top: 0, left: 0 } : $element.offset())
  }

  Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
    return placement == 'bottom' ? { top: pos.top + pos.height,   left: pos.left + pos.width / 2 - actualWidth / 2  } :
           placement == 'top'    ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2  } :
           //reworked calculation of placements
           //placement == 'left'   ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
           placement == 'left'   ? { top: pos.top, left: pos.left - actualWidth } :
        /* placement == 'right' */ { top: pos.top, left: pos.left + pos.width   }

  }

  Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
    var delta = { top: 0, left: 0 }
    if (!this.$viewport) return delta

    var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
    var viewportDimensions = this.getPosition(this.$viewport)

    if (/right|left/.test(placement)) {
      var topEdgeOffset    = pos.top - viewportPadding - viewportDimensions.scroll
      var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
      if (topEdgeOffset < viewportDimensions.top) { // top overflow
        delta.top = viewportDimensions.top - topEdgeOffset
      } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
        delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
      }
    } else {
      var leftEdgeOffset  = pos.left - viewportPadding
      var rightEdgeOffset = pos.left + viewportPadding + actualWidth
      if (leftEdgeOffset < viewportDimensions.left) { // left overflow
        delta.left = viewportDimensions.left - leftEdgeOffset
      } else if (rightEdgeOffset > viewportDimensions.width) { // right overflow
        delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
      }
    }

    return delta
  }

  Tooltip.prototype.getTitle = function () {
    var title
    var $e = this.$element
    var o  = this.options

    title = $e.attr('data-original-title')
      || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)

    return title
  }

  Tooltip.prototype.getUID = function (prefix) {
    do prefix += ~~(Math.random() * 1000000)
    while (document.getElementById(prefix))
    return prefix
  }

  Tooltip.prototype.tip = function () {
    return (this.$tip = this.$tip || $(this.options.template))
  }

  Tooltip.prototype.arrow = function () {
    return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))
  }

  Tooltip.prototype.validate = function () {
    if (!this.$element[0].parentNode) {
      this.hide()
      this.$element = null
      this.options  = null
    }
  }

  Tooltip.prototype.enable = function () {
    this.enabled = true
  }

  Tooltip.prototype.disable = function () {
    this.enabled = false
  }

  Tooltip.prototype.toggleEnabled = function () {
    this.enabled = !this.enabled
  }

  Tooltip.prototype.toggle = function (e) {
    var self = this
    if (e) {
      self = $(e.currentTarget).data('bs.' + this.type)
      if (!self) {
        self = new this.constructor(e.currentTarget, this.getDelegateOptions())
        $(e.currentTarget).data('bs.' + this.type, self)
      }
    }

    self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
  }

  Tooltip.prototype.destroy = function () {
    clearTimeout(this.timeout)
    this.hide().$element.off('.' + this.type).removeData('bs.' + this.type)
  }


  // TOOLTIP PLUGIN DEFINITION
  // =========================

  function Plugin(option) {
    return this.each(function () {
      var $this   = $(this)
      var data    = $this.data('bs.tooltip')
      var options = typeof option == 'object' && option

      if (!data && option == 'destroy') return
      if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
      if (typeof option == 'string') data[option]()
    })
  }

  var old = $.fn.tooltip

  $.fn.tooltip             = Plugin
  $.fn.tooltip.Constructor = Tooltip


  // TOOLTIP NO CONFLICT
  // ===================

  $.fn.tooltip.noConflict = function () {
    $.fn.tooltip = old
    return this
  }

}(jQuery);

/* ========================================================================
 * Bootstrap: popover.js v3.2.0
 * http://getbootstrap.com/javascript/#popovers
 * ========================================================================
 * Copyright 2011-2014 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */


+function ($) {
  'use strict';

  // POPOVER PUBLIC CLASS DEFINITION
  // ===============================

  var Popover = function (element, options) {
    this.init('popover', element, options)
  }

  if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')

  Popover.VERSION  = '3.2.0'

  Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
    placement: 'right',
    trigger: 'click',
    content: '',
    template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
  })


  // NOTE: POPOVER EXTENDS tooltip.js
  // ================================

  Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)

  Popover.prototype.constructor = Popover

  Popover.prototype.getDefaults = function () {
    return Popover.DEFAULTS
  }

  Popover.prototype.setContent = function () {
    var $tip    = this.tip()
    var title   = this.getTitle()
    var content = this.getContent()

    $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
    $tip.find('.popover-content').empty()[ // we use append for html objects to maintain js events
      this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'
    ](content)

    $tip.removeClass('fade top bottom left right in')

    // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
    // this manually by checking the contents.
    if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
  }

  Popover.prototype.hasContent = function () {
    return this.getTitle() || this.getContent()
  }

  Popover.prototype.getContent = function () {
    var $e = this.$element
    var o  = this.options

    return $e.attr('data-content')
      || (typeof o.content == 'function' ?
            o.content.call($e[0]) :
            o.content)
  }

  Popover.prototype.arrow = function () {
    return (this.$arrow = this.$arrow || this.tip().find('.arrow'))
  }

  Popover.prototype.tip = function () {
    if (!this.$tip) this.$tip = $(this.options.template)
    return this.$tip
  }


  // POPOVER PLUGIN DEFINITION
  // =========================

  function Plugin(option) {
    return this.each(function () {
      var $this   = $(this)
      var data    = $this.data('bs.popover')
      var options = typeof option == 'object' && option

      if (!data && option == 'destroy') return
      if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
      if (typeof option == 'string') data[option]()
    })
  }

  var old = $.fn.popover

  $.fn.popover             = Plugin
  $.fn.popover.Constructor = Popover


  // POPOVER NO CONFLICT
  // ===================

  $.fn.popover.noConflict = function () {
    $.fn.popover = old
    return this
  }

}(jQuery);

/* ========================================================================
 * Bootstrap: transition.js v3.2.0
 * http://getbootstrap.com/javascript/#transitions
 * ========================================================================
 * Copyright 2011-2014 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */


+function ($) {
  'use strict';

  // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
  // ============================================================

  function transitionEnd() {
    var el = document.createElement('bootstrap')

    var transEndEventNames = {
      WebkitTransition : 'webkitTransitionEnd',
      MozTransition    : 'transitionend',
      OTransition      : 'oTransitionEnd otransitionend',
      transition       : 'transitionend'
    }

    for (var name in transEndEventNames) {
      if (el.style[name] !== undefined) {
        return { end: transEndEventNames[name] }
      }
    }

    return false // explicit for ie8 (  ._.)
  }

  // http://blog.alexmaccaw.com/css-transitions
  $.fn.emulateTransitionEnd = function (duration) {
    var called = false
    var $el = this
    $(this).one('bsTransitionEnd', function () { called = true })
    var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
    setTimeout(callback, duration)
    return this
  }

  $(function () {
    $.support.transition = transitionEnd()

    if (!$.support.transition) return

    $.event.special.bsTransitionEnd = {
      bindType: $.support.transition.end,
      delegateType: $.support.transition.end,
      handle: function (e) {
        if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)
      }
    }
  })

}(jQuery);
;
// TinyColor v1.4.1
// https://github.com/bgrins/TinyColor
// Brian Grinstead, MIT License

(function (Math) {

    var trimLeft = /^\s+/,
        trimRight = /\s+$/,
        tinyCounter = 0,
        mathRound = Math.round,
        mathMin = Math.min,
        mathMax = Math.max,
        mathRandom = Math.random;

    function tinycolor(color, opts) {

        color = (color) ? color : '';
        opts = opts || {};

        // If input is already a tinycolor, return itself
        if (color instanceof tinycolor) {
            return color;
        }
        // If we are called as a function, call using new instead
        if (!(this instanceof tinycolor)) {
            return new tinycolor(color, opts);
        }

        var rgb = inputToRGB(color);
        this._originalInput = color,
        this._r = rgb.r,
        this._g = rgb.g,
        this._b = rgb.b,
        this._a = rgb.a,
        this._roundA = mathRound(100 * this._a) / 100,
        this._format = opts.format || rgb.format;
        this._gradientType = opts.gradientType;

        // Don't let the range of [0,255] come back in [0,1].
        // Potentially lose a little bit of precision here, but will fix issues where
        // .5 gets interpreted as half of the total, instead of half of 1
        // If it was supposed to be 128, this was already taken care of by `inputToRgb`
        if (this._r < 1) { this._r = mathRound(this._r); }
        if (this._g < 1) { this._g = mathRound(this._g); }
        if (this._b < 1) { this._b = mathRound(this._b); }

        this._ok = rgb.ok;
        this._tc_id = tinyCounter++;
    }

    tinycolor.prototype = {
        isDark: function () {
            return this.getBrightness() < 128;
        },
        isLight: function () {
            return !this.isDark();
        },
        isValid: function () {
            return this._ok;
        },
        getOriginalInput: function () {
            return this._originalInput;
        },
        getFormat: function () {
            return this._format;
        },
        getAlpha: function () {
            return this._a;
        },
        getBrightness: function () {
            //http://www.w3.org/TR/AERT#color-contrast
            var rgb = this.toRgb();
            return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
        },
        getLuminance: function () {
            //http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
            var rgb = this.toRgb();
            var RsRGB, GsRGB, BsRGB, R, G, B;
            RsRGB = rgb.r / 255;
            GsRGB = rgb.g / 255;
            BsRGB = rgb.b / 255;

            if (RsRGB <= 0.03928) { R = RsRGB / 12.92; } else { R = Math.pow(((RsRGB + 0.055) / 1.055), 2.4); }
            if (GsRGB <= 0.03928) { G = GsRGB / 12.92; } else { G = Math.pow(((GsRGB + 0.055) / 1.055), 2.4); }
            if (BsRGB <= 0.03928) { B = BsRGB / 12.92; } else { B = Math.pow(((BsRGB + 0.055) / 1.055), 2.4); }
            return (0.2126 * R) + (0.7152 * G) + (0.0722 * B);
        },
        setAlpha: function (value) {
            this._a = boundAlpha(value);
            this._roundA = mathRound(100 * this._a) / 100;
            return this;
        },
        toHsv: function () {
            var hsv = rgbToHsv(this._r, this._g, this._b);
            return { h: hsv.h * 360, s: hsv.s, v: hsv.v, a: this._a };
        },
        toHsvString: function () {
            var hsv = rgbToHsv(this._r, this._g, this._b);
            var h = mathRound(hsv.h * 360), s = mathRound(hsv.s * 100), v = mathRound(hsv.v * 100);
            return (this._a == 1) ?
              "hsv(" + h + ", " + s + "%, " + v + "%)" :
              "hsva(" + h + ", " + s + "%, " + v + "%, " + this._roundA + ")";
        },
        toHsl: function () {
            var hsl = rgbToHsl(this._r, this._g, this._b);
            return { h: hsl.h * 360, s: hsl.s, l: hsl.l, a: this._a };
        },
        toHslString: function () {
            var hsl = rgbToHsl(this._r, this._g, this._b);
            var h = mathRound(hsl.h * 360), s = mathRound(hsl.s * 100), l = mathRound(hsl.l * 100);
            return (this._a == 1) ?
              "hsl(" + h + ", " + s + "%, " + l + "%)" :
              "hsla(" + h + ", " + s + "%, " + l + "%, " + this._roundA + ")";
        },
        toHex: function (allow3Char) {
            return rgbToHex(this._r, this._g, this._b, allow3Char);
        },
        toHexString: function (allow3Char) {
            return '#' + this.toHex(allow3Char);
        },
        toHex8: function (allow4Char) {
            return rgbaToHex(this._r, this._g, this._b, this._a, allow4Char);
        },
        toHex8String: function (allow4Char) {
            return '#' + this.toHex8(allow4Char);
        },
        toRgb: function () {
            return { r: mathRound(this._r), g: mathRound(this._g), b: mathRound(this._b), a: this._a };
        },
        toRgbString: function () {
            return (this._a == 1) ?
              "rgb(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ")" :
              "rgba(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ", " + this._roundA + ")";
        },
        toPercentageRgb: function () {
            return { r: mathRound(bound01(this._r, 255) * 100) + "%", g: mathRound(bound01(this._g, 255) * 100) + "%", b: mathRound(bound01(this._b, 255) * 100) + "%", a: this._a };
        },
        toPercentageRgbString: function () {
            return (this._a == 1) ?
              "rgb(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%)" :
              "rgba(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%, " + this._roundA + ")";
        },
        toName: function () {
            if (this._a === 0) {
                return "transparent";
            }

            if (this._a < 1) {
                return false;
            }

            return hexNames[rgbToHex(this._r, this._g, this._b, true)] || false;
        },
        toFilter: function (secondColor) {
            var hex8String = '#' + rgbaToArgbHex(this._r, this._g, this._b, this._a);
            var secondHex8String = hex8String;
            var gradientType = this._gradientType ? "GradientType = 1, " : "";

            if (secondColor) {
                var s = tinycolor(secondColor);
                secondHex8String = '#' + rgbaToArgbHex(s._r, s._g, s._b, s._a);
            }

            return "progid:DXImageTransform.Microsoft.gradient(" + gradientType + "startColorstr=" + hex8String + ",endColorstr=" + secondHex8String + ")";
        },
        toString: function (format) {
            var formatSet = !!format;
            format = format || this._format;

            var formattedString = false;
            var hasAlpha = this._a < 1 && this._a >= 0;
            var needsAlphaFormat = !formatSet && hasAlpha && (format === "hex" || format === "hex6" || format === "hex3" || format === "hex4" || format === "hex8" || format === "name");

            if (needsAlphaFormat) {
                // Special case for "transparent", all other non-alpha formats
                // will return rgba when there is transparency.
                if (format === "name" && this._a === 0) {
                    return this.toName();
                }
                return this.toRgbString();
            }
            if (format === "rgb") {
                formattedString = this.toRgbString();
            }
            if (format === "prgb") {
                formattedString = this.toPercentageRgbString();
            }
            if (format === "hex" || format === "hex6") {
                formattedString = this.toHexString();
            }
            if (format === "hex3") {
                formattedString = this.toHexString(true);
            }
            if (format === "hex4") {
                formattedString = this.toHex8String(true);
            }
            if (format === "hex8") {
                formattedString = this.toHex8String();
            }
            if (format === "name") {
                formattedString = this.toName();
            }
            if (format === "hsl") {
                formattedString = this.toHslString();
            }
            if (format === "hsv") {
                formattedString = this.toHsvString();
            }

            return formattedString || this.toHexString();
        },
        clone: function () {
            return tinycolor(this.toString());
        },

        _applyModification: function (fn, args) {
            var color = fn.apply(null, [this].concat([].slice.call(args)));
            this._r = color._r;
            this._g = color._g;
            this._b = color._b;
            this.setAlpha(color._a);
            return this;
        },
        lighten: function () {
            return this._applyModification(lighten, arguments);
        },
        brighten: function () {
            return this._applyModification(brighten, arguments);
        },
        darken: function () {
            return this._applyModification(darken, arguments);
        },
        desaturate: function () {
            return this._applyModification(desaturate, arguments);
        },
        saturate: function () {
            return this._applyModification(saturate, arguments);
        },
        greyscale: function () {
            return this._applyModification(greyscale, arguments);
        },
        spin: function () {
            return this._applyModification(spin, arguments);
        },

        _applyCombination: function (fn, args) {
            return fn.apply(null, [this].concat([].slice.call(args)));
        },
        analogous: function () {
            return this._applyCombination(analogous, arguments);
        },
        complement: function () {
            return this._applyCombination(complement, arguments);
        },
        monochromatic: function () {
            return this._applyCombination(monochromatic, arguments);
        },
        splitcomplement: function () {
            return this._applyCombination(splitcomplement, arguments);
        },
        triad: function () {
            return this._applyCombination(triad, arguments);
        },
        tetrad: function () {
            return this._applyCombination(tetrad, arguments);
        }
    };

    // If input is an object, force 1 into "1.0" to handle ratios properly
    // String input requires "1.0" as input, so 1 will be treated as 1
    tinycolor.fromRatio = function (color, opts) {
        if (typeof color == "object") {
            var newColor = {};
            for (var i in color) {
                if (color.hasOwnProperty(i)) {
                    if (i === "a") {
                        newColor[i] = color[i];
                    }
                    else {
                        newColor[i] = convertToPercentage(color[i]);
                    }
                }
            }
            color = newColor;
        }

        return tinycolor(color, opts);
    };

    // Given a string or object, convert that input to RGB
    // Possible string inputs:
    //
    //     "red"
    //     "#f00" or "f00"
    //     "#ff0000" or "ff0000"
    //     "#ff000000" or "ff000000"
    //     "rgb 255 0 0" or "rgb (255, 0, 0)"
    //     "rgb 1.0 0 0" or "rgb (1, 0, 0)"
    //     "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1"
    //     "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1"
    //     "hsl(0, 100%, 50%)" or "hsl 0 100% 50%"
    //     "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1"
    //     "hsv(0, 100%, 100%)" or "hsv 0 100% 100%"
    //
    function inputToRGB(color) {

        var rgb = { r: 0, g: 0, b: 0 };
        var a = 1;
        var s = null;
        var v = null;
        var l = null;
        var ok = false;
        var format = false;

        if (typeof color == "string") {
            color = stringInputToObject(color);
        }

        if (typeof color == "object") {
            if (isValidCSSUnit(color.r) && isValidCSSUnit(color.g) && isValidCSSUnit(color.b)) {
                rgb = rgbToRgb(color.r, color.g, color.b);
                ok = true;
                format = String(color.r).substr(-1) === "%" ? "prgb" : "rgb";
            }
            else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.v)) {
                s = convertToPercentage(color.s);
                v = convertToPercentage(color.v);
                rgb = hsvToRgb(color.h, s, v);
                ok = true;
                format = "hsv";
            }
            else if (isValidCSSUnit(color.h) && isValidCSSUnit(color.s) && isValidCSSUnit(color.l)) {
                s = convertToPercentage(color.s);
                l = convertToPercentage(color.l);
                rgb = hslToRgb(color.h, s, l);
                ok = true;
                format = "hsl";
            }

            if (color.hasOwnProperty("a")) {
                a = color.a;
            }
        }

        a = boundAlpha(a);

        return {
            ok: ok,
            format: color.format || format,
            r: mathMin(255, mathMax(rgb.r, 0)),
            g: mathMin(255, mathMax(rgb.g, 0)),
            b: mathMin(255, mathMax(rgb.b, 0)),
            a: a
        };
    }


    // Conversion Functions
    // --------------------

    // `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from:
    // <http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript>

    // `rgbToRgb`
    // Handle bounds / percentage checking to conform to CSS color spec
    // <http://www.w3.org/TR/css3-color/>
    // *Assumes:* r, g, b in [0, 255] or [0, 1]
    // *Returns:* { r, g, b } in [0, 255]
    function rgbToRgb(r, g, b) {
        return {
            r: bound01(r, 255) * 255,
            g: bound01(g, 255) * 255,
            b: bound01(b, 255) * 255
        };
    }

    // `rgbToHsl`
    // Converts an RGB color value to HSL.
    // *Assumes:* r, g, and b are contained in [0, 255] or [0, 1]
    // *Returns:* { h, s, l } in [0,1]
    function rgbToHsl(r, g, b) {

        r = bound01(r, 255);
        g = bound01(g, 255);
        b = bound01(b, 255);

        var max = mathMax(r, g, b), min = mathMin(r, g, b);
        var h, s, l = (max + min) / 2;

        if (max == min) {
            h = s = 0; // achromatic
        }
        else {
            var d = max - min;
            s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
            switch (max) {
                case r: h = (g - b) / d + (g < b ? 6 : 0); break;
                case g: h = (b - r) / d + 2; break;
                case b: h = (r - g) / d + 4; break;
            }

            h /= 6;
        }

        return { h: h, s: s, l: l };
    }

    // `hslToRgb`
    // Converts an HSL color value to RGB.
    // *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100]
    // *Returns:* { r, g, b } in the set [0, 255]
    function hslToRgb(h, s, l) {
        var r, g, b;

        h = bound01(h, 360);
        s = bound01(s, 100);
        l = bound01(l, 100);

        function hue2rgb(p, q, t) {
            if (t < 0) t += 1;
            if (t > 1) t -= 1;
            if (t < 1 / 6) return p + (q - p) * 6 * t;
            if (t < 1 / 2) return q;
            if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
            return p;
        }

        if (s === 0) {
            r = g = b = l; // achromatic
        }
        else {
            var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
            var p = 2 * l - q;
            r = hue2rgb(p, q, h + 1 / 3);
            g = hue2rgb(p, q, h);
            b = hue2rgb(p, q, h - 1 / 3);
        }

        return { r: r * 255, g: g * 255, b: b * 255 };
    }

    // `rgbToHsv`
    // Converts an RGB color value to HSV
    // *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1]
    // *Returns:* { h, s, v } in [0,1]
    function rgbToHsv(r, g, b) {

        r = bound01(r, 255);
        g = bound01(g, 255);
        b = bound01(b, 255);

        var max = mathMax(r, g, b), min = mathMin(r, g, b);
        var h, s, v = max;

        var d = max - min;
        s = max === 0 ? 0 : d / max;

        if (max == min) {
            h = 0; // achromatic
        }
        else {
            switch (max) {
                case r: h = (g - b) / d + (g < b ? 6 : 0); break;
                case g: h = (b - r) / d + 2; break;
                case b: h = (r - g) / d + 4; break;
            }
            h /= 6;
        }
        return { h: h, s: s, v: v };
    }

    // `hsvToRgb`
    // Converts an HSV color value to RGB.
    // *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100]
    // *Returns:* { r, g, b } in the set [0, 255]
    function hsvToRgb(h, s, v) {

        h = bound01(h, 360) * 6;
        s = bound01(s, 100);
        v = bound01(v, 100);

        var i = Math.floor(h),
            f = h - i,
            p = v * (1 - s),
            q = v * (1 - f * s),
            t = v * (1 - (1 - f) * s),
            mod = i % 6,
            r = [v, q, p, p, t, v][mod],
            g = [t, v, v, q, p, p][mod],
            b = [p, p, t, v, v, q][mod];

        return { r: r * 255, g: g * 255, b: b * 255 };
    }

    // `rgbToHex`
    // Converts an RGB color to hex
    // Assumes r, g, and b are contained in the set [0, 255]
    // Returns a 3 or 6 character hex
    function rgbToHex(r, g, b, allow3Char) {

        var hex = [
            pad2(mathRound(r).toString(16)),
            pad2(mathRound(g).toString(16)),
            pad2(mathRound(b).toString(16))
        ];

        // Return a 3 character hex if possible
        if (allow3Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1)) {
            return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
        }

        return hex.join("");
    }

    // `rgbaToHex`
    // Converts an RGBA color plus alpha transparency to hex
    // Assumes r, g, b are contained in the set [0, 255] and
    // a in [0, 1]. Returns a 4 or 8 character rgba hex
    function rgbaToHex(r, g, b, a, allow4Char) {

        var hex = [
            pad2(mathRound(r).toString(16)),
            pad2(mathRound(g).toString(16)),
            pad2(mathRound(b).toString(16)),
            pad2(convertDecimalToHex(a))
        ];

        // Return a 4 character hex if possible
        if (allow4Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1) && hex[3].charAt(0) == hex[3].charAt(1)) {
            return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0) + hex[3].charAt(0);
        }

        return hex.join("");
    }

    // `rgbaToArgbHex`
    // Converts an RGBA color to an ARGB Hex8 string
    // Rarely used, but required for "toFilter()"
    function rgbaToArgbHex(r, g, b, a) {

        var hex = [
            pad2(convertDecimalToHex(a)),
            pad2(mathRound(r).toString(16)),
            pad2(mathRound(g).toString(16)),
            pad2(mathRound(b).toString(16))
        ];

        return hex.join("");
    }

    // `equals`
    // Can be called with any tinycolor input
    tinycolor.equals = function (color1, color2) {
        if (!color1 || !color2) { return false; }
        return tinycolor(color1).toRgbString() == tinycolor(color2).toRgbString();
    };

    tinycolor.random = function () {
        return tinycolor.fromRatio({
            r: mathRandom(),
            g: mathRandom(),
            b: mathRandom()
        });
    };


    // Modification Functions
    // ----------------------
    // Thanks to less.js for some of the basics here
    // <https://github.com/cloudhead/less.js/blob/master/lib/less/functions.js>

    function desaturate(color, amount) {
        amount = (amount === 0) ? 0 : (amount || 10);
        var hsl = tinycolor(color).toHsl();
        hsl.s -= amount / 100;
        hsl.s = clamp01(hsl.s);
        return tinycolor(hsl);
    }

    function saturate(color, amount) {
        amount = (amount === 0) ? 0 : (amount || 10);
        var hsl = tinycolor(color).toHsl();
        hsl.s += amount / 100;
        hsl.s = clamp01(hsl.s);
        return tinycolor(hsl);
    }

    function greyscale(color) {
        return tinycolor(color).desaturate(100);
    }

    function lighten(color, amount) {
        amount = (amount === 0) ? 0 : (amount || 10);
        var hsl = tinycolor(color).toHsl();
        hsl.l += amount / 100;
        hsl.l = clamp01(hsl.l);
        return tinycolor(hsl);
    }

    function brighten(color, amount) {
        amount = (amount === 0) ? 0 : (amount || 10);
        var rgb = tinycolor(color).toRgb();
        rgb.r = mathMax(0, mathMin(255, rgb.r - mathRound(255 * -(amount / 100))));
        rgb.g = mathMax(0, mathMin(255, rgb.g - mathRound(255 * -(amount / 100))));
        rgb.b = mathMax(0, mathMin(255, rgb.b - mathRound(255 * -(amount / 100))));
        return tinycolor(rgb);
    }

    function darken(color, amount) {
        amount = (amount === 0) ? 0 : (amount || 10);
        var hsl = tinycolor(color).toHsl();
        hsl.l -= amount / 100;
        hsl.l = clamp01(hsl.l);
        return tinycolor(hsl);
    }

    // Spin takes a positive or negative amount within [-360, 360] indicating the change of hue.
    // Values outside of this range will be wrapped into this range.
    function spin(color, amount) {
        var hsl = tinycolor(color).toHsl();
        var hue = (hsl.h + amount) % 360;
        hsl.h = hue < 0 ? 360 + hue : hue;
        return tinycolor(hsl);
    }

    // Combination Functions
    // ---------------------
    // Thanks to jQuery xColor for some of the ideas behind these
    // <https://github.com/infusion/jQuery-xcolor/blob/master/jquery.xcolor.js>

    function complement(color) {
        var hsl = tinycolor(color).toHsl();
        hsl.h = (hsl.h + 180) % 360;
        return tinycolor(hsl);
    }

    function triad(color) {
        var hsl = tinycolor(color).toHsl();
        var h = hsl.h;
        return [
            tinycolor(color),
            tinycolor({ h: (h + 120) % 360, s: hsl.s, l: hsl.l }),
            tinycolor({ h: (h + 240) % 360, s: hsl.s, l: hsl.l })
        ];
    }

    function tetrad(color) {
        var hsl = tinycolor(color).toHsl();
        var h = hsl.h;
        return [
            tinycolor(color),
            tinycolor({ h: (h + 90) % 360, s: hsl.s, l: hsl.l }),
            tinycolor({ h: (h + 180) % 360, s: hsl.s, l: hsl.l }),
            tinycolor({ h: (h + 270) % 360, s: hsl.s, l: hsl.l })
        ];
    }

    function splitcomplement(color) {
        var hsl = tinycolor(color).toHsl();
        var h = hsl.h;
        return [
            tinycolor(color),
            tinycolor({ h: (h + 72) % 360, s: hsl.s, l: hsl.l }),
            tinycolor({ h: (h + 216) % 360, s: hsl.s, l: hsl.l })
        ];
    }

    function analogous(color, results, slices) {
        results = results || 6;
        slices = slices || 30;

        var hsl = tinycolor(color).toHsl();
        var part = 360 / slices;
        var ret = [tinycolor(color)];

        for (hsl.h = ((hsl.h - (part * results >> 1)) + 720) % 360; --results;) {
            hsl.h = (hsl.h + part) % 360;
            ret.push(tinycolor(hsl));
        }
        return ret;
    }

    function monochromatic(color, results) {
        results = results || 6;
        var hsv = tinycolor(color).toHsv();
        var h = hsv.h, s = hsv.s, v = hsv.v;
        var ret = [];
        var modification = 1 / results;

        while (results--) {
            ret.push(tinycolor({ h: h, s: s, v: v }));
            v = (v + modification) % 1;
        }

        return ret;
    }

    // Utility Functions
    // ---------------------

    tinycolor.mix = function (color1, color2, amount) {
        amount = (amount === 0) ? 0 : (amount || 50);

        var rgb1 = tinycolor(color1).toRgb();
        var rgb2 = tinycolor(color2).toRgb();

        var p = amount / 100;

        var rgba = {
            r: ((rgb2.r - rgb1.r) * p) + rgb1.r,
            g: ((rgb2.g - rgb1.g) * p) + rgb1.g,
            b: ((rgb2.b - rgb1.b) * p) + rgb1.b,
            a: ((rgb2.a - rgb1.a) * p) + rgb1.a
        };

        return tinycolor(rgba);
    };


    // Readability Functions
    // ---------------------
    // <http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef (WCAG Version 2)

    // `contrast`
    // Analyze the 2 colors and returns the color contrast defined by (WCAG Version 2)
    tinycolor.readability = function (color1, color2) {
        var c1 = tinycolor(color1);
        var c2 = tinycolor(color2);
        return (Math.max(c1.getLuminance(), c2.getLuminance()) + 0.05) / (Math.min(c1.getLuminance(), c2.getLuminance()) + 0.05);
    };

    // `isReadable`
    // Ensure that foreground and background color combinations meet WCAG2 guidelines.
    // The third argument is an optional Object.
    //      the 'level' property states 'AA' or 'AAA' - if missing or invalid, it defaults to 'AA';
    //      the 'size' property states 'large' or 'small' - if missing or invalid, it defaults to 'small'.
    // If the entire object is absent, isReadable defaults to {level:"AA",size:"small"}.

    // *Example*
    //    tinycolor.isReadable("#000", "#111") => false
    //    tinycolor.isReadable("#000", "#111",{level:"AA",size:"large"}) => false
    tinycolor.isReadable = function (color1, color2, wcag2) {
        var readability = tinycolor.readability(color1, color2);
        var wcag2Parms, out;

        out = false;

        wcag2Parms = validateWCAG2Parms(wcag2);
        switch (wcag2Parms.level + wcag2Parms.size) {
            case "AAsmall":
            case "AAAlarge":
                out = readability >= 4.5;
                break;
            case "AAlarge":
                out = readability >= 3;
                break;
            case "AAAsmall":
                out = readability >= 7;
                break;
        }
        return out;

    };

    // `mostReadable`
    // Given a base color and a list of possible foreground or background
    // colors for that base, returns the most readable color.
    // Optionally returns Black or White if the most readable color is unreadable.
    // *Example*
    //    tinycolor.mostReadable(tinycolor.mostReadable("#123", ["#124", "#125"],{includeFallbackColors:false}).toHexString(); // "#112255"
    //    tinycolor.mostReadable(tinycolor.mostReadable("#123", ["#124", "#125"],{includeFallbackColors:true}).toHexString();  // "#ffffff"
    //    tinycolor.mostReadable("#a8015a", ["#faf3f3"],{includeFallbackColors:true,level:"AAA",size:"large"}).toHexString(); // "#faf3f3"
    //    tinycolor.mostReadable("#a8015a", ["#faf3f3"],{includeFallbackColors:true,level:"AAA",size:"small"}).toHexString(); // "#ffffff"
    tinycolor.mostReadable = function (baseColor, colorList, args) {
        var bestColor = null;
        var bestScore = 0;
        var readability;
        var includeFallbackColors, level, size;
        args = args || {};
        includeFallbackColors = args.includeFallbackColors;
        level = args.level;
        size = args.size;

        for (var i = 0; i < colorList.length ; i++) {
            readability = tinycolor.readability(baseColor, colorList[i]);
            if (readability > bestScore) {
                bestScore = readability;
                bestColor = tinycolor(colorList[i]);
            }
        }

        if (tinycolor.isReadable(baseColor, bestColor, { "level": level, "size": size }) || !includeFallbackColors) {
            return bestColor;
        }
        else {
            args.includeFallbackColors = false;
            return tinycolor.mostReadable(baseColor, ["#fff", "#000"], args);
        }
    };


    // Big List of Colors
    // ------------------
    // <http://www.w3.org/TR/css3-color/#svg-color>
    var names = tinycolor.names = {
        aliceblue: "f0f8ff",
        antiquewhite: "faebd7",
        aqua: "0ff",
        aquamarine: "7fffd4",
        azure: "f0ffff",
        beige: "f5f5dc",
        bisque: "ffe4c4",
        black: "000",
        blanchedalmond: "ffebcd",
        blue: "00f",
        blueviolet: "8a2be2",
        brown: "a52a2a",
        burlywood: "deb887",
        burntsienna: "ea7e5d",
        cadetblue: "5f9ea0",
        chartreuse: "7fff00",
        chocolate: "d2691e",
        coral: "ff7f50",
        cornflowerblue: "6495ed",
        cornsilk: "fff8dc",
        crimson: "dc143c",
        cyan: "0ff",
        darkblue: "00008b",
        darkcyan: "008b8b",
        darkgoldenrod: "b8860b",
        darkgray: "a9a9a9",
        darkgreen: "006400",
        darkgrey: "a9a9a9",
        darkkhaki: "bdb76b",
        darkmagenta: "8b008b",
        darkolivegreen: "556b2f",
        darkorange: "ff8c00",
        darkorchid: "9932cc",
        darkred: "8b0000",
        darksalmon: "e9967a",
        darkseagreen: "8fbc8f",
        darkslateblue: "483d8b",
        darkslategray: "2f4f4f",
        darkslategrey: "2f4f4f",
        darkturquoise: "00ced1",
        darkviolet: "9400d3",
        deeppink: "ff1493",
        deepskyblue: "00bfff",
        dimgray: "696969",
        dimgrey: "696969",
        dodgerblue: "1e90ff",
        firebrick: "b22222",
        floralwhite: "fffaf0",
        forestgreen: "228b22",
        fuchsia: "f0f",
        gainsboro: "dcdcdc",
        ghostwhite: "f8f8ff",
        gold: "ffd700",
        goldenrod: "daa520",
        gray: "808080",
        green: "008000",
        greenyellow: "adff2f",
        grey: "808080",
        honeydew: "f0fff0",
        hotpink: "ff69b4",
        indianred: "cd5c5c",
        indigo: "4b0082",
        ivory: "fffff0",
        khaki: "f0e68c",
        lavender: "e6e6fa",
        lavenderblush: "fff0f5",
        lawngreen: "7cfc00",
        lemonchiffon: "fffacd",
        lightblue: "add8e6",
        lightcoral: "f08080",
        lightcyan: "e0ffff",
        lightgoldenrodyellow: "fafad2",
        lightgray: "d3d3d3",
        lightgreen: "90ee90",
        lightgrey: "d3d3d3",
        lightpink: "ffb6c1",
        lightsalmon: "ffa07a",
        lightseagreen: "20b2aa",
        lightskyblue: "87cefa",
        lightslategray: "789",
        lightslategrey: "789",
        lightsteelblue: "b0c4de",
        lightyellow: "ffffe0",
        lime: "0f0",
        limegreen: "32cd32",
        linen: "faf0e6",
        magenta: "f0f",
        maroon: "800000",
        mediumaquamarine: "66cdaa",
        mediumblue: "0000cd",
        mediumorchid: "ba55d3",
        mediumpurple: "9370db",
        mediumseagreen: "3cb371",
        mediumslateblue: "7b68ee",
        mediumspringgreen: "00fa9a",
        mediumturquoise: "48d1cc",
        mediumvioletred: "c71585",
        midnightblue: "191970",
        mintcream: "f5fffa",
        mistyrose: "ffe4e1",
        moccasin: "ffe4b5",
        navajowhite: "ffdead",
        navy: "000080",
        oldlace: "fdf5e6",
        olive: "808000",
        olivedrab: "6b8e23",
        orange: "ffa500",
        orangered: "ff4500",
        orchid: "da70d6",
        palegoldenrod: "eee8aa",
        palegreen: "98fb98",
        paleturquoise: "afeeee",
        palevioletred: "db7093",
        papayawhip: "ffefd5",
        peachpuff: "ffdab9",
        peru: "cd853f",
        pink: "ffc0cb",
        plum: "dda0dd",
        powderblue: "b0e0e6",
        purple: "800080",
        rebeccapurple: "663399",
        red: "f00",
        rosybrown: "bc8f8f",
        royalblue: "4169e1",
        saddlebrown: "8b4513",
        salmon: "fa8072",
        sandybrown: "f4a460",
        seagreen: "2e8b57",
        seashell: "fff5ee",
        sienna: "a0522d",
        silver: "c0c0c0",
        skyblue: "87ceeb",
        slateblue: "6a5acd",
        slategray: "708090",
        slategrey: "708090",
        snow: "fffafa",
        springgreen: "00ff7f",
        steelblue: "4682b4",
        tan: "d2b48c",
        teal: "008080",
        thistle: "d8bfd8",
        tomato: "ff6347",
        turquoise: "40e0d0",
        violet: "ee82ee",
        wheat: "f5deb3",
        white: "fff",
        whitesmoke: "f5f5f5",
        yellow: "ff0",
        yellowgreen: "9acd32"
    };

    // Make it easy to access colors via `hexNames[hex]`
    var hexNames = tinycolor.hexNames = flip(names);


    // Utilities
    // ---------

    // `{ 'name1': 'val1' }` becomes `{ 'val1': 'name1' }`
    function flip(o) {
        var flipped = {};
        for (var i in o) {
            if (o.hasOwnProperty(i)) {
                flipped[o[i]] = i;
            }
        }
        return flipped;
    }

    // Return a valid alpha value [0,1] with all invalid values being set to 1
    function boundAlpha(a) {
        a = parseFloat(a);

        if (isNaN(a) || a < 0 || a > 1) {
            a = 1;
        }

        return a;
    }

    // Take input from [0, n] and return it as [0, 1]
    function bound01(n, max) {
        if (isOnePointZero(n)) { n = "100%"; }

        var processPercent = isPercentage(n);
        n = mathMin(max, mathMax(0, parseFloat(n)));

        // Automatically convert percentage into number
        if (processPercent) {
            n = parseInt(n * max, 10) / 100;
        }

        // Handle floating point rounding errors
        if ((Math.abs(n - max) < 0.000001)) {
            return 1;
        }

        // Convert into [0, 1] range if it isn't already
        return (n % max) / parseFloat(max);
    }

    // Force a number between 0 and 1
    function clamp01(val) {
        return mathMin(1, mathMax(0, val));
    }

    // Parse a base-16 hex value into a base-10 integer
    function parseIntFromHex(val) {
        return parseInt(val, 16);
    }

    // Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
    // <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
    function isOnePointZero(n) {
        return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1;
    }

    // Check to see if string passed in is a percentage
    function isPercentage(n) {
        return typeof n === "string" && n.indexOf('%') != -1;
    }

    // Force a hex value to have 2 characters
    function pad2(c) {
        return c.length == 1 ? '0' + c : '' + c;
    }

    // Replace a decimal with it's percentage value
    function convertToPercentage(n) {
        if (n <= 1) {
            n = (n * 100) + "%";
        }

        return n;
    }

    // Converts a decimal to a hex value
    function convertDecimalToHex(d) {
        return Math.round(parseFloat(d) * 255).toString(16);
    }
    // Converts a hex value to a decimal
    function convertHexToDecimal(h) {
        return (parseIntFromHex(h) / 255);
    }

    var matchers = (function () {

        // <http://www.w3.org/TR/css3-values/#integers>
        var CSS_INTEGER = "[-\\+]?\\d+%?";

        // <http://www.w3.org/TR/css3-values/#number-value>
        var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?";

        // Allow positive/negative integer/number.  Don't capture the either/or, just the entire outcome.
        var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")";

        // Actual matching.
        // Parentheses and commas are optional, but not required.
        // Whitespace can take the place of commas or opening paren
        var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
        var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";

        return {
            CSS_UNIT: new RegExp(CSS_UNIT),
            rgb: new RegExp("rgb" + PERMISSIVE_MATCH3),
            rgba: new RegExp("rgba" + PERMISSIVE_MATCH4),
            hsl: new RegExp("hsl" + PERMISSIVE_MATCH3),
            hsla: new RegExp("hsla" + PERMISSIVE_MATCH4),
            hsv: new RegExp("hsv" + PERMISSIVE_MATCH3),
            hsva: new RegExp("hsva" + PERMISSIVE_MATCH4),
            hex3: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
            hex6: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
            hex4: /^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
            hex8: /^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/
        };
    })();

    // `isValidCSSUnit`
    // Take in a single string / number and check to see if it looks like a CSS unit
    // (see `matchers` above for definition).
    function isValidCSSUnit(color) {
        return !!matchers.CSS_UNIT.exec(color);
    }

    // `stringInputToObject`
    // Permissive string parsing.  Take in a number of formats, and output an object
    // based on detected format.  Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}`
    function stringInputToObject(color) {

        color = color.replace(trimLeft, '').replace(trimRight, '').toLowerCase();
        var named = false;
        if (names[color]) {
            color = names[color];
            named = true;
        }
        else if (color == 'transparent') {
            return { r: 0, g: 0, b: 0, a: 0, format: "name" };
        }

        // Try to match string input using regular expressions.
        // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360]
        // Just return an object and let the conversion functions handle that.
        // This way the result will be the same whether the tinycolor is initialized with string or object.
        var match;
        if ((match = matchers.rgb.exec(color))) {
            return { r: match[1], g: match[2], b: match[3] };
        }
        if ((match = matchers.rgba.exec(color))) {
            return { r: match[1], g: match[2], b: match[3], a: match[4] };
        }
        if ((match = matchers.hsl.exec(color))) {
            return { h: match[1], s: match[2], l: match[3] };
        }
        if ((match = matchers.hsla.exec(color))) {
            return { h: match[1], s: match[2], l: match[3], a: match[4] };
        }
        if ((match = matchers.hsv.exec(color))) {
            return { h: match[1], s: match[2], v: match[3] };
        }
        if ((match = matchers.hsva.exec(color))) {
            return { h: match[1], s: match[2], v: match[3], a: match[4] };
        }
        if ((match = matchers.hex8.exec(color))) {
            return {
                r: parseIntFromHex(match[1]),
                g: parseIntFromHex(match[2]),
                b: parseIntFromHex(match[3]),
                a: convertHexToDecimal(match[4]),
                format: named ? "name" : "hex8"
            };
        }
        if ((match = matchers.hex6.exec(color))) {
            return {
                r: parseIntFromHex(match[1]),
                g: parseIntFromHex(match[2]),
                b: parseIntFromHex(match[3]),
                format: named ? "name" : "hex"
            };
        }
        if ((match = matchers.hex4.exec(color))) {
            return {
                r: parseIntFromHex(match[1] + '' + match[1]),
                g: parseIntFromHex(match[2] + '' + match[2]),
                b: parseIntFromHex(match[3] + '' + match[3]),
                a: convertHexToDecimal(match[4] + '' + match[4]),
                format: named ? "name" : "hex8"
            };
        }
        if ((match = matchers.hex3.exec(color))) {
            return {
                r: parseIntFromHex(match[1] + '' + match[1]),
                g: parseIntFromHex(match[2] + '' + match[2]),
                b: parseIntFromHex(match[3] + '' + match[3]),
                format: named ? "name" : "hex"
            };
        }

        return false;
    }

    function validateWCAG2Parms(parms) {
        // return valid WCAG2 parms for isReadable.
        // If input parms are invalid, return {"level":"AA", "size":"small"}
        var level, size;
        parms = parms || { "level": "AA", "size": "small" };
        level = (parms.level || "AA").toUpperCase();
        size = (parms.size || "small").toLowerCase();
        if (level !== "AA" && level !== "AAA") {
            level = "AA";
        }
        if (size !== "small" && size !== "large") {
            size = "small";
        }
        return { "level": level, "size": size };
    }

    // Node: Export function
    if (typeof module !== "undefined" && module.exports) {
        module.exports = tinycolor;
    }
        // AMD/requirejs: Define the module
    else if (typeof define === 'function' && define.amd) {
        define(function () { return tinycolor; });
    }
        // Browser: Expose to window
    else {
        window.tinycolor = tinycolor;
    }

})(Math);
;
// Spectrum Colorpicker v1.8.0
// https://github.com/bgrins/spectrum
// Author: Brian Grinstead
// License: MIT

(function (factory) {
    "use strict";

    if (typeof define === 'function' && define.amd) { // AMD
        define(['jquery'], factory);
    }
    else if (typeof exports == "object" && typeof module == "object") { // CommonJS
        module.exports = factory(require('jquery'));
    }
    else { // Browser
        factory(jQuery);
    }
})(function ($, undefined) {
    "use strict";

    var defaultOpts = {

        // Callbacks
        beforeShow: noop,
        move: noop,
        change: noop,
        show: noop,
        hide: noop,

        // Options
        color: false,
        flat: false,
        showInput: false,
        allowEmpty: false,
        showButtons: true,
        clickoutFiresChange: true,
        showInitial: false,
        showPalette: false,
        showPaletteOnly: false,
        hideAfterPaletteSelect: false,
        togglePaletteOnly: false,
        showSelectionPalette: true,
        localStorageKey: false,
        appendTo: "body",
        maxSelectionSize: 7,
        cancelText: "cancel",
        chooseText: "choose",
        togglePaletteMoreText: "more",
        togglePaletteLessText: "less",
        clearText: "Clear Color Selection",
        noColorSelectedText: "No Color Selected",
        preferredFormat: false,
        className: "", // Deprecated - use containerClassName and replacerClassName instead.
        containerClassName: "",
        replacerClassName: "",
        showAlpha: false,
        theme: "sp-light",
        palette: [["#ffffff", "#000000", "#ff0000", "#ff8000", "#ffff00", "#008000", "#0000ff", "#4b0082", "#9400d3"]],
        selectionPalette: [],
        disabled: false,
        offset: null
    },
    spectrums = [],
    IE = !!/msie/i.exec(window.navigator.userAgent),
    rgbaSupport = (function () {
        function contains(str, substr) {
            return !!~('' + str).indexOf(substr);
        }

        var elem = document.createElement('div');
        var style = elem.style;
        style.cssText = 'background-color:rgba(0,0,0,.5)';
        return contains(style.backgroundColor, 'rgba') || contains(style.backgroundColor, 'hsla');
    })(),
    replaceInput = [
        "<div class='sp-replacer'>",
            "<div class='sp-preview'><div class='sp-preview-inner'></div></div>",
            "<div class='sp-dd'>&#9660;</div>",
        "</div>"
    ].join(''),
    markup = (function () {

        // IE does not support gradients with multiple stops, so we need to simulate
        //  that for the rainbow slider with 8 divs that each have a single gradient
        var gradientFix = "";
        if (IE) {
            for (var i = 1; i <= 6; i++) {
                gradientFix += "<div class='sp-" + i + "'></div>";
            }
        }

        return [
            "<div class='sp-container sp-hidden'>",
                "<div class='sp-palette-container'>",
                    "<div class='sp-palette sp-thumb sp-cf'></div>",
                    "<div class='sp-palette-button-container sp-cf'>",
                        "<button type='button' class='sp-palette-toggle'></button>",
                    "</div>",
                "</div>",
                "<div class='sp-picker-container'>",
                    "<div class='sp-top sp-cf'>",
                        "<div class='sp-fill'></div>",
                        "<div class='sp-top-inner'>",
                            "<div class='sp-color'>",
                                "<div class='sp-sat'>",
                                    "<div class='sp-val'>",
                                        "<div class='sp-dragger'></div>",
                                    "</div>",
                                "</div>",
                            "</div>",
                            "<div class='sp-clear sp-clear-display'>",
                            "</div>",
                            "<div class='sp-hue'>",
                                "<div class='sp-slider'></div>",
                                gradientFix,
                            "</div>",
                        "</div>",
                        "<div class='sp-alpha'><div class='sp-alpha-inner'><div class='sp-alpha-handle'></div></div></div>",
                    "</div>",
                    "<div class='sp-input-container sp-cf'>",
                        "<input class='sp-input' type='text' spellcheck='false'  />",
                    "</div>",
                    "<div class='sp-initial sp-thumb sp-cf'></div>",
                    "<div class='sp-button-container sp-cf'>",
                        "<a class='sp-cancel' href='#'></a>",
                        "<button type='button' class='sp-choose'></button>",
                    "</div>",
                "</div>",
            "</div>"
        ].join("");
    })();

    function paletteTemplate(p, color, className, opts) {
        var html = [];
        for (var i = 0; i < p.length; i++) {
            var current = p[i];
            if (current) {
                var tiny = tinycolor(current);
                var c = tiny.toHsl().l < 0.5 ? "sp-thumb-el sp-thumb-dark" : "sp-thumb-el sp-thumb-light";
                c += (tinycolor.equals(color, current)) ? " sp-thumb-active" : "";
                var formattedString = tiny.toString(opts.preferredFormat || "rgb");
                var swatchStyle = rgbaSupport ? ("background-color:" + tiny.toRgbString()) : "filter:" + tiny.toFilter();
                html.push('<span title="' + formattedString + '" data-color="' + tiny.toRgbString() + '" class="' + c + '"><span class="sp-thumb-inner" style="' + swatchStyle + ';" /></span>');
            } else {
                var cls = 'sp-clear-display';
                html.push($('<div />')
                    .append($('<span data-color="" style="background-color:transparent;" class="' + cls + '"></span>')
                        .attr('title', opts.noColorSelectedText)
                    )
                    .html()
                );
            }
        }
        return "<div class='sp-cf " + className + "'>" + html.join('') + "</div>";
    }

    function hideAll() {
        for (var i = 0; i < spectrums.length; i++) {
            if (spectrums[i]) {
                spectrums[i].hide();
            }
        }
    }

    function instanceOptions(o, callbackContext) {
        var opts = $.extend({}, defaultOpts, o);
        opts.callbacks = {
            'move': bind(opts.move, callbackContext),
            'change': bind(opts.change, callbackContext),
            'show': bind(opts.show, callbackContext),
            'hide': bind(opts.hide, callbackContext),
            'beforeShow': bind(opts.beforeShow, callbackContext)
        };

        return opts;
    }

    function spectrum(element, o) {

        var opts = instanceOptions(o, element),
            flat = opts.flat,
            showSelectionPalette = opts.showSelectionPalette,
            localStorageKey = opts.localStorageKey,
            theme = opts.theme,
            callbacks = opts.callbacks,
            resize = throttle(reflow, 10),
            visible = false,
            isDragging = false,
            dragWidth = 0,
            dragHeight = 0,
            dragHelperHeight = 0,
            slideHeight = 0,
            slideWidth = 0,
            alphaWidth = 0,
            alphaSlideHelperWidth = 0,
            slideHelperHeight = 0,
            currentHue = 0,
            currentSaturation = 0,
            currentValue = 0,
            currentAlpha = 1,
            palette = [],
            paletteArray = [],
            paletteLookup = {},
            selectionPalette = opts.selectionPalette.slice(0),
            maxSelectionSize = opts.maxSelectionSize,
            draggingClass = "sp-dragging",
            shiftMovementDirection = null;

        var doc = element.ownerDocument,
            body = doc.body,
            boundElement = $(element),
            disabled = false,
            container = $(markup, doc).addClass(theme),
            pickerContainer = container.find(".sp-picker-container"),
            dragger = container.find(".sp-color"),
            dragHelper = container.find(".sp-dragger"),
            slider = container.find(".sp-hue"),
            slideHelper = container.find(".sp-slider"),
            alphaSliderInner = container.find(".sp-alpha-inner"),
            alphaSlider = container.find(".sp-alpha"),
            alphaSlideHelper = container.find(".sp-alpha-handle"),
            textInput = container.find(".sp-input"),
            paletteContainer = container.find(".sp-palette"),
            initialColorContainer = container.find(".sp-initial"),
            cancelButton = container.find(".sp-cancel"),
            clearButton = container.find(".sp-clear"),
            chooseButton = container.find(".sp-choose"),
            toggleButton = container.find(".sp-palette-toggle"),
            isInput = boundElement.is("input"),
            isInputTypeColor = isInput && boundElement.attr("type") === "color" && inputTypeColorSupport(),
            shouldReplace = isInput && !flat,
            replacer = (shouldReplace) ? $(replaceInput).addClass(theme).addClass(opts.className).addClass(opts.replacerClassName) : $([]),
            offsetElement = (shouldReplace) ? replacer : boundElement,
            previewElement = replacer.find(".sp-preview-inner"),
            initialColor = opts.color || (isInput && boundElement.val()),
            colorOnShow = false,
            currentPreferredFormat = opts.preferredFormat,
            clickoutFiresChange = !opts.showButtons || opts.clickoutFiresChange,
            isEmpty = !initialColor,
            allowEmpty = opts.allowEmpty && !isInputTypeColor;

        function applyOptions() {

            if (opts.showPaletteOnly) {
                opts.showPalette = true;
            }

            toggleButton.text(opts.showPaletteOnly ? opts.togglePaletteMoreText : opts.togglePaletteLessText);

            if (opts.palette) {
                palette = opts.palette.slice(0);
                paletteArray = $.isArray(palette[0]) ? palette : [palette];
                paletteLookup = {};
                for (var i = 0; i < paletteArray.length; i++) {
                    for (var j = 0; j < paletteArray[i].length; j++) {
                        var rgb = tinycolor(paletteArray[i][j]).toRgbString();
                        paletteLookup[rgb] = true;
                    }
                }
            }

            container.toggleClass("sp-flat", flat);
            container.toggleClass("sp-input-disabled", !opts.showInput);
            container.toggleClass("sp-alpha-enabled", opts.showAlpha);
            container.toggleClass("sp-clear-enabled", allowEmpty);
            container.toggleClass("sp-buttons-disabled", !opts.showButtons);
            container.toggleClass("sp-palette-buttons-disabled", !opts.togglePaletteOnly);
            container.toggleClass("sp-palette-disabled", !opts.showPalette);
            container.toggleClass("sp-palette-only", opts.showPaletteOnly);
            container.toggleClass("sp-initial-disabled", !opts.showInitial);
            container.addClass(opts.className).addClass(opts.containerClassName);

            reflow();
        }

        function initialize() {

            if (IE) {
                container.find("*:not(input)").attr("unselectable", "on");
            }

            applyOptions();

            if (shouldReplace) {
                boundElement.after(replacer).hide();
            }

            if (!allowEmpty) {
                clearButton.hide();
            }

            if (flat) {
                boundElement.after(container).hide();
            }
            else {

                var appendTo = opts.appendTo === "parent" ? boundElement.parent() : $(opts.appendTo);
                if (appendTo.length !== 1) {
                    appendTo = $("body");
                }

                appendTo.append(container);
            }

            updateSelectionPaletteFromStorage();

            offsetElement.bind("click.spectrum touchstart.spectrum", function (e) {
                if (!disabled) {
                    toggle();
                }

                e.stopPropagation();

                if (!$(e.target).is("input")) {
                    e.preventDefault();
                }
            });

            if (boundElement.is(":disabled") || (opts.disabled === true)) {
                disable();
            }

            // Prevent clicks from bubbling up to document.  This would cause it to be hidden.
            container.click(stopPropagation);

            // Handle user typed input
            textInput.change(setFromTextInput);
            textInput.bind("paste", function () {
                setTimeout(setFromTextInput, 1);
            });
            textInput.keydown(function (e) { if (e.keyCode == 13) { setFromTextInput(); } });

            cancelButton.text(opts.cancelText);
            cancelButton.bind("click.spectrum", function (e) {
                e.stopPropagation();
                e.preventDefault();
                revert();
                hide();
            });

            clearButton.attr("title", opts.clearText);
            clearButton.bind("click.spectrum", function (e) {
                e.stopPropagation();
                e.preventDefault();
                isEmpty = true;
                move();

                if (flat) {
                    //for the flat style, this is a change event
                    updateOriginalInput(true);
                }
            });

            chooseButton.text(opts.chooseText);
            chooseButton.bind("click.spectrum", function (e) {
                e.stopPropagation();
                e.preventDefault();

                if (IE && textInput.is(":focus")) {
                    textInput.trigger('change');
                }

                if (isValid()) {
                    updateOriginalInput(true);
                    hide();
                }
            });

            toggleButton.text(opts.showPaletteOnly ? opts.togglePaletteMoreText : opts.togglePaletteLessText);
            toggleButton.bind("click.spectrum", function (e) {
                e.stopPropagation();
                e.preventDefault();

                opts.showPaletteOnly = !opts.showPaletteOnly;

                // To make sure the Picker area is drawn on the right, next to the
                // Palette area (and not below the palette), first move the Palette
                // to the left to make space for the picker, plus 5px extra.
                // The 'applyOptions' function puts the whole container back into place
                // and takes care of the button-text and the sp-palette-only CSS class.
                if (!opts.showPaletteOnly && !flat) {
                    container.css('left', '-=' + (pickerContainer.outerWidth(true) + 5));
                }
                applyOptions();
            });

            draggable(alphaSlider, function (dragX, dragY, e) {
                currentAlpha = (dragX / alphaWidth);
                isEmpty = false;
                if (e.shiftKey) {
                    currentAlpha = Math.round(currentAlpha * 10) / 10;
                }

                move();
            }, dragStart, dragStop);

            draggable(slider, function (dragX, dragY) {
                currentHue = parseFloat(dragY / slideHeight);
                isEmpty = false;
                if (!opts.showAlpha) {
                    currentAlpha = 1;
                }
                move();
            }, dragStart, dragStop);

            draggable(dragger, function (dragX, dragY, e) {

                // shift+drag should snap the movement to either the x or y axis.
                if (!e.shiftKey) {
                    shiftMovementDirection = null;
                }
                else if (!shiftMovementDirection) {
                    var oldDragX = currentSaturation * dragWidth;
                    var oldDragY = dragHeight - (currentValue * dragHeight);
                    var furtherFromX = Math.abs(dragX - oldDragX) > Math.abs(dragY - oldDragY);

                    shiftMovementDirection = furtherFromX ? "x" : "y";
                }

                var setSaturation = !shiftMovementDirection || shiftMovementDirection === "x";
                var setValue = !shiftMovementDirection || shiftMovementDirection === "y";

                if (setSaturation) {
                    currentSaturation = parseFloat(dragX / dragWidth);
                }
                if (setValue) {
                    currentValue = parseFloat((dragHeight - dragY) / dragHeight);
                }

                isEmpty = false;
                if (!opts.showAlpha) {
                    currentAlpha = 1;
                }

                move();

            }, dragStart, dragStop);

            if (!!initialColor) {
                set(initialColor);

                // In case color was black - update the preview UI and set the format
                // since the set function will not run (default color is black).
                updateUI();
                currentPreferredFormat = opts.preferredFormat || tinycolor(initialColor).format;

                addColorToSelectionPalette(initialColor);
            }
            else {
                updateUI();
            }

            if (flat) {
                show();
            }

            function paletteElementClick(e) {
                if (e.data && e.data.ignore) {
                    set($(e.target).closest(".sp-thumb-el").data("color"));
                    move();
                }
                else {
                    set($(e.target).closest(".sp-thumb-el").data("color"));
                    move();
                    updateOriginalInput(true);
                    if (opts.hideAfterPaletteSelect) {
                        hide();
                    }
                }

                return false;
            }

            var paletteEvent = IE ? "mousedown.spectrum" : "click.spectrum touchstart.spectrum";
            paletteContainer.delegate(".sp-thumb-el", paletteEvent, paletteElementClick);
            initialColorContainer.delegate(".sp-thumb-el:nth-child(1)", paletteEvent, { ignore: true }, paletteElementClick);
        }

        function updateSelectionPaletteFromStorage() {

            if (localStorageKey && window.localStorage) {

                // Migrate old palettes over to new format.  May want to remove this eventually.
                try {
                    var oldPalette = window.localStorage[localStorageKey].split(",#");
                    if (oldPalette.length > 1) {
                        delete window.localStorage[localStorageKey];
                        $.each(oldPalette, function (i, c) {
                            addColorToSelectionPalette(c);
                        });
                    }
                }
                catch (e) { }

                try {
                    selectionPalette = window.localStorage[localStorageKey].split(";");
                }
                catch (e) { }
            }
        }

        function addColorToSelectionPalette(color) {
            if (showSelectionPalette) {
                var rgb = tinycolor(color).toRgbString();
                if (!paletteLookup[rgb] && $.inArray(rgb, selectionPalette) === -1) {
                    selectionPalette.push(rgb);
                    while (selectionPalette.length > maxSelectionSize) {
                        selectionPalette.shift();
                    }
                }

                if (localStorageKey && window.localStorage) {
                    try {
                        window.localStorage[localStorageKey] = selectionPalette.join(";");
                    }
                    catch (e) { }
                }
            }
        }

        function getUniqueSelectionPalette() {
            var unique = [];
            if (opts.showPalette) {
                for (var i = 0; i < selectionPalette.length; i++) {
                    var rgb = tinycolor(selectionPalette[i]).toRgbString();

                    if (!paletteLookup[rgb]) {
                        unique.push(selectionPalette[i]);
                    }
                }
            }

            return unique.reverse().slice(0, opts.maxSelectionSize);
        }

        function drawPalette() {

            var currentColor = get();

            var html = $.map(paletteArray, function (palette, i) {
                return paletteTemplate(palette, currentColor, "sp-palette-row sp-palette-row-" + i, opts);
            });

            updateSelectionPaletteFromStorage();

            if (selectionPalette) {
                html.push(paletteTemplate(getUniqueSelectionPalette(), currentColor, "sp-palette-row sp-palette-row-selection", opts));
            }

            paletteContainer.html(html.join(""));
        }

        function drawInitial() {
            if (opts.showInitial) {
                var initial = colorOnShow;
                var current = get();
                initialColorContainer.html(paletteTemplate([initial, current], current, "sp-palette-row-initial", opts));
            }
        }

        function dragStart() {
            if (dragHeight <= 0 || dragWidth <= 0 || slideHeight <= 0) {
                reflow();
            }
            isDragging = true;
            container.addClass(draggingClass);
            shiftMovementDirection = null;
            boundElement.trigger('dragstart.spectrum', [get()]);
        }

        function dragStop() {
            isDragging = false;
            container.removeClass(draggingClass);
            boundElement.trigger('dragstop.spectrum', [get()]);
        }

        function setFromTextInput() {

            var value = textInput.val();

            if ((value === null || value === "") && allowEmpty) {
                set(null);
                updateOriginalInput(true);
            }
            else {
                var tiny = tinycolor(value);
                if (tiny.isValid()) {
                    set(tiny);
                    updateOriginalInput(true);
                }
                else {
                    textInput.addClass("sp-validation-error");
                }
            }
        }

        function toggle() {
            if (visible) {
                hide();
            }
            else {
                show();
            }
        }

        function show() {
            var event = $.Event('beforeShow.spectrum');

            if (visible) {
                reflow();
                return;
            }

            boundElement.trigger(event, [get()]);

            if (callbacks.beforeShow(get()) === false || event.isDefaultPrevented()) {
                return;
            }

            hideAll();
            visible = true;

            $(doc).bind("keydown.spectrum", onkeydown);
            $(doc).bind("click.spectrum", clickout);
            $(window).bind("resize.spectrum", resize);
            replacer.addClass("sp-active");
            container.removeClass("sp-hidden");

            reflow();
            updateUI();

            colorOnShow = get();

            drawInitial();
            callbacks.show(colorOnShow);
            boundElement.trigger('show.spectrum', [colorOnShow]);
        }

        function onkeydown(e) {
            // Close on ESC
            if (e.keyCode === 27) {
                hide();
            }
        }

        function clickout(e) {
            // Return on right click.
            if (e.button == 2) { return; }

            // If a drag event was happening during the mouseup, don't hide
            // on click.
            if (isDragging) { return; }

            if (clickoutFiresChange) {
                updateOriginalInput(true);
            }
            else {
                revert();
            }
            hide();
        }

        function hide() {
            // Return if hiding is unnecessary
            if (!visible || flat) { return; }
            visible = false;

            $(doc).unbind("keydown.spectrum", onkeydown);
            $(doc).unbind("click.spectrum", clickout);
            $(window).unbind("resize.spectrum", resize);

            replacer.removeClass("sp-active");
            container.addClass("sp-hidden");

            callbacks.hide(get());
            boundElement.trigger('hide.spectrum', [get()]);
        }

        function revert() {
            set(colorOnShow, true);
        }

        function set(color, ignoreFormatChange) {
            if (tinycolor.equals(color, get())) {
                // Update UI just in case a validation error needs
                // to be cleared.
                updateUI();
                return;
            }

            var newColor, newHsv;
            if (!color && allowEmpty) {
                isEmpty = true;
            } else {
                isEmpty = false;
                newColor = tinycolor(color);
                newHsv = newColor.toHsv();

                currentHue = (newHsv.h % 360) / 360;
                currentSaturation = newHsv.s;
                currentValue = newHsv.v;
                currentAlpha = newHsv.a;
            }
            updateUI();

            if (newColor && newColor.isValid() && !ignoreFormatChange) {
                currentPreferredFormat = opts.preferredFormat || newColor.getFormat();
            }
        }

        function get(opts) {
            opts = opts || {};

            if (allowEmpty && isEmpty) {
                return null;
            }

            return tinycolor.fromRatio({
                h: currentHue,
                s: currentSaturation,
                v: currentValue,
                a: Math.round(currentAlpha * 100) / 100
            }, { format: opts.format || currentPreferredFormat });
        }

        function isValid() {
            return !textInput.hasClass("sp-validation-error");
        }

        function move() {
            updateUI();

            callbacks.move(get());
            boundElement.trigger('move.spectrum', [get()]);
        }

        function updateUI() {

            textInput.removeClass("sp-validation-error");

            updateHelperLocations();

            // Update dragger background color (gradients take care of saturation and value).
            var flatColor = tinycolor.fromRatio({ h: currentHue, s: 1, v: 1 });
            dragger.css("background-color", flatColor.toHexString());

            // Get a format that alpha will be included in (hex and names ignore alpha)
            var format = currentPreferredFormat;
            if (currentAlpha < 1 && !(currentAlpha === 0 && format === "name")) {
                if (format === "hex" || format === "hex3" || format === "hex6" || format === "name") {
                    format = "rgb";
                }
            }

            var realColor = get({ format: format }),
                displayColor = '';

            //reset background info for preview element
            previewElement.removeClass("sp-clear-display");
            previewElement.css('background-color', 'transparent');

            if (!realColor && allowEmpty) {
                // Update the replaced elements background with icon indicating no color selection
                previewElement.addClass("sp-clear-display");
            }
            else {
                var realHex = realColor.toHexString(),
                    realRgb = realColor.toRgbString();

                // Update the replaced elements background color (with actual selected color)
                if (rgbaSupport || realColor.alpha === 1) {
                    previewElement.css("background-color", realRgb);
                }
                else {
                    previewElement.css("background-color", "transparent");
                    previewElement.css("filter", realColor.toFilter());
                }

                if (opts.showAlpha) {
                    var rgb = realColor.toRgb();
                    rgb.a = 0;
                    var realAlpha = tinycolor(rgb).toRgbString();
                    var gradient = "linear-gradient(left, " + realAlpha + ", " + realHex + ")";

                    if (IE) {
                        alphaSliderInner.css("filter", tinycolor(realAlpha).toFilter({ gradientType: 1 }, realHex));
                    }
                    else {
                        alphaSliderInner.css("background", "-webkit-" + gradient);
                        alphaSliderInner.css("background", "-moz-" + gradient);
                        alphaSliderInner.css("background", "-ms-" + gradient);
                        // Use current syntax gradient on unprefixed property.
                        alphaSliderInner.css("background",
                            "linear-gradient(to right, " + realAlpha + ", " + realHex + ")");
                    }
                }

                displayColor = realColor.toString(format);
            }

            // Update the text entry input as it changes happen
            if (opts.showInput) {
                textInput.val(displayColor);
            }

            if (opts.showPalette) {
                drawPalette();
            }

            drawInitial();
        }

        function updateHelperLocations() {
            var s = currentSaturation;
            var v = currentValue;

            if (allowEmpty && isEmpty) {
                //if selected color is empty, hide the helpers
                alphaSlideHelper.hide();
                slideHelper.hide();
                dragHelper.hide();
            }
            else {
                //make sure helpers are visible
                alphaSlideHelper.show();
                slideHelper.show();
                dragHelper.show();

                // Where to show the little circle in that displays your current selected color
                var dragX = s * dragWidth;
                var dragY = dragHeight - (v * dragHeight);
                dragX = Math.max(
                    -dragHelperHeight,
                    Math.min(dragWidth - dragHelperHeight, dragX - dragHelperHeight)
                );
                dragY = Math.max(
                    -dragHelperHeight,
                    Math.min(dragHeight - dragHelperHeight, dragY - dragHelperHeight)
                );
                dragHelper.css({
                    "top": dragY + "px",
                    "left": dragX + "px"
                });

                var alphaX = currentAlpha * alphaWidth;
                alphaSlideHelper.css({
                    "left": (alphaX - (alphaSlideHelperWidth / 2)) + "px"
                });

                // Where to show the bar that displays your current selected hue
                var slideY = (currentHue) * slideHeight;
                slideHelper.css({
                    "top": (slideY - slideHelperHeight) + "px"
                });
            }
        }

        function updateOriginalInput(fireCallback) {
            var color = get(),
                displayColor = '',
                hasChanged = !tinycolor.equals(color, colorOnShow);

            if (color) {
                displayColor = color.toString(currentPreferredFormat);
                // Update the selection palette with the current color
                addColorToSelectionPalette(color);
            }

            if (isInput) {
                boundElement.val(displayColor);
            }

            if (fireCallback && hasChanged) {
                callbacks.change(color);
                boundElement.trigger('change', [color]);
            }
        }

        function reflow() {
            if (!visible) {
                return; // Calculations would be useless and wouldn't be reliable anyways
            }
            dragWidth = dragger.width();
            dragHeight = dragger.height();
            dragHelperHeight = dragHelper.height();
            slideWidth = slider.width();
            slideHeight = slider.height();
            slideHelperHeight = slideHelper.height();
            alphaWidth = alphaSlider.width();
            alphaSlideHelperWidth = alphaSlideHelper.width();

            if (!flat) {
                container.css("position", "absolute");
                if (opts.offset) {
                    container.offset(opts.offset);
                } else {
                    container.offset(getOffset(container, offsetElement));
                }
            }

            updateHelperLocations();

            if (opts.showPalette) {
                drawPalette();
            }

            boundElement.trigger('reflow.spectrum');
        }

        function destroy() {
            boundElement.show();
            offsetElement.unbind("click.spectrum touchstart.spectrum");
            container.remove();
            replacer.remove();
            spectrums[spect.id] = null;
        }

        function option(optionName, optionValue) {
            if (optionName === undefined) {
                return $.extend({}, opts);
            }
            if (optionValue === undefined) {
                return opts[optionName];
            }

            opts[optionName] = optionValue;

            if (optionName === "preferredFormat") {
                currentPreferredFormat = opts.preferredFormat;
            }
            applyOptions();
        }

        function enable() {
            disabled = false;
            boundElement.attr("disabled", false);
            offsetElement.removeClass("sp-disabled");
        }

        function disable() {
            hide();
            disabled = true;
            boundElement.attr("disabled", true);
            offsetElement.addClass("sp-disabled");
        }

        function setOffset(coord) {
            opts.offset = coord;
            reflow();
        }

        initialize();

        var spect = {
            show: show,
            hide: hide,
            toggle: toggle,
            reflow: reflow,
            option: option,
            enable: enable,
            disable: disable,
            offset: setOffset,
            set: function (c) {
                set(c);
                updateOriginalInput();
            },
            get: get,
            destroy: destroy,
            container: container
        };

        spect.id = spectrums.push(spect) - 1;

        return spect;
    }

    /**
    * checkOffset - get the offset below/above and left/right element depending on screen position
    * Thanks https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.datepicker.js
    */
    function getOffset(picker, input) {
        var extraY = 0;
        var dpWidth = picker.outerWidth();
        var dpHeight = picker.outerHeight();
        var inputHeight = input.outerHeight();
        var doc = picker[0].ownerDocument;
        var docElem = doc.documentElement;
        var viewWidth = docElem.clientWidth + $(doc).scrollLeft();
        var viewHeight = docElem.clientHeight + $(doc).scrollTop();
        var offset = input.offset();
        offset.top += inputHeight;

        offset.left -=
            Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ?
            Math.abs(offset.left + dpWidth - viewWidth) : 0);

        offset.top -=
            Math.min(offset.top, ((offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ?
            Math.abs(dpHeight + inputHeight - extraY) : extraY));

        return offset;
    }

    /**
    * noop - do nothing
    */
    function noop() {

    }

    /**
    * stopPropagation - makes the code only doing this a little easier to read in line
    */
    function stopPropagation(e) {
        e.stopPropagation();
    }

    /**
    * Create a function bound to a given object
    * Thanks to underscore.js
    */
    function bind(func, obj) {
        var slice = Array.prototype.slice;
        var args = slice.call(arguments, 2);
        return function () {
            return func.apply(obj, args.concat(slice.call(arguments)));
        };
    }

    /**
    * Lightweight drag helper.  Handles containment within the element, so that
    * when dragging, the x is within [0,element.width] and y is within [0,element.height]
    */
    function draggable(element, onmove, onstart, onstop) {
        onmove = onmove || function () { };
        onstart = onstart || function () { };
        onstop = onstop || function () { };
        var doc = document;
        var dragging = false;
        var offset = {};
        var maxHeight = 0;
        var maxWidth = 0;
        var hasTouch = ('ontouchstart' in window);

        var duringDragEvents = {};
        duringDragEvents["selectstart"] = prevent;
        duringDragEvents["dragstart"] = prevent;
        duringDragEvents["touchmove mousemove"] = move;
        duringDragEvents["touchend mouseup"] = stop;

        function prevent(e) {
            if (e.stopPropagation) {
                e.stopPropagation();
            }
            if (e.preventDefault) {
                e.preventDefault();
            }
            e.returnValue = false;
        }

        function move(e) {
            if (dragging) {
                // Mouseup happened outside of window
                if (IE && doc.documentMode < 9 && !e.button) {
                    return stop();
                }

                var t0 = e.originalEvent && e.originalEvent.touches && e.originalEvent.touches[0];
                var pageX = t0 && t0.pageX || e.pageX;
                var pageY = t0 && t0.pageY || e.pageY;

                var dragX = Math.max(0, Math.min(pageX - offset.left, maxWidth));
                var dragY = Math.max(0, Math.min(pageY - offset.top, maxHeight));

                if (hasTouch) {
                    // Stop scrolling in iOS
                    prevent(e);
                }

                onmove.apply(element, [dragX, dragY, e]);
            }
        }

        function start(e) {
            var rightclick = (e.which) ? (e.which == 3) : (e.button == 2);

            if (!rightclick && !dragging) {
                if (onstart.apply(element, arguments) !== false) {
                    dragging = true;
                    maxHeight = $(element).height();
                    maxWidth = $(element).width();
                    offset = $(element).offset();

                    $(doc).bind(duringDragEvents);
                    $(doc.body).addClass("sp-dragging");

                    move(e);

                    prevent(e);
                }
            }
        }

        function stop() {
            if (dragging) {
                $(doc).unbind(duringDragEvents);
                $(doc.body).removeClass("sp-dragging");

                // Wait a tick before notifying observers to allow the click event
                // to fire in Chrome.
                setTimeout(function () {
                    onstop.apply(element, arguments);
                }, 0);
            }
            dragging = false;
        }

        $(element).bind("touchstart mousedown", start);
    }

    function throttle(func, wait, debounce) {
        var timeout;
        return function () {
            var context = this, args = arguments;
            var throttler = function () {
                timeout = null;
                func.apply(context, args);
            };
            if (debounce) clearTimeout(timeout);
            if (debounce || !timeout) timeout = setTimeout(throttler, wait);
        };
    }

    function inputTypeColorSupport() {
        return $.fn.spectrum.inputTypeColorSupport();
    }

    /**
    * Define a jQuery plugin
    */
    var dataID = "spectrum.id";
    $.fn.spectrum = function (opts, extra) {

        if (typeof opts == "string") {

            var returnValue = this;
            var args = Array.prototype.slice.call(arguments, 1);

            this.each(function () {
                var spect = spectrums[$(this).data(dataID)];
                if (spect) {
                    var method = spect[opts];
                    if (!method) {
                        throw new Error("Spectrum: no such method: '" + opts + "'");
                    }

                    if (opts == "get") {
                        returnValue = spect.get();
                    }
                    else if (opts == "container") {
                        returnValue = spect.container;
                    }
                    else if (opts == "option") {
                        returnValue = spect.option.apply(spect, args);
                    }
                    else if (opts == "destroy") {
                        spect.destroy();
                        $(this).removeData(dataID);
                    }
                    else {
                        method.apply(spect, args);
                    }
                }
            });

            return returnValue;
        }

        // Initializing a new instance of spectrum
        return this.spectrum("destroy").each(function () {
            var options = $.extend({}, opts, $(this).data());
            var spect = spectrum(this, options);
            $(this).data(dataID, spect.id);
        });
    };

    $.fn.spectrum.load = true;
    $.fn.spectrum.loadOpts = {};
    $.fn.spectrum.draggable = draggable;
    $.fn.spectrum.defaults = defaultOpts;
    $.fn.spectrum.inputTypeColorSupport = function inputTypeColorSupport() {
        if (typeof inputTypeColorSupport._cachedResult === "undefined") {
            var colorInput = $("<input type='color'/>")[0]; // if color element is supported, value will default to not null
            inputTypeColorSupport._cachedResult = colorInput.type === "color" && colorInput.value !== "";
        }
        return inputTypeColorSupport._cachedResult;
    };

    $.spectrum = {};
    $.spectrum.localization = {};
    $.spectrum.palettes = {};

    $.fn.spectrum.processNativeColorInputs = function () {
        var colorInputs = $("input[type=color]");
        if (colorInputs.length && !inputTypeColorSupport()) {
            colorInputs.spectrum({
                preferredFormat: "hex6"
            });
        }
    };

    // TinyColor v1.1.2
    // https://github.com/bgrins/TinyColor
    // Brian Grinstead, MIT License

    (function () {

        var trimLeft = /^[\s,#]+/,
            trimRight = /\s+$/,
            tinyCounter = 0,
            math = Math,
            mathRound = math.round,
            mathMin = math.min,
            mathMax = math.max,
            mathRandom = math.random;

        var tinycolor = function (color, opts) {

            color = (color) ? color : '';
            opts = opts || {};

            // If input is already a tinycolor, return itself
            if (color instanceof tinycolor) {
                return color;
            }
            // If we are called as a function, call using new instead
            if (!(this instanceof tinycolor)) {
                return new tinycolor(color, opts);
            }

            var rgb = inputToRGB(color);
            this._originalInput = color,
            this._r = rgb.r,
            this._g = rgb.g,
            this._b = rgb.b,
            this._a = rgb.a,
            this._roundA = mathRound(100 * this._a) / 100,
            this._format = opts.format || rgb.format;
            this._gradientType = opts.gradientType;

            // Don't let the range of [0,255] come back in [0,1].
            // Potentially lose a little bit of precision here, but will fix issues where
            // .5 gets interpreted as half of the total, instead of half of 1
            // If it was supposed to be 128, this was already taken care of by `inputToRgb`
            if (this._r < 1) { this._r = mathRound(this._r); }
            if (this._g < 1) { this._g = mathRound(this._g); }
            if (this._b < 1) { this._b = mathRound(this._b); }

            this._ok = rgb.ok;
            this._tc_id = tinyCounter++;
        };

        tinycolor.prototype = {
            isDark: function () {
                return this.getBrightness() < 128;
            },
            isLight: function () {
                return !this.isDark();
            },
            isValid: function () {
                return this._ok;
            },
            getOriginalInput: function () {
                return this._originalInput;
            },
            getFormat: function () {
                return this._format;
            },
            getAlpha: function () {
                return this._a;
            },
            getBrightness: function () {
                var rgb = this.toRgb();
                return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
            },
            setAlpha: function (value) {
                this._a = boundAlpha(value);
                this._roundA = mathRound(100 * this._a) / 100;
                return this;
            },
            toHsv: function () {
                var hsv = rgbToHsv(this._r, this._g, this._b);
                return { h: hsv.h * 360, s: hsv.s, v: hsv.v, a: this._a };
            },
            toHsvString: function () {
                var hsv = rgbToHsv(this._r, this._g, this._b);
                var h = mathRound(hsv.h * 360), s = mathRound(hsv.s * 100), v = mathRound(hsv.v * 100);
                return (this._a == 1) ?
                  "hsv(" + h + ", " + s + "%, " + v + "%)" :
                  "hsva(" + h + ", " + s + "%, " + v + "%, " + this._roundA + ")";
            },
            toHsl: function () {
                var hsl = rgbToHsl(this._r, this._g, this._b);
                return { h: hsl.h * 360, s: hsl.s, l: hsl.l, a: this._a };
            },
            toHslString: function () {
                var hsl = rgbToHsl(this._r, this._g, this._b);
                var h = mathRound(hsl.h * 360), s = mathRound(hsl.s * 100), l = mathRound(hsl.l * 100);
                return (this._a == 1) ?
                  "hsl(" + h + ", " + s + "%, " + l + "%)" :
                  "hsla(" + h + ", " + s + "%, " + l + "%, " + this._roundA + ")";
            },
            toHex: function (allow3Char) {
                return rgbToHex(this._r, this._g, this._b, allow3Char);
            },
            toHexString: function (allow3Char) {
                return '#' + this.toHex(allow3Char);
            },
            toHex8: function () {
                return rgbaToHex(this._r, this._g, this._b, this._a);
            },
            toHex8String: function () {
                return '#' + this.toHex8();
            },
            toRgb: function () {
                return { r: mathRound(this._r), g: mathRound(this._g), b: mathRound(this._b), a: this._a };
            },
            toRgbString: function () {
                return (this._a == 1) ?
                  "rgb(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ")" :
                  "rgba(" + mathRound(this._r) + ", " + mathRound(this._g) + ", " + mathRound(this._b) + ", " + this._roundA + ")";
            },
            toPercentageRgb: function () {
                return { r: mathRound(bound01(this._r, 255) * 100) + "%", g: mathRound(bound01(this._g, 255) * 100) + "%", b: mathRound(bound01(this._b, 255) * 100) + "%", a: this._a };
            },
            toPercentageRgbString: function () {
                return (this._a == 1) ?
                  "rgb(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%)" :
                  "rgba(" + mathRound(bound01(this._r, 255) * 100) + "%, " + mathRound(bound01(this._g, 255) * 100) + "%, " + mathRound(bound01(this._b, 255) * 100) + "%, " + this._roundA + ")";
            },
            toName: function () {
                if (this._a === 0) {
                    return "transparent";
                }

                if (this._a < 1) {
                    return false;
                }

                return hexNames[rgbToHex(this._r, this._g, this._b, true)] || false;
            },
            toFilter: function (secondColor) {
                var hex8String = '#' + rgbaToHex(this._r, this._g, this._b, this._a);
                var secondHex8String = hex8String;
                var gradientType = this._gradientType ? "GradientType = 1, " : "";

                if (secondColor) {
                    var s = tinycolor(secondColor);
                    secondHex8String = s.toHex8String();
                }

                return "progid:DXImageTransform.Microsoft.gradient(" + gradientType + "startColorstr=" + hex8String + ",endColorstr=" + secondHex8String + ")";
            },
            toString: function (format) {
                var formatSet = !!format;
                format = format || this._format;

                var formattedString = false;
                var hasAlpha = this._a < 1 && this._a >= 0;
                var needsAlphaFormat = !formatSet && hasAlpha && (format === "hex" || format === "hex6" || format === "hex3" || format === "name");

                if (needsAlphaFormat) {
                    // Special case for "transparent", all other non-alpha formats
                    // will return rgba when there is transparency.
                    if (format === "name" && this._a === 0) {
                        return this.toName();
                    }
                    return this.toRgbString();
                }
                if (format === "rgb") {
                    formattedString = this.toRgbString();
                }
                if (format === "prgb") {
                    formattedString = this.toPercentageRgbString();
                }
                if (format === "hex" || format === "hex6") {
                    formattedString = this.toHexString();
                }
                if (format === "hex3") {
                    formattedString = this.toHexString(true);
                }
                if (format === "hex8") {
                    formattedString = this.toHex8String();
                }
                if (format === "name") {
                    formattedString = this.toName();
                }
                if (format === "hsl") {
                    formattedString = this.toHslString();
                }
                if (format === "hsv") {
                    formattedString = this.toHsvString();
                }

                return formattedString || this.toHexString();
            },

            _applyModification: function (fn, args) {
                var color = fn.apply(null, [this].concat([].slice.call(args)));
                this._r = color._r;
                this._g = color._g;
                this._b = color._b;
                this.setAlpha(color._a);
                return this;
            },
            lighten: function () {
                return this._applyModification(lighten, arguments);
            },
            brighten: function () {
                return this._applyModification(brighten, arguments);
            },
            darken: function () {
                return this._applyModification(darken, arguments);
            },
            desaturate: function () {
                return this._applyModification(desaturate, arguments);
            },
            saturate: function () {
                return this._applyModification(saturate, arguments);
            },
            greyscale: function () {
                return this._applyModification(greyscale, arguments);
            },
            spin: function () {
                return this._applyModification(spin, arguments);
            },

            _applyCombination: function (fn, args) {
                return fn.apply(null, [this].concat([].slice.call(args)));
            },
            analogous: function () {
                return this._applyCombination(analogous, arguments);
            },
            complement: function () {
                return this._applyCombination(complement, arguments);
            },
            monochromatic: function () {
                return this._applyCombination(monochromatic, arguments);
            },
            splitcomplement: function () {
                return this._applyCombination(splitcomplement, arguments);
            },
            triad: function () {
                return this._applyCombination(triad, arguments);
            },
            tetrad: function () {
                return this._applyCombination(tetrad, arguments);
            }
        };

        // If input is an object, force 1 into "1.0" to handle ratios properly
        // String input requires "1.0" as input, so 1 will be treated as 1
        tinycolor.fromRatio = function (color, opts) {
            if (typeof color == "object") {
                var newColor = {};
                for (var i in color) {
                    if (color.hasOwnProperty(i)) {
                        if (i === "a") {
                            newColor[i] = color[i];
                        }
                        else {
                            newColor[i] = convertToPercentage(color[i]);
                        }
                    }
                }
                color = newColor;
            }

            return tinycolor(color, opts);
        };

        // Given a string or object, convert that input to RGB
        // Possible string inputs:
        //
        //     "red"
        //     "#f00" or "f00"
        //     "#ff0000" or "ff0000"
        //     "#ff000000" or "ff000000"
        //     "rgb 255 0 0" or "rgb (255, 0, 0)"
        //     "rgb 1.0 0 0" or "rgb (1, 0, 0)"
        //     "rgba (255, 0, 0, 1)" or "rgba 255, 0, 0, 1"
        //     "rgba (1.0, 0, 0, 1)" or "rgba 1.0, 0, 0, 1"
        //     "hsl(0, 100%, 50%)" or "hsl 0 100% 50%"
        //     "hsla(0, 100%, 50%, 1)" or "hsla 0 100% 50%, 1"
        //     "hsv(0, 100%, 100%)" or "hsv 0 100% 100%"
        //
        function inputToRGB(color) {

            var rgb = { r: 0, g: 0, b: 0 };
            var a = 1;
            var ok = false;
            var format = false;

            if (typeof color == "string") {
                color = stringInputToObject(color);
            }

            if (typeof color == "object") {
                if (color.hasOwnProperty("r") && color.hasOwnProperty("g") && color.hasOwnProperty("b")) {
                    rgb = rgbToRgb(color.r, color.g, color.b);
                    ok = true;
                    format = String(color.r).substr(-1) === "%" ? "prgb" : "rgb";
                }
                else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("v")) {
                    color.s = convertToPercentage(color.s);
                    color.v = convertToPercentage(color.v);
                    rgb = hsvToRgb(color.h, color.s, color.v);
                    ok = true;
                    format = "hsv";
                }
                else if (color.hasOwnProperty("h") && color.hasOwnProperty("s") && color.hasOwnProperty("l")) {
                    color.s = convertToPercentage(color.s);
                    color.l = convertToPercentage(color.l);
                    rgb = hslToRgb(color.h, color.s, color.l);
                    ok = true;
                    format = "hsl";
                }

                if (color.hasOwnProperty("a")) {
                    a = color.a;
                }
            }

            a = boundAlpha(a);

            return {
                ok: ok,
                format: color.format || format,
                r: mathMin(255, mathMax(rgb.r, 0)),
                g: mathMin(255, mathMax(rgb.g, 0)),
                b: mathMin(255, mathMax(rgb.b, 0)),
                a: a
            };
        }


        // Conversion Functions
        // --------------------

        // `rgbToHsl`, `rgbToHsv`, `hslToRgb`, `hsvToRgb` modified from:
        // <http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript>

        // `rgbToRgb`
        // Handle bounds / percentage checking to conform to CSS color spec
        // <http://www.w3.org/TR/css3-color/>
        // *Assumes:* r, g, b in [0, 255] or [0, 1]
        // *Returns:* { r, g, b } in [0, 255]
        function rgbToRgb(r, g, b) {
            return {
                r: bound01(r, 255) * 255,
                g: bound01(g, 255) * 255,
                b: bound01(b, 255) * 255
            };
        }

        // `rgbToHsl`
        // Converts an RGB color value to HSL.
        // *Assumes:* r, g, and b are contained in [0, 255] or [0, 1]
        // *Returns:* { h, s, l } in [0,1]
        function rgbToHsl(r, g, b) {

            r = bound01(r, 255);
            g = bound01(g, 255);
            b = bound01(b, 255);

            var max = mathMax(r, g, b), min = mathMin(r, g, b);
            var h, s, l = (max + min) / 2;

            if (max == min) {
                h = s = 0; // achromatic
            }
            else {
                var d = max - min;
                s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
                switch (max) {
                    case r: h = (g - b) / d + (g < b ? 6 : 0); break;
                    case g: h = (b - r) / d + 2; break;
                    case b: h = (r - g) / d + 4; break;
                }

                h /= 6;
            }

            return { h: h, s: s, l: l };
        }

        // `hslToRgb`
        // Converts an HSL color value to RGB.
        // *Assumes:* h is contained in [0, 1] or [0, 360] and s and l are contained [0, 1] or [0, 100]
        // *Returns:* { r, g, b } in the set [0, 255]
        function hslToRgb(h, s, l) {
            var r, g, b;

            h = bound01(h, 360);
            s = bound01(s, 100);
            l = bound01(l, 100);

            function hue2rgb(p, q, t) {
                if (t < 0) t += 1;
                if (t > 1) t -= 1;
                if (t < 1 / 6) return p + (q - p) * 6 * t;
                if (t < 1 / 2) return q;
                if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
                return p;
            }

            if (s === 0) {
                r = g = b = l; // achromatic
            }
            else {
                var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
                var p = 2 * l - q;
                r = hue2rgb(p, q, h + 1 / 3);
                g = hue2rgb(p, q, h);
                b = hue2rgb(p, q, h - 1 / 3);
            }

            return { r: r * 255, g: g * 255, b: b * 255 };
        }

        // `rgbToHsv`
        // Converts an RGB color value to HSV
        // *Assumes:* r, g, and b are contained in the set [0, 255] or [0, 1]
        // *Returns:* { h, s, v } in [0,1]
        function rgbToHsv(r, g, b) {

            r = bound01(r, 255);
            g = bound01(g, 255);
            b = bound01(b, 255);

            var max = mathMax(r, g, b), min = mathMin(r, g, b);
            var h, s, v = max;

            var d = max - min;
            s = max === 0 ? 0 : d / max;

            if (max == min) {
                h = 0; // achromatic
            }
            else {
                switch (max) {
                    case r: h = (g - b) / d + (g < b ? 6 : 0); break;
                    case g: h = (b - r) / d + 2; break;
                    case b: h = (r - g) / d + 4; break;
                }
                h /= 6;
            }
            return { h: h, s: s, v: v };
        }

        // `hsvToRgb`
        // Converts an HSV color value to RGB.
        // *Assumes:* h is contained in [0, 1] or [0, 360] and s and v are contained in [0, 1] or [0, 100]
        // *Returns:* { r, g, b } in the set [0, 255]
        function hsvToRgb(h, s, v) {

            h = bound01(h, 360) * 6;
            s = bound01(s, 100);
            v = bound01(v, 100);

            var i = math.floor(h),
                f = h - i,
                p = v * (1 - s),
                q = v * (1 - f * s),
                t = v * (1 - (1 - f) * s),
                mod = i % 6,
                r = [v, q, p, p, t, v][mod],
                g = [t, v, v, q, p, p][mod],
                b = [p, p, t, v, v, q][mod];

            return { r: r * 255, g: g * 255, b: b * 255 };
        }

        // `rgbToHex`
        // Converts an RGB color to hex
        // Assumes r, g, and b are contained in the set [0, 255]
        // Returns a 3 or 6 character hex
        function rgbToHex(r, g, b, allow3Char) {

            var hex = [
                pad2(mathRound(r).toString(16)),
                pad2(mathRound(g).toString(16)),
                pad2(mathRound(b).toString(16))
            ];

            // Return a 3 character hex if possible
            if (allow3Char && hex[0].charAt(0) == hex[0].charAt(1) && hex[1].charAt(0) == hex[1].charAt(1) && hex[2].charAt(0) == hex[2].charAt(1)) {
                return hex[0].charAt(0) + hex[1].charAt(0) + hex[2].charAt(0);
            }

            return hex.join("");
        }
        // `rgbaToHex`
        // Converts an RGBA color plus alpha transparency to hex
        // Assumes r, g, b and a are contained in the set [0, 255]
        // Returns an 8 character hex
        function rgbaToHex(r, g, b, a) {

            var hex = [
                pad2(convertDecimalToHex(a)),
                pad2(mathRound(r).toString(16)),
                pad2(mathRound(g).toString(16)),
                pad2(mathRound(b).toString(16))
            ];

            return hex.join("");
        }

        // `equals`
        // Can be called with any tinycolor input
        tinycolor.equals = function (color1, color2) {
            if (!color1 || !color2) { return false; }
            return tinycolor(color1).toRgbString() == tinycolor(color2).toRgbString();
        };
        tinycolor.random = function () {
            return tinycolor.fromRatio({
                r: mathRandom(),
                g: mathRandom(),
                b: mathRandom()
            });
        };


        // Modification Functions
        // ----------------------
        // Thanks to less.js for some of the basics here
        // <https://github.com/cloudhead/less.js/blob/master/lib/less/functions.js>

        function desaturate(color, amount) {
            amount = (amount === 0) ? 0 : (amount || 10);
            var hsl = tinycolor(color).toHsl();
            hsl.s -= amount / 100;
            hsl.s = clamp01(hsl.s);
            return tinycolor(hsl);
        }

        function saturate(color, amount) {
            amount = (amount === 0) ? 0 : (amount || 10);
            var hsl = tinycolor(color).toHsl();
            hsl.s += amount / 100;
            hsl.s = clamp01(hsl.s);
            return tinycolor(hsl);
        }

        function greyscale(color) {
            return tinycolor(color).desaturate(100);
        }

        function lighten(color, amount) {
            amount = (amount === 0) ? 0 : (amount || 10);
            var hsl = tinycolor(color).toHsl();
            hsl.l += amount / 100;
            hsl.l = clamp01(hsl.l);
            return tinycolor(hsl);
        }

        function brighten(color, amount) {
            amount = (amount === 0) ? 0 : (amount || 10);
            var rgb = tinycolor(color).toRgb();
            rgb.r = mathMax(0, mathMin(255, rgb.r - mathRound(255 * -(amount / 100))));
            rgb.g = mathMax(0, mathMin(255, rgb.g - mathRound(255 * -(amount / 100))));
            rgb.b = mathMax(0, mathMin(255, rgb.b - mathRound(255 * -(amount / 100))));
            return tinycolor(rgb);
        }

        function darken(color, amount) {
            amount = (amount === 0) ? 0 : (amount || 10);
            var hsl = tinycolor(color).toHsl();
            hsl.l -= amount / 100;
            hsl.l = clamp01(hsl.l);
            return tinycolor(hsl);
        }

        // Spin takes a positive or negative amount within [-360, 360] indicating the change of hue.
        // Values outside of this range will be wrapped into this range.
        function spin(color, amount) {
            var hsl = tinycolor(color).toHsl();
            var hue = (mathRound(hsl.h) + amount) % 360;
            hsl.h = hue < 0 ? 360 + hue : hue;
            return tinycolor(hsl);
        }

        // Combination Functions
        // ---------------------
        // Thanks to jQuery xColor for some of the ideas behind these
        // <https://github.com/infusion/jQuery-xcolor/blob/master/jquery.xcolor.js>

        function complement(color) {
            var hsl = tinycolor(color).toHsl();
            hsl.h = (hsl.h + 180) % 360;
            return tinycolor(hsl);
        }

        function triad(color) {
            var hsl = tinycolor(color).toHsl();
            var h = hsl.h;
            return [
                tinycolor(color),
                tinycolor({ h: (h + 120) % 360, s: hsl.s, l: hsl.l }),
                tinycolor({ h: (h + 240) % 360, s: hsl.s, l: hsl.l })
            ];
        }

        function tetrad(color) {
            var hsl = tinycolor(color).toHsl();
            var h = hsl.h;
            return [
                tinycolor(color),
                tinycolor({ h: (h + 90) % 360, s: hsl.s, l: hsl.l }),
                tinycolor({ h: (h + 180) % 360, s: hsl.s, l: hsl.l }),
                tinycolor({ h: (h + 270) % 360, s: hsl.s, l: hsl.l })
            ];
        }

        function splitcomplement(color) {
            var hsl = tinycolor(color).toHsl();
            var h = hsl.h;
            return [
                tinycolor(color),
                tinycolor({ h: (h + 72) % 360, s: hsl.s, l: hsl.l }),
                tinycolor({ h: (h + 216) % 360, s: hsl.s, l: hsl.l })
            ];
        }

        function analogous(color, results, slices) {
            results = results || 6;
            slices = slices || 30;

            var hsl = tinycolor(color).toHsl();
            var part = 360 / slices;
            var ret = [tinycolor(color)];

            for (hsl.h = ((hsl.h - (part * results >> 1)) + 720) % 360; --results;) {
                hsl.h = (hsl.h + part) % 360;
                ret.push(tinycolor(hsl));
            }
            return ret;
        }

        function monochromatic(color, results) {
            results = results || 6;
            var hsv = tinycolor(color).toHsv();
            var h = hsv.h, s = hsv.s, v = hsv.v;
            var ret = [];
            var modification = 1 / results;

            while (results--) {
                ret.push(tinycolor({ h: h, s: s, v: v }));
                v = (v + modification) % 1;
            }

            return ret;
        }

        // Utility Functions
        // ---------------------

        tinycolor.mix = function (color1, color2, amount) {
            amount = (amount === 0) ? 0 : (amount || 50);

            var rgb1 = tinycolor(color1).toRgb();
            var rgb2 = tinycolor(color2).toRgb();

            var p = amount / 100;
            var w = p * 2 - 1;
            var a = rgb2.a - rgb1.a;

            var w1;

            if (w * a == -1) {
                w1 = w;
            } else {
                w1 = (w + a) / (1 + w * a);
            }

            w1 = (w1 + 1) / 2;

            var w2 = 1 - w1;

            var rgba = {
                r: rgb2.r * w1 + rgb1.r * w2,
                g: rgb2.g * w1 + rgb1.g * w2,
                b: rgb2.b * w1 + rgb1.b * w2,
                a: rgb2.a * p + rgb1.a * (1 - p)
            };

            return tinycolor(rgba);
        };


        // Readability Functions
        // ---------------------
        // <http://www.w3.org/TR/AERT#color-contrast>

        // `readability`
        // Analyze the 2 colors and returns an object with the following properties:
        //    `brightness`: difference in brightness between the two colors
        //    `color`: difference in color/hue between the two colors
        tinycolor.readability = function (color1, color2) {
            var c1 = tinycolor(color1);
            var c2 = tinycolor(color2);
            var rgb1 = c1.toRgb();
            var rgb2 = c2.toRgb();
            var brightnessA = c1.getBrightness();
            var brightnessB = c2.getBrightness();
            var colorDiff = (
                Math.max(rgb1.r, rgb2.r) - Math.min(rgb1.r, rgb2.r) +
                Math.max(rgb1.g, rgb2.g) - Math.min(rgb1.g, rgb2.g) +
                Math.max(rgb1.b, rgb2.b) - Math.min(rgb1.b, rgb2.b)
            );

            return {
                brightness: Math.abs(brightnessA - brightnessB),
                color: colorDiff
            };
        };

        // `readable`
        // http://www.w3.org/TR/AERT#color-contrast
        // Ensure that foreground and background color combinations provide sufficient contrast.
        // *Example*
        //    tinycolor.isReadable("#000", "#111") => false
        tinycolor.isReadable = function (color1, color2) {
            var readability = tinycolor.readability(color1, color2);
            return readability.brightness > 125 && readability.color > 500;
        };

        // `mostReadable`
        // Given a base color and a list of possible foreground or background
        // colors for that base, returns the most readable color.
        // *Example*
        //    tinycolor.mostReadable("#123", ["#fff", "#000"]) => "#000"
        tinycolor.mostReadable = function (baseColor, colorList) {
            var bestColor = null;
            var bestScore = 0;
            var bestIsReadable = false;
            for (var i = 0; i < colorList.length; i++) {

                // We normalize both around the "acceptable" breaking point,
                // but rank brightness constrast higher than hue.

                var readability = tinycolor.readability(baseColor, colorList[i]);
                var readable = readability.brightness > 125 && readability.color > 500;
                var score = 3 * (readability.brightness / 125) + (readability.color / 500);

                if ((readable && !bestIsReadable) ||
                    (readable && bestIsReadable && score > bestScore) ||
                    ((!readable) && (!bestIsReadable) && score > bestScore)) {
                    bestIsReadable = readable;
                    bestScore = score;
                    bestColor = tinycolor(colorList[i]);
                }
            }
            return bestColor;
        };


        // Big List of Colors
        // ------------------
        // <http://www.w3.org/TR/css3-color/#svg-color>
        var names = tinycolor.names = {
            aliceblue: "f0f8ff",
            antiquewhite: "faebd7",
            aqua: "0ff",
            aquamarine: "7fffd4",
            azure: "f0ffff",
            beige: "f5f5dc",
            bisque: "ffe4c4",
            black: "000",
            blanchedalmond: "ffebcd",
            blue: "00f",
            blueviolet: "8a2be2",
            brown: "a52a2a",
            burlywood: "deb887",
            burntsienna: "ea7e5d",
            cadetblue: "5f9ea0",
            chartreuse: "7fff00",
            chocolate: "d2691e",
            coral: "ff7f50",
            cornflowerblue: "6495ed",
            cornsilk: "fff8dc",
            crimson: "dc143c",
            cyan: "0ff",
            darkblue: "00008b",
            darkcyan: "008b8b",
            darkgoldenrod: "b8860b",
            darkgray: "a9a9a9",
            darkgreen: "006400",
            darkgrey: "a9a9a9",
            darkkhaki: "bdb76b",
            darkmagenta: "8b008b",
            darkolivegreen: "556b2f",
            darkorange: "ff8c00",
            darkorchid: "9932cc",
            darkred: "8b0000",
            darksalmon: "e9967a",
            darkseagreen: "8fbc8f",
            darkslateblue: "483d8b",
            darkslategray: "2f4f4f",
            darkslategrey: "2f4f4f",
            darkturquoise: "00ced1",
            darkviolet: "9400d3",
            deeppink: "ff1493",
            deepskyblue: "00bfff",
            dimgray: "696969",
            dimgrey: "696969",
            dodgerblue: "1e90ff",
            firebrick: "b22222",
            floralwhite: "fffaf0",
            forestgreen: "228b22",
            fuchsia: "f0f",
            gainsboro: "dcdcdc",
            ghostwhite: "f8f8ff",
            gold: "ffd700",
            goldenrod: "daa520",
            gray: "808080",
            green: "008000",
            greenyellow: "adff2f",
            grey: "808080",
            honeydew: "f0fff0",
            hotpink: "ff69b4",
            indianred: "cd5c5c",
            indigo: "4b0082",
            ivory: "fffff0",
            khaki: "f0e68c",
            lavender: "e6e6fa",
            lavenderblush: "fff0f5",
            lawngreen: "7cfc00",
            lemonchiffon: "fffacd",
            lightblue: "add8e6",
            lightcoral: "f08080",
            lightcyan: "e0ffff",
            lightgoldenrodyellow: "fafad2",
            lightgray: "d3d3d3",
            lightgreen: "90ee90",
            lightgrey: "d3d3d3",
            lightpink: "ffb6c1",
            lightsalmon: "ffa07a",
            lightseagreen: "20b2aa",
            lightskyblue: "87cefa",
            lightslategray: "789",
            lightslategrey: "789",
            lightsteelblue: "b0c4de",
            lightyellow: "ffffe0",
            lime: "0f0",
            limegreen: "32cd32",
            linen: "faf0e6",
            magenta: "f0f",
            maroon: "800000",
            mediumaquamarine: "66cdaa",
            mediumblue: "0000cd",
            mediumorchid: "ba55d3",
            mediumpurple: "9370db",
            mediumseagreen: "3cb371",
            mediumslateblue: "7b68ee",
            mediumspringgreen: "00fa9a",
            mediumturquoise: "48d1cc",
            mediumvioletred: "c71585",
            midnightblue: "191970",
            mintcream: "f5fffa",
            mistyrose: "ffe4e1",
            moccasin: "ffe4b5",
            navajowhite: "ffdead",
            navy: "000080",
            oldlace: "fdf5e6",
            olive: "808000",
            olivedrab: "6b8e23",
            orange: "ffa500",
            orangered: "ff4500",
            orchid: "da70d6",
            palegoldenrod: "eee8aa",
            palegreen: "98fb98",
            paleturquoise: "afeeee",
            palevioletred: "db7093",
            papayawhip: "ffefd5",
            peachpuff: "ffdab9",
            peru: "cd853f",
            pink: "ffc0cb",
            plum: "dda0dd",
            powderblue: "b0e0e6",
            purple: "800080",
            rebeccapurple: "663399",
            red: "f00",
            rosybrown: "bc8f8f",
            royalblue: "4169e1",
            saddlebrown: "8b4513",
            salmon: "fa8072",
            sandybrown: "f4a460",
            seagreen: "2e8b57",
            seashell: "fff5ee",
            sienna: "a0522d",
            silver: "c0c0c0",
            skyblue: "87ceeb",
            slateblue: "6a5acd",
            slategray: "708090",
            slategrey: "708090",
            snow: "fffafa",
            springgreen: "00ff7f",
            steelblue: "4682b4",
            tan: "d2b48c",
            teal: "008080",
            thistle: "d8bfd8",
            tomato: "ff6347",
            turquoise: "40e0d0",
            violet: "ee82ee",
            wheat: "f5deb3",
            white: "fff",
            whitesmoke: "f5f5f5",
            yellow: "ff0",
            yellowgreen: "9acd32"
        };

        // Make it easy to access colors via `hexNames[hex]`
        var hexNames = tinycolor.hexNames = flip(names);


        // Utilities
        // ---------

        // `{ 'name1': 'val1' }` becomes `{ 'val1': 'name1' }`
        function flip(o) {
            var flipped = {};
            for (var i in o) {
                if (o.hasOwnProperty(i)) {
                    flipped[o[i]] = i;
                }
            }
            return flipped;
        }

        // Return a valid alpha value [0,1] with all invalid values being set to 1
        function boundAlpha(a) {
            a = parseFloat(a);

            if (isNaN(a) || a < 0 || a > 1) {
                a = 1;
            }

            return a;
        }

        // Take input from [0, n] and return it as [0, 1]
        function bound01(n, max) {
            if (isOnePointZero(n)) { n = "100%"; }

            var processPercent = isPercentage(n);
            n = mathMin(max, mathMax(0, parseFloat(n)));

            // Automatically convert percentage into number
            if (processPercent) {
                n = parseInt(n * max, 10) / 100;
            }

            // Handle floating point rounding errors
            if ((math.abs(n - max) < 0.000001)) {
                return 1;
            }

            // Convert into [0, 1] range if it isn't already
            return (n % max) / parseFloat(max);
        }

        // Force a number between 0 and 1
        function clamp01(val) {
            return mathMin(1, mathMax(0, val));
        }

        // Parse a base-16 hex value into a base-10 integer
        function parseIntFromHex(val) {
            return parseInt(val, 16);
        }

        // Need to handle 1.0 as 100%, since once it is a number, there is no difference between it and 1
        // <http://stackoverflow.com/questions/7422072/javascript-how-to-detect-number-as-a-decimal-including-1-0>
        function isOnePointZero(n) {
            return typeof n == "string" && n.indexOf('.') != -1 && parseFloat(n) === 1;
        }

        // Check to see if string passed in is a percentage
        function isPercentage(n) {
            return typeof n === "string" && n.indexOf('%') != -1;
        }

        // Force a hex value to have 2 characters
        function pad2(c) {
            return c.length == 1 ? '0' + c : '' + c;
        }

        // Replace a decimal with it's percentage value
        function convertToPercentage(n) {
            if (n <= 1) {
                n = (n * 100) + "%";
            }

            return n;
        }

        // Converts a decimal to a hex value
        function convertDecimalToHex(d) {
            return Math.round(parseFloat(d) * 255).toString(16);
        }
        // Converts a hex value to a decimal
        function convertHexToDecimal(h) {
            return (parseIntFromHex(h) / 255);
        }

        var matchers = (function () {

            // <http://www.w3.org/TR/css3-values/#integers>
            var CSS_INTEGER = "[-\\+]?\\d+%?";

            // <http://www.w3.org/TR/css3-values/#number-value>
            var CSS_NUMBER = "[-\\+]?\\d*\\.\\d+%?";

            // Allow positive/negative integer/number.  Don't capture the either/or, just the entire outcome.
            var CSS_UNIT = "(?:" + CSS_NUMBER + ")|(?:" + CSS_INTEGER + ")";

            // Actual matching.
            // Parentheses and commas are optional, but not required.
            // Whitespace can take the place of commas or opening paren
            var PERMISSIVE_MATCH3 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";
            var PERMISSIVE_MATCH4 = "[\\s|\\(]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")[,|\\s]+(" + CSS_UNIT + ")\\s*\\)?";

            return {
                rgb: new RegExp("rgb" + PERMISSIVE_MATCH3),
                rgba: new RegExp("rgba" + PERMISSIVE_MATCH4),
                hsl: new RegExp("hsl" + PERMISSIVE_MATCH3),
                hsla: new RegExp("hsla" + PERMISSIVE_MATCH4),
                hsv: new RegExp("hsv" + PERMISSIVE_MATCH3),
                hsva: new RegExp("hsva" + PERMISSIVE_MATCH4),
                hex3: /^([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,
                hex6: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,
                hex8: /^([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/
            };
        })();

        // `stringInputToObject`
        // Permissive string parsing.  Take in a number of formats, and output an object
        // based on detected format.  Returns `{ r, g, b }` or `{ h, s, l }` or `{ h, s, v}`
        function stringInputToObject(color) {

            color = color.replace(trimLeft, '').replace(trimRight, '').toLowerCase();
            var named = false;
            if (names[color]) {
                color = names[color];
                named = true;
            }
            else if (color == 'transparent') {
                return { r: 0, g: 0, b: 0, a: 0, format: "name" };
            }

            // Try to match string input using regular expressions.
            // Keep most of the number bounding out of this function - don't worry about [0,1] or [0,100] or [0,360]
            // Just return an object and let the conversion functions handle that.
            // This way the result will be the same whether the tinycolor is initialized with string or object.
            var match;
            if ((match = matchers.rgb.exec(color))) {
                return { r: match[1], g: match[2], b: match[3] };
            }
            if ((match = matchers.rgba.exec(color))) {
                return { r: match[1], g: match[2], b: match[3], a: match[4] };
            }
            if ((match = matchers.hsl.exec(color))) {
                return { h: match[1], s: match[2], l: match[3] };
            }
            if ((match = matchers.hsla.exec(color))) {
                return { h: match[1], s: match[2], l: match[3], a: match[4] };
            }
            if ((match = matchers.hsv.exec(color))) {
                return { h: match[1], s: match[2], v: match[3] };
            }
            if ((match = matchers.hsva.exec(color))) {
                return { h: match[1], s: match[2], v: match[3], a: match[4] };
            }
            if ((match = matchers.hex8.exec(color))) {
                return {
                    a: convertHexToDecimal(match[1]),
                    r: parseIntFromHex(match[2]),
                    g: parseIntFromHex(match[3]),
                    b: parseIntFromHex(match[4]),
                    format: named ? "name" : "hex8"
                };
            }
            if ((match = matchers.hex6.exec(color))) {
                return {
                    r: parseIntFromHex(match[1]),
                    g: parseIntFromHex(match[2]),
                    b: parseIntFromHex(match[3]),
                    format: named ? "name" : "hex"
                };
            }
            if ((match = matchers.hex3.exec(color))) {
                return {
                    r: parseIntFromHex(match[1] + '' + match[1]),
                    g: parseIntFromHex(match[2] + '' + match[2]),
                    b: parseIntFromHex(match[3] + '' + match[3]),
                    format: named ? "name" : "hex"
                };
            }

            return false;
        }

        window.tinycolor = tinycolor;
    })();

    $(function () {
        if ($.fn.spectrum.load) {
            $.fn.spectrum.processNativeColorInputs();
        }
    });

});
;
/**
 * @license
 * Lo-Dash 2.4.1 (Custom Build) <http://lodash.com/>
 * Build: `lodash modern -o ./dist/lodash.js`
 * Copyright 2012-2013 The Dojo Foundation <http://dojofoundation.org/>
 * Based on Underscore.js 1.5.2 <http://underscorejs.org/LICENSE>
 * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
 * Available under MIT license <http://lodash.com/license>
 */
;(function() {

  /** Used as a safe reference for `undefined` in pre ES5 environments */
  var undefined;

  /** Used to pool arrays and objects used internally */
  var arrayPool = [],
      objectPool = [];

  /** Used to generate unique IDs */
  var idCounter = 0;

  /** Used to prefix keys to avoid issues with `__proto__` and properties on `Object.prototype` */
  var keyPrefix = +new Date + '';

  /** Used as the size when optimizations are enabled for large arrays */
  var largeArraySize = 75;

  /** Used as the max size of the `arrayPool` and `objectPool` */
  var maxPoolSize = 40;

  /** Used to detect and test whitespace */
  var whitespace = (
    // whitespace
    ' \t\x0B\f\xA0\ufeff' +

    // line terminators
    '\n\r\u2028\u2029' +

    // unicode category "Zs" space separators
    '\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000'
  );

  /** Used to match empty string literals in compiled template source */
  var reEmptyStringLeading = /\b__p \+= '';/g,
      reEmptyStringMiddle = /\b(__p \+=) '' \+/g,
      reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g;

  /**
   * Used to match ES6 template delimiters
   * http://people.mozilla.org/~jorendorff/es6-draft.html#sec-literals-string-literals
   */
  var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;

  /** Used to match regexp flags from their coerced string values */
  var reFlags = /\w*$/;

  /** Used to detected named functions */
  var reFuncName = /^\s*function[ \n\r\t]+\w/;

  /** Used to match "interpolate" template delimiters */
  var reInterpolate = /<%=([\s\S]+?)%>/g;

  /** Used to match leading whitespace and zeros to be removed */
  var reLeadingSpacesAndZeros = RegExp('^[' + whitespace + ']*0+(?=.$)');

  /** Used to ensure capturing order of template delimiters */
  var reNoMatch = /($^)/;

  /** Used to detect functions containing a `this` reference */
  var reThis = /\bthis\b/;

  /** Used to match unescaped characters in compiled string literals */
  var reUnescapedString = /['\n\r\t\u2028\u2029\\]/g;

  /** Used to assign default `context` object properties */
  var contextProps = [
    'Array', 'Boolean', 'Date', 'Function', 'Math', 'Number', 'Object',
    'RegExp', 'String', '_', 'attachEvent', 'clearTimeout', 'isFinite', 'isNaN',
    'parseInt', 'setTimeout'
  ];

  /** Used to make template sourceURLs easier to identify */
  var templateCounter = 0;

  /** `Object#toString` result shortcuts */
  var argsClass = '[object Arguments]',
      arrayClass = '[object Array]',
      boolClass = '[object Boolean]',
      dateClass = '[object Date]',
      funcClass = '[object Function]',
      numberClass = '[object Number]',
      objectClass = '[object Object]',
      regexpClass = '[object RegExp]',
      stringClass = '[object String]';

  /** Used to identify object classifications that `_.clone` supports */
  var cloneableClasses = {};
  cloneableClasses[funcClass] = false;
  cloneableClasses[argsClass] = cloneableClasses[arrayClass] =
  cloneableClasses[boolClass] = cloneableClasses[dateClass] =
  cloneableClasses[numberClass] = cloneableClasses[objectClass] =
  cloneableClasses[regexpClass] = cloneableClasses[stringClass] = true;

  /** Used as an internal `_.debounce` options object */
  var debounceOptions = {
    'leading': false,
    'maxWait': 0,
    'trailing': false
  };

  /** Used as the property descriptor for `__bindData__` */
  var descriptor = {
    'configurable': false,
    'enumerable': false,
    'value': null,
    'writable': false
  };

  /** Used to determine if values are of the language type Object */
  var objectTypes = {
    'boolean': false,
    'function': true,
    'object': true,
    'number': false,
    'string': false,
    'undefined': false
  };

  /** Used to escape characters for inclusion in compiled string literals */
  var stringEscapes = {
    '\\': '\\',
    "'": "'",
    '\n': 'n',
    '\r': 'r',
    '\t': 't',
    '\u2028': 'u2028',
    '\u2029': 'u2029'
  };

  /** Used as a reference to the global object */
  var root = (objectTypes[typeof window] && window) || this;

  /** Detect free variable `exports` */
  var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports;

  /** Detect free variable `module` */
  var freeModule = objectTypes[typeof module] && module && !module.nodeType && module;

  /** Detect the popular CommonJS extension `module.exports` */
  var moduleExports = freeModule && freeModule.exports === freeExports && freeExports;

  /** Detect free variable `global` from Node.js or Browserified code and use it as `root` */
  var freeGlobal = objectTypes[typeof global] && global;
  if (freeGlobal && (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal)) {
    root = freeGlobal;
  }

  /*--------------------------------------------------------------------------*/

  /**
   * The base implementation of `_.indexOf` without support for binary searches
   * or `fromIndex` constraints.
   *
   * @private
   * @param {Array} array The array to search.
   * @param {*} value The value to search for.
   * @param {number} [fromIndex=0] The index to search from.
   * @returns {number} Returns the index of the matched value or `-1`.
   */
  function baseIndexOf(array, value, fromIndex) {
    var index = (fromIndex || 0) - 1,
        length = array ? array.length : 0;

    while (++index < length) {
      if (array[index] === value) {
        return index;
      }
    }
    return -1;
  }

  /**
   * An implementation of `_.contains` for cache objects that mimics the return
   * signature of `_.indexOf` by returning `0` if the value is found, else `-1`.
   *
   * @private
   * @param {Object} cache The cache object to inspect.
   * @param {*} value The value to search for.
   * @returns {number} Returns `0` if `value` is found, else `-1`.
   */
  function cacheIndexOf(cache, value) {
    var type = typeof value;
    cache = cache.cache;

    if (type == 'boolean' || value == null) {
      return cache[value] ? 0 : -1;
    }
    if (type != 'number' && type != 'string') {
      type = 'object';
    }
    var key = type == 'number' ? value : keyPrefix + value;
    cache = (cache = cache[type]) && cache[key];

    return type == 'object'
      ? (cache && baseIndexOf(cache, value) > -1 ? 0 : -1)
      : (cache ? 0 : -1);
  }

  /**
   * Adds a given value to the corresponding cache object.
   *
   * @private
   * @param {*} value The value to add to the cache.
   */
  function cachePush(value) {
    var cache = this.cache,
        type = typeof value;

    if (type == 'boolean' || value == null) {
      cache[value] = true;
    } else {
      if (type != 'number' && type != 'string') {
        type = 'object';
      }
      var key = type == 'number' ? value : keyPrefix + value,
          typeCache = cache[type] || (cache[type] = {});

      if (type == 'object') {
        (typeCache[key] || (typeCache[key] = [])).push(value);
      } else {
        typeCache[key] = true;
      }
    }
  }

  /**
   * Used by `_.max` and `_.min` as the default callback when a given
   * collection is a string value.
   *
   * @private
   * @param {string} value The character to inspect.
   * @returns {number} Returns the code unit of given character.
   */
  function charAtCallback(value) {
    return value.charCodeAt(0);
  }

  /**
   * Used by `sortBy` to compare transformed `collection` elements, stable sorting
   * them in ascending order.
   *
   * @private
   * @param {Object} a The object to compare to `b`.
   * @param {Object} b The object to compare to `a`.
   * @returns {number} Returns the sort order indicator of `1` or `-1`.
   */
  function compareAscending(a, b) {
    var ac = a.criteria,
        bc = b.criteria,
        index = -1,
        length = ac.length;

    while (++index < length) {
      var value = ac[index],
          other = bc[index];

      if (value !== other) {
        if (value > other || typeof value == 'undefined') {
          return 1;
        }
        if (value < other || typeof other == 'undefined') {
          return -1;
        }
      }
    }
    // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications
    // that causes it, under certain circumstances, to return the same value for
    // `a` and `b`. See https://github.com/jashkenas/underscore/pull/1247
    //
    // This also ensures a stable sort in V8 and other engines.
    // See http://code.google.com/p/v8/issues/detail?id=90
    return a.index - b.index;
  }

  /**
   * Creates a cache object to optimize linear searches of large arrays.
   *
   * @private
   * @param {Array} [array=[]] The array to search.
   * @returns {null|Object} Returns the cache object or `null` if caching should not be used.
   */
  function createCache(array) {
    var index = -1,
        length = array.length,
        first = array[0],
        mid = array[(length / 2) | 0],
        last = array[length - 1];

    if (first && typeof first == 'object' &&
        mid && typeof mid == 'object' && last && typeof last == 'object') {
      return false;
    }
    var cache = getObject();
    cache['false'] = cache['null'] = cache['true'] = cache['undefined'] = false;

    var result = getObject();
    result.array = array;
    result.cache = cache;
    result.push = cachePush;

    while (++index < length) {
      result.push(array[index]);
    }
    return result;
  }

  /**
   * Used by `template` to escape characters for inclusion in compiled
   * string literals.
   *
   * @private
   * @param {string} match The matched character to escape.
   * @returns {string} Returns the escaped character.
   */
  function escapeStringChar(match) {
    return '\\' + stringEscapes[match];
  }

  /**
   * Gets an array from the array pool or creates a new one if the pool is empty.
   *
   * @private
   * @returns {Array} The array from the pool.
   */
  function getArray() {
    return arrayPool.pop() || [];
  }

  /**
   * Gets an object from the object pool or creates a new one if the pool is empty.
   *
   * @private
   * @returns {Object} The object from the pool.
   */
  function getObject() {
    return objectPool.pop() || {
      'array': null,
      'cache': null,
      'criteria': null,
      'false': false,
      'index': 0,
      'null': false,
      'number': null,
      'object': null,
      'push': null,
      'string': null,
      'true': false,
      'undefined': false,
      'value': null
    };
  }

  /**
   * Releases the given array back to the array pool.
   *
   * @private
   * @param {Array} [array] The array to release.
   */
  function releaseArray(array) {
    array.length = 0;
    if (arrayPool.length < maxPoolSize) {
      arrayPool.push(array);
    }
  }

  /**
   * Releases the given object back to the object pool.
   *
   * @private
   * @param {Object} [object] The object to release.
   */
  function releaseObject(object) {
    var cache = object.cache;
    if (cache) {
      releaseObject(cache);
    }
    object.array = object.cache = object.criteria = object.object = object.number = object.string = object.value = null;
    if (objectPool.length < maxPoolSize) {
      objectPool.push(object);
    }
  }

  /**
   * Slices the `collection` from the `start` index up to, but not including,
   * the `end` index.
   *
   * Note: This function is used instead of `Array#slice` to support node lists
   * in IE < 9 and to ensure dense arrays are returned.
   *
   * @private
   * @param {Array|Object|string} collection The collection to slice.
   * @param {number} start The start index.
   * @param {number} end The end index.
   * @returns {Array} Returns the new array.
   */
  function slice(array, start, end) {
    start || (start = 0);
    if (typeof end == 'undefined') {
      end = array ? array.length : 0;
    }
    var index = -1,
        length = end - start || 0,
        result = Array(length < 0 ? 0 : length);

    while (++index < length) {
      result[index] = array[start + index];
    }
    return result;
  }

  /*--------------------------------------------------------------------------*/

  /**
   * Create a new `lodash` function using the given context object.
   *
   * @static
   * @memberOf _
   * @category Utilities
   * @param {Object} [context=root] The context object.
   * @returns {Function} Returns the `lodash` function.
   */
  function runInContext(context) {
    // Avoid issues with some ES3 environments that attempt to use values, named
    // after built-in constructors like `Object`, for the creation of literals.
    // ES5 clears this up by stating that literals must use built-in constructors.
    // See http://es5.github.io/#x11.1.5.
    context = context ? _.defaults(root.Object(), context, _.pick(root, contextProps)) : root;

    /** Native constructor references */
    var Array = context.Array,
        Boolean = context.Boolean,
        Date = context.Date,
        Function = context.Function,
        Math = context.Math,
        Number = context.Number,
        Object = context.Object,
        RegExp = context.RegExp,
        String = context.String,
        TypeError = context.TypeError;

    /**
     * Used for `Array` method references.
     *
     * Normally `Array.prototype` would suffice, however, using an array literal
     * avoids issues in Narwhal.
     */
    var arrayRef = [];

    /** Used for native method references */
    var objectProto = Object.prototype;

    /** Used to restore the original `_` reference in `noConflict` */
    var oldDash = context._;

    /** Used to resolve the internal [[Class]] of values */
    var toString = objectProto.toString;

    /** Used to detect if a method is native */
    var reNative = RegExp('^' +
      String(toString)
        .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
        .replace(/toString| for [^\]]+/g, '.*?') + '$'
    );

    /** Native method shortcuts */
    var ceil = Math.ceil,
        clearTimeout = context.clearTimeout,
        floor = Math.floor,
        fnToString = Function.prototype.toString,
        getPrototypeOf = isNative(getPrototypeOf = Object.getPrototypeOf) && getPrototypeOf,
        hasOwnProperty = objectProto.hasOwnProperty,
        push = arrayRef.push,
        setTimeout = context.setTimeout,
        splice = arrayRef.splice,
        unshift = arrayRef.unshift;

    /** Used to set meta data on functions */
    var defineProperty = (function() {
      // IE 8 only accepts DOM elements
      try {
        var o = {},
            func = isNative(func = Object.defineProperty) && func,
            result = func(o, o, o) && func;
      } catch(e) { }
      return result;
    }());

    /* Native method shortcuts for methods with the same name as other `lodash` methods */
    var nativeCreate = isNative(nativeCreate = Object.create) && nativeCreate,
        nativeIsArray = isNative(nativeIsArray = Array.isArray) && nativeIsArray,
        nativeIsFinite = context.isFinite,
        nativeIsNaN = context.isNaN,
        nativeKeys = isNative(nativeKeys = Object.keys) && nativeKeys,
        nativeMax = Math.max,
        nativeMin = Math.min,
        nativeParseInt = context.parseInt,
        nativeRandom = Math.random;

    /** Used to lookup a built-in constructor by [[Class]] */
    var ctorByClass = {};
    ctorByClass[arrayClass] = Array;
    ctorByClass[boolClass] = Boolean;
    ctorByClass[dateClass] = Date;
    ctorByClass[funcClass] = Function;
    ctorByClass[objectClass] = Object;
    ctorByClass[numberClass] = Number;
    ctorByClass[regexpClass] = RegExp;
    ctorByClass[stringClass] = String;

    /*--------------------------------------------------------------------------*/

    /**
     * Creates a `lodash` object which wraps the given value to enable intuitive
     * method chaining.
     *
     * In addition to Lo-Dash methods, wrappers also have the following `Array` methods:
     * `concat`, `join`, `pop`, `push`, `reverse`, `shift`, `slice`, `sort`, `splice`,
     * and `unshift`
     *
     * Chaining is supported in custom builds as long as the `value` method is
     * implicitly or explicitly included in the build.
     *
     * The chainable wrapper functions are:
     * `after`, `assign`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`,
     * `compose`, `concat`, `countBy`, `create`, `createCallback`, `curry`,
     * `debounce`, `defaults`, `defer`, `delay`, `difference`, `filter`, `flatten`,
     * `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`, `forOwnRight`,
     * `functions`, `groupBy`, `indexBy`, `initial`, `intersection`, `invert`,
     * `invoke`, `keys`, `map`, `max`, `memoize`, `merge`, `min`, `object`, `omit`,
     * `once`, `pairs`, `partial`, `partialRight`, `pick`, `pluck`, `pull`, `push`,
     * `range`, `reject`, `remove`, `rest`, `reverse`, `shuffle`, `slice`, `sort`,
     * `sortBy`, `splice`, `tap`, `throttle`, `times`, `toArray`, `transform`,
     * `union`, `uniq`, `unshift`, `unzip`, `values`, `where`, `without`, `wrap`,
     * and `zip`
     *
     * The non-chainable wrapper functions are:
     * `clone`, `cloneDeep`, `contains`, `escape`, `every`, `find`, `findIndex`,
     * `findKey`, `findLast`, `findLastIndex`, `findLastKey`, `has`, `identity`,
     * `indexOf`, `isArguments`, `isArray`, `isBoolean`, `isDate`, `isElement`,
     * `isEmpty`, `isEqual`, `isFinite`, `isFunction`, `isNaN`, `isNull`, `isNumber`,
     * `isObject`, `isPlainObject`, `isRegExp`, `isString`, `isUndefined`, `join`,
     * `lastIndexOf`, `mixin`, `noConflict`, `parseInt`, `pop`, `random`, `reduce`,
     * `reduceRight`, `result`, `shift`, `size`, `some`, `sortedIndex`, `runInContext`,
     * `template`, `unescape`, `uniqueId`, and `value`
     *
     * The wrapper functions `first` and `last` return wrapped values when `n` is
     * provided, otherwise they return unwrapped values.
     *
     * Explicit chaining can be enabled by using the `_.chain` method.
     *
     * @name _
     * @constructor
     * @category Chaining
     * @param {*} value The value to wrap in a `lodash` instance.
     * @returns {Object} Returns a `lodash` instance.
     * @example
     *
     * var wrapped = _([1, 2, 3]);
     *
     * // returns an unwrapped value
     * wrapped.reduce(function(sum, num) {
     *   return sum + num;
     * });
     * // => 6
     *
     * // returns a wrapped value
     * var squares = wrapped.map(function(num) {
     *   return num * num;
     * });
     *
     * _.isArray(squares);
     * // => false
     *
     * _.isArray(squares.value());
     * // => true
     */
    function lodash(value) {
      // don't wrap if already wrapped, even if wrapped by a different `lodash` constructor
      return (value && typeof value == 'object' && !isArray(value) && hasOwnProperty.call(value, '__wrapped__'))
       ? value
       : new lodashWrapper(value);
    }

    /**
     * A fast path for creating `lodash` wrapper objects.
     *
     * @private
     * @param {*} value The value to wrap in a `lodash` instance.
     * @param {boolean} chainAll A flag to enable chaining for all methods
     * @returns {Object} Returns a `lodash` instance.
     */
    function lodashWrapper(value, chainAll) {
      this.__chain__ = !!chainAll;
      this.__wrapped__ = value;
    }
    // ensure `new lodashWrapper` is an instance of `lodash`
    lodashWrapper.prototype = lodash.prototype;

    /**
     * An object used to flag environments features.
     *
     * @static
     * @memberOf _
     * @type Object
     */
    var support = lodash.support = {};

    /**
     * Detect if functions can be decompiled by `Function#toString`
     * (all but PS3 and older Opera mobile browsers & avoided in Windows 8 apps).
     *
     * @memberOf _.support
     * @type boolean
     */
    support.funcDecomp = !isNative(context.WinRTError) && reThis.test(runInContext);

    /**
     * Detect if `Function#name` is supported (all but IE).
     *
     * @memberOf _.support
     * @type boolean
     */
    support.funcNames = typeof Function.name == 'string';

    /**
     * By default, the template delimiters used by Lo-Dash are similar to those in
     * embedded Ruby (ERB). Change the following template settings to use alternative
     * delimiters.
     *
     * @static
     * @memberOf _
     * @type Object
     */
    lodash.templateSettings = {

      /**
       * Used to detect `data` property values to be HTML-escaped.
       *
       * @memberOf _.templateSettings
       * @type RegExp
       */
      'escape': /<%-([\s\S]+?)%>/g,

      /**
       * Used to detect code to be evaluated.
       *
       * @memberOf _.templateSettings
       * @type RegExp
       */
      'evaluate': /<%([\s\S]+?)%>/g,

      /**
       * Used to detect `data` property values to inject.
       *
       * @memberOf _.templateSettings
       * @type RegExp
       */
      'interpolate': reInterpolate,

      /**
       * Used to reference the data object in the template text.
       *
       * @memberOf _.templateSettings
       * @type string
       */
      'variable': '',

      /**
       * Used to import variables into the compiled template.
       *
       * @memberOf _.templateSettings
       * @type Object
       */
      'imports': {

        /**
         * A reference to the `lodash` function.
         *
         * @memberOf _.templateSettings.imports
         * @type Function
         */
        '_': lodash
      }
    };

    /*--------------------------------------------------------------------------*/

    /**
     * The base implementation of `_.bind` that creates the bound function and
     * sets its meta data.
     *
     * @private
     * @param {Array} bindData The bind data array.
     * @returns {Function} Returns the new bound function.
     */
    function baseBind(bindData) {
      var func = bindData[0],
          partialArgs = bindData[2],
          thisArg = bindData[4];

      function bound() {
        // `Function#bind` spec
        // http://es5.github.io/#x15.3.4.5
        if (partialArgs) {
          // avoid `arguments` object deoptimizations by using `slice` instead
          // of `Array.prototype.slice.call` and not assigning `arguments` to a
          // variable as a ternary expression
          var args = slice(partialArgs);
          push.apply(args, arguments);
        }
        // mimic the constructor's `return` behavior
        // http://es5.github.io/#x13.2.2
        if (this instanceof bound) {
          // ensure `new bound` is an instance of `func`
          var thisBinding = baseCreate(func.prototype),
              result = func.apply(thisBinding, args || arguments);
          return isObject(result) ? result : thisBinding;
        }
        return func.apply(thisArg, args || arguments);
      }
      setBindData(bound, bindData);
      return bound;
    }

    /**
     * The base implementation of `_.clone` without argument juggling or support
     * for `thisArg` binding.
     *
     * @private
     * @param {*} value The value to clone.
     * @param {boolean} [isDeep=false] Specify a deep clone.
     * @param {Function} [callback] The function to customize cloning values.
     * @param {Array} [stackA=[]] Tracks traversed source objects.
     * @param {Array} [stackB=[]] Associates clones with source counterparts.
     * @returns {*} Returns the cloned value.
     */
    function baseClone(value, isDeep, callback, stackA, stackB) {
      if (callback) {
        var result = callback(value);
        if (typeof result != 'undefined') {
          return result;
        }
      }
      // inspect [[Class]]
      var isObj = isObject(value);
      if (isObj) {
        var className = toString.call(value);
        if (!cloneableClasses[className]) {
          return value;
        }
        var ctor = ctorByClass[className];
        switch (className) {
          case boolClass:
          case dateClass:
            return new ctor(+value);

          case numberClass:
          case stringClass:
            return new ctor(value);

          case regexpClass:
            result = ctor(value.source, reFlags.exec(value));
            result.lastIndex = value.lastIndex;
            return result;
        }
      } else {
        return value;
      }
      var isArr = isArray(value);
      if (isDeep) {
        // check for circular references and return corresponding clone
        var initedStack = !stackA;
        stackA || (stackA = getArray());
        stackB || (stackB = getArray());

        var length = stackA.length;
        while (length--) {
          if (stackA[length] == value) {
            return stackB[length];
          }
        }
        result = isArr ? ctor(value.length) : {};
      }
      else {
        result = isArr ? slice(value) : assign({}, value);
      }
      // add array properties assigned by `RegExp#exec`
      if (isArr) {
        if (hasOwnProperty.call(value, 'index')) {
          result.index = value.index;
        }
        if (hasOwnProperty.call(value, 'input')) {
          result.input = value.input;
        }
      }
      // exit for shallow clone
      if (!isDeep) {
        return result;
      }
      // add the source value to the stack of traversed objects
      // and associate it with its clone
      stackA.push(value);
      stackB.push(result);

      // recursively populate clone (susceptible to call stack limits)
      (isArr ? forEach : forOwn)(value, function(objValue, key) {
        result[key] = baseClone(objValue, isDeep, callback, stackA, stackB);
      });

      if (initedStack) {
        releaseArray(stackA);
        releaseArray(stackB);
      }
      return result;
    }

    /**
     * The base implementation of `_.create` without support for assigning
     * properties to the created object.
     *
     * @private
     * @param {Object} prototype The object to inherit from.
     * @returns {Object} Returns the new object.
     */
    function baseCreate(prototype, properties) {
      return isObject(prototype) ? nativeCreate(prototype) : {};
    }
    // fallback for browsers without `Object.create`
    if (!nativeCreate) {
      baseCreate = (function() {
        function Object() {}
        return function(prototype) {
          if (isObject(prototype)) {
            Object.prototype = prototype;
            var result = new Object;
            Object.prototype = null;
          }
          return result || context.Object();
        };
      }());
    }

    /**
     * The base implementation of `_.createCallback` without support for creating
     * "_.pluck" or "_.where" style callbacks.
     *
     * @private
     * @param {*} [func=identity] The value to convert to a callback.
     * @param {*} [thisArg] The `this` binding of the created callback.
     * @param {number} [argCount] The number of arguments the callback accepts.
     * @returns {Function} Returns a callback function.
     */
    function baseCreateCallback(func, thisArg, argCount) {
      if (typeof func != 'function') {
        return identity;
      }
      // exit early for no `thisArg` or already bound by `Function#bind`
      if (typeof thisArg == 'undefined' || !('prototype' in func)) {
        return func;
      }
      var bindData = func.__bindData__;
      if (typeof bindData == 'undefined') {
        if (support.funcNames) {
          bindData = !func.name;
        }
        bindData = bindData || !support.funcDecomp;
        if (!bindData) {
          var source = fnToString.call(func);
          if (!support.funcNames) {
            bindData = !reFuncName.test(source);
          }
          if (!bindData) {
            // checks if `func` references the `this` keyword and stores the result
            bindData = reThis.test(source);
            setBindData(func, bindData);
          }
        }
      }
      // exit early if there are no `this` references or `func` is bound
      if (bindData === false || (bindData !== true && bindData[1] & 1)) {
        return func;
      }
      switch (argCount) {
        case 1: return function(value) {
          return func.call(thisArg, value);
        };
        case 2: return function(a, b) {
          return func.call(thisArg, a, b);
        };
        case 3: return function(value, index, collection) {
          return func.call(thisArg, value, index, collection);
        };
        case 4: return function(accumulator, value, index, collection) {
          return func.call(thisArg, accumulator, value, index, collection);
        };
      }
      return bind(func, thisArg);
    }

    /**
     * The base implementation of `createWrapper` that creates the wrapper and
     * sets its meta data.
     *
     * @private
     * @param {Array} bindData The bind data array.
     * @returns {Function} Returns the new function.
     */
    function baseCreateWrapper(bindData) {
      var func = bindData[0],
          bitmask = bindData[1],
          partialArgs = bindData[2],
          partialRightArgs = bindData[3],
          thisArg = bindData[4],
          arity = bindData[5];

      var isBind = bitmask & 1,
          isBindKey = bitmask & 2,
          isCurry = bitmask & 4,
          isCurryBound = bitmask & 8,
          key = func;

      function bound() {
        var thisBinding = isBind ? thisArg : this;
        if (partialArgs) {
          var args = slice(partialArgs);
          push.apply(args, arguments);
        }
        if (partialRightArgs || isCurry) {
          args || (args = slice(arguments));
          if (partialRightArgs) {
            push.apply(args, partialRightArgs);
          }
          if (isCurry && args.length < arity) {
            bitmask |= 16 & ~32;
            return baseCreateWrapper([func, (isCurryBound ? bitmask : bitmask & ~3), args, null, thisArg, arity]);
          }
        }
        args || (args = arguments);
        if (isBindKey) {
          func = thisBinding[key];
        }
        if (this instanceof bound) {
          thisBinding = baseCreate(func.prototype);
          var result = func.apply(thisBinding, args);
          return isObject(result) ? result : thisBinding;
        }
        return func.apply(thisBinding, args);
      }
      setBindData(bound, bindData);
      return bound;
    }

    /**
     * The base implementation of `_.difference` that accepts a single array
     * of values to exclude.
     *
     * @private
     * @param {Array} array The array to process.
     * @param {Array} [values] The array of values to exclude.
     * @returns {Array} Returns a new array of filtered values.
     */
    function baseDifference(array, values) {
      var index = -1,
          indexOf = getIndexOf(),
          length = array ? array.length : 0,
          isLarge = length >= largeArraySize && indexOf === baseIndexOf,
          result = [];

      if (isLarge) {
        var cache = createCache(values);
        if (cache) {
          indexOf = cacheIndexOf;
          values = cache;
        } else {
          isLarge = false;
        }
      }
      while (++index < length) {
        var value = array[index];
        if (indexOf(values, value) < 0) {
          result.push(value);
        }
      }
      if (isLarge) {
        releaseObject(values);
      }
      return result;
    }

    /**
     * The base implementation of `_.flatten` without support for callback
     * shorthands or `thisArg` binding.
     *
     * @private
     * @param {Array} array The array to flatten.
     * @param {boolean} [isShallow=false] A flag to restrict flattening to a single level.
     * @param {boolean} [isStrict=false] A flag to restrict flattening to arrays and `arguments` objects.
     * @param {number} [fromIndex=0] The index to start from.
     * @returns {Array} Returns a new flattened array.
     */
    function baseFlatten(array, isShallow, isStrict, fromIndex) {
      var index = (fromIndex || 0) - 1,
          length = array ? array.length : 0,
          result = [];

      while (++index < length) {
        var value = array[index];

        if (value && typeof value == 'object' && typeof value.length == 'number'
            && (isArray(value) || isArguments(value))) {
          // recursively flatten arrays (susceptible to call stack limits)
          if (!isShallow) {
            value = baseFlatten(value, isShallow, isStrict);
          }
          var valIndex = -1,
              valLength = value.length,
              resIndex = result.length;

          result.length += valLength;
          while (++valIndex < valLength) {
            result[resIndex++] = value[valIndex];
          }
        } else if (!isStrict) {
          result.push(value);
        }
      }
      return result;
    }

    /**
     * The base implementation of `_.isEqual`, without support for `thisArg` binding,
     * that allows partial "_.where" style comparisons.
     *
     * @private
     * @param {*} a The value to compare.
     * @param {*} b The other value to compare.
     * @param {Function} [callback] The function to customize comparing values.
     * @param {Function} [isWhere=false] A flag to indicate performing partial comparisons.
     * @param {Array} [stackA=[]] Tracks traversed `a` objects.
     * @param {Array} [stackB=[]] Tracks traversed `b` objects.
     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
     */
    function baseIsEqual(a, b, callback, isWhere, stackA, stackB) {
      // used to indicate that when comparing objects, `a` has at least the properties of `b`
      if (callback) {
        var result = callback(a, b);
        if (typeof result != 'undefined') {
          return !!result;
        }
      }
      // exit early for identical values
      if (a === b) {
        // treat `+0` vs. `-0` as not equal
        return a !== 0 || (1 / a == 1 / b);
      }
      var type = typeof a,
          otherType = typeof b;

      // exit early for unlike primitive values
      if (a === a &&
          !(a && objectTypes[type]) &&
          !(b && objectTypes[otherType])) {
        return false;
      }
      // exit early for `null` and `undefined` avoiding ES3's Function#call behavior
      // http://es5.github.io/#x15.3.4.4
      if (a == null || b == null) {
        return a === b;
      }
      // compare [[Class]] names
      var className = toString.call(a),
          otherClass = toString.call(b);

      if (className == argsClass) {
        className = objectClass;
      }
      if (otherClass == argsClass) {
        otherClass = objectClass;
      }
      if (className != otherClass) {
        return false;
      }
      switch (className) {
        case boolClass:
        case dateClass:
          // coerce dates and booleans to numbers, dates to milliseconds and booleans
          // to `1` or `0` treating invalid dates coerced to `NaN` as not equal
          return +a == +b;

        case numberClass:
          // treat `NaN` vs. `NaN` as equal
          return (a != +a)
            ? b != +b
            // but treat `+0` vs. `-0` as not equal
            : (a == 0 ? (1 / a == 1 / b) : a == +b);

        case regexpClass:
        case stringClass:
          // coerce regexes to strings (http://es5.github.io/#x15.10.6.4)
          // treat string primitives and their corresponding object instances as equal
          return a == String(b);
      }
      var isArr = className == arrayClass;
      if (!isArr) {
        // unwrap any `lodash` wrapped values
        var aWrapped = hasOwnProperty.call(a, '__wrapped__'),
            bWrapped = hasOwnProperty.call(b, '__wrapped__');

        if (aWrapped || bWrapped) {
          return baseIsEqual(aWrapped ? a.__wrapped__ : a, bWrapped ? b.__wrapped__ : b, callback, isWhere, stackA, stackB);
        }
        // exit for functions and DOM nodes
        if (className != objectClass) {
          return false;
        }
        // in older versions of Opera, `arguments` objects have `Array` constructors
        var ctorA = a.constructor,
            ctorB = b.constructor;

        // non `Object` object instances with different constructors are not equal
        if (ctorA != ctorB &&
              !(isFunction(ctorA) && ctorA instanceof ctorA && isFunction(ctorB) && ctorB instanceof ctorB) &&
              ('constructor' in a && 'constructor' in b)
            ) {
          return false;
        }
      }
      // assume cyclic structures are equal
      // the algorithm for detecting cyclic structures is adapted from ES 5.1
      // section 15.12.3, abstract operation `JO` (http://es5.github.io/#x15.12.3)
      var initedStack = !stackA;
      stackA || (stackA = getArray());
      stackB || (stackB = getArray());

      var length = stackA.length;
      while (length--) {
        if (stackA[length] == a) {
          return stackB[length] == b;
        }
      }
      var size = 0;
      result = true;

      // add `a` and `b` to the stack of traversed objects
      stackA.push(a);
      stackB.push(b);

      // recursively compare objects and arrays (susceptible to call stack limits)
      if (isArr) {
        // compare lengths to determine if a deep comparison is necessary
        length = a.length;
        size = b.length;
        result = size == length;

        if (result || isWhere) {
          // deep compare the contents, ignoring non-numeric properties
          while (size--) {
            var index = length,
                value = b[size];

            if (isWhere) {
              while (index--) {
                if ((result = baseIsEqual(a[index], value, callback, isWhere, stackA, stackB))) {
                  break;
                }
              }
            } else if (!(result = baseIsEqual(a[size], value, callback, isWhere, stackA, stackB))) {
              break;
            }
          }
        }
      }
      else {
        // deep compare objects using `forIn`, instead of `forOwn`, to avoid `Object.keys`
        // which, in this case, is more costly
        forIn(b, function(value, key, b) {
          if (hasOwnProperty.call(b, key)) {
            // count the number of properties.
            size++;
            // deep compare each property value.
            return (result = hasOwnProperty.call(a, key) && baseIsEqual(a[key], value, callback, isWhere, stackA, stackB));
          }
        });

        if (result && !isWhere) {
          // ensure both objects have the same number of properties
          forIn(a, function(value, key, a) {
            if (hasOwnProperty.call(a, key)) {
              // `size` will be `-1` if `a` has more properties than `b`
              return (result = --size > -1);
            }
          });
        }
      }
      stackA.pop();
      stackB.pop();

      if (initedStack) {
        releaseArray(stackA);
        releaseArray(stackB);
      }
      return result;
    }

    /**
     * The base implementation of `_.merge` without argument juggling or support
     * for `thisArg` binding.
     *
     * @private
     * @param {Object} object The destination object.
     * @param {Object} source The source object.
     * @param {Function} [callback] The function to customize merging properties.
     * @param {Array} [stackA=[]] Tracks traversed source objects.
     * @param {Array} [stackB=[]] Associates values with source counterparts.
     */
    function baseMerge(object, source, callback, stackA, stackB) {
      (isArray(source) ? forEach : forOwn)(source, function(source, key) {
        var found,
            isArr,
            result = source,
            value = object[key];

        if (source && ((isArr = isArray(source)) || isPlainObject(source))) {
          // avoid merging previously merged cyclic sources
          var stackLength = stackA.length;
          while (stackLength--) {
            if ((found = stackA[stackLength] == source)) {
              value = stackB[stackLength];
              break;
            }
          }
          if (!found) {
            var isShallow;
            if (callback) {
              result = callback(value, source);
              if ((isShallow = typeof result != 'undefined')) {
                value = result;
              }
            }
            if (!isShallow) {
              value = isArr
                ? (isArray(value) ? value : [])
                : (isPlainObject(value) ? value : {});
            }
            // add `source` and associated `value` to the stack of traversed objects
            stackA.push(source);
            stackB.push(value);

            // recursively merge objects and arrays (susceptible to call stack limits)
            if (!isShallow) {
              baseMerge(value, source, callback, stackA, stackB);
            }
          }
        }
        else {
          if (callback) {
            result = callback(value, source);
            if (typeof result == 'undefined') {
              result = source;
            }
          }
          if (typeof result != 'undefined') {
            value = result;
          }
        }
        object[key] = value;
      });
    }

    /**
     * The base implementation of `_.random` without argument juggling or support
     * for returning floating-point numbers.
     *
     * @private
     * @param {number} min The minimum possible value.
     * @param {number} max The maximum possible value.
     * @returns {number} Returns a random number.
     */
    function baseRandom(min, max) {
      return min + floor(nativeRandom() * (max - min + 1));
    }

    /**
     * The base implementation of `_.uniq` without support for callback shorthands
     * or `thisArg` binding.
     *
     * @private
     * @param {Array} array The array to process.
     * @param {boolean} [isSorted=false] A flag to indicate that `array` is sorted.
     * @param {Function} [callback] The function called per iteration.
     * @returns {Array} Returns a duplicate-value-free array.
     */
    function baseUniq(array, isSorted, callback) {
      var index = -1,
          indexOf = getIndexOf(),
          length = array ? array.length : 0,
          result = [];

      var isLarge = !isSorted && length >= largeArraySize && indexOf === baseIndexOf,
          seen = (callback || isLarge) ? getArray() : result;

      if (isLarge) {
        var cache = createCache(seen);
        indexOf = cacheIndexOf;
        seen = cache;
      }
      while (++index < length) {
        var value = array[index],
            computed = callback ? callback(value, index, array) : value;

        if (isSorted
              ? !index || seen[seen.length - 1] !== computed
              : indexOf(seen, computed) < 0
            ) {
          if (callback || isLarge) {
            seen.push(computed);
          }
          result.push(value);
        }
      }
      if (isLarge) {
        releaseArray(seen.array);
        releaseObject(seen);
      } else if (callback) {
        releaseArray(seen);
      }
      return result;
    }

    /**
     * Creates a function that aggregates a collection, creating an object composed
     * of keys generated from the results of running each element of the collection
     * through a callback. The given `setter` function sets the keys and values
     * of the composed object.
     *
     * @private
     * @param {Function} setter The setter function.
     * @returns {Function} Returns the new aggregator function.
     */
    function createAggregator(setter) {
      return function(collection, callback, thisArg) {
        var result = {};
        callback = lodash.createCallback(callback, thisArg, 3);

        var index = -1,
            length = collection ? collection.length : 0;

        if (typeof length == 'number') {
          while (++index < length) {
            var value = collection[index];
            setter(result, value, callback(value, index, collection), collection);
          }
        } else {
          forOwn(collection, function(value, key, collection) {
            setter(result, value, callback(value, key, collection), collection);
          });
        }
        return result;
      };
    }

    /**
     * Creates a function that, when called, either curries or invokes `func`
     * with an optional `this` binding and partially applied arguments.
     *
     * @private
     * @param {Function|string} func The function or method name to reference.
     * @param {number} bitmask The bitmask of method flags to compose.
     *  The bitmask may be composed of the following flags:
     *  1 - `_.bind`
     *  2 - `_.bindKey`
     *  4 - `_.curry`
     *  8 - `_.curry` (bound)
     *  16 - `_.partial`
     *  32 - `_.partialRight`
     * @param {Array} [partialArgs] An array of arguments to prepend to those
     *  provided to the new function.
     * @param {Array} [partialRightArgs] An array of arguments to append to those
     *  provided to the new function.
     * @param {*} [thisArg] The `this` binding of `func`.
     * @param {number} [arity] The arity of `func`.
     * @returns {Function} Returns the new function.
     */
    function createWrapper(func, bitmask, partialArgs, partialRightArgs, thisArg, arity) {
      var isBind = bitmask & 1,
          isBindKey = bitmask & 2,
          isCurry = bitmask & 4,
          isCurryBound = bitmask & 8,
          isPartial = bitmask & 16,
          isPartialRight = bitmask & 32;

      if (!isBindKey && !isFunction(func)) {
        throw new TypeError;
      }
      if (isPartial && !partialArgs.length) {
        bitmask &= ~16;
        isPartial = partialArgs = false;
      }
      if (isPartialRight && !partialRightArgs.length) {
        bitmask &= ~32;
        isPartialRight = partialRightArgs = false;
      }
      var bindData = func && func.__bindData__;
      if (bindData && bindData !== true) {
        // clone `bindData`
        bindData = slice(bindData);
        if (bindData[2]) {
          bindData[2] = slice(bindData[2]);
        }
        if (bindData[3]) {
          bindData[3] = slice(bindData[3]);
        }
        // set `thisBinding` is not previously bound
        if (isBind && !(bindData[1] & 1)) {
          bindData[4] = thisArg;
        }
        // set if previously bound but not currently (subsequent curried functions)
        if (!isBind && bindData[1] & 1) {
          bitmask |= 8;
        }
        // set curried arity if not yet set
        if (isCurry && !(bindData[1] & 4)) {
          bindData[5] = arity;
        }
        // append partial left arguments
        if (isPartial) {
          push.apply(bindData[2] || (bindData[2] = []), partialArgs);
        }
        // append partial right arguments
        if (isPartialRight) {
          unshift.apply(bindData[3] || (bindData[3] = []), partialRightArgs);
        }
        // merge flags
        bindData[1] |= bitmask;
        return createWrapper.apply(null, bindData);
      }
      // fast path for `_.bind`
      var creater = (bitmask == 1 || bitmask === 17) ? baseBind : baseCreateWrapper;
      return creater([func, bitmask, partialArgs, partialRightArgs, thisArg, arity]);
    }

    /**
     * Used by `escape` to convert characters to HTML entities.
     *
     * @private
     * @param {string} match The matched character to escape.
     * @returns {string} Returns the escaped character.
     */
    function escapeHtmlChar(match) {
      return htmlEscapes[match];
    }

    /**
     * Gets the appropriate "indexOf" function. If the `_.indexOf` method is
     * customized, this method returns the custom method, otherwise it returns
     * the `baseIndexOf` function.
     *
     * @private
     * @returns {Function} Returns the "indexOf" function.
     */
    function getIndexOf() {
      var result = (result = lodash.indexOf) === indexOf ? baseIndexOf : result;
      return result;
    }

    /**
     * Checks if `value` is a native function.
     *
     * @private
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if the `value` is a native function, else `false`.
     */
    function isNative(value) {
      return typeof value == 'function' && reNative.test(value);
    }

    /**
     * Sets `this` binding data on a given function.
     *
     * @private
     * @param {Function} func The function to set data on.
     * @param {Array} value The data array to set.
     */
    var setBindData = !defineProperty ? noop : function(func, value) {
      descriptor.value = value;
      defineProperty(func, '__bindData__', descriptor);
    };

    /**
     * A fallback implementation of `isPlainObject` which checks if a given value
     * is an object created by the `Object` constructor, assuming objects created
     * by the `Object` constructor have no inherited enumerable properties and that
     * there are no `Object.prototype` extensions.
     *
     * @private
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
     */
    function shimIsPlainObject(value) {
      var ctor,
          result;

      // avoid non Object objects, `arguments` objects, and DOM elements
      if (!(value && toString.call(value) == objectClass) ||
          (ctor = value.constructor, isFunction(ctor) && !(ctor instanceof ctor))) {
        return false;
      }
      // In most environments an object's own properties are iterated before
      // its inherited properties. If the last iterated property is an object's
      // own property then there are no inherited enumerable properties.
      forIn(value, function(value, key) {
        result = key;
      });
      return typeof result == 'undefined' || hasOwnProperty.call(value, result);
    }

    /**
     * Used by `unescape` to convert HTML entities to characters.
     *
     * @private
     * @param {string} match The matched character to unescape.
     * @returns {string} Returns the unescaped character.
     */
    function unescapeHtmlChar(match) {
      return htmlUnescapes[match];
    }

    /*--------------------------------------------------------------------------*/

    /**
     * Checks if `value` is an `arguments` object.
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if the `value` is an `arguments` object, else `false`.
     * @example
     *
     * (function() { return _.isArguments(arguments); })(1, 2, 3);
     * // => true
     *
     * _.isArguments([1, 2, 3]);
     * // => false
     */
    function isArguments(value) {
      return value && typeof value == 'object' && typeof value.length == 'number' &&
        toString.call(value) == argsClass || false;
    }

    /**
     * Checks if `value` is an array.
     *
     * @static
     * @memberOf _
     * @type Function
     * @category Objects
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if the `value` is an array, else `false`.
     * @example
     *
     * (function() { return _.isArray(arguments); })();
     * // => false
     *
     * _.isArray([1, 2, 3]);
     * // => true
     */
    var isArray = nativeIsArray || function(value) {
      return value && typeof value == 'object' && typeof value.length == 'number' &&
        toString.call(value) == arrayClass || false;
    };

    /**
     * A fallback implementation of `Object.keys` which produces an array of the
     * given object's own enumerable property names.
     *
     * @private
     * @type Function
     * @param {Object} object The object to inspect.
     * @returns {Array} Returns an array of property names.
     */
    var shimKeys = function(object) {
      var index, iterable = object, result = [];
      if (!iterable) return result;
      if (!(objectTypes[typeof object])) return result;
        for (index in iterable) {
          if (hasOwnProperty.call(iterable, index)) {
            result.push(index);
          }
        }
      return result
    };

    /**
     * Creates an array composed of the own enumerable property names of an object.
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {Object} object The object to inspect.
     * @returns {Array} Returns an array of property names.
     * @example
     *
     * _.keys({ 'one': 1, 'two': 2, 'three': 3 });
     * // => ['one', 'two', 'three'] (property order is not guaranteed across environments)
     */
    var keys = !nativeKeys ? shimKeys : function(object) {
      if (!isObject(object)) {
        return [];
      }
      return nativeKeys(object);
    };

    /**
     * Used to convert characters to HTML entities:
     *
     * Though the `>` character is escaped for symmetry, characters like `>` and `/`
     * don't require escaping in HTML and have no special meaning unless they're part
     * of a tag or an unquoted attribute value.
     * http://mathiasbynens.be/notes/ambiguous-ampersands (under "semi-related fun fact")
     */
    var htmlEscapes = {
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      '"': '&quot;',
      "'": '&#39;'
    };

    /** Used to convert HTML entities to characters */
    var htmlUnescapes = invert(htmlEscapes);

    /** Used to match HTML entities and HTML characters */
    var reEscapedHtml = RegExp('(' + keys(htmlUnescapes).join('|') + ')', 'g'),
        reUnescapedHtml = RegExp('[' + keys(htmlEscapes).join('') + ']', 'g');

    /*--------------------------------------------------------------------------*/

    /**
     * Assigns own enumerable properties of source object(s) to the destination
     * object. Subsequent sources will overwrite property assignments of previous
     * sources. If a callback is provided it will be executed to produce the
     * assigned values. The callback is bound to `thisArg` and invoked with two
     * arguments; (objectValue, sourceValue).
     *
     * @static
     * @memberOf _
     * @type Function
     * @alias extend
     * @category Objects
     * @param {Object} object The destination object.
     * @param {...Object} [source] The source objects.
     * @param {Function} [callback] The function to customize assigning values.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {Object} Returns the destination object.
     * @example
     *
     * _.assign({ 'name': 'fred' }, { 'employer': 'slate' });
     * // => { 'name': 'fred', 'employer': 'slate' }
     *
     * var defaults = _.partialRight(_.assign, function(a, b) {
     *   return typeof a == 'undefined' ? b : a;
     * });
     *
     * var object = { 'name': 'barney' };
     * defaults(object, { 'name': 'fred', 'employer': 'slate' });
     * // => { 'name': 'barney', 'employer': 'slate' }
     */
    var assign = function(object, source, guard) {
      var index, iterable = object, result = iterable;
      if (!iterable) return result;
      var args = arguments,
          argsIndex = 0,
          argsLength = typeof guard == 'number' ? 2 : args.length;
      if (argsLength > 3 && typeof args[argsLength - 2] == 'function') {
        var callback = baseCreateCallback(args[--argsLength - 1], args[argsLength--], 2);
      } else if (argsLength > 2 && typeof args[argsLength - 1] == 'function') {
        callback = args[--argsLength];
      }
      while (++argsIndex < argsLength) {
        iterable = args[argsIndex];
        if (iterable && objectTypes[typeof iterable]) {
        var ownIndex = -1,
            ownProps = objectTypes[typeof iterable] && keys(iterable),
            length = ownProps ? ownProps.length : 0;

        while (++ownIndex < length) {
          index = ownProps[ownIndex];
          result[index] = callback ? callback(result[index], iterable[index]) : iterable[index];
        }
        }
      }
      return result
    };

    /**
     * Creates a clone of `value`. If `isDeep` is `true` nested objects will also
     * be cloned, otherwise they will be assigned by reference. If a callback
     * is provided it will be executed to produce the cloned values. If the
     * callback returns `undefined` cloning will be handled by the method instead.
     * The callback is bound to `thisArg` and invoked with one argument; (value).
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {*} value The value to clone.
     * @param {boolean} [isDeep=false] Specify a deep clone.
     * @param {Function} [callback] The function to customize cloning values.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {*} Returns the cloned value.
     * @example
     *
     * var characters = [
     *   { 'name': 'barney', 'age': 36 },
     *   { 'name': 'fred',   'age': 40 }
     * ];
     *
     * var shallow = _.clone(characters);
     * shallow[0] === characters[0];
     * // => true
     *
     * var deep = _.clone(characters, true);
     * deep[0] === characters[0];
     * // => false
     *
     * _.mixin({
     *   'clone': _.partialRight(_.clone, function(value) {
     *     return _.isElement(value) ? value.cloneNode(false) : undefined;
     *   })
     * });
     *
     * var clone = _.clone(document.body);
     * clone.childNodes.length;
     * // => 0
     */
    function clone(value, isDeep, callback, thisArg) {
      // allows working with "Collections" methods without using their `index`
      // and `collection` arguments for `isDeep` and `callback`
      if (typeof isDeep != 'boolean' && isDeep != null) {
        thisArg = callback;
        callback = isDeep;
        isDeep = false;
      }
      return baseClone(value, isDeep, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 1));
    }

    /**
     * Creates a deep clone of `value`. If a callback is provided it will be
     * executed to produce the cloned values. If the callback returns `undefined`
     * cloning will be handled by the method instead. The callback is bound to
     * `thisArg` and invoked with one argument; (value).
     *
     * Note: This method is loosely based on the structured clone algorithm. Functions
     * and DOM nodes are **not** cloned. The enumerable properties of `arguments` objects and
     * objects created by constructors other than `Object` are cloned to plain `Object` objects.
     * See http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm.
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {*} value The value to deep clone.
     * @param {Function} [callback] The function to customize cloning values.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {*} Returns the deep cloned value.
     * @example
     *
     * var characters = [
     *   { 'name': 'barney', 'age': 36 },
     *   { 'name': 'fred',   'age': 40 }
     * ];
     *
     * var deep = _.cloneDeep(characters);
     * deep[0] === characters[0];
     * // => false
     *
     * var view = {
     *   'label': 'docs',
     *   'node': element
     * };
     *
     * var clone = _.cloneDeep(view, function(value) {
     *   return _.isElement(value) ? value.cloneNode(true) : undefined;
     * });
     *
     * clone.node == view.node;
     * // => false
     */
    function cloneDeep(value, callback, thisArg) {
      return baseClone(value, true, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 1));
    }

    /**
     * Creates an object that inherits from the given `prototype` object. If a
     * `properties` object is provided its own enumerable properties are assigned
     * to the created object.
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {Object} prototype The object to inherit from.
     * @param {Object} [properties] The properties to assign to the object.
     * @returns {Object} Returns the new object.
     * @example
     *
     * function Shape() {
     *   this.x = 0;
     *   this.y = 0;
     * }
     *
     * function Circle() {
     *   Shape.call(this);
     * }
     *
     * Circle.prototype = _.create(Shape.prototype, { 'constructor': Circle });
     *
     * var circle = new Circle;
     * circle instanceof Circle;
     * // => true
     *
     * circle instanceof Shape;
     * // => true
     */
    function create(prototype, properties) {
      var result = baseCreate(prototype);
      return properties ? assign(result, properties) : result;
    }

    /**
     * Assigns own enumerable properties of source object(s) to the destination
     * object for all destination properties that resolve to `undefined`. Once a
     * property is set, additional defaults of the same property will be ignored.
     *
     * @static
     * @memberOf _
     * @type Function
     * @category Objects
     * @param {Object} object The destination object.
     * @param {...Object} [source] The source objects.
     * @param- {Object} [guard] Allows working with `_.reduce` without using its
     *  `key` and `object` arguments as sources.
     * @returns {Object} Returns the destination object.
     * @example
     *
     * var object = { 'name': 'barney' };
     * _.defaults(object, { 'name': 'fred', 'employer': 'slate' });
     * // => { 'name': 'barney', 'employer': 'slate' }
     */
    var defaults = function(object, source, guard) {
      var index, iterable = object, result = iterable;
      if (!iterable) return result;
      var args = arguments,
          argsIndex = 0,
          argsLength = typeof guard == 'number' ? 2 : args.length;
      while (++argsIndex < argsLength) {
        iterable = args[argsIndex];
        if (iterable && objectTypes[typeof iterable]) {
        var ownIndex = -1,
            ownProps = objectTypes[typeof iterable] && keys(iterable),
            length = ownProps ? ownProps.length : 0;

        while (++ownIndex < length) {
          index = ownProps[ownIndex];
          if (typeof result[index] == 'undefined') result[index] = iterable[index];
        }
        }
      }
      return result
    };

    /**
     * This method is like `_.findIndex` except that it returns the key of the
     * first element that passes the callback check, instead of the element itself.
     *
     * If a property name is provided for `callback` the created "_.pluck" style
     * callback will return the property value of the given element.
     *
     * If an object is provided for `callback` the created "_.where" style callback
     * will return `true` for elements that have the properties of the given object,
     * else `false`.
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {Object} object The object to search.
     * @param {Function|Object|string} [callback=identity] The function called per
     *  iteration. If a property name or object is provided it will be used to
     *  create a "_.pluck" or "_.where" style callback, respectively.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {string|undefined} Returns the key of the found element, else `undefined`.
     * @example
     *
     * var characters = {
     *   'barney': {  'age': 36, 'blocked': false },
     *   'fred': {    'age': 40, 'blocked': true },
     *   'pebbles': { 'age': 1,  'blocked': false }
     * };
     *
     * _.findKey(characters, function(chr) {
     *   return chr.age < 40;
     * });
     * // => 'barney' (property order is not guaranteed across environments)
     *
     * // using "_.where" callback shorthand
     * _.findKey(characters, { 'age': 1 });
     * // => 'pebbles'
     *
     * // using "_.pluck" callback shorthand
     * _.findKey(characters, 'blocked');
     * // => 'fred'
     */
    function findKey(object, callback, thisArg) {
      var result;
      callback = lodash.createCallback(callback, thisArg, 3);
      forOwn(object, function(value, key, object) {
        if (callback(value, key, object)) {
          result = key;
          return false;
        }
      });
      return result;
    }

    /**
     * This method is like `_.findKey` except that it iterates over elements
     * of a `collection` in the opposite order.
     *
     * If a property name is provided for `callback` the created "_.pluck" style
     * callback will return the property value of the given element.
     *
     * If an object is provided for `callback` the created "_.where" style callback
     * will return `true` for elements that have the properties of the given object,
     * else `false`.
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {Object} object The object to search.
     * @param {Function|Object|string} [callback=identity] The function called per
     *  iteration. If a property name or object is provided it will be used to
     *  create a "_.pluck" or "_.where" style callback, respectively.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {string|undefined} Returns the key of the found element, else `undefined`.
     * @example
     *
     * var characters = {
     *   'barney': {  'age': 36, 'blocked': true },
     *   'fred': {    'age': 40, 'blocked': false },
     *   'pebbles': { 'age': 1,  'blocked': true }
     * };
     *
     * _.findLastKey(characters, function(chr) {
     *   return chr.age < 40;
     * });
     * // => returns `pebbles`, assuming `_.findKey` returns `barney`
     *
     * // using "_.where" callback shorthand
     * _.findLastKey(characters, { 'age': 40 });
     * // => 'fred'
     *
     * // using "_.pluck" callback shorthand
     * _.findLastKey(characters, 'blocked');
     * // => 'pebbles'
     */
    function findLastKey(object, callback, thisArg) {
      var result;
      callback = lodash.createCallback(callback, thisArg, 3);
      forOwnRight(object, function(value, key, object) {
        if (callback(value, key, object)) {
          result = key;
          return false;
        }
      });
      return result;
    }

    /**
     * Iterates over own and inherited enumerable properties of an object,
     * executing the callback for each property. The callback is bound to `thisArg`
     * and invoked with three arguments; (value, key, object). Callbacks may exit
     * iteration early by explicitly returning `false`.
     *
     * @static
     * @memberOf _
     * @type Function
     * @category Objects
     * @param {Object} object The object to iterate over.
     * @param {Function} [callback=identity] The function called per iteration.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {Object} Returns `object`.
     * @example
     *
     * function Shape() {
     *   this.x = 0;
     *   this.y = 0;
     * }
     *
     * Shape.prototype.move = function(x, y) {
     *   this.x += x;
     *   this.y += y;
     * };
     *
     * _.forIn(new Shape, function(value, key) {
     *   console.log(key);
     * });
     * // => logs 'x', 'y', and 'move' (property order is not guaranteed across environments)
     */
    var forIn = function(collection, callback, thisArg) {
      var index, iterable = collection, result = iterable;
      if (!iterable) return result;
      if (!objectTypes[typeof iterable]) return result;
      callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
        for (index in iterable) {
          if (callback(iterable[index], index, collection) === false) return result;
        }
      return result
    };

    /**
     * This method is like `_.forIn` except that it iterates over elements
     * of a `collection` in the opposite order.
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {Object} object The object to iterate over.
     * @param {Function} [callback=identity] The function called per iteration.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {Object} Returns `object`.
     * @example
     *
     * function Shape() {
     *   this.x = 0;
     *   this.y = 0;
     * }
     *
     * Shape.prototype.move = function(x, y) {
     *   this.x += x;
     *   this.y += y;
     * };
     *
     * _.forInRight(new Shape, function(value, key) {
     *   console.log(key);
     * });
     * // => logs 'move', 'y', and 'x' assuming `_.forIn ` logs 'x', 'y', and 'move'
     */
    function forInRight(object, callback, thisArg) {
      var pairs = [];

      forIn(object, function(value, key) {
        pairs.push(key, value);
      });

      var length = pairs.length;
      callback = baseCreateCallback(callback, thisArg, 3);
      while (length--) {
        if (callback(pairs[length--], pairs[length], object) === false) {
          break;
        }
      }
      return object;
    }

    /**
     * Iterates over own enumerable properties of an object, executing the callback
     * for each property. The callback is bound to `thisArg` and invoked with three
     * arguments; (value, key, object). Callbacks may exit iteration early by
     * explicitly returning `false`.
     *
     * @static
     * @memberOf _
     * @type Function
     * @category Objects
     * @param {Object} object The object to iterate over.
     * @param {Function} [callback=identity] The function called per iteration.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {Object} Returns `object`.
     * @example
     *
     * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) {
     *   console.log(key);
     * });
     * // => logs '0', '1', and 'length' (property order is not guaranteed across environments)
     */
    var forOwn = function(collection, callback, thisArg) {
      var index, iterable = collection, result = iterable;
      if (!iterable) return result;
      if (!objectTypes[typeof iterable]) return result;
      callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
        var ownIndex = -1,
            ownProps = objectTypes[typeof iterable] && keys(iterable),
            length = ownProps ? ownProps.length : 0;

        while (++ownIndex < length) {
          index = ownProps[ownIndex];
          if (callback(iterable[index], index, collection) === false) return result;
        }
      return result
    };

    /**
     * This method is like `_.forOwn` except that it iterates over elements
     * of a `collection` in the opposite order.
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {Object} object The object to iterate over.
     * @param {Function} [callback=identity] The function called per iteration.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {Object} Returns `object`.
     * @example
     *
     * _.forOwnRight({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) {
     *   console.log(key);
     * });
     * // => logs 'length', '1', and '0' assuming `_.forOwn` logs '0', '1', and 'length'
     */
    function forOwnRight(object, callback, thisArg) {
      var props = keys(object),
          length = props.length;

      callback = baseCreateCallback(callback, thisArg, 3);
      while (length--) {
        var key = props[length];
        if (callback(object[key], key, object) === false) {
          break;
        }
      }
      return object;
    }

    /**
     * Creates a sorted array of property names of all enumerable properties,
     * own and inherited, of `object` that have function values.
     *
     * @static
     * @memberOf _
     * @alias methods
     * @category Objects
     * @param {Object} object The object to inspect.
     * @returns {Array} Returns an array of property names that have function values.
     * @example
     *
     * _.functions(_);
     * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...]
     */
    function functions(object) {
      var result = [];
      forIn(object, function(value, key) {
        if (isFunction(value)) {
          result.push(key);
        }
      });
      return result.sort();
    }

    /**
     * Checks if the specified property name exists as a direct property of `object`,
     * instead of an inherited property.
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {Object} object The object to inspect.
     * @param {string} key The name of the property to check.
     * @returns {boolean} Returns `true` if key is a direct property, else `false`.
     * @example
     *
     * _.has({ 'a': 1, 'b': 2, 'c': 3 }, 'b');
     * // => true
     */
    function has(object, key) {
      return object ? hasOwnProperty.call(object, key) : false;
    }

    /**
     * Creates an object composed of the inverted keys and values of the given object.
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {Object} object The object to invert.
     * @returns {Object} Returns the created inverted object.
     * @example
     *
     * _.invert({ 'first': 'fred', 'second': 'barney' });
     * // => { 'fred': 'first', 'barney': 'second' }
     */
    function invert(object) {
      var index = -1,
          props = keys(object),
          length = props.length,
          result = {};

      while (++index < length) {
        var key = props[index];
        result[object[key]] = key;
      }
      return result;
    }

    /**
     * Checks if `value` is a boolean value.
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if the `value` is a boolean value, else `false`.
     * @example
     *
     * _.isBoolean(null);
     * // => false
     */
    function isBoolean(value) {
      return value === true || value === false ||
        value && typeof value == 'object' && toString.call(value) == boolClass || false;
    }

    /**
     * Checks if `value` is a date.
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if the `value` is a date, else `false`.
     * @example
     *
     * _.isDate(new Date);
     * // => true
     */
    function isDate(value) {
      return value && typeof value == 'object' && toString.call(value) == dateClass || false;
    }

    /**
     * Checks if `value` is a DOM element.
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if the `value` is a DOM element, else `false`.
     * @example
     *
     * _.isElement(document.body);
     * // => true
     */
    function isElement(value) {
      return value && value.nodeType === 1 || false;
    }

    /**
     * Checks if `value` is empty. Arrays, strings, or `arguments` objects with a
     * length of `0` and objects with no own enumerable properties are considered
     * "empty".
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {Array|Object|string} value The value to inspect.
     * @returns {boolean} Returns `true` if the `value` is empty, else `false`.
     * @example
     *
     * _.isEmpty([1, 2, 3]);
     * // => false
     *
     * _.isEmpty({});
     * // => true
     *
     * _.isEmpty('');
     * // => true
     */
    function isEmpty(value) {
      var result = true;
      if (!value) {
        return result;
      }
      var className = toString.call(value),
          length = value.length;

      if ((className == arrayClass || className == stringClass || className == argsClass ) ||
          (className == objectClass && typeof length == 'number' && isFunction(value.splice))) {
        return !length;
      }
      forOwn(value, function() {
        return (result = false);
      });
      return result;
    }

    /**
     * Performs a deep comparison between two values to determine if they are
     * equivalent to each other. If a callback is provided it will be executed
     * to compare values. If the callback returns `undefined` comparisons will
     * be handled by the method instead. The callback is bound to `thisArg` and
     * invoked with two arguments; (a, b).
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {*} a The value to compare.
     * @param {*} b The other value to compare.
     * @param {Function} [callback] The function to customize comparing values.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
     * @example
     *
     * var object = { 'name': 'fred' };
     * var copy = { 'name': 'fred' };
     *
     * object == copy;
     * // => false
     *
     * _.isEqual(object, copy);
     * // => true
     *
     * var words = ['hello', 'goodbye'];
     * var otherWords = ['hi', 'goodbye'];
     *
     * _.isEqual(words, otherWords, function(a, b) {
     *   var reGreet = /^(?:hello|hi)$/i,
     *       aGreet = _.isString(a) && reGreet.test(a),
     *       bGreet = _.isString(b) && reGreet.test(b);
     *
     *   return (aGreet || bGreet) ? (aGreet == bGreet) : undefined;
     * });
     * // => true
     */
    function isEqual(a, b, callback, thisArg) {
      return baseIsEqual(a, b, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 2));
    }

    /**
     * Checks if `value` is, or can be coerced to, a finite number.
     *
     * Note: This is not the same as native `isFinite` which will return true for
     * booleans and empty strings. See http://es5.github.io/#x15.1.2.5.
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if the `value` is finite, else `false`.
     * @example
     *
     * _.isFinite(-101);
     * // => true
     *
     * _.isFinite('10');
     * // => true
     *
     * _.isFinite(true);
     * // => false
     *
     * _.isFinite('');
     * // => false
     *
     * _.isFinite(Infinity);
     * // => false
     */
    function isFinite(value) {
      return nativeIsFinite(value) && !nativeIsNaN(parseFloat(value));
    }

    /**
     * Checks if `value` is a function.
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if the `value` is a function, else `false`.
     * @example
     *
     * _.isFunction(_);
     * // => true
     */
    function isFunction(value) {
      return typeof value == 'function';
    }

    /**
     * Checks if `value` is the language type of Object.
     * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if the `value` is an object, else `false`.
     * @example
     *
     * _.isObject({});
     * // => true
     *
     * _.isObject([1, 2, 3]);
     * // => true
     *
     * _.isObject(1);
     * // => false
     */
    function isObject(value) {
      // check if the value is the ECMAScript language type of Object
      // http://es5.github.io/#x8
      // and avoid a V8 bug
      // http://code.google.com/p/v8/issues/detail?id=2291
      return !!(value && objectTypes[typeof value]);
    }

    /**
     * Checks if `value` is `NaN`.
     *
     * Note: This is not the same as native `isNaN` which will return `true` for
     * `undefined` and other non-numeric values. See http://es5.github.io/#x15.1.2.4.
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if the `value` is `NaN`, else `false`.
     * @example
     *
     * _.isNaN(NaN);
     * // => true
     *
     * _.isNaN(new Number(NaN));
     * // => true
     *
     * isNaN(undefined);
     * // => true
     *
     * _.isNaN(undefined);
     * // => false
     */
    function isNaN(value) {
      // `NaN` as a primitive is the only value that is not equal to itself
      // (perform the [[Class]] check first to avoid errors with some host objects in IE)
      return isNumber(value) && value != +value;
    }

    /**
     * Checks if `value` is `null`.
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if the `value` is `null`, else `false`.
     * @example
     *
     * _.isNull(null);
     * // => true
     *
     * _.isNull(undefined);
     * // => false
     */
    function isNull(value) {
      return value === null;
    }

    /**
     * Checks if `value` is a number.
     *
     * Note: `NaN` is considered a number. See http://es5.github.io/#x8.5.
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if the `value` is a number, else `false`.
     * @example
     *
     * _.isNumber(8.4 * 5);
     * // => true
     */
    function isNumber(value) {
      return typeof value == 'number' ||
        value && typeof value == 'object' && toString.call(value) == numberClass || false;
    }

    /**
     * Checks if `value` is an object created by the `Object` constructor.
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
     * @example
     *
     * function Shape() {
     *   this.x = 0;
     *   this.y = 0;
     * }
     *
     * _.isPlainObject(new Shape);
     * // => false
     *
     * _.isPlainObject([1, 2, 3]);
     * // => false
     *
     * _.isPlainObject({ 'x': 0, 'y': 0 });
     * // => true
     */
    var isPlainObject = !getPrototypeOf ? shimIsPlainObject : function(value) {
      if (!(value && toString.call(value) == objectClass)) {
        return false;
      }
      var valueOf = value.valueOf,
          objProto = isNative(valueOf) && (objProto = getPrototypeOf(valueOf)) && getPrototypeOf(objProto);

      return objProto
        ? (value == objProto || getPrototypeOf(value) == objProto)
        : shimIsPlainObject(value);
    };

    /**
     * Checks if `value` is a regular expression.
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if the `value` is a regular expression, else `false`.
     * @example
     *
     * _.isRegExp(/fred/);
     * // => true
     */
    function isRegExp(value) {
      return value && typeof value == 'object' && toString.call(value) == regexpClass || false;
    }

    /**
     * Checks if `value` is a string.
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if the `value` is a string, else `false`.
     * @example
     *
     * _.isString('fred');
     * // => true
     */
    function isString(value) {
      return typeof value == 'string' ||
        value && typeof value == 'object' && toString.call(value) == stringClass || false;
    }

    /**
     * Checks if `value` is `undefined`.
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {*} value The value to check.
     * @returns {boolean} Returns `true` if the `value` is `undefined`, else `false`.
     * @example
     *
     * _.isUndefined(void 0);
     * // => true
     */
    function isUndefined(value) {
      return typeof value == 'undefined';
    }

    /**
     * Creates an object with the same keys as `object` and values generated by
     * running each own enumerable property of `object` through the callback.
     * The callback is bound to `thisArg` and invoked with three arguments;
     * (value, key, object).
     *
     * If a property name is provided for `callback` the created "_.pluck" style
     * callback will return the property value of the given element.
     *
     * If an object is provided for `callback` the created "_.where" style callback
     * will return `true` for elements that have the properties of the given object,
     * else `false`.
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {Object} object The object to iterate over.
     * @param {Function|Object|string} [callback=identity] The function called
     *  per iteration. If a property name or object is provided it will be used
     *  to create a "_.pluck" or "_.where" style callback, respectively.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {Array} Returns a new object with values of the results of each `callback` execution.
     * @example
     *
     * _.mapValues({ 'a': 1, 'b': 2, 'c': 3} , function(num) { return num * 3; });
     * // => { 'a': 3, 'b': 6, 'c': 9 }
     *
     * var characters = {
     *   'fred': { 'name': 'fred', 'age': 40 },
     *   'pebbles': { 'name': 'pebbles', 'age': 1 }
     * };
     *
     * // using "_.pluck" callback shorthand
     * _.mapValues(characters, 'age');
     * // => { 'fred': 40, 'pebbles': 1 }
     */
    function mapValues(object, callback, thisArg) {
      var result = {};
      callback = lodash.createCallback(callback, thisArg, 3);

      forOwn(object, function(value, key, object) {
        result[key] = callback(value, key, object);
      });
      return result;
    }

    /**
     * Recursively merges own enumerable properties of the source object(s), that
     * don't resolve to `undefined` into the destination object. Subsequent sources
     * will overwrite property assignments of previous sources. If a callback is
     * provided it will be executed to produce the merged values of the destination
     * and source properties. If the callback returns `undefined` merging will
     * be handled by the method instead. The callback is bound to `thisArg` and
     * invoked with two arguments; (objectValue, sourceValue).
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {Object} object The destination object.
     * @param {...Object} [source] The source objects.
     * @param {Function} [callback] The function to customize merging properties.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {Object} Returns the destination object.
     * @example
     *
     * var names = {
     *   'characters': [
     *     { 'name': 'barney' },
     *     { 'name': 'fred' }
     *   ]
     * };
     *
     * var ages = {
     *   'characters': [
     *     { 'age': 36 },
     *     { 'age': 40 }
     *   ]
     * };
     *
     * _.merge(names, ages);
     * // => { 'characters': [{ 'name': 'barney', 'age': 36 }, { 'name': 'fred', 'age': 40 }] }
     *
     * var food = {
     *   'fruits': ['apple'],
     *   'vegetables': ['beet']
     * };
     *
     * var otherFood = {
     *   'fruits': ['banana'],
     *   'vegetables': ['carrot']
     * };
     *
     * _.merge(food, otherFood, function(a, b) {
     *   return _.isArray(a) ? a.concat(b) : undefined;
     * });
     * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot] }
     */
    function merge(object) {
      var args = arguments,
          length = 2;

      if (!isObject(object)) {
        return object;
      }
      // allows working with `_.reduce` and `_.reduceRight` without using
      // their `index` and `collection` arguments
      if (typeof args[2] != 'number') {
        length = args.length;
      }
      if (length > 3 && typeof args[length - 2] == 'function') {
        var callback = baseCreateCallback(args[--length - 1], args[length--], 2);
      } else if (length > 2 && typeof args[length - 1] == 'function') {
        callback = args[--length];
      }
      var sources = slice(arguments, 1, length),
          index = -1,
          stackA = getArray(),
          stackB = getArray();

      while (++index < length) {
        baseMerge(object, sources[index], callback, stackA, stackB);
      }
      releaseArray(stackA);
      releaseArray(stackB);
      return object;
    }

    /**
     * Creates a shallow clone of `object` excluding the specified properties.
     * Property names may be specified as individual arguments or as arrays of
     * property names. If a callback is provided it will be executed for each
     * property of `object` omitting the properties the callback returns truey
     * for. The callback is bound to `thisArg` and invoked with three arguments;
     * (value, key, object).
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {Object} object The source object.
     * @param {Function|...string|string[]} [callback] The properties to omit or the
     *  function called per iteration.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {Object} Returns an object without the omitted properties.
     * @example
     *
     * _.omit({ 'name': 'fred', 'age': 40 }, 'age');
     * // => { 'name': 'fred' }
     *
     * _.omit({ 'name': 'fred', 'age': 40 }, function(value) {
     *   return typeof value == 'number';
     * });
     * // => { 'name': 'fred' }
     */
    function omit(object, callback, thisArg) {
      var result = {};
      if (typeof callback != 'function') {
        var props = [];
        forIn(object, function(value, key) {
          props.push(key);
        });
        props = baseDifference(props, baseFlatten(arguments, true, false, 1));

        var index = -1,
            length = props.length;

        while (++index < length) {
          var key = props[index];
          result[key] = object[key];
        }
      } else {
        callback = lodash.createCallback(callback, thisArg, 3);
        forIn(object, function(value, key, object) {
          if (!callback(value, key, object)) {
            result[key] = value;
          }
        });
      }
      return result;
    }

    /**
     * Creates a two dimensional array of an object's key-value pairs,
     * i.e. `[[key1, value1], [key2, value2]]`.
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {Object} object The object to inspect.
     * @returns {Array} Returns new array of key-value pairs.
     * @example
     *
     * _.pairs({ 'barney': 36, 'fred': 40 });
     * // => [['barney', 36], ['fred', 40]] (property order is not guaranteed across environments)
     */
    function pairs(object) {
      var index = -1,
          props = keys(object),
          length = props.length,
          result = Array(length);

      while (++index < length) {
        var key = props[index];
        result[index] = [key, object[key]];
      }
      return result;
    }

    /**
     * Creates a shallow clone of `object` composed of the specified properties.
     * Property names may be specified as individual arguments or as arrays of
     * property names. If a callback is provided it will be executed for each
     * property of `object` picking the properties the callback returns truey
     * for. The callback is bound to `thisArg` and invoked with three arguments;
     * (value, key, object).
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {Object} object The source object.
     * @param {Function|...string|string[]} [callback] The function called per
     *  iteration or property names to pick, specified as individual property
     *  names or arrays of property names.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {Object} Returns an object composed of the picked properties.
     * @example
     *
     * _.pick({ 'name': 'fred', '_userid': 'fred1' }, 'name');
     * // => { 'name': 'fred' }
     *
     * _.pick({ 'name': 'fred', '_userid': 'fred1' }, function(value, key) {
     *   return key.charAt(0) != '_';
     * });
     * // => { 'name': 'fred' }
     */
    function pick(object, callback, thisArg) {
      var result = {};
      if (typeof callback != 'function') {
        var index = -1,
            props = baseFlatten(arguments, true, false, 1),
            length = isObject(object) ? props.length : 0;

        while (++index < length) {
          var key = props[index];
          if (key in object) {
            result[key] = object[key];
          }
        }
      } else {
        callback = lodash.createCallback(callback, thisArg, 3);
        forIn(object, function(value, key, object) {
          if (callback(value, key, object)) {
            result[key] = value;
          }
        });
      }
      return result;
    }

    /**
     * An alternative to `_.reduce` this method transforms `object` to a new
     * `accumulator` object which is the result of running each of its own
     * enumerable properties through a callback, with each callback execution
     * potentially mutating the `accumulator` object. The callback is bound to
     * `thisArg` and invoked with four arguments; (accumulator, value, key, object).
     * Callbacks may exit iteration early by explicitly returning `false`.
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {Array|Object} object The object to iterate over.
     * @param {Function} [callback=identity] The function called per iteration.
     * @param {*} [accumulator] The custom accumulator value.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {*} Returns the accumulated value.
     * @example
     *
     * var squares = _.transform([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], function(result, num) {
     *   num *= num;
     *   if (num % 2) {
     *     return result.push(num) < 3;
     *   }
     * });
     * // => [1, 9, 25]
     *
     * var mapped = _.transform({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) {
     *   result[key] = num * 3;
     * });
     * // => { 'a': 3, 'b': 6, 'c': 9 }
     */
    function transform(object, callback, accumulator, thisArg) {
      var isArr = isArray(object);
      if (accumulator == null) {
        if (isArr) {
          accumulator = [];
        } else {
          var ctor = object && object.constructor,
              proto = ctor && ctor.prototype;

          accumulator = baseCreate(proto);
        }
      }
      if (callback) {
        callback = lodash.createCallback(callback, thisArg, 4);
        (isArr ? forEach : forOwn)(object, function(value, index, object) {
          return callback(accumulator, value, index, object);
        });
      }
      return accumulator;
    }

    /**
     * Creates an array composed of the own enumerable property values of `object`.
     *
     * @static
     * @memberOf _
     * @category Objects
     * @param {Object} object The object to inspect.
     * @returns {Array} Returns an array of property values.
     * @example
     *
     * _.values({ 'one': 1, 'two': 2, 'three': 3 });
     * // => [1, 2, 3] (property order is not guaranteed across environments)
     */
    function values(object) {
      var index = -1,
          props = keys(object),
          length = props.length,
          result = Array(length);

      while (++index < length) {
        result[index] = object[props[index]];
      }
      return result;
    }

    /*--------------------------------------------------------------------------*/

    /**
     * Creates an array of elements from the specified indexes, or keys, of the
     * `collection`. Indexes may be specified as individual arguments or as arrays
     * of indexes.
     *
     * @static
     * @memberOf _
     * @category Collections
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {...(number|number[]|string|string[])} [index] The indexes of `collection`
     *   to retrieve, specified as individual indexes or arrays of indexes.
     * @returns {Array} Returns a new array of elements corresponding to the
     *  provided indexes.
     * @example
     *
     * _.at(['a', 'b', 'c', 'd', 'e'], [0, 2, 4]);
     * // => ['a', 'c', 'e']
     *
     * _.at(['fred', 'barney', 'pebbles'], 0, 2);
     * // => ['fred', 'pebbles']
     */
    function at(collection) {
      var args = arguments,
          index = -1,
          props = baseFlatten(args, true, false, 1),
          length = (args[2] && args[2][args[1]] === collection) ? 1 : props.length,
          result = Array(length);

      while(++index < length) {
        result[index] = collection[props[index]];
      }
      return result;
    }

    /**
     * Checks if a given value is present in a collection using strict equality
     * for comparisons, i.e. `===`. If `fromIndex` is negative, it is used as the
     * offset from the end of the collection.
     *
     * @static
     * @memberOf _
     * @alias include
     * @category Collections
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {*} target The value to check for.
     * @param {number} [fromIndex=0] The index to search from.
     * @returns {boolean} Returns `true` if the `target` element is found, else `false`.
     * @example
     *
     * _.contains([1, 2, 3], 1);
     * // => true
     *
     * _.contains([1, 2, 3], 1, 2);
     * // => false
     *
     * _.contains({ 'name': 'fred', 'age': 40 }, 'fred');
     * // => true
     *
     * _.contains('pebbles', 'eb');
     * // => true
     */
    function contains(collection, target, fromIndex) {
      var index = -1,
          indexOf = getIndexOf(),
          length = collection ? collection.length : 0,
          result = false;

      fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex) || 0;
      if (isArray(collection)) {
        result = indexOf(collection, target, fromIndex) > -1;
      } else if (typeof length == 'number') {
        result = (isString(collection) ? collection.indexOf(target, fromIndex) : indexOf(collection, target, fromIndex)) > -1;
      } else {
        forOwn(collection, function(value) {
          if (++index >= fromIndex) {
            return !(result = value === target);
          }
        });
      }
      return result;
    }

    /**
     * Creates an object composed of keys generated from the results of running
     * each element of `collection` through the callback. The corresponding value
     * of each key is the number of times the key was returned by the callback.
     * The callback is bound to `thisArg` and invoked with three arguments;
     * (value, index|key, collection).
     *
     * If a property name is provided for `callback` the created "_.pluck" style
     * callback will return the property value of the given element.
     *
     * If an object is provided for `callback` the created "_.where" style callback
     * will return `true` for elements that have the properties of the given object,
     * else `false`.
     *
     * @static
     * @memberOf _
     * @category Collections
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function|Object|string} [callback=identity] The function called
     *  per iteration. If a property name or object is provided it will be used
     *  to create a "_.pluck" or "_.where" style callback, respectively.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {Object} Returns the composed aggregate object.
     * @example
     *
     * _.countBy([4.3, 6.1, 6.4], function(num) { return Math.floor(num); });
     * // => { '4': 1, '6': 2 }
     *
     * _.countBy([4.3, 6.1, 6.4], function(num) { return this.floor(num); }, Math);
     * // => { '4': 1, '6': 2 }
     *
     * _.countBy(['one', 'two', 'three'], 'length');
     * // => { '3': 2, '5': 1 }
     */
    var countBy = createAggregator(function(result, value, key) {
      (hasOwnProperty.call(result, key) ? result[key]++ : result[key] = 1);
    });

    /**
     * Checks if the given callback returns truey value for **all** elements of
     * a collection. The callback is bound to `thisArg` and invoked with three
     * arguments; (value, index|key, collection).
     *
     * If a property name is provided for `callback` the created "_.pluck" style
     * callback will return the property value of the given element.
     *
     * If an object is provided for `callback` the created "_.where" style callback
     * will return `true` for elements that have the properties of the given object,
     * else `false`.
     *
     * @static
     * @memberOf _
     * @alias all
     * @category Collections
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function|Object|string} [callback=identity] The function called
     *  per iteration. If a property name or object is provided it will be used
     *  to create a "_.pluck" or "_.where" style callback, respectively.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {boolean} Returns `true` if all elements passed the callback check,
     *  else `false`.
     * @example
     *
     * _.every([true, 1, null, 'yes']);
     * // => false
     *
     * var characters = [
     *   { 'name': 'barney', 'age': 36 },
     *   { 'name': 'fred',   'age': 40 }
     * ];
     *
     * // using "_.pluck" callback shorthand
     * _.every(characters, 'age');
     * // => true
     *
     * // using "_.where" callback shorthand
     * _.every(characters, { 'age': 36 });
     * // => false
     */
    function every(collection, callback, thisArg) {
      var result = true;
      callback = lodash.createCallback(callback, thisArg, 3);

      var index = -1,
          length = collection ? collection.length : 0;

      if (typeof length == 'number') {
        while (++index < length) {
          if (!(result = !!callback(collection[index], index, collection))) {
            break;
          }
        }
      } else {
        forOwn(collection, function(value, index, collection) {
          return (result = !!callback(value, index, collection));
        });
      }
      return result;
    }

    /**
     * Iterates over elements of a collection, returning an array of all elements
     * the callback returns truey for. The callback is bound to `thisArg` and
     * invoked with three arguments; (value, index|key, collection).
     *
     * If a property name is provided for `callback` the created "_.pluck" style
     * callback will return the property value of the given element.
     *
     * If an object is provided for `callback` the created "_.where" style callback
     * will return `true` for elements that have the properties of the given object,
     * else `false`.
     *
     * @static
     * @memberOf _
     * @alias select
     * @category Collections
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function|Object|string} [callback=identity] The function called
     *  per iteration. If a property name or object is provided it will be used
     *  to create a "_.pluck" or "_.where" style callback, respectively.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {Array} Returns a new array of elements that passed the callback check.
     * @example
     *
     * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
     * // => [2, 4, 6]
     *
     * var characters = [
     *   { 'name': 'barney', 'age': 36, 'blocked': false },
     *   { 'name': 'fred',   'age': 40, 'blocked': true }
     * ];
     *
     * // using "_.pluck" callback shorthand
     * _.filter(characters, 'blocked');
     * // => [{ 'name': 'fred', 'age': 40, 'blocked': true }]
     *
     * // using "_.where" callback shorthand
     * _.filter(characters, { 'age': 36 });
     * // => [{ 'name': 'barney', 'age': 36, 'blocked': false }]
     */
    function filter(collection, callback, thisArg) {
      var result = [];
      callback = lodash.createCallback(callback, thisArg, 3);

      var index = -1,
          length = collection ? collection.length : 0;

      if (typeof length == 'number') {
        while (++index < length) {
          var value = collection[index];
          if (callback(value, index, collection)) {
            result.push(value);
          }
        }
      } else {
        forOwn(collection, function(value, index, collection) {
          if (callback(value, index, collection)) {
            result.push(value);
          }
        });
      }
      return result;
    }

    /**
     * Iterates over elements of a collection, returning the first element that
     * the callback returns truey for. The callback is bound to `thisArg` and
     * invoked with three arguments; (value, index|key, collection).
     *
     * If a property name is provided for `callback` the created "_.pluck" style
     * callback will return the property value of the given element.
     *
     * If an object is provided for `callback` the created "_.where" style callback
     * will return `true` for elements that have the properties of the given object,
     * else `false`.
     *
     * @static
     * @memberOf _
     * @alias detect, findWhere
     * @category Collections
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function|Object|string} [callback=identity] The function called
     *  per iteration. If a property name or object is provided it will be used
     *  to create a "_.pluck" or "_.where" style callback, respectively.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {*} Returns the found element, else `undefined`.
     * @example
     *
     * var characters = [
     *   { 'name': 'barney',  'age': 36, 'blocked': false },
     *   { 'name': 'fred',    'age': 40, 'blocked': true },
     *   { 'name': 'pebbles', 'age': 1,  'blocked': false }
     * ];
     *
     * _.find(characters, function(chr) {
     *   return chr.age < 40;
     * });
     * // => { 'name': 'barney', 'age': 36, 'blocked': false }
     *
     * // using "_.where" callback shorthand
     * _.find(characters, { 'age': 1 });
     * // =>  { 'name': 'pebbles', 'age': 1, 'blocked': false }
     *
     * // using "_.pluck" callback shorthand
     * _.find(characters, 'blocked');
     * // => { 'name': 'fred', 'age': 40, 'blocked': true }
     */
    function find(collection, callback, thisArg) {
      callback = lodash.createCallback(callback, thisArg, 3);

      var index = -1,
          length = collection ? collection.length : 0;

      if (typeof length == 'number') {
        while (++index < length) {
          var value = collection[index];
          if (callback(value, index, collection)) {
            return value;
          }
        }
      } else {
        var result;
        forOwn(collection, function(value, index, collection) {
          if (callback(value, index, collection)) {
            result = value;
            return false;
          }
        });
        return result;
      }
    }

    /**
     * This method is like `_.find` except that it iterates over elements
     * of a `collection` from right to left.
     *
     * @static
     * @memberOf _
     * @category Collections
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function|Object|string} [callback=identity] The function called
     *  per iteration. If a property name or object is provided it will be used
     *  to create a "_.pluck" or "_.where" style callback, respectively.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {*} Returns the found element, else `undefined`.
     * @example
     *
     * _.findLast([1, 2, 3, 4], function(num) {
     *   return num % 2 == 1;
     * });
     * // => 3
     */
    function findLast(collection, callback, thisArg) {
      var result;
      callback = lodash.createCallback(callback, thisArg, 3);
      forEachRight(collection, function(value, index, collection) {
        if (callback(value, index, collection)) {
          result = value;
          return false;
        }
      });
      return result;
    }

    /**
     * Iterates over elements of a collection, executing the callback for each
     * element. The callback is bound to `thisArg` and invoked with three arguments;
     * (value, index|key, collection). Callbacks may exit iteration early by
     * explicitly returning `false`.
     *
     * Note: As with other "Collections" methods, objects with a `length` property
     * are iterated like arrays. To avoid this behavior `_.forIn` or `_.forOwn`
     * may be used for object iteration.
     *
     * @static
     * @memberOf _
     * @alias each
     * @category Collections
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function} [callback=identity] The function called per iteration.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {Array|Object|string} Returns `collection`.
     * @example
     *
     * _([1, 2, 3]).forEach(function(num) { console.log(num); }).join(',');
     * // => logs each number and returns '1,2,3'
     *
     * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { console.log(num); });
     * // => logs each number and returns the object (property order is not guaranteed across environments)
     */
    function forEach(collection, callback, thisArg) {
      var index = -1,
          length = collection ? collection.length : 0;

      callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
      if (typeof length == 'number') {
        while (++index < length) {
          if (callback(collection[index], index, collection) === false) {
            break;
          }
        }
      } else {
        forOwn(collection, callback);
      }
      return collection;
    }

    /**
     * This method is like `_.forEach` except that it iterates over elements
     * of a `collection` from right to left.
     *
     * @static
     * @memberOf _
     * @alias eachRight
     * @category Collections
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function} [callback=identity] The function called per iteration.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {Array|Object|string} Returns `collection`.
     * @example
     *
     * _([1, 2, 3]).forEachRight(function(num) { console.log(num); }).join(',');
     * // => logs each number from right to left and returns '3,2,1'
     */
    function forEachRight(collection, callback, thisArg) {
      var length = collection ? collection.length : 0;
      callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3);
      if (typeof length == 'number') {
        while (length--) {
          if (callback(collection[length], length, collection) === false) {
            break;
          }
        }
      } else {
        var props = keys(collection);
        length = props.length;
        forOwn(collection, function(value, key, collection) {
          key = props ? props[--length] : --length;
          return callback(collection[key], key, collection);
        });
      }
      return collection;
    }

    /**
     * Creates an object composed of keys generated from the results of running
     * each element of a collection through the callback. The corresponding value
     * of each key is an array of the elements responsible for generating the key.
     * The callback is bound to `thisArg` and invoked with three arguments;
     * (value, index|key, collection).
     *
     * If a property name is provided for `callback` the created "_.pluck" style
     * callback will return the property value of the given element.
     *
     * If an object is provided for `callback` the created "_.where" style callback
     * will return `true` for elements that have the properties of the given object,
     * else `false`
     *
     * @static
     * @memberOf _
     * @category Collections
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function|Object|string} [callback=identity] The function called
     *  per iteration. If a property name or object is provided it will be used
     *  to create a "_.pluck" or "_.where" style callback, respectively.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {Object} Returns the composed aggregate object.
     * @example
     *
     * _.groupBy([4.2, 6.1, 6.4], function(num) { return Math.floor(num); });
     * // => { '4': [4.2], '6': [6.1, 6.4] }
     *
     * _.groupBy([4.2, 6.1, 6.4], function(num) { return this.floor(num); }, Math);
     * // => { '4': [4.2], '6': [6.1, 6.4] }
     *
     * // using "_.pluck" callback shorthand
     * _.groupBy(['one', 'two', 'three'], 'length');
     * // => { '3': ['one', 'two'], '5': ['three'] }
     */
    var groupBy = createAggregator(function(result, value, key) {
      (hasOwnProperty.call(result, key) ? result[key] : result[key] = []).push(value);
    });

    /**
     * Creates an object composed of keys generated from the results of running
     * each element of the collection through the given callback. The corresponding
     * value of each key is the last element responsible for generating the key.
     * The callback is bound to `thisArg` and invoked with three arguments;
     * (value, index|key, collection).
     *
     * If a property name is provided for `callback` the created "_.pluck" style
     * callback will return the property value of the given element.
     *
     * If an object is provided for `callback` the created "_.where" style callback
     * will return `true` for elements that have the properties of the given object,
     * else `false`.
     *
     * @static
     * @memberOf _
     * @category Collections
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function|Object|string} [callback=identity] The function called
     *  per iteration. If a property name or object is provided it will be used
     *  to create a "_.pluck" or "_.where" style callback, respectively.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {Object} Returns the composed aggregate object.
     * @example
     *
     * var keys = [
     *   { 'dir': 'left', 'code': 97 },
     *   { 'dir': 'right', 'code': 100 }
     * ];
     *
     * _.indexBy(keys, 'dir');
     * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } }
     *
     * _.indexBy(keys, function(key) { return String.fromCharCode(key.code); });
     * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
     *
     * _.indexBy(characters, function(key) { this.fromCharCode(key.code); }, String);
     * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
     */
    var indexBy = createAggregator(function(result, value, key) {
      result[key] = value;
    });

    /**
     * Invokes the method named by `methodName` on each element in the `collection`
     * returning an array of the results of each invoked method. Additional arguments
     * will be provided to each invoked method. If `methodName` is a function it
     * will be invoked for, and `this` bound to, each element in the `collection`.
     *
     * @static
     * @memberOf _
     * @category Collections
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function|string} methodName The name of the method to invoke or
     *  the function invoked per iteration.
     * @param {...*} [arg] Arguments to invoke the method with.
     * @returns {Array} Returns a new array of the results of each invoked method.
     * @example
     *
     * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort');
     * // => [[1, 5, 7], [1, 2, 3]]
     *
     * _.invoke([123, 456], String.prototype.split, '');
     * // => [['1', '2', '3'], ['4', '5', '6']]
     */
    function invoke(collection, methodName) {
      var args = slice(arguments, 2),
          index = -1,
          isFunc = typeof methodName == 'function',
          length = collection ? collection.length : 0,
          result = Array(typeof length == 'number' ? length : 0);

      forEach(collection, function(value) {
        result[++index] = (isFunc ? methodName : value[methodName]).apply(value, args);
      });
      return result;
    }

    /**
     * Creates an array of values by running each element in the collection
     * through the callback. The callback is bound to `thisArg` and invoked with
     * three arguments; (value, index|key, collection).
     *
     * If a property name is provided for `callback` the created "_.pluck" style
     * callback will return the property value of the given element.
     *
     * If an object is provided for `callback` the created "_.where" style callback
     * will return `true` for elements that have the properties of the given object,
     * else `false`.
     *
     * @static
     * @memberOf _
     * @alias collect
     * @category Collections
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function|Object|string} [callback=identity] The function called
     *  per iteration. If a property name or object is provided it will be used
     *  to create a "_.pluck" or "_.where" style callback, respectively.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {Array} Returns a new array of the results of each `callback` execution.
     * @example
     *
     * _.map([1, 2, 3], function(num) { return num * 3; });
     * // => [3, 6, 9]
     *
     * _.map({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { return num * 3; });
     * // => [3, 6, 9] (property order is not guaranteed across environments)
     *
     * var characters = [
     *   { 'name': 'barney', 'age': 36 },
     *   { 'name': 'fred',   'age': 40 }
     * ];
     *
     * // using "_.pluck" callback shorthand
     * _.map(characters, 'name');
     * // => ['barney', 'fred']
     */
    function map(collection, callback, thisArg) {
      var index = -1,
          length = collection ? collection.length : 0;

      callback = lodash.createCallback(callback, thisArg, 3);
      if (typeof length == 'number') {
        var result = Array(length);
        while (++index < length) {
          result[index] = callback(collection[index], index, collection);
        }
      } else {
        result = [];
        forOwn(collection, function(value, key, collection) {
          result[++index] = callback(value, key, collection);
        });
      }
      return result;
    }

    /**
     * Retrieves the maximum value of a collection. If the collection is empty or
     * falsey `-Infinity` is returned. If a callback is provided it will be executed
     * for each value in the collection to generate the criterion by which the value
     * is ranked. The callback is bound to `thisArg` and invoked with three
     * arguments; (value, index, collection).
     *
     * If a property name is provided for `callback` the created "_.pluck" style
     * callback will return the property value of the given element.
     *
     * If an object is provided for `callback` the created "_.where" style callback
     * will return `true` for elements that have the properties of the given object,
     * else `false`.
     *
     * @static
     * @memberOf _
     * @category Collections
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function|Object|string} [callback=identity] The function called
     *  per iteration. If a property name or object is provided it will be used
     *  to create a "_.pluck" or "_.where" style callback, respectively.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {*} Returns the maximum value.
     * @example
     *
     * _.max([4, 2, 8, 6]);
     * // => 8
     *
     * var characters = [
     *   { 'name': 'barney', 'age': 36 },
     *   { 'name': 'fred',   'age': 40 }
     * ];
     *
     * _.max(characters, function(chr) { return chr.age; });
     * // => { 'name': 'fred', 'age': 40 };
     *
     * // using "_.pluck" callback shorthand
     * _.max(characters, 'age');
     * // => { 'name': 'fred', 'age': 40 };
     */
    function max(collection, callback, thisArg) {
      var computed = -Infinity,
          result = computed;

      // allows working with functions like `_.map` without using
      // their `index` argument as a callback
      if (typeof callback != 'function' && thisArg && thisArg[callback] === collection) {
        callback = null;
      }
      if (callback == null && isArray(collection)) {
        var index = -1,
            length = collection.length;

        while (++index < length) {
          var value = collection[index];
          if (value > result) {
            result = value;
          }
        }
      } else {
        callback = (callback == null && isString(collection))
          ? charAtCallback
          : lodash.createCallback(callback, thisArg, 3);

        forEach(collection, function(value, index, collection) {
          var current = callback(value, index, collection);
          if (current > computed) {
            computed = current;
            result = value;
          }
        });
      }
      return result;
    }

    /**
     * Retrieves the minimum value of a collection. If the collection is empty or
     * falsey `Infinity` is returned. If a callback is provided it will be executed
     * for each value in the collection to generate the criterion by which the value
     * is ranked. The callback is bound to `thisArg` and invoked with three
     * arguments; (value, index, collection).
     *
     * If a property name is provided for `callback` the created "_.pluck" style
     * callback will return the property value of the given element.
     *
     * If an object is provided for `callback` the created "_.where" style callback
     * will return `true` for elements that have the properties of the given object,
     * else `false`.
     *
     * @static
     * @memberOf _
     * @category Collections
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function|Object|string} [callback=identity] The function called
     *  per iteration. If a property name or object is provided it will be used
     *  to create a "_.pluck" or "_.where" style callback, respectively.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {*} Returns the minimum value.
     * @example
     *
     * _.min([4, 2, 8, 6]);
     * // => 2
     *
     * var characters = [
     *   { 'name': 'barney', 'age': 36 },
     *   { 'name': 'fred',   'age': 40 }
     * ];
     *
     * _.min(characters, function(chr) { return chr.age; });
     * // => { 'name': 'barney', 'age': 36 };
     *
     * // using "_.pluck" callback shorthand
     * _.min(characters, 'age');
     * // => { 'name': 'barney', 'age': 36 };
     */
    function min(collection, callback, thisArg) {
      var computed = Infinity,
          result = computed;

      // allows working with functions like `_.map` without using
      // their `index` argument as a callback
      if (typeof callback != 'function' && thisArg && thisArg[callback] === collection) {
        callback = null;
      }
      if (callback == null && isArray(collection)) {
        var index = -1,
            length = collection.length;

        while (++index < length) {
          var value = collection[index];
          if (value < result) {
            result = value;
          }
        }
      } else {
        callback = (callback == null && isString(collection))
          ? charAtCallback
          : lodash.createCallback(callback, thisArg, 3);

        forEach(collection, function(value, index, collection) {
          var current = callback(value, index, collection);
          if (current < computed) {
            computed = current;
            result = value;
          }
        });
      }
      return result;
    }

    /**
     * Retrieves the value of a specified property from all elements in the collection.
     *
     * @static
     * @memberOf _
     * @type Function
     * @category Collections
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {string} property The name of the property to pluck.
     * @returns {Array} Returns a new array of property values.
     * @example
     *
     * var characters = [
     *   { 'name': 'barney', 'age': 36 },
     *   { 'name': 'fred',   'age': 40 }
     * ];
     *
     * _.pluck(characters, 'name');
     * // => ['barney', 'fred']
     */
    var pluck = map;

    /**
     * Reduces a collection to a value which is the accumulated result of running
     * each element in the collection through the callback, where each successive
     * callback execution consumes the return value of the previous execution. If
     * `accumulator` is not provided the first element of the collection will be
     * used as the initial `accumulator` value. The callback is bound to `thisArg`
     * and invoked with four arguments; (accumulator, value, index|key, collection).
     *
     * @static
     * @memberOf _
     * @alias foldl, inject
     * @category Collections
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function} [callback=identity] The function called per iteration.
     * @param {*} [accumulator] Initial value of the accumulator.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {*} Returns the accumulated value.
     * @example
     *
     * var sum = _.reduce([1, 2, 3], function(sum, num) {
     *   return sum + num;
     * });
     * // => 6
     *
     * var mapped = _.reduce({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) {
     *   result[key] = num * 3;
     *   return result;
     * }, {});
     * // => { 'a': 3, 'b': 6, 'c': 9 }
     */
    function reduce(collection, callback, accumulator, thisArg) {
      if (!collection) return accumulator;
      var noaccum = arguments.length < 3;
      callback = lodash.createCallback(callback, thisArg, 4);

      var index = -1,
          length = collection.length;

      if (typeof length == 'number') {
        if (noaccum) {
          accumulator = collection[++index];
        }
        while (++index < length) {
          accumulator = callback(accumulator, collection[index], index, collection);
        }
      } else {
        forOwn(collection, function(value, index, collection) {
          accumulator = noaccum
            ? (noaccum = false, value)
            : callback(accumulator, value, index, collection)
        });
      }
      return accumulator;
    }

    /**
     * This method is like `_.reduce` except that it iterates over elements
     * of a `collection` from right to left.
     *
     * @static
     * @memberOf _
     * @alias foldr
     * @category Collections
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function} [callback=identity] The function called per iteration.
     * @param {*} [accumulator] Initial value of the accumulator.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {*} Returns the accumulated value.
     * @example
     *
     * var list = [[0, 1], [2, 3], [4, 5]];
     * var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []);
     * // => [4, 5, 2, 3, 0, 1]
     */
    function reduceRight(collection, callback, accumulator, thisArg) {
      var noaccum = arguments.length < 3;
      callback = lodash.createCallback(callback, thisArg, 4);
      forEachRight(collection, function(value, index, collection) {
        accumulator = noaccum
          ? (noaccum = false, value)
          : callback(accumulator, value, index, collection);
      });
      return accumulator;
    }

    /**
     * The opposite of `_.filter` this method returns the elements of a
     * collection that the callback does **not** return truey for.
     *
     * If a property name is provided for `callback` the created "_.pluck" style
     * callback will return the property value of the given element.
     *
     * If an object is provided for `callback` the created "_.where" style callback
     * will return `true` for elements that have the properties of the given object,
     * else `false`.
     *
     * @static
     * @memberOf _
     * @category Collections
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function|Object|string} [callback=identity] The function called
     *  per iteration. If a property name or object is provided it will be used
     *  to create a "_.pluck" or "_.where" style callback, respectively.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {Array} Returns a new array of elements that failed the callback check.
     * @example
     *
     * var odds = _.reject([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; });
     * // => [1, 3, 5]
     *
     * var characters = [
     *   { 'name': 'barney', 'age': 36, 'blocked': false },
     *   { 'name': 'fred',   'age': 40, 'blocked': true }
     * ];
     *
     * // using "_.pluck" callback shorthand
     * _.reject(characters, 'blocked');
     * // => [{ 'name': 'barney', 'age': 36, 'blocked': false }]
     *
     * // using "_.where" callback shorthand
     * _.reject(characters, { 'age': 36 });
     * // => [{ 'name': 'fred', 'age': 40, 'blocked': true }]
     */
    function reject(collection, callback, thisArg) {
      callback = lodash.createCallback(callback, thisArg, 3);
      return filter(collection, function(value, index, collection) {
        return !callback(value, index, collection);
      });
    }

    /**
     * Retrieves a random element or `n` random elements from a collection.
     *
     * @static
     * @memberOf _
     * @category Collections
     * @param {Array|Object|string} collection The collection to sample.
     * @param {number} [n] The number of elements to sample.
     * @param- {Object} [guard] Allows working with functions like `_.map`
     *  without using their `index` arguments as `n`.
     * @returns {Array} Returns the random sample(s) of `collection`.
     * @example
     *
     * _.sample([1, 2, 3, 4]);
     * // => 2
     *
     * _.sample([1, 2, 3, 4], 2);
     * // => [3, 1]
     */
    function sample(collection, n, guard) {
      if (collection && typeof collection.length != 'number') {
        collection = values(collection);
      }
      if (n == null || guard) {
        return collection ? collection[baseRandom(0, collection.length - 1)] : undefined;
      }
      var result = shuffle(collection);
      result.length = nativeMin(nativeMax(0, n), result.length);
      return result;
    }

    /**
     * Creates an array of shuffled values, using a version of the Fisher-Yates
     * shuffle. See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle.
     *
     * @static
     * @memberOf _
     * @category Collections
     * @param {Array|Object|string} collection The collection to shuffle.
     * @returns {Array} Returns a new shuffled collection.
     * @example
     *
     * _.shuffle([1, 2, 3, 4, 5, 6]);
     * // => [4, 1, 6, 3, 5, 2]
     */
    function shuffle(collection) {
      var index = -1,
          length = collection ? collection.length : 0,
          result = Array(typeof length == 'number' ? length : 0);

      forEach(collection, function(value) {
        var rand = baseRandom(0, ++index);
        result[index] = result[rand];
        result[rand] = value;
      });
      return result;
    }

    /**
     * Gets the size of the `collection` by returning `collection.length` for arrays
     * and array-like objects or the number of own enumerable properties for objects.
     *
     * @static
     * @memberOf _
     * @category Collections
     * @param {Array|Object|string} collection The collection to inspect.
     * @returns {number} Returns `collection.length` or number of own enumerable properties.
     * @example
     *
     * _.size([1, 2]);
     * // => 2
     *
     * _.size({ 'one': 1, 'two': 2, 'three': 3 });
     * // => 3
     *
     * _.size('pebbles');
     * // => 7
     */
    function size(collection) {
      var length = collection ? collection.length : 0;
      return typeof length == 'number' ? length : keys(collection).length;
    }

    /**
     * Checks if the callback returns a truey value for **any** element of a
     * collection. The function returns as soon as it finds a passing value and
     * does not iterate over the entire collection. The callback is bound to
     * `thisArg` and invoked with three arguments; (value, index|key, collection).
     *
     * If a property name is provided for `callback` the created "_.pluck" style
     * callback will return the property value of the given element.
     *
     * If an object is provided for `callback` the created "_.where" style callback
     * will return `true` for elements that have the properties of the given object,
     * else `false`.
     *
     * @static
     * @memberOf _
     * @alias any
     * @category Collections
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Function|Object|string} [callback=identity] The function called
     *  per iteration. If a property name or object is provided it will be used
     *  to create a "_.pluck" or "_.where" style callback, respectively.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {boolean} Returns `true` if any element passed the callback check,
     *  else `false`.
     * @example
     *
     * _.some([null, 0, 'yes', false], Boolean);
     * // => true
     *
     * var characters = [
     *   { 'name': 'barney', 'age': 36, 'blocked': false },
     *   { 'name': 'fred',   'age': 40, 'blocked': true }
     * ];
     *
     * // using "_.pluck" callback shorthand
     * _.some(characters, 'blocked');
     * // => true
     *
     * // using "_.where" callback shorthand
     * _.some(characters, { 'age': 1 });
     * // => false
     */
    function some(collection, callback, thisArg) {
      var result;
      callback = lodash.createCallback(callback, thisArg, 3);

      var index = -1,
          length = collection ? collection.length : 0;

      if (typeof length == 'number') {
        while (++index < length) {
          if ((result = callback(collection[index], index, collection))) {
            break;
          }
        }
      } else {
        forOwn(collection, function(value, index, collection) {
          return !(result = callback(value, index, collection));
        });
      }
      return !!result;
    }

    /**
     * Creates an array of elements, sorted in ascending order by the results of
     * running each element in a collection through the callback. This method
     * performs a stable sort, that is, it will preserve the original sort order
     * of equal elements. The callback is bound to `thisArg` and invoked with
     * three arguments; (value, index|key, collection).
     *
     * If a property name is provided for `callback` the created "_.pluck" style
     * callback will return the property value of the given element.
     *
     * If an array of property names is provided for `callback` the collection
     * will be sorted by each property value.
     *
     * If an object is provided for `callback` the created "_.where" style callback
     * will return `true` for elements that have the properties of the given object,
     * else `false`.
     *
     * @static
     * @memberOf _
     * @category Collections
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Array|Function|Object|string} [callback=identity] The function called
     *  per iteration. If a property name or object is provided it will be used
     *  to create a "_.pluck" or "_.where" style callback, respectively.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {Array} Returns a new array of sorted elements.
     * @example
     *
     * _.sortBy([1, 2, 3], function(num) { return Math.sin(num); });
     * // => [3, 1, 2]
     *
     * _.sortBy([1, 2, 3], function(num) { return this.sin(num); }, Math);
     * // => [3, 1, 2]
     *
     * var characters = [
     *   { 'name': 'barney',  'age': 36 },
     *   { 'name': 'fred',    'age': 40 },
     *   { 'name': 'barney',  'age': 26 },
     *   { 'name': 'fred',    'age': 30 }
     * ];
     *
     * // using "_.pluck" callback shorthand
     * _.map(_.sortBy(characters, 'age'), _.values);
     * // => [['barney', 26], ['fred', 30], ['barney', 36], ['fred', 40]]
     *
     * // sorting by multiple properties
     * _.map(_.sortBy(characters, ['name', 'age']), _.values);
     * // = > [['barney', 26], ['barney', 36], ['fred', 30], ['fred', 40]]
     */
    function sortBy(collection, callback, thisArg) {
      var index = -1,
          isArr = isArray(callback),
          length = collection ? collection.length : 0,
          result = Array(typeof length == 'number' ? length : 0);

      if (!isArr) {
        callback = lodash.createCallback(callback, thisArg, 3);
      }
      forEach(collection, function(value, key, collection) {
        var object = result[++index] = getObject();
        if (isArr) {
          object.criteria = map(callback, function(key) { return value[key]; });
        } else {
          (object.criteria = getArray())[0] = callback(value, key, collection);
        }
        object.index = index;
        object.value = value;
      });

      length = result.length;
      result.sort(compareAscending);
      while (length--) {
        var object = result[length];
        result[length] = object.value;
        if (!isArr) {
          releaseArray(object.criteria);
        }
        releaseObject(object);
      }
      return result;
    }

    /**
     * Converts the `collection` to an array.
     *
     * @static
     * @memberOf _
     * @category Collections
     * @param {Array|Object|string} collection The collection to convert.
     * @returns {Array} Returns the new converted array.
     * @example
     *
     * (function() { return _.toArray(arguments).slice(1); })(1, 2, 3, 4);
     * // => [2, 3, 4]
     */
    function toArray(collection) {
      if (collection && typeof collection.length == 'number') {
        return slice(collection);
      }
      return values(collection);
    }

    /**
     * Performs a deep comparison of each element in a `collection` to the given
     * `properties` object, returning an array of all elements that have equivalent
     * property values.
     *
     * @static
     * @memberOf _
     * @type Function
     * @category Collections
     * @param {Array|Object|string} collection The collection to iterate over.
     * @param {Object} props The object of property values to filter by.
     * @returns {Array} Returns a new array of elements that have the given properties.
     * @example
     *
     * var characters = [
     *   { 'name': 'barney', 'age': 36, 'pets': ['hoppy'] },
     *   { 'name': 'fred',   'age': 40, 'pets': ['baby puss', 'dino'] }
     * ];
     *
     * _.where(characters, { 'age': 36 });
     * // => [{ 'name': 'barney', 'age': 36, 'pets': ['hoppy'] }]
     *
     * _.where(characters, { 'pets': ['dino'] });
     * // => [{ 'name': 'fred', 'age': 40, 'pets': ['baby puss', 'dino'] }]
     */
    var where = filter;

    /*--------------------------------------------------------------------------*/

    /**
     * Creates an array with all falsey values removed. The values `false`, `null`,
     * `0`, `""`, `undefined`, and `NaN` are all falsey.
     *
     * @static
     * @memberOf _
     * @category Arrays
     * @param {Array} array The array to compact.
     * @returns {Array} Returns a new array of filtered values.
     * @example
     *
     * _.compact([0, 1, false, 2, '', 3]);
     * // => [1, 2, 3]
     */
    function compact(array) {
      var index = -1,
          length = array ? array.length : 0,
          result = [];

      while (++index < length) {
        var value = array[index];
        if (value) {
          result.push(value);
        }
      }
      return result;
    }

    /**
     * Creates an array excluding all values of the provided arrays using strict
     * equality for comparisons, i.e. `===`.
     *
     * @static
     * @memberOf _
     * @category Arrays
     * @param {Array} array The array to process.
     * @param {...Array} [values] The arrays of values to exclude.
     * @returns {Array} Returns a new array of filtered values.
     * @example
     *
     * _.difference([1, 2, 3, 4, 5], [5, 2, 10]);
     * // => [1, 3, 4]
     */
    function difference(array) {
      return baseDifference(array, baseFlatten(arguments, true, true, 1));
    }

    /**
     * This method is like `_.find` except that it returns the index of the first
     * element that passes the callback check, instead of the element itself.
     *
     * If a property name is provided for `callback` the created "_.pluck" style
     * callback will return the property value of the given element.
     *
     * If an object is provided for `callback` the created "_.where" style callback
     * will return `true` for elements that have the properties of the given object,
     * else `false`.
     *
     * @static
     * @memberOf _
     * @category Arrays
     * @param {Array} array The array to search.
     * @param {Function|Object|string} [callback=identity] The function called
     *  per iteration. If a property name or object is provided it will be used
     *  to create a "_.pluck" or "_.where" style callback, respectively.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {number} Returns the index of the found element, else `-1`.
     * @example
     *
     * var characters = [
     *   { 'name': 'barney',  'age': 36, 'blocked': false },
     *   { 'name': 'fred',    'age': 40, 'blocked': true },
     *   { 'name': 'pebbles', 'age': 1,  'blocked': false }
     * ];
     *
     * _.findIndex(characters, function(chr) {
     *   return chr.age < 20;
     * });
     * // => 2
     *
     * // using "_.where" callback shorthand
     * _.findIndex(characters, { 'age': 36 });
     * // => 0
     *
     * // using "_.pluck" callback shorthand
     * _.findIndex(characters, 'blocked');
     * // => 1
     */
    function findIndex(array, callback, thisArg) {
      var index = -1,
          length = array ? array.length : 0;

      callback = lodash.createCallback(callback, thisArg, 3);
      while (++index < length) {
        if (callback(array[index], index, array)) {
          return index;
        }
      }
      return -1;
    }

    /**
     * This method is like `_.findIndex` except that it iterates over elements
     * of a `collection` from right to left.
     *
     * If a property name is provided for `callback` the created "_.pluck" style
     * callback will return the property value of the given element.
     *
     * If an object is provided for `callback` the created "_.where" style callback
     * will return `true` for elements that have the properties of the given object,
     * else `false`.
     *
     * @static
     * @memberOf _
     * @category Arrays
     * @param {Array} array The array to search.
     * @param {Function|Object|string} [callback=identity] The function called
     *  per iteration. If a property name or object is provided it will be used
     *  to create a "_.pluck" or "_.where" style callback, respectively.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {number} Returns the index of the found element, else `-1`.
     * @example
     *
     * var characters = [
     *   { 'name': 'barney',  'age': 36, 'blocked': true },
     *   { 'name': 'fred',    'age': 40, 'blocked': false },
     *   { 'name': 'pebbles', 'age': 1,  'blocked': true }
     * ];
     *
     * _.findLastIndex(characters, function(chr) {
     *   return chr.age > 30;
     * });
     * // => 1
     *
     * // using "_.where" callback shorthand
     * _.findLastIndex(characters, { 'age': 36 });
     * // => 0
     *
     * // using "_.pluck" callback shorthand
     * _.findLastIndex(characters, 'blocked');
     * // => 2
     */
    function findLastIndex(array, callback, thisArg) {
      var length = array ? array.length : 0;
      callback = lodash.createCallback(callback, thisArg, 3);
      while (length--) {
        if (callback(array[length], length, array)) {
          return length;
        }
      }
      return -1;
    }

    /**
     * Gets the first element or first `n` elements of an array. If a callback
     * is provided elements at the beginning of the array are returned as long
     * as the callback returns truey. The callback is bound to `thisArg` and
     * invoked with three arguments; (value, index, array).
     *
     * If a property name is provided for `callback` the created "_.pluck" style
     * callback will return the property value of the given element.
     *
     * If an object is provided for `callback` the created "_.where" style callback
     * will return `true` for elements that have the properties of the given object,
     * else `false`.
     *
     * @static
     * @memberOf _
     * @alias head, take
     * @category Arrays
     * @param {Array} array The array to query.
     * @param {Function|Object|number|string} [callback] The function called
     *  per element or the number of elements to return. If a property name or
     *  object is provided it will be used to create a "_.pluck" or "_.where"
     *  style callback, respectively.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {*} Returns the first element(s) of `array`.
     * @example
     *
     * _.first([1, 2, 3]);
     * // => 1
     *
     * _.first([1, 2, 3], 2);
     * // => [1, 2]
     *
     * _.first([1, 2, 3], function(num) {
     *   return num < 3;
     * });
     * // => [1, 2]
     *
     * var characters = [
     *   { 'name': 'barney',  'blocked': true,  'employer': 'slate' },
     *   { 'name': 'fred',    'blocked': false, 'employer': 'slate' },
     *   { 'name': 'pebbles', 'blocked': true,  'employer': 'na' }
     * ];
     *
     * // using "_.pluck" callback shorthand
     * _.first(characters, 'blocked');
     * // => [{ 'name': 'barney', 'blocked': true, 'employer': 'slate' }]
     *
     * // using "_.where" callback shorthand
     * _.pluck(_.first(characters, { 'employer': 'slate' }), 'name');
     * // => ['barney', 'fred']
     */
    function first(array, callback, thisArg) {
      var n = 0,
          length = array ? array.length : 0;

      if (typeof callback != 'number' && callback != null) {
        var index = -1;
        callback = lodash.createCallback(callback, thisArg, 3);
        while (++index < length && callback(array[index], index, array)) {
          n++;
        }
      } else {
        n = callback;
        if (n == null || thisArg) {
          return array ? array[0] : undefined;
        }
      }
      return slice(array, 0, nativeMin(nativeMax(0, n), length));
    }

    /**
     * Flattens a nested array (the nesting can be to any depth). If `isShallow`
     * is truey, the array will only be flattened a single level. If a callback
     * is provided each element of the array is passed through the callback before
     * flattening. The callback is bound to `thisArg` and invoked with three
     * arguments; (value, index, array).
     *
     * If a property name is provided for `callback` the created "_.pluck" style
     * callback will return the property value of the given element.
     *
     * If an object is provided for `callback` the created "_.where" style callback
     * will return `true` for elements that have the properties of the given object,
     * else `false`.
     *
     * @static
     * @memberOf _
     * @category Arrays
     * @param {Array} array The array to flatten.
     * @param {boolean} [isShallow=false] A flag to restrict flattening to a single level.
     * @param {Function|Object|string} [callback=identity] The function called
     *  per iteration. If a property name or object is provided it will be used
     *  to create a "_.pluck" or "_.where" style callback, respectively.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {Array} Returns a new flattened array.
     * @example
     *
     * _.flatten([1, [2], [3, [[4]]]]);
     * // => [1, 2, 3, 4];
     *
     * _.flatten([1, [2], [3, [[4]]]], true);
     * // => [1, 2, 3, [[4]]];
     *
     * var characters = [
     *   { 'name': 'barney', 'age': 30, 'pets': ['hoppy'] },
     *   { 'name': 'fred',   'age': 40, 'pets': ['baby puss', 'dino'] }
     * ];
     *
     * // using "_.pluck" callback shorthand
     * _.flatten(characters, 'pets');
     * // => ['hoppy', 'baby puss', 'dino']
     */
    function flatten(array, isShallow, callback, thisArg) {
      // juggle arguments
      if (typeof isShallow != 'boolean' && isShallow != null) {
        thisArg = callback;
        callback = (typeof isShallow != 'function' && thisArg && thisArg[isShallow] === array) ? null : isShallow;
        isShallow = false;
      }
      if (callback != null) {
        array = map(array, callback, thisArg);
      }
      return baseFlatten(array, isShallow);
    }

    /**
     * Gets the index at which the first occurrence of `value` is found using
     * strict equality for comparisons, i.e. `===`. If the array is already sorted
     * providing `true` for `fromIndex` will run a faster binary search.
     *
     * @static
     * @memberOf _
     * @category Arrays
     * @param {Array} array The array to search.
     * @param {*} value The value to search for.
     * @param {boolean|number} [fromIndex=0] The index to search from or `true`
     *  to perform a binary search on a sorted array.
     * @returns {number} Returns the index of the matched value or `-1`.
     * @example
     *
     * _.indexOf([1, 2, 3, 1, 2, 3], 2);
     * // => 1
     *
     * _.indexOf([1, 2, 3, 1, 2, 3], 2, 3);
     * // => 4
     *
     * _.indexOf([1, 1, 2, 2, 3, 3], 2, true);
     * // => 2
     */
    function indexOf(array, value, fromIndex) {
      if (typeof fromIndex == 'number') {
        var length = array ? array.length : 0;
        fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex || 0);
      } else if (fromIndex) {
        var index = sortedIndex(array, value);
        return array[index] === value ? index : -1;
      }
      return baseIndexOf(array, value, fromIndex);
    }

    /**
     * Gets all but the last element or last `n` elements of an array. If a
     * callback is provided elements at the end of the array are excluded from
     * the result as long as the callback returns truey. The callback is bound
     * to `thisArg` and invoked with three arguments; (value, index, array).
     *
     * If a property name is provided for `callback` the created "_.pluck" style
     * callback will return the property value of the given element.
     *
     * If an object is provided for `callback` the created "_.where" style callback
     * will return `true` for elements that have the properties of the given object,
     * else `false`.
     *
     * @static
     * @memberOf _
     * @category Arrays
     * @param {Array} array The array to query.
     * @param {Function|Object|number|string} [callback=1] The function called
     *  per element or the number of elements to exclude. If a property name or
     *  object is provided it will be used to create a "_.pluck" or "_.where"
     *  style callback, respectively.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {Array} Returns a slice of `array`.
     * @example
     *
     * _.initial([1, 2, 3]);
     * // => [1, 2]
     *
     * _.initial([1, 2, 3], 2);
     * // => [1]
     *
     * _.initial([1, 2, 3], function(num) {
     *   return num > 1;
     * });
     * // => [1]
     *
     * var characters = [
     *   { 'name': 'barney',  'blocked': false, 'employer': 'slate' },
     *   { 'name': 'fred',    'blocked': true,  'employer': 'slate' },
     *   { 'name': 'pebbles', 'blocked': true,  'employer': 'na' }
     * ];
     *
     * // using "_.pluck" callback shorthand
     * _.initial(characters, 'blocked');
     * // => [{ 'name': 'barney',  'blocked': false, 'employer': 'slate' }]
     *
     * // using "_.where" callback shorthand
     * _.pluck(_.initial(characters, { 'employer': 'na' }), 'name');
     * // => ['barney', 'fred']
     */
    function initial(array, callback, thisArg) {
      var n = 0,
          length = array ? array.length : 0;

      if (typeof callback != 'number' && callback != null) {
        var index = length;
        callback = lodash.createCallback(callback, thisArg, 3);
        while (index-- && callback(array[index], index, array)) {
          n++;
        }
      } else {
        n = (callback == null || thisArg) ? 1 : callback || n;
      }
      return slice(array, 0, nativeMin(nativeMax(0, length - n), length));
    }

    /**
     * Creates an array of unique values present in all provided arrays using
     * strict equality for comparisons, i.e. `===`.
     *
     * @static
     * @memberOf _
     * @category Arrays
     * @param {...Array} [array] The arrays to inspect.
     * @returns {Array} Returns an array of shared values.
     * @example
     *
     * _.intersection([1, 2, 3], [5, 2, 1, 4], [2, 1]);
     * // => [1, 2]
     */
    function intersection() {
      var args = [],
          argsIndex = -1,
          argsLength = arguments.length,
          caches = getArray(),
          indexOf = getIndexOf(),
          trustIndexOf = indexOf === baseIndexOf,
          seen = getArray();

      while (++argsIndex < argsLength) {
        var value = arguments[argsIndex];
        if (isArray(value) || isArguments(value)) {
          args.push(value);
          caches.push(trustIndexOf && value.length >= largeArraySize &&
            createCache(argsIndex ? args[argsIndex] : seen));
        }
      }
      var array = args[0],
          index = -1,
          length = array ? array.length : 0,
          result = [];

      outer:
      while (++index < length) {
        var cache = caches[0];
        value = array[index];

        if ((cache ? cacheIndexOf(cache, value) : indexOf(seen, value)) < 0) {
          argsIndex = argsLength;
          (cache || seen).push(value);
          while (--argsIndex) {
            cache = caches[argsIndex];
            if ((cache ? cacheIndexOf(cache, value) : indexOf(args[argsIndex], value)) < 0) {
              continue outer;
            }
          }
          result.push(value);
        }
      }
      while (argsLength--) {
        cache = caches[argsLength];
        if (cache) {
          releaseObject(cache);
        }
      }
      releaseArray(caches);
      releaseArray(seen);
      return result;
    }

    /**
     * Gets the last element or last `n` elements of an array. If a callback is
     * provided elements at the end of the array are returned as long as the
     * callback returns truey. The callback is bound to `thisArg` and invoked
     * with three arguments; (value, index, array).
     *
     * If a property name is provided for `callback` the created "_.pluck" style
     * callback will return the property value of the given element.
     *
     * If an object is provided for `callback` the created "_.where" style callback
     * will return `true` for elements that have the properties of the given object,
     * else `false`.
     *
     * @static
     * @memberOf _
     * @category Arrays
     * @param {Array} array The array to query.
     * @param {Function|Object|number|string} [callback] The function called
     *  per element or the number of elements to return. If a property name or
     *  object is provided it will be used to create a "_.pluck" or "_.where"
     *  style callback, respectively.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {*} Returns the last element(s) of `array`.
     * @example
     *
     * _.last([1, 2, 3]);
     * // => 3
     *
     * _.last([1, 2, 3], 2);
     * // => [2, 3]
     *
     * _.last([1, 2, 3], function(num) {
     *   return num > 1;
     * });
     * // => [2, 3]
     *
     * var characters = [
     *   { 'name': 'barney',  'blocked': false, 'employer': 'slate' },
     *   { 'name': 'fred',    'blocked': true,  'employer': 'slate' },
     *   { 'name': 'pebbles', 'blocked': true,  'employer': 'na' }
     * ];
     *
     * // using "_.pluck" callback shorthand
     * _.pluck(_.last(characters, 'blocked'), 'name');
     * // => ['fred', 'pebbles']
     *
     * // using "_.where" callback shorthand
     * _.last(characters, { 'employer': 'na' });
     * // => [{ 'name': 'pebbles', 'blocked': true, 'employer': 'na' }]
     */
    function last(array, callback, thisArg) {
      var n = 0,
          length = array ? array.length : 0;

      if (typeof callback != 'number' && callback != null) {
        var index = length;
        callback = lodash.createCallback(callback, thisArg, 3);
        while (index-- && callback(array[index], index, array)) {
          n++;
        }
      } else {
        n = callback;
        if (n == null || thisArg) {
          return array ? array[length - 1] : undefined;
        }
      }
      return slice(array, nativeMax(0, length - n));
    }

    /**
     * Gets the index at which the last occurrence of `value` is found using strict
     * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used
     * as the offset from the end of the collection.
     *
     * If a property name is provided for `callback` the created "_.pluck" style
     * callback will return the property value of the given element.
     *
     * If an object is provided for `callback` the created "_.where" style callback
     * will return `true` for elements that have the properties of the given object,
     * else `false`.
     *
     * @static
     * @memberOf _
     * @category Arrays
     * @param {Array} array The array to search.
     * @param {*} value The value to search for.
     * @param {number} [fromIndex=array.length-1] The index to search from.
     * @returns {number} Returns the index of the matched value or `-1`.
     * @example
     *
     * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2);
     * // => 4
     *
     * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2, 3);
     * // => 1
     */
    function lastIndexOf(array, value, fromIndex) {
      var index = array ? array.length : 0;
      if (typeof fromIndex == 'number') {
        index = (fromIndex < 0 ? nativeMax(0, index + fromIndex) : nativeMin(fromIndex, index - 1)) + 1;
      }
      while (index--) {
        if (array[index] === value) {
          return index;
        }
      }
      return -1;
    }

    /**
     * Removes all provided values from the given array using strict equality for
     * comparisons, i.e. `===`.
     *
     * @static
     * @memberOf _
     * @category Arrays
     * @param {Array} array The array to modify.
     * @param {...*} [value] The values to remove.
     * @returns {Array} Returns `array`.
     * @example
     *
     * var array = [1, 2, 3, 1, 2, 3];
     * _.pull(array, 2, 3);
     * console.log(array);
     * // => [1, 1]
     */
    function pull(array) {
      var args = arguments,
          argsIndex = 0,
          argsLength = args.length,
          length = array ? array.length : 0;

      while (++argsIndex < argsLength) {
        var index = -1,
            value = args[argsIndex];
        while (++index < length) {
          if (array[index] === value) {
            splice.call(array, index--, 1);
            length--;
          }
        }
      }
      return array;
    }

    /**
     * Creates an array of numbers (positive and/or negative) progressing from
     * `start` up to but not including `end`. If `start` is less than `stop` a
     * zero-length range is created unless a negative `step` is specified.
     *
     * @static
     * @memberOf _
     * @category Arrays
     * @param {number} [start=0] The start of the range.
     * @param {number} end The end of the range.
     * @param {number} [step=1] The value to increment or decrement by.
     * @returns {Array} Returns a new range array.
     * @example
     *
     * _.range(4);
     * // => [0, 1, 2, 3]
     *
     * _.range(1, 5);
     * // => [1, 2, 3, 4]
     *
     * _.range(0, 20, 5);
     * // => [0, 5, 10, 15]
     *
     * _.range(0, -4, -1);
     * // => [0, -1, -2, -3]
     *
     * _.range(1, 4, 0);
     * // => [1, 1, 1]
     *
     * _.range(0);
     * // => []
     */
    function range(start, end, step) {
      start = +start || 0;
      step = typeof step == 'number' ? step : (+step || 1);

      if (end == null) {
        end = start;
        start = 0;
      }
      // use `Array(length)` so engines like Chakra and V8 avoid slower modes
      // http://youtu.be/XAqIpGU8ZZk#t=17m25s
      var index = -1,
          length = nativeMax(0, ceil((end - start) / (step || 1))),
          result = Array(length);

      while (++index < length) {
        result[index] = start;
        start += step;
      }
      return result;
    }

    /**
     * Removes all elements from an array that the callback returns truey for
     * and returns an array of removed elements. The callback is bound to `thisArg`
     * and invoked with three arguments; (value, index, array).
     *
     * If a property name is provided for `callback` the created "_.pluck" style
     * callback will return the property value of the given element.
     *
     * If an object is provided for `callback` the created "_.where" style callback
     * will return `true` for elements that have the properties of the given object,
     * else `false`.
     *
     * @static
     * @memberOf _
     * @category Arrays
     * @param {Array} array The array to modify.
     * @param {Function|Object|string} [callback=identity] The function called
     *  per iteration. If a property name or object is provided it will be used
     *  to create a "_.pluck" or "_.where" style callback, respectively.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {Array} Returns a new array of removed elements.
     * @example
     *
     * var array = [1, 2, 3, 4, 5, 6];
     * var evens = _.remove(array, function(num) { return num % 2 == 0; });
     *
     * console.log(array);
     * // => [1, 3, 5]
     *
     * console.log(evens);
     * // => [2, 4, 6]
     */
    function remove(array, callback, thisArg) {
      var index = -1,
          length = array ? array.length : 0,
          result = [];

      callback = lodash.createCallback(callback, thisArg, 3);
      while (++index < length) {
        var value = array[index];
        if (callback(value, index, array)) {
          result.push(value);
          splice.call(array, index--, 1);
          length--;
        }
      }
      return result;
    }

    /**
     * The opposite of `_.initial` this method gets all but the first element or
     * first `n` elements of an array. If a callback function is provided elements
     * at the beginning of the array are excluded from the result as long as the
     * callback returns truey. The callback is bound to `thisArg` and invoked
     * with three arguments; (value, index, array).
     *
     * If a property name is provided for `callback` the created "_.pluck" style
     * callback will return the property value of the given element.
     *
     * If an object is provided for `callback` the created "_.where" style callback
     * will return `true` for elements that have the properties of the given object,
     * else `false`.
     *
     * @static
     * @memberOf _
     * @alias drop, tail
     * @category Arrays
     * @param {Array} array The array to query.
     * @param {Function|Object|number|string} [callback=1] The function called
     *  per element or the number of elements to exclude. If a property name or
     *  object is provided it will be used to create a "_.pluck" or "_.where"
     *  style callback, respectively.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {Array} Returns a slice of `array`.
     * @example
     *
     * _.rest([1, 2, 3]);
     * // => [2, 3]
     *
     * _.rest([1, 2, 3], 2);
     * // => [3]
     *
     * _.rest([1, 2, 3], function(num) {
     *   return num < 3;
     * });
     * // => [3]
     *
     * var characters = [
     *   { 'name': 'barney',  'blocked': true,  'employer': 'slate' },
     *   { 'name': 'fred',    'blocked': false,  'employer': 'slate' },
     *   { 'name': 'pebbles', 'blocked': true, 'employer': 'na' }
     * ];
     *
     * // using "_.pluck" callback shorthand
     * _.pluck(_.rest(characters, 'blocked'), 'name');
     * // => ['fred', 'pebbles']
     *
     * // using "_.where" callback shorthand
     * _.rest(characters, { 'employer': 'slate' });
     * // => [{ 'name': 'pebbles', 'blocked': true, 'employer': 'na' }]
     */
    function rest(array, callback, thisArg) {
      if (typeof callback != 'number' && callback != null) {
        var n = 0,
            index = -1,
            length = array ? array.length : 0;

        callback = lodash.createCallback(callback, thisArg, 3);
        while (++index < length && callback(array[index], index, array)) {
          n++;
        }
      } else {
        n = (callback == null || thisArg) ? 1 : nativeMax(0, callback);
      }
      return slice(array, n);
    }

    /**
     * Uses a binary search to determine the smallest index at which a value
     * should be inserted into a given sorted array in order to maintain the sort
     * order of the array. If a callback is provided it will be executed for
     * `value` and each element of `array` to compute their sort ranking. The
     * callback is bound to `thisArg` and invoked with one argument; (value).
     *
     * If a property name is provided for `callback` the created "_.pluck" style
     * callback will return the property value of the given element.
     *
     * If an object is provided for `callback` the created "_.where" style callback
     * will return `true` for elements that have the properties of the given object,
     * else `false`.
     *
     * @static
     * @memberOf _
     * @category Arrays
     * @param {Array} array The array to inspect.
     * @param {*} value The value to evaluate.
     * @param {Function|Object|string} [callback=identity] The function called
     *  per iteration. If a property name or object is provided it will be used
     *  to create a "_.pluck" or "_.where" style callback, respectively.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {number} Returns the index at which `value` should be inserted
     *  into `array`.
     * @example
     *
     * _.sortedIndex([20, 30, 50], 40);
     * // => 2
     *
     * // using "_.pluck" callback shorthand
     * _.sortedIndex([{ 'x': 20 }, { 'x': 30 }, { 'x': 50 }], { 'x': 40 }, 'x');
     * // => 2
     *
     * var dict = {
     *   'wordToNumber': { 'twenty': 20, 'thirty': 30, 'fourty': 40, 'fifty': 50 }
     * };
     *
     * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) {
     *   return dict.wordToNumber[word];
     * });
     * // => 2
     *
     * _.sortedIndex(['twenty', 'thirty', 'fifty'], 'fourty', function(word) {
     *   return this.wordToNumber[word];
     * }, dict);
     * // => 2
     */
    function sortedIndex(array, value, callback, thisArg) {
      var low = 0,
          high = array ? array.length : low;

      // explicitly reference `identity` for better inlining in Firefox
      callback = callback ? lodash.createCallback(callback, thisArg, 1) : identity;
      value = callback(value);

      while (low < high) {
        var mid = (low + high) >>> 1;
        (callback(array[mid]) < value)
          ? low = mid + 1
          : high = mid;
      }
      return low;
    }

    /**
     * Creates an array of unique values, in order, of the provided arrays using
     * strict equality for comparisons, i.e. `===`.
     *
     * @static
     * @memberOf _
     * @category Arrays
     * @param {...Array} [array] The arrays to inspect.
     * @returns {Array} Returns an array of combined values.
     * @example
     *
     * _.union([1, 2, 3], [5, 2, 1, 4], [2, 1]);
     * // => [1, 2, 3, 5, 4]
     */
    function union() {
      return baseUniq(baseFlatten(arguments, true, true));
    }

    /**
     * Creates a duplicate-value-free version of an array using strict equality
     * for comparisons, i.e. `===`. If the array is sorted, providing
     * `true` for `isSorted` will use a faster algorithm. If a callback is provided
     * each element of `array` is passed through the callback before uniqueness
     * is computed. The callback is bound to `thisArg` and invoked with three
     * arguments; (value, index, array).
     *
     * If a property name is provided for `callback` the created "_.pluck" style
     * callback will return the property value of the given element.
     *
     * If an object is provided for `callback` the created "_.where" style callback
     * will return `true` for elements that have the properties of the given object,
     * else `false`.
     *
     * @static
     * @memberOf _
     * @alias unique
     * @category Arrays
     * @param {Array} array The array to process.
     * @param {boolean} [isSorted=false] A flag to indicate that `array` is sorted.
     * @param {Function|Object|string} [callback=identity] The function called
     *  per iteration. If a property name or object is provided it will be used
     *  to create a "_.pluck" or "_.where" style callback, respectively.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {Array} Returns a duplicate-value-free array.
     * @example
     *
     * _.uniq([1, 2, 1, 3, 1]);
     * // => [1, 2, 3]
     *
     * _.uniq([1, 1, 2, 2, 3], true);
     * // => [1, 2, 3]
     *
     * _.uniq(['A', 'b', 'C', 'a', 'B', 'c'], function(letter) { return letter.toLowerCase(); });
     * // => ['A', 'b', 'C']
     *
     * _.uniq([1, 2.5, 3, 1.5, 2, 3.5], function(num) { return this.floor(num); }, Math);
     * // => [1, 2.5, 3]
     *
     * // using "_.pluck" callback shorthand
     * _.uniq([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
     * // => [{ 'x': 1 }, { 'x': 2 }]
     */
    function uniq(array, isSorted, callback, thisArg) {
      // juggle arguments
      if (typeof isSorted != 'boolean' && isSorted != null) {
        thisArg = callback;
        callback = (typeof isSorted != 'function' && thisArg && thisArg[isSorted] === array) ? null : isSorted;
        isSorted = false;
      }
      if (callback != null) {
        callback = lodash.createCallback(callback, thisArg, 3);
      }
      return baseUniq(array, isSorted, callback);
    }

    /**
     * Creates an array excluding all provided values using strict equality for
     * comparisons, i.e. `===`.
     *
     * @static
     * @memberOf _
     * @category Arrays
     * @param {Array} array The array to filter.
     * @param {...*} [value] The values to exclude.
     * @returns {Array} Returns a new array of filtered values.
     * @example
     *
     * _.without([1, 2, 1, 0, 3, 1, 4], 0, 1);
     * // => [2, 3, 4]
     */
    function without(array) {
      return baseDifference(array, slice(arguments, 1));
    }

    /**
     * Creates an array that is the symmetric difference of the provided arrays.
     * See http://en.wikipedia.org/wiki/Symmetric_difference.
     *
     * @static
     * @memberOf _
     * @category Arrays
     * @param {...Array} [array] The arrays to inspect.
     * @returns {Array} Returns an array of values.
     * @example
     *
     * _.xor([1, 2, 3], [5, 2, 1, 4]);
     * // => [3, 5, 4]
     *
     * _.xor([1, 2, 5], [2, 3, 5], [3, 4, 5]);
     * // => [1, 4, 5]
     */
    function xor() {
      var index = -1,
          length = arguments.length;

      while (++index < length) {
        var array = arguments[index];
        if (isArray(array) || isArguments(array)) {
          var result = result
            ? baseUniq(baseDifference(result, array).concat(baseDifference(array, result)))
            : array;
        }
      }
      return result || [];
    }

    /**
     * Creates an array of grouped elements, the first of which contains the first
     * elements of the given arrays, the second of which contains the second
     * elements of the given arrays, and so on.
     *
     * @static
     * @memberOf _
     * @alias unzip
     * @category Arrays
     * @param {...Array} [array] Arrays to process.
     * @returns {Array} Returns a new array of grouped elements.
     * @example
     *
     * _.zip(['fred', 'barney'], [30, 40], [true, false]);
     * // => [['fred', 30, true], ['barney', 40, false]]
     */
    function zip() {
      var array = arguments.length > 1 ? arguments : arguments[0],
          index = -1,
          length = array ? max(pluck(array, 'length')) : 0,
          result = Array(length < 0 ? 0 : length);

      while (++index < length) {
        result[index] = pluck(array, index);
      }
      return result;
    }

    /**
     * Creates an object composed from arrays of `keys` and `values`. Provide
     * either a single two dimensional array, i.e. `[[key1, value1], [key2, value2]]`
     * or two arrays, one of `keys` and one of corresponding `values`.
     *
     * @static
     * @memberOf _
     * @alias object
     * @category Arrays
     * @param {Array} keys The array of keys.
     * @param {Array} [values=[]] The array of values.
     * @returns {Object} Returns an object composed of the given keys and
     *  corresponding values.
     * @example
     *
     * _.zipObject(['fred', 'barney'], [30, 40]);
     * // => { 'fred': 30, 'barney': 40 }
     */
    function zipObject(keys, values) {
      var index = -1,
          length = keys ? keys.length : 0,
          result = {};

      if (!values && length && !isArray(keys[0])) {
        values = [];
      }
      while (++index < length) {
        var key = keys[index];
        if (values) {
          result[key] = values[index];
        } else if (key) {
          result[key[0]] = key[1];
        }
      }
      return result;
    }

    /*--------------------------------------------------------------------------*/

    /**
     * Creates a function that executes `func`, with  the `this` binding and
     * arguments of the created function, only after being called `n` times.
     *
     * @static
     * @memberOf _
     * @category Functions
     * @param {number} n The number of times the function must be called before
     *  `func` is executed.
     * @param {Function} func The function to restrict.
     * @returns {Function} Returns the new restricted function.
     * @example
     *
     * var saves = ['profile', 'settings'];
     *
     * var done = _.after(saves.length, function() {
     *   console.log('Done saving!');
     * });
     *
     * _.forEach(saves, function(type) {
     *   asyncSave({ 'type': type, 'complete': done });
     * });
     * // => logs 'Done saving!', after all saves have completed
     */
    function after(n, func) {
      if (!isFunction(func)) {
        throw new TypeError;
      }
      return function() {
        if (--n < 1) {
          return func.apply(this, arguments);
        }
      };
    }

    /**
     * Creates a function that, when called, invokes `func` with the `this`
     * binding of `thisArg` and prepends any additional `bind` arguments to those
     * provided to the bound function.
     *
     * @static
     * @memberOf _
     * @category Functions
     * @param {Function} func The function to bind.
     * @param {*} [thisArg] The `this` binding of `func`.
     * @param {...*} [arg] Arguments to be partially applied.
     * @returns {Function} Returns the new bound function.
     * @example
     *
     * var func = function(greeting) {
     *   return greeting + ' ' + this.name;
     * };
     *
     * func = _.bind(func, { 'name': 'fred' }, 'hi');
     * func();
     * // => 'hi fred'
     */
    function bind(func, thisArg) {
      return arguments.length > 2
        ? createWrapper(func, 17, slice(arguments, 2), null, thisArg)
        : createWrapper(func, 1, null, null, thisArg);
    }

    /**
     * Binds methods of an object to the object itself, overwriting the existing
     * method. Method names may be specified as individual arguments or as arrays
     * of method names. If no method names are provided all the function properties
     * of `object` will be bound.
     *
     * @static
     * @memberOf _
     * @category Functions
     * @param {Object} object The object to bind and assign the bound methods to.
     * @param {...string} [methodName] The object method names to
     *  bind, specified as individual method names or arrays of method names.
     * @returns {Object} Returns `object`.
     * @example
     *
     * var view = {
     *   'label': 'docs',
     *   'onClick': function() { console.log('clicked ' + this.label); }
     * };
     *
     * _.bindAll(view);
     * jQuery('#docs').on('click', view.onClick);
     * // => logs 'clicked docs', when the button is clicked
     */
    function bindAll(object) {
      var funcs = arguments.length > 1 ? baseFlatten(arguments, true, false, 1) : functions(object),
          index = -1,
          length = funcs.length;

      while (++index < length) {
        var key = funcs[index];
        object[key] = createWrapper(object[key], 1, null, null, object);
      }
      return object;
    }

    /**
     * Creates a function that, when called, invokes the method at `object[key]`
     * and prepends any additional `bindKey` arguments to those provided to the bound
     * function. This method differs from `_.bind` by allowing bound functions to
     * reference methods that will be redefined or don't yet exist.
     * See http://michaux.ca/articles/lazy-function-definition-pattern.
     *
     * @static
     * @memberOf _
     * @category Functions
     * @param {Object} object The object the method belongs to.
     * @param {string} key The key of the method.
     * @param {...*} [arg] Arguments to be partially applied.
     * @returns {Function} Returns the new bound function.
     * @example
     *
     * var object = {
     *   'name': 'fred',
     *   'greet': function(greeting) {
     *     return greeting + ' ' + this.name;
     *   }
     * };
     *
     * var func = _.bindKey(object, 'greet', 'hi');
     * func();
     * // => 'hi fred'
     *
     * object.greet = function(greeting) {
     *   return greeting + 'ya ' + this.name + '!';
     * };
     *
     * func();
     * // => 'hiya fred!'
     */
    function bindKey(object, key) {
      return arguments.length > 2
        ? createWrapper(key, 19, slice(arguments, 2), null, object)
        : createWrapper(key, 3, null, null, object);
    }

    /**
     * Creates a function that is the composition of the provided functions,
     * where each function consumes the return value of the function that follows.
     * For example, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`.
     * Each function is executed with the `this` binding of the composed function.
     *
     * @static
     * @memberOf _
     * @category Functions
     * @param {...Function} [func] Functions to compose.
     * @returns {Function} Returns the new composed function.
     * @example
     *
     * var realNameMap = {
     *   'pebbles': 'penelope'
     * };
     *
     * var format = function(name) {
     *   name = realNameMap[name.toLowerCase()] || name;
     *   return name.charAt(0).toUpperCase() + name.slice(1).toLowerCase();
     * };
     *
     * var greet = function(formatted) {
     *   return 'Hiya ' + formatted + '!';
     * };
     *
     * var welcome = _.compose(greet, format);
     * welcome('pebbles');
     * // => 'Hiya Penelope!'
     */
    function compose() {
      var funcs = arguments,
          length = funcs.length;

      while (length--) {
        if (!isFunction(funcs[length])) {
          throw new TypeError;
        }
      }
      return function() {
        var args = arguments,
            length = funcs.length;

        while (length--) {
          args = [funcs[length].apply(this, args)];
        }
        return args[0];
      };
    }

    /**
     * Creates a function which accepts one or more arguments of `func` that when
     * invoked either executes `func` returning its result, if all `func` arguments
     * have been provided, or returns a function that accepts one or more of the
     * remaining `func` arguments, and so on. The arity of `func` can be specified
     * if `func.length` is not sufficient.
     *
     * @static
     * @memberOf _
     * @category Functions
     * @param {Function} func The function to curry.
     * @param {number} [arity=func.length] The arity of `func`.
     * @returns {Function} Returns the new curried function.
     * @example
     *
     * var curried = _.curry(function(a, b, c) {
     *   console.log(a + b + c);
     * });
     *
     * curried(1)(2)(3);
     * // => 6
     *
     * curried(1, 2)(3);
     * // => 6
     *
     * curried(1, 2, 3);
     * // => 6
     */
    function curry(func, arity) {
      arity = typeof arity == 'number' ? arity : (+arity || func.length);
      return createWrapper(func, 4, null, null, null, arity);
    }

    /**
     * Creates a function that will delay the execution of `func` until after
     * `wait` milliseconds have elapsed since the last time it was invoked.
     * Provide an options object to indicate that `func` should be invoked on
     * the leading and/or trailing edge of the `wait` timeout. Subsequent calls
     * to the debounced function will return the result of the last `func` call.
     *
     * Note: If `leading` and `trailing` options are `true` `func` will be called
     * on the trailing edge of the timeout only if the the debounced function is
     * invoked more than once during the `wait` timeout.
     *
     * @static
     * @memberOf _
     * @category Functions
     * @param {Function} func The function to debounce.
     * @param {number} wait The number of milliseconds to delay.
     * @param {Object} [options] The options object.
     * @param {boolean} [options.leading=false] Specify execution on the leading edge of the timeout.
     * @param {number} [options.maxWait] The maximum time `func` is allowed to be delayed before it's called.
     * @param {boolean} [options.trailing=true] Specify execution on the trailing edge of the timeout.
     * @returns {Function} Returns the new debounced function.
     * @example
     *
     * // avoid costly calculations while the window size is in flux
     * var lazyLayout = _.debounce(calculateLayout, 150);
     * jQuery(window).on('resize', lazyLayout);
     *
     * // execute `sendMail` when the click event is fired, debouncing subsequent calls
     * jQuery('#postbox').on('click', _.debounce(sendMail, 300, {
     *   'leading': true,
     *   'trailing': false
     * });
     *
     * // ensure `batchLog` is executed once after 1 second of debounced calls
     * var source = new EventSource('/stream');
     * source.addEventListener('message', _.debounce(batchLog, 250, {
     *   'maxWait': 1000
     * }, false);
     */
    function debounce(func, wait, options) {
      var args,
          maxTimeoutId,
          result,
          stamp,
          thisArg,
          timeoutId,
          trailingCall,
          lastCalled = 0,
          maxWait = false,
          trailing = true;

      if (!isFunction(func)) {
        throw new TypeError;
      }
      wait = nativeMax(0, wait) || 0;
      if (options === true) {
        var leading = true;
        trailing = false;
      } else if (isObject(options)) {
        leading = options.leading;
        maxWait = 'maxWait' in options && (nativeMax(wait, options.maxWait) || 0);
        trailing = 'trailing' in options ? options.trailing : trailing;
      }
      var delayed = function() {
        var remaining = wait - (now() - stamp);
        if (remaining <= 0) {
          if (maxTimeoutId) {
            clearTimeout(maxTimeoutId);
          }
          var isCalled = trailingCall;
          maxTimeoutId = timeoutId = trailingCall = undefined;
          if (isCalled) {
            lastCalled = now();
            result = func.apply(thisArg, args);
            if (!timeoutId && !maxTimeoutId) {
              args = thisArg = null;
            }
          }
        } else {
          timeoutId = setTimeout(delayed, remaining);
        }
      };

      var maxDelayed = function() {
        if (timeoutId) {
          clearTimeout(timeoutId);
        }
        maxTimeoutId = timeoutId = trailingCall = undefined;
        if (trailing || (maxWait !== wait)) {
          lastCalled = now();
          result = func.apply(thisArg, args);
          if (!timeoutId && !maxTimeoutId) {
            args = thisArg = null;
          }
        }
      };

      return function() {
        args = arguments;
        stamp = now();
        thisArg = this;
        trailingCall = trailing && (timeoutId || !leading);

        if (maxWait === false) {
          var leadingCall = leading && !timeoutId;
        } else {
          if (!maxTimeoutId && !leading) {
            lastCalled = stamp;
          }
          var remaining = maxWait - (stamp - lastCalled),
              isCalled = remaining <= 0;

          if (isCalled) {
            if (maxTimeoutId) {
              maxTimeoutId = clearTimeout(maxTimeoutId);
            }
            lastCalled = stamp;
            result = func.apply(thisArg, args);
          }
          else if (!maxTimeoutId) {
            maxTimeoutId = setTimeout(maxDelayed, remaining);
          }
        }
        if (isCalled && timeoutId) {
          timeoutId = clearTimeout(timeoutId);
        }
        else if (!timeoutId && wait !== maxWait) {
          timeoutId = setTimeout(delayed, wait);
        }
        if (leadingCall) {
          isCalled = true;
          result = func.apply(thisArg, args);
        }
        if (isCalled && !timeoutId && !maxTimeoutId) {
          args = thisArg = null;
        }
        return result;
      };
    }

    /**
     * Defers executing the `func` function until the current call stack has cleared.
     * Additional arguments will be provided to `func` when it is invoked.
     *
     * @static
     * @memberOf _
     * @category Functions
     * @param {Function} func The function to defer.
     * @param {...*} [arg] Arguments to invoke the function with.
     * @returns {number} Returns the timer id.
     * @example
     *
     * _.defer(function(text) { console.log(text); }, 'deferred');
     * // logs 'deferred' after one or more milliseconds
     */
    function defer(func) {
      if (!isFunction(func)) {
        throw new TypeError;
      }
      var args = slice(arguments, 1);
      return setTimeout(function() { func.apply(undefined, args); }, 1);
    }

    /**
     * Executes the `func` function after `wait` milliseconds. Additional arguments
     * will be provided to `func` when it is invoked.
     *
     * @static
     * @memberOf _
     * @category Functions
     * @param {Function} func The function to delay.
     * @param {number} wait The number of milliseconds to delay execution.
     * @param {...*} [arg] Arguments to invoke the function with.
     * @returns {number} Returns the timer id.
     * @example
     *
     * _.delay(function(text) { console.log(text); }, 1000, 'later');
     * // => logs 'later' after one second
     */
    function delay(func, wait) {
      if (!isFunction(func)) {
        throw new TypeError;
      }
      var args = slice(arguments, 2);
      return setTimeout(function() { func.apply(undefined, args); }, wait);
    }

    /**
     * Creates a function that memoizes the result of `func`. If `resolver` is
     * provided it will be used to determine the cache key for storing the result
     * based on the arguments provided to the memoized function. By default, the
     * first argument provided to the memoized function is used as the cache key.
     * The `func` is executed with the `this` binding of the memoized function.
     * The result cache is exposed as the `cache` property on the memoized function.
     *
     * @static
     * @memberOf _
     * @category Functions
     * @param {Function} func The function to have its output memoized.
     * @param {Function} [resolver] A function used to resolve the cache key.
     * @returns {Function} Returns the new memoizing function.
     * @example
     *
     * var fibonacci = _.memoize(function(n) {
     *   return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
     * });
     *
     * fibonacci(9)
     * // => 34
     *
     * var data = {
     *   'fred': { 'name': 'fred', 'age': 40 },
     *   'pebbles': { 'name': 'pebbles', 'age': 1 }
     * };
     *
     * // modifying the result cache
     * var get = _.memoize(function(name) { return data[name]; }, _.identity);
     * get('pebbles');
     * // => { 'name': 'pebbles', 'age': 1 }
     *
     * get.cache.pebbles.name = 'penelope';
     * get('pebbles');
     * // => { 'name': 'penelope', 'age': 1 }
     */
    function memoize(func, resolver) {
      if (!isFunction(func)) {
        throw new TypeError;
      }
      var memoized = function() {
        var cache = memoized.cache,
            key = resolver ? resolver.apply(this, arguments) : keyPrefix + arguments[0];

        return hasOwnProperty.call(cache, key)
          ? cache[key]
          : (cache[key] = func.apply(this, arguments));
      }
      memoized.cache = {};
      return memoized;
    }

    /**
     * Creates a function that is restricted to execute `func` once. Repeat calls to
     * the function will return the value of the first call. The `func` is executed
     * with the `this` binding of the created function.
     *
     * @static
     * @memberOf _
     * @category Functions
     * @param {Function} func The function to restrict.
     * @returns {Function} Returns the new restricted function.
     * @example
     *
     * var initialize = _.once(createApplication);
     * initialize();
     * initialize();
     * // `initialize` executes `createApplication` once
     */
    function once(func) {
      var ran,
          result;

      if (!isFunction(func)) {
        throw new TypeError;
      }
      return function() {
        if (ran) {
          return result;
        }
        ran = true;
        result = func.apply(this, arguments);

        // clear the `func` variable so the function may be garbage collected
        func = null;
        return result;
      };
    }

    /**
     * Creates a function that, when called, invokes `func` with any additional
     * `partial` arguments prepended to those provided to the new function. This
     * method is similar to `_.bind` except it does **not** alter the `this` binding.
     *
     * @static
     * @memberOf _
     * @category Functions
     * @param {Function} func The function to partially apply arguments to.
     * @param {...*} [arg] Arguments to be partially applied.
     * @returns {Function} Returns the new partially applied function.
     * @example
     *
     * var greet = function(greeting, name) { return greeting + ' ' + name; };
     * var hi = _.partial(greet, 'hi');
     * hi('fred');
     * // => 'hi fred'
     */
    function partial(func) {
      return createWrapper(func, 16, slice(arguments, 1));
    }

    /**
     * This method is like `_.partial` except that `partial` arguments are
     * appended to those provided to the new function.
     *
     * @static
     * @memberOf _
     * @category Functions
     * @param {Function} func The function to partially apply arguments to.
     * @param {...*} [arg] Arguments to be partially applied.
     * @returns {Function} Returns the new partially applied function.
     * @example
     *
     * var defaultsDeep = _.partialRight(_.merge, _.defaults);
     *
     * var options = {
     *   'variable': 'data',
     *   'imports': { 'jq': $ }
     * };
     *
     * defaultsDeep(options, _.templateSettings);
     *
     * options.variable
     * // => 'data'
     *
     * options.imports
     * // => { '_': _, 'jq': $ }
     */
    function partialRight(func) {
      return createWrapper(func, 32, null, slice(arguments, 1));
    }

    /**
     * Creates a function that, when executed, will only call the `func` function
     * at most once per every `wait` milliseconds. Provide an options object to
     * indicate that `func` should be invoked on the leading and/or trailing edge
     * of the `wait` timeout. Subsequent calls to the throttled function will
     * return the result of the last `func` call.
     *
     * Note: If `leading` and `trailing` options are `true` `func` will be called
     * on the trailing edge of the timeout only if the the throttled function is
     * invoked more than once during the `wait` timeout.
     *
     * @static
     * @memberOf _
     * @category Functions
     * @param {Function} func The function to throttle.
     * @param {number} wait The number of milliseconds to throttle executions to.
     * @param {Object} [options] The options object.
     * @param {boolean} [options.leading=true] Specify execution on the leading edge of the timeout.
     * @param {boolean} [options.trailing=true] Specify execution on the trailing edge of the timeout.
     * @returns {Function} Returns the new throttled function.
     * @example
     *
     * // avoid excessively updating the position while scrolling
     * var throttled = _.throttle(updatePosition, 100);
     * jQuery(window).on('scroll', throttled);
     *
     * // execute `renewToken` when the click event is fired, but not more than once every 5 minutes
     * jQuery('.interactive').on('click', _.throttle(renewToken, 300000, {
     *   'trailing': false
     * }));
     */
    function throttle(func, wait, options) {
      var leading = true,
          trailing = true;

      if (!isFunction(func)) {
        throw new TypeError;
      }
      if (options === false) {
        leading = false;
      } else if (isObject(options)) {
        leading = 'leading' in options ? options.leading : leading;
        trailing = 'trailing' in options ? options.trailing : trailing;
      }
      debounceOptions.leading = leading;
      debounceOptions.maxWait = wait;
      debounceOptions.trailing = trailing;

      return debounce(func, wait, debounceOptions);
    }

    /**
     * Creates a function that provides `value` to the wrapper function as its
     * first argument. Additional arguments provided to the function are appended
     * to those provided to the wrapper function. The wrapper is executed with
     * the `this` binding of the created function.
     *
     * @static
     * @memberOf _
     * @category Functions
     * @param {*} value The value to wrap.
     * @param {Function} wrapper The wrapper function.
     * @returns {Function} Returns the new function.
     * @example
     *
     * var p = _.wrap(_.escape, function(func, text) {
     *   return '<p>' + func(text) + '</p>';
     * });
     *
     * p('Fred, Wilma, & Pebbles');
     * // => '<p>Fred, Wilma, &amp; Pebbles</p>'
     */
    function wrap(value, wrapper) {
      return createWrapper(wrapper, 16, [value]);
    }

    /*--------------------------------------------------------------------------*/

    /**
     * Creates a function that returns `value`.
     *
     * @static
     * @memberOf _
     * @category Utilities
     * @param {*} value The value to return from the new function.
     * @returns {Function} Returns the new function.
     * @example
     *
     * var object = { 'name': 'fred' };
     * var getter = _.constant(object);
     * getter() === object;
     * // => true
     */
    function constant(value) {
      return function() {
        return value;
      };
    }

    /**
     * Produces a callback bound to an optional `thisArg`. If `func` is a property
     * name the created callback will return the property value for a given element.
     * If `func` is an object the created callback will return `true` for elements
     * that contain the equivalent object properties, otherwise it will return `false`.
     *
     * @static
     * @memberOf _
     * @category Utilities
     * @param {*} [func=identity] The value to convert to a callback.
     * @param {*} [thisArg] The `this` binding of the created callback.
     * @param {number} [argCount] The number of arguments the callback accepts.
     * @returns {Function} Returns a callback function.
     * @example
     *
     * var characters = [
     *   { 'name': 'barney', 'age': 36 },
     *   { 'name': 'fred',   'age': 40 }
     * ];
     *
     * // wrap to create custom callback shorthands
     * _.createCallback = _.wrap(_.createCallback, function(func, callback, thisArg) {
     *   var match = /^(.+?)__([gl]t)(.+)$/.exec(callback);
     *   return !match ? func(callback, thisArg) : function(object) {
     *     return match[2] == 'gt' ? object[match[1]] > match[3] : object[match[1]] < match[3];
     *   };
     * });
     *
     * _.filter(characters, 'age__gt38');
     * // => [{ 'name': 'fred', 'age': 40 }]
     */
    function createCallback(func, thisArg, argCount) {
      var type = typeof func;
      if (func == null || type == 'function') {
        return baseCreateCallback(func, thisArg, argCount);
      }
      // handle "_.pluck" style callback shorthands
      if (type != 'object') {
        return property(func);
      }
      var props = keys(func),
          key = props[0],
          a = func[key];

      // handle "_.where" style callback shorthands
      if (props.length == 1 && a === a && !isObject(a)) {
        // fast path the common case of providing an object with a single
        // property containing a primitive value
        return function(object) {
          var b = object[key];
          return a === b && (a !== 0 || (1 / a == 1 / b));
        };
      }
      return function(object) {
        var length = props.length,
            result = false;

        while (length--) {
          if (!(result = baseIsEqual(object[props[length]], func[props[length]], null, true))) {
            break;
          }
        }
        return result;
      };
    }

    /**
     * Converts the characters `&`, `<`, `>`, `"`, and `'` in `string` to their
     * corresponding HTML entities.
     *
     * @static
     * @memberOf _
     * @category Utilities
     * @param {string} string The string to escape.
     * @returns {string} Returns the escaped string.
     * @example
     *
     * _.escape('Fred, Wilma, & Pebbles');
     * // => 'Fred, Wilma, &amp; Pebbles'
     */
    function escape(string) {
      return string == null ? '' : String(string).replace(reUnescapedHtml, escapeHtmlChar);
    }

    /**
     * This method returns the first argument provided to it.
     *
     * @static
     * @memberOf _
     * @category Utilities
     * @param {*} value Any value.
     * @returns {*} Returns `value`.
     * @example
     *
     * var object = { 'name': 'fred' };
     * _.identity(object) === object;
     * // => true
     */
    function identity(value) {
      return value;
    }

    /**
     * Adds function properties of a source object to the destination object.
     * If `object` is a function methods will be added to its prototype as well.
     *
     * @static
     * @memberOf _
     * @category Utilities
     * @param {Function|Object} [object=lodash] object The destination object.
     * @param {Object} source The object of functions to add.
     * @param {Object} [options] The options object.
     * @param {boolean} [options.chain=true] Specify whether the functions added are chainable.
     * @example
     *
     * function capitalize(string) {
     *   return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
     * }
     *
     * _.mixin({ 'capitalize': capitalize });
     * _.capitalize('fred');
     * // => 'Fred'
     *
     * _('fred').capitalize().value();
     * // => 'Fred'
     *
     * _.mixin({ 'capitalize': capitalize }, { 'chain': false });
     * _('fred').capitalize();
     * // => 'Fred'
     */
    function mixin(object, source, options) {
      var chain = true,
          methodNames = source && functions(source);

      if (!source || (!options && !methodNames.length)) {
        if (options == null) {
          options = source;
        }
        ctor = lodashWrapper;
        source = object;
        object = lodash;
        methodNames = functions(source);
      }
      if (options === false) {
        chain = false;
      } else if (isObject(options) && 'chain' in options) {
        chain = options.chain;
      }
      var ctor = object,
          isFunc = isFunction(ctor);

      forEach(methodNames, function(methodName) {
        var func = object[methodName] = source[methodName];
        if (isFunc) {
          ctor.prototype[methodName] = function() {
            var chainAll = this.__chain__,
                value = this.__wrapped__,
                args = [value];

            push.apply(args, arguments);
            var result = func.apply(object, args);
            if (chain || chainAll) {
              if (value === result && isObject(result)) {
                return this;
              }
              result = new ctor(result);
              result.__chain__ = chainAll;
            }
            return result;
          };
        }
      });
    }

    /**
     * Reverts the '_' variable to its previous value and returns a reference to
     * the `lodash` function.
     *
     * @static
     * @memberOf _
     * @category Utilities
     * @returns {Function} Returns the `lodash` function.
     * @example
     *
     * var lodash = _.noConflict();
     */
    function noConflict() {
      context._ = oldDash;
      return this;
    }

    /**
     * A no-operation function.
     *
     * @static
     * @memberOf _
     * @category Utilities
     * @example
     *
     * var object = { 'name': 'fred' };
     * _.noop(object) === undefined;
     * // => true
     */
    function noop() {
      // no operation performed
    }

    /**
     * Gets the number of milliseconds that have elapsed since the Unix epoch
     * (1 January 1970 00:00:00 UTC).
     *
     * @static
     * @memberOf _
     * @category Utilities
     * @example
     *
     * var stamp = _.now();
     * _.defer(function() { console.log(_.now() - stamp); });
     * // => logs the number of milliseconds it took for the deferred function to be called
     */
    var now = isNative(now = Date.now) && now || function() {
      return new Date().getTime();
    };

    /**
     * Converts the given value into an integer of the specified radix.
     * If `radix` is `undefined` or `0` a `radix` of `10` is used unless the
     * `value` is a hexadecimal, in which case a `radix` of `16` is used.
     *
     * Note: This method avoids differences in native ES3 and ES5 `parseInt`
     * implementations. See http://es5.github.io/#E.
     *
     * @static
     * @memberOf _
     * @category Utilities
     * @param {string} value The value to parse.
     * @param {number} [radix] The radix used to interpret the value to parse.
     * @returns {number} Returns the new integer value.
     * @example
     *
     * _.parseInt('08');
     * // => 8
     */
    var parseInt = nativeParseInt(whitespace + '08') == 8 ? nativeParseInt : function(value, radix) {
      // Firefox < 21 and Opera < 15 follow the ES3 specified implementation of `parseInt`
      return nativeParseInt(isString(value) ? value.replace(reLeadingSpacesAndZeros, '') : value, radix || 0);
    };

    /**
     * Creates a "_.pluck" style function, which returns the `key` value of a
     * given object.
     *
     * @static
     * @memberOf _
     * @category Utilities
     * @param {string} key The name of the property to retrieve.
     * @returns {Function} Returns the new function.
     * @example
     *
     * var characters = [
     *   { 'name': 'fred',   'age': 40 },
     *   { 'name': 'barney', 'age': 36 }
     * ];
     *
     * var getName = _.property('name');
     *
     * _.map(characters, getName);
     * // => ['barney', 'fred']
     *
     * _.sortBy(characters, getName);
     * // => [{ 'name': 'barney', 'age': 36 }, { 'name': 'fred',   'age': 40 }]
     */
    function property(key) {
      return function(object) {
        return object[key];
      };
    }

    /**
     * Produces a random number between `min` and `max` (inclusive). If only one
     * argument is provided a number between `0` and the given number will be
     * returned. If `floating` is truey or either `min` or `max` are floats a
     * floating-point number will be returned instead of an integer.
     *
     * @static
     * @memberOf _
     * @category Utilities
     * @param {number} [min=0] The minimum possible value.
     * @param {number} [max=1] The maximum possible value.
     * @param {boolean} [floating=false] Specify returning a floating-point number.
     * @returns {number} Returns a random number.
     * @example
     *
     * _.random(0, 5);
     * // => an integer between 0 and 5
     *
     * _.random(5);
     * // => also an integer between 0 and 5
     *
     * _.random(5, true);
     * // => a floating-point number between 0 and 5
     *
     * _.random(1.2, 5.2);
     * // => a floating-point number between 1.2 and 5.2
     */
    function random(min, max, floating) {
      var noMin = min == null,
          noMax = max == null;

      if (floating == null) {
        if (typeof min == 'boolean' && noMax) {
          floating = min;
          min = 1;
        }
        else if (!noMax && typeof max == 'boolean') {
          floating = max;
          noMax = true;
        }
      }
      if (noMin && noMax) {
        max = 1;
      }
      min = +min || 0;
      if (noMax) {
        max = min;
        min = 0;
      } else {
        max = +max || 0;
      }
      if (floating || min % 1 || max % 1) {
        var rand = nativeRandom();
        return nativeMin(min + (rand * (max - min + parseFloat('1e-' + ((rand +'').length - 1)))), max);
      }
      return baseRandom(min, max);
    }

    /**
     * Resolves the value of property `key` on `object`. If `key` is a function
     * it will be invoked with the `this` binding of `object` and its result returned,
     * else the property value is returned. If `object` is falsey then `undefined`
     * is returned.
     *
     * @static
     * @memberOf _
     * @category Utilities
     * @param {Object} object The object to inspect.
     * @param {string} key The name of the property to resolve.
     * @returns {*} Returns the resolved value.
     * @example
     *
     * var object = {
     *   'cheese': 'crumpets',
     *   'stuff': function() {
     *     return 'nonsense';
     *   }
     * };
     *
     * _.result(object, 'cheese');
     * // => 'crumpets'
     *
     * _.result(object, 'stuff');
     * // => 'nonsense'
     */
    function result(object, key) {
      if (object) {
        var value = object[key];
        return isFunction(value) ? object[key]() : value;
      }
    }

    /**
     * A micro-templating method that handles arbitrary delimiters, preserves
     * whitespace, and correctly escapes quotes within interpolated code.
     *
     * Note: In the development build, `_.template` utilizes sourceURLs for easier
     * debugging. See http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl
     *
     * For more information on precompiling templates see:
     * http://lodash.com/custom-builds
     *
     * For more information on Chrome extension sandboxes see:
     * http://developer.chrome.com/stable/extensions/sandboxingEval.html
     *
     * @static
     * @memberOf _
     * @category Utilities
     * @param {string} text The template text.
     * @param {Object} data The data object used to populate the text.
     * @param {Object} [options] The options object.
     * @param {RegExp} [options.escape] The "escape" delimiter.
     * @param {RegExp} [options.evaluate] The "evaluate" delimiter.
     * @param {Object} [options.imports] An object to import into the template as local variables.
     * @param {RegExp} [options.interpolate] The "interpolate" delimiter.
     * @param {string} [sourceURL] The sourceURL of the template's compiled source.
     * @param {string} [variable] The data object variable name.
     * @returns {Function|string} Returns a compiled function when no `data` object
     *  is given, else it returns the interpolated text.
     * @example
     *
     * // using the "interpolate" delimiter to create a compiled template
     * var compiled = _.template('hello <%= name %>');
     * compiled({ 'name': 'fred' });
     * // => 'hello fred'
     *
     * // using the "escape" delimiter to escape HTML in data property values
     * _.template('<b><%- value %></b>', { 'value': '<script>' });
     * // => '<b>&lt;script&gt;</b>'
     *
     * // using the "evaluate" delimiter to generate HTML
     * var list = '<% _.forEach(people, function(name) { %><li><%- name %></li><% }); %>';
     * _.template(list, { 'people': ['fred', 'barney'] });
     * // => '<li>fred</li><li>barney</li>'
     *
     * // using the ES6 delimiter as an alternative to the default "interpolate" delimiter
     * _.template('hello ${ name }', { 'name': 'pebbles' });
     * // => 'hello pebbles'
     *
     * // using the internal `print` function in "evaluate" delimiters
     * _.template('<% print("hello " + name); %>!', { 'name': 'barney' });
     * // => 'hello barney!'
     *
     * // using a custom template delimiters
     * _.templateSettings = {
     *   'interpolate': /{{([\s\S]+?)}}/g
     * };
     *
     * _.template('hello {{ name }}!', { 'name': 'mustache' });
     * // => 'hello mustache!'
     *
     * // using the `imports` option to import jQuery
     * var list = '<% jq.each(people, function(name) { %><li><%- name %></li><% }); %>';
     * _.template(list, { 'people': ['fred', 'barney'] }, { 'imports': { 'jq': jQuery } });
     * // => '<li>fred</li><li>barney</li>'
     *
     * // using the `sourceURL` option to specify a custom sourceURL for the template
     * var compiled = _.template('hello <%= name %>', null, { 'sourceURL': '/basic/greeting.jst' });
     * compiled(data);
     * // => find the source of "greeting.jst" under the Sources tab or Resources panel of the web inspector
     *
     * // using the `variable` option to ensure a with-statement isn't used in the compiled template
     * var compiled = _.template('hi <%= data.name %>!', null, { 'variable': 'data' });
     * compiled.source;
     * // => function(data) {
     *   var __t, __p = '', __e = _.escape;
     *   __p += 'hi ' + ((__t = ( data.name )) == null ? '' : __t) + '!';
     *   return __p;
     * }
     *
     * // using the `source` property to inline compiled templates for meaningful
     * // line numbers in error messages and a stack trace
     * fs.writeFileSync(path.join(cwd, 'jst.js'), '\
     *   var JST = {\
     *     "main": ' + _.template(mainText).source + '\
     *   };\
     * ');
     */
    function template(text, data, options) {
      // based on John Resig's `tmpl` implementation
      // http://ejohn.org/blog/javascript-micro-templating/
      // and Laura Doktorova's doT.js
      // https://github.com/olado/doT
      var settings = lodash.templateSettings;
      text = String(text || '');

      // avoid missing dependencies when `iteratorTemplate` is not defined
      options = defaults({}, options, settings);

      var imports = defaults({}, options.imports, settings.imports),
          importsKeys = keys(imports),
          importsValues = values(imports);

      var isEvaluating,
          index = 0,
          interpolate = options.interpolate || reNoMatch,
          source = "__p += '";

      // compile the regexp to match each delimiter
      var reDelimiters = RegExp(
        (options.escape || reNoMatch).source + '|' +
        interpolate.source + '|' +
        (interpolate === reInterpolate ? reEsTemplate : reNoMatch).source + '|' +
        (options.evaluate || reNoMatch).source + '|$'
      , 'g');

      text.replace(reDelimiters, function(match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) {
        interpolateValue || (interpolateValue = esTemplateValue);

        // escape characters that cannot be included in string literals
        source += text.slice(index, offset).replace(reUnescapedString, escapeStringChar);

        // replace delimiters with snippets
        if (escapeValue) {
          source += "' +\n__e(" + escapeValue + ") +\n'";
        }
        if (evaluateValue) {
          isEvaluating = true;
          source += "';\n" + evaluateValue + ";\n__p += '";
        }
        if (interpolateValue) {
          source += "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'";
        }
        index = offset + match.length;

        // the JS engine embedded in Adobe products requires returning the `match`
        // string in order to produce the correct `offset` value
        return match;
      });

      source += "';\n";

      // if `variable` is not specified, wrap a with-statement around the generated
      // code to add the data object to the top of the scope chain
      var variable = options.variable,
          hasVariable = variable;

      if (!hasVariable) {
        variable = 'obj';
        source = 'with (' + variable + ') {\n' + source + '\n}\n';
      }
      // cleanup code by stripping empty strings
      source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source)
        .replace(reEmptyStringMiddle, '$1')
        .replace(reEmptyStringTrailing, '$1;');

      // frame code as the function body
      source = 'function(' + variable + ') {\n' +
        (hasVariable ? '' : variable + ' || (' + variable + ' = {});\n') +
        "var __t, __p = '', __e = _.escape" +
        (isEvaluating
          ? ', __j = Array.prototype.join;\n' +
            "function print() { __p += __j.call(arguments, '') }\n"
          : ';\n'
        ) +
        source +
        'return __p\n}';

      // Use a sourceURL for easier debugging.
      // http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl
      var sourceURL = '\n/*\n//# sourceURL=' + (options.sourceURL || '/lodash/template/source[' + (templateCounter++) + ']') + '\n*/';

      try {
        var result = Function(importsKeys, 'return ' + source + sourceURL).apply(undefined, importsValues);
      } catch(e) {
        e.source = source;
        throw e;
      }
      if (data) {
        return result(data);
      }
      // provide the compiled function's source by its `toString` method, in
      // supported environments, or the `source` property as a convenience for
      // inlining compiled templates during the build process
      result.source = source;
      return result;
    }

    /**
     * Executes the callback `n` times, returning an array of the results
     * of each callback execution. The callback is bound to `thisArg` and invoked
     * with one argument; (index).
     *
     * @static
     * @memberOf _
     * @category Utilities
     * @param {number} n The number of times to execute the callback.
     * @param {Function} callback The function called per iteration.
     * @param {*} [thisArg] The `this` binding of `callback`.
     * @returns {Array} Returns an array of the results of each `callback` execution.
     * @example
     *
     * var diceRolls = _.times(3, _.partial(_.random, 1, 6));
     * // => [3, 6, 4]
     *
     * _.times(3, function(n) { mage.castSpell(n); });
     * // => calls `mage.castSpell(n)` three times, passing `n` of `0`, `1`, and `2` respectively
     *
     * _.times(3, function(n) { this.cast(n); }, mage);
     * // => also calls `mage.castSpell(n)` three times
     */
    function times(n, callback, thisArg) {
      n = (n = +n) > -1 ? n : 0;
      var index = -1,
          result = Array(n);

      callback = baseCreateCallback(callback, thisArg, 1);
      while (++index < n) {
        result[index] = callback(index);
      }
      return result;
    }

    /**
     * The inverse of `_.escape` this method converts the HTML entities
     * `&amp;`, `&lt;`, `&gt;`, `&quot;`, and `&#39;` in `string` to their
     * corresponding characters.
     *
     * @static
     * @memberOf _
     * @category Utilities
     * @param {string} string The string to unescape.
     * @returns {string} Returns the unescaped string.
     * @example
     *
     * _.unescape('Fred, Barney &amp; Pebbles');
     * // => 'Fred, Barney & Pebbles'
     */
    function unescape(string) {
      return string == null ? '' : String(string).replace(reEscapedHtml, unescapeHtmlChar);
    }

    /**
     * Generates a unique ID. If `prefix` is provided the ID will be appended to it.
     *
     * @static
     * @memberOf _
     * @category Utilities
     * @param {string} [prefix] The value to prefix the ID with.
     * @returns {string} Returns the unique ID.
     * @example
     *
     * _.uniqueId('contact_');
     * // => 'contact_104'
     *
     * _.uniqueId();
     * // => '105'
     */
    function uniqueId(prefix) {
      var id = ++idCounter;
      return String(prefix == null ? '' : prefix) + id;
    }

    /*--------------------------------------------------------------------------*/

    /**
     * Creates a `lodash` object that wraps the given value with explicit
     * method chaining enabled.
     *
     * @static
     * @memberOf _
     * @category Chaining
     * @param {*} value The value to wrap.
     * @returns {Object} Returns the wrapper object.
     * @example
     *
     * var characters = [
     *   { 'name': 'barney',  'age': 36 },
     *   { 'name': 'fred',    'age': 40 },
     *   { 'name': 'pebbles', 'age': 1 }
     * ];
     *
     * var youngest = _.chain(characters)
     *     .sortBy('age')
     *     .map(function(chr) { return chr.name + ' is ' + chr.age; })
     *     .first()
     *     .value();
     * // => 'pebbles is 1'
     */
    function chain(value) {
      value = new lodashWrapper(value);
      value.__chain__ = true;
      return value;
    }

    /**
     * Invokes `interceptor` with the `value` as the first argument and then
     * returns `value`. The purpose of this method is to "tap into" a method
     * chain in order to perform operations on intermediate results within
     * the chain.
     *
     * @static
     * @memberOf _
     * @category Chaining
     * @param {*} value The value to provide to `interceptor`.
     * @param {Function} interceptor The function to invoke.
     * @returns {*} Returns `value`.
     * @example
     *
     * _([1, 2, 3, 4])
     *  .tap(function(array) { array.pop(); })
     *  .reverse()
     *  .value();
     * // => [3, 2, 1]
     */
    function tap(value, interceptor) {
      interceptor(value);
      return value;
    }

    /**
     * Enables explicit method chaining on the wrapper object.
     *
     * @name chain
     * @memberOf _
     * @category Chaining
     * @returns {*} Returns the wrapper object.
     * @example
     *
     * var characters = [
     *   { 'name': 'barney', 'age': 36 },
     *   { 'name': 'fred',   'age': 40 }
     * ];
     *
     * // without explicit chaining
     * _(characters).first();
     * // => { 'name': 'barney', 'age': 36 }
     *
     * // with explicit chaining
     * _(characters).chain()
     *   .first()
     *   .pick('age')
     *   .value();
     * // => { 'age': 36 }
     */
    function wrapperChain() {
      this.__chain__ = true;
      return this;
    }

    /**
     * Produces the `toString` result of the wrapped value.
     *
     * @name toString
     * @memberOf _
     * @category Chaining
     * @returns {string} Returns the string result.
     * @example
     *
     * _([1, 2, 3]).toString();
     * // => '1,2,3'
     */
    function wrapperToString() {
      return String(this.__wrapped__);
    }

    /**
     * Extracts the wrapped value.
     *
     * @name valueOf
     * @memberOf _
     * @alias value
     * @category Chaining
     * @returns {*} Returns the wrapped value.
     * @example
     *
     * _([1, 2, 3]).valueOf();
     * // => [1, 2, 3]
     */
    function wrapperValueOf() {
      return this.__wrapped__;
    }

    /*--------------------------------------------------------------------------*/

    // add functions that return wrapped values when chaining
    lodash.after = after;
    lodash.assign = assign;
    lodash.at = at;
    lodash.bind = bind;
    lodash.bindAll = bindAll;
    lodash.bindKey = bindKey;
    lodash.chain = chain;
    lodash.compact = compact;
    lodash.compose = compose;
    lodash.constant = constant;
    lodash.countBy = countBy;
    lodash.create = create;
    lodash.createCallback = createCallback;
    lodash.curry = curry;
    lodash.debounce = debounce;
    lodash.defaults = defaults;
    lodash.defer = defer;
    lodash.delay = delay;
    lodash.difference = difference;
    lodash.filter = filter;
    lodash.flatten = flatten;
    lodash.forEach = forEach;
    lodash.forEachRight = forEachRight;
    lodash.forIn = forIn;
    lodash.forInRight = forInRight;
    lodash.forOwn = forOwn;
    lodash.forOwnRight = forOwnRight;
    lodash.functions = functions;
    lodash.groupBy = groupBy;
    lodash.indexBy = indexBy;
    lodash.initial = initial;
    lodash.intersection = intersection;
    lodash.invert = invert;
    lodash.invoke = invoke;
    lodash.keys = keys;
    lodash.map = map;
    lodash.mapValues = mapValues;
    lodash.max = max;
    lodash.memoize = memoize;
    lodash.merge = merge;
    lodash.min = min;
    lodash.omit = omit;
    lodash.once = once;
    lodash.pairs = pairs;
    lodash.partial = partial;
    lodash.partialRight = partialRight;
    lodash.pick = pick;
    lodash.pluck = pluck;
    lodash.property = property;
    lodash.pull = pull;
    lodash.range = range;
    lodash.reject = reject;
    lodash.remove = remove;
    lodash.rest = rest;
    lodash.shuffle = shuffle;
    lodash.sortBy = sortBy;
    lodash.tap = tap;
    lodash.throttle = throttle;
    lodash.times = times;
    lodash.toArray = toArray;
    lodash.transform = transform;
    lodash.union = union;
    lodash.uniq = uniq;
    lodash.values = values;
    lodash.where = where;
    lodash.without = without;
    lodash.wrap = wrap;
    lodash.xor = xor;
    lodash.zip = zip;
    lodash.zipObject = zipObject;

    // add aliases
    lodash.collect = map;
    lodash.drop = rest;
    lodash.each = forEach;
    lodash.eachRight = forEachRight;
    lodash.extend = assign;
    lodash.methods = functions;
    lodash.object = zipObject;
    lodash.select = filter;
    lodash.tail = rest;
    lodash.unique = uniq;
    lodash.unzip = zip;

    // add functions to `lodash.prototype`
    mixin(lodash);

    /*--------------------------------------------------------------------------*/

    // add functions that return unwrapped values when chaining
    lodash.clone = clone;
    lodash.cloneDeep = cloneDeep;
    lodash.contains = contains;
    lodash.escape = escape;
    lodash.every = every;
    lodash.find = find;
    lodash.findIndex = findIndex;
    lodash.findKey = findKey;
    lodash.findLast = findLast;
    lodash.findLastIndex = findLastIndex;
    lodash.findLastKey = findLastKey;
    lodash.has = has;
    lodash.identity = identity;
    lodash.indexOf = indexOf;
    lodash.isArguments = isArguments;
    lodash.isArray = isArray;
    lodash.isBoolean = isBoolean;
    lodash.isDate = isDate;
    lodash.isElement = isElement;
    lodash.isEmpty = isEmpty;
    lodash.isEqual = isEqual;
    lodash.isFinite = isFinite;
    lodash.isFunction = isFunction;
    lodash.isNaN = isNaN;
    lodash.isNull = isNull;
    lodash.isNumber = isNumber;
    lodash.isObject = isObject;
    lodash.isPlainObject = isPlainObject;
    lodash.isRegExp = isRegExp;
    lodash.isString = isString;
    lodash.isUndefined = isUndefined;
    lodash.lastIndexOf = lastIndexOf;
    lodash.mixin = mixin;
    lodash.noConflict = noConflict;
    lodash.noop = noop;
    lodash.now = now;
    lodash.parseInt = parseInt;
    lodash.random = random;
    lodash.reduce = reduce;
    lodash.reduceRight = reduceRight;
    lodash.result = result;
    lodash.runInContext = runInContext;
    lodash.size = size;
    lodash.some = some;
    lodash.sortedIndex = sortedIndex;
    lodash.template = template;
    lodash.unescape = unescape;
    lodash.uniqueId = uniqueId;

    // add aliases
    lodash.all = every;
    lodash.any = some;
    lodash.detect = find;
    lodash.findWhere = find;
    lodash.foldl = reduce;
    lodash.foldr = reduceRight;
    lodash.include = contains;
    lodash.inject = reduce;

    mixin(function() {
      var source = {}
      forOwn(lodash, function(func, methodName) {
        if (!lodash.prototype[methodName]) {
          source[methodName] = func;
        }
      });
      return source;
    }(), false);

    /*--------------------------------------------------------------------------*/

    // add functions capable of returning wrapped and unwrapped values when chaining
    lodash.first = first;
    lodash.last = last;
    lodash.sample = sample;

    // add aliases
    lodash.take = first;
    lodash.head = first;

    forOwn(lodash, function(func, methodName) {
      var callbackable = methodName !== 'sample';
      if (!lodash.prototype[methodName]) {
        lodash.prototype[methodName]= function(n, guard) {
          var chainAll = this.__chain__,
              result = func(this.__wrapped__, n, guard);

          return !chainAll && (n == null || (guard && !(callbackable && typeof n == 'function')))
            ? result
            : new lodashWrapper(result, chainAll);
        };
      }
    });

    /*--------------------------------------------------------------------------*/

    /**
     * The semantic version number.
     *
     * @static
     * @memberOf _
     * @type string
     */
    lodash.VERSION = '2.4.1';

    // add "Chaining" functions to the wrapper
    lodash.prototype.chain = wrapperChain;
    lodash.prototype.toString = wrapperToString;
    lodash.prototype.value = wrapperValueOf;
    lodash.prototype.valueOf = wrapperValueOf;

    // add `Array` functions that return unwrapped values
    forEach(['join', 'pop', 'shift'], function(methodName) {
      var func = arrayRef[methodName];
      lodash.prototype[methodName] = function() {
        var chainAll = this.__chain__,
            result = func.apply(this.__wrapped__, arguments);

        return chainAll
          ? new lodashWrapper(result, chainAll)
          : result;
      };
    });

    // add `Array` functions that return the existing wrapped value
    forEach(['push', 'reverse', 'sort', 'unshift'], function(methodName) {
      var func = arrayRef[methodName];
      lodash.prototype[methodName] = function() {
        func.apply(this.__wrapped__, arguments);
        return this;
      };
    });

    // add `Array` functions that return new wrapped values
    forEach(['concat', 'slice', 'splice'], function(methodName) {
      var func = arrayRef[methodName];
      lodash.prototype[methodName] = function() {
        return new lodashWrapper(func.apply(this.__wrapped__, arguments), this.__chain__);
      };
    });

    return lodash;
  }

  /*--------------------------------------------------------------------------*/

  // expose Lo-Dash
  var _ = runInContext();

  // some AMD build optimizers like r.js check for condition patterns like the following:
  if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
    // Expose Lo-Dash to the global object even when an AMD loader is present in
    // case Lo-Dash is loaded with a RequireJS shim config.
    // See http://requirejs.org/docs/api.html#config-shim
    root._ = _;

    // define as an anonymous module so, through path mapping, it can be
    // referenced as the "underscore" module
    define(function() {
      return _;
    });
  }
  // check for `exports` after `define` in case a build optimizer adds an `exports` object
  else if (freeExports && freeModule) {
    // in Node.js or RingoJS
    if (moduleExports) {
      (freeModule.exports = _)._ = _;
    }
    // in Narwhal or Rhino -require
    else {
      freeExports._ = _;
    }
  }
  else {
    // in a browser or Rhino
    root._ = _;
  }
}.call(this));
;
// Ion.RangeSlider
// version 1.9.3 Build: 176
// © 2013-2014 Denis Ineshin | IonDen.com
//
// Project page:    http://ionden.com/a/plugins/ion.rangeSlider/
// GitHub page:     https://github.com/IonDen/ion.rangeSlider
//
// Released under MIT licence:
// http://ionden.com/a/plugins/licence-en.html
// =====================================================================================================================

(function ($, document, window, navigator) {
    "use strict";

    var plugin_count = 0,
        current;

    var is_old_ie = (function () {
        var n = navigator.userAgent,
            r = /msie\s\d+/i,
            v;
        if (n.search(r) > 0) {
            v = r.exec(n).toString();
            v = v.split(" ")[1];
            if (v < 9) {
                return true;
            }
        }
        return false;
    }());
    var is_touch = (('ontouchstart' in window) || (navigator.msMaxTouchPoints > 0));

    var testNumber = function (num) {
        if (typeof num === "number") {
            if (isNaN(num)) {
                return null;
            } else {
                return num;
            }
        } else {
            num = parseFloat(num);
            if (isNaN(num)) {
                return null;
            } else {
                return num;
            }
        }
    };

    var methods = {
        init: function (options) {

            // irs = ion range slider css prefix
            var baseHTML =
                '<span class="irs">' +
                '<span class="irs-line"><span class="irs-line-left"></span><span class="irs-line-mid"></span><span class="irs-line-right"></span></span>' +
                '<span class="irs-min">0</span><span class="irs-max">1</span>' +
                '<span class="irs-from">0</span><span class="irs-to">0</span><span class="irs-single">0</span>' +
                '</span>' +
                '<span class="irs-grid"></span>';

            var singleHTML =
                '<span class="irs-slider single"></span>';

            var doubleHTML =
                '<span class="irs-diapason"></span>' +
                '<span class="irs-slider from"></span>' +
                '<span class="irs-slider to"></span>';

            var disableHTML =
                '<span class="irs-disable-mask"></span>';



            return this.each(function () {
                var settings = $.extend({
                    min: null,
                    max: null,
                    from: null,
                    to: null,
                    type: "single",
                    step: null,
                    prefix: "",
                    postfix: "",
                    maxPostfix: "",
                    hasGrid: false,
                    gridMargin: 0,
                    hideMinMax: false,
                    hideFromTo: false,
                    prettify: true,
                    disable: false,
                    values: null,
                    onLoad: null,
                    onChange: null,
                    onFinish: null
                }, options);



                var slider = $(this),
                    self = this,
                    allow_values = false,
                    value_array = null;

                if (slider.data("isActive")) {
                    return;
                }
                slider.data("isActive", true);

                plugin_count += 1;
                this.plugin_count = plugin_count;



                // check default values
                if (slider.prop("value")) {
                    value_array = slider.prop("value").split(";");
                }

                if (settings.type === "single") {

                    if (value_array && value_array.length > 1) {

                        if (typeof settings.min !== "number") {
                            settings.min = parseFloat(value_array[0]);
                        } else {
                            if (typeof settings.from !== "number") {
                                settings.from = parseFloat(value_array[0]);
                            }
                        }

                        if (typeof settings.max !== "number") {
                            settings.max = parseFloat(value_array[1]);
                        }

                    } else if (value_array && value_array.length === 1) {

                        if (typeof settings.from !== "number") {
                            settings.from = parseFloat(value_array[0]);
                        }

                    }

                } else if (settings.type === "double") {

                    if (value_array && value_array.length > 1) {

                        if (typeof settings.min !== "number") {
                            settings.min = parseFloat(value_array[0]);
                        } else {
                            if (typeof settings.from !== "number") {
                                settings.from = parseFloat(value_array[0]);
                            }
                        }

                        if (typeof settings.max !== "number") {
                            settings.max = parseFloat(value_array[1]);
                        } else {
                            if (typeof settings.to !== "number") {
                                settings.to = parseFloat(value_array[1]);
                            }
                        }

                    } else if (value_array && value_array.length === 1) {

                        if (typeof settings.min !== "number") {
                            settings.min = parseFloat(value_array[0]);
                        } else {
                            if (typeof settings.from !== "number") {
                                settings.from = parseFloat(value_array[0]);
                            }
                        }

                    }

                }



                // extend from data-*
                if (typeof slider.data("min") === "number") {
                    settings.min = parseFloat(slider.data("min"));
                }
                if (typeof slider.data("max") === "number") {
                    settings.max = parseFloat(slider.data("max"));
                }
                if (typeof slider.data("from") === "number") {
                    settings.from = parseFloat(slider.data("from"));
                }
                if (typeof slider.data("to") === "number") {
                    settings.to = parseFloat(slider.data("to"));
                }
                if (slider.data("step")) {
                    settings.step = parseFloat(slider.data("step"));
                }
                if (slider.data("type")) {
                    settings.type = slider.data("type");
                }
                if (slider.data("prefix")) {
                    settings.prefix = slider.data("prefix");
                }
                if (slider.data("postfix")) {
                    settings.postfix = slider.data("postfix");
                }
                if (slider.data("maxpostfix")) {
                    settings.maxPostfix = slider.data("maxpostfix");
                }
                if (slider.data("hasgrid")) {
                    settings.hasGrid = slider.data("hasgrid");
                }
                if (slider.data("gridmargin")) {
                    settings.gridMargin = +slider.data("gridmargin");
                }
                if (slider.data("hideminmax")) {
                    settings.hideMinMax = slider.data("hideminmax");
                }
                if (slider.data("hidefromto")) {
                    settings.hideFromTo = slider.data("hidefromto");
                }
                if (slider.data("prettify")) {
                    settings.prettify = slider.data("prettify");
                }
                if (slider.data("disable")) {
                    settings.disable = slider.data("disable");
                }
                if (slider.data("values")) {
                    settings.values = slider.data("values").split(",");
                }



                // Set Min and Max if no
                settings.min = testNumber(settings.min);
                if (!settings.min && settings.min !== 0) {
                    settings.min = 10;
                }

                settings.max = testNumber(settings.max);
                if (!settings.max && settings.max !== 0) {
                    settings.max = 100;
                }



                // Set values
                if (Object.prototype.toString.call(settings.values) !== "[object Array]") {
                    settings.values = null;
                }
                if (settings.values && settings.values.length > 0) {
                    settings.min = 0;
                    settings.max = settings.values.length - 1;
                    settings.step = 1;
                    allow_values = true;
                }



                // Set From and To if no
                settings.from = testNumber(settings.from);
                if (!settings.from && settings.from !== 0) {
                    settings.from = settings.min;
                }

                settings.to = testNumber(settings.to);
                if (!settings.to && settings.to !== 0) {
                    settings.to = settings.max;
                }


                // Set step
                settings.step = testNumber(settings.step);
                if (!settings.step) {
                    settings.step = 1;
                }



                // fix diapason
                if (settings.from < settings.min) {
                    settings.from = settings.min;
                }
                if (settings.from > settings.max) {
                    settings.from = settings.min;
                }

                if (settings.to < settings.min) {
                    settings.to = settings.max;
                }
                if (settings.to > settings.max) {
                    settings.to = settings.max;
                }

                if (settings.type === "double") {
                    if (settings.from > settings.to) {
                        settings.from = settings.to;
                    }
                    if (settings.to < settings.from) {
                        settings.to = settings.from;
                    }
                }


                var prettify = function (num) {
                    var n = num.toString();
                    if (settings.prettify) {
                        n = n.replace(/(\d{1,3}(?=(?:\d\d\d)+(?!\d)))/g, "$1 ");
                    }
                    return n;
                };


                var containerHTML = '<span class="irs" id="irs-' + this.plugin_count + '"></span>';
                slider[0].style.display = "none";
                slider.before(containerHTML);

                var $container = slider.prev(),
                    $body = $(document.body),
                    $window = $(window),
                    $rangeSlider,
                    $fieldMin,
                    $fieldMax,
                    $fieldFrom,
                    $fieldTo,
                    $fieldSingle,
                    $singleSlider,
                    $fromSlider,
                    $toSlider,
                    $activeSlider,
                    $diapason,
                    $grid;

                var allowDrag = false,
                    is_slider_active = false,
                    is_first_start = true,
                    numbers = {};

                var mouseX = 0,
                    fieldMinWidth = 0,
                    fieldMaxWidth = 0,
                    normalWidth = 0,
                    fullWidth = 0,
                    sliderWidth = 0,
                    width = 0,
                    left = 0,
                    right = 0,
                    minusX = 0,
                    stepFloat = 0;


                if (parseInt(settings.step, 10) !== parseFloat(settings.step)) {
                    stepFloat = settings.step.toString().split(".")[1];
                    stepFloat = Math.pow(10, stepFloat.length);
                }



                // public methods
                this.updateData = function (options) {
                    $.extend(settings, options);
                    removeHTML();
                };
                this.removeSlider = function () {
                    $container.find("*").off();
                    $window.off("mouseup.irs" + self.plugin_count);
                    $body.off("mouseup.irs" + self.plugin_count);
                    $body.off("mouseleave.irs" + self.plugin_count);
                    $body.off("mousemove.irs" + self.plugin_count);
                    $container.html("").remove();
                    slider.data("isActive", false);
                    slider.show();
                };





                // private methods
                var removeHTML = function () {
                    $container.find("*").off();
                    $window.off("mouseup.irs" + self.plugin_count);
                    $body.off("mouseup.irs" + self.plugin_count);
                    $body.off("mouseleave.irs" + self.plugin_count);
                    $body.off("mousemove.irs" + self.plugin_count);
                    $container.html("");

                    placeHTML();
                };
                var placeHTML = function () {
                    $container.html(baseHTML);
                    $rangeSlider = $container.find(".irs");

                    $fieldMin = $rangeSlider.find(".irs-min");
                    $fieldMax = $rangeSlider.find(".irs-max");
                    $fieldFrom = $rangeSlider.find(".irs-from");
                    $fieldTo = $rangeSlider.find(".irs-to");
                    $fieldSingle = $rangeSlider.find(".irs-single");
                    $grid = $container.find(".irs-grid");

                    if (settings.hideFromTo) {
                        $fieldFrom[0].style.visibility = "hidden";
                        $fieldTo[0].style.visibility = "hidden";
                        $fieldSingle[0].style.visibility = "hidden";
                    }
                    if (!settings.hideFromTo) {
                        $fieldFrom[0].style.visibility = "visible";
                        $fieldTo[0].style.visibility = "visible";
                        $fieldSingle[0].style.visibility = "visible";
                    }

                    if (settings.hideMinMax) {
                        $fieldMin[0].style.visibility = "hidden";
                        $fieldMax[0].style.visibility = "hidden";

                        fieldMinWidth = 0;
                        fieldMaxWidth = 0;
                    }
                    if (!settings.hideMinMax) {
                        $fieldMin[0].style.visibility = "visible";
                        $fieldMax[0].style.visibility = "visible";

                        if (settings.values) {
                            $fieldMin.html(settings.prefix + settings.values[0] + settings.postfix);
                            $fieldMax.html(settings.prefix + settings.values[settings.values.length - 1] + settings.maxPostfix + settings.postfix);
                        } else {
                            $fieldMin.html(settings.prefix + prettify(settings.min) + settings.postfix);
                            $fieldMax.html(settings.prefix + prettify(settings.max) + settings.maxPostfix + settings.postfix);
                        }

                        fieldMinWidth = $fieldMin.outerWidth(false);
                        fieldMaxWidth = $fieldMax.outerWidth(false);
                    }

                    bindEvents();
                };

                var bindEvents = function () {
                    if (settings.type === "single") {
                        $rangeSlider.append(singleHTML);

                        $singleSlider = $rangeSlider.find(".single");

                        $singleSlider.on("mousedown", function (e) {
                            e.preventDefault();
                            e.stopPropagation();

                            calcDimensions(e, $(this), null);

                            allowDrag = true;
                            is_slider_active = true;
                            current = self.plugin_count;

                            if (is_old_ie) {
                                $("*").prop("unselectable", true);
                            }
                        });
                        if (is_touch) {
                            $singleSlider.on("touchstart", function (e) {
                                e.preventDefault();
                                e.stopPropagation();

                                calcDimensions(e.originalEvent.touches[0], $(this), null);

                                allowDrag = true;
                                is_slider_active = true;
                                current = self.plugin_count;
                            });
                        }

                    } else if (settings.type === "double") {
                        $rangeSlider.append(doubleHTML);

                        $fromSlider = $rangeSlider.find(".from");
                        $toSlider = $rangeSlider.find(".to");
                        $diapason = $rangeSlider.find(".irs-diapason");

                        setDiapason();

                        $fromSlider.on("mousedown", function (e) {
                            e.preventDefault();
                            e.stopPropagation();

                            $(this).addClass("last");
                            $toSlider.removeClass("last");
                            calcDimensions(e, $(this), "from");

                            allowDrag = true;
                            is_slider_active = true;
                            current = self.plugin_count;

                            if (is_old_ie) {
                                $("*").prop("unselectable", true);
                            }
                        });
                        $toSlider.on("mousedown", function (e) {
                            e.preventDefault();
                            e.stopPropagation();

                            $(this).addClass("last");
                            $fromSlider.removeClass("last");
                            calcDimensions(e, $(this), "to");

                            allowDrag = true;
                            is_slider_active = true;
                            current = self.plugin_count;

                            if (is_old_ie) {
                                $("*").prop("unselectable", true);
                            }
                        });

                        if (is_touch) {
                            $fromSlider.on("touchstart", function (e) {
                                e.preventDefault();
                                e.stopPropagation();

                                $(this).addClass("last");
                                $toSlider.removeClass("last");
                                calcDimensions(e.originalEvent.touches[0], $(this), "from");

                                allowDrag = true;
                                is_slider_active = true;
                                current = self.plugin_count;
                            });
                            $toSlider.on("touchstart", function (e) {
                                e.preventDefault();
                                e.stopPropagation();

                                $(this).addClass("last");
                                $fromSlider.removeClass("last");
                                calcDimensions(e.originalEvent.touches[0], $(this), "to");

                                allowDrag = true;
                                is_slider_active = true;
                                current = self.plugin_count;
                            });
                        }

                        if (settings.to === settings.max) {
                            $fromSlider.addClass("last");
                        }
                    }

                    var mouseup = function () {
                        if (current !== self.plugin_count) {
                            return;
                        }

                        if (allowDrag) {
                            is_slider_active = false;
                            allowDrag = false;
                            $activeSlider.removeAttr("id");
                            $activeSlider = null;
                            if (settings.type === "double") {
                                setDiapason();
                            }
                            getNumbers();

                            if (is_old_ie) {
                                $("*").prop("unselectable", false);
                            }
                        }
                    };

                    $window.on("mouseup.irs" + self.plugin_count, function () {
                        mouseup();
                    });

                    if (is_old_ie) {
                        $body.on("mouseleave.irs" + self.plugin_count, function () {
                            mouseup();
                        });
                    }


                    $body.on("mousemove.irs" + self.plugin_count, function (e) {
                        if (allowDrag) {
                            mouseX = e.pageX;
                            dragSlider();
                        }
                    });

                    $container.on("mousedown", function () {
                        current = self.plugin_count;
                    });

                    $container.on("mouseup", function (e) {
                        if (current !== self.plugin_count) {
                            return;
                        }

                        if (allowDrag || settings.disable) {
                            return;
                        }

                        moveByClick(e.pageX);
                    });

                    if (is_touch) {
                        $window.on("touchend", function () {
                            if (allowDrag) {
                                is_slider_active = false;
                                allowDrag = false;
                                $activeSlider.removeAttr("id");
                                $activeSlider = null;
                                if (settings.type === "double") {
                                    setDiapason();
                                }
                                getNumbers();
                            }
                        });
                        $window.on("touchmove", function (e) {
                            if (allowDrag) {
                                mouseX = e.originalEvent.touches[0].pageX;
                                dragSlider();
                            }
                        });
                    }

                    getSize();
                    setNumbers();
                    if (settings.hasGrid) {
                        setGrid();
                    }
                    if (settings.disable) {
                        setMask();
                    } else {
                        removeMask();
                    }
                };

                var getSize = function () {
                    normalWidth = $rangeSlider.width();
                    if ($singleSlider) {
                        sliderWidth = $singleSlider.width();
                    } else {
                        sliderWidth = $fromSlider.width();
                    }
                    fullWidth = normalWidth - sliderWidth;
                };

                var calcDimensions = function (e, currentSlider, whichSlider) {
                    getSize();

                    is_first_start = false;
                    $activeSlider = currentSlider;
                    $activeSlider.prop("id", "irs-active-slider");

                    var _x1 = $activeSlider.offset().left,
                        _x2 = e.pageX - _x1;
                    minusX = _x1 + _x2 - $activeSlider.position().left;

                    if (settings.type === "single") {

                        width = $rangeSlider.width() - sliderWidth;

                    } else if (settings.type === "double") {

                        if (whichSlider === "from") {
                            left = 0;
                            right = parseInt($toSlider.css("left"), 10);
                        } else {
                            left = parseInt($fromSlider.css("left"), 10);
                            right = $rangeSlider.width() - sliderWidth;
                        }

                    }
                };

                var setDiapason = function () {
                    var _w = $fromSlider.width(),
                        _x = $.data($fromSlider[0], "x") || parseInt($fromSlider[0].style.left, 10) || $fromSlider.position().left,
                        _width = $.data($toSlider[0], "x") || parseInt($toSlider[0].style.left, 10) || $toSlider.position().left,
                        x = _x + (_w / 2),
                        w = _width - _x;
                    $diapason[0].style.left = x + "px";
                    $diapason[0].style.width = w + "px";
                };

                var dragSlider = function (manual_x) {
                    var x_pure = mouseX - minusX,
                        x;

                    if (manual_x) {
                        x_pure = manual_x;
                    } else {
                        x_pure = mouseX - minusX;
                    }

                    if (settings.type === "single") {

                        if (x_pure < 0) {
                            x_pure = 0;
                        }
                        if (x_pure > width) {
                            x_pure = width;
                        }

                    } else if (settings.type === "double") {

                        if (x_pure < left) {
                            x_pure = left;
                        }
                        if (x_pure > right) {
                            x_pure = right;
                        }
                        setDiapason();

                    }

                    $.data($activeSlider[0], "x", x_pure);
                    getNumbers();

                    x = Math.round(x_pure);
                    $activeSlider[0].style.left = x + "px";
                };

                var getNumbers = function () {
                    var nums = {
                        input: slider,
                        slider: $container,
                        min: settings.min,
                        max: settings.max,
                        fromNumber: 0,
                        toNumber: 0,
                        fromPers: 0,
                        toPers: 0,
                        fromX: 0,
                        fromX_pure: 0,
                        toX: 0,
                        toX_pure: 0
                    };
                    var diapason = settings.max - settings.min, _from, _to;

                    if (settings.type === "single") {

                        nums.fromX = $.data($singleSlider[0], "x") || parseInt($singleSlider[0].style.left, 10) || $singleSlider.position().left;
                        nums.fromPers = nums.fromX / fullWidth * 100;
                        _from = (diapason / 100 * nums.fromPers) + settings.min;
                        nums.fromNumber = Math.round(_from / settings.step) * settings.step;
                        if (nums.fromNumber < settings.min) {
                            nums.fromNumber = settings.min;
                        }
                        if (nums.fromNumber > settings.max) {
                            nums.fromNumber = settings.max;
                        }

                        if (stepFloat) {
                            nums.fromNumber = parseInt(nums.fromNumber * stepFloat, 10) / stepFloat;
                        }

                        if (allow_values) {
                            nums.fromValue = settings.values[nums.fromNumber];
                        }

                    } else if (settings.type === "double") {

                        nums.fromX = $.data($fromSlider[0], "x") || parseInt($fromSlider[0].style.left, 10) || $fromSlider.position().left;
                        nums.fromPers = nums.fromX / fullWidth * 100;
                        _from = (diapason / 100 * nums.fromPers) + settings.min;
                        nums.fromNumber = Math.round(_from / settings.step) * settings.step;
                        if (nums.fromNumber < settings.min) {
                            nums.fromNumber = settings.min;
                        }

                        nums.toX = $.data($toSlider[0], "x") || parseInt($toSlider[0].style.left, 10) || $toSlider.position().left;
                        nums.toPers = nums.toX / fullWidth * 100;
                        _to = (diapason / 100 * nums.toPers) + settings.min;
                        nums.toNumber = Math.round(_to / settings.step) * settings.step;
                        if (nums.toNumber > settings.max) {
                            nums.toNumber = settings.max;
                        }

                        if (stepFloat) {
                            nums.fromNumber = parseInt(nums.fromNumber * stepFloat, 10) / stepFloat;
                            nums.toNumber = parseInt(nums.toNumber * stepFloat, 10) / stepFloat;
                        }

                        if (allow_values) {
                            nums.fromValue = settings.values[nums.fromNumber];
                            nums.toValue = settings.values[nums.toNumber];
                        }

                    }

                    numbers = nums;
                    setFields();
                };

                var setNumbers = function () {
                    var nums = {
                        input: slider,
                        slider: $container,
                        min: settings.min,
                        max: settings.max,
                        fromNumber: settings.from,
                        toNumber: settings.to,
                        fromPers: 0,
                        toPers: 0,
                        fromX: 0,
                        fromX_pure: 0,
                        toX: 0,
                        toX_pure: 0
                    };
                    var diapason = settings.max - settings.min;

                    if (settings.type === "single") {

                        nums.fromPers = (diapason !== 0) ? (nums.fromNumber - settings.min) / diapason * 100 : 0;
                        nums.fromX_pure = fullWidth / 100 * nums.fromPers;
                        nums.fromX = Math.round(nums.fromX_pure);
                        $singleSlider[0].style.left = nums.fromX + "px";
                        $.data($singleSlider[0], "x", nums.fromX_pure);

                    } else if (settings.type === "double") {

                        nums.fromPers = (diapason !== 0) ? (nums.fromNumber - settings.min) / diapason * 100 : 0;
                        nums.fromX_pure = fullWidth / 100 * nums.fromPers;
                        nums.fromX = Math.round(nums.fromX_pure);
                        $fromSlider[0].style.left = nums.fromX + "px";
                        $.data($fromSlider[0], "x", nums.fromX_pure);

                        nums.toPers = (diapason !== 0) ? (nums.toNumber - settings.min) / diapason * 100 : 1;
                        nums.toX_pure = fullWidth / 100 * nums.toPers;
                        nums.toX = Math.round(nums.toX_pure);
                        $toSlider[0].style.left = nums.toX + "px";
                        $.data($toSlider[0], "x", nums.toX_pure);

                        setDiapason();

                    }

                    numbers = nums;
                    setFields();
                };

                var moveByClick = function (page_x) {
                    is_first_start = false;

                    var x = page_x - $container.offset().left,
                        d = numbers.toX - numbers.fromX,
                        zero_point = numbers.fromX + (d / 2);

                    left = 0;
                    width = $rangeSlider.width() - sliderWidth;
                    right = $rangeSlider.width() - sliderWidth;

                    if (settings.type === "single") {
                        $activeSlider = $singleSlider;
                        $activeSlider.prop("id", "irs-active-slider");
                        dragSlider(x);
                    } else if (settings.type === "double") {
                        if (x <= zero_point) {
                            $activeSlider = $fromSlider;
                        } else {
                            $activeSlider = $toSlider;
                        }
                        $activeSlider.prop("id", "irs-active-slider");
                        dragSlider(x);
                        setDiapason();
                    }

                    $activeSlider.removeAttr("id");
                    $activeSlider = null;
                };

                var setFields = function () {
                    var _from, _fromW, _fromX,
                        _to, _toW, _toX,
                        _single, _singleW, _singleX,
                        _slW = (sliderWidth / 2),
                        maxPostfix = "";

                    if (settings.type === "single") {

                        if (numbers.fromNumber === settings.max) {
                            maxPostfix = settings.maxPostfix;
                        } else {
                            maxPostfix = "";
                        }

                        $fieldFrom[0].style.display = "none";
                        $fieldTo[0].style.display = "none";

                        if (allow_values) {
                            _single =
                                settings.prefix +
                                settings.values[numbers.fromNumber] +
                                maxPostfix +
                                settings.postfix;
                        } else {
                            _single =
                                settings.prefix +
                                prettify(numbers.fromNumber) +
                                maxPostfix +
                                settings.postfix;
                        }

                        $fieldSingle.html(_single);

                        _singleW = $fieldSingle.outerWidth(false);
                        _singleX = numbers.fromX - (_singleW / 2) + _slW;
                        if (_singleX < 0) {
                            _singleX = 0;
                        }
                        if (_singleX > normalWidth - _singleW) {
                            _singleX = normalWidth - _singleW;
                        }
                        $fieldSingle[0].style.left = _singleX + "px";

                        if (!settings.hideMinMax && !settings.hideFromTo) {
                            if (_singleX < fieldMinWidth) {
                                $fieldMin[0].style.display = "none";
                            } else {
                                $fieldMin[0].style.display = "block";
                            }

                            if (_singleX + _singleW > normalWidth - fieldMaxWidth) {
                                $fieldMax[0].style.display = "none";
                            } else {
                                $fieldMax[0].style.display = "block";
                            }
                        }

                        slider.prop("value", parseFloat(numbers.fromNumber));

                    } else if (settings.type === "double") {

                        if (numbers.fromNumber === settings.max) {
                            maxPostfix = settings.maxPostfix;
                        } else {
                            maxPostfix = "";
                        }

                        if (numbers.toNumber === settings.max) {
                            maxPostfix = settings.maxPostfix;
                        } else {
                            maxPostfix = "";
                        }

                        if (allow_values) {
                            _from =
                                settings.prefix +
                                settings.values[numbers.fromNumber] +
                                settings.postfix;

                            _to =
                                settings.prefix +
                                settings.values[numbers.toNumber] +
                                maxPostfix +
                                settings.postfix;

                            if (numbers.fromNumber !== numbers.toNumber) {
                                _single =
                                    settings.prefix +
                                    settings.values[numbers.fromNumber] +
                                    " — " + settings.prefix +
                                    settings.values[numbers.toNumber] +
                                    maxPostfix +
                                    settings.postfix;
                            } else {
                                _single =
                                    settings.prefix +
                                    settings.values[numbers.fromNumber] +
                                    maxPostfix +
                                    settings.postfix;
                            }
                        } else {
                            _from =
                                settings.prefix +
                                prettify(numbers.fromNumber) +
                                settings.postfix;

                            _to =
                                settings.prefix +
                                prettify(numbers.toNumber) +
                                maxPostfix +
                                settings.postfix;

                            if (numbers.fromNumber !== numbers.toNumber) {
                                _single =
                                    settings.prefix +
                                    prettify(numbers.fromNumber) +
                                    " — " + settings.prefix +
                                    prettify(numbers.toNumber) +
                                    maxPostfix +
                                    settings.postfix;
                            } else {
                                _single =
                                    settings.prefix +
                                    prettify(numbers.fromNumber) +
                                    maxPostfix +
                                    settings.postfix;
                            }
                        }

                        $fieldFrom.html(_from);
                        $fieldTo.html(_to);
                        $fieldSingle.html(_single);

                        _fromW = $fieldFrom.outerWidth(false);
                        _fromX = numbers.fromX - (_fromW / 2) + _slW;
                        if (_fromX < 0) {
                            _fromX = 0;
                        }
                        if (_fromX > normalWidth - _fromW) {
                            _fromX = normalWidth - _fromW;
                        }
                        $fieldFrom[0].style.left = _fromX + "px";

                        _toW = $fieldTo.outerWidth(false);
                        _toX = numbers.toX - (_toW / 2) + _slW;
                        if (_toX < 0) {
                            _toX = 0;
                        }
                        if (_toX > normalWidth - _toW) {
                            _toX = normalWidth - _toW;
                        }
                        $fieldTo[0].style.left = _toX + "px";

                        _singleW = $fieldSingle.outerWidth(false);
                        _singleX = numbers.fromX + ((numbers.toX - numbers.fromX) / 2) - (_singleW / 2) + _slW;
                        if (_singleX < 0) {
                            _singleX = 0;
                        }
                        if (_singleX > normalWidth - _singleW) {
                            _singleX = normalWidth - _singleW;
                        }
                        $fieldSingle[0].style.left = _singleX + "px";

                        if (_fromX + _fromW < _toX) {
                            $fieldSingle[0].style.display = "none";
                            $fieldFrom[0].style.display = "block";
                            $fieldTo[0].style.display = "block";
                        } else {
                            $fieldSingle[0].style.display = "block";
                            $fieldFrom[0].style.display = "none";
                            $fieldTo[0].style.display = "none";
                        }

                        if (!settings.hideMinMax && !settings.hideFromTo) {
                            if (_singleX < fieldMinWidth || _fromX < fieldMinWidth) {
                                $fieldMin[0].style.display = "none";
                            } else {
                                $fieldMin[0].style.display = "block";
                            }

                            if (_singleX + _singleW > normalWidth - fieldMaxWidth || _toX + _toW > normalWidth - fieldMaxWidth) {
                                $fieldMax[0].style.display = "none";
                            } else {
                                $fieldMax[0].style.display = "block";
                            }
                        }

                        slider.prop("value", parseFloat(numbers.fromNumber) + ";" + parseFloat(numbers.toNumber));

                    }

                    settings.from = numbers.fromNumber;
                    settings.to = numbers.toNumber;
                    callbacks();
                };


                var callbacks = function () {
                    // trigger onFinish function
                    if (typeof settings.onFinish === "function" && !is_slider_active && !is_first_start) {
                        settings.onFinish.call(this, numbers);
                    }

                    // trigger onChange function
                    if (typeof settings.onChange === "function" && !is_first_start) {
                        settings.onChange.call(this, numbers);
                    }

                    // trigger onLoad function
                    if (typeof settings.onLoad === "function" && !is_slider_active && is_first_start) {
                        settings.onLoad.call(this, numbers);
                        is_first_start = false;
                    }
                };


                var setGrid = function () {
                    $container.addClass("irs-with-grid");

                    var i,
                        text = '',
                        step = 0,
                        tStep = 0,
                        gridHTML = '',
                        smNum = 20,
                        bigNum = 4,
                        cont_width = normalWidth - (settings.gridMargin * 2);



                    for (i = 0; i <= smNum; i += 1) {
                        step = Math.floor(cont_width / smNum * i);

                        if (step >= cont_width) {
                            step = cont_width - 1;
                        }
                        gridHTML += '<span class="irs-grid-pol small" style="left: ' + step + 'px;"></span>';
                    }
                    for (i = 0; i <= bigNum; i += 1) {
                        step = Math.floor(cont_width / bigNum * i);

                        if (step >= cont_width) {
                            step = cont_width - 1;
                        }
                        gridHTML += '<span class="irs-grid-pol" style="left: ' + step + 'px;"></span>';

                        if (stepFloat) {
                            text = (settings.min + ((settings.max - settings.min) / bigNum * i));
                            text = (text / settings.step) * settings.step;
                            text = parseInt(text * stepFloat, 10) / stepFloat;
                        } else {
                            text = Math.round(settings.min + ((settings.max - settings.min) / bigNum * i));
                            text = Math.round(text / settings.step) * settings.step;
                            text = prettify(text);
                        }

                        if (allow_values) {
                            if (settings.hideMinMax) {
                                text = Math.round(settings.min + ((settings.max - settings.min) / bigNum * i));
                                text = Math.round(text / settings.step) * settings.step;
                                if (i === 0 || i === bigNum) {
                                    text = settings.values[text];
                                } else {
                                    text = "";
                                }
                            } else {
                                text = "";
                            }
                        }

                        if (i === 0) {
                            tStep = step;
                            gridHTML += '<span class="irs-grid-text" style="left: ' + tStep + 'px; text-align: left;">' + text + '</span>';
                        } else if (i === bigNum) {
                            tStep = step - 100;
                            gridHTML += '<span class="irs-grid-text" style="left: ' + tStep + 'px; text-align: right;">' + text + '</span>';
                        } else {
                            tStep = step - 50;
                            gridHTML += '<span class="irs-grid-text" style="left: ' + tStep + 'px;">' + text + '</span>';
                        }
                    }

                    $grid.html(gridHTML);
                    $grid[0].style.left = settings.gridMargin + "px";
                };



                // Disable state
                var setMask = function () {
                    $container.addClass("irs-disabled");
                    $container.append(disableHTML);
                };

                var removeMask = function () {
                    $container.removeClass("irs-disabled");
                    $container.find(".irs-disable-mask").remove();
                };



                placeHTML();
            });
        },
        update: function (options) {
            return this.each(function () {
                this.updateData(options);
            });
        },
        remove: function () {
            return this.each(function () {
                this.removeSlider();
            });
        }
    };

    $.fn.ionRangeSlider = function (method) {
        if (methods[method]) {
            return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
        } else if (typeof method === 'object' || !method) {
            return methods.init.apply(this, arguments);
        } else {
            $.error('Method ' + method + ' does not exist for jQuery.ionRangeSlider');
        }
    };

}(jQuery, document, window, navigator));
;
/*
 * Pointer Events Polyfill: Adds support for the style attribute "pointer-events: none" to browsers without this feature (namely, IE).
 * (c) 2013, Kent Mewhort, licensed under BSD. See LICENSE.txt for details.
 */

// constructor
function PointerEventsPolyfill(options){
    // set defaults
    this.options = {
        selector: '*',
        mouseEvents: ['click','dblclick','mousedown','mouseup'],
        usePolyfillIf: function(){
            if(navigator.appName == 'Microsoft Internet Explorer')
            {
                var agent = navigator.userAgent;
                if (agent.match(/MSIE ([0-9]{1,}[\.0-9]{0,})/) != null){
                    var version = parseFloat( RegExp.$1 );
                    if(version < 11)
                        return true;
                }
            }
            return false;
        }
    };
    if(options){
        var obj = this;
        $.each(options, function(k,v){
            obj.options[k] = v;
        });
    }

    if(this.options.usePolyfillIf())
        this.register_mouse_events();
}

// singleton initializer
PointerEventsPolyfill.initialize = function(options){
    if(PointerEventsPolyfill.singleton == null)
        PointerEventsPolyfill.singleton = new PointerEventsPolyfill(options);
    return PointerEventsPolyfill.singleton;
};

// handle mouse events w/ support for pointer-events: none
PointerEventsPolyfill.prototype.register_mouse_events = function(){
    // register on all elements (and all future elements) matching the selector
    $(document).on(this.options.mouseEvents.join(" "), this.options.selector, function(e){
        if($(this).css('pointer-events') == 'none'){
            // peak at the element below
            var origDisplayAttribute = $(this).css('display');
            $(this).css('display','none');

            var underneathElem = document.elementFromPoint(e.clientX, e.clientY);

            if(origDisplayAttribute)
                $(this)
                    .css('display', origDisplayAttribute);
            else
                $(this).css('display','');

            // fire the mouse event on the element below
            e.target = underneathElem;
            $(underneathElem).trigger(e);

            return false;
        }
        return true;
    });
};
;
/*!
 * fancyBox - jQuery Plugin
 * version: 2.1.5 (Fri, 14 Jun 2013)
 * @requires jQuery v1.6 or later
 *
 * Examples at http://fancyapps.com/fancybox/
 * License: www.fancyapps.com/fancybox/#license
 *
 * Copyright 2012 Janis Skarnelis - janis@fancyapps.com
 *
 */

(function (window, document, $, undefined) {
	"use strict";

	var H = $("html"),
		W = $(window),
		D = $(document),
		F = $.fancybox = function () {
			F.open.apply( this, arguments );
		},
		IE =  navigator.userAgent.match(/msie/i),
		didUpdate	= null,
		isTouch		= document.createTouch !== undefined,

		isQuery	= function(obj) {
			return obj && obj.hasOwnProperty && obj instanceof $;
		},
		isString = function(str) {
			return str && $.type(str) === "string";
		},
		isPercentage = function(str) {
			return isString(str) && str.indexOf('%') > 0;
		},
		isScrollable = function(el) {
			return (el && !(el.style.overflow && el.style.overflow === 'hidden') && ((el.clientWidth && el.scrollWidth > el.clientWidth) || (el.clientHeight && el.scrollHeight > el.clientHeight)));
		},
		getScalar = function(orig, dim) {
			var value = parseInt(orig, 10) || 0;

			if (dim && isPercentage(orig)) {
				value = F.getViewport()[ dim ] / 100 * value;
			}

			return Math.ceil(value);
		},
		getValue = function(value, dim) {
			return getScalar(value, dim) + 'px';
		};

	$.extend(F, {
		// The current version of fancyBox
		version: '2.1.5',

		defaults: {
			padding : 15,
			margin  : 20,

			width     : 800,
			height    : 600,
			minWidth  : 100,
			minHeight : 100,
			maxWidth  : 9999,
			maxHeight : 9999,
			pixelRatio: 1, // Set to 2 for retina display support

			autoSize   : true,
			autoHeight : false,
			autoWidth  : false,

			autoResize  : true,
			autoCenter  : !isTouch,
			fitToView   : true,
			aspectRatio : false,
			topRatio    : 0.5,
			leftRatio   : 0.5,

			scrolling : 'auto', // 'auto', 'yes' or 'no'
			wrapCSS   : '',

			arrows     : true,
			closeBtn   : true,
			closeClick : false,
			nextClick  : false,
			mouseWheel : true,
			autoPlay   : false,
			playSpeed  : 3000,
			preload    : 3,
			modal      : false,
			loop       : true,

			ajax  : {
				dataType : 'html',
				headers  : { 'X-fancyBox': true }
			},
			iframe : {
				scrolling : 'auto',
				preload   : true
			},
			swf : {
				wmode: 'transparent',
				allowfullscreen   : 'true',
				allowscriptaccess : 'always'
			},

			keys  : {
				next : {
					13 : 'left', // enter
					34 : 'up',   // page down
					39 : 'left', // right arrow
					40 : 'up'    // down arrow
				},
				prev : {
					8  : 'right',  // backspace
					33 : 'down',   // page up
					37 : 'right',  // left arrow
					38 : 'down'    // up arrow
				},
				close  : [27], // escape key
				play   : [32], // space - start/stop slideshow
				toggle : [70]  // letter "f" - toggle fullscreen
			},

			direction : {
				next : 'left',
				prev : 'right'
			},

			scrollOutside  : true,

			// Override some properties
			index   : 0,
			type    : null,
			href    : null,
			content : null,
			title   : null,

			// HTML templates
			tpl: {
				wrap     : '<div class="fancybox-wrap" tabIndex="-1"><div class="fancybox-skin"><div class="fancybox-outer"><div class="fancybox-inner"></div></div></div></div>',
				image    : '<img class="fancybox-image" src="{href}" alt="" />',
				iframe   : '<iframe id="fancybox-frame{rnd}" name="fancybox-frame{rnd}" class="fancybox-iframe" frameborder="0" vspace="0" hspace="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen' + (IE ? ' allowtransparency="true"' : '') + '></iframe>',
				error    : '<p class="fancybox-error">The requested content cannot be loaded.<br/>Please try again later.</p>',
				closeBtn : '<a title="Close" class="fancybox-item fancybox-close" href="javascript:;"></a>',
				next     : '<a title="Next" class="fancybox-nav fancybox-next" href="javascript:;"><span></span></a>',
				prev     : '<a title="Previous" class="fancybox-nav fancybox-prev" href="javascript:;"><span></span></a>'
			},

			// Properties for each animation type
			// Opening fancyBox
			openEffect  : 'fade', // 'elastic', 'fade' or 'none'
			openSpeed   : 250,
			openEasing  : 'swing',
			openOpacity : true,
			openMethod  : 'zoomIn',

			// Closing fancyBox
			closeEffect  : 'fade', // 'elastic', 'fade' or 'none'
			closeSpeed   : 250,
			closeEasing  : 'swing',
			closeOpacity : true,
			closeMethod  : 'zoomOut',

			// Changing next gallery item
			nextEffect : 'elastic', // 'elastic', 'fade' or 'none'
			nextSpeed  : 250,
			nextEasing : 'swing',
			nextMethod : 'changeIn',

			// Changing previous gallery item
			prevEffect : 'elastic', // 'elastic', 'fade' or 'none'
			prevSpeed  : 250,
			prevEasing : 'swing',
			prevMethod : 'changeOut',

			// Enable default helpers
			helpers : {
				overlay : true,
				title   : true
			},

			// Callbacks
			onCancel     : $.noop, // If canceling
			beforeLoad   : $.noop, // Before loading
			afterLoad    : $.noop, // After loading
			beforeShow   : $.noop, // Before changing in current item
			afterShow    : $.noop, // After opening
			beforeChange : $.noop, // Before changing gallery item
			beforeClose  : $.noop, // Before closing
			afterClose   : $.noop  // After closing
		},

		//Current state
		group    : {}, // Selected group
		opts     : {}, // Group options
		previous : null,  // Previous element
		coming   : null,  // Element being loaded
		current  : null,  // Currently loaded element
		isActive : false, // Is activated
		isOpen   : false, // Is currently open
		isOpened : false, // Have been fully opened at least once

		wrap  : null,
		skin  : null,
		outer : null,
		inner : null,

		player : {
			timer    : null,
			isActive : false
		},

		// Loaders
		ajaxLoad   : null,
		imgPreload : null,

		// Some collections
		transitions : {},
		helpers     : {},

		/*
		 *	Static methods
		 */

		open: function (group, opts) {
			if (!group) {
				return;
			}

			if (!$.isPlainObject(opts)) {
				opts = {};
			}

			// Close if already active
			if (false === F.close(true)) {
				return;
			}

			// Normalize group
			if (!$.isArray(group)) {
				group = isQuery(group) ? $(group).get() : [group];
			}

			// Recheck if the type of each element is `object` and set content type (image, ajax, etc)
			$.each(group, function(i, element) {
				var obj = {},
					href,
					title,
					content,
					type,
					rez,
					hrefParts,
					selector;

				if ($.type(element) === "object") {
					// Check if is DOM element
					if (element.nodeType) {
						element = $(element);
					}

					if (isQuery(element)) {
						obj = {
							href    : element.data('fancybox-href') || element.attr('href'),
							title   : element.data('fancybox-title') || element.attr('title'),
							isDom   : true,
							element : element
						};

						if ($.metadata) {
							$.extend(true, obj, element.metadata());
						}

					} else {
						obj = element;
					}
				}

				href  = opts.href  || obj.href || (isString(element) ? element : null);
				title = opts.title !== undefined ? opts.title : obj.title || '';

				content = opts.content || obj.content;
				type    = content ? 'html' : (opts.type  || obj.type);

				if (!type && obj.isDom) {
					type = element.data('fancybox-type');

					if (!type) {
						rez  = element.prop('class').match(/fancybox\.(\w+)/);
						type = rez ? rez[1] : null;
					}
				}

				if (isString(href)) {
					// Try to guess the content type
					if (!type) {
						if (F.isImage(href)) {
							type = 'image';

						} else if (F.isSWF(href)) {
							type = 'swf';

						} else if (href.charAt(0) === '#') {
							type = 'inline';

						} else if (isString(element)) {
							type    = 'html';
							content = element;
						}
					}

					// Split url into two pieces with source url and content selector, e.g,
					// "/mypage.html #my_id" will load "/mypage.html" and display element having id "my_id"
					if (type === 'ajax') {
						hrefParts = href.split(/\s+/, 2);
						href      = hrefParts.shift();
						selector  = hrefParts.shift();
					}
				}

				if (!content) {
					if (type === 'inline') {
						if (href) {
							content = $( isString(href) ? href.replace(/.*(?=#[^\s]+$)/, '') : href ); //strip for ie7

						} else if (obj.isDom) {
							content = element;
						}

					} else if (type === 'html') {
						content = href;

					} else if (!type && !href && obj.isDom) {
						type    = 'inline';
						content = element;
					}
				}

				$.extend(obj, {
					href     : href,
					type     : type,
					content  : content,
					title    : title,
					selector : selector
				});

				group[ i ] = obj;
			});

			// Extend the defaults
			F.opts = $.extend(true, {}, F.defaults, opts);

			// All options are merged recursive except keys
			if (opts.keys !== undefined) {
				F.opts.keys = opts.keys ? $.extend({}, F.defaults.keys, opts.keys) : false;
			}

			F.group = group;

			return F._start(F.opts.index);
		},

		// Cancel image loading or abort ajax request
		cancel: function () {
			var coming = F.coming;

			if (!coming || false === F.trigger('onCancel')) {
				return;
			}

			F.hideLoading();

			if (F.ajaxLoad) {
				F.ajaxLoad.abort();
			}

			F.ajaxLoad = null;

			if (F.imgPreload) {
				F.imgPreload.onload = F.imgPreload.onerror = null;
			}

			if (coming.wrap) {
				coming.wrap.stop(true, true).trigger('onReset').remove();
			}

			F.coming = null;

			// If the first item has been canceled, then clear everything
			if (!F.current) {
				F._afterZoomOut( coming );
			}
		},

		// Start closing animation if is open; remove immediately if opening/closing
		close: function (event) {
			F.cancel();

			if (false === F.trigger('beforeClose')) {
				return;
			}

			F.unbindEvents();

			if (!F.isActive) {
				return;
			}

			if (!F.isOpen || event === true) {
				$('.fancybox-wrap').stop(true).trigger('onReset').remove();

				F._afterZoomOut();

			} else {
				F.isOpen = F.isOpened = false;
				F.isClosing = true;

				$('.fancybox-item, .fancybox-nav').remove();

				F.wrap.stop(true, true).removeClass('fancybox-opened');

				F.transitions[ F.current.closeMethod ]();
			}
		},

		// Manage slideshow:
		//   $.fancybox.play(); - toggle slideshow
		//   $.fancybox.play( true ); - start
		//   $.fancybox.play( false ); - stop
		play: function ( action ) {
			var clear = function () {
					clearTimeout(F.player.timer);
				},
				set = function () {
					clear();

					if (F.current && F.player.isActive) {
						F.player.timer = setTimeout(F.next, F.current.playSpeed);
					}
				},
				stop = function () {
					clear();

					D.unbind('.player');

					F.player.isActive = false;

					F.trigger('onPlayEnd');
				},
				start = function () {
					if (F.current && (F.current.loop || F.current.index < F.group.length - 1)) {
						F.player.isActive = true;

						D.bind({
							'onCancel.player beforeClose.player' : stop,
							'onUpdate.player'   : set,
							'beforeLoad.player' : clear
						});

						set();

						F.trigger('onPlayStart');
					}
				};

			if (action === true || (!F.player.isActive && action !== false)) {
				start();
			} else {
				stop();
			}
		},

		// Navigate to next gallery item
		next: function ( direction ) {
			var current = F.current;

			if (current) {
				if (!isString(direction)) {
					direction = current.direction.next;
				}

				F.jumpto(current.index + 1, direction, 'next');
			}
		},

		// Navigate to previous gallery item
		prev: function ( direction ) {
			var current = F.current;

			if (current) {
				if (!isString(direction)) {
					direction = current.direction.prev;
				}

				F.jumpto(current.index - 1, direction, 'prev');
			}
		},

		// Navigate to gallery item by index
		jumpto: function ( index, direction, router ) {
			var current = F.current;

			if (!current) {
				return;
			}

			index = getScalar(index);

			F.direction = direction || current.direction[ (index >= current.index ? 'next' : 'prev') ];
			F.router    = router || 'jumpto';

			if (current.loop) {
				if (index < 0) {
					index = current.group.length + (index % current.group.length);
				}

				index = index % current.group.length;
			}

			if (current.group[ index ] !== undefined) {
				F.cancel();

				F._start(index);
			}
		},

		// Center inside viewport and toggle position type to fixed or absolute if needed
		reposition: function (e, onlyAbsolute) {
			var current = F.current,
				wrap    = current ? current.wrap : null,
				pos;

			if (wrap) {
				pos = F._getPosition(onlyAbsolute);

				if (e && e.type === 'scroll') {
					delete pos.position;

					wrap.stop(true, true).animate(pos, 200);

				} else {
					wrap.css(pos);

					current.pos = $.extend({}, current.dim, pos);
				}
			}
		},

		update: function (e) {
			var type = (e && e.type),
				anyway = !type || type === 'orientationchange';

			if (anyway) {
				clearTimeout(didUpdate);

				didUpdate = null;
			}

			if (!F.isOpen || didUpdate) {
				return;
			}

			didUpdate = setTimeout(function() {
				var current = F.current;

				if (!current || F.isClosing) {
					return;
				}

				F.wrap.removeClass('fancybox-tmp');

				if (anyway || type === 'load' || (type === 'resize' && current.autoResize)) {
					F._setDimension();
				}

				if (!(type === 'scroll' && current.canShrink)) {
					F.reposition(e);
				}

				F.trigger('onUpdate');

				didUpdate = null;

			}, (anyway && !isTouch ? 0 : 300));
		},

		// Shrink content to fit inside viewport or restore if resized
		toggle: function ( action ) {
			if (F.isOpen) {
				F.current.fitToView = $.type(action) === "boolean" ? action : !F.current.fitToView;

				// Help browser to restore document dimensions
				if (isTouch) {
					F.wrap.removeAttr('style').addClass('fancybox-tmp');

					F.trigger('onUpdate');
				}

				F.update();
			}
		},

		hideLoading: function () {
			D.unbind('.loading');

			$('#fancybox-loading').remove();
		},

		showLoading: function () {
			var el, viewport;

			F.hideLoading();

			el = $('<div id="fancybox-loading"><div></div></div>').click(F.cancel).appendTo('body');

			// If user will press the escape-button, the request will be canceled
			D.bind('keydown.loading', function(e) {
				if ((e.which || e.keyCode) === 27) {
					e.preventDefault();

					F.cancel();
				}
			});

			if (!F.defaults.fixed) {
				viewport = F.getViewport();

				el.css({
					position : 'absolute',
					top  : (viewport.h * 0.5) + viewport.y,
					left : (viewport.w * 0.5) + viewport.x
				});
			}
		},

		getViewport: function () {
			var locked = (F.current && F.current.locked) || false,
				rez    = {
					x: W.scrollLeft(),
					y: W.scrollTop()
				};

			if (locked) {
				rez.w = locked[0].clientWidth;
				rez.h = locked[0].clientHeight;

			} else {
				// See http://bugs.jquery.com/ticket/6724
				rez.w = isTouch && window.innerWidth  ? window.innerWidth  : W.width();
				rez.h = isTouch && window.innerHeight ? window.innerHeight : W.height();
			}

			return rez;
		},

		// Unbind the keyboard / clicking actions
		unbindEvents: function () {
			if (F.wrap && isQuery(F.wrap)) {
				F.wrap.unbind('.fb');
			}

			D.unbind('.fb');
			W.unbind('.fb');
		},

		bindEvents: function () {
			var current = F.current,
				keys;

			if (!current) {
				return;
			}

			// Changing document height on iOS devices triggers a 'resize' event,
			// that can change document height... repeating infinitely
			W.bind('orientationchange.fb' + (isTouch ? '' : ' resize.fb') + (current.autoCenter && !current.locked ? ' scroll.fb' : ''), F.update);

			keys = current.keys;

			if (keys) {
				D.bind('keydown.fb', function (e) {
					var code   = e.which || e.keyCode,
						target = e.target || e.srcElement;

					// Skip esc key if loading, because showLoading will cancel preloading
					if (code === 27 && F.coming) {
						return false;
					}

					// Ignore key combinations and key events within form elements
					if (!e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey && !(target && (target.type || $(target).is('[contenteditable]')))) {
						$.each(keys, function(i, val) {
							if (current.group.length > 1 && val[ code ] !== undefined) {
								F[ i ]( val[ code ] );

								e.preventDefault();
								return false;
							}

							if ($.inArray(code, val) > -1) {
								F[ i ] ();

								e.preventDefault();
								return false;
							}
						});
					}
				});
			}

			if ($.fn.mousewheel && current.mouseWheel) {
				F.wrap.bind('mousewheel.fb', function (e, delta, deltaX, deltaY) {
					var target = e.target || null,
						parent = $(target),
						canScroll = false;

					while (parent.length) {
						if (canScroll || parent.is('.fancybox-skin') || parent.is('.fancybox-wrap')) {
							break;
						}

						canScroll = isScrollable( parent[0] );
						parent    = $(parent).parent();
					}

					if (delta !== 0 && !canScroll) {
						if (F.group.length > 1 && !current.canShrink) {
							if (deltaY > 0 || deltaX > 0) {
								F.prev( deltaY > 0 ? 'down' : 'left' );

							} else if (deltaY < 0 || deltaX < 0) {
								F.next( deltaY < 0 ? 'up' : 'right' );
							}

							e.preventDefault();
						}
					}
				});
			}
		},

		trigger: function (event, o) {
			var ret, obj = o || F.coming || F.current;

			if (!obj) {
				return;
			}

			if ($.isFunction( obj[event] )) {
				ret = obj[event].apply(obj, Array.prototype.slice.call(arguments, 1));
			}

			if (ret === false) {
				return false;
			}

			if (obj.helpers) {
				$.each(obj.helpers, function (helper, opts) {
					if (opts && F.helpers[helper] && $.isFunction(F.helpers[helper][event])) {
						F.helpers[helper][event]($.extend(true, {}, F.helpers[helper].defaults, opts), obj);
					}
				});
			}

			D.trigger(event);
		},

		isImage: function (str) {
			return isString(str) && str.match(/(^data:image\/.*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg)((\?|#).*)?$)/i);
		},

		isSWF: function (str) {
			return isString(str) && str.match(/\.(swf)((\?|#).*)?$/i);
		},

		_start: function (index) {
			var coming = {},
				obj,
				href,
				type,
				margin,
				padding;

			index = getScalar( index );
			obj   = F.group[ index ] || null;

			if (!obj) {
				return false;
			}

			coming = $.extend(true, {}, F.opts, obj);

			// Convert margin and padding properties to array - top, right, bottom, left
			margin  = coming.margin;
			padding = coming.padding;

			if ($.type(margin) === 'number') {
				coming.margin = [margin, margin, margin, margin];
			}

			if ($.type(padding) === 'number') {
				coming.padding = [padding, padding, padding, padding];
			}

			// 'modal' propery is just a shortcut
			if (coming.modal) {
				$.extend(true, coming, {
					closeBtn   : false,
					closeClick : false,
					nextClick  : false,
					arrows     : false,
					mouseWheel : false,
					keys       : null,
					helpers: {
						overlay : {
							closeClick : false
						}
					}
				});
			}

			// 'autoSize' property is a shortcut, too
			if (coming.autoSize) {
				coming.autoWidth = coming.autoHeight = true;
			}

			if (coming.width === 'auto') {
				coming.autoWidth = true;
			}

			if (coming.height === 'auto') {
				coming.autoHeight = true;
			}

			/*
			 * Add reference to the group, so it`s possible to access from callbacks, example:
			 * afterLoad : function() {
			 *     this.title = 'Image ' + (this.index + 1) + ' of ' + this.group.length + (this.title ? ' - ' + this.title : '');
			 * }
			 */

			coming.group  = F.group;
			coming.index  = index;

			// Give a chance for callback or helpers to update coming item (type, title, etc)
			F.coming = coming;

			if (false === F.trigger('beforeLoad')) {
				F.coming = null;

				return;
			}

			type = coming.type;
			href = coming.href;

			if (!type) {
				F.coming = null;

				//If we can not determine content type then drop silently or display next/prev item if looping through gallery
				if (F.current && F.router && F.router !== 'jumpto') {
					F.current.index = index;

					return F[ F.router ]( F.direction );
				}

				return false;
			}

			F.isActive = true;

			if (type === 'image' || type === 'swf') {
				coming.autoHeight = coming.autoWidth = false;
				coming.scrolling  = 'visible';
			}

			if (type === 'image') {
				coming.aspectRatio = true;
			}

			if (type === 'iframe' && isTouch) {
				coming.scrolling = 'scroll';
			}

			// Build the neccessary markup
			coming.wrap = $(coming.tpl.wrap).addClass('fancybox-' + (isTouch ? 'mobile' : 'desktop') + ' fancybox-type-' + type + ' fancybox-tmp ' + coming.wrapCSS).appendTo( coming.parent || 'body' );

			$.extend(coming, {
				skin  : $('.fancybox-skin',  coming.wrap),
				outer : $('.fancybox-outer', coming.wrap),
				inner : $('.fancybox-inner', coming.wrap)
			});

			$.each(["Top", "Right", "Bottom", "Left"], function(i, v) {
				coming.skin.css('padding' + v, getValue(coming.padding[ i ]));
			});

			F.trigger('onReady');

			// Check before try to load; 'inline' and 'html' types need content, others - href
			if (type === 'inline' || type === 'html') {
				if (!coming.content || !coming.content.length) {
					return F._error( 'content' );
				}

			} else if (!href) {
				return F._error( 'href' );
			}

			if (type === 'image') {
				F._loadImage();

			} else if (type === 'ajax') {
				F._loadAjax();

			} else if (type === 'iframe') {
				F._loadIframe();

			} else {
				F._afterLoad();
			}
		},

		_error: function ( type ) {
			$.extend(F.coming, {
				type       : 'html',
				autoWidth  : true,
				autoHeight : true,
				minWidth   : 0,
				minHeight  : 0,
				scrolling  : 'no',
				hasError   : type,
				content    : F.coming.tpl.error
			});

			F._afterLoad();
		},

		_loadImage: function () {
			// Reset preload image so it is later possible to check "complete" property
			var img = F.imgPreload = new Image();

			img.onload = function () {
				this.onload = this.onerror = null;

				F.coming.width  = this.width / F.opts.pixelRatio;
				F.coming.height = this.height / F.opts.pixelRatio;

				F._afterLoad();
			};

			img.onerror = function () {
				this.onload = this.onerror = null;

				F._error( 'image' );
			};

			img.src = F.coming.href;

			if (img.complete !== true) {
				F.showLoading();
			}
		},

		_loadAjax: function () {
			var coming = F.coming;

			F.showLoading();

			F.ajaxLoad = $.ajax($.extend({}, coming.ajax, {
				url: coming.href,
				error: function (jqXHR, textStatus) {
					if (F.coming && textStatus !== 'abort') {
						F._error( 'ajax', jqXHR );

					} else {
						F.hideLoading();
					}
				},
				success: function (data, textStatus) {
					if (textStatus === 'success') {
						coming.content = data;

						F._afterLoad();
					}
				}
			}));
		},

		_loadIframe: function() {
			var coming = F.coming,
				iframe = $(coming.tpl.iframe.replace(/\{rnd\}/g, new Date().getTime()))
					.attr('scrolling', isTouch ? 'auto' : coming.iframe.scrolling)
					.attr('src', coming.href);

			// This helps IE
			$(coming.wrap).bind('onReset', function () {
				try {
					$(this).find('iframe').hide().attr('src', '//about:blank').end().empty();
				} catch (e) {}
			});

			if (coming.iframe.preload) {
				F.showLoading();

				iframe.one('load', function() {
					$(this).data('ready', 1);

					// iOS will lose scrolling if we resize
					if (!isTouch) {
						$(this).bind('load.fb', F.update);
					}

					// Without this trick:
					//   - iframe won't scroll on iOS devices
					//   - IE7 sometimes displays empty iframe
					$(this).parents('.fancybox-wrap').width('100%').removeClass('fancybox-tmp').show();

					F._afterLoad();
				});
			}

			coming.content = iframe.appendTo( coming.inner );

			if (!coming.iframe.preload) {
				F._afterLoad();
			}
		},

		_preloadImages: function() {
			var group   = F.group,
				current = F.current,
				len     = group.length,
				cnt     = current.preload ? Math.min(current.preload, len - 1) : 0,
				item,
				i;

			for (i = 1; i <= cnt; i += 1) {
				item = group[ (current.index + i ) % len ];

				if (item.type === 'image' && item.href) {
					new Image().src = item.href;
				}
			}
		},

		_afterLoad: function () {
			var coming   = F.coming,
				previous = F.current,
				placeholder = 'fancybox-placeholder',
				current,
				content,
				type,
				scrolling,
				href,
				embed;

			F.hideLoading();

			if (!coming || F.isActive === false) {
				return;
			}

			if (false === F.trigger('afterLoad', coming, previous)) {
				coming.wrap.stop(true).trigger('onReset').remove();

				F.coming = null;

				return;
			}

			if (previous) {
				F.trigger('beforeChange', previous);

				previous.wrap.stop(true).removeClass('fancybox-opened')
					.find('.fancybox-item, .fancybox-nav')
					.remove();
			}

			F.unbindEvents();

			current   = coming;
			content   = coming.content;
			type      = coming.type;
			scrolling = coming.scrolling;

			$.extend(F, {
				wrap  : current.wrap,
				skin  : current.skin,
				outer : current.outer,
				inner : current.inner,
				current  : current,
				previous : previous
			});

			href = current.href;

			switch (type) {
				case 'inline':
				case 'ajax':
				case 'html':
					if (current.selector) {
						content = $('<div>').html(content).find(current.selector);

					} else if (isQuery(content)) {
						if (!content.data(placeholder)) {
							content.data(placeholder, $('<div class="' + placeholder + '"></div>').insertAfter( content ).hide() );
						}

						content = content.show().detach();

						current.wrap.bind('onReset', function () {
							if ($(this).find(content).length) {
								content.hide().replaceAll( content.data(placeholder) ).data(placeholder, false);
							}
						});
					}
				break;

				case 'image':
					content = current.tpl.image.replace('{href}', href);
				break;

				case 'swf':
					content = '<object id="fancybox-swf" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="100%" height="100%"><param name="movie" value="' + href + '"></param>';
					embed   = '';

					$.each(current.swf, function(name, val) {
						content += '<param name="' + name + '" value="' + val + '"></param>';
						embed   += ' ' + name + '="' + val + '"';
					});

					content += '<embed src="' + href + '" type="application/x-shockwave-flash" width="100%" height="100%"' + embed + '></embed></object>';
				break;
			}

			if (!(isQuery(content) && content.parent().is(current.inner))) {
				current.inner.append( content );
			}

			// Give a chance for helpers or callbacks to update elements
			F.trigger('beforeShow');

			// Set scrolling before calculating dimensions
			current.inner.css('overflow', scrolling === 'yes' ? 'scroll' : (scrolling === 'no' ? 'hidden' : scrolling));

			// Set initial dimensions and start position
			F._setDimension();

			F.reposition();

			F.isOpen = false;
			F.coming = null;

			F.bindEvents();

			if (!F.isOpened) {
				$('.fancybox-wrap').not( current.wrap ).stop(true).trigger('onReset').remove();

			} else if (previous.prevMethod) {
				F.transitions[ previous.prevMethod ]();
			}

			F.transitions[ F.isOpened ? current.nextMethod : current.openMethod ]();

			F._preloadImages();
		},

		_setDimension: function () {
			var viewport   = F.getViewport(),
				steps      = 0,
				canShrink  = false,
				canExpand  = false,
				wrap       = F.wrap,
				skin       = F.skin,
				inner      = F.inner,
				current    = F.current,
				width      = current.width,
				height     = current.height,
				minWidth   = current.minWidth,
				minHeight  = current.minHeight,
				maxWidth   = current.maxWidth,
				maxHeight  = current.maxHeight,
				scrolling  = current.scrolling,
				scrollOut  = current.scrollOutside ? current.scrollbarWidth : 0,
				margin     = current.margin,
				wMargin    = getScalar(margin[1] + margin[3]),
				hMargin    = getScalar(margin[0] + margin[2]),
				wPadding,
				hPadding,
				wSpace,
				hSpace,
				origWidth,
				origHeight,
				origMaxWidth,
				origMaxHeight,
				ratio,
				width_,
				height_,
				maxWidth_,
				maxHeight_,
				iframe,
				body;

			// Reset dimensions so we could re-check actual size
			wrap.add(skin).add(inner).width('auto').height('auto').removeClass('fancybox-tmp');

			wPadding = getScalar(skin.outerWidth(true)  - skin.width());
			hPadding = getScalar(skin.outerHeight(true) - skin.height());

			// Any space between content and viewport (margin, padding, border, title)
			wSpace = wMargin + wPadding;
			hSpace = hMargin + hPadding;

			origWidth  = isPercentage(width)  ? (viewport.w - wSpace) * getScalar(width)  / 100 : width;
			origHeight = isPercentage(height) ? (viewport.h - hSpace) * getScalar(height) / 100 : height;

			if (current.type === 'iframe') {
				iframe = current.content;

				if (current.autoHeight && iframe.data('ready') === 1) {
					try {
						if (iframe[0].contentWindow.document.location) {
							inner.width( origWidth ).height(9999);

							body = iframe.contents().find('body');

							if (scrollOut) {
								body.css('overflow-x', 'hidden');
							}

							origHeight = body.outerHeight(true);
						}

					} catch (e) {}
				}

			} else if (current.autoWidth || current.autoHeight) {
				inner.addClass( 'fancybox-tmp' );

				// Set width or height in case we need to calculate only one dimension
				if (!current.autoWidth) {
					inner.width( origWidth );
				}

				if (!current.autoHeight) {
					inner.height( origHeight );
				}

				if (current.autoWidth) {
					origWidth = inner.width();
				}

				if (current.autoHeight) {
					origHeight = inner.height();
				}

				inner.removeClass( 'fancybox-tmp' );
			}

			width  = getScalar( origWidth );
			height = getScalar( origHeight );

			ratio  = origWidth / origHeight;

			// Calculations for the content
			minWidth  = getScalar(isPercentage(minWidth) ? getScalar(minWidth, 'w') - wSpace : minWidth);
			maxWidth  = getScalar(isPercentage(maxWidth) ? getScalar(maxWidth, 'w') - wSpace : maxWidth);

			minHeight = getScalar(isPercentage(minHeight) ? getScalar(minHeight, 'h') - hSpace : minHeight);
			maxHeight = getScalar(isPercentage(maxHeight) ? getScalar(maxHeight, 'h') - hSpace : maxHeight);

			// These will be used to determine if wrap can fit in the viewport
			origMaxWidth  = maxWidth;
			origMaxHeight = maxHeight;

			if (current.fitToView) {
				maxWidth  = Math.min(viewport.w - wSpace, maxWidth);
				maxHeight = Math.min(viewport.h - hSpace, maxHeight);
			}

			maxWidth_  = viewport.w - wMargin;
			maxHeight_ = viewport.h - hMargin;

			if (current.aspectRatio) {
				if (width > maxWidth) {
					width  = maxWidth;
					height = getScalar(width / ratio);
				}

				if (height > maxHeight) {
					height = maxHeight;
					width  = getScalar(height * ratio);
				}

				if (width < minWidth) {
					width  = minWidth;
					height = getScalar(width / ratio);
				}

				if (height < minHeight) {
					height = minHeight;
					width  = getScalar(height * ratio);
				}

			} else {
				width = Math.max(minWidth, Math.min(width, maxWidth));

				if (current.autoHeight && current.type !== 'iframe') {
					inner.width( width );

					height = inner.height();
				}

				height = Math.max(minHeight, Math.min(height, maxHeight));
			}

			// Try to fit inside viewport (including the title)
			if (current.fitToView) {
				inner.width( width ).height( height );

				wrap.width( width + wPadding );

				// Real wrap dimensions
				width_  = wrap.width();
				height_ = wrap.height();

				if (current.aspectRatio) {
					while ((width_ > maxWidth_ || height_ > maxHeight_) && width > minWidth && height > minHeight) {
						if (steps++ > 19) {
							break;
						}

						height = Math.max(minHeight, Math.min(maxHeight, height - 10));
						width  = getScalar(height * ratio);

						if (width < minWidth) {
							width  = minWidth;
							height = getScalar(width / ratio);
						}

						if (width > maxWidth) {
							width  = maxWidth;
							height = getScalar(width / ratio);
						}

						inner.width( width ).height( height );

						wrap.width( width + wPadding );

						width_  = wrap.width();
						height_ = wrap.height();
					}

				} else {
					width  = Math.max(minWidth,  Math.min(width,  width  - (width_  - maxWidth_)));
					height = Math.max(minHeight, Math.min(height, height - (height_ - maxHeight_)));
				}
			}

			if (scrollOut && scrolling === 'auto' && height < origHeight && (width + wPadding + scrollOut) < maxWidth_) {
				width += scrollOut;
			}

			inner.width( width ).height( height );

			wrap.width( width + wPadding );

			width_  = wrap.width();
			height_ = wrap.height();

			canShrink = (width_ > maxWidth_ || height_ > maxHeight_) && width > minWidth && height > minHeight;
			canExpand = current.aspectRatio ? (width < origMaxWidth && height < origMaxHeight && width < origWidth && height < origHeight) : ((width < origMaxWidth || height < origMaxHeight) && (width < origWidth || height < origHeight));

			$.extend(current, {
				dim : {
					width	: getValue( width_ ),
					height	: getValue( height_ )
				},
				origWidth  : origWidth,
				origHeight : origHeight,
				canShrink  : canShrink,
				canExpand  : canExpand,
				wPadding   : wPadding,
				hPadding   : hPadding,
				wrapSpace  : height_ - skin.outerHeight(true),
				skinSpace  : skin.height() - height
			});

			if (!iframe && current.autoHeight && height > minHeight && height < maxHeight && !canExpand) {
				inner.height('auto');
			}
		},

		_getPosition: function (onlyAbsolute) {
			var current  = F.current,
				viewport = F.getViewport(),
				margin   = current.margin,
				width    = F.wrap.width()  + margin[1] + margin[3],
				height   = F.wrap.height() + margin[0] + margin[2],
				rez      = {
					position: 'absolute',
					top  : margin[0],
					left : margin[3]
				};

			if (current.autoCenter && current.fixed && !onlyAbsolute && height <= viewport.h && width <= viewport.w) {
				rez.position = 'fixed';

			} else if (!current.locked) {
				rez.top  += viewport.y;
				rez.left += viewport.x;
			}

			rez.top  = getValue(Math.max(rez.top,  rez.top  + ((viewport.h - height) * current.topRatio)));
			rez.left = getValue(Math.max(rez.left, rez.left + ((viewport.w - width)  * current.leftRatio)));

			return rez;
		},

		_afterZoomIn: function () {
			var current = F.current;

			if (!current) {
				return;
			}

			F.isOpen = F.isOpened = true;

			F.wrap.css('overflow', 'visible').addClass('fancybox-opened');

			F.update();

			// Assign a click event
			if ( current.closeClick || (current.nextClick && F.group.length > 1) ) {
				F.inner.css('cursor', 'pointer').bind('click.fb', function(e) {
					if (!$(e.target).is('a') && !$(e.target).parent().is('a')) {
						e.preventDefault();

						F[ current.closeClick ? 'close' : 'next' ]();
					}
				});
			}

			// Create a close button
			if (current.closeBtn) {
				$(current.tpl.closeBtn).appendTo(F.skin).bind('click.fb', function(e) {
					e.preventDefault();

					F.close();
				});
			}

			// Create navigation arrows
			if (current.arrows && F.group.length > 1) {
				if (current.loop || current.index > 0) {
					$(current.tpl.prev).appendTo(F.outer).bind('click.fb', F.prev);
				}

				if (current.loop || current.index < F.group.length - 1) {
					$(current.tpl.next).appendTo(F.outer).bind('click.fb', F.next);
				}
			}

			F.trigger('afterShow');

			// Stop the slideshow if this is the last item
			if (!current.loop && current.index === current.group.length - 1) {
				F.play( false );

			} else if (F.opts.autoPlay && !F.player.isActive) {
				F.opts.autoPlay = false;

				F.play();
			}
		},

		_afterZoomOut: function ( obj ) {
			obj = obj || F.current;

			$('.fancybox-wrap').trigger('onReset').remove();

			$.extend(F, {
				group  : {},
				opts   : {},
				router : false,
				current   : null,
				isActive  : false,
				isOpened  : false,
				isOpen    : false,
				isClosing : false,
				wrap   : null,
				skin   : null,
				outer  : null,
				inner  : null
			});

			F.trigger('afterClose', obj);
		}
	});

	/*
	 *	Default transitions
	 */

	F.transitions = {
		getOrigPosition: function () {
			var current  = F.current,
				element  = current.element,
				orig     = current.orig,
				pos      = {},
				width    = 50,
				height   = 50,
				hPadding = current.hPadding,
				wPadding = current.wPadding,
				viewport = F.getViewport();

			if (!orig && current.isDom && element.is(':visible')) {
				orig = element.find('img:first');

				if (!orig.length) {
					orig = element;
				}
			}

			if (isQuery(orig)) {
				pos = orig.offset();

				if (orig.is('img')) {
					width  = orig.outerWidth();
					height = orig.outerHeight();
				}

			} else {
				pos.top  = viewport.y + (viewport.h - height) * current.topRatio;
				pos.left = viewport.x + (viewport.w - width)  * current.leftRatio;
			}

			if (F.wrap.css('position') === 'fixed' || current.locked) {
				pos.top  -= viewport.y;
				pos.left -= viewport.x;
			}

			pos = {
				top     : getValue(pos.top  - hPadding * current.topRatio),
				left    : getValue(pos.left - wPadding * current.leftRatio),
				width   : getValue(width  + wPadding),
				height  : getValue(height + hPadding)
			};

			return pos;
		},

		step: function (now, fx) {
			var ratio,
				padding,
				value,
				prop       = fx.prop,
				current    = F.current,
				wrapSpace  = current.wrapSpace,
				skinSpace  = current.skinSpace;

			if (prop === 'width' || prop === 'height') {
				ratio = fx.end === fx.start ? 1 : (now - fx.start) / (fx.end - fx.start);

				if (F.isClosing) {
					ratio = 1 - ratio;
				}

				padding = prop === 'width' ? current.wPadding : current.hPadding;
				value   = now - padding;

				F.skin[ prop ](  getScalar( prop === 'width' ?  value : value - (wrapSpace * ratio) ) );
				F.inner[ prop ]( getScalar( prop === 'width' ?  value : value - (wrapSpace * ratio) - (skinSpace * ratio) ) );
			}
		},

		zoomIn: function () {
			var current  = F.current,
				startPos = current.pos,
				effect   = current.openEffect,
				elastic  = effect === 'elastic',
				endPos   = $.extend({opacity : 1}, startPos);

			// Remove "position" property that breaks older IE
			delete endPos.position;

			if (elastic) {
				startPos = this.getOrigPosition();

				if (current.openOpacity) {
					startPos.opacity = 0.1;
				}

			} else if (effect === 'fade') {
				startPos.opacity = 0.1;
			}

			F.wrap.css(startPos).animate(endPos, {
				duration : effect === 'none' ? 0 : current.openSpeed,
				easing   : current.openEasing,
				step     : elastic ? this.step : null,
				complete : F._afterZoomIn
			});
		},

		zoomOut: function () {
			var current  = F.current,
				effect   = current.closeEffect,
				elastic  = effect === 'elastic',
				endPos   = {opacity : 0.1};

			if (elastic) {
				endPos = this.getOrigPosition();

				if (current.closeOpacity) {
					endPos.opacity = 0.1;
				}
			}

			F.wrap.animate(endPos, {
				duration : effect === 'none' ? 0 : current.closeSpeed,
				easing   : current.closeEasing,
				step     : elastic ? this.step : null,
				complete : F._afterZoomOut
			});
		},

		changeIn: function () {
			var current   = F.current,
				effect    = current.nextEffect,
				startPos  = current.pos,
				endPos    = { opacity : 1 },
				direction = F.direction,
				distance  = 200,
				field;

			startPos.opacity = 0.1;

			if (effect === 'elastic') {
				field = direction === 'down' || direction === 'up' ? 'top' : 'left';

				if (direction === 'down' || direction === 'right') {
					startPos[ field ] = getValue(getScalar(startPos[ field ]) - distance);
					endPos[ field ]   = '+=' + distance + 'px';

				} else {
					startPos[ field ] = getValue(getScalar(startPos[ field ]) + distance);
					endPos[ field ]   = '-=' + distance + 'px';
				}
			}

			// Workaround for http://bugs.jquery.com/ticket/12273
			if (effect === 'none') {
				F._afterZoomIn();

			} else {
				F.wrap.css(startPos).animate(endPos, {
					duration : current.nextSpeed,
					easing   : current.nextEasing,
					complete : F._afterZoomIn
				});
			}
		},

		changeOut: function () {
			var previous  = F.previous,
				effect    = previous.prevEffect,
				endPos    = { opacity : 0.1 },
				direction = F.direction,
				distance  = 200;

			if (effect === 'elastic') {
				endPos[ direction === 'down' || direction === 'up' ? 'top' : 'left' ] = ( direction === 'up' || direction === 'left' ? '-' : '+' ) + '=' + distance + 'px';
			}

			previous.wrap.animate(endPos, {
				duration : effect === 'none' ? 0 : previous.prevSpeed,
				easing   : previous.prevEasing,
				complete : function () {
					$(this).trigger('onReset').remove();
				}
			});
		}
	};

	/*
	 *	Overlay helper
	 */

	F.helpers.overlay = {
		defaults : {
			closeClick : true,      // if true, fancyBox will be closed when user clicks on the overlay
			speedOut   : 200,       // duration of fadeOut animation
			showEarly  : true,      // indicates if should be opened immediately or wait until the content is ready
			css        : {},        // custom CSS properties
			locked     : !isTouch,  // if true, the content will be locked into overlay
			fixed      : true       // if false, the overlay CSS position property will not be set to "fixed"
		},

		overlay : null,      // current handle
		fixed   : false,     // indicates if the overlay has position "fixed"
		el      : $('html'), // element that contains "the lock"

		// Public methods
		create : function(opts) {
			opts = $.extend({}, this.defaults, opts);

			if (this.overlay) {
				this.close();
			}

			this.overlay = $('<div class="fancybox-overlay"></div>').appendTo( F.coming ? F.coming.parent : opts.parent );
			this.fixed   = false;

			if (opts.fixed && F.defaults.fixed) {
				this.overlay.addClass('fancybox-overlay-fixed');

				this.fixed = true;
			}
		},

		open : function(opts) {
			var that = this;

			opts = $.extend({}, this.defaults, opts);

			if (this.overlay) {
				this.overlay.unbind('.overlay').width('auto').height('auto');

			} else {
				this.create(opts);
			}

			if (!this.fixed) {
				W.bind('resize.overlay', $.proxy( this.update, this) );

				this.update();
			}

			if (opts.closeClick) {
				this.overlay.bind('click.overlay', function(e) {
					if ($(e.target).hasClass('fancybox-overlay')) {
						if (F.isActive) {
							F.close();
						} else {
							that.close();
						}

						return false;
					}
				});
			}

			this.overlay.css( opts.css ).show();
		},

		close : function() {
			var scrollV, scrollH;

			W.unbind('resize.overlay');

			if (this.el.hasClass('fancybox-lock')) {
				$('.fancybox-margin').removeClass('fancybox-margin');

				scrollV = W.scrollTop();
				scrollH = W.scrollLeft();

				this.el.removeClass('fancybox-lock');

				W.scrollTop( scrollV ).scrollLeft( scrollH );
			}

			$('.fancybox-overlay').remove().hide();

			$.extend(this, {
				overlay : null,
				fixed   : false
			});
		},

		// Private, callbacks

		update : function () {
			var width = '100%', offsetWidth;

			// Reset width/height so it will not mess
			this.overlay.width(width).height('100%');

			// jQuery does not return reliable result for IE
			if (IE) {
				offsetWidth = Math.max(document.documentElement.offsetWidth, document.body.offsetWidth);

				if (D.width() > offsetWidth) {
					width = D.width();
				}

			} else if (D.width() > W.width()) {
				width = D.width();
			}

			this.overlay.width(width).height(D.height());
		},

		// This is where we can manipulate DOM, because later it would cause iframes to reload
		onReady : function (opts, obj) {
			var overlay = this.overlay;

			$('.fancybox-overlay').stop(true, true);

			if (!overlay) {
				this.create(opts);
			}

			if (opts.locked && this.fixed && obj.fixed) {
				if (!overlay) {
					this.margin = D.height() > W.height() ? $('html').css('margin-right').replace("px", "") : false;
				}

				obj.locked = this.overlay.append( obj.wrap );
				obj.fixed  = false;
			}

			if (opts.showEarly === true) {
				this.beforeShow.apply(this, arguments);
			}
		},

		beforeShow : function(opts, obj) {
			var scrollV, scrollH;

			if (obj.locked) {
				if (this.margin !== false) {
					$('*').filter(function(){
						return ($(this).css('position') === 'fixed' && !$(this).hasClass("fancybox-overlay") && !$(this).hasClass("fancybox-wrap") );
					}).addClass('fancybox-margin');

					this.el.addClass('fancybox-margin');
				}

				scrollV = W.scrollTop();
				scrollH = W.scrollLeft();

				this.el.addClass('fancybox-lock');

				W.scrollTop( scrollV ).scrollLeft( scrollH );
			}

			this.open(opts);
		},

		onUpdate : function() {
			if (!this.fixed) {
				this.update();
			}
		},

		afterClose: function (opts) {
			// Remove overlay if exists and fancyBox is not opening
			// (e.g., it is not being open using afterClose callback)
			//if (this.overlay && !F.isActive) {
			if (this.overlay && !F.coming) {
				this.overlay.fadeOut(opts.speedOut, $.proxy( this.close, this ));
			}
		}
	};

	/*
	 *	Title helper
	 */

	F.helpers.title = {
		defaults : {
			type     : 'float', // 'float', 'inside', 'outside' or 'over',
			position : 'bottom' // 'top' or 'bottom'
		},

		beforeShow: function (opts) {
			var current = F.current,
				text    = current.title,
				type    = opts.type,
				title,
				target;

			if ($.isFunction(text)) {
				text = text.call(current.element, current);
			}

			if (!isString(text) || $.trim(text) === '') {
				return;
			}

			title = $('<div class="fancybox-title fancybox-title-' + type + '-wrap">' + text + '</div>');

			switch (type) {
				case 'inside':
					target = F.skin;
				break;

				case 'outside':
					target = F.wrap;
				break;

				case 'over':
					target = F.inner;
				break;

				default: // 'float'
					target = F.skin;

					title.appendTo('body');

					if (IE) {
						title.width( title.width() );
					}

					title.wrapInner('<span class="child"></span>');

					//Increase bottom margin so this title will also fit into viewport
					F.current.margin[2] += Math.abs( getScalar(title.css('margin-bottom')) );
				break;
			}

			title[ (opts.position === 'top' ? 'prependTo'  : 'appendTo') ](target);
		}
	};

	// jQuery plugin initialization
	$.fn.fancybox = function (options) {
		var index,
			that     = $(this),
			selector = this.selector || '',
			run      = function(e) {
				var what = $(this).blur(), idx = index, relType, relVal;

				if (!(e.ctrlKey || e.altKey || e.shiftKey || e.metaKey) && !what.is('.fancybox-wrap')) {
					relType = options.groupAttr || 'data-fancybox-group';
					relVal  = what.attr(relType);

					if (!relVal) {
						relType = 'rel';
						relVal  = what.get(0)[ relType ];
					}

					if (relVal && relVal !== '' && relVal !== 'nofollow') {
						what = selector.length ? $(selector) : that;
						what = what.filter('[' + relType + '="' + relVal + '"]');
						idx  = what.index(this);
					}

					options.index = idx;

					// Stop an event from bubbling if everything is fine
					if (F.open(what, options) !== false) {
						e.preventDefault();
					}
				}
			};

		options = options || {};
		index   = options.index || 0;

		if (!selector || options.live === false) {
			that.unbind('click.fb-start').bind('click.fb-start', run);

		} else {
			D.undelegate(selector, 'click.fb-start').delegate(selector + ":not('.fancybox-item, .fancybox-nav')", 'click.fb-start', run);
		}

		this.filter('[data-fancybox-start=1]').trigger('click');

		return this;
	};

	// Tests that need a body at doc ready
	D.ready(function() {
		var w1, w2;

		if ( $.scrollbarWidth === undefined ) {
			// http://benalman.com/projects/jquery-misc-plugins/#scrollbarwidth
			$.scrollbarWidth = function() {
				var parent = $('<div style="width:50px;height:50px;overflow:auto"><div/></div>').appendTo('body'),
					child  = parent.children(),
					width  = child.innerWidth() - child.height( 99 ).innerWidth();

				parent.remove();

				return width;
			};
		}

		if ( $.support.fixedPosition === undefined ) {
			$.support.fixedPosition = (function() {
				var elem  = $('<div style="position:fixed;top:20px;"></div>').appendTo('body'),
					fixed = ( elem[0].offsetTop === 20 || elem[0].offsetTop === 15 );

				elem.remove();

				return fixed;
			}());
		}

		$.extend(F.defaults, {
			scrollbarWidth : $.scrollbarWidth(),
			fixed  : $.support.fixedPosition,
			parent : $('body')
		});

		//Get real width of page scroll-bar
		w1 = $(window).width();

		H.addClass('fancybox-lock-test');

		w2 = $(window).width();

		H.removeClass('fancybox-lock-test');

		$("<style type='text/css'>.fancybox-margin{margin-right:" + (w2 - w1) + "px;}</style>").appendTo("head");
	});

}(window, document, jQuery));;
/*
 * upclick(params)
 *
 *  parameters:
 *      element:        DOM object
 *      action:         Server script who receive file
 *      action_params:  Server script parameters. Array: key=value
 *      dataname:       File data name. Default: Filedata
 *      maxsize:        Maximum file size (in Bytes). 0 - unlimited
 *      target:         Response target name: '_new', '_blank',... Default: <Hidden frame name>
 *      zindex:         z-index listener
 *      onstart:        Callback function
 *                        onstart(filename).
 *                        Emited when file started upload
 *      oncomplete:      Callback function
 *                        oncomplete(server_response_data).
 *                        Emited when file successfully onloaded
 */
function upclick(params) {
    // Parse params
    var defaults =
        {
            element: null,
            action: 'about:blank',
            action_params: {},
            maxsize: 0,
            onstart: null,
            oncomplete: null,
            dataname: 'Filedata',
            target: null,
            zindex: 'auto',
            accept: "*",
            containerClass:"upclick-container"
        };

    for (var key in defaults) {
        params[key] = params[key] ? params[key] : defaults[key];
    }


    var element = params['element'];
    if (typeof element == 'string')
        element = document.getElementById(element);

    var doc = element.ownerDocument;
    var input;

    // div
    var container = doc.createElement("div");
    $(container).addClass(params.containerClass);

    // frame -> div
    var frame_name = 'frame' + new Date().getTime().toString().substr(8);

    // IE require such creation method
    container.innerHTML =
        '<iframe name="' + frame_name + '" src="about:blank" onload="this.onload_callback()"></iframe>';
    var frame = container.childNodes[0];

    // Callback for 'onload' event. Fired when form submited and data retrived
    frame.onload_callback =
        function () {
            // Phase 1. First 'onload' when element created
            // form -> div
            var form = doc.createElement('form');
            container.appendChild(form);
            form.method = 'post';
            form.enctype = 'multipart/form-data';
            form.encoding = 'multipart/form-data';
            if (params['target']) {
                form.target = params['target'];
                form.setAttribute('target', params['target']);
            }
            else {
                form.target = frame_name;
                form.setAttribute('target', frame_name);
            }
            form.action = params['action'];
            form.setAttribute('action', params['action']);
            form.style.margin = 0;
            form.style.padding = 0;
            form.style.height = '80px';
            form.style.width = '40px';
            form.runat = 'server';

            // append params in form
            var action_params = params['action_params'];
            for (var key in action_params) {
                var hidden = doc.createElement("input");
                hidden.type = "hidden";
                hidden.name = key;
                hidden.value = String(action_params[key]);
                form.appendChild(hidden);
            }

            // MAX_FILESIZE. Maximum file size
            if (params['maxsize']) {
                var input_ms = doc.createElement('input');
                input_ms.type = 'hidden';
                input_ms.name = 'MAX_FILE_SIZE';
                input_ms.value = String(params['maxsize']);
                form.appendChild(input_ms);
            }

            // input -> form
            input = doc.createElement("input");
            input.name = params['dataname'];
            input.type = 'file';
            input.size = '1';
            input.accept = params["accept"];
            input.runat = 'server';
            form.appendChild(input);

            // input style
            input.style.position = 'absolute';
            input.style.display = 'block';
            input.style.top = 0;
            input.style.left = 0;
            input.style.height = form.style.height;
            input.style.width = '80px';
            input.style.opacity = 0;
            input.style.filter = 'alpha(opacity=0)';
            input.style.fontSize = 8;
            input.style.zIndex = 1;
            input.style.visiblity = 'hidden';
            input.style.marginLeft = '-40px'; // hide IE text field
            //input.style.cursor = 'pointer';
    
            if (params['multiple']) {
                input.multiple = 'multiple';
            }
            // input click handler (enable container event listener)

            // 'change' event handler. Submit form
            var onchange_callback =
                function (e) {
                    // empty filename check
                    if (!input.value)
                        return;

                    // Run onstart callback. When upload started
                    var onstart = params['onstart'];
                    if (onstart)
                        onstart(input.value);

                    form.submit();
                };

            // bind 'change' callback
            // DOM2: FF, Opera, Chrome
            if (input.addEventListener)
                input.addEventListener('change', onchange_callback, false);

                // IE 5+
            else if (input.attachEvent) {
                input.attachEvent(
                    'onpropertychange',
                    function (e) {
                        // Get event details for IE
                        if (!e)
                            e = window.event;

                        if (e.propertyName == 'value')
                            onchange_callback();
                    }
                    );
            }
                // IE 4
            else
                input.onpropertychange = onchange_callback;


            // Phase 2. Next 'onload' when data received from server
            frame.onload_callback =
                function () {
                    var ifwin = null;

                    // Get frame window
                    // IE5.5+, Mozilla, NN7
                    if (frame.contentWindow)
                        ifwin = frame.contentWindow;
                        // NN6, Konqueror
                    else if (frame.contentDocument)
                        ifwin = frame.contentDocument.defaultView;

                    // Run 'oncomplete' callback
                    var data = ifwin.document.body.innerHTML;
                    var oncomplete = params['oncomplete'];
                    if (oncomplete)
                        oncomplete(data);

                    // Clear filename
                    form.reset();
                }
        };

    // frame style
    frame.style.display = 'none';
    frame.width = 0;
    frame.height = 0;
    frame.marginHeight = 0;
    frame.marginWidth = 0;

    // container -> DOM
    doc.body.insertBefore(container, doc.body.firstChild);

    // container style
    container.style.position = 'absolute';
    container.style.overflow = 'hidden';
    container.style.padding = 0;
    container.style.margin = 0;
    container.style.visiblity = 'hidden';
    container.style.width = '0px';
    container.style.height = '0px';
    //container.style.cursor = 'pointer'; // FIXME flashed on IE

    // zindex detection
    if (params['zindex'] == 'auto') {
        var zi = 0, ziparsed;
        var obj = element;
        var comp;

        while (obj.tagName != 'BODY') {
            comp = obj.currentStyle ? obj.currentStyle : getComputedStyle(obj, null);
            ziparsed = parseInt(comp.zIndex);
            ziparsed = isNaN(ziparsed) ? 0 : ziparsed;
            zi += ziparsed + 1;
            obj = obj.parentNode
        }

        container.style.zIndex = zi;
    }
    else {
        container.style.zIndex = params['zindex'];
    }

    var idElement = params['element'];

    // hover style for button upload
    var hoverStyle = function () {
        $("#" + idElement).css({
            "text-decoration": "none",
            "color": "#ffffff",
            "box-shadow": "0 3px 10px 0 rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(0, 0, 0, 0.1)",
            "opacity": "0.8"
        });
    }

    // If cursor out of element => shitch off listener
    var onmouseout_callback =
    function (e) {
        if (!e)
            e = window.event;

        $("#" + idElement).css({
            "color": "#ffffff",
            "box-shadow": "0 2px 3px 0 rgba(0,0,0,0.16), 0 0 0 1px rgba(0,0,0,0.08)",
            "opacity": "1"
        });

        container.style.width = '0px';
        container.style.height = '0px';
        var receiver = doc.elementFromPoint(e.clientX, e.clientY);

        if (receiver === element) {
            container.style.width = '40px';
            container.style.height = '80px';
        }
    }
    // DOM2: FF, Chrome, Opera
    if (container.addEventListener)
        container.addEventListener('mousemove', onmouseout_callback, false);
        // IE 5+
    else if (container.attachEvent)
        container.attachEvent("onmousemove", onmouseout_callback);


    // Move the input with the mouse to make sure it get clicked!
    var onmousemove_callback =
        function (e) {
            // Get event details for IE
            if (!e)
                e = window.event;

            hoverStyle();

            // find element position,
            var x = y = 0;

            if (e.pageX)
                x = e.pageX;
            else if (e.clientX)
                x = e.clientX +
                 (doc.documentElement.scrollLeft ?
                     doc.documentElement.scrollLeft :
                     doc.body.scrollLeft);

            if (e.pageY)
                y = e.pageY;
            else if (e.clientY)
                y = e.clientY +
                     (doc.documentElement.scrollTop ?
                         doc.documentElement.scrollTop :
                         doc.body.scrollTop);

            // move listener
            container.style.left = x - 20 + 'px';
            container.style.top = y - 40 + 'px';
            container.style.width = '40px';
            container.style.height = '80px';
        };

    // bind mousemove callback (for place button under cursor)
    // DOM2: FF, Chrome, Opera
    if (element.addEventListener)
        element.addEventListener('mousemove', onmousemove_callback, false);
        // IE 5+
    else if (element.attachEvent)
        element.attachEvent("onmousemove", onmousemove_callback);
}
;
/**
 * Swiper 3.4.1
 * Most modern mobile touch slider and framework with hardware accelerated transitions
 * 
 * http://www.idangero.us/swiper/
 * 
 * Copyright 2016, Vladimir Kharlampidi
 * The iDangero.us
 * http://www.idangero.us/
 * 
 * Licensed under MIT
 * 
 * Released on: December 13, 2016
 */
(function () {
    'use strict';
    var $;
    /*===========================
    Swiper
    ===========================*/
    var Swiper = function (container, params) {
        if (!(this instanceof Swiper)) return new Swiper(container, params);

        var defaults = {
            direction: 'horizontal',
            touchEventsTarget: 'container',
            initialSlide: 0,
            speed: 300,
            // autoplay
            autoplay: false,
            autoplayDisableOnInteraction: true,
            autoplayStopOnLast: false,
            // To support iOS's swipe-to-go-back gesture (when being used in-app, with UIWebView).
            iOSEdgeSwipeDetection: false,
            iOSEdgeSwipeThreshold: 20,
            // Free mode
            freeMode: false,
            freeModeMomentum: true,
            freeModeMomentumRatio: 1,
            freeModeMomentumBounce: true,
            freeModeMomentumBounceRatio: 1,
            freeModeMomentumVelocityRatio: 1,
            freeModeSticky: false,
            freeModeMinimumVelocity: 0.02,
            // Autoheight
            autoHeight: false,
            // Set wrapper width
            setWrapperSize: false,
            // Virtual Translate
            virtualTranslate: false,
            // Effects
            effect: 'slide', // 'slide' or 'fade' or 'cube' or 'coverflow' or 'flip'
            coverflow: {
                rotate: 50,
                stretch: 0,
                depth: 100,
                modifier: 1,
                slideShadows : true
            },
            flip: {
                slideShadows : true,
                limitRotation: true
            },
            cube: {
                slideShadows: true,
                shadow: true,
                shadowOffset: 20,
                shadowScale: 0.94
            },
            fade: {
                crossFade: false
            },
            // Parallax
            parallax: false,
            // Zoom
            zoom: false,
            zoomMax: 3,
            zoomMin: 1,
            zoomToggle: true,
            // Scrollbar
            scrollbar: null,
            scrollbarHide: true,
            scrollbarDraggable: false,
            scrollbarSnapOnRelease: false,
            // Keyboard Mousewheel
            keyboardControl: false,
            mousewheelControl: false,
            mousewheelReleaseOnEdges: false,
            mousewheelInvert: false,
            mousewheelForceToAxis: false,
            mousewheelSensitivity: 1,
            mousewheelEventsTarged: 'container',
            // Hash Navigation
            hashnav: false,
            hashnavWatchState: false,
            // History
            history: false,
            // Commong Nav State
            replaceState: false,
            // Breakpoints
            breakpoints: undefined,
            // Slides grid
            spaceBetween: 0,
            slidesPerView: 1,
            slidesPerColumn: 1,
            slidesPerColumnFill: 'column',
            slidesPerGroup: 1,
            centeredSlides: false,
            slidesOffsetBefore: 0, // in px
            slidesOffsetAfter: 0, // in px
            // Round length
            roundLengths: false,
            // Touches
            touchRatio: 1,
            touchAngle: 45,
            simulateTouch: true,
            shortSwipes: true,
            longSwipes: true,
            longSwipesRatio: 0.5,
            longSwipesMs: 300,
            followFinger: true,
            onlyExternal: false,
            threshold: 0,
            touchMoveStopPropagation: true,
            touchReleaseOnEdges: false,
            // Unique Navigation Elements
            uniqueNavElements: true,
            // Pagination
            pagination: null,
            paginationElement: 'span',
            paginationClickable: false,
            paginationHide: false,
            paginationBulletRender: null,
            paginationProgressRender: null,
            paginationFractionRender: null,
            paginationCustomRender: null,
            paginationType: 'bullets', // 'bullets' or 'progress' or 'fraction' or 'custom'
            // Resistance
            resistance: true,
            resistanceRatio: 0.85,
            // Next/prev buttons
            nextButton: null,
            prevButton: null,
            // Progress
            watchSlidesProgress: false,
            watchSlidesVisibility: false,
            // Cursor
            grabCursor: false,
            // Clicks
            preventClicks: true,
            preventClicksPropagation: true,
            slideToClickedSlide: false,
            // Lazy Loading
            lazyLoading: false,
            lazyLoadingInPrevNext: false,
            lazyLoadingInPrevNextAmount: 1,
            lazyLoadingOnTransitionStart: false,
            // Images
            preloadImages: true,
            updateOnImagesReady: true,
            // loop
            loop: false,
            loopAdditionalSlides: 0,
            loopedSlides: null,
            // Control
            control: undefined,
            controlInverse: false,
            controlBy: 'slide', //or 'container'
            normalizeSlideIndex: true,
            // Swiping/no swiping
            allowSwipeToPrev: true,
            allowSwipeToNext: true,
            swipeHandler: null, //'.swipe-handler',
            noSwiping: true,
            noSwipingClass: 'swiper-no-swiping',
            // Passive Listeners
            passiveListeners: true,
            // NS
            containerModifierClass: 'swiper-container-', // NEW
            slideClass: 'swiper-slide',
            slideActiveClass: 'swiper-slide-active',
            slideDuplicateActiveClass: 'swiper-slide-duplicate-active',
            slideVisibleClass: 'swiper-slide-visible',
            slideDuplicateClass: 'swiper-slide-duplicate',
            slideNextClass: 'swiper-slide-next',
            slideDuplicateNextClass: 'swiper-slide-duplicate-next',
            slidePrevClass: 'swiper-slide-prev',
            slideDuplicatePrevClass: 'swiper-slide-duplicate-prev',
            wrapperClass: 'swiper-wrapper',
            bulletClass: 'swiper-pagination-bullet',
            bulletActiveClass: 'swiper-pagination-bullet-active',
            buttonDisabledClass: 'swiper-button-disabled',
            paginationCurrentClass: 'swiper-pagination-current',
            paginationTotalClass: 'swiper-pagination-total',
            paginationHiddenClass: 'swiper-pagination-hidden',
            paginationProgressbarClass: 'swiper-pagination-progressbar',
            paginationClickableClass: 'swiper-pagination-clickable', // NEW
            paginationModifierClass: 'swiper-pagination-', // NEW
            lazyLoadingClass: 'swiper-lazy',
            lazyStatusLoadingClass: 'swiper-lazy-loading',
            lazyStatusLoadedClass: 'swiper-lazy-loaded',
            lazyPreloaderClass: 'swiper-lazy-preloader',
            notificationClass: 'swiper-notification',
            preloaderClass: 'preloader',
            zoomContainerClass: 'swiper-zoom-container',
        
            // Observer
            observer: false,
            observeParents: false,
            // Accessibility
            a11y: false,
            prevSlideMessage: 'Previous slide',
            nextSlideMessage: 'Next slide',
            firstSlideMessage: 'This is the first slide',
            lastSlideMessage: 'This is the last slide',
            paginationBulletMessage: 'Go to slide {{index}}',
            // Callbacks
            runCallbacksOnInit: true
            /*
            Callbacks:
            onInit: function (swiper)
            onDestroy: function (swiper)
            onClick: function (swiper, e)
            onTap: function (swiper, e)
            onDoubleTap: function (swiper, e)
            onSliderMove: function (swiper, e)
            onSlideChangeStart: function (swiper)
            onSlideChangeEnd: function (swiper)
            onTransitionStart: function (swiper)
            onTransitionEnd: function (swiper)
            onImagesReady: function (swiper)
            onProgress: function (swiper, progress)
            onTouchStart: function (swiper, e)
            onTouchMove: function (swiper, e)
            onTouchMoveOpposite: function (swiper, e)
            onTouchEnd: function (swiper, e)
            onReachBeginning: function (swiper)
            onReachEnd: function (swiper)
            onSetTransition: function (swiper, duration)
            onSetTranslate: function (swiper, translate)
            onAutoplayStart: function (swiper)
            onAutoplayStop: function (swiper),
            onLazyImageLoad: function (swiper, slide, image)
            onLazyImageReady: function (swiper, slide, image)
            */
        
        };
        var initialVirtualTranslate = params && params.virtualTranslate;
        
        params = params || {};
        var originalParams = {};
        for (var param in params) {
            if (typeof params[param] === 'object' && params[param] !== null && !(params[param].nodeType || params[param] === window || params[param] === document || (typeof Dom7 !== 'undefined' && params[param] instanceof Dom7) || (typeof jQuery !== 'undefined' && params[param] instanceof jQuery))) {
                originalParams[param] = {};
                for (var deepParam in params[param]) {
                    originalParams[param][deepParam] = params[param][deepParam];
                }
            }
            else {
                originalParams[param] = params[param];
            }
        }
        for (var def in defaults) {
            if (typeof params[def] === 'undefined') {
                params[def] = defaults[def];
            }
            else if (typeof params[def] === 'object') {
                for (var deepDef in defaults[def]) {
                    if (typeof params[def][deepDef] === 'undefined') {
                        params[def][deepDef] = defaults[def][deepDef];
                    }
                }
            }
        }
        
        // Swiper
        var s = this;
        
        // Params
        s.params = params;
        s.originalParams = originalParams;
        
        // Classname
        s.classNames = [];
        /*=========================
          Dom Library and plugins
          ===========================*/
        if (typeof $ !== 'undefined' && typeof Dom7 !== 'undefined'){
            $ = Dom7;
        }
        if (typeof $ === 'undefined') {
            if (typeof Dom7 === 'undefined') {
                $ = window.Dom7 || window.Zepto || window.jQuery;
            }
            else {
                $ = Dom7;
            }
            if (!$) return;
        }
        // Export it to Swiper instance
        s.$ = $;
        
        /*=========================
          Breakpoints
          ===========================*/
        s.currentBreakpoint = undefined;
        s.getActiveBreakpoint = function () {
            //Get breakpoint for window width
            if (!s.params.breakpoints) return false;
            var breakpoint = false;
            var points = [], point;
            for ( point in s.params.breakpoints ) {
                if (s.params.breakpoints.hasOwnProperty(point)) {
                    points.push(point);
                }
            }
            points.sort(function (a, b) {
                return parseInt(a, 10) > parseInt(b, 10);
            });
            for (var i = 0; i < points.length; i++) {
                point = points[i];
                if (point >= window.innerWidth && !breakpoint) {
                    breakpoint = point;
                }
            }
            return breakpoint || 'max';
        };
        s.setBreakpoint = function () {
            //Set breakpoint for window width and update parameters
            var breakpoint = s.getActiveBreakpoint();
            if (breakpoint && s.currentBreakpoint !== breakpoint) {
                var breakPointsParams = breakpoint in s.params.breakpoints ? s.params.breakpoints[breakpoint] : s.originalParams;
                var needsReLoop = s.params.loop && (breakPointsParams.slidesPerView !== s.params.slidesPerView);
                for ( var param in breakPointsParams ) {
                    s.params[param] = breakPointsParams[param];
                }
                s.currentBreakpoint = breakpoint;
                if(needsReLoop && s.destroyLoop) {
                    s.reLoop(true);
                }
            }
        };
        // Set breakpoint on load
        if (s.params.breakpoints) {
            s.setBreakpoint();
        }
        
        /*=========================
          Preparation - Define Container, Wrapper and Pagination
          ===========================*/
        s.container = $(container);
        if (s.container.length === 0) return;
        if (s.container.length > 1) {
            var swipers = [];
            s.container.each(function () {
                var container = this;
                swipers.push(new Swiper(this, params));
            });
            return swipers;
        }
        
        // Save instance in container HTML Element and in data
        s.container[0].swiper = s;
        s.container.data('swiper', s);
        
        s.classNames.push(s.params.containerModifierClass + s.params.direction);
        
        if (s.params.freeMode) {
            s.classNames.push(s.params.containerModifierClass + 'free-mode');
        }
        if (!s.support.flexbox) {
            s.classNames.push(s.params.containerModifierClass + 'no-flexbox');
            s.params.slidesPerColumn = 1;
        }
        if (s.params.autoHeight) {
            s.classNames.push(s.params.containerModifierClass + 'autoheight');
        }
        // Enable slides progress when required
        if (s.params.parallax || s.params.watchSlidesVisibility) {
            s.params.watchSlidesProgress = true;
        }
        // Max resistance when touchReleaseOnEdges
        if (s.params.touchReleaseOnEdges) {
            s.params.resistanceRatio = 0;
        }
        // Coverflow / 3D
        if (['cube', 'coverflow', 'flip'].indexOf(s.params.effect) >= 0) {
            if (s.support.transforms3d) {
                s.params.watchSlidesProgress = true;
                s.classNames.push(s.params.containerModifierClass + '3d');
            }
            else {
                s.params.effect = 'slide';
            }
        }
        if (s.params.effect !== 'slide') {
            s.classNames.push(s.params.containerModifierClass + s.params.effect);
        }
        if (s.params.effect === 'cube') {
            s.params.resistanceRatio = 0;
            s.params.slidesPerView = 1;
            s.params.slidesPerColumn = 1;
            s.params.slidesPerGroup = 1;
            s.params.centeredSlides = false;
            s.params.spaceBetween = 0;
            s.params.virtualTranslate = true;
            s.params.setWrapperSize = false;
        }
        if (s.params.effect === 'fade' || s.params.effect === 'flip') {
            s.params.slidesPerView = 1;
            s.params.slidesPerColumn = 1;
            s.params.slidesPerGroup = 1;
            s.params.watchSlidesProgress = true;
            s.params.spaceBetween = 0;
            s.params.setWrapperSize = false;
            if (typeof initialVirtualTranslate === 'undefined') {
                s.params.virtualTranslate = true;
            }
        }
        
        // Grab Cursor
        if (s.params.grabCursor && s.support.touch) {
            s.params.grabCursor = false;
        }
        
        // Wrapper
        s.wrapper = s.container.children('.' + s.params.wrapperClass);
        
        // Pagination
        if (s.params.pagination) {
            s.paginationContainer = $(s.params.pagination);
            if (s.params.uniqueNavElements && typeof s.params.pagination === 'string' && s.paginationContainer.length > 1 && s.container.find(s.params.pagination).length === 1) {
                s.paginationContainer = s.container.find(s.params.pagination);
            }
        
            if (s.params.paginationType === 'bullets' && s.params.paginationClickable) {
                s.paginationContainer.addClass(s.params.paginationModifierClass + 'clickable');
            }
            else {
                s.params.paginationClickable = false;
            }
            s.paginationContainer.addClass(s.params.paginationModifierClass + s.params.paginationType);
        }
        // Next/Prev Buttons
        if (s.params.nextButton || s.params.prevButton) {
            if (s.params.nextButton) {
                s.nextButton = $(s.params.nextButton);
                if (s.params.uniqueNavElements && typeof s.params.nextButton === 'string' && s.nextButton.length > 1 && s.container.find(s.params.nextButton).length === 1) {
                    s.nextButton = s.container.find(s.params.nextButton);
                }
            }
            if (s.params.prevButton) {
                s.prevButton = $(s.params.prevButton);
                if (s.params.uniqueNavElements && typeof s.params.prevButton === 'string' && s.prevButton.length > 1 && s.container.find(s.params.prevButton).length === 1) {
                    s.prevButton = s.container.find(s.params.prevButton);
                }
            }
        }
        
        // Is Horizontal
        s.isHorizontal = function () {
            return s.params.direction === 'horizontal';
        };
        // s.isH = isH;
        
        // RTL
        s.rtl = s.isHorizontal() && (s.container[0].dir.toLowerCase() === 'rtl' || s.container.css('direction') === 'rtl');
        if (s.rtl) {
            s.classNames.push(s.params.containerModifierClass + 'rtl');
        }
        
        // Wrong RTL support
        if (s.rtl) {
            s.wrongRTL = s.wrapper.css('display') === '-webkit-box';
        }
        
        // Columns
        if (s.params.slidesPerColumn > 1) {
            s.classNames.push(s.params.containerModifierClass + 'multirow');
        }
        
        // Check for Android
        if (s.device.android) {
            s.classNames.push(s.params.containerModifierClass + 'android');
        }
        
        // Add classes
        s.container.addClass(s.classNames.join(' '));
        
        // Translate
        s.translate = 0;
        
        // Progress
        s.progress = 0;
        
        // Velocity
        s.velocity = 0;
        
        /*=========================
          Locks, unlocks
          ===========================*/
        s.lockSwipeToNext = function () {
            s.params.allowSwipeToNext = false;
            if (s.params.allowSwipeToPrev === false && s.params.grabCursor) {
                s.unsetGrabCursor();
            }
        };
        s.lockSwipeToPrev = function () {
            s.params.allowSwipeToPrev = false;
            if (s.params.allowSwipeToNext === false && s.params.grabCursor) {
                s.unsetGrabCursor();
            }
        };
        s.lockSwipes = function () {
            s.params.allowSwipeToNext = s.params.allowSwipeToPrev = false;
            if (s.params.grabCursor) s.unsetGrabCursor();
        };
        s.unlockSwipeToNext = function () {
            s.params.allowSwipeToNext = true;
            if (s.params.allowSwipeToPrev === true && s.params.grabCursor) {
                s.setGrabCursor();
            }
        };
        s.unlockSwipeToPrev = function () {
            s.params.allowSwipeToPrev = true;
            if (s.params.allowSwipeToNext === true && s.params.grabCursor) {
                s.setGrabCursor();
            }
        };
        s.unlockSwipes = function () {
            s.params.allowSwipeToNext = s.params.allowSwipeToPrev = true;
            if (s.params.grabCursor) s.setGrabCursor();
        };
        
        /*=========================
          Round helper
          ===========================*/
        function round(a) {
            return Math.floor(a);
        }
        /*=========================
          Set grab cursor
          ===========================*/
        s.setGrabCursor = function(moving) {
            s.container[0].style.cursor = 'move';
            s.container[0].style.cursor = moving ? '-webkit-grabbing' : '-webkit-grab';
            s.container[0].style.cursor = moving ? '-moz-grabbin' : '-moz-grab';
            s.container[0].style.cursor = moving ? 'grabbing': 'grab';
        };
        s.unsetGrabCursor = function () {
            s.container[0].style.cursor = '';
        };
        if (s.params.grabCursor) {
            s.setGrabCursor();
        }
        /*=========================
          Update on Images Ready
          ===========================*/
        s.imagesToLoad = [];
        s.imagesLoaded = 0;
        
        s.loadImage = function (imgElement, src, srcset, sizes, checkForComplete, callback) {
            var image;
            function onReady () {
                if (callback) callback();
            }
            if (!imgElement.complete || !checkForComplete) {
                if (src) {
                    image = new window.Image();
                    image.onload = onReady;
                    image.onerror = onReady;
                    if (sizes) {
                        image.sizes = sizes;
                    }
                    if (srcset) {
                        image.srcset = srcset;
                    }
                    if (src) {
                        image.src = src;
                    }
                } else {
                    onReady();
                }
        
            } else {//image already loaded...
                onReady();
            }
        };
        s.preloadImages = function () {
            s.imagesToLoad = s.container.find('img');
            function _onReady() {
                if (typeof s === 'undefined' || s === null || !s) return;
                if (s.imagesLoaded !== undefined) s.imagesLoaded++;
                if (s.imagesLoaded === s.imagesToLoad.length) {
                    if (s.params.updateOnImagesReady) s.update();
                    s.emit('onImagesReady', s);
                }
            }
            for (var i = 0; i < s.imagesToLoad.length; i++) {
                s.loadImage(s.imagesToLoad[i], (s.imagesToLoad[i].currentSrc || s.imagesToLoad[i].getAttribute('src')), (s.imagesToLoad[i].srcset || s.imagesToLoad[i].getAttribute('srcset')), s.imagesToLoad[i].sizes || s.imagesToLoad[i].getAttribute('sizes'), true, _onReady);
            }
        };
        
        /*=========================
          Autoplay
          ===========================*/
        s.autoplayTimeoutId = undefined;
        s.autoplaying = false;
        s.autoplayPaused = false;
        function autoplay() {
            var autoplayDelay = s.params.autoplay;
            var activeSlide = s.slides.eq(s.activeIndex);
            if (activeSlide.attr('data-swiper-autoplay')) {
                autoplayDelay = activeSlide.attr('data-swiper-autoplay') || s.params.autoplay;
            }
            s.autoplayTimeoutId = setTimeout(function () {
                if (s.params.loop) {
                    s.fixLoop();
                    s._slideNext();
                    s.emit('onAutoplay', s);
                }
                else {
                    if (!s.isEnd) {
                        s._slideNext();
                        s.emit('onAutoplay', s);
                    }
                    else {
                        if (!params.autoplayStopOnLast) {
                            s._slideTo(0);
                            s.emit('onAutoplay', s);
                        }
                        else {
                            s.stopAutoplay();
                        }
                    }
                }
            }, autoplayDelay);
        }
        s.startAutoplay = function () {
            if (typeof s.autoplayTimeoutId !== 'undefined') return false;
            if (!s.params.autoplay) return false;
            if (s.autoplaying) return false;
            s.autoplaying = true;
            s.emit('onAutoplayStart', s);
            autoplay();
        };
        s.stopAutoplay = function (internal) {
            if (!s.autoplayTimeoutId) return;
            if (s.autoplayTimeoutId) clearTimeout(s.autoplayTimeoutId);
            s.autoplaying = false;
            s.autoplayTimeoutId = undefined;
            s.emit('onAutoplayStop', s);
        };
        s.pauseAutoplay = function (speed) {
            if (s.autoplayPaused) return;
            if (s.autoplayTimeoutId) clearTimeout(s.autoplayTimeoutId);
            s.autoplayPaused = true;
            if (speed === 0) {
                s.autoplayPaused = false;
                autoplay();
            }
            else {
                s.wrapper.transitionEnd(function () {
                    if (!s) return;
                    s.autoplayPaused = false;
                    if (!s.autoplaying) {
                        s.stopAutoplay();
                    }
                    else {
                        autoplay();
                    }
                });
            }
        };
        /*=========================
          Min/Max Translate
          ===========================*/
        s.minTranslate = function () {
            return (-s.snapGrid[0]);
        };
        s.maxTranslate = function () {
            return (-s.snapGrid[s.snapGrid.length - 1]);
        };
        /*=========================
          Slider/slides sizes
          ===========================*/
        s.updateAutoHeight = function () {
            var activeSlides = [];
            var newHeight = 0;
            var i;
        
            // Find slides currently in view
            if(s.params.slidesPerView !== 'auto' && s.params.slidesPerView > 1) {
                for (i = 0; i < Math.ceil(s.params.slidesPerView); i++) {
                    var index = s.activeIndex + i;
                    if(index > s.slides.length) break;
                    activeSlides.push(s.slides.eq(index)[0]);
                }
            } else {
                activeSlides.push(s.slides.eq(s.activeIndex)[0]);
            }
        
            // Find new height from heighest slide in view
            for (i = 0; i < activeSlides.length; i++) {
                if (typeof activeSlides[i] !== 'undefined') {
                    var height = activeSlides[i].offsetHeight;
                    newHeight = height > newHeight ? height : newHeight;
                }
            }
        
            // Update Height
            if (newHeight) s.wrapper.css('height', newHeight + 'px');
        };
        s.updateContainerSize = function () {
            var width, height;
            if (typeof s.params.width !== 'undefined') {
                width = s.params.width;
            }
            else {
                width = s.container[0].clientWidth;
            }
            if (typeof s.params.height !== 'undefined') {
                height = s.params.height;
            }
            else {
                height = s.container[0].clientHeight;
            }
            if (width === 0 && s.isHorizontal() || height === 0 && !s.isHorizontal()) {
                return;
            }
        
            //Subtract paddings
            width = width - parseInt(s.container.css('padding-left'), 10) - parseInt(s.container.css('padding-right'), 10);
            height = height - parseInt(s.container.css('padding-top'), 10) - parseInt(s.container.css('padding-bottom'), 10);
        
            // Store values
            s.width = width;
            s.height = height;
            s.size = s.isHorizontal() ? s.width : s.height;
        };
        
        s.updateSlidesSize = function () {
            s.slides = s.wrapper.children('.' + s.params.slideClass);
            s.snapGrid = [];
            s.slidesGrid = [];
            s.slidesSizesGrid = [];
        
            var spaceBetween = s.params.spaceBetween,
                slidePosition = -s.params.slidesOffsetBefore,
                i,
                prevSlideSize = 0,
                index = 0;
            if (typeof s.size === 'undefined') return;
            if (typeof spaceBetween === 'string' && spaceBetween.indexOf('%') >= 0) {
                spaceBetween = parseFloat(spaceBetween.replace('%', '')) / 100 * s.size;
            }
        
            s.virtualSize = -spaceBetween;
            // reset margins
            if (s.rtl) s.slides.css({marginLeft: '', marginTop: ''});
            else s.slides.css({marginRight: '', marginBottom: ''});
        
            var slidesNumberEvenToRows;
            if (s.params.slidesPerColumn > 1) {
                if (Math.floor(s.slides.length / s.params.slidesPerColumn) === s.slides.length / s.params.slidesPerColumn) {
                    slidesNumberEvenToRows = s.slides.length;
                }
                else {
                    slidesNumberEvenToRows = Math.ceil(s.slides.length / s.params.slidesPerColumn) * s.params.slidesPerColumn;
                }
                if (s.params.slidesPerView !== 'auto' && s.params.slidesPerColumnFill === 'row') {
                    slidesNumberEvenToRows = Math.max(slidesNumberEvenToRows, s.params.slidesPerView * s.params.slidesPerColumn);
                }
            }
        
            // Calc slides
            var slideSize;
            var slidesPerColumn = s.params.slidesPerColumn;
            var slidesPerRow = slidesNumberEvenToRows / slidesPerColumn;
            var numFullColumns = slidesPerRow - (s.params.slidesPerColumn * slidesPerRow - s.slides.length);
            for (i = 0; i < s.slides.length; i++) {
                slideSize = 0;
                var slide = s.slides.eq(i);
                if (s.params.slidesPerColumn > 1) {
                    // Set slides order
                    var newSlideOrderIndex;
                    var column, row;
                    if (s.params.slidesPerColumnFill === 'column') {
                        column = Math.floor(i / slidesPerColumn);
                        row = i - column * slidesPerColumn;
                        if (column > numFullColumns || (column === numFullColumns && row === slidesPerColumn-1)) {
                            if (++row >= slidesPerColumn) {
                                row = 0;
                                column++;
                            }
                        }
                        newSlideOrderIndex = column + row * slidesNumberEvenToRows / slidesPerColumn;
                        slide
                            .css({
                                '-webkit-box-ordinal-group': newSlideOrderIndex,
                                '-moz-box-ordinal-group': newSlideOrderIndex,
                                '-ms-flex-order': newSlideOrderIndex,
                                '-webkit-order': newSlideOrderIndex,
                                'order': newSlideOrderIndex
                            });
                    }
                    else {
                        row = Math.floor(i / slidesPerRow);
                        column = i - row * slidesPerRow;
                    }
                    slide
                        .css(
                            'margin-' + (s.isHorizontal() ? 'top' : 'left'),
                            (row !== 0 && s.params.spaceBetween) && (s.params.spaceBetween + 'px')
                        )
                        .attr('data-swiper-column', column)
                        .attr('data-swiper-row', row);
        
                }
                if (slide.css('display') === 'none') continue;
                if (s.params.slidesPerView === 'auto') {
                    slideSize = s.isHorizontal() ? slide.outerWidth(true) : slide.outerHeight(true);
                    if (s.params.roundLengths) slideSize = round(slideSize);
                }
                else {
                    slideSize = (s.size - (s.params.slidesPerView - 1) * spaceBetween) / s.params.slidesPerView;
                    if (s.params.roundLengths) slideSize = round(slideSize);
        
                    if (s.isHorizontal()) {
                        s.slides[i].style.width = slideSize + 'px';
                    }
                    else {
                        s.slides[i].style.height = slideSize + 'px';
                    }
                }
                s.slides[i].swiperSlideSize = slideSize;
                s.slidesSizesGrid.push(slideSize);
        
        
                if (s.params.centeredSlides) {
                    slidePosition = slidePosition + slideSize / 2 + prevSlideSize / 2 + spaceBetween;
                    if (i === 0) slidePosition = slidePosition - s.size / 2 - spaceBetween;
                    if (Math.abs(slidePosition) < 1 / 1000) slidePosition = 0;
                    if ((index) % s.params.slidesPerGroup === 0) s.snapGrid.push(slidePosition);
                    s.slidesGrid.push(slidePosition);
                }
                else {
                    if ((index) % s.params.slidesPerGroup === 0) s.snapGrid.push(slidePosition);
                    s.slidesGrid.push(slidePosition);
                    slidePosition = slidePosition + slideSize + spaceBetween;
                }
        
                s.virtualSize += slideSize + spaceBetween;
        
                prevSlideSize = slideSize;
        
                index ++;
            }
            s.virtualSize = Math.max(s.virtualSize, s.size) + s.params.slidesOffsetAfter;
            var newSlidesGrid;
        
            if (
                s.rtl && s.wrongRTL && (s.params.effect === 'slide' || s.params.effect === 'coverflow')) {
                s.wrapper.css({width: s.virtualSize + s.params.spaceBetween + 'px'});
            }
            if (!s.support.flexbox || s.params.setWrapperSize) {
                if (s.isHorizontal()) s.wrapper.css({width: s.virtualSize + s.params.spaceBetween + 'px'});
                else s.wrapper.css({height: s.virtualSize + s.params.spaceBetween + 'px'});
            }
        
            if (s.params.slidesPerColumn > 1) {
                s.virtualSize = (slideSize + s.params.spaceBetween) * slidesNumberEvenToRows;
                s.virtualSize = Math.ceil(s.virtualSize / s.params.slidesPerColumn) - s.params.spaceBetween;
                if (s.isHorizontal()) s.wrapper.css({width: s.virtualSize + s.params.spaceBetween + 'px'});
                else s.wrapper.css({height: s.virtualSize + s.params.spaceBetween + 'px'});
                if (s.params.centeredSlides) {
                    newSlidesGrid = [];
                    for (i = 0; i < s.snapGrid.length; i++) {
                        if (s.snapGrid[i] < s.virtualSize + s.snapGrid[0]) newSlidesGrid.push(s.snapGrid[i]);
                    }
                    s.snapGrid = newSlidesGrid;
                }
            }
        
            // Remove last grid elements depending on width
            if (!s.params.centeredSlides) {
                newSlidesGrid = [];
                for (i = 0; i < s.snapGrid.length; i++) {
                    if (s.snapGrid[i] <= s.virtualSize - s.size) {
                        newSlidesGrid.push(s.snapGrid[i]);
                    }
                }
                s.snapGrid = newSlidesGrid;
                if (Math.floor(s.virtualSize - s.size) - Math.floor(s.snapGrid[s.snapGrid.length - 1]) > 1) {
                    s.snapGrid.push(s.virtualSize - s.size);
                }
            }
            if (s.snapGrid.length === 0) s.snapGrid = [0];
        
            if (s.params.spaceBetween !== 0) {
                if (s.isHorizontal()) {
                    if (s.rtl) s.slides.css({marginLeft: spaceBetween + 'px'});
                    else s.slides.css({marginRight: spaceBetween + 'px'});
                }
                else s.slides.css({marginBottom: spaceBetween + 'px'});
            }
            if (s.params.watchSlidesProgress) {
                s.updateSlidesOffset();
            }
        };
        s.updateSlidesOffset = function () {
            for (var i = 0; i < s.slides.length; i++) {
                s.slides[i].swiperSlideOffset = s.isHorizontal() ? s.slides[i].offsetLeft : s.slides[i].offsetTop;
            }
        };
        
        /*=========================
          Dynamic Slides Per View
          ===========================*/
        s.currentSlidesPerView = function () {
            var spv = 1, i, j;
            if (s.params.centeredSlides) {
                var size = s.slides[s.activeIndex].swiperSlideSize;
                var breakLoop;
                for (i = s.activeIndex + 1; i < s.slides.length; i++) {
                    if (s.slides[i] && !breakLoop) {
                        size += s.slides[i].swiperSlideSize;
                        spv ++;
                        if (size > s.size) breakLoop = true;
                    }
                }
                for (j = s.activeIndex - 1; j >= 0; j--) {
                    if (s.slides[j] && !breakLoop) {
                        size += s.slides[j].swiperSlideSize;
                        spv ++;
                        if (size > s.size) breakLoop = true;
                    }
                }
            }
            else {
                for (i = s.activeIndex + 1; i < s.slides.length; i++) {
                    if (s.slidesGrid[i] - s.slidesGrid[s.activeIndex] < s.size) {
                        spv++;
                    }
                }
            }
            return spv;
        };
        /*=========================
          Slider/slides progress
          ===========================*/
        s.updateSlidesProgress = function (translate) {
            if (typeof translate === 'undefined') {
                translate = s.translate || 0;
            }
            if (s.slides.length === 0) return;
            if (typeof s.slides[0].swiperSlideOffset === 'undefined') s.updateSlidesOffset();
        
            var offsetCenter = -translate;
            if (s.rtl) offsetCenter = translate;
        
            // Visible Slides
            s.slides.removeClass(s.params.slideVisibleClass);
            for (var i = 0; i < s.slides.length; i++) {
                var slide = s.slides[i];
                var slideProgress = (offsetCenter + (s.params.centeredSlides ? s.minTranslate() : 0) - slide.swiperSlideOffset) / (slide.swiperSlideSize + s.params.spaceBetween);
                if (s.params.watchSlidesVisibility) {
                    var slideBefore = -(offsetCenter - slide.swiperSlideOffset);
                    var slideAfter = slideBefore + s.slidesSizesGrid[i];
                    var isVisible =
                        (slideBefore >= 0 && slideBefore < s.size) ||
                        (slideAfter > 0 && slideAfter <= s.size) ||
                        (slideBefore <= 0 && slideAfter >= s.size);
                    if (isVisible) {
                        s.slides.eq(i).addClass(s.params.slideVisibleClass);
                    }
                }
                slide.progress = s.rtl ? -slideProgress : slideProgress;
            }
        };
        s.updateProgress = function (translate) {
            if (typeof translate === 'undefined') {
                translate = s.translate || 0;
            }
            var translatesDiff = s.maxTranslate() - s.minTranslate();
            var wasBeginning = s.isBeginning;
            var wasEnd = s.isEnd;
            if (translatesDiff === 0) {
                s.progress = 0;
                s.isBeginning = s.isEnd = true;
            }
            else {
                s.progress = (translate - s.minTranslate()) / (translatesDiff);
                s.isBeginning = s.progress <= 0;
                s.isEnd = s.progress >= 1;
            }
            if (s.isBeginning && !wasBeginning) s.emit('onReachBeginning', s);
            if (s.isEnd && !wasEnd) s.emit('onReachEnd', s);
        
            if (s.params.watchSlidesProgress) s.updateSlidesProgress(translate);
            s.emit('onProgress', s, s.progress);
        };
        s.updateActiveIndex = function () {
            var translate = s.rtl ? s.translate : -s.translate;
            var newActiveIndex, i, snapIndex;
            for (i = 0; i < s.slidesGrid.length; i ++) {
                if (typeof s.slidesGrid[i + 1] !== 'undefined') {
                    if (translate >= s.slidesGrid[i] && translate < s.slidesGrid[i + 1] - (s.slidesGrid[i + 1] - s.slidesGrid[i]) / 2) {
                        newActiveIndex = i;
                    }
                    else if (translate >= s.slidesGrid[i] && translate < s.slidesGrid[i + 1]) {
                        newActiveIndex = i + 1;
                    }
                }
                else {
                    if (translate >= s.slidesGrid[i]) {
                        newActiveIndex = i;
                    }
                }
            }
            // Normalize slideIndex
            if(s.params.normalizeSlideIndex){
                if (newActiveIndex < 0 || typeof newActiveIndex === 'undefined') newActiveIndex = 0;
            }
            // for (i = 0; i < s.slidesGrid.length; i++) {
                // if (- translate >= s.slidesGrid[i]) {
                    // newActiveIndex = i;
                // }
            // }
            snapIndex = Math.floor(newActiveIndex / s.params.slidesPerGroup);
            if (snapIndex >= s.snapGrid.length) snapIndex = s.snapGrid.length - 1;
        
            if (newActiveIndex === s.activeIndex) {
                return;
            }
            s.snapIndex = snapIndex;
            s.previousIndex = s.activeIndex;
            s.activeIndex = newActiveIndex;
            s.updateClasses();
            s.updateRealIndex();
        };
        s.updateRealIndex = function(){
            s.realIndex = parseInt(s.slides.eq(s.activeIndex).attr('data-swiper-slide-index') || s.activeIndex, 10);
        };
        
        /*=========================
          Classes
          ===========================*/
        s.updateClasses = function () {
            s.slides.removeClass(s.params.slideActiveClass + ' ' + s.params.slideNextClass + ' ' + s.params.slidePrevClass + ' ' + s.params.slideDuplicateActiveClass + ' ' + s.params.slideDuplicateNextClass + ' ' + s.params.slideDuplicatePrevClass);
            var activeSlide = s.slides.eq(s.activeIndex);
            // Active classes
            activeSlide.addClass(s.params.slideActiveClass);
            if (params.loop) {
                // Duplicate to all looped slides
                if (activeSlide.hasClass(s.params.slideDuplicateClass)) {
                    s.wrapper.children('.' + s.params.slideClass + ':not(.' + s.params.slideDuplicateClass + ')[data-swiper-slide-index="' + s.realIndex + '"]').addClass(s.params.slideDuplicateActiveClass);
                }
                else {
                    s.wrapper.children('.' + s.params.slideClass + '.' + s.params.slideDuplicateClass + '[data-swiper-slide-index="' + s.realIndex + '"]').addClass(s.params.slideDuplicateActiveClass);
                }
            }
            // Next Slide
            var nextSlide = activeSlide.next('.' + s.params.slideClass).addClass(s.params.slideNextClass);
            if (s.params.loop && nextSlide.length === 0) {
                nextSlide = s.slides.eq(0);
                nextSlide.addClass(s.params.slideNextClass);
            }
            // Prev Slide
            var prevSlide = activeSlide.prev('.' + s.params.slideClass).addClass(s.params.slidePrevClass);
            if (s.params.loop && prevSlide.length === 0) {
                prevSlide = s.slides.eq(-1);
                prevSlide.addClass(s.params.slidePrevClass);
            }
            if (params.loop) {
                // Duplicate to all looped slides
                if (nextSlide.hasClass(s.params.slideDuplicateClass)) {
                    s.wrapper.children('.' + s.params.slideClass + ':not(.' + s.params.slideDuplicateClass + ')[data-swiper-slide-index="' + nextSlide.attr('data-swiper-slide-index') + '"]').addClass(s.params.slideDuplicateNextClass);
                }
                else {
                    s.wrapper.children('.' + s.params.slideClass + '.' + s.params.slideDuplicateClass + '[data-swiper-slide-index="' + nextSlide.attr('data-swiper-slide-index') + '"]').addClass(s.params.slideDuplicateNextClass);
                }
                if (prevSlide.hasClass(s.params.slideDuplicateClass)) {
                    s.wrapper.children('.' + s.params.slideClass + ':not(.' + s.params.slideDuplicateClass + ')[data-swiper-slide-index="' + prevSlide.attr('data-swiper-slide-index') + '"]').addClass(s.params.slideDuplicatePrevClass);
                }
                else {
                    s.wrapper.children('.' + s.params.slideClass + '.' + s.params.slideDuplicateClass + '[data-swiper-slide-index="' + prevSlide.attr('data-swiper-slide-index') + '"]').addClass(s.params.slideDuplicatePrevClass);
                }
            }
        
            // Pagination
            if (s.paginationContainer && s.paginationContainer.length > 0) {
                // Current/Total
                var current,
                    total = s.params.loop ? Math.ceil((s.slides.length - s.loopedSlides * 2) / s.params.slidesPerGroup) : s.snapGrid.length;
                if (s.params.loop) {
                    current = Math.ceil((s.activeIndex - s.loopedSlides)/s.params.slidesPerGroup);
                    if (current > s.slides.length - 1 - s.loopedSlides * 2) {
                        current = current - (s.slides.length - s.loopedSlides * 2);
                    }
                    if (current > total - 1) current = current - total;
                    if (current < 0 && s.params.paginationType !== 'bullets') current = total + current;
                }
                else {
                    if (typeof s.snapIndex !== 'undefined') {
                        current = s.snapIndex;
                    }
                    else {
                        current = s.activeIndex || 0;
                    }
                }
                // Types
                if (s.params.paginationType === 'bullets' && s.bullets && s.bullets.length > 0) {
                    s.bullets.removeClass(s.params.bulletActiveClass);
                    if (s.paginationContainer.length > 1) {
                        s.bullets.each(function () {
                            if ($(this).index() === current) $(this).addClass(s.params.bulletActiveClass);
                        });
                    }
                    else {
                        s.bullets.eq(current).addClass(s.params.bulletActiveClass);
                    }
                }
                if (s.params.paginationType === 'fraction') {
                    s.paginationContainer.find('.' + s.params.paginationCurrentClass).text(current + 1);
                    s.paginationContainer.find('.' + s.params.paginationTotalClass).text(total);
                }
                if (s.params.paginationType === 'progress') {
                    var scale = (current + 1) / total,
                        scaleX = scale,
                        scaleY = 1;
                    if (!s.isHorizontal()) {
                        scaleY = scale;
                        scaleX = 1;
                    }
                    s.paginationContainer.find('.' + s.params.paginationProgressbarClass).transform('translate3d(0,0,0) scaleX(' + scaleX + ') scaleY(' + scaleY + ')').transition(s.params.speed);
                }
                if (s.params.paginationType === 'custom' && s.params.paginationCustomRender) {
                    s.paginationContainer.html(s.params.paginationCustomRender(s, current + 1, total));
                    s.emit('onPaginationRendered', s, s.paginationContainer[0]);
                }
            }
        
            // Next/active buttons
            if (!s.params.loop) {
                if (s.params.prevButton && s.prevButton && s.prevButton.length > 0) {
                    if (s.isBeginning) {
                        s.prevButton.addClass(s.params.buttonDisabledClass);
                        if (s.params.a11y && s.a11y) s.a11y.disable(s.prevButton);
                    }
                    else {
                        s.prevButton.removeClass(s.params.buttonDisabledClass);
                        if (s.params.a11y && s.a11y) s.a11y.enable(s.prevButton);
                    }
                }
                if (s.params.nextButton && s.nextButton && s.nextButton.length > 0) {
                    if (s.isEnd) {
                        s.nextButton.addClass(s.params.buttonDisabledClass);
                        if (s.params.a11y && s.a11y) s.a11y.disable(s.nextButton);
                    }
                    else {
                        s.nextButton.removeClass(s.params.buttonDisabledClass);
                        if (s.params.a11y && s.a11y) s.a11y.enable(s.nextButton);
                    }
                }
            }
        };
        
        /*=========================
          Pagination
          ===========================*/
        s.updatePagination = function () {
            if (!s.params.pagination) return;
            if (s.paginationContainer && s.paginationContainer.length > 0) {
                var paginationHTML = '';
                if (s.params.paginationType === 'bullets') {
                    var numberOfBullets = s.params.loop ? Math.ceil((s.slides.length - s.loopedSlides * 2) / s.params.slidesPerGroup) : s.snapGrid.length;
                    for (var i = 0; i < numberOfBullets; i++) {
                        if (s.params.paginationBulletRender) {
                            paginationHTML += s.params.paginationBulletRender(s, i, s.params.bulletClass);
                        }
                        else {
                            paginationHTML += '<' + s.params.paginationElement+' class="' + s.params.bulletClass + '"></' + s.params.paginationElement + '>';
                        }
                    }
                    s.paginationContainer.html(paginationHTML);
                    s.bullets = s.paginationContainer.find('.' + s.params.bulletClass);
                    if (s.params.paginationClickable && s.params.a11y && s.a11y) {
                        s.a11y.initPagination();
                    }
                }
                if (s.params.paginationType === 'fraction') {
                    if (s.params.paginationFractionRender) {
                        paginationHTML = s.params.paginationFractionRender(s, s.params.paginationCurrentClass, s.params.paginationTotalClass);
                    }
                    else {
                        paginationHTML =
                            '<span class="' + s.params.paginationCurrentClass + '"></span>' +
                            ' / ' +
                            '<span class="' + s.params.paginationTotalClass+'"></span>';
                    }
                    s.paginationContainer.html(paginationHTML);
                }
                if (s.params.paginationType === 'progress') {
                    if (s.params.paginationProgressRender) {
                        paginationHTML = s.params.paginationProgressRender(s, s.params.paginationProgressbarClass);
                    }
                    else {
                        paginationHTML = '<span class="' + s.params.paginationProgressbarClass + '"></span>';
                    }
                    s.paginationContainer.html(paginationHTML);
                }
                if (s.params.paginationType !== 'custom') {
                    s.emit('onPaginationRendered', s, s.paginationContainer[0]);
                }
            }
        };
        /*=========================
          Common update method
          ===========================*/
        s.update = function (updateTranslate) {
            if (!s) return;
            s.updateContainerSize();
            s.updateSlidesSize();
            s.updateProgress();
            s.updatePagination();
            s.updateClasses();
            if (s.params.scrollbar && s.scrollbar) {
                s.scrollbar.set();
            }
            function forceSetTranslate() {
                var translate = s.rtl ? -s.translate : s.translate;
                newTranslate = Math.min(Math.max(s.translate, s.maxTranslate()), s.minTranslate());
                s.setWrapperTranslate(newTranslate);
                s.updateActiveIndex();
                s.updateClasses();
            }
            if (updateTranslate) {
                var translated, newTranslate;
                if (s.controller && s.controller.spline) {
                    s.controller.spline = undefined;
                }
                if (s.params.freeMode) {
                    forceSetTranslate();
                    if (s.params.autoHeight) {
                        s.updateAutoHeight();
                    }
                }
                else {
                    if ((s.params.slidesPerView === 'auto' || s.params.slidesPerView > 1) && s.isEnd && !s.params.centeredSlides) {
                        translated = s.slideTo(s.slides.length - 1, 0, false, true);
                    }
                    else {
                        translated = s.slideTo(s.activeIndex, 0, false, true);
                    }
                    if (!translated) {
                        forceSetTranslate();
                    }
                }
            }
            else if (s.params.autoHeight) {
                s.updateAutoHeight();
            }
        };
        
        /*=========================
          Resize Handler
          ===========================*/
        s.onResize = function (forceUpdatePagination) {
            //Breakpoints
            if (s.params.breakpoints) {
                s.setBreakpoint();
            }
        
            // Disable locks on resize
            var allowSwipeToPrev = s.params.allowSwipeToPrev;
            var allowSwipeToNext = s.params.allowSwipeToNext;
            s.params.allowSwipeToPrev = s.params.allowSwipeToNext = true;
        
            s.updateContainerSize();
            s.updateSlidesSize();
            if (s.params.slidesPerView === 'auto' || s.params.freeMode || forceUpdatePagination) s.updatePagination();
            if (s.params.scrollbar && s.scrollbar) {
                s.scrollbar.set();
            }
            if (s.controller && s.controller.spline) {
                s.controller.spline = undefined;
            }
            var slideChangedBySlideTo = false;
            if (s.params.freeMode) {
                var newTranslate = Math.min(Math.max(s.translate, s.maxTranslate()), s.minTranslate());
                s.setWrapperTranslate(newTranslate);
                s.updateActiveIndex();
                s.updateClasses();
        
                if (s.params.autoHeight) {
                    s.updateAutoHeight();
                }
            }
            else {
                s.updateClasses();
                if ((s.params.slidesPerView === 'auto' || s.params.slidesPerView > 1) && s.isEnd && !s.params.centeredSlides) {
                    slideChangedBySlideTo = s.slideTo(s.slides.length - 1, 0, false, true);
                }
                else {
                    slideChangedBySlideTo = s.slideTo(s.activeIndex, 0, false, true);
                }
            }
            if (s.params.lazyLoading && !slideChangedBySlideTo && s.lazy) {
                s.lazy.load();
            }
            // Return locks after resize
            s.params.allowSwipeToPrev = allowSwipeToPrev;
            s.params.allowSwipeToNext = allowSwipeToNext;
        };
        
        /*=========================
          Events
          ===========================*/
        
        //Define Touch Events
        s.touchEventsDesktop = {start: 'mousedown', move: 'mousemove', end: 'mouseup'};
        if (window.navigator.pointerEnabled) s.touchEventsDesktop = {start: 'pointerdown', move: 'pointermove', end: 'pointerup'};
        else if (window.navigator.msPointerEnabled) s.touchEventsDesktop = {start: 'MSPointerDown', move: 'MSPointerMove', end: 'MSPointerUp'};
        s.touchEvents = {
            start : s.support.touch || !s.params.simulateTouch  ? 'touchstart' : s.touchEventsDesktop.start,
            move : s.support.touch || !s.params.simulateTouch ? 'touchmove' : s.touchEventsDesktop.move,
            end : s.support.touch || !s.params.simulateTouch ? 'touchend' : s.touchEventsDesktop.end
        };
        
        
        // WP8 Touch Events Fix
        if (window.navigator.pointerEnabled || window.navigator.msPointerEnabled) {
            (s.params.touchEventsTarget === 'container' ? s.container : s.wrapper).addClass('swiper-wp8-' + s.params.direction);
        }
        
        // Attach/detach events
        s.initEvents = function (detach) {
            var actionDom = detach ? 'off' : 'on';
            var action = detach ? 'removeEventListener' : 'addEventListener';
            var touchEventsTarget = s.params.touchEventsTarget === 'container' ? s.container[0] : s.wrapper[0];
            var target = s.support.touch ? touchEventsTarget : document;
        
            var moveCapture = s.params.nested ? true : false;
        
            //Touch Events
            if (s.browser.ie) {
                touchEventsTarget[action](s.touchEvents.start, s.onTouchStart, false);
                target[action](s.touchEvents.move, s.onTouchMove, moveCapture);
                target[action](s.touchEvents.end, s.onTouchEnd, false);
            }
            else {
                if (s.support.touch) {
                    var passiveListener = s.touchEvents.start === 'touchstart' && s.support.passiveListener && s.params.passiveListeners ? {passive: true, capture: false} : false;
                    touchEventsTarget[action](s.touchEvents.start, s.onTouchStart, passiveListener);
                    touchEventsTarget[action](s.touchEvents.move, s.onTouchMove, moveCapture);
                    touchEventsTarget[action](s.touchEvents.end, s.onTouchEnd, passiveListener);
                }
                if ((params.simulateTouch && !s.device.ios && !s.device.android) || (params.simulateTouch && !s.support.touch && s.device.ios)) {
                    touchEventsTarget[action]('mousedown', s.onTouchStart, false);
                    document[action]('mousemove', s.onTouchMove, moveCapture);
                    document[action]('mouseup', s.onTouchEnd, false);
                }
            }
            window[action]('resize', s.onResize);
        
            // Next, Prev, Index
            if (s.params.nextButton && s.nextButton && s.nextButton.length > 0) {
                s.nextButton[actionDom]('click', s.onClickNext);
                if (s.params.a11y && s.a11y) s.nextButton[actionDom]('keydown', s.a11y.onEnterKey);
            }
            if (s.params.prevButton && s.prevButton && s.prevButton.length > 0) {
                s.prevButton[actionDom]('click', s.onClickPrev);
                if (s.params.a11y && s.a11y) s.prevButton[actionDom]('keydown', s.a11y.onEnterKey);
            }
            if (s.params.pagination && s.params.paginationClickable) {
                s.paginationContainer[actionDom]('click', '.' + s.params.bulletClass, s.onClickIndex);
                if (s.params.a11y && s.a11y) s.paginationContainer[actionDom]('keydown', '.' + s.params.bulletClass, s.a11y.onEnterKey);
            }
        
            // Prevent Links Clicks
            if (s.params.preventClicks || s.params.preventClicksPropagation) touchEventsTarget[action]('click', s.preventClicks, true);
        };
        s.attachEvents = function () {
            s.initEvents();
        };
        s.detachEvents = function () {
            s.initEvents(true);
        };
        
        /*=========================
          Handle Clicks
          ===========================*/
        // Prevent Clicks
        s.allowClick = true;
        s.preventClicks = function (e) {
            if (!s.allowClick) {
                if (s.params.preventClicks) e.preventDefault();
                if (s.params.preventClicksPropagation && s.animating) {
                    e.stopPropagation();
                    e.stopImmediatePropagation();
                }
            }
        };
        // Clicks
        s.onClickNext = function (e) {
            e.preventDefault();
            if (s.isEnd && !s.params.loop) return;
            s.slideNext();
        };
        s.onClickPrev = function (e) {
            e.preventDefault();
            if (s.isBeginning && !s.params.loop) return;
            s.slidePrev();
        };
        s.onClickIndex = function (e) {
            e.preventDefault();
            var index = $(this).index() * s.params.slidesPerGroup;
            if (s.params.loop) index = index + s.loopedSlides;
            s.slideTo(index);
        };
        
        /*=========================
          Handle Touches
          ===========================*/
        function findElementInEvent(e, selector) {
            var el = $(e.target);
            if (!el.is(selector)) {
                if (typeof selector === 'string') {
                    el = el.parents(selector);
                }
                else if (selector.nodeType) {
                    var found;
                    el.parents().each(function (index, _el) {
                        if (_el === selector) found = selector;
                    });
                    if (!found) return undefined;
                    else return selector;
                }
            }
            if (el.length === 0) {
                return undefined;
            }
            return el[0];
        }
        s.updateClickedSlide = function (e) {
            var slide = findElementInEvent(e, '.' + s.params.slideClass);
            var slideFound = false;
            if (slide) {
                for (var i = 0; i < s.slides.length; i++) {
                    if (s.slides[i] === slide) slideFound = true;
                }
            }
        
            if (slide && slideFound) {
                s.clickedSlide = slide;
                s.clickedIndex = $(slide).index();
            }
            else {
                s.clickedSlide = undefined;
                s.clickedIndex = undefined;
                return;
            }
            if (s.params.slideToClickedSlide && s.clickedIndex !== undefined && s.clickedIndex !== s.activeIndex) {
                var slideToIndex = s.clickedIndex,
                    realIndex,
                    duplicatedSlides,
                    slidesPerView = s.params.slidesPerView === 'auto' ? s.currentSlidesPerView() : s.params.slidesPerView;
                if (s.params.loop) {
                    if (s.animating) return;
                    realIndex = parseInt($(s.clickedSlide).attr('data-swiper-slide-index'), 10);
                    if (s.params.centeredSlides) {
                        if ((slideToIndex < s.loopedSlides - slidesPerView/2) || (slideToIndex > s.slides.length - s.loopedSlides + slidesPerView/2)) {
                            s.fixLoop();
                            slideToIndex = s.wrapper.children('.' + s.params.slideClass + '[data-swiper-slide-index="' + realIndex + '"]:not(.' + s.params.slideDuplicateClass + ')').eq(0).index();
                            setTimeout(function () {
                                s.slideTo(slideToIndex);
                            }, 0);
                        }
                        else {
                            s.slideTo(slideToIndex);
                        }
                    }
                    else {
                        if (slideToIndex > s.slides.length - slidesPerView) {
                            s.fixLoop();
                            slideToIndex = s.wrapper.children('.' + s.params.slideClass + '[data-swiper-slide-index="' + realIndex + '"]:not(.' + s.params.slideDuplicateClass + ')').eq(0).index();
                            setTimeout(function () {
                                s.slideTo(slideToIndex);
                            }, 0);
                        }
                        else {
                            s.slideTo(slideToIndex);
                        }
                    }
                }
                else {
                    s.slideTo(slideToIndex);
                }
            }
        };
        
        var isTouched,
            isMoved,
            allowTouchCallbacks,
            touchStartTime,
            isScrolling,
            currentTranslate,
            startTranslate,
            allowThresholdMove,
            // Form elements to match
            formElements = 'input, select, textarea, button, video',
            // Last click time
            lastClickTime = Date.now(), clickTimeout,
            //Velocities
            velocities = [],
            allowMomentumBounce;
        
        // Animating Flag
        s.animating = false;
        
        // Touches information
        s.touches = {
            startX: 0,
            startY: 0,
            currentX: 0,
            currentY: 0,
            diff: 0
        };
        
        // Touch handlers
        var isTouchEvent, startMoving;
        s.onTouchStart = function (e) {
            if (e.originalEvent) e = e.originalEvent;
            isTouchEvent = e.type === 'touchstart';
            if (!isTouchEvent && 'which' in e && e.which === 3) return;
            if (s.params.noSwiping && findElementInEvent(e, '.' + s.params.noSwipingClass)) {
                s.allowClick = true;
                return;
            }
            if (s.params.swipeHandler) {
                if (!findElementInEvent(e, s.params.swipeHandler)) return;
            }
        
            var startX = s.touches.currentX = e.type === 'touchstart' ? e.targetTouches[0].pageX : e.pageX;
            var startY = s.touches.currentY = e.type === 'touchstart' ? e.targetTouches[0].pageY : e.pageY;
        
            // Do NOT start if iOS edge swipe is detected. Otherwise iOS app (UIWebView) cannot swipe-to-go-back anymore
            if(s.device.ios && s.params.iOSEdgeSwipeDetection && startX <= s.params.iOSEdgeSwipeThreshold) {
                return;
            }
        
            isTouched = true;
            isMoved = false;
            allowTouchCallbacks = true;
            isScrolling = undefined;
            startMoving = undefined;
            s.touches.startX = startX;
            s.touches.startY = startY;
            touchStartTime = Date.now();
            s.allowClick = true;
            s.updateContainerSize();
            s.swipeDirection = undefined;
            if (s.params.threshold > 0) allowThresholdMove = false;
            if (e.type !== 'touchstart') {
                var preventDefault = true;
                if ($(e.target).is(formElements)) preventDefault = false;
                if (document.activeElement && $(document.activeElement).is(formElements)) {
                    document.activeElement.blur();
                }
                if (preventDefault) {
                    e.preventDefault();
                }
            }
            s.emit('onTouchStart', s, e);
        };
        
        s.onTouchMove = function (e) {
            if (e.originalEvent) e = e.originalEvent;
            if (isTouchEvent && e.type === 'mousemove') return;
            if (e.preventedByNestedSwiper) {
                s.touches.startX = e.type === 'touchmove' ? e.targetTouches[0].pageX : e.pageX;
                s.touches.startY = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.pageY;
                return;
            }
            if (s.params.onlyExternal) {
                // isMoved = true;
                s.allowClick = false;
                if (isTouched) {
                    s.touches.startX = s.touches.currentX = e.type === 'touchmove' ? e.targetTouches[0].pageX : e.pageX;
                    s.touches.startY = s.touches.currentY = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.pageY;
                    touchStartTime = Date.now();
                }
                return;
            }
            if (isTouchEvent && s.params.touchReleaseOnEdges && !s.params.loop) {
                if (!s.isHorizontal()) {
                    // Vertical
                    if (
                        (s.touches.currentY < s.touches.startY && s.translate <= s.maxTranslate()) ||
                        (s.touches.currentY > s.touches.startY && s.translate >= s.minTranslate())
                        ) {
                        return;
                    }
                }
                else {
                    if (
                        (s.touches.currentX < s.touches.startX && s.translate <= s.maxTranslate()) ||
                        (s.touches.currentX > s.touches.startX && s.translate >= s.minTranslate())
                        ) {
                        return;
                    }
                }
            }
            if (isTouchEvent && document.activeElement) {
                if (e.target === document.activeElement && $(e.target).is(formElements)) {
                    isMoved = true;
                    s.allowClick = false;
                    return;
                }
            }
            if (allowTouchCallbacks) {
                s.emit('onTouchMove', s, e);
            }
            if (e.targetTouches && e.targetTouches.length > 1) return;
        
            s.touches.currentX = e.type === 'touchmove' ? e.targetTouches[0].pageX : e.pageX;
            s.touches.currentY = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.pageY;
        
            if (typeof isScrolling === 'undefined') {
                var touchAngle;
                if (s.isHorizontal() && s.touches.currentY === s.touches.startY || !s.isHorizontal() && s.touches.currentX === s.touches.startX) {
                    isScrolling = false;
                }
                else {
                    touchAngle = Math.atan2(Math.abs(s.touches.currentY - s.touches.startY), Math.abs(s.touches.currentX - s.touches.startX)) * 180 / Math.PI;
                    isScrolling = s.isHorizontal() ? touchAngle > s.params.touchAngle : (90 - touchAngle > s.params.touchAngle);
                }
            }
            if (isScrolling) {
                s.emit('onTouchMoveOpposite', s, e);
            }
            if (typeof startMoving === 'undefined' && s.browser.ieTouch) {
                if (s.touches.currentX !== s.touches.startX || s.touches.currentY !== s.touches.startY) {
                    startMoving = true;
                }
            }
            if (!isTouched) return;
            if (isScrolling)  {
                isTouched = false;
                return;
            }
            if (!startMoving && s.browser.ieTouch) {
                return;
            }
            s.allowClick = false;
            s.emit('onSliderMove', s, e);
            e.preventDefault();
            if (s.params.touchMoveStopPropagation && !s.params.nested) {
                e.stopPropagation();
            }
        
            if (!isMoved) {
                if (params.loop) {
                    s.fixLoop();
                }
                startTranslate = s.getWrapperTranslate();
                s.setWrapperTransition(0);
                if (s.animating) {
                    s.wrapper.trigger('webkitTransitionEnd transitionend oTransitionEnd MSTransitionEnd msTransitionEnd');
                }
                if (s.params.autoplay && s.autoplaying) {
                    if (s.params.autoplayDisableOnInteraction) {
                        s.stopAutoplay();
                    }
                    else {
                        s.pauseAutoplay();
                    }
                }
                allowMomentumBounce = false;
                //Grab Cursor
                if (s.params.grabCursor && (s.params.allowSwipeToNext === true || s.params.allowSwipeToPrev === true)) {
                    s.setGrabCursor(true);
                }
            }
            isMoved = true;
        
            var diff = s.touches.diff = s.isHorizontal() ? s.touches.currentX - s.touches.startX : s.touches.currentY - s.touches.startY;
        
            diff = diff * s.params.touchRatio;
            if (s.rtl) diff = -diff;
        
            s.swipeDirection = diff > 0 ? 'prev' : 'next';
            currentTranslate = diff + startTranslate;
        
            var disableParentSwiper = true;
            if ((diff > 0 && currentTranslate > s.minTranslate())) {
                disableParentSwiper = false;
                if (s.params.resistance) currentTranslate = s.minTranslate() - 1 + Math.pow(-s.minTranslate() + startTranslate + diff, s.params.resistanceRatio);
            }
            else if (diff < 0 && currentTranslate < s.maxTranslate()) {
                disableParentSwiper = false;
                if (s.params.resistance) currentTranslate = s.maxTranslate() + 1 - Math.pow(s.maxTranslate() - startTranslate - diff, s.params.resistanceRatio);
            }
        
            if (disableParentSwiper) {
                e.preventedByNestedSwiper = true;
            }
        
            // Directions locks
            if (!s.params.allowSwipeToNext && s.swipeDirection === 'next' && currentTranslate < startTranslate) {
                currentTranslate = startTranslate;
            }
            if (!s.params.allowSwipeToPrev && s.swipeDirection === 'prev' && currentTranslate > startTranslate) {
                currentTranslate = startTranslate;
            }
        
        
            // Threshold
            if (s.params.threshold > 0) {
                if (Math.abs(diff) > s.params.threshold || allowThresholdMove) {
                    if (!allowThresholdMove) {
                        allowThresholdMove = true;
                        s.touches.startX = s.touches.currentX;
                        s.touches.startY = s.touches.currentY;
                        currentTranslate = startTranslate;
                        s.touches.diff = s.isHorizontal() ? s.touches.currentX - s.touches.startX : s.touches.currentY - s.touches.startY;
                        return;
                    }
                }
                else {
                    currentTranslate = startTranslate;
                    return;
                }
            }
        
            if (!s.params.followFinger) return;
        
            // Update active index in free mode
            if (s.params.freeMode || s.params.watchSlidesProgress) {
                s.updateActiveIndex();
            }
            if (s.params.freeMode) {
                //Velocity
                if (velocities.length === 0) {
                    velocities.push({
                        position: s.touches[s.isHorizontal() ? 'startX' : 'startY'],
                        time: touchStartTime
                    });
                }
                velocities.push({
                    position: s.touches[s.isHorizontal() ? 'currentX' : 'currentY'],
                    time: (new window.Date()).getTime()
                });
            }
            // Update progress
            s.updateProgress(currentTranslate);
            // Update translate
            s.setWrapperTranslate(currentTranslate);
        };
        s.onTouchEnd = function (e) {
            if (e.originalEvent) e = e.originalEvent;
            if (allowTouchCallbacks) {
                s.emit('onTouchEnd', s, e);
            }
            allowTouchCallbacks = false;
            if (!isTouched) return;
            //Return Grab Cursor
            if (s.params.grabCursor && isMoved && isTouched  && (s.params.allowSwipeToNext === true || s.params.allowSwipeToPrev === true)) {
                s.setGrabCursor(false);
            }
        
            // Time diff
            var touchEndTime = Date.now();
            var timeDiff = touchEndTime - touchStartTime;
        
            // Tap, doubleTap, Click
            if (s.allowClick) {
                s.updateClickedSlide(e);
                s.emit('onTap', s, e);
                if (timeDiff < 300 && (touchEndTime - lastClickTime) > 300) {
                    if (clickTimeout) clearTimeout(clickTimeout);
                    clickTimeout = setTimeout(function () {
                        if (!s) return;
                        if (s.params.paginationHide && s.paginationContainer.length > 0 && !$(e.target).hasClass(s.params.bulletClass)) {
                            s.paginationContainer.toggleClass(s.params.paginationHiddenClass);
                        }
                        s.emit('onClick', s, e);
                    }, 300);
        
                }
                if (timeDiff < 300 && (touchEndTime - lastClickTime) < 300) {
                    if (clickTimeout) clearTimeout(clickTimeout);
                    s.emit('onDoubleTap', s, e);
                }
            }
        
            lastClickTime = Date.now();
            setTimeout(function () {
                if (s) s.allowClick = true;
            }, 0);
        
            if (!isTouched || !isMoved || !s.swipeDirection || s.touches.diff === 0 || currentTranslate === startTranslate) {
                isTouched = isMoved = false;
                return;
            }
            isTouched = isMoved = false;
        
            var currentPos;
            if (s.params.followFinger) {
                currentPos = s.rtl ? s.translate : -s.translate;
            }
            else {
                currentPos = -currentTranslate;
            }
            if (s.params.freeMode) {
                if (currentPos < -s.minTranslate()) {
                    s.slideTo(s.activeIndex);
                    return;
                }
                else if (currentPos > -s.maxTranslate()) {
                    if (s.slides.length < s.snapGrid.length) {
                        s.slideTo(s.snapGrid.length - 1);
                    }
                    else {
                        s.slideTo(s.slides.length - 1);
                    }
                    return;
                }
        
                if (s.params.freeModeMomentum) {
                    if (velocities.length > 1) {
                        var lastMoveEvent = velocities.pop(), velocityEvent = velocities.pop();
        
                        var distance = lastMoveEvent.position - velocityEvent.position;
                        var time = lastMoveEvent.time - velocityEvent.time;
                        s.velocity = distance / time;
                        s.velocity = s.velocity / 2;
                        if (Math.abs(s.velocity) < s.params.freeModeMinimumVelocity) {
                            s.velocity = 0;
                        }
                        // this implies that the user stopped moving a finger then released.
                        // There would be no events with distance zero, so the last event is stale.
                        if (time > 150 || (new window.Date().getTime() - lastMoveEvent.time) > 300) {
                            s.velocity = 0;
                        }
                    } else {
                        s.velocity = 0;
                    }
                    s.velocity = s.velocity * s.params.freeModeMomentumVelocityRatio;
        
                    velocities.length = 0;
                    var momentumDuration = 1000 * s.params.freeModeMomentumRatio;
                    var momentumDistance = s.velocity * momentumDuration;
        
                    var newPosition = s.translate + momentumDistance;
                    if (s.rtl) newPosition = - newPosition;
                    var doBounce = false;
                    var afterBouncePosition;
                    var bounceAmount = Math.abs(s.velocity) * 20 * s.params.freeModeMomentumBounceRatio;
                    if (newPosition < s.maxTranslate()) {
                        if (s.params.freeModeMomentumBounce) {
                            if (newPosition + s.maxTranslate() < -bounceAmount) {
                                newPosition = s.maxTranslate() - bounceAmount;
                            }
                            afterBouncePosition = s.maxTranslate();
                            doBounce = true;
                            allowMomentumBounce = true;
                        }
                        else {
                            newPosition = s.maxTranslate();
                        }
                    }
                    else if (newPosition > s.minTranslate()) {
                        if (s.params.freeModeMomentumBounce) {
                            if (newPosition - s.minTranslate() > bounceAmount) {
                                newPosition = s.minTranslate() + bounceAmount;
                            }
                            afterBouncePosition = s.minTranslate();
                            doBounce = true;
                            allowMomentumBounce = true;
                        }
                        else {
                            newPosition = s.minTranslate();
                        }
                    }
                    else if (s.params.freeModeSticky) {
                        var j = 0,
                            nextSlide;
                        for (j = 0; j < s.snapGrid.length; j += 1) {
                            if (s.snapGrid[j] > -newPosition) {
                                nextSlide = j;
                                break;
                            }
        
                        }
                        if (Math.abs(s.snapGrid[nextSlide] - newPosition) < Math.abs(s.snapGrid[nextSlide - 1] - newPosition) || s.swipeDirection === 'next') {
                            newPosition = s.snapGrid[nextSlide];
                        } else {
                            newPosition = s.snapGrid[nextSlide - 1];
                        }
                        if (!s.rtl) newPosition = - newPosition;
                    }
                    //Fix duration
                    if (s.velocity !== 0) {
                        if (s.rtl) {
                            momentumDuration = Math.abs((-newPosition - s.translate) / s.velocity);
                        }
                        else {
                            momentumDuration = Math.abs((newPosition - s.translate) / s.velocity);
                        }
                    }
                    else if (s.params.freeModeSticky) {
                        s.slideReset();
                        return;
                    }
        
                    if (s.params.freeModeMomentumBounce && doBounce) {
                        s.updateProgress(afterBouncePosition);
                        s.setWrapperTransition(momentumDuration);
                        s.setWrapperTranslate(newPosition);
                        s.onTransitionStart();
                        s.animating = true;
                        s.wrapper.transitionEnd(function () {
                            if (!s || !allowMomentumBounce) return;
                            s.emit('onMomentumBounce', s);
        
                            s.setWrapperTransition(s.params.speed);
                            s.setWrapperTranslate(afterBouncePosition);
                            s.wrapper.transitionEnd(function () {
                                if (!s) return;
                                s.onTransitionEnd();
                            });
                        });
                    } else if (s.velocity) {
                        s.updateProgress(newPosition);
                        s.setWrapperTransition(momentumDuration);
                        s.setWrapperTranslate(newPosition);
                        s.onTransitionStart();
                        if (!s.animating) {
                            s.animating = true;
                            s.wrapper.transitionEnd(function () {
                                if (!s) return;
                                s.onTransitionEnd();
                            });
                        }
        
                    } else {
                        s.updateProgress(newPosition);
                    }
        
                    s.updateActiveIndex();
                }
                if (!s.params.freeModeMomentum || timeDiff >= s.params.longSwipesMs) {
                    s.updateProgress();
                    s.updateActiveIndex();
                }
                return;
            }
        
            // Find current slide
            var i, stopIndex = 0, groupSize = s.slidesSizesGrid[0];
            for (i = 0; i < s.slidesGrid.length; i += s.params.slidesPerGroup) {
                if (typeof s.slidesGrid[i + s.params.slidesPerGroup] !== 'undefined') {
                    if (currentPos >= s.slidesGrid[i] && currentPos < s.slidesGrid[i + s.params.slidesPerGroup]) {
                        stopIndex = i;
                        groupSize = s.slidesGrid[i + s.params.slidesPerGroup] - s.slidesGrid[i];
                    }
                }
                else {
                    if (currentPos >= s.slidesGrid[i]) {
                        stopIndex = i;
                        groupSize = s.slidesGrid[s.slidesGrid.length - 1] - s.slidesGrid[s.slidesGrid.length - 2];
                    }
                }
            }
        
            // Find current slide size
            var ratio = (currentPos - s.slidesGrid[stopIndex]) / groupSize;
        
            if (timeDiff > s.params.longSwipesMs) {
                // Long touches
                if (!s.params.longSwipes) {
                    s.slideTo(s.activeIndex);
                    return;
                }
                if (s.swipeDirection === 'next') {
                    if (ratio >= s.params.longSwipesRatio) s.slideTo(stopIndex + s.params.slidesPerGroup);
                    else s.slideTo(stopIndex);
        
                }
                if (s.swipeDirection === 'prev') {
                    if (ratio > (1 - s.params.longSwipesRatio)) s.slideTo(stopIndex + s.params.slidesPerGroup);
                    else s.slideTo(stopIndex);
                }
            }
            else {
                // Short swipes
                if (!s.params.shortSwipes) {
                    s.slideTo(s.activeIndex);
                    return;
                }
                if (s.swipeDirection === 'next') {
                    s.slideTo(stopIndex + s.params.slidesPerGroup);
        
                }
                if (s.swipeDirection === 'prev') {
                    s.slideTo(stopIndex);
                }
            }
        };
        /*=========================
          Transitions
          ===========================*/
        s._slideTo = function (slideIndex, speed) {
            return s.slideTo(slideIndex, speed, true, true);
        };
        s.slideTo = function (slideIndex, speed, runCallbacks, internal) {
            if (typeof runCallbacks === 'undefined') runCallbacks = true;
            if (typeof slideIndex === 'undefined') slideIndex = 0;
            if (slideIndex < 0) slideIndex = 0;
            s.snapIndex = Math.floor(slideIndex / s.params.slidesPerGroup);
            if (s.snapIndex >= s.snapGrid.length) s.snapIndex = s.snapGrid.length - 1;
        
            var translate = - s.snapGrid[s.snapIndex];
            // Stop autoplay
            if (s.params.autoplay && s.autoplaying) {
                if (internal || !s.params.autoplayDisableOnInteraction) {
                    s.pauseAutoplay(speed);
                }
                else {
                    s.stopAutoplay();
                }
            }
            // Update progress
            s.updateProgress(translate);
        
            // Normalize slideIndex
            if(s.params.normalizeSlideIndex){
                for (var i = 0; i < s.slidesGrid.length; i++) {
                    if (- Math.floor(translate * 100) >= Math.floor(s.slidesGrid[i] * 100)) {
                        slideIndex = i;
                    }
                }
            }
        
            // Directions locks
            if (!s.params.allowSwipeToNext && translate < s.translate && translate < s.minTranslate()) {
                return false;
            }
            if (!s.params.allowSwipeToPrev && translate > s.translate && translate > s.maxTranslate()) {
                if ((s.activeIndex || 0) !== slideIndex ) return false;
            }
        
            // Update Index
            if (typeof speed === 'undefined') speed = s.params.speed;
            s.previousIndex = s.activeIndex || 0;
            s.activeIndex = slideIndex;
            s.updateRealIndex();
            if ((s.rtl && -translate === s.translate) || (!s.rtl && translate === s.translate)) {
                // Update Height
                if (s.params.autoHeight) {
                    s.updateAutoHeight();
                }
                s.updateClasses();
                if (s.params.effect !== 'slide') {
                    s.setWrapperTranslate(translate);
                }
                return false;
            }
            s.updateClasses();
            s.onTransitionStart(runCallbacks);
        
            if (speed === 0 || s.browser.lteIE9) {
                s.setWrapperTranslate(translate);
                s.setWrapperTransition(0);
                s.onTransitionEnd(runCallbacks);
            }
            else {
                s.setWrapperTranslate(translate);
                s.setWrapperTransition(speed);
                if (!s.animating) {
                    s.animating = true;
                    s.wrapper.transitionEnd(function () {
                        if (!s) return;
                        s.onTransitionEnd(runCallbacks);
                    });
                }
        
            }
        
            return true;
        };
        
        s.onTransitionStart = function (runCallbacks) {
            if (typeof runCallbacks === 'undefined') runCallbacks = true;
            if (s.params.autoHeight) {
                s.updateAutoHeight();
            }
            if (s.lazy) s.lazy.onTransitionStart();
            if (runCallbacks) {
                s.emit('onTransitionStart', s);
                if (s.activeIndex !== s.previousIndex) {
                    s.emit('onSlideChangeStart', s);
                    if (s.activeIndex > s.previousIndex) {
                        s.emit('onSlideNextStart', s);
                    }
                    else {
                        s.emit('onSlidePrevStart', s);
                    }
                }
        
            }
        };
        s.onTransitionEnd = function (runCallbacks) {
            s.animating = false;
            s.setWrapperTransition(0);
            if (typeof runCallbacks === 'undefined') runCallbacks = true;
            if (s.lazy) s.lazy.onTransitionEnd();
            if (runCallbacks) {
                s.emit('onTransitionEnd', s);
                if (s.activeIndex !== s.previousIndex) {
                    s.emit('onSlideChangeEnd', s);
                    if (s.activeIndex > s.previousIndex) {
                        s.emit('onSlideNextEnd', s);
                    }
                    else {
                        s.emit('onSlidePrevEnd', s);
                    }
                }
            }
            if (s.params.history && s.history) {
                s.history.setHistory(s.params.history, s.activeIndex);
            }
            if (s.params.hashnav && s.hashnav) {
                s.hashnav.setHash();
            }
        
        };
        s.slideNext = function (runCallbacks, speed, internal) {
            if (s.params.loop) {
                if (s.animating) return false;
                s.fixLoop();
                var clientLeft = s.container[0].clientLeft;
                return s.slideTo(s.activeIndex + s.params.slidesPerGroup, speed, runCallbacks, internal);
            }
            else return s.slideTo(s.activeIndex + s.params.slidesPerGroup, speed, runCallbacks, internal);
        };
        s._slideNext = function (speed) {
            return s.slideNext(true, speed, true);
        };
        s.slidePrev = function (runCallbacks, speed, internal) {
            if (s.params.loop) {
                if (s.animating) return false;
                s.fixLoop();
                var clientLeft = s.container[0].clientLeft;
                return s.slideTo(s.activeIndex - 1, speed, runCallbacks, internal);
            }
            else return s.slideTo(s.activeIndex - 1, speed, runCallbacks, internal);
        };
        s._slidePrev = function (speed) {
            return s.slidePrev(true, speed, true);
        };
        s.slideReset = function (runCallbacks, speed, internal) {
            return s.slideTo(s.activeIndex, speed, runCallbacks);
        };
        
        s.disableTouchControl = function () {
            s.params.onlyExternal = true;
            return true;
        };
        s.enableTouchControl = function () {
            s.params.onlyExternal = false;
            return true;
        };
        
        /*=========================
          Translate/transition helpers
          ===========================*/
        s.setWrapperTransition = function (duration, byController) {
            s.wrapper.transition(duration);
            if (s.params.effect !== 'slide' && s.effects[s.params.effect]) {
                s.effects[s.params.effect].setTransition(duration);
            }
            if (s.params.parallax && s.parallax) {
                s.parallax.setTransition(duration);
            }
            if (s.params.scrollbar && s.scrollbar) {
                s.scrollbar.setTransition(duration);
            }
            if (s.params.control && s.controller) {
                s.controller.setTransition(duration, byController);
            }
            s.emit('onSetTransition', s, duration);
        };
        s.setWrapperTranslate = function (translate, updateActiveIndex, byController) {
            var x = 0, y = 0, z = 0;
            if (s.isHorizontal()) {
                x = s.rtl ? -translate : translate;
            }
            else {
                y = translate;
            }
        
            if (s.params.roundLengths) {
                x = round(x);
                y = round(y);
            }
        
            if (!s.params.virtualTranslate) {
                if (s.support.transforms3d) s.wrapper.transform('translate3d(' + x + 'px, ' + y + 'px, ' + z + 'px)');
                else s.wrapper.transform('translate(' + x + 'px, ' + y + 'px)');
            }
        
            s.translate = s.isHorizontal() ? x : y;
        
            // Check if we need to update progress
            var progress;
            var translatesDiff = s.maxTranslate() - s.minTranslate();
            if (translatesDiff === 0) {
                progress = 0;
            }
            else {
                progress = (translate - s.minTranslate()) / (translatesDiff);
            }
            if (progress !== s.progress) {
                s.updateProgress(translate);
            }
        
            if (updateActiveIndex) s.updateActiveIndex();
            if (s.params.effect !== 'slide' && s.effects[s.params.effect]) {
                s.effects[s.params.effect].setTranslate(s.translate);
            }
            if (s.params.parallax && s.parallax) {
                s.parallax.setTranslate(s.translate);
            }
            if (s.params.scrollbar && s.scrollbar) {
                s.scrollbar.setTranslate(s.translate);
            }
            if (s.params.control && s.controller) {
                s.controller.setTranslate(s.translate, byController);
            }
            s.emit('onSetTranslate', s, s.translate);
        };
        
        s.getTranslate = function (el, axis) {
            var matrix, curTransform, curStyle, transformMatrix;
        
            // automatic axis detection
            if (typeof axis === 'undefined') {
                axis = 'x';
            }
        
            if (s.params.virtualTranslate) {
                return s.rtl ? -s.translate : s.translate;
            }
        
            curStyle = window.getComputedStyle(el, null);
            if (window.WebKitCSSMatrix) {
                curTransform = curStyle.transform || curStyle.webkitTransform;
                if (curTransform.split(',').length > 6) {
                    curTransform = curTransform.split(', ').map(function(a){
                        return a.replace(',','.');
                    }).join(', ');
                }
                // Some old versions of Webkit choke when 'none' is passed; pass
                // empty string instead in this case
                transformMatrix = new window.WebKitCSSMatrix(curTransform === 'none' ? '' : curTransform);
            }
            else {
                transformMatrix = curStyle.MozTransform || curStyle.OTransform || curStyle.MsTransform || curStyle.msTransform  || curStyle.transform || curStyle.getPropertyValue('transform').replace('translate(', 'matrix(1, 0, 0, 1,');
                matrix = transformMatrix.toString().split(',');
            }
        
            if (axis === 'x') {
                //Latest Chrome and webkits Fix
                if (window.WebKitCSSMatrix)
                    curTransform = transformMatrix.m41;
                //Crazy IE10 Matrix
                else if (matrix.length === 16)
                    curTransform = parseFloat(matrix[12]);
                //Normal Browsers
                else
                    curTransform = parseFloat(matrix[4]);
            }
            if (axis === 'y') {
                //Latest Chrome and webkits Fix
                if (window.WebKitCSSMatrix)
                    curTransform = transformMatrix.m42;
                //Crazy IE10 Matrix
                else if (matrix.length === 16)
                    curTransform = parseFloat(matrix[13]);
                //Normal Browsers
                else
                    curTransform = parseFloat(matrix[5]);
            }
            if (s.rtl && curTransform) curTransform = -curTransform;
            return curTransform || 0;
        };
        s.getWrapperTranslate = function (axis) {
            if (typeof axis === 'undefined') {
                axis = s.isHorizontal() ? 'x' : 'y';
            }
            return s.getTranslate(s.wrapper[0], axis);
        };
        
        /*=========================
          Observer
          ===========================*/
        s.observers = [];
        function initObserver(target, options) {
            options = options || {};
            // create an observer instance
            var ObserverFunc = window.MutationObserver || window.WebkitMutationObserver;
            var observer = new ObserverFunc(function (mutations) {
                mutations.forEach(function (mutation) {
                    s.onResize(true);
                    s.emit('onObserverUpdate', s, mutation);
                });
            });
        
            observer.observe(target, {
                attributes: typeof options.attributes === 'undefined' ? true : options.attributes,
                childList: typeof options.childList === 'undefined' ? true : options.childList,
                characterData: typeof options.characterData === 'undefined' ? true : options.characterData
            });
        
            s.observers.push(observer);
        }
        s.initObservers = function () {
            if (s.params.observeParents) {
                var containerParents = s.container.parents();
                for (var i = 0; i < containerParents.length; i++) {
                    initObserver(containerParents[i]);
                }
            }
        
            // Observe container
            initObserver(s.container[0], {childList: false});
        
            // Observe wrapper
            initObserver(s.wrapper[0], {attributes: false});
        };
        s.disconnectObservers = function () {
            for (var i = 0; i < s.observers.length; i++) {
                s.observers[i].disconnect();
            }
            s.observers = [];
        };
        /*=========================
          Loop
          ===========================*/
        // Create looped slides
        s.createLoop = function () {
            // Remove duplicated slides
            s.wrapper.children('.' + s.params.slideClass + '.' + s.params.slideDuplicateClass).remove();
        
            var slides = s.wrapper.children('.' + s.params.slideClass);
        
            if(s.params.slidesPerView === 'auto' && !s.params.loopedSlides) s.params.loopedSlides = slides.length;
        
            s.loopedSlides = parseInt(s.params.loopedSlides || s.params.slidesPerView, 10);
            s.loopedSlides = s.loopedSlides + s.params.loopAdditionalSlides;
            if (s.loopedSlides > slides.length) {
                s.loopedSlides = slides.length;
            }
        
            var prependSlides = [], appendSlides = [], i;
            slides.each(function (index, el) {
                var slide = $(this);
                if (index < s.loopedSlides) appendSlides.push(el);
                if (index < slides.length && index >= slides.length - s.loopedSlides) prependSlides.push(el);
                slide.attr('data-swiper-slide-index', index);
            });
            for (i = 0; i < appendSlides.length; i++) {
                s.wrapper.append($(appendSlides[i].cloneNode(true)).addClass(s.params.slideDuplicateClass));
            }
            for (i = prependSlides.length - 1; i >= 0; i--) {
                s.wrapper.prepend($(prependSlides[i].cloneNode(true)).addClass(s.params.slideDuplicateClass));
            }
        };
        s.destroyLoop = function () {
            s.wrapper.children('.' + s.params.slideClass + '.' + s.params.slideDuplicateClass).remove();
            s.slides.removeAttr('data-swiper-slide-index');
        };
        s.reLoop = function (updatePosition) {
            var oldIndex = s.activeIndex - s.loopedSlides;
            s.destroyLoop();
            s.createLoop();
            s.updateSlidesSize();
            if (updatePosition) {
                s.slideTo(oldIndex + s.loopedSlides, 0, false);
            }
        
        };
        s.fixLoop = function () {
            var newIndex;
            //Fix For Negative Oversliding
            if (s.activeIndex < s.loopedSlides) {
                newIndex = s.slides.length - s.loopedSlides * 3 + s.activeIndex;
                newIndex = newIndex + s.loopedSlides;
                s.slideTo(newIndex, 0, false, true);
            }
            //Fix For Positive Oversliding
            else if ((s.params.slidesPerView === 'auto' && s.activeIndex >= s.loopedSlides * 2) || (s.activeIndex > s.slides.length - s.params.slidesPerView * 2)) {
                newIndex = -s.slides.length + s.activeIndex + s.loopedSlides;
                newIndex = newIndex + s.loopedSlides;
                s.slideTo(newIndex, 0, false, true);
            }
        };
        /*=========================
          Append/Prepend/Remove Slides
          ===========================*/
        s.appendSlide = function (slides) {
            if (s.params.loop) {
                s.destroyLoop();
            }
            if (typeof slides === 'object' && slides.length) {
                for (var i = 0; i < slides.length; i++) {
                    if (slides[i]) s.wrapper.append(slides[i]);
                }
            }
            else {
                s.wrapper.append(slides);
            }
            if (s.params.loop) {
                s.createLoop();
            }
            if (!(s.params.observer && s.support.observer)) {
                s.update(true);
            }
        };
        s.prependSlide = function (slides) {
            if (s.params.loop) {
                s.destroyLoop();
            }
            var newActiveIndex = s.activeIndex + 1;
            if (typeof slides === 'object' && slides.length) {
                for (var i = 0; i < slides.length; i++) {
                    if (slides[i]) s.wrapper.prepend(slides[i]);
                }
                newActiveIndex = s.activeIndex + slides.length;
            }
            else {
                s.wrapper.prepend(slides);
            }
            if (s.params.loop) {
                s.createLoop();
            }
            if (!(s.params.observer && s.support.observer)) {
                s.update(true);
            }
            s.slideTo(newActiveIndex, 0, false);
        };
        s.removeSlide = function (slidesIndexes) {
            if (s.params.loop) {
                s.destroyLoop();
                s.slides = s.wrapper.children('.' + s.params.slideClass);
            }
            var newActiveIndex = s.activeIndex,
                indexToRemove;
            if (typeof slidesIndexes === 'object' && slidesIndexes.length) {
                for (var i = 0; i < slidesIndexes.length; i++) {
                    indexToRemove = slidesIndexes[i];
                    if (s.slides[indexToRemove]) s.slides.eq(indexToRemove).remove();
                    if (indexToRemove < newActiveIndex) newActiveIndex--;
                }
                newActiveIndex = Math.max(newActiveIndex, 0);
            }
            else {
                indexToRemove = slidesIndexes;
                if (s.slides[indexToRemove]) s.slides.eq(indexToRemove).remove();
                if (indexToRemove < newActiveIndex) newActiveIndex--;
                newActiveIndex = Math.max(newActiveIndex, 0);
            }
        
            if (s.params.loop) {
                s.createLoop();
            }
        
            if (!(s.params.observer && s.support.observer)) {
                s.update(true);
            }
            if (s.params.loop) {
                s.slideTo(newActiveIndex + s.loopedSlides, 0, false);
            }
            else {
                s.slideTo(newActiveIndex, 0, false);
            }
        
        };
        s.removeAllSlides = function () {
            var slidesIndexes = [];
            for (var i = 0; i < s.slides.length; i++) {
                slidesIndexes.push(i);
            }
            s.removeSlide(slidesIndexes);
        };
        

        /*=========================
          Effects
          ===========================*/
        s.effects = {
            fade: {
                setTranslate: function () {
                    for (var i = 0; i < s.slides.length; i++) {
                        var slide = s.slides.eq(i);
                        var offset = slide[0].swiperSlideOffset;
                        var tx = -offset;
                        if (!s.params.virtualTranslate) tx = tx - s.translate;
                        var ty = 0;
                        if (!s.isHorizontal()) {
                            ty = tx;
                            tx = 0;
                        }
                        var slideOpacity = s.params.fade.crossFade ?
                                Math.max(1 - Math.abs(slide[0].progress), 0) :
                                1 + Math.min(Math.max(slide[0].progress, -1), 0);
                        slide
                            .css({
                                opacity: slideOpacity
                            })
                            .transform('translate3d(' + tx + 'px, ' + ty + 'px, 0px)');
        
                    }
        
                },
                setTransition: function (duration) {
                    s.slides.transition(duration);
                    if (s.params.virtualTranslate && duration !== 0) {
                        var eventTriggered = false;
                        s.slides.transitionEnd(function () {
                            if (eventTriggered) return;
                            if (!s) return;
                            eventTriggered = true;
                            s.animating = false;
                            var triggerEvents = ['webkitTransitionEnd', 'transitionend', 'oTransitionEnd', 'MSTransitionEnd', 'msTransitionEnd'];
                            for (var i = 0; i < triggerEvents.length; i++) {
                                s.wrapper.trigger(triggerEvents[i]);
                            }
                        });
                    }
                }
            },
            flip: {
                setTranslate: function () {
                    for (var i = 0; i < s.slides.length; i++) {
                        var slide = s.slides.eq(i);
                        var progress = slide[0].progress;
                        if (s.params.flip.limitRotation) {
                            progress = Math.max(Math.min(slide[0].progress, 1), -1);
                        }
                        var offset = slide[0].swiperSlideOffset;
                        var rotate = -180 * progress,
                            rotateY = rotate,
                            rotateX = 0,
                            tx = -offset,
                            ty = 0;
                        if (!s.isHorizontal()) {
                            ty = tx;
                            tx = 0;
                            rotateX = -rotateY;
                            rotateY = 0;
                        }
                        else if (s.rtl) {
                            rotateY = -rotateY;
                        }
        
                        slide[0].style.zIndex = -Math.abs(Math.round(progress)) + s.slides.length;
        
                        if (s.params.flip.slideShadows) {
                            //Set shadows
                            var shadowBefore = s.isHorizontal() ? slide.find('.swiper-slide-shadow-left') : slide.find('.swiper-slide-shadow-top');
                            var shadowAfter = s.isHorizontal() ? slide.find('.swiper-slide-shadow-right') : slide.find('.swiper-slide-shadow-bottom');
                            if (shadowBefore.length === 0) {
                                shadowBefore = $('<div class="swiper-slide-shadow-' + (s.isHorizontal() ? 'left' : 'top') + '"></div>');
                                slide.append(shadowBefore);
                            }
                            if (shadowAfter.length === 0) {
                                shadowAfter = $('<div class="swiper-slide-shadow-' + (s.isHorizontal() ? 'right' : 'bottom') + '"></div>');
                                slide.append(shadowAfter);
                            }
                            if (shadowBefore.length) shadowBefore[0].style.opacity = Math.max(-progress, 0);
                            if (shadowAfter.length) shadowAfter[0].style.opacity = Math.max(progress, 0);
                        }
        
                        slide
                            .transform('translate3d(' + tx + 'px, ' + ty + 'px, 0px) rotateX(' + rotateX + 'deg) rotateY(' + rotateY + 'deg)');
                    }
                },
                setTransition: function (duration) {
                    s.slides.transition(duration).find('.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left').transition(duration);
                    if (s.params.virtualTranslate && duration !== 0) {
                        var eventTriggered = false;
                        s.slides.eq(s.activeIndex).transitionEnd(function () {
                            if (eventTriggered) return;
                            if (!s) return;
                            if (!$(this).hasClass(s.params.slideActiveClass)) return;
                            eventTriggered = true;
                            s.animating = false;
                            var triggerEvents = ['webkitTransitionEnd', 'transitionend', 'oTransitionEnd', 'MSTransitionEnd', 'msTransitionEnd'];
                            for (var i = 0; i < triggerEvents.length; i++) {
                                s.wrapper.trigger(triggerEvents[i]);
                            }
                        });
                    }
                }
            },
            cube: {
                setTranslate: function () {
                    var wrapperRotate = 0, cubeShadow;
                    if (s.params.cube.shadow) {
                        if (s.isHorizontal()) {
                            cubeShadow = s.wrapper.find('.swiper-cube-shadow');
                            if (cubeShadow.length === 0) {
                                cubeShadow = $('<div class="swiper-cube-shadow"></div>');
                                s.wrapper.append(cubeShadow);
                            }
                            cubeShadow.css({height: s.width + 'px'});
                        }
                        else {
                            cubeShadow = s.container.find('.swiper-cube-shadow');
                            if (cubeShadow.length === 0) {
                                cubeShadow = $('<div class="swiper-cube-shadow"></div>');
                                s.container.append(cubeShadow);
                            }
                        }
                    }
                    for (var i = 0; i < s.slides.length; i++) {
                        var slide = s.slides.eq(i);
                        var slideAngle = i * 90;
                        var round = Math.floor(slideAngle / 360);
                        if (s.rtl) {
                            slideAngle = -slideAngle;
                            round = Math.floor(-slideAngle / 360);
                        }
                        var progress = Math.max(Math.min(slide[0].progress, 1), -1);
                        var tx = 0, ty = 0, tz = 0;
                        if (i % 4 === 0) {
                            tx = - round * 4 * s.size;
                            tz = 0;
                        }
                        else if ((i - 1) % 4 === 0) {
                            tx = 0;
                            tz = - round * 4 * s.size;
                        }
                        else if ((i - 2) % 4 === 0) {
                            tx = s.size + round * 4 * s.size;
                            tz = s.size;
                        }
                        else if ((i - 3) % 4 === 0) {
                            tx = - s.size;
                            tz = 3 * s.size + s.size * 4 * round;
                        }
                        if (s.rtl) {
                            tx = -tx;
                        }
        
                        if (!s.isHorizontal()) {
                            ty = tx;
                            tx = 0;
                        }
        
                        var transform = 'rotateX(' + (s.isHorizontal() ? 0 : -slideAngle) + 'deg) rotateY(' + (s.isHorizontal() ? slideAngle : 0) + 'deg) translate3d(' + tx + 'px, ' + ty + 'px, ' + tz + 'px)';
                        if (progress <= 1 && progress > -1) {
                            wrapperRotate = i * 90 + progress * 90;
                            if (s.rtl) wrapperRotate = -i * 90 - progress * 90;
                        }
                        slide.transform(transform);
                        if (s.params.cube.slideShadows) {
                            //Set shadows
                            var shadowBefore = s.isHorizontal() ? slide.find('.swiper-slide-shadow-left') : slide.find('.swiper-slide-shadow-top');
                            var shadowAfter = s.isHorizontal() ? slide.find('.swiper-slide-shadow-right') : slide.find('.swiper-slide-shadow-bottom');
                            if (shadowBefore.length === 0) {
                                shadowBefore = $('<div class="swiper-slide-shadow-' + (s.isHorizontal() ? 'left' : 'top') + '"></div>');
                                slide.append(shadowBefore);
                            }
                            if (shadowAfter.length === 0) {
                                shadowAfter = $('<div class="swiper-slide-shadow-' + (s.isHorizontal() ? 'right' : 'bottom') + '"></div>');
                                slide.append(shadowAfter);
                            }
                            if (shadowBefore.length) shadowBefore[0].style.opacity = Math.max(-progress, 0);
                            if (shadowAfter.length) shadowAfter[0].style.opacity = Math.max(progress, 0);
                        }
                    }
                    s.wrapper.css({
                        '-webkit-transform-origin': '50% 50% -' + (s.size / 2) + 'px',
                        '-moz-transform-origin': '50% 50% -' + (s.size / 2) + 'px',
                        '-ms-transform-origin': '50% 50% -' + (s.size / 2) + 'px',
                        'transform-origin': '50% 50% -' + (s.size / 2) + 'px'
                    });
        
                    if (s.params.cube.shadow) {
                        if (s.isHorizontal()) {
                            cubeShadow.transform('translate3d(0px, ' + (s.width / 2 + s.params.cube.shadowOffset) + 'px, ' + (-s.width / 2) + 'px) rotateX(90deg) rotateZ(0deg) scale(' + (s.params.cube.shadowScale) + ')');
                        }
                        else {
                            var shadowAngle = Math.abs(wrapperRotate) - Math.floor(Math.abs(wrapperRotate) / 90) * 90;
                            var multiplier = 1.5 - (Math.sin(shadowAngle * 2 * Math.PI / 360) / 2 + Math.cos(shadowAngle * 2 * Math.PI / 360) / 2);
                            var scale1 = s.params.cube.shadowScale,
                                scale2 = s.params.cube.shadowScale / multiplier,
                                offset = s.params.cube.shadowOffset;
                            cubeShadow.transform('scale3d(' + scale1 + ', 1, ' + scale2 + ') translate3d(0px, ' + (s.height / 2 + offset) + 'px, ' + (-s.height / 2 / scale2) + 'px) rotateX(-90deg)');
                        }
                    }
                    var zFactor = (s.isSafari || s.isUiWebView) ? (-s.size / 2) : 0;
                    s.wrapper.transform('translate3d(0px,0,' + zFactor + 'px) rotateX(' + (s.isHorizontal() ? 0 : wrapperRotate) + 'deg) rotateY(' + (s.isHorizontal() ? -wrapperRotate : 0) + 'deg)');
                },
                setTransition: function (duration) {
                    s.slides.transition(duration).find('.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left').transition(duration);
                    if (s.params.cube.shadow && !s.isHorizontal()) {
                        s.container.find('.swiper-cube-shadow').transition(duration);
                    }
                }
            },
            coverflow: {
                setTranslate: function () {
                    var transform = s.translate;
                    var center = s.isHorizontal() ? -transform + s.width / 2 : -transform + s.height / 2;
                    var rotate = s.isHorizontal() ? s.params.coverflow.rotate: -s.params.coverflow.rotate;
                    var translate = s.params.coverflow.depth;
                    //Each slide offset from center
                    for (var i = 0, length = s.slides.length; i < length; i++) {
                        var slide = s.slides.eq(i);
                        var slideSize = s.slidesSizesGrid[i];
                        var slideOffset = slide[0].swiperSlideOffset;
                        var offsetMultiplier = (center - slideOffset - slideSize / 2) / slideSize * s.params.coverflow.modifier;
        
                        var rotateY = s.isHorizontal() ? rotate * offsetMultiplier : 0;
                        var rotateX = s.isHorizontal() ? 0 : rotate * offsetMultiplier;
                        // var rotateZ = 0
                        var translateZ = -translate * Math.abs(offsetMultiplier);
        
                        var translateY = s.isHorizontal() ? 0 : s.params.coverflow.stretch * (offsetMultiplier);
                        var translateX = s.isHorizontal() ? s.params.coverflow.stretch * (offsetMultiplier) : 0;
        
                        //Fix for ultra small values
                        if (Math.abs(translateX) < 0.001) translateX = 0;
                        if (Math.abs(translateY) < 0.001) translateY = 0;
                        if (Math.abs(translateZ) < 0.001) translateZ = 0;
                        if (Math.abs(rotateY) < 0.001) rotateY = 0;
                        if (Math.abs(rotateX) < 0.001) rotateX = 0;
        
                        var slideTransform = 'translate3d(' + translateX + 'px,' + translateY + 'px,' + translateZ + 'px)  rotateX(' + rotateX + 'deg) rotateY(' + rotateY + 'deg)';
        
                        slide.transform(slideTransform);
                        slide[0].style.zIndex = -Math.abs(Math.round(offsetMultiplier)) + 1;
                        if (s.params.coverflow.slideShadows) {
                            //Set shadows
                            var shadowBefore = s.isHorizontal() ? slide.find('.swiper-slide-shadow-left') : slide.find('.swiper-slide-shadow-top');
                            var shadowAfter = s.isHorizontal() ? slide.find('.swiper-slide-shadow-right') : slide.find('.swiper-slide-shadow-bottom');
                            if (shadowBefore.length === 0) {
                                shadowBefore = $('<div class="swiper-slide-shadow-' + (s.isHorizontal() ? 'left' : 'top') + '"></div>');
                                slide.append(shadowBefore);
                            }
                            if (shadowAfter.length === 0) {
                                shadowAfter = $('<div class="swiper-slide-shadow-' + (s.isHorizontal() ? 'right' : 'bottom') + '"></div>');
                                slide.append(shadowAfter);
                            }
                            if (shadowBefore.length) shadowBefore[0].style.opacity = offsetMultiplier > 0 ? offsetMultiplier : 0;
                            if (shadowAfter.length) shadowAfter[0].style.opacity = (-offsetMultiplier) > 0 ? -offsetMultiplier : 0;
                        }
                    }
        
                    //Set correct perspective for IE10
                    if (s.browser.ie) {
                        var ws = s.wrapper[0].style;
                        ws.perspectiveOrigin = center + 'px 50%';
                    }
                },
                setTransition: function (duration) {
                    s.slides.transition(duration).find('.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left').transition(duration);
                }
            }
        };

        /*=========================
          Images Lazy Loading
          ===========================*/
        s.lazy = {
            initialImageLoaded: false,
            loadImageInSlide: function (index, loadInDuplicate) {
                if (typeof index === 'undefined') return;
                if (typeof loadInDuplicate === 'undefined') loadInDuplicate = true;
                if (s.slides.length === 0) return;
        
                var slide = s.slides.eq(index);
                var img = slide.find('.' + s.params.lazyLoadingClass + ':not(.' + s.params.lazyStatusLoadedClass + '):not(.' + s.params.lazyStatusLoadingClass + ')');
                if (slide.hasClass(s.params.lazyLoadingClass) && !slide.hasClass(s.params.lazyStatusLoadedClass) && !slide.hasClass(s.params.lazyStatusLoadingClass)) {
                    img = img.add(slide[0]);
                }
                if (img.length === 0) return;
        
                img.each(function () {
                    var _img = $(this);
                    _img.addClass(s.params.lazyStatusLoadingClass);
                    var background = _img.attr('data-background');
                    var src = _img.attr('data-src'),
                        srcset = _img.attr('data-srcset'),
                        sizes = _img.attr('data-sizes');
                    s.loadImage(_img[0], (src || background), srcset, sizes, false, function () {
                        if (background) {
                            _img.css('background-image', 'url("' + background + '")');
                            _img.removeAttr('data-background');
                        }
                        else {
                            if (srcset) {
                                _img.attr('srcset', srcset);
                                _img.removeAttr('data-srcset');
                            }
                            if (sizes) {
                                _img.attr('sizes', sizes);
                                _img.removeAttr('data-sizes');
                            }
                            if (src) {
                                _img.attr('src', src);
                                _img.removeAttr('data-src');
                            }
        
                        }
        
                        _img.addClass(s.params.lazyStatusLoadedClass).removeClass(s.params.lazyStatusLoadingClass);
                        slide.find('.' + s.params.lazyPreloaderClass + ', .' + s.params.preloaderClass).remove();
                        if (s.params.loop && loadInDuplicate) {
                            var slideOriginalIndex = slide.attr('data-swiper-slide-index');
                            if (slide.hasClass(s.params.slideDuplicateClass)) {
                                var originalSlide = s.wrapper.children('[data-swiper-slide-index="' + slideOriginalIndex + '"]:not(.' + s.params.slideDuplicateClass + ')');
                                s.lazy.loadImageInSlide(originalSlide.index(), false);
                            }
                            else {
                                var duplicatedSlide = s.wrapper.children('.' + s.params.slideDuplicateClass + '[data-swiper-slide-index="' + slideOriginalIndex + '"]');
                                s.lazy.loadImageInSlide(duplicatedSlide.index(), false);
                            }
                        }
                        s.emit('onLazyImageReady', s, slide[0], _img[0]);
                    });
        
                    s.emit('onLazyImageLoad', s, slide[0], _img[0]);
                });
        
            },
            load: function () {
                var i;
                var slidesPerView = s.params.slidesPerView;
                if (slidesPerView === 'auto') {
                    slidesPerView = 0;
                }
                if (!s.lazy.initialImageLoaded) s.lazy.initialImageLoaded = true;
                if (s.params.watchSlidesVisibility) {
                    s.wrapper.children('.' + s.params.slideVisibleClass).each(function () {
                        s.lazy.loadImageInSlide($(this).index());
                    });
                }
                else {
                    if (slidesPerView > 1) {
                        for (i = s.activeIndex; i < s.activeIndex + slidesPerView ; i++) {
                            if (s.slides[i]) s.lazy.loadImageInSlide(i);
                        }
                    }
                    else {
                        s.lazy.loadImageInSlide(s.activeIndex);
                    }
                }
                if (s.params.lazyLoadingInPrevNext) {
                    if (slidesPerView > 1 || (s.params.lazyLoadingInPrevNextAmount && s.params.lazyLoadingInPrevNextAmount > 1)) {
                        var amount = s.params.lazyLoadingInPrevNextAmount;
                        var spv = slidesPerView;
                        var maxIndex = Math.min(s.activeIndex + spv + Math.max(amount, spv), s.slides.length);
                        var minIndex = Math.max(s.activeIndex - Math.max(spv, amount), 0);
                        // Next Slides
                        for (i = s.activeIndex + slidesPerView; i < maxIndex; i++) {
                            if (s.slides[i]) s.lazy.loadImageInSlide(i);
                        }
                        // Prev Slides
                        for (i = minIndex; i < s.activeIndex ; i++) {
                            if (s.slides[i]) s.lazy.loadImageInSlide(i);
                        }
                    }
                    else {
                        var nextSlide = s.wrapper.children('.' + s.params.slideNextClass);
                        if (nextSlide.length > 0) s.lazy.loadImageInSlide(nextSlide.index());
        
                        var prevSlide = s.wrapper.children('.' + s.params.slidePrevClass);
                        if (prevSlide.length > 0) s.lazy.loadImageInSlide(prevSlide.index());
                    }
                }
            },
            onTransitionStart: function () {
                if (s.params.lazyLoading) {
                    if (s.params.lazyLoadingOnTransitionStart || (!s.params.lazyLoadingOnTransitionStart && !s.lazy.initialImageLoaded)) {
                        s.lazy.load();
                    }
                }
            },
            onTransitionEnd: function () {
                if (s.params.lazyLoading && !s.params.lazyLoadingOnTransitionStart) {
                    s.lazy.load();
                }
            }
        };
        

        /*=========================
          Scrollbar
          ===========================*/
        s.scrollbar = {
            isTouched: false,
            setDragPosition: function (e) {
                var sb = s.scrollbar;
                var x = 0, y = 0;
                var translate;
                var pointerPosition = s.isHorizontal() ?
                    ((e.type === 'touchstart' || e.type === 'touchmove') ? e.targetTouches[0].pageX : e.pageX || e.clientX) :
                    ((e.type === 'touchstart' || e.type === 'touchmove') ? e.targetTouches[0].pageY : e.pageY || e.clientY) ;
                var position = (pointerPosition) - sb.track.offset()[s.isHorizontal() ? 'left' : 'top'] - sb.dragSize / 2;
                var positionMin = -s.minTranslate() * sb.moveDivider;
                var positionMax = -s.maxTranslate() * sb.moveDivider;
                if (position < positionMin) {
                    position = positionMin;
                }
                else if (position > positionMax) {
                    position = positionMax;
                }
                position = -position / sb.moveDivider;
                s.updateProgress(position);
                s.setWrapperTranslate(position, true);
            },
            dragStart: function (e) {
                var sb = s.scrollbar;
                sb.isTouched = true;
                e.preventDefault();
                e.stopPropagation();
        
                sb.setDragPosition(e);
                clearTimeout(sb.dragTimeout);
        
                sb.track.transition(0);
                if (s.params.scrollbarHide) {
                    sb.track.css('opacity', 1);
                }
                s.wrapper.transition(100);
                sb.drag.transition(100);
                s.emit('onScrollbarDragStart', s);
            },
            dragMove: function (e) {
                var sb = s.scrollbar;
                if (!sb.isTouched) return;
                if (e.preventDefault) e.preventDefault();
                else e.returnValue = false;
                sb.setDragPosition(e);
                s.wrapper.transition(0);
                sb.track.transition(0);
                sb.drag.transition(0);
                s.emit('onScrollbarDragMove', s);
            },
            dragEnd: function (e) {
                var sb = s.scrollbar;
                if (!sb.isTouched) return;
                sb.isTouched = false;
                if (s.params.scrollbarHide) {
                    clearTimeout(sb.dragTimeout);
                    sb.dragTimeout = setTimeout(function () {
                        sb.track.css('opacity', 0);
                        sb.track.transition(400);
                    }, 1000);
        
                }
                s.emit('onScrollbarDragEnd', s);
                if (s.params.scrollbarSnapOnRelease) {
                    s.slideReset();
                }
            },
            draggableEvents: (function () {
                if ((s.params.simulateTouch === false && !s.support.touch)) return s.touchEventsDesktop;
                else return s.touchEvents;
            })(),
            enableDraggable: function () {
                var sb = s.scrollbar;
                var target = s.support.touch ? sb.track : document;
                $(sb.track).on(sb.draggableEvents.start, sb.dragStart);
                $(target).on(sb.draggableEvents.move, sb.dragMove);
                $(target).on(sb.draggableEvents.end, sb.dragEnd);
            },
            disableDraggable: function () {
                var sb = s.scrollbar;
                var target = s.support.touch ? sb.track : document;
                $(sb.track).off(sb.draggableEvents.start, sb.dragStart);
                $(target).off(sb.draggableEvents.move, sb.dragMove);
                $(target).off(sb.draggableEvents.end, sb.dragEnd);
            },
            set: function () {
                if (!s.params.scrollbar) return;
                var sb = s.scrollbar;
                sb.track = $(s.params.scrollbar);
                if (s.params.uniqueNavElements && typeof s.params.scrollbar === 'string' && sb.track.length > 1 && s.container.find(s.params.scrollbar).length === 1) {
                    sb.track = s.container.find(s.params.scrollbar);
                }
                sb.drag = sb.track.find('.swiper-scrollbar-drag');
                if (sb.drag.length === 0) {
                    sb.drag = $('<div class="swiper-scrollbar-drag"></div>');
                    sb.track.append(sb.drag);
                }
                sb.drag[0].style.width = '';
                sb.drag[0].style.height = '';
                sb.trackSize = s.isHorizontal() ? sb.track[0].offsetWidth : sb.track[0].offsetHeight;
        
                sb.divider = s.size / s.virtualSize;
                sb.moveDivider = sb.divider * (sb.trackSize / s.size);
                sb.dragSize = sb.trackSize * sb.divider;
        
                if (s.isHorizontal()) {
                    sb.drag[0].style.width = sb.dragSize + 'px';
                }
                else {
                    sb.drag[0].style.height = sb.dragSize + 'px';
                }
        
                if (sb.divider >= 1) {
                    sb.track[0].style.display = 'none';
                }
                else {
                    sb.track[0].style.display = '';
                }
                if (s.params.scrollbarHide) {
                    sb.track[0].style.opacity = 0;
                }
            },
            setTranslate: function () {
                if (!s.params.scrollbar) return;
                var diff;
                var sb = s.scrollbar;
                var translate = s.translate || 0;
                var newPos;
        
                var newSize = sb.dragSize;
                newPos = (sb.trackSize - sb.dragSize) * s.progress;
                if (s.rtl && s.isHorizontal()) {
                    newPos = -newPos;
                    if (newPos > 0) {
                        newSize = sb.dragSize - newPos;
                        newPos = 0;
                    }
                    else if (-newPos + sb.dragSize > sb.trackSize) {
                        newSize = sb.trackSize + newPos;
                    }
                }
                else {
                    if (newPos < 0) {
                        newSize = sb.dragSize + newPos;
                        newPos = 0;
                    }
                    else if (newPos + sb.dragSize > sb.trackSize) {
                        newSize = sb.trackSize - newPos;
                    }
                }
                if (s.isHorizontal()) {
                    if (s.support.transforms3d) {
                        sb.drag.transform('translate3d(' + (newPos) + 'px, 0, 0)');
                    }
                    else {
                        sb.drag.transform('translateX(' + (newPos) + 'px)');
                    }
                    sb.drag[0].style.width = newSize + 'px';
                }
                else {
                    if (s.support.transforms3d) {
                        sb.drag.transform('translate3d(0px, ' + (newPos) + 'px, 0)');
                    }
                    else {
                        sb.drag.transform('translateY(' + (newPos) + 'px)');
                    }
                    sb.drag[0].style.height = newSize + 'px';
                }
                if (s.params.scrollbarHide) {
                    clearTimeout(sb.timeout);
                    sb.track[0].style.opacity = 1;
                    sb.timeout = setTimeout(function () {
                        sb.track[0].style.opacity = 0;
                        sb.track.transition(400);
                    }, 1000);
                }
            },
            setTransition: function (duration) {
                if (!s.params.scrollbar) return;
                s.scrollbar.drag.transition(duration);
            }
        };

        /*=========================
          Controller
          ===========================*/
        s.controller = {
            LinearSpline: function (x, y) {
                this.x = x;
                this.y = y;
                this.lastIndex = x.length - 1;
                // Given an x value (x2), return the expected y2 value:
                // (x1,y1) is the known point before given value,
                // (x3,y3) is the known point after given value.
                var i1, i3;
                var l = this.x.length;
        
                this.interpolate = function (x2) {
                    if (!x2) return 0;
        
                    // Get the indexes of x1 and x3 (the array indexes before and after given x2):
                    i3 = binarySearch(this.x, x2);
                    i1 = i3 - 1;
        
                    // We have our indexes i1 & i3, so we can calculate already:
                    // y2 := ((x2−x1) × (y3−y1)) ÷ (x3−x1) + y1
                    return ((x2 - this.x[i1]) * (this.y[i3] - this.y[i1])) / (this.x[i3] - this.x[i1]) + this.y[i1];
                };
        
                var binarySearch = (function() {
                    var maxIndex, minIndex, guess;
                    return function(array, val) {
                        minIndex = -1;
                        maxIndex = array.length;
                        while (maxIndex - minIndex > 1)
                            if (array[guess = maxIndex + minIndex >> 1] <= val) {
                                minIndex = guess;
                            } else {
                                maxIndex = guess;
                            }
                        return maxIndex;
                    };
                })();
            },
            //xxx: for now i will just save one spline function to to
            getInterpolateFunction: function(c){
                if(!s.controller.spline) s.controller.spline = s.params.loop ?
                    new s.controller.LinearSpline(s.slidesGrid, c.slidesGrid) :
                    new s.controller.LinearSpline(s.snapGrid, c.snapGrid);
            },
            setTranslate: function (translate, byController) {
               var controlled = s.params.control;
               var multiplier, controlledTranslate;
               function setControlledTranslate(c) {
                    // this will create an Interpolate function based on the snapGrids
                    // x is the Grid of the scrolled scroller and y will be the controlled scroller
                    // it makes sense to create this only once and recall it for the interpolation
                    // the function does a lot of value caching for performance
                    translate = c.rtl && c.params.direction === 'horizontal' ? -s.translate : s.translate;
                    if (s.params.controlBy === 'slide') {
                        s.controller.getInterpolateFunction(c);
                        // i am not sure why the values have to be multiplicated this way, tried to invert the snapGrid
                        // but it did not work out
                        controlledTranslate = -s.controller.spline.interpolate(-translate);
                    }
        
                    if(!controlledTranslate || s.params.controlBy === 'container'){
                        multiplier = (c.maxTranslate() - c.minTranslate()) / (s.maxTranslate() - s.minTranslate());
                        controlledTranslate = (translate - s.minTranslate()) * multiplier + c.minTranslate();
                    }
        
                    if (s.params.controlInverse) {
                        controlledTranslate = c.maxTranslate() - controlledTranslate;
                    }
                    c.updateProgress(controlledTranslate);
                    c.setWrapperTranslate(controlledTranslate, false, s);
                    c.updateActiveIndex();
               }
               if (s.isArray(controlled)) {
                   for (var i = 0; i < controlled.length; i++) {
                       if (controlled[i] !== byController && controlled[i] instanceof Swiper) {
                           setControlledTranslate(controlled[i]);
                       }
                   }
               }
               else if (controlled instanceof Swiper && byController !== controlled) {
        
                   setControlledTranslate(controlled);
               }
            },
            setTransition: function (duration, byController) {
                var controlled = s.params.control;
                var i;
                function setControlledTransition(c) {
                    c.setWrapperTransition(duration, s);
                    if (duration !== 0) {
                        c.onTransitionStart();
                        c.wrapper.transitionEnd(function(){
                            if (!controlled) return;
                            if (c.params.loop && s.params.controlBy === 'slide') {
                                c.fixLoop();
                            }
                            c.onTransitionEnd();
        
                        });
                    }
                }
                if (s.isArray(controlled)) {
                    for (i = 0; i < controlled.length; i++) {
                        if (controlled[i] !== byController && controlled[i] instanceof Swiper) {
                            setControlledTransition(controlled[i]);
                        }
                    }
                }
                else if (controlled instanceof Swiper && byController !== controlled) {
                    setControlledTransition(controlled);
                }
            }
        };

        /*=========================
          Hash Navigation
          ===========================*/
        s.hashnav = {
            onHashCange: function (e, a) {
                var newHash = document.location.hash.replace('#', '');
                var activeSlideHash = s.slides.eq(s.activeIndex).attr('data-hash');
                if (newHash !== activeSlideHash) {
                    s.slideTo(s.wrapper.children('.' + s.params.slideClass + '[data-hash="' + (newHash) + '"]').index());
                }
            },
            attachEvents: function (detach) {
                var action = detach ? 'off' : 'on';
                $(window)[action]('hashchange', s.hashnav.onHashCange);
            },
            setHash: function () {
                if (!s.hashnav.initialized || !s.params.hashnav) return;
                if (s.params.replaceState && window.history && window.history.replaceState) {
                    window.history.replaceState(null, null, ('#' + s.slides.eq(s.activeIndex).attr('data-hash') || ''));
                } else {
                    var slide = s.slides.eq(s.activeIndex);
                    var hash = slide.attr('data-hash') || slide.attr('data-history');
                    document.location.hash = hash || '';
                }
            },
            init: function () {
                if (!s.params.hashnav || s.params.history) return;
                s.hashnav.initialized = true;
                var hash = document.location.hash.replace('#', '');
                if (hash) {
                    var speed = 0;
                    for (var i = 0, length = s.slides.length; i < length; i++) {
                        var slide = s.slides.eq(i);
                        var slideHash = slide.attr('data-hash') || slide.attr('data-history');
                        if (slideHash === hash && !slide.hasClass(s.params.slideDuplicateClass)) {
                            var index = slide.index();
                            s.slideTo(index, speed, s.params.runCallbacksOnInit, true);
                        }
                    }
                }
                if (s.params.hashnavWatchState) s.hashnav.attachEvents();
            },
            destroy: function () {
                if (s.params.hashnavWatchState) s.hashnav.attachEvents(true);
            }
        };

        /*=========================
          History Api with fallback to Hashnav
          ===========================*/
        s.history = {
            init: function () {
                if (!s.params.history) return;
                if (!window.history || !window.history.pushState) {
                    s.params.history = false;
                    s.params.hashnav = true;
                    return;
                }
                s.history.initialized = true;
                this.paths = this.getPathValues();
                if (!this.paths.key && !this.paths.value) return;
                this.scrollToSlide(0, this.paths.value, s.params.runCallbacksOnInit);
                if (!s.params.replaceState) {
                    window.addEventListener('popstate', this.setHistoryPopState);
                }
            },
            setHistoryPopState: function() {
                s.history.paths = s.history.getPathValues();
                s.history.scrollToSlide(s.params.speed, s.history.paths.value, false);
            },
            getPathValues: function() {
                var pathArray = window.location.pathname.slice(1).split('/');
                var total = pathArray.length;
                var key = pathArray[total - 2];
                var value = pathArray[total - 1];
                return { key: key, value: value };
            },
            setHistory: function (key, index) {
                if (!s.history.initialized || !s.params.history) return;
                var slide = s.slides.eq(index);
                var value = this.slugify(slide.attr('data-history'));
                if (!window.location.pathname.includes(key)) {
                    value = key + '/' + value;
                }
                if (s.params.replaceState) {
                    window.history.replaceState(null, null, value);
                } else {
                    window.history.pushState(null, null, value);
                }
            },
            slugify: function(text) {
                return text.toString().toLowerCase()
                    .replace(/\s+/g, '-')
                    .replace(/[^\w\-]+/g, '')
                    .replace(/\-\-+/g, '-')
                    .replace(/^-+/, '')
                    .replace(/-+$/, '');
            },
            scrollToSlide: function(speed, value, runCallbacks) {
                if (value) {
                    for (var i = 0, length = s.slides.length; i < length; i++) {
                        var slide = s.slides.eq(i);
                        var slideHistory = this.slugify(slide.attr('data-history'));
                        if (slideHistory === value && !slide.hasClass(s.params.slideDuplicateClass)) {
                            var index = slide.index();
                            s.slideTo(index, speed, runCallbacks);
                        }
                    }
                } else {
                    s.slideTo(0, speed, runCallbacks);
                }
            }
        };

        /*=========================
          Keyboard Control
          ===========================*/
        function handleKeyboard(e) {
            if (e.originalEvent) e = e.originalEvent; //jquery fix
            var kc = e.keyCode || e.charCode;
            // Directions locks
            if (!s.params.allowSwipeToNext && (s.isHorizontal() && kc === 39 || !s.isHorizontal() && kc === 40)) {
                return false;
            }
            if (!s.params.allowSwipeToPrev && (s.isHorizontal() && kc === 37 || !s.isHorizontal() && kc === 38)) {
                return false;
            }
            if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey) {
                return;
            }
            if (document.activeElement && document.activeElement.nodeName && (document.activeElement.nodeName.toLowerCase() === 'input' || document.activeElement.nodeName.toLowerCase() === 'textarea')) {
                return;
            }
            if (kc === 37 || kc === 39 || kc === 38 || kc === 40) {
                var inView = false;
                //Check that swiper should be inside of visible area of window
                if (s.container.parents('.' + s.params.slideClass).length > 0 && s.container.parents('.' + s.params.slideActiveClass).length === 0) {
                    return;
                }
                var windowScroll = {
                    left: window.pageXOffset,
                    top: window.pageYOffset
                };
                var windowWidth = window.innerWidth;
                var windowHeight = window.innerHeight;
                var swiperOffset = s.container.offset();
                if (s.rtl) swiperOffset.left = swiperOffset.left - s.container[0].scrollLeft;
                var swiperCoord = [
                    [swiperOffset.left, swiperOffset.top],
                    [swiperOffset.left + s.width, swiperOffset.top],
                    [swiperOffset.left, swiperOffset.top + s.height],
                    [swiperOffset.left + s.width, swiperOffset.top + s.height]
                ];
                for (var i = 0; i < swiperCoord.length; i++) {
                    var point = swiperCoord[i];
                    if (
                        point[0] >= windowScroll.left && point[0] <= windowScroll.left + windowWidth &&
                        point[1] >= windowScroll.top && point[1] <= windowScroll.top + windowHeight
                    ) {
                        inView = true;
                    }
        
                }
                if (!inView) return;
            }
            if (s.isHorizontal()) {
                if (kc === 37 || kc === 39) {
                    if (e.preventDefault) e.preventDefault();
                    else e.returnValue = false;
                }
                if ((kc === 39 && !s.rtl) || (kc === 37 && s.rtl)) s.slideNext();
                if ((kc === 37 && !s.rtl) || (kc === 39 && s.rtl)) s.slidePrev();
            }
            else {
                if (kc === 38 || kc === 40) {
                    if (e.preventDefault) e.preventDefault();
                    else e.returnValue = false;
                }
                if (kc === 40) s.slideNext();
                if (kc === 38) s.slidePrev();
            }
        }
        s.disableKeyboardControl = function () {
            s.params.keyboardControl = false;
            $(document).off('keydown', handleKeyboard);
        };
        s.enableKeyboardControl = function () {
            s.params.keyboardControl = true;
            $(document).on('keydown', handleKeyboard);
        };
        

        /*=========================
          Mousewheel Control
          ===========================*/
        s.mousewheel = {
            event: false,
            lastScrollTime: (new window.Date()).getTime()
        };
        if (s.params.mousewheelControl) {
            /**
             * The best combination if you prefer spinX + spinY normalization.  It favors
             * the older DOMMouseScroll for Firefox, as FF does not include wheelDelta with
             * 'wheel' event, making spin speed determination impossible.
             */
            s.mousewheel.event = (navigator.userAgent.indexOf('firefox') > -1) ?
                'DOMMouseScroll' :
                isEventSupported() ?
                    'wheel' : 'mousewheel';
        }
        
        function isEventSupported() {
            var eventName = 'onwheel';
            var isSupported = eventName in document;
        
            if (!isSupported) {
                var element = document.createElement('div');
                element.setAttribute(eventName, 'return;');
                isSupported = typeof element[eventName] === 'function';
            }
        
            if (!isSupported &&
                document.implementation &&
                document.implementation.hasFeature &&
                    // always returns true in newer browsers as per the standard.
                    // @see http://dom.spec.whatwg.org/#dom-domimplementation-hasfeature
                document.implementation.hasFeature('', '') !== true ) {
                // This is the only way to test support for the `wheel` event in IE9+.
                isSupported = document.implementation.hasFeature('Events.wheel', '3.0');
            }
        
            return isSupported;
        }
        
        function handleMousewheel(e) {
            if (e.originalEvent) e = e.originalEvent; //jquery fix
            var delta = 0;
            var rtlFactor = s.rtl ? -1 : 1;
        
            var data = normalizeWheel( e );
        
            if (s.params.mousewheelForceToAxis) {
                if (s.isHorizontal()) {
                    if (Math.abs(data.pixelX) > Math.abs(data.pixelY)) delta = data.pixelX * rtlFactor;
                    else return;
                }
                else {
                    if (Math.abs(data.pixelY) > Math.abs(data.pixelX)) delta = data.pixelY;
                    else return;
                }
            }
            else {
                delta = Math.abs(data.pixelX) > Math.abs(data.pixelY) ? - data.pixelX * rtlFactor : - data.pixelY;
            }
        
            if (delta === 0) return;
        
            if (s.params.mousewheelInvert) delta = -delta;
        
            if (!s.params.freeMode) {
                if ((new window.Date()).getTime() - s.mousewheel.lastScrollTime > 60) {
                    if (delta < 0) {
                        if ((!s.isEnd || s.params.loop) && !s.animating) {
                            s.slideNext();
                            s.emit('onScroll', s, e);
                        }
                        else if (s.params.mousewheelReleaseOnEdges) return true;
                    }
                    else {
                        if ((!s.isBeginning || s.params.loop) && !s.animating) {
                            s.slidePrev();
                            s.emit('onScroll', s, e);
                        }
                        else if (s.params.mousewheelReleaseOnEdges) return true;
                    }
                }
                s.mousewheel.lastScrollTime = (new window.Date()).getTime();
        
            }
            else {
                //Freemode or scrollContainer:
                var position = s.getWrapperTranslate() + delta * s.params.mousewheelSensitivity;
                var wasBeginning = s.isBeginning,
                    wasEnd = s.isEnd;
        
                if (position >= s.minTranslate()) position = s.minTranslate();
                if (position <= s.maxTranslate()) position = s.maxTranslate();
        
                s.setWrapperTransition(0);
                s.setWrapperTranslate(position);
                s.updateProgress();
                s.updateActiveIndex();
        
                if (!wasBeginning && s.isBeginning || !wasEnd && s.isEnd) {
                    s.updateClasses();
                }
        
                if (s.params.freeModeSticky) {
                    clearTimeout(s.mousewheel.timeout);
                    s.mousewheel.timeout = setTimeout(function () {
                        s.slideReset();
                    }, 300);
                }
                else {
                    if (s.params.lazyLoading && s.lazy) {
                        s.lazy.load();
                    }
                }
                // Emit event
                s.emit('onScroll', s, e);
        
                // Stop autoplay
                if (s.params.autoplay && s.params.autoplayDisableOnInteraction) s.stopAutoplay();
        
                // Return page scroll on edge positions
                if (position === 0 || position === s.maxTranslate()) return;
            }
        
            if (e.preventDefault) e.preventDefault();
            else e.returnValue = false;
            return false;
        }
        s.disableMousewheelControl = function () {
            if (!s.mousewheel.event) return false;
            var target = s.container;
            if (s.params.mousewheelEventsTarged !== 'container') {
                target = $(s.params.mousewheelEventsTarged);
            }
            target.off(s.mousewheel.event, handleMousewheel);
            return true;
        };
        
        s.enableMousewheelControl = function () {
            if (!s.mousewheel.event) return false;
            var target = s.container;
            if (s.params.mousewheelEventsTarged !== 'container') {
                target = $(s.params.mousewheelEventsTarged);
            }
            target.on(s.mousewheel.event, handleMousewheel);
            return true;
        };
        
        /**
         * Mouse wheel (and 2-finger trackpad) support on the web sucks.  It is
         * complicated, thus this doc is long and (hopefully) detailed enough to answer
         * your questions.
         *
         * If you need to react to the mouse wheel in a predictable way, this code is
         * like your bestest friend. * hugs *
         *
         * As of today, there are 4 DOM event types you can listen to:
         *
         *   'wheel'                -- Chrome(31+), FF(17+), IE(9+)
         *   'mousewheel'           -- Chrome, IE(6+), Opera, Safari
         *   'MozMousePixelScroll'  -- FF(3.5 only!) (2010-2013) -- don't bother!
         *   'DOMMouseScroll'       -- FF(0.9.7+) since 2003
         *
         * So what to do?  The is the best:
         *
         *   normalizeWheel.getEventType();
         *
         * In your event callback, use this code to get sane interpretation of the
         * deltas.  This code will return an object with properties:
         *
         *   spinX   -- normalized spin speed (use for zoom) - x plane
         *   spinY   -- " - y plane
         *   pixelX  -- normalized distance (to pixels) - x plane
         *   pixelY  -- " - y plane
         *
         * Wheel values are provided by the browser assuming you are using the wheel to
         * scroll a web page by a number of lines or pixels (or pages).  Values can vary
         * significantly on different platforms and browsers, forgetting that you can
         * scroll at different speeds.  Some devices (like trackpads) emit more events
         * at smaller increments with fine granularity, and some emit massive jumps with
         * linear speed or acceleration.
         *
         * This code does its best to normalize the deltas for you:
         *
         *   - spin is trying to normalize how far the wheel was spun (or trackpad
         *     dragged).  This is super useful for zoom support where you want to
         *     throw away the chunky scroll steps on the PC and make those equal to
         *     the slow and smooth tiny steps on the Mac. Key data: This code tries to
         *     resolve a single slow step on a wheel to 1.
         *
         *   - pixel is normalizing the desired scroll delta in pixel units.  You'll
         *     get the crazy differences between browsers, but at least it'll be in
         *     pixels!
         *
         *   - positive value indicates scrolling DOWN/RIGHT, negative UP/LEFT.  This
         *     should translate to positive value zooming IN, negative zooming OUT.
         *     This matches the newer 'wheel' event.
         *
         * Why are there spinX, spinY (or pixels)?
         *
         *   - spinX is a 2-finger side drag on the trackpad, and a shift + wheel turn
         *     with a mouse.  It results in side-scrolling in the browser by default.
         *
         *   - spinY is what you expect -- it's the classic axis of a mouse wheel.
         *
         *   - I dropped spinZ/pixelZ.  It is supported by the DOM 3 'wheel' event and
         *     probably is by browsers in conjunction with fancy 3D controllers .. but
         *     you know.
         *
         * Implementation info:
         *
         * Examples of 'wheel' event if you scroll slowly (down) by one step with an
         * average mouse:
         *
         *   OS X + Chrome  (mouse)     -    4   pixel delta  (wheelDelta -120)
         *   OS X + Safari  (mouse)     -  N/A   pixel delta  (wheelDelta  -12)
         *   OS X + Firefox (mouse)     -    0.1 line  delta  (wheelDelta  N/A)
         *   Win8 + Chrome  (mouse)     -  100   pixel delta  (wheelDelta -120)
         *   Win8 + Firefox (mouse)     -    3   line  delta  (wheelDelta -120)
         *
         * On the trackpad:
         *
         *   OS X + Chrome  (trackpad)  -    2   pixel delta  (wheelDelta   -6)
         *   OS X + Firefox (trackpad)  -    1   pixel delta  (wheelDelta  N/A)
         *
         * On other/older browsers.. it's more complicated as there can be multiple and
         * also missing delta values.
         *
         * The 'wheel' event is more standard:
         *
         * http://www.w3.org/TR/DOM-Level-3-Events/#events-wheelevents
         *
         * The basics is that it includes a unit, deltaMode (pixels, lines, pages), and
         * deltaX, deltaY and deltaZ.  Some browsers provide other values to maintain
         * backward compatibility with older events.  Those other values help us
         * better normalize spin speed.  Example of what the browsers provide:
         *
         *                          | event.wheelDelta | event.detail
         *        ------------------+------------------+--------------
         *          Safari v5/OS X  |       -120       |       0
         *          Safari v5/Win7  |       -120       |       0
         *         Chrome v17/OS X  |       -120       |       0
         *         Chrome v17/Win7  |       -120       |       0
         *                IE9/Win7  |       -120       |   undefined
         *         Firefox v4/OS X  |     undefined    |       1
         *         Firefox v4/Win7  |     undefined    |       3
         *
         */
        function normalizeWheel( /*object*/ event ) /*object*/ {
            // Reasonable defaults
            var PIXEL_STEP = 10;
            var LINE_HEIGHT = 40;
            var PAGE_HEIGHT = 800;
        
            var sX = 0, sY = 0,       // spinX, spinY
                pX = 0, pY = 0;       // pixelX, pixelY
        
            // Legacy
            if( 'detail' in event ) {
                sY = event.detail;
            }
            if( 'wheelDelta' in event ) {
                sY = -event.wheelDelta / 120;
            }
            if( 'wheelDeltaY' in event ) {
                sY = -event.wheelDeltaY / 120;
            }
            if( 'wheelDeltaX' in event ) {
                sX = -event.wheelDeltaX / 120;
            }
        
            // side scrolling on FF with DOMMouseScroll
            if( 'axis' in event && event.axis === event.HORIZONTAL_AXIS ) {
                sX = sY;
                sY = 0;
            }
        
            pX = sX * PIXEL_STEP;
            pY = sY * PIXEL_STEP;
        
            if( 'deltaY' in event ) {
                pY = event.deltaY;
            }
            if( 'deltaX' in event ) {
                pX = event.deltaX;
            }
        
            if( (pX || pY) && event.deltaMode ) {
                if( event.deltaMode === 1 ) {          // delta in LINE units
                    pX *= LINE_HEIGHT;
                    pY *= LINE_HEIGHT;
                } else {                             // delta in PAGE units
                    pX *= PAGE_HEIGHT;
                    pY *= PAGE_HEIGHT;
                }
            }
        
            // Fall-back if spin cannot be determined
            if( pX && !sX ) {
                sX = (pX < 1) ? -1 : 1;
            }
            if( pY && !sY ) {
                sY = (pY < 1) ? -1 : 1;
            }
        
            return {
                spinX: sX,
                spinY: sY,
                pixelX: pX,
                pixelY: pY
            };
        }

        /*=========================
          Parallax
          ===========================*/
        function setParallaxTransform(el, progress) {
            el = $(el);
            var p, pX, pY;
            var rtlFactor = s.rtl ? -1 : 1;
        
            p = el.attr('data-swiper-parallax') || '0';
            pX = el.attr('data-swiper-parallax-x');
            pY = el.attr('data-swiper-parallax-y');
            if (pX || pY) {
                pX = pX || '0';
                pY = pY || '0';
            }
            else {
                if (s.isHorizontal()) {
                    pX = p;
                    pY = '0';
                }
                else {
                    pY = p;
                    pX = '0';
                }
            }
        
            if ((pX).indexOf('%') >= 0) {
                pX = parseInt(pX, 10) * progress * rtlFactor + '%';
            }
            else {
                pX = pX * progress * rtlFactor + 'px' ;
            }
            if ((pY).indexOf('%') >= 0) {
                pY = parseInt(pY, 10) * progress + '%';
            }
            else {
                pY = pY * progress + 'px' ;
            }
        
            el.transform('translate3d(' + pX + ', ' + pY + ',0px)');
        }
        s.parallax = {
            setTranslate: function () {
                s.container.children('[data-swiper-parallax], [data-swiper-parallax-x], [data-swiper-parallax-y]').each(function(){
                    setParallaxTransform(this, s.progress);
        
                });
                s.slides.each(function () {
                    var slide = $(this);
                    slide.find('[data-swiper-parallax], [data-swiper-parallax-x], [data-swiper-parallax-y]').each(function () {
                        var progress = Math.min(Math.max(slide[0].progress, -1), 1);
                        setParallaxTransform(this, progress);
                    });
                });
            },
            setTransition: function (duration) {
                if (typeof duration === 'undefined') duration = s.params.speed;
                s.container.find('[data-swiper-parallax], [data-swiper-parallax-x], [data-swiper-parallax-y]').each(function(){
                    var el = $(this);
                    var parallaxDuration = parseInt(el.attr('data-swiper-parallax-duration'), 10) || duration;
                    if (duration === 0) parallaxDuration = 0;
                    el.transition(parallaxDuration);
                });
            }
        };
        

        /*=========================
          Zoom
          ===========================*/
        s.zoom = {
            // "Global" Props
            scale: 1,
            currentScale: 1,
            isScaling: false,
            gesture: {
                slide: undefined,
                slideWidth: undefined,
                slideHeight: undefined,
                image: undefined,
                imageWrap: undefined,
                zoomMax: s.params.zoomMax
            },
            image: {
                isTouched: undefined,
                isMoved: undefined,
                currentX: undefined,
                currentY: undefined,
                minX: undefined,
                minY: undefined,
                maxX: undefined,
                maxY: undefined,
                width: undefined,
                height: undefined,
                startX: undefined,
                startY: undefined,
                touchesStart: {},
                touchesCurrent: {}
            },
            velocity: {
                x: undefined,
                y: undefined,
                prevPositionX: undefined,
                prevPositionY: undefined,
                prevTime: undefined
            },
            // Calc Scale From Multi-touches
            getDistanceBetweenTouches: function (e) {
                if (e.targetTouches.length < 2) return 1;
                var x1 = e.targetTouches[0].pageX,
                    y1 = e.targetTouches[0].pageY,
                    x2 = e.targetTouches[1].pageX,
                    y2 = e.targetTouches[1].pageY;
                var distance = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
                return distance;
            },
            // Events
            onGestureStart: function (e) {
                var z = s.zoom;
                if (!s.support.gestures) {
                    if (e.type !== 'touchstart' || e.type === 'touchstart' && e.targetTouches.length < 2) {
                        return;
                    }
                    z.gesture.scaleStart = z.getDistanceBetweenTouches(e);
                }
                if (!z.gesture.slide || !z.gesture.slide.length) {
                    z.gesture.slide = $(this);
                    if (z.gesture.slide.length === 0) z.gesture.slide = s.slides.eq(s.activeIndex);
                    z.gesture.image = z.gesture.slide.find('img, svg, canvas');
                    z.gesture.imageWrap = z.gesture.image.parent('.' + s.params.zoomContainerClass);
                    z.gesture.zoomMax = z.gesture.imageWrap.attr('data-swiper-zoom') || s.params.zoomMax ;
                    if (z.gesture.imageWrap.length === 0) {
                        z.gesture.image = undefined;
                        return;
                    }
                }
                z.gesture.image.transition(0);
                z.isScaling = true;
            },
            onGestureChange: function (e) {
                var z = s.zoom;
                if (!s.support.gestures) {
                    if (e.type !== 'touchmove' || e.type === 'touchmove' && e.targetTouches.length < 2) {
                        return;
                    }
                    z.gesture.scaleMove = z.getDistanceBetweenTouches(e);
                }
                if (!z.gesture.image || z.gesture.image.length === 0) return;
                if (s.support.gestures) {
                    z.scale = e.scale * z.currentScale;
                }
                else {
                    z.scale = (z.gesture.scaleMove / z.gesture.scaleStart) * z.currentScale;
                }
                if (z.scale > z.gesture.zoomMax) {
                    z.scale = z.gesture.zoomMax - 1 + Math.pow((z.scale - z.gesture.zoomMax + 1), 0.5);
                }
                if (z.scale < s.params.zoomMin) {
                    z.scale =  s.params.zoomMin + 1 - Math.pow((s.params.zoomMin - z.scale + 1), 0.5);
                }
                z.gesture.image.transform('translate3d(0,0,0) scale(' + z.scale + ')');
            },
            onGestureEnd: function (e) {
                var z = s.zoom;
                if (!s.support.gestures) {
                    if (e.type !== 'touchend' || e.type === 'touchend' && e.changedTouches.length < 2) {
                        return;
                    }
                }
                if (!z.gesture.image || z.gesture.image.length === 0) return;
                z.scale = Math.max(Math.min(z.scale, z.gesture.zoomMax), s.params.zoomMin);
                z.gesture.image.transition(s.params.speed).transform('translate3d(0,0,0) scale(' + z.scale + ')');
                z.currentScale = z.scale;
                z.isScaling = false;
                if (z.scale === 1) z.gesture.slide = undefined;
            },
            onTouchStart: function (s, e) {
                var z = s.zoom;
                if (!z.gesture.image || z.gesture.image.length === 0) return;
                if (z.image.isTouched) return;
                if (s.device.os === 'android') e.preventDefault();
                z.image.isTouched = true;
                z.image.touchesStart.x = e.type === 'touchstart' ? e.targetTouches[0].pageX : e.pageX;
                z.image.touchesStart.y = e.type === 'touchstart' ? e.targetTouches[0].pageY : e.pageY;
            },
            onTouchMove: function (e) {
                var z = s.zoom;
                if (!z.gesture.image || z.gesture.image.length === 0) return;
                s.allowClick = false;
                if (!z.image.isTouched || !z.gesture.slide) return;
        
                if (!z.image.isMoved) {
                    z.image.width = z.gesture.image[0].offsetWidth;
                    z.image.height = z.gesture.image[0].offsetHeight;
                    z.image.startX = s.getTranslate(z.gesture.imageWrap[0], 'x') || 0;
                    z.image.startY = s.getTranslate(z.gesture.imageWrap[0], 'y') || 0;
                    z.gesture.slideWidth = z.gesture.slide[0].offsetWidth;
                    z.gesture.slideHeight = z.gesture.slide[0].offsetHeight;
                    z.gesture.imageWrap.transition(0);
                    if (s.rtl) z.image.startX = -z.image.startX;
                    if (s.rtl) z.image.startY = -z.image.startY;
                }
                // Define if we need image drag
                var scaledWidth = z.image.width * z.scale;
                var scaledHeight = z.image.height * z.scale;
        
                if (scaledWidth < z.gesture.slideWidth && scaledHeight < z.gesture.slideHeight) return;
        
                z.image.minX = Math.min((z.gesture.slideWidth / 2 - scaledWidth / 2), 0);
                z.image.maxX = -z.image.minX;
                z.image.minY = Math.min((z.gesture.slideHeight / 2 - scaledHeight / 2), 0);
                z.image.maxY = -z.image.minY;
        
                z.image.touchesCurrent.x = e.type === 'touchmove' ? e.targetTouches[0].pageX : e.pageX;
                z.image.touchesCurrent.y = e.type === 'touchmove' ? e.targetTouches[0].pageY : e.pageY;
        
                if (!z.image.isMoved && !z.isScaling) {
                    if (s.isHorizontal() &&
                        (Math.floor(z.image.minX) === Math.floor(z.image.startX) && z.image.touchesCurrent.x < z.image.touchesStart.x) ||
                        (Math.floor(z.image.maxX) === Math.floor(z.image.startX) && z.image.touchesCurrent.x > z.image.touchesStart.x)
                        ) {
                        z.image.isTouched = false;
                        return;
                    }
                    else if (!s.isHorizontal() &&
                        (Math.floor(z.image.minY) === Math.floor(z.image.startY) && z.image.touchesCurrent.y < z.image.touchesStart.y) ||
                        (Math.floor(z.image.maxY) === Math.floor(z.image.startY) && z.image.touchesCurrent.y > z.image.touchesStart.y)
                        ) {
                        z.image.isTouched = false;
                        return;
                    }
                }
                e.preventDefault();
                e.stopPropagation();
        
                z.image.isMoved = true;
                z.image.currentX = z.image.touchesCurrent.x - z.image.touchesStart.x + z.image.startX;
                z.image.currentY = z.image.touchesCurrent.y - z.image.touchesStart.y + z.image.startY;
        
                if (z.image.currentX < z.image.minX) {
                    z.image.currentX =  z.image.minX + 1 - Math.pow((z.image.minX - z.image.currentX + 1), 0.8);
                }
                if (z.image.currentX > z.image.maxX) {
                    z.image.currentX = z.image.maxX - 1 + Math.pow((z.image.currentX - z.image.maxX + 1), 0.8);
                }
        
                if (z.image.currentY < z.image.minY) {
                    z.image.currentY =  z.image.minY + 1 - Math.pow((z.image.minY - z.image.currentY + 1), 0.8);
                }
                if (z.image.currentY > z.image.maxY) {
                    z.image.currentY = z.image.maxY - 1 + Math.pow((z.image.currentY - z.image.maxY + 1), 0.8);
                }
        
                //Velocity
                if (!z.velocity.prevPositionX) z.velocity.prevPositionX = z.image.touchesCurrent.x;
                if (!z.velocity.prevPositionY) z.velocity.prevPositionY = z.image.touchesCurrent.y;
                if (!z.velocity.prevTime) z.velocity.prevTime = Date.now();
                z.velocity.x = (z.image.touchesCurrent.x - z.velocity.prevPositionX) / (Date.now() - z.velocity.prevTime) / 2;
                z.velocity.y = (z.image.touchesCurrent.y - z.velocity.prevPositionY) / (Date.now() - z.velocity.prevTime) / 2;
                if (Math.abs(z.image.touchesCurrent.x - z.velocity.prevPositionX) < 2) z.velocity.x = 0;
                if (Math.abs(z.image.touchesCurrent.y - z.velocity.prevPositionY) < 2) z.velocity.y = 0;
                z.velocity.prevPositionX = z.image.touchesCurrent.x;
                z.velocity.prevPositionY = z.image.touchesCurrent.y;
                z.velocity.prevTime = Date.now();
        
                z.gesture.imageWrap.transform('translate3d(' + z.image.currentX + 'px, ' + z.image.currentY + 'px,0)');
            },
            onTouchEnd: function (s, e) {
                var z = s.zoom;
                if (!z.gesture.image || z.gesture.image.length === 0) return;
                if (!z.image.isTouched || !z.image.isMoved) {
                    z.image.isTouched = false;
                    z.image.isMoved = false;
                    return;
                }
                z.image.isTouched = false;
                z.image.isMoved = false;
                var momentumDurationX = 300;
                var momentumDurationY = 300;
                var momentumDistanceX = z.velocity.x * momentumDurationX;
                var newPositionX = z.image.currentX + momentumDistanceX;
                var momentumDistanceY = z.velocity.y * momentumDurationY;
                var newPositionY = z.image.currentY + momentumDistanceY;
        
                //Fix duration
                if (z.velocity.x !== 0) momentumDurationX = Math.abs((newPositionX - z.image.currentX) / z.velocity.x);
                if (z.velocity.y !== 0) momentumDurationY = Math.abs((newPositionY - z.image.currentY) / z.velocity.y);
                var momentumDuration = Math.max(momentumDurationX, momentumDurationY);
        
                z.image.currentX = newPositionX;
                z.image.currentY = newPositionY;
        
                // Define if we need image drag
                var scaledWidth = z.image.width * z.scale;
                var scaledHeight = z.image.height * z.scale;
                z.image.minX = Math.min((z.gesture.slideWidth / 2 - scaledWidth / 2), 0);
                z.image.maxX = -z.image.minX;
                z.image.minY = Math.min((z.gesture.slideHeight / 2 - scaledHeight / 2), 0);
                z.image.maxY = -z.image.minY;
                z.image.currentX = Math.max(Math.min(z.image.currentX, z.image.maxX), z.image.minX);
                z.image.currentY = Math.max(Math.min(z.image.currentY, z.image.maxY), z.image.minY);
        
                z.gesture.imageWrap.transition(momentumDuration).transform('translate3d(' + z.image.currentX + 'px, ' + z.image.currentY + 'px,0)');
            },
            onTransitionEnd: function (s) {
                var z = s.zoom;
                if (z.gesture.slide && s.previousIndex !== s.activeIndex) {
                    z.gesture.image.transform('translate3d(0,0,0) scale(1)');
                    z.gesture.imageWrap.transform('translate3d(0,0,0)');
                    z.gesture.slide = z.gesture.image = z.gesture.imageWrap = undefined;
                    z.scale = z.currentScale = 1;
                }
            },
            // Toggle Zoom
            toggleZoom: function (s, e) {
                var z = s.zoom;
                if (!z.gesture.slide) {
                    z.gesture.slide = s.clickedSlide ? $(s.clickedSlide) : s.slides.eq(s.activeIndex);
                    z.gesture.image = z.gesture.slide.find('img, svg, canvas');
                    z.gesture.imageWrap = z.gesture.image.parent('.' + s.params.zoomContainerClass);
                }
                if (!z.gesture.image || z.gesture.image.length === 0) return;
        
                var touchX, touchY, offsetX, offsetY, diffX, diffY, translateX, translateY, imageWidth, imageHeight, scaledWidth, scaledHeight, translateMinX, translateMinY, translateMaxX, translateMaxY, slideWidth, slideHeight;
        
                if (typeof z.image.touchesStart.x === 'undefined' && e) {
                    touchX = e.type === 'touchend' ? e.changedTouches[0].pageX : e.pageX;
                    touchY = e.type === 'touchend' ? e.changedTouches[0].pageY : e.pageY;
                }
                else {
                    touchX = z.image.touchesStart.x;
                    touchY = z.image.touchesStart.y;
                }
        
                if (z.scale && z.scale !== 1) {
                    // Zoom Out
                    z.scale = z.currentScale = 1;
                    z.gesture.imageWrap.transition(300).transform('translate3d(0,0,0)');
                    z.gesture.image.transition(300).transform('translate3d(0,0,0) scale(1)');
                    z.gesture.slide = undefined;
                }
                else {
                    // Zoom In
                    z.scale = z.currentScale = z.gesture.imageWrap.attr('data-swiper-zoom') || s.params.zoomMax;
                    if (e) {
                        slideWidth = z.gesture.slide[0].offsetWidth;
                        slideHeight = z.gesture.slide[0].offsetHeight;
                        offsetX = z.gesture.slide.offset().left;
                        offsetY = z.gesture.slide.offset().top;
                        diffX = offsetX + slideWidth/2 - touchX;
                        diffY = offsetY + slideHeight/2 - touchY;
        
                        imageWidth = z.gesture.image[0].offsetWidth;
                        imageHeight = z.gesture.image[0].offsetHeight;
                        scaledWidth = imageWidth * z.scale;
                        scaledHeight = imageHeight * z.scale;
        
                        translateMinX = Math.min((slideWidth / 2 - scaledWidth / 2), 0);
                        translateMinY = Math.min((slideHeight / 2 - scaledHeight / 2), 0);
                        translateMaxX = -translateMinX;
                        translateMaxY = -translateMinY;
        
                        translateX = diffX * z.scale;
                        translateY = diffY * z.scale;
        
                        if (translateX < translateMinX) {
                            translateX =  translateMinX;
                        }
                        if (translateX > translateMaxX) {
                            translateX = translateMaxX;
                        }
        
                        if (translateY < translateMinY) {
                            translateY =  translateMinY;
                        }
                        if (translateY > translateMaxY) {
                            translateY = translateMaxY;
                        }
                    }
                    else {
                        translateX = 0;
                        translateY = 0;
                    }
                    z.gesture.imageWrap.transition(300).transform('translate3d(' + translateX + 'px, ' + translateY + 'px,0)');
                    z.gesture.image.transition(300).transform('translate3d(0,0,0) scale(' + z.scale + ')');
                }
            },
            // Attach/Detach Events
            attachEvents: function (detach) {
                var action = detach ? 'off' : 'on';
        
                if (s.params.zoom) {
                    var target = s.slides;
                    var passiveListener = s.touchEvents.start === 'touchstart' && s.support.passiveListener && s.params.passiveListeners ? {passive: true, capture: false} : false;
                    // Scale image
                    if (s.support.gestures) {
                        s.slides[action]('gesturestart', s.zoom.onGestureStart, passiveListener);
                        s.slides[action]('gesturechange', s.zoom.onGestureChange, passiveListener);
                        s.slides[action]('gestureend', s.zoom.onGestureEnd, passiveListener);
                    }
                    else if (s.touchEvents.start === 'touchstart') {
                        s.slides[action](s.touchEvents.start, s.zoom.onGestureStart, passiveListener);
                        s.slides[action](s.touchEvents.move, s.zoom.onGestureChange, passiveListener);
                        s.slides[action](s.touchEvents.end, s.zoom.onGestureEnd, passiveListener);
                    }
        
                    // Move image
                    s[action]('touchStart', s.zoom.onTouchStart);
                    s.slides.each(function (index, slide){
                        if ($(slide).find('.' + s.params.zoomContainerClass).length > 0) {
                            $(slide)[action](s.touchEvents.move, s.zoom.onTouchMove);
                        }
                    });
                    s[action]('touchEnd', s.zoom.onTouchEnd);
        
                    // Scale Out
                    s[action]('transitionEnd', s.zoom.onTransitionEnd);
                    if (s.params.zoomToggle) {
                        s.on('doubleTap', s.zoom.toggleZoom);
                    }
                }
            },
            init: function () {
                s.zoom.attachEvents();
            },
            destroy: function () {
                s.zoom.attachEvents(true);
            }
        };

        /*=========================
          Plugins API. Collect all and init all plugins
          ===========================*/
        s._plugins = [];
        for (var plugin in s.plugins) {
            var p = s.plugins[plugin](s, s.params[plugin]);
            if (p) s._plugins.push(p);
        }
        // Method to call all plugins event/method
        s.callPlugins = function (eventName) {
            for (var i = 0; i < s._plugins.length; i++) {
                if (eventName in s._plugins[i]) {
                    s._plugins[i][eventName](arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]);
                }
            }
        };

        /*=========================
          Events/Callbacks/Plugins Emitter
          ===========================*/
        function normalizeEventName (eventName) {
            if (eventName.indexOf('on') !== 0) {
                if (eventName[0] !== eventName[0].toUpperCase()) {
                    eventName = 'on' + eventName[0].toUpperCase() + eventName.substring(1);
                }
                else {
                    eventName = 'on' + eventName;
                }
            }
            return eventName;
        }
        s.emitterEventListeners = {
        
        };
        s.emit = function (eventName) {
            // Trigger callbacks
            if (s.params[eventName]) {
                s.params[eventName](arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]);
            }
            var i;
            // Trigger events
            if (s.emitterEventListeners[eventName]) {
                for (i = 0; i < s.emitterEventListeners[eventName].length; i++) {
                    s.emitterEventListeners[eventName][i](arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]);
                }
            }
            // Trigger plugins
            if (s.callPlugins) s.callPlugins(eventName, arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]);
        };
        s.on = function (eventName, handler) {
            eventName = normalizeEventName(eventName);
            if (!s.emitterEventListeners[eventName]) s.emitterEventListeners[eventName] = [];
            s.emitterEventListeners[eventName].push(handler);
            return s;
        };
        s.off = function (eventName, handler) {
            var i;
            eventName = normalizeEventName(eventName);
            if (typeof handler === 'undefined') {
                // Remove all handlers for such event
                s.emitterEventListeners[eventName] = [];
                return s;
            }
            if (!s.emitterEventListeners[eventName] || s.emitterEventListeners[eventName].length === 0) return;
            for (i = 0; i < s.emitterEventListeners[eventName].length; i++) {
                if(s.emitterEventListeners[eventName][i] === handler) s.emitterEventListeners[eventName].splice(i, 1);
            }
            return s;
        };
        s.once = function (eventName, handler) {
            eventName = normalizeEventName(eventName);
            var _handler = function () {
                handler(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4]);
                s.off(eventName, _handler);
            };
            s.on(eventName, _handler);
            return s;
        };

        // Accessibility tools
        s.a11y = {
            makeFocusable: function ($el) {
                $el.attr('tabIndex', '0');
                return $el;
            },
            addRole: function ($el, role) {
                $el.attr('role', role);
                return $el;
            },
        
            addLabel: function ($el, label) {
                $el.attr('aria-label', label);
                return $el;
            },
        
            disable: function ($el) {
                $el.attr('aria-disabled', true);
                return $el;
            },
        
            enable: function ($el) {
                $el.attr('aria-disabled', false);
                return $el;
            },
        
            onEnterKey: function (event) {
                if (event.keyCode !== 13) return;
                if ($(event.target).is(s.params.nextButton)) {
                    s.onClickNext(event);
                    if (s.isEnd) {
                        s.a11y.notify(s.params.lastSlideMessage);
                    }
                    else {
                        s.a11y.notify(s.params.nextSlideMessage);
                    }
                }
                else if ($(event.target).is(s.params.prevButton)) {
                    s.onClickPrev(event);
                    if (s.isBeginning) {
                        s.a11y.notify(s.params.firstSlideMessage);
                    }
                    else {
                        s.a11y.notify(s.params.prevSlideMessage);
                    }
                }
                if ($(event.target).is('.' + s.params.bulletClass)) {
                    $(event.target)[0].click();
                }
            },
        
            liveRegion: $('<span class="' + s.params.notificationClass + '" aria-live="assertive" aria-atomic="true"></span>'),
        
            notify: function (message) {
                var notification = s.a11y.liveRegion;
                if (notification.length === 0) return;
                notification.html('');
                notification.html(message);
            },
            init: function () {
                // Setup accessibility
                if (s.params.nextButton && s.nextButton && s.nextButton.length > 0) {
                    s.a11y.makeFocusable(s.nextButton);
                    s.a11y.addRole(s.nextButton, 'button');
                    s.a11y.addLabel(s.nextButton, s.params.nextSlideMessage);
                }
                if (s.params.prevButton && s.prevButton && s.prevButton.length > 0) {
                    s.a11y.makeFocusable(s.prevButton);
                    s.a11y.addRole(s.prevButton, 'button');
                    s.a11y.addLabel(s.prevButton, s.params.prevSlideMessage);
                }
        
                $(s.container).append(s.a11y.liveRegion);
            },
            initPagination: function () {
                if (s.params.pagination && s.params.paginationClickable && s.bullets && s.bullets.length) {
                    s.bullets.each(function () {
                        var bullet = $(this);
                        s.a11y.makeFocusable(bullet);
                        s.a11y.addRole(bullet, 'button');
                        s.a11y.addLabel(bullet, s.params.paginationBulletMessage.replace(/{{index}}/, bullet.index() + 1));
                    });
                }
            },
            destroy: function () {
                if (s.a11y.liveRegion && s.a11y.liveRegion.length > 0) s.a11y.liveRegion.remove();
            }
        };
        

        /*=========================
          Init/Destroy
          ===========================*/
        s.init = function () {
            if (s.params.loop) s.createLoop();
            s.updateContainerSize();
            s.updateSlidesSize();
            s.updatePagination();
            if (s.params.scrollbar && s.scrollbar) {
                s.scrollbar.set();
                if (s.params.scrollbarDraggable) {
                    s.scrollbar.enableDraggable();
                }
            }
            if (s.params.effect !== 'slide' && s.effects[s.params.effect]) {
                if (!s.params.loop) s.updateProgress();
                s.effects[s.params.effect].setTranslate();
            }
            if (s.params.loop) {
                s.slideTo(s.params.initialSlide + s.loopedSlides, 0, s.params.runCallbacksOnInit);
            }
            else {
                s.slideTo(s.params.initialSlide, 0, s.params.runCallbacksOnInit);
                if (s.params.initialSlide === 0) {
                    if (s.parallax && s.params.parallax) s.parallax.setTranslate();
                    if (s.lazy && s.params.lazyLoading) {
                        s.lazy.load();
                        s.lazy.initialImageLoaded = true;
                    }
                }
            }
            s.attachEvents();
            if (s.params.observer && s.support.observer) {
                s.initObservers();
            }
            if (s.params.preloadImages && !s.params.lazyLoading) {
                s.preloadImages();
            }
            if (s.params.zoom && s.zoom) {
                s.zoom.init();
            }
            if (s.params.autoplay) {
                s.startAutoplay();
            }
            if (s.params.keyboardControl) {
                if (s.enableKeyboardControl) s.enableKeyboardControl();
            }
            if (s.params.mousewheelControl) {
                if (s.enableMousewheelControl) s.enableMousewheelControl();
            }
            // Deprecated hashnavReplaceState changed to replaceState for use in hashnav and history
            if (s.params.hashnavReplaceState) {
                s.params.replaceState = s.params.hashnavReplaceState;
            }
            if (s.params.history) {
                if (s.history) s.history.init();
            }
            if (s.params.hashnav) {
                if (s.hashnav) s.hashnav.init();
            }
            if (s.params.a11y && s.a11y) s.a11y.init();
            s.emit('onInit', s);
        };
        
        // Cleanup dynamic styles
        s.cleanupStyles = function () {
            // Container
            s.container.removeClass(s.classNames.join(' ')).removeAttr('style');
        
            // Wrapper
            s.wrapper.removeAttr('style');
        
            // Slides
            if (s.slides && s.slides.length) {
                s.slides
                    .removeClass([
                      s.params.slideVisibleClass,
                      s.params.slideActiveClass,
                      s.params.slideNextClass,
                      s.params.slidePrevClass
                    ].join(' '))
                    .removeAttr('style')
                    .removeAttr('data-swiper-column')
                    .removeAttr('data-swiper-row');
            }
        
            // Pagination/Bullets
            if (s.paginationContainer && s.paginationContainer.length) {
                s.paginationContainer.removeClass(s.params.paginationHiddenClass);
            }
            if (s.bullets && s.bullets.length) {
                s.bullets.removeClass(s.params.bulletActiveClass);
            }
        
            // Buttons
            if (s.params.prevButton) $(s.params.prevButton).removeClass(s.params.buttonDisabledClass);
            if (s.params.nextButton) $(s.params.nextButton).removeClass(s.params.buttonDisabledClass);
        
            // Scrollbar
            if (s.params.scrollbar && s.scrollbar) {
                if (s.scrollbar.track && s.scrollbar.track.length) s.scrollbar.track.removeAttr('style');
                if (s.scrollbar.drag && s.scrollbar.drag.length) s.scrollbar.drag.removeAttr('style');
            }
        };
        
        // Destroy
        s.destroy = function (deleteInstance, cleanupStyles) {
            // Detach evebts
            s.detachEvents();
            // Stop autoplay
            s.stopAutoplay();
            // Disable draggable
            if (s.params.scrollbar && s.scrollbar) {
                if (s.params.scrollbarDraggable) {
                    s.scrollbar.disableDraggable();
                }
            }
            // Destroy loop
            if (s.params.loop) {
                s.destroyLoop();
            }
            // Cleanup styles
            if (cleanupStyles) {
                s.cleanupStyles();
            }
            // Disconnect observer
            s.disconnectObservers();
        
            // Destroy zoom
            if (s.params.zoom && s.zoom) {
                s.zoom.destroy();
            }
            // Disable keyboard/mousewheel
            if (s.params.keyboardControl) {
                if (s.disableKeyboardControl) s.disableKeyboardControl();
            }
            if (s.params.mousewheelControl) {
                if (s.disableMousewheelControl) s.disableMousewheelControl();
            }
            // Disable a11y
            if (s.params.a11y && s.a11y) s.a11y.destroy();
            // Delete history popstate
            if (s.params.history && !s.params.replaceState) {
                window.removeEventListener('popstate', s.history.setHistoryPopState);
            }
            if (s.params.hashnav && s.hashnav)  {
                s.hashnav.destroy();
            }
            // Destroy callback
            s.emit('onDestroy');
            // Delete instance
            if (deleteInstance !== false) s = null;
        };
        
        s.init();
        

    
        // Return swiper instance
        return s;
    };
    

    /*==================================================
        Prototype
    ====================================================*/
    Swiper.prototype = {
        isSafari: (function () {
            var ua = window.navigator.userAgent.toLowerCase();
            return (ua.indexOf('safari') >= 0 && ua.indexOf('chrome') < 0 && ua.indexOf('android') < 0);
        })(),
        isUiWebView: /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test(window.navigator.userAgent),
        isArray: function (arr) {
            return Object.prototype.toString.apply(arr) === '[object Array]';
        },
        /*==================================================
        Browser
        ====================================================*/
        browser: {
            ie: window.navigator.pointerEnabled || window.navigator.msPointerEnabled,
            ieTouch: (window.navigator.msPointerEnabled && window.navigator.msMaxTouchPoints > 1) || (window.navigator.pointerEnabled && window.navigator.maxTouchPoints > 1),
            lteIE9: (function() {
                // create temporary DIV
                var div = document.createElement('div');
                // add content to tmp DIV which is wrapped into the IE HTML conditional statement
                div.innerHTML = '<!--[if lte IE 9]><i></i><![endif]-->';
                // return true / false value based on what will browser render
                return div.getElementsByTagName('i').length === 1;
            })()
        },
        /*==================================================
        Devices
        ====================================================*/
        device: (function () {
            var ua = window.navigator.userAgent;
            var android = ua.match(/(Android);?[\s\/]+([\d.]+)?/);
            var ipad = ua.match(/(iPad).*OS\s([\d_]+)/);
            var ipod = ua.match(/(iPod)(.*OS\s([\d_]+))?/);
            var iphone = !ipad && ua.match(/(iPhone\sOS|iOS)\s([\d_]+)/);
            return {
                ios: ipad || iphone || ipod,
                android: android
            };
        })(),
        /*==================================================
        Feature Detection
        ====================================================*/
        support: {
            touch : (window.Modernizr && Modernizr.touch === true) || (function () {
                return !!(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch);
            })(),
    
            transforms3d : (window.Modernizr && Modernizr.csstransforms3d === true) || (function () {
                var div = document.createElement('div').style;
                return ('webkitPerspective' in div || 'MozPerspective' in div || 'OPerspective' in div || 'MsPerspective' in div || 'perspective' in div);
            })(),
    
            flexbox: (function () {
                var div = document.createElement('div').style;
                var styles = ('alignItems webkitAlignItems webkitBoxAlign msFlexAlign mozBoxAlign webkitFlexDirection msFlexDirection mozBoxDirection mozBoxOrient webkitBoxDirection webkitBoxOrient').split(' ');
                for (var i = 0; i < styles.length; i++) {
                    if (styles[i] in div) return true;
                }
            })(),
    
            observer: (function () {
                return ('MutationObserver' in window || 'WebkitMutationObserver' in window);
            })(),
    
            passiveListener: (function () {
                var supportsPassive = false;
                try {
                    var opts = Object.defineProperty({}, 'passive', {
                        get: function() {
                            supportsPassive = true;
                        }
                    });
                    window.addEventListener('testPassiveListener', null, opts);
                } catch (e) {}
                return supportsPassive;
            })(),
    
            gestures: (function () {
                return 'ongesturestart' in window;
            })()
        },
        /*==================================================
        Plugins
        ====================================================*/
        plugins: {}
    };
    

    /*===========================
    Dom7 Library
    ===========================*/
    var Dom7 = (function () {
        var Dom7 = function (arr) {
            var _this = this, i = 0;
            // Create array-like object
            for (i = 0; i < arr.length; i++) {
                _this[i] = arr[i];
            }
            _this.length = arr.length;
            // Return collection with methods
            return this;
        };
        var $ = function (selector, context) {
            var arr = [], i = 0;
            if (selector && !context) {
                if (selector instanceof Dom7) {
                    return selector;
                }
            }
            if (selector) {
                // String
                if (typeof selector === 'string') {
                    var els, tempParent, html = selector.trim();
                    if (html.indexOf('<') >= 0 && html.indexOf('>') >= 0) {
                        var toCreate = 'div';
                        if (html.indexOf('<li') === 0) toCreate = 'ul';
                        if (html.indexOf('<tr') === 0) toCreate = 'tbody';
                        if (html.indexOf('<td') === 0 || html.indexOf('<th') === 0) toCreate = 'tr';
                        if (html.indexOf('<tbody') === 0) toCreate = 'table';
                        if (html.indexOf('<option') === 0) toCreate = 'select';
                        tempParent = document.createElement(toCreate);
                        tempParent.innerHTML = selector;
                        for (i = 0; i < tempParent.childNodes.length; i++) {
                            arr.push(tempParent.childNodes[i]);
                        }
                    }
                    else {
                        if (!context && selector[0] === '#' && !selector.match(/[ .<>:~]/)) {
                            // Pure ID selector
                            els = [document.getElementById(selector.split('#')[1])];
                        }
                        else {
                            // Other selectors
                            els = (context || document).querySelectorAll(selector);
                        }
                        for (i = 0; i < els.length; i++) {
                            if (els[i]) arr.push(els[i]);
                        }
                    }
                }
                // Node/element
                else if (selector.nodeType || selector === window || selector === document) {
                    arr.push(selector);
                }
                //Array of elements or instance of Dom
                else if (selector.length > 0 && selector[0].nodeType) {
                    for (i = 0; i < selector.length; i++) {
                        arr.push(selector[i]);
                    }
                }
            }
            return new Dom7(arr);
        };
        Dom7.prototype = {
            // Classes and attriutes
            addClass: function (className) {
                if (typeof className === 'undefined') {
                    return this;
                }
                var classes = className.split(' ');
                for (var i = 0; i < classes.length; i++) {
                    for (var j = 0; j < this.length; j++) {
                        this[j].classList.add(classes[i]);
                    }
                }
                return this;
            },
            removeClass: function (className) {
                var classes = className.split(' ');
                for (var i = 0; i < classes.length; i++) {
                    for (var j = 0; j < this.length; j++) {
                        this[j].classList.remove(classes[i]);
                    }
                }
                return this;
            },
            hasClass: function (className) {
                if (!this[0]) return false;
                else return this[0].classList.contains(className);
            },
            toggleClass: function (className) {
                var classes = className.split(' ');
                for (var i = 0; i < classes.length; i++) {
                    for (var j = 0; j < this.length; j++) {
                        this[j].classList.toggle(classes[i]);
                    }
                }
                return this;
            },
            attr: function (attrs, value) {
                if (arguments.length === 1 && typeof attrs === 'string') {
                    // Get attr
                    if (this[0]) return this[0].getAttribute(attrs);
                    else return undefined;
                }
                else {
                    // Set attrs
                    for (var i = 0; i < this.length; i++) {
                        if (arguments.length === 2) {
                            // String
                            this[i].setAttribute(attrs, value);
                        }
                        else {
                            // Object
                            for (var attrName in attrs) {
                                this[i][attrName] = attrs[attrName];
                                this[i].setAttribute(attrName, attrs[attrName]);
                            }
                        }
                    }
                    return this;
                }
            },
            removeAttr: function (attr) {
                for (var i = 0; i < this.length; i++) {
                    this[i].removeAttribute(attr);
                }
                return this;
            },
            data: function (key, value) {
                if (typeof value === 'undefined') {
                    // Get value
                    if (this[0]) {
                        var dataKey = this[0].getAttribute('data-' + key);
                        if (dataKey) return dataKey;
                        else if (this[0].dom7ElementDataStorage && (key in this[0].dom7ElementDataStorage)) return this[0].dom7ElementDataStorage[key];
                        else return undefined;
                    }
                    else return undefined;
                }
                else {
                    // Set value
                    for (var i = 0; i < this.length; i++) {
                        var el = this[i];
                        if (!el.dom7ElementDataStorage) el.dom7ElementDataStorage = {};
                        el.dom7ElementDataStorage[key] = value;
                    }
                    return this;
                }
            },
            // Transforms
            transform : function (transform) {
                for (var i = 0; i < this.length; i++) {
                    var elStyle = this[i].style;
                    elStyle.webkitTransform = elStyle.MsTransform = elStyle.msTransform = elStyle.MozTransform = elStyle.OTransform = elStyle.transform = transform;
                }
                return this;
            },
            transition: function (duration) {
                if (typeof duration !== 'string') {
                    duration = duration + 'ms';
                }
                for (var i = 0; i < this.length; i++) {
                    var elStyle = this[i].style;
                    elStyle.webkitTransitionDuration = elStyle.MsTransitionDuration = elStyle.msTransitionDuration = elStyle.MozTransitionDuration = elStyle.OTransitionDuration = elStyle.transitionDuration = duration;
                }
                return this;
            },
            //Events
            on: function (eventName, targetSelector, listener, capture) {
                function handleLiveEvent(e) {
                    var target = e.target;
                    if ($(target).is(targetSelector)) listener.call(target, e);
                    else {
                        var parents = $(target).parents();
                        for (var k = 0; k < parents.length; k++) {
                            if ($(parents[k]).is(targetSelector)) listener.call(parents[k], e);
                        }
                    }
                }
                var events = eventName.split(' ');
                var i, j;
                for (i = 0; i < this.length; i++) {
                    if (typeof targetSelector === 'function' || targetSelector === false) {
                        // Usual events
                        if (typeof targetSelector === 'function') {
                            listener = arguments[1];
                            capture = arguments[2] || false;
                        }
                        for (j = 0; j < events.length; j++) {
                            this[i].addEventListener(events[j], listener, capture);
                        }
                    }
                    else {
                        //Live events
                        for (j = 0; j < events.length; j++) {
                            if (!this[i].dom7LiveListeners) this[i].dom7LiveListeners = [];
                            this[i].dom7LiveListeners.push({listener: listener, liveListener: handleLiveEvent});
                            this[i].addEventListener(events[j], handleLiveEvent, capture);
                        }
                    }
                }
    
                return this;
            },
            off: function (eventName, targetSelector, listener, capture) {
                var events = eventName.split(' ');
                for (var i = 0; i < events.length; i++) {
                    for (var j = 0; j < this.length; j++) {
                        if (typeof targetSelector === 'function' || targetSelector === false) {
                            // Usual events
                            if (typeof targetSelector === 'function') {
                                listener = arguments[1];
                                capture = arguments[2] || false;
                            }
                            this[j].removeEventListener(events[i], listener, capture);
                        }
                        else {
                            // Live event
                            if (this[j].dom7LiveListeners) {
                                for (var k = 0; k < this[j].dom7LiveListeners.length; k++) {
                                    if (this[j].dom7LiveListeners[k].listener === listener) {
                                        this[j].removeEventListener(events[i], this[j].dom7LiveListeners[k].liveListener, capture);
                                    }
                                }
                            }
                        }
                    }
                }
                return this;
            },
            once: function (eventName, targetSelector, listener, capture) {
                var dom = this;
                if (typeof targetSelector === 'function') {
                    targetSelector = false;
                    listener = arguments[1];
                    capture = arguments[2];
                }
                function proxy(e) {
                    listener(e);
                    dom.off(eventName, targetSelector, proxy, capture);
                }
                dom.on(eventName, targetSelector, proxy, capture);
            },
            trigger: function (eventName, eventData) {
                for (var i = 0; i < this.length; i++) {
                    var evt;
                    try {
                        evt = new window.CustomEvent(eventName, {detail: eventData, bubbles: true, cancelable: true});
                    }
                    catch (e) {
                        evt = document.createEvent('Event');
                        evt.initEvent(eventName, true, true);
                        evt.detail = eventData;
                    }
                    this[i].dispatchEvent(evt);
                }
                return this;
            },
            transitionEnd: function (callback) {
                var events = ['webkitTransitionEnd', 'transitionend', 'oTransitionEnd', 'MSTransitionEnd', 'msTransitionEnd'],
                    i, j, dom = this;
                function fireCallBack(e) {
                    /*jshint validthis:true */
                    if (e.target !== this) return;
                    callback.call(this, e);
                    for (i = 0; i < events.length; i++) {
                        dom.off(events[i], fireCallBack);
                    }
                }
                if (callback) {
                    for (i = 0; i < events.length; i++) {
                        dom.on(events[i], fireCallBack);
                    }
                }
                return this;
            },
            // Sizing/Styles
            width: function () {
                if (this[0] === window) {
                    return window.innerWidth;
                }
                else {
                    if (this.length > 0) {
                        return parseFloat(this.css('width'));
                    }
                    else {
                        return null;
                    }
                }
            },
            outerWidth: function (includeMargins) {
                if (this.length > 0) {
                    if (includeMargins)
                        return this[0].offsetWidth + parseFloat(this.css('margin-right')) + parseFloat(this.css('margin-left'));
                    else
                        return this[0].offsetWidth;
                }
                else return null;
            },
            height: function () {
                if (this[0] === window) {
                    return window.innerHeight;
                }
                else {
                    if (this.length > 0) {
                        return parseFloat(this.css('height'));
                    }
                    else {
                        return null;
                    }
                }
            },
            outerHeight: function (includeMargins) {
                if (this.length > 0) {
                    if (includeMargins)
                        return this[0].offsetHeight + parseFloat(this.css('margin-top')) + parseFloat(this.css('margin-bottom'));
                    else
                        return this[0].offsetHeight;
                }
                else return null;
            },
            offset: function () {
                if (this.length > 0) {
                    var el = this[0];
                    var box = el.getBoundingClientRect();
                    var body = document.body;
                    var clientTop  = el.clientTop  || body.clientTop  || 0;
                    var clientLeft = el.clientLeft || body.clientLeft || 0;
                    var scrollTop  = window.pageYOffset || el.scrollTop;
                    var scrollLeft = window.pageXOffset || el.scrollLeft;
                    return {
                        top: box.top  + scrollTop  - clientTop,
                        left: box.left + scrollLeft - clientLeft
                    };
                }
                else {
                    return null;
                }
            },
            css: function (props, value) {
                var i;
                if (arguments.length === 1) {
                    if (typeof props === 'string') {
                        if (this[0]) return window.getComputedStyle(this[0], null).getPropertyValue(props);
                    }
                    else {
                        for (i = 0; i < this.length; i++) {
                            for (var prop in props) {
                                this[i].style[prop] = props[prop];
                            }
                        }
                        return this;
                    }
                }
                if (arguments.length === 2 && typeof props === 'string') {
                    for (i = 0; i < this.length; i++) {
                        this[i].style[props] = value;
                    }
                    return this;
                }
                return this;
            },
    
            //Dom manipulation
            each: function (callback) {
                for (var i = 0; i < this.length; i++) {
                    callback.call(this[i], i, this[i]);
                }
                return this;
            },
            html: function (html) {
                if (typeof html === 'undefined') {
                    return this[0] ? this[0].innerHTML : undefined;
                }
                else {
                    for (var i = 0; i < this.length; i++) {
                        this[i].innerHTML = html;
                    }
                    return this;
                }
            },
            text: function (text) {
                if (typeof text === 'undefined') {
                    if (this[0]) {
                        return this[0].textContent.trim();
                    }
                    else return null;
                }
                else {
                    for (var i = 0; i < this.length; i++) {
                        this[i].textContent = text;
                    }
                    return this;
                }
            },
            is: function (selector) {
                if (!this[0]) return false;
                var compareWith, i;
                if (typeof selector === 'string') {
                    var el = this[0];
                    if (el === document) return selector === document;
                    if (el === window) return selector === window;
    
                    if (el.matches) return el.matches(selector);
                    else if (el.webkitMatchesSelector) return el.webkitMatchesSelector(selector);
                    else if (el.mozMatchesSelector) return el.mozMatchesSelector(selector);
                    else if (el.msMatchesSelector) return el.msMatchesSelector(selector);
                    else {
                        compareWith = $(selector);
                        for (i = 0; i < compareWith.length; i++) {
                            if (compareWith[i] === this[0]) return true;
                        }
                        return false;
                    }
                }
                else if (selector === document) return this[0] === document;
                else if (selector === window) return this[0] === window;
                else {
                    if (selector.nodeType || selector instanceof Dom7) {
                        compareWith = selector.nodeType ? [selector] : selector;
                        for (i = 0; i < compareWith.length; i++) {
                            if (compareWith[i] === this[0]) return true;
                        }
                        return false;
                    }
                    return false;
                }
    
            },
            index: function () {
                if (this[0]) {
                    var child = this[0];
                    var i = 0;
                    while ((child = child.previousSibling) !== null) {
                        if (child.nodeType === 1) i++;
                    }
                    return i;
                }
                else return undefined;
            },
            eq: function (index) {
                if (typeof index === 'undefined') return this;
                var length = this.length;
                var returnIndex;
                if (index > length - 1) {
                    return new Dom7([]);
                }
                if (index < 0) {
                    returnIndex = length + index;
                    if (returnIndex < 0) return new Dom7([]);
                    else return new Dom7([this[returnIndex]]);
                }
                return new Dom7([this[index]]);
            },
            append: function (newChild) {
                var i, j;
                for (i = 0; i < this.length; i++) {
                    if (typeof newChild === 'string') {
                        var tempDiv = document.createElement('div');
                        tempDiv.innerHTML = newChild;
                        while (tempDiv.firstChild) {
                            this[i].appendChild(tempDiv.firstChild);
                        }
                    }
                    else if (newChild instanceof Dom7) {
                        for (j = 0; j < newChild.length; j++) {
                            this[i].appendChild(newChild[j]);
                        }
                    }
                    else {
                        this[i].appendChild(newChild);
                    }
                }
                return this;
            },
            prepend: function (newChild) {
                var i, j;
                for (i = 0; i < this.length; i++) {
                    if (typeof newChild === 'string') {
                        var tempDiv = document.createElement('div');
                        tempDiv.innerHTML = newChild;
                        for (j = tempDiv.childNodes.length - 1; j >= 0; j--) {
                            this[i].insertBefore(tempDiv.childNodes[j], this[i].childNodes[0]);
                        }
                        // this[i].insertAdjacentHTML('afterbegin', newChild);
                    }
                    else if (newChild instanceof Dom7) {
                        for (j = 0; j < newChild.length; j++) {
                            this[i].insertBefore(newChild[j], this[i].childNodes[0]);
                        }
                    }
                    else {
                        this[i].insertBefore(newChild, this[i].childNodes[0]);
                    }
                }
                return this;
            },
            insertBefore: function (selector) {
                var before = $(selector);
                for (var i = 0; i < this.length; i++) {
                    if (before.length === 1) {
                        before[0].parentNode.insertBefore(this[i], before[0]);
                    }
                    else if (before.length > 1) {
                        for (var j = 0; j < before.length; j++) {
                            before[j].parentNode.insertBefore(this[i].cloneNode(true), before[j]);
                        }
                    }
                }
            },
            insertAfter: function (selector) {
                var after = $(selector);
                for (var i = 0; i < this.length; i++) {
                    if (after.length === 1) {
                        after[0].parentNode.insertBefore(this[i], after[0].nextSibling);
                    }
                    else if (after.length > 1) {
                        for (var j = 0; j < after.length; j++) {
                            after[j].parentNode.insertBefore(this[i].cloneNode(true), after[j].nextSibling);
                        }
                    }
                }
            },
            next: function (selector) {
                if (this.length > 0) {
                    if (selector) {
                        if (this[0].nextElementSibling && $(this[0].nextElementSibling).is(selector)) return new Dom7([this[0].nextElementSibling]);
                        else return new Dom7([]);
                    }
                    else {
                        if (this[0].nextElementSibling) return new Dom7([this[0].nextElementSibling]);
                        else return new Dom7([]);
                    }
                }
                else return new Dom7([]);
            },
            nextAll: function (selector) {
                var nextEls = [];
                var el = this[0];
                if (!el) return new Dom7([]);
                while (el.nextElementSibling) {
                    var next = el.nextElementSibling;
                    if (selector) {
                        if($(next).is(selector)) nextEls.push(next);
                    }
                    else nextEls.push(next);
                    el = next;
                }
                return new Dom7(nextEls);
            },
            prev: function (selector) {
                if (this.length > 0) {
                    if (selector) {
                        if (this[0].previousElementSibling && $(this[0].previousElementSibling).is(selector)) return new Dom7([this[0].previousElementSibling]);
                        else return new Dom7([]);
                    }
                    else {
                        if (this[0].previousElementSibling) return new Dom7([this[0].previousElementSibling]);
                        else return new Dom7([]);
                    }
                }
                else return new Dom7([]);
            },
            prevAll: function (selector) {
                var prevEls = [];
                var el = this[0];
                if (!el) return new Dom7([]);
                while (el.previousElementSibling) {
                    var prev = el.previousElementSibling;
                    if (selector) {
                        if($(prev).is(selector)) prevEls.push(prev);
                    }
                    else prevEls.push(prev);
                    el = prev;
                }
                return new Dom7(prevEls);
            },
            parent: function (selector) {
                var parents = [];
                for (var i = 0; i < this.length; i++) {
                    if (selector) {
                        if ($(this[i].parentNode).is(selector)) parents.push(this[i].parentNode);
                    }
                    else {
                        parents.push(this[i].parentNode);
                    }
                }
                return $($.unique(parents));
            },
            parents: function (selector) {
                var parents = [];
                for (var i = 0; i < this.length; i++) {
                    var parent = this[i].parentNode;
                    while (parent) {
                        if (selector) {
                            if ($(parent).is(selector)) parents.push(parent);
                        }
                        else {
                            parents.push(parent);
                        }
                        parent = parent.parentNode;
                    }
                }
                return $($.unique(parents));
            },
            find : function (selector) {
                var foundElements = [];
                for (var i = 0; i < this.length; i++) {
                    var found = this[i].querySelectorAll(selector);
                    for (var j = 0; j < found.length; j++) {
                        foundElements.push(found[j]);
                    }
                }
                return new Dom7(foundElements);
            },
            children: function (selector) {
                var children = [];
                for (var i = 0; i < this.length; i++) {
                    var childNodes = this[i].childNodes;
    
                    for (var j = 0; j < childNodes.length; j++) {
                        if (!selector) {
                            if (childNodes[j].nodeType === 1) children.push(childNodes[j]);
                        }
                        else {
                            if (childNodes[j].nodeType === 1 && $(childNodes[j]).is(selector)) children.push(childNodes[j]);
                        }
                    }
                }
                return new Dom7($.unique(children));
            },
            remove: function () {
                for (var i = 0; i < this.length; i++) {
                    if (this[i].parentNode) this[i].parentNode.removeChild(this[i]);
                }
                return this;
            },
            add: function () {
                var dom = this;
                var i, j;
                for (i = 0; i < arguments.length; i++) {
                    var toAdd = $(arguments[i]);
                    for (j = 0; j < toAdd.length; j++) {
                        dom[dom.length] = toAdd[j];
                        dom.length++;
                    }
                }
                return dom;
            }
        };
        $.fn = Dom7.prototype;
        $.unique = function (arr) {
            var unique = [];
            for (var i = 0; i < arr.length; i++) {
                if (unique.indexOf(arr[i]) === -1) unique.push(arr[i]);
            }
            return unique;
        };
    
        return $;
    })();
    

    /*===========================
     Get Dom libraries
     ===========================*/
    var swiperDomPlugins = ['jQuery', 'Zepto', 'Dom7'];
    for (var i = 0; i < swiperDomPlugins.length; i++) {
    	if (window[swiperDomPlugins[i]]) {
    		addLibraryPlugin(window[swiperDomPlugins[i]]);
    	}
    }
    // Required DOM Plugins
    var domLib;
    if (typeof Dom7 === 'undefined') {
    	domLib = window.Dom7 || window.Zepto || window.jQuery;
    }
    else {
    	domLib = Dom7;
    }

    /*===========================
    Add .swiper plugin from Dom libraries
    ===========================*/
    function addLibraryPlugin(lib) {
        lib.fn.swiper = function (params) {
            var firstInstance;
            lib(this).each(function () {
                var s = new Swiper(this, params);
                if (!firstInstance) firstInstance = s;
            });
            return firstInstance;
        };
    }
    
    if (domLib) {
        if (!('transitionEnd' in domLib.fn)) {
            domLib.fn.transitionEnd = function (callback) {
                var events = ['webkitTransitionEnd', 'transitionend', 'oTransitionEnd', 'MSTransitionEnd', 'msTransitionEnd'],
                    i, j, dom = this;
                function fireCallBack(e) {
                    /*jshint validthis:true */
                    if (e.target !== this) return;
                    callback.call(this, e);
                    for (i = 0; i < events.length; i++) {
                        dom.off(events[i], fireCallBack);
                    }
                }
                if (callback) {
                    for (i = 0; i < events.length; i++) {
                        dom.on(events[i], fireCallBack);
                    }
                }
                return this;
            };
        }
        if (!('transform' in domLib.fn)) {
            domLib.fn.transform = function (transform) {
                for (var i = 0; i < this.length; i++) {
                    var elStyle = this[i].style;
                    elStyle.webkitTransform = elStyle.MsTransform = elStyle.msTransform = elStyle.MozTransform = elStyle.OTransform = elStyle.transform = transform;
                }
                return this;
            };
        }
        if (!('transition' in domLib.fn)) {
            domLib.fn.transition = function (duration) {
                if (typeof duration !== 'string') {
                    duration = duration + 'ms';
                }
                for (var i = 0; i < this.length; i++) {
                    var elStyle = this[i].style;
                    elStyle.webkitTransitionDuration = elStyle.MsTransitionDuration = elStyle.msTransitionDuration = elStyle.MozTransitionDuration = elStyle.OTransitionDuration = elStyle.transitionDuration = duration;
                }
                return this;
            };
        }
        if (!('outerWidth' in domLib.fn)) {
            domLib.fn.outerWidth = function (includeMargins) {
                if (this.length > 0) {
                    if (includeMargins)
                        return this[0].offsetWidth + parseFloat(this.css('margin-right')) + parseFloat(this.css('margin-left'));
                    else
                        return this[0].offsetWidth;
                }
                else return null;
            };
        }
    }

    window.Swiper = Swiper;
})();
/*===========================
Swiper AMD Export
===========================*/
if (typeof(module) !== 'undefined')
{
    module.exports = window.Swiper;
}
else if (typeof define === 'function' && define.amd) {
    define([], function () {
        'use strict';
        return window.Swiper;
    });
}
//# sourceMappingURL=maps/swiper.js.map
;
var SwitchManager = function (switchertype) {
    var self = this;

    var params = {
        type: SWITCHER_DESKTOP,
        width: "960"
    };

    if (switchertype != undefined)
    {
        type = switchertype;
    }

    self.correct = function () {
        $('.std-component, .main, .footer, .header').each(function (i, obj) {
            var elem = $(obj);
            if (elem.css('position') == 'absolute')
            {
                elem.css('position', 'relative');
                elem.css('top', '0');
                elem.css('left', '0');
                elem.css('float', 'left');
                elem.css('width', '100%');
            }
        });
        
    }
    
}
;
var Grouping = function (items) {
    this.ctrlIsPressed = false;
    this.selectedItems = ko.observableArray(items || []);
    this.clipboardGroup = []; // clipboard group
    this.showGroupingOptions = ko.observable(null);
    this.queueToSelectAfterPaste = [];
    this.editableGroupId = null;
    this.section = null;

    this.multipleSelection = {
        isActive: false,
        startX: 0,
        startY: 0,
        postSelectionClick: false,
        mouseDown: function (e) {

            this.postSelectionClick = false;
            if (!this.isActive && Grouping.isAllowedRectangleSelect(e.target) && !Helpers.hasInputClass(e.target)) {
                
                e = e || window.event
                Grouping.fixPageXY(e);

                this.isActive = true;
                this.startX = e.pageX;
                this.startY = e.pageY;
                console.log("Grouping mousedown X:" + this.startX + " Y:" + this.startY);

            }
        },
        mouseMove: function (e) {

            if (this.isActive) {
                this.clearDefaultSelection();
                e.preventDefault();

                e = e || window.event
                Grouping.fixPageXY(e);

                var selection = $('#group-multiple-selection');
                selection.css('display', 'block');

                var resultX = e.pageX;
                var resultY = e.pageY;           

                var width = Math.abs(resultX - this.startX);
                var height = Math.abs(resultY - this.startY);

                if (resultX > this.startX) {
                    resultX = this.startX;
                }
                if (resultY > this.startY) {
                    resultY = this.startY;
                }

                selection.css('top', resultY + 'px');
                selection.css('left', resultX + 'px');
                selection.css('width', width + 'px');
                selection.css('height', height + 'px');

                //console.log("Grouping mousemove X:" + resultX + " Y:" + resultY + " W:" + width + " H:" + height );
            }
        },
        mouseUp: function (e) {
            if (this.isActive) {
                e.preventDefault();
                e.stopPropagation();
                e = e || window.event;
                Grouping.fixPageXY(e);
                var resultX = e.pageX;
                var resultY = e.pageY;

                var width = Math.abs(resultX - this.startX);
                var height = Math.abs(resultY - this.startY);
                if (resultX > this.startX) {
                    resultX = this.startX;
                }
                if (resultY > this.startY) {
                    resultY = this.startY;
                }

                //add elements in area
                var addedGroups = new Array();
                var elements = $('.std-component');

                if (!grouping.ctrlIsPressed)
                {
                    Grouping.dropItems();
                }


                for (var i = elements.length - 1; i >= 0; i--) {
                    if (Grouping.elementInsideArea(resultX, resultY, width, height, elements[i])) { //elemend inside area
                        console.log('inside');
                       
                        var prop = Grouping.getComponent(elements[i].id).getProperty(GROUPID);
                        if (prop == null || prop.value == '') {
                            Grouping.addItem(elements[i], true);
                        }
                        else {
                            if (addedGroups.indexOf(prop.value) < 0) {
                                Grouping.addItem(elements[i]);
                                addedGroups.push(prop.value);
                            }
                        }
                    }
                }
                //end add elements in area


                //wrap and bind
                Grouping.wrap();
                Resizer.groupBind();

                //console.log("Grouping mouseup");
                this.startX = 0;
                this.startY = 0;
                this.isActive = false;
                var selection = $('#group-multiple-selection');
                selection.css('display', 'none');
                this.postSelectionClick = true;
            }
        },

        stop: function() {
            this.isActive = false;
            $('#group-multiple-selection').css('display', 'none');
            this.postSelectionClick = true;
        },

        clearDefaultSelection: function() {
            if (window.getSelection) {
                window.getSelection().removeAllRanges();
            } else if (document.selection) {
                document.selection.empty();
            }
        }
    };

    this.dropEditableGroupId = function () {
        this.editableGroupId = null;
    }
}

Grouping.init = function () {
    grouping.ctrlIsPressed = false;

    $(document).keydown(function (event) {
        if (event.ctrlKey || event.metaKey) {
            grouping.ctrlIsPressed = true;
            //console.log("Grouping.ctrlIsPressed = " + grouping.ctrlIsPressed);
        }

        //dragkeys
        if (Grouping.isActive()) {
            dragDrop.dragKeys(event);
        }
    });

    $(document).keyup(function (event) {
        if (!event.ctrlKey && !event.metaKey) {
            grouping.ctrlIsPressed = false;
            //console.log("Grouping.ctrlIsPressed = " + grouping.ctrlIsPressed);
        }
    });

    //region multiple selection
    $(document).mousedown(function (e) {
        grouping.multipleSelection.mouseDown(e);
    });

    $(document).mousemove(function (e) {
        grouping.multipleSelection.mouseMove(e);
    });

    $(document).mouseup(function (e) {
        grouping.multipleSelection.mouseUp(e);       
    });
    //endregion multiple selection

    $('#bgsite, .wrapper').on('click', function (e) {
        if (e.target.tagName == 'A') return;

        if (!UI.getSetting("ispreview") && !dragDrop.groupDragging && !grouping.multipleSelection.isActive) {
            if (grouping.multipleSelection.postSelectionClick) {
                grouping.multipleSelection.postSelectionClick = false;
            }
            else {
                Grouping.dropItems();
            }
        }
    });

    $('.wrapper').on("click", ".std-component", function (e) {
        if (!UI.getSetting("ispreview")) {
            if (UI.isckeditorworking) {
                event.stopPropagation();
                return false;
            }

            var hasAnyGroup = Grouping.hasAnyGroup(this.id);
            var isSelected = Grouping.isSelected(this.id);

            if (grouping.ctrlIsPressed) {
                e.preventDefault();
                e.stopPropagation();
                Grouping.addItem(this);
                return false;
            }
            else if (hasAnyGroup && !dragDrop.groupDragging) {
                e.preventDefault();
                e.stopPropagation();
                Grouping.dropItems();
                Grouping.addItem(this);
            }
        }
    });

    $('.wrapper').on("dblclick", ".std-component", function (e) {
        if (!UI.getSetting("ispreview")) {
            if (UI.isckeditorworking) {
                event.stopPropagation();
                return false;
            }

            var isSelected = Grouping.isSelected(this.id);

            //if ctrl is pressed - selection logic is changed and dblclick is disabled
            if (isSelected && !grouping.ctrlIsPressed) {
  
                var groupId = Grouping.getGroupId(this.id);
                grouping.editableGroupId = groupId;

                var component = UI.siteComponentRepository.lookupData({ id: this.id });
                UI.callEditor(component);
                Grouping.dropItems();
                $(this).highlightSelectedElement(component, true);

                console.log('grouping dblclick: ' + groupId);
            }
        }
    });

    $(document).on("dblclick", function (e) {
        if (!UI.getSetting("ispreview")) {

            if (Grouping.isAllowedToDropEditableGroupId(e.target))
            {
                grouping.dropEditableGroupId();
            }
            
        }
    });

    if (!UI.getSetting("ispreview")) {
        //ko.applyBindings(grouping);
    }
    console.log('Grouping initialized');
}

Grouping.addItem = function (element, ignoreWrap) {
    clipBoard.selectedItem(null);
    var currentElementProperty = Grouping.getComponent(element.id).getProperty(GROUPID);
    var inst = $(".std-component");

    //place current element to last position
    var elementIndex = inst.index(element);
    var instLastIndex = inst.length - 1;
    if (elementIndex != instLastIndex) {
        var tmp = inst[elementIndex];
        inst[elementIndex] = inst[instLastIndex];
        inst[instLastIndex] = tmp;
    }

    for (var i = inst.length - 1; i >= 0; i--) {
        if (inst[i].id == "") {
            //photo-tour exception
            continue;
        }

        var prop = Grouping.getComponent(inst[i].id).getProperty(GROUPID);
        if (inst[i].id != element.id && (prop == null || currentElementProperty == null || prop.value == '' || currentElementProperty.value == '' || prop.value != currentElementProperty.value)) {
            continue;
        }
        if (!Grouping.compareParent(inst[i])) {
            continue;
        }

        if ($(inst[i]).hasClass('std-component-fixed')) {
            continue;
        }

        if ($(inst[i]).hasClass('std-store-subcomponent')) {
            continue;
        }

        if (_.indexOf(grouping.selectedItems(), inst[i]) < 0) {
            grouping.selectedItems.push(inst[i]);
            $(inst[i]).highlightGroupElement(Grouping.getComponent($(inst[i]).attr('id')));
            //console.log("items array: " + grouping.selectedItems());
            $(inst[i]).mousemove(function () {
                var currentElement = $(this);
                currentElement.data('selected', true);
                if (!currentElement.hasClass('std-component-fixed')) {
                    currentElement.addClass('drag');
                }
                if (grouping.ctrlIsPressed) {
                    currentElement.data('selected', false);
                    currentElement.removeClass('drag');
                }
            });
            $(inst[i]).mouseleave(function () {
                var currentElement = $(this);
                currentElement.data('selected', false);
                currentElement.removeClass('drag');
            });
         
            if (Grouping.isActive()) {
                dragDrop.groupingDragKeys();
            }

            
            if (i == instLastIndex) {
                //triggered after onclick
                //set draggable for current element at once
                setTimeout(function () {
                    var currentElement = $('#' + element.id);
                    currentElement.data('selected', true);
                    if (!currentElement.hasClass('std-component-fixed')) {
                        currentElement.addClass('drag');
                    }
                }, 0);
            }
            
        }
        else {
            Grouping.dropItem(inst[i]);
            if (i == instLastIndex) {
                //triggered after onclick
                //remove draggable for current element
                setTimeout(function () {
                    $('#' + element.id).data('selected', false);
                    $('#' + element.id).removeClass('drag');
                }, 0);
            }
        }
    }//end for

    if (!ignoreWrap) {
        Grouping.wrap();
        Resizer.groupBind();   
    }

    if (grouping.selectedItems().length > 1)
        grouping.showGroupingOptions(true);
}

Grouping.isActive = function () {
    if (UI.getSetting("ispreview")) {
        return false;
    }
    return grouping.selectedItems().length != 0;
}

Grouping.getComponent = function(id) {
    var com = UI.siteComponentRepository.lookupData({ id: id });
    return com;
}

Grouping.dropItems = function () {
    console.log("items dropped: " + grouping.selectedItems());
    if (Grouping.isActive()) {
        dragDrop.releaseElement();
    }
    grouping.section = null;
    while (grouping.selectedItems().length > 0) {
        Grouping.dropItem(grouping.selectedItems()[0]);
    }
    
    $('.' + GROUP_WRAPPER).remove();
}

Grouping.dropItem = function (item) {
    grouping.selectedItems.remove(item);
    $(item).unbind('mousemove mouseleave');
    if (grouping.selectedItems().length == 0) {
        grouping.showGroupingOptions(null);
    }
}
Grouping.checkElementStretchedToFullWidth = function (element) {
    var component = UI.siteComponentRepository.lookupData({ id: $(element).getId() });
    var stretchToFullWidth = StretcherFactory.getCurrentStretchStatus(component);
    return stretchToFullWidth;
}

Grouping.wrap = function () {
    $('.' + GROUP_WRAPPER).remove();
    if (grouping.selectedItems().length) {
        var top = null, left = null, width = null, height = null, hasFixed = false;
        ko.utils.arrayForEach(grouping.selectedItems(),
            function(item) {
                var element = $(item);
                if (!hasFixed && element.hasClass('std-component-fixed')) {
                    hasFixed = true;
                }
                var elementWidth = element.outerWidth() + (Grouping.checkElementStretchedToFullWidth(element) ? 0 : 2);
                var elementHeight = element.outerHeight() + 2;
                var elementTop = element.offset().top - 1;
                var elementLeft = element.offset().left - 1;
                if (top == null || left == null || width == null || height == null) {
                    top = elementTop;
                    left = elementLeft;
                    width = elementWidth;
                    height = elementHeight;
                } else {
                    //calculate wrapper position and size
                    if (elementTop < top) {
                        if ((top - elementTop + height) > height) {
                            height = top - elementTop + height;
                            if (elementHeight > height) {
                                height = elementHeight;
                            }
                        }
                        top = elementTop;
                    } else {
                        if ((elementTop - top + elementHeight) > height) {
                            height = elementTop - top + elementHeight;
                        }
                    }
                    if (elementLeft < left) {
                        if ((left - elementLeft + width) > width) {
                            width = left - elementLeft + width;
                            if (elementWidth > width) {
                                width = elementWidth;
                            }
                        }
                        left = elementLeft;
                    } else {
                        if ((elementLeft - left + elementWidth) > width) {
                            width = elementLeft - left + elementWidth;
                        }
                    }
                }
                var newGuid = Grouping.generateGuid();

                var selectWrapper = $('<div class="' + GROUP_WRAPPER + '"></div>');
                selectWrapper.css({
                    width: elementWidth + 'px',
                    height: elementHeight + 'px',
                    pointerEvents: 'none',
                    top: elementTop + 'px',
                    left: elementLeft + 'px'
                });
                selectWrapper.data('for', element.getId());
                selectWrapper.attr("id", newGuid);
                $('body').append(selectWrapper);
            });
        var container = $('<div class="' + GROUP_WRAPPER + ' ' + GROUP_WRAPPER_OUTER + (hasFixed ? ' ' + GROUP_WRAPPER_FIXED : '') + '"><ul class="' + GROUP_WRAPPER_MENU + '"></ul></div>');
        container.css({ width: width, height: height, left: left, top: top });
        $('body').append(container);

		//ability to move grouped elements with .group-wrapper.outer
        $('.group-wrapper.outer').on('mousedown', function (event) {
            if ($(event.target).closest('.group-actions-menu').length === 0) { // Stop event bubbling if it occurs on child elements
                var elementFromSection = grouping.selectedItems()[0]
                dragDrop.startDragMouse.call(elementFromSection, event);
            }
        });
    }
}

Grouping.groupUp = function()
{
    var groupId = Grouping.generateGuid();
    ko.utils.arrayForEach(grouping.selectedItems(), function (item) {
        var com = Grouping.getComponent(item.id);
        com.setProperty(GROUPID, groupId, true);
    });
}

Grouping.unGroup = function () {
    ko.utils.arrayForEach(grouping.selectedItems(), function (item) {
        var com = Grouping.getComponent(item.id);
        com.setProperty(GROUPID, '', true);
    });
}

Grouping.copyGroup = function () {
    grouping.clipboardGroup = []; // clear clipboard group
    ko.utils.arrayForEach(grouping.selectedItems(), function (item) {
        var com = Grouping.getComponent(item.id);
        clipBoard.copyComponent(GROUP, com);
    });
}

Grouping.duplicateGroup = function() {
    Grouping.copyGroup();
    Grouping.pasteGroup();
}

Grouping.pasteGroup = function(isPaste) {
    var groupId = Grouping.generateGuid(); // create new group
    for (var i = 0; i < grouping.clipboardGroup.length; i++) {
        if (grouping.clipboardGroup[i].getProperty(GROUPID).value == String.empty) {
            groupId = false;
        }

        if (i == grouping.clipboardGroup.length - 1 && isPaste) {
            clipBoard.pasteComponent(grouping.clipboardGroup[i], groupId, null, false, false, true);
        } else {
            clipBoard.pasteComponent(grouping.clipboardGroup[i], groupId);
        }
    }
    grouping.clipboardGroup = []; // clear clipboard groups

    //remove selection from old items
    Grouping.dropItems();

    //select new items
    grouping.queueToSelectAfterPaste.forEach(function (id) {
        var alreadyAdded = grouping.selectedItems().map(function (el) { return el.id });

        if (alreadyAdded.indexOf(id.substring(1)) == -1) {
            Grouping.addItem($(id)[0]);
        }   
    });

    //remove items from queue to select
    grouping.queueToSelectAfterPaste.splice(0);

}

Grouping.generateGuid = function () {
    function S4() {
        return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
    }
    guid = (S4() + S4() + "-" + S4() + "-4" + S4().substr(0, 3) + "-" + S4() + "-" + S4() + S4() + S4()).toLowerCase();
    return guid;
}

Grouping.getMinOffsetTopValue = function () {
    var result = null;
    ko.utils.arrayForEach(grouping.selectedItems(), function (item) {
        if (result == null || item.offsetTop < result) {
            result = item.offsetTop;
        }
    });
    return result;
}

Grouping.getGroupFullOffset = function () {
    var result =
        {
            groupHeight: 0,
            offsetY: null
        };
    var bottomLine = null;

    ko.utils.arrayForEach(grouping.selectedItems(), function (item) {
        if (result.offsetY == null || item.offsetTop < result.offsetY) {
            result.offsetY = item.offsetTop;
        }
        if (bottomLine == null || item.offsetTop + item.offsetHeight > bottomLine) {
            bottomLine = item.offsetTop + item.offsetHeight;
        }
    });
    result.groupHeight = bottomLine - result.offsetY;
    return result;
}

Grouping.compareParent = function (element) {
    try {
        if (grouping.selectedItems().length == 0) {
            return true;
        }
        if (element.parentElement.id == grouping.selectedItems()[0].parentElement.id) {
            return true;
        }
        else {
            return false;
        }
    }
    catch (e) {
        return false;
    }
}

Grouping.fixPageXY = function(e) {
    if (e.pageX == null && e.clientX != null) {
        var html = document.documentElement
        var body = document.body
        e.pageX = e.clientX + (html.scrollLeft || body && body.scrollLeft || 0)
        e.pageX -= html.clientLeft || 0
        e.pageY = e.clientY + (html.scrollTop || body && body.scrollTop || 0)
        e.pageY -= html.clientTop || 0
    }
}

Grouping.elementInsideArea = function (x, y, w, h, element) {
    var endX = x + w;
    var endY = y + h;
    var cumulativeOffset = Grouping.cumulativeOffset(element);
    var elementX = cumulativeOffset.left;
    var elementY = cumulativeOffset.top;
    var elementEndX = elementX + element.offsetWidth;
    var elementEndY = elementY + element.offsetHeight;
 
    if (x <= elementX && y <= elementY && endX > elementEndX && endY > elementEndY) {
        return true;
    }
    else {
        return false;
    }
}

Grouping.cumulativeOffset = function(element) {
    var top = 0, left = 0;
    do {
        top += element.offsetTop || 0;
        left += element.offsetLeft || 0;
        element = element.offsetParent;
    } while (element);

    return {
        top: top,
        left: left
    };
};

Grouping.deleteGroupSelectedComponents = function () {

    var selectedItems = _.map(grouping.selectedItems(), _.clone);

    for (var i = selectedItems.length - 1; i >= 0; i--) {
        if ($('#' + selectedItems[i].id).attr('contenteditable') == "true") return;
    }

    var redo = function (selectedItems) {
        var removedArray = new Array();
        for (var i = selectedItems.length - 1; i >= 0; i--) {

            var id = selectedItems[i].id;
            var selectedComponent = Grouping.getComponent(id);
            var removedModel = UI.siteComponentRepository.remove({ id: selectedComponent.id });
            removedArray.push({ model: removedModel });
        }
        UI.removeEditor();
        grouping.selectedItems([]);
        Grouping.wrap();
        grouping.showGroupingOptions(false);
        return removedArray;
    };
    var removed = redo(selectedItems);

    var undo = function (removed) {

        function returnRemoveModel(removedModel) {
            var compiledTemplate = TemplateFactory.templateFor(removedModel, VIEWER_TEMPLATE).compiledTemplate;
            $('#' + removedModel.parentComponent.id).append(compiledTemplate);
            removedModel.viewer();

            var specialComponentId = "88cbc4c2-bb93-45e9-a318-57218c7c0171";
            if (removedModel.componentId === specialComponentId) {
                UI.renderMenus();
            }

            if (removedModel.isDockable && removedModel.children.length > 0) {
                removedModel.children.forEach(returnRemoveModel);
            }
        }//end function returnRemoveModel

        for (var i = removed.length - 1; i >= 0; i--) {
            var parent = UI.siteComponentRepository.lookupData({ id: removed[i].model.parentComponent.id });
            UI.siteComponentRepository.appendTo(removed[i].model, parent);
            returnRemoveModel(removed[i].model);
        }
    }

    UI.undoManagerAdd({ undo: function () { undo(removed) }, redo: function () { redo(selectedItems) } });


}

Grouping.showOrHideGroupSelectedComponents = function (isHideProcess) {
    isHideProcess = defined(isHideProcess) ? isHideProcess.toBoolean() : false;
    var selectedItems = _.map(grouping.selectedItems(), _.clone);

    var hiddenArray = [];
    for (var i = selectedItems.length - 1; i >= 0; i--) {
        if ($('#' + selectedItems[i].id).attr('contenteditable') == "true") {
            return;
        } else {
            var id = selectedItems[i].id;
            var selectedComponent = Grouping.getComponent(id);
            if (selectedComponent.proto.name === SIGNIN) return;
            var isHide = selectedComponent.getProperty(HIDE_COMPONENT);
            var newValue = isHideProcess;
            var oldValue = isHide != null ? isHide.value.toBoolean() : false;
            if (newValue !== oldValue) {
                hiddenArray.push({ component: selectedComponent, property: HIDE_COMPONENT, newvalue: newValue.toString(), oldvalue: oldValue.toString() });
            }
        }
    }
    var undoredo = function(value, component) {
        UI.removeEditor();
        grouping.selectedItems([]);
        grouping.showGroupingOptions(false);
        Grouping.wrap();
        value = value.toBoolean();
        if (value || UI.getSetting('showHidden')) {
            UI.actionService.runActionForComponent(component, ACTION_REMOVE_FROM_FORM, true);
        }
        if (!value || UI.getSetting('showHidden')) {
            UI.actionService.runActionForComponent(component, ACTION_ADD_TO_FORM, true);
        }
    }
    UI.undoManagerAddSimpleArr(hiddenArray, undoredo, undoredo, true);    
}

Grouping.setElementsAccess = function () {
    var groupingElements = document.getElementsByClassName('grouping-item');
    for (i = 0; i < groupingElements.length; i++) {
        var element = groupingElements[i];
        if (grouping.showGroupingOptions() === true) {
            element.classList.remove('disabled');
        }
        else {
            element.classList.add('disabled');
        }
    }
}

Grouping.isAllowedRectangleSelect = function (target) {

    if (target === undefined || target === null)
        return true;

    if ($(target).closest('.std-component-fixed').length > 0)
        return false;

    if ($(target).closest('.menu-resizer').length > 0)
        return false;

    var bgSite = $(target).parents('#bg').length;
    var wrapper = $(target).parents('.wrapper').length;
    return (bgSite + wrapper) > 0;
}

Grouping.isAllowedToDropEditableGroupId = function (target) {

    if (target === undefined || target === null)
        return true;

    var stdComponent = $(target).closest('.std-component').length;

    if (stdComponent > 0)
        return false;

    if ($(target).is('body'))
        return true;

    var bgSite = $(target).parents('#bg').length;
    var wrapper = $(target).parents('.wrapper').length;

    return (bgSite + wrapper) > 0;
}

Grouping.getGroupZIndexValue = function (isMinValue) {
    isMinValue = isMinValue || false;
    var result = null;

    grouping.selectedItems().forEach(function (element) {
        var zIndex = Number($(element).css("z-index"));

        if (result == null)
            result = zIndex;

        result = isMinValue ? Math.min(result, zIndex) : Math.max(result, zIndex);
    });

    return result;
}

Grouping.getGroupItemsCountByZindex = function (zIndex) {
    var result = 0;

    grouping.selectedItems().forEach(function (element) {
        var itemZIndex = Number($(element).css("z-index"));

        if (itemZIndex === zIndex) {
            result++;
        }
    });

    return result;
}

Grouping.hasAnyGroup = function (elementId) {
    var prop = Grouping.getComponent(elementId).getProperty(GROUPID);

    if (prop == null || prop.value == '') {
        return false;
    }

    return true;
}

Grouping.getGroupId = function (elementId) {
    var prop = Grouping.getComponent(elementId).getProperty(GROUPID);

    if (prop == null || prop.value == '') {
        return null;
    }

    return prop.value;
}

Grouping.isSelected = function (elementId) {
    var alreadyAdded = grouping.selectedItems().map(function (el) { return el.id });

    return alreadyAdded.indexOf(elementId) != -1;
}


var grouping = new Grouping([]);
;
var PostLoadInit = function () {

    var self = this;

    self.execute = function () {
        if (!UI.getSetting("ispreview")) {
            InitPageResizers();
            InitPositionFieldValidation();
            InitMetaFieldValidation();
            InitTopMenuScrolling();
        }
        else {
            InitHashChangeEvent();            
        }
    }
}

var InitPageResizers = function () {

    function showTab(a, component) {
        $(' .tab-li').removeClass('active');
        $(a).parent().addClass('active');
        $(' .tab-pane').removeClass('active');
        $('.popover-content #' + component).addClass('active');
    }
    $('#header-resizer').css({ 'top' : $('.header').height()});

    $('#header-resizer').draggable({
        axis: "y",
        start: function () {
            Helpers.removeSelectWrapper();
        },
        drag: function () {
            Resizer.resizeMenu($(this));
            Grouping.wrap();
        },
        stop: function () {
            Resizer.resizedMenuSave($(this));
        }
    });

    $('#page-resizer').draggable({
        axis: "y",
        start: function () {
            Helpers.removeSelectWrapper();
        },
        drag: function () {
            Resizer.resizeMenu($(this));
            Grouping.wrap();
        },
        stop: function () {
            Resizer.resizedMenuSave($(this));
        }
    });

    $('#bottom-body-resizer').draggable({
        axis: "y",
        start: function () {
            Helpers.removeSelectWrapper();
        },
        drag: function () {
            Resizer.resizeMenu($(this));
            Grouping.wrap();
        },
        stop: function () {
            Resizer.resizedMenuSave($(this));
        }
    });    
}

var InitTopMenuScrolling = function () {
    $(window).scroll(function () { navigation.scroll(false); });
    $(window).resize(function () { navigation.scroll(true); });
    
    var navigation = {
        scroll: function (isResize) {
            var scroll_left = $(window).scrollLeft();

            var add_new_com_dinamyc_left = $('.site-wrapper')[0].offsetLeft;
            var add_new_com_dinamyc_width = $('.site-wrapper')[0].offsetWidth;

            $('.main-menu').css('left', (this.settings.main_menu - scroll_left) + 'px');
            $('.position-add-new-com').css('left', (add_new_com_dinamyc_left + this.settings.add_element_dx - scroll_left) + 'px');
            $('.component-bar').css('left', (this.settings.component_bar - scroll_left) + 'px');
            $('.site-popover-custom, .design-popover-custom, .page-popover-custom').css('left', (this.settings.menu_popovers - scroll_left) + 'px');
            $('.add-elements-popover-custom').css('left', (add_new_com_dinamyc_left + this.settings.add_element_popover_dx - scroll_left) + 'px');
            $('.content-icons').css('left', (this.settings.content_icons - scroll_left) + 'px');

            if (isResize) {
                eventsystem.publish('/component/stretch/');
            }

            if (UI.RulerGuides != undefined) {
                UI.RulerGuides.changeRulerGuidesOnScroll(isResize);
            }
        },

        settings: 
        {
            component_bar: 8,
            main_menu: 0,
            menu_popovers: 48,
            right_navigation_panel_popover: 11,
            add_element_dx: 1,
            add_element_popover_dx: 14,
            content_icons: 500
        }
    };
    navigation.scroll();
}

    var InitPositionFieldValidation = function () {
        $(document).on('change', '#widthInput, #heightInput, #topInput, #leftInput', function () {
            var value = parseInt(this.value);
            var isChanged = false;
            var maxValue = 5000;

            if (value == NaN) {
                value = 0;
                isChanged = true;
            }
            if (value > maxValue) {
                value = maxValue;
                isChanged = true;
            }
            if (value < -maxValue) {
                value = -maxValue;
                isChanged = true;
            }
            if (isChanged) {
                $(this).val(value + 'px');
                $(this).trigger('change');
            }
        });
    }

    var InitMetaFieldValidation = function () {
        $(document).on('keypress', '#seo-title-input, #seo-descr-input, #seo-custom-date-input, #seo-words-input, #fb-title-input, #fb-descr-input, #seo-custom-title-input, #seo-custom-descr-input, #seo-custom-words-input, #site-settings-header-content', function (e) {
            var code = e.which || e.keyCode;  
            if (code == 96 || code == 39 || code == 34 || code == 64) {
                e.preventDefault();
            }
        });

        $(document).on('change', '#seo-title-input, #seo-descr-input, #seo-custom-date-input, #seo-words-input, #fb-title-input, #fb-descr-input, #seo-custom-title-input, #seo-custom-descr-input, #seo-custom-words-input, #site-settings-header-content', function (e) {
            $(this).val($(this).val().replace(/'/g, ''));
            $(this).val($(this).val().replace(/"/g, ''));
            $(this).val($(this).val().replace(/`/g, ''));
            $(this).val($(this).val().replace(/@/g, ''));
        });
    }


    PostLoadInit.InitTextLinksToPages = function (pager) {
        $('.std-paragraph, .std-headertext, .html-container').find('a').toArray().forEach(function (item) {
            var url = $(item).attr('href');
            var blank = $(item).attr('target');

            var bindRedirectToPage = function (item, sitepageid) {
                $(item).click(function (event) {
                    event.preventDefault();
                    pager.goToPage(sitepageid);
                });
            };

            if (url != undefined) {
                //internal links
                if (url.indexOf(location.host) !== -1 && blank !== '_blank') {
                    var sitepage = '';
                    if (url.indexOf('#') != -1) {
                        sitepage = url.substring(url.indexOf('#'));
                        sitepage = sitepage.replace('#', '').replace('!', '');
                    } else {
                        try {
                            sitepage = Helpers.parseURL(url).pathname.replace("\\", "").replace("/", "").replace(/ /g, '').split('/')[0];
                        } catch (e) {
                            sitepage = url.split('/').pop().replace('!', '');
                        }
                    }
                    bindRedirectToPage(item, pager.getPageId(sitepage));
                }
                //links in ckeditor
                else if (url.indexOf(CKEDITOR_PAGE) != -1) {
                    var sitepage = url.substring(CKEDITOR_PAGE.length);
                    bindRedirectToPage(item, pager.getPageId(sitepage));
                }
            }

        });
    }

    var InitHashChangeEvent = function () {
        $(window).bind('hashchange', function (e) {
            var page = $.bbq.getState();
            try {
                var pageAddress = JSON.stringify(page).match(/"(.*?)"/)[1];
                if (pageAddress && pageAddress[0] == '!') {
                    pageAddress = pageAddress.substr(1);
                }
                if (!pageAddress.length) {
                    throw new Error("This is home page");
                }
                var i = 0;
                for (i = 0; i < UI.pager.pages.length; i += 1) {
                    var cur = UI.pager.pages[i];
                    if (cur.name.replace(/ /g, '').toLowerCase() == pageAddress.toLowerCase()) {
                        UI.pager.goToPage(cur.id);
                    }
                }
            } catch (err) {
                var homePageId = UI.pager.getHomePageId();
                UI.pager.goToPage(homePageId);
            }
        });

        //event when you back or forvard in browser
        if (UI.getSetting("ispublished")) {
            $(window).on("popstate", function (e) {
                var curpageparts = document.location.pathname.replace("\\", "").replace("/", "").replace(/ /g, '').split('/');
                var curpagename = curpageparts[0];
                var hashpagename = document.location.hash.replace("#", "").replace("!", "").replace(/ /g, '');

                if (curpagename) {
                    //check page from pathname
                    var objCurPage = UI.pager.getPageByName(curpagename);
                    if (objCurPage != null) {
                        //set additionalParam
                        var additionalParam = {};
                        if ((objCurPage.name === 'product' || objCurPage.name === 'thank-you') && curpageparts[1]) {
                            additionalParam = {
                                key: objCurPage.name === 'product' ? 'productId' : 'orderId',
                                value: curpageparts[1]
                            }
                        }
                        //set ignoreChangeUrl to true
                        UI.pager.goToPage(objCurPage.id, false, true, additionalParam);
                    }
                } else if (hashpagename) {
                    //check page from hash
                    var objHashPage = UI.pager.getPageByName(hashpagename);
                    if (objHashPage != null) {
                        UI.pager.goToPage(objHashPage.id);
                    }
                } else {
                    UI.pager.goToHomePage(true);
                }
            });
        }
    };
var HashHelper = function() {
    
}
//setting current hash on page transition etc.
HashHelper.set = function (hash, additionalParam) {
    additionalParam = defined(additionalParam) ? additionalParam : {};
    if (UI.getSetting("ispublished")) {
        try {
            history.pushState("", document.title, '/' + HashHelper.prepare(hash) + (_.isEmpty(additionalParam) ? '' : '/' + additionalParam.value));
        } catch (e) { }
    } else {
        if (!_.isEmpty(additionalParam)) {
            //add additionalParam
            sessionStorage.setItem(additionalParam.key, additionalParam.value);
            //history.replaceState("", document.title, window.location.pathname + (window.location.search ? Helpers.setQueryParamValue(additionalParam.key, additionalParam.value) : ''));
        }
        location.hash = HashHelper.prepare(hash);
    }
}
//getting current hash value
HashHelper.get = function () {
    var hash = location.hash.substring(1, location.hash.length);
    return hash[0] == '!' ? hash.substring(1) : hash;
}
//clearing current hash
HashHelper.clear = function() {  
    try {
        if (UI.getSetting("ispublished") && !UI.settings.isComponentEditor) {
            history.replaceState("", document.title, "/");
        } else {
            history.replaceState("", document.title, window.location.pathname + window.location.search);
        }
    } catch (e) {
        location.hash = "";
    }
}
//preparing hash in case if page address contains invalid characters
HashHelper.prepare = function(name) {
    return name.replace(/ /g, '');
};
var HandlebarHelper = function () { }
//helper for template compiling
HandlebarHelper.compileTemplate = function (source, context) {
    if (source !== undefined) {
        var template = Handlebars.compile(source, { noEscape: true });
        var compiledTemplate = template(context);
        return compiledTemplate;
    }
    else {
        return "";
    }

}

Handlebars.registerHelper('ifEqual',
    function (current, value, options) {
        return current == value ? options.fn(this) : options.inverse(this);
    });

Handlebars.registerHelper('ifFunc',
    function (current, value, options) {
        return current == value ? options.fn(this) : options.inverse(this);
    });

Handlebars.registerHelper('unlessFunc',
    function (current, value, options) {
        return typeof current === 'function' ? current() ? value.inverse(this) : value.fn(this) : value.fn(this);
    });;
var ColorPickerHelper = function() {}

ColorPickerHelper.bind = function (element, input, component, property, callback) {
    if ($(input).length) {
        if (!$(input).hasClass('sp-color-input')) {
            var currentInput = $(input).clone().val(tinycolor(component.getProperty(property).value).toRgbString());
            var container = $('<div class="sp-color-input-container"></div>').append(currentInput);
            $(input).replaceWith(container);
        }
        $(input).spectrum({
            preferredFormat: "rgb",
            showAlpha: true,
            showInput: true,
            showInitial: true,
            showPalette: true,
            hideAfterPaletteSelect: true,
            showSelectionPalette: false,
            beforeShow: function() {
                $(this).spectrum("option", 'palette', [UI.getSiteColors()]);
                return true;
            }
        });
        $(input).addClass('sp-color-input').show()
            .change(function(event) {
                var el = $(this);
                el.spectrum('set', event.target.value);
                var newvalue = _.clone(el.spectrum('get').toRgbString());
                var undoredo = function(val) {
                    callback = callback || function() {};
                    element = $(element.selector ? element.selector : element);
                    component.setProperty(property, val);
                    if (property === DIVIDER_COLOR) {
                        element.find('hr').css(BORDER_COLOR, val);
                        if (component.proto.name === STORE_THANK_YOU) {
                            element.find('th').css(BORDER_COLOR, val);
                        } else if (component.proto.name === STORE_CART) {
                            element.find('td').css(BORDER_COLOR, val);
                        }
                    } else {
                        element.css(property, val);
                    }
                    UI.addSiteColor(val);
                    callback(component, element, val);

                }
                UI.undoManagerAddSimple(component, property, newvalue, undoredo, true);
            });
    }
};

ColorPickerHelper.hide = function(){
    $('.sp-container').remove();
};
;

var SwitcherHelper = function () { }

SwitcherHelper.bind = function (element, input, component, property, callback) {
    callback = callback || function () { };
    var action = function () { }

    switch (property) {
        case IS_PINED:
            action = function (switcherValue) {
                if (switcherValue.toBoolean()) {
                    $('#pinnedPosition').show('fast');
                } else {

                    if (component.getProperty(IS_PINED).value.toBoolean()) {

                        var struct = [
                            {
                                component: component,
                                property: FIXED_LOCATION,
                                newvalue: '',
                                oldvalue: component.getProperty(FIXED_LOCATION).value
                            },
                            {
                                component: component,
                                property: IS_PINED,
                                newvalue: false.toString(),
                                oldvalue: component.getProperty(IS_PINED).value
                            }
                        ];

                        UI.undoManagerAddSimpleArr(struct, function () { }, function () { }, true);

                        UI.actionService.runActionForComponent(component, 'remove-component-from-form', true);
                        UI.actionService.runActionForComponent(component, 'add-component-to-form', true);
                        $(element).highlightSelectedElement(component, true);
                    }
                    $('#pinnedPosition').hide('fast');
                    $('#pinnedOffsetXY').hide('fast');
                    $('#pinnedPosition label.elemposition-selected').removeClass('elemposition-selected');
                }
            }
            break;
        case STRETCH_TO_FULL_WIDTH:
            action = function () {
                StretcherFactory.toggleStretchToFullWidth(component);
            }
            break;
        case SHOW_MAP_TYPE:
        case SHOW_ZOOM:
        case MAP_INTERACTIVE:
        case SHOW_STREET_VIEW:
        case HOVER_STYLE:        
        case IMAGE_RATIO:
        case CAPTION_POSITION:
            action = function (switcherValue) {                
                UI.undoManagerAddSimple(component, property, switcherValue, function() {}, true);                
            }
            break;
        case LAYOUT:
            if (component.proto.name !== STORE_PRODUCT) {
                action = function (switcherValue) {
                    UI.undoManagerAddSimple(component, property, switcherValue, function () { }, true);
                }
            }
            break;
        case SUCCESS_PAGE_MASTER_LINK:
            action = function (switcherValue) {
                var actionCallback = function() {
                    var components = UI.siteComponentRepository.lookupDataSetByPropertyName(property);
                    var newValue = switcherValue ? component.id : '';
                    var structs = _.map(components, function (item) {
                        return {
                            component: item,
                            property: property,
                            newvalue: _.clone(newValue),
                            oldvalue: _.clone(item.getProperty(property).value)
                        };
                    });

                    UI.undoManagerAddSimpleArr(structs, function () { }, function () { }, true);
                }
                actionCallback();
            }
            break;
    }

    $(input).children().click(function (e) {
        e.stopPropagation();
        e.preventDefault();
        var currentElement = $(this);
        if (currentElement.hasClass('active')) {
            if (currentElement.siblings().length <= 1) {
                currentElement.toggleClass('active');
                currentElement.siblings().toggleClass('active');
                action(currentElement.parent().find('.active').data().value);
                callback(component, property, currentElement.parent().find('.active').data().value);
            }
        } else {
            currentElement.siblings().removeClass('active');
            currentElement.addClass('active');
            action(currentElement.parent().find('.active').data().value);
            callback(component, property, currentElement.parent().find('.active').data().value);
        }
    });
};

SwitcherHelper.change = function (input) {
    $($(input).children().get(0)).click();
}

;
var PopoverHelper = function () { }
//helper to make popover usage short and 
PopoverHelper.bind = function (selectedElement, template, customClass, container, callbacks) {
    customClass = defined(customClass) ? customClass : '';
    container = defined(container) ? container : 'body';

    var element = selectedElement.popover(
        {
            content: template,
            container: container,
            html: true,
            trigger: 'manual',
            template:
                '<div class="popover"><div class="arrow"></div><div class="popover-inner"><h3 style="display:none;" class="popover-title"></h3><div class="popover-content"><p></p></div></div></div>',
            placement: function (context, src) {
                //if some custom class needed to apply to popover object
                if (customClass) {
                    $(context).addClass(customClass);
                }
                $(context).appendTo(container).find(".accordion").accordion({ heightStyle: "content" });
                return 'auto left';
            },
            animation: false
        });
    PopoverHelper.bindEvents(element, callbacks).popover('show');

    //$(UI.getConfigurationValue(EDITOR) + " .accordion .ui-accordion-content").show();

    var maxPopoverX = $('.site-wrapper')[0].offsetLeft + $('.site-wrapper')[0].offsetWidth;
    var unicPositionPopovers = PopoverHelper.getUnicPositionPopovers();
    var elements = document.getElementsByClassName('popover');
    ko.utils.arrayForEach(elements,
        function (element) {
            if (unicPositionPopovers.indexOf(element.id) == -1 && UI.getDevice().isDesktop()) {

                var popoverRightSide = 0;

                if (defined(element)) {
                    popoverRightSide = element.offsetLeft + element.offsetWidth;
                }

                if (maxPopoverX < popoverRightSide) {
                    var allowedX = maxPopoverX - element.offsetWidth;
                    $(element).css('left', allowedX + 'px');
                }
            }

            //margin for show/hide button   
            if ($(element).hasClass('left') && selectedElement.hasClass('std-component')) {
                var left = parseInt($(element).css('left')) - 15;
                $(element).css('left', left + 'px');
            }

            if (element.offsetTop < 70) {
                $(element).css('top', '70px');
            }

            $(element).bind('click',
                function (e) {
                    e.stopPropagation();
                });
        });
};

PopoverHelper.bindEvents = function (element, callbacks) {
    callbacks = {
        show: (callbacks && defined(callbacks.show)) ? callbacks.show : function () { Helpers.consoleLog('show.bs.popover'); },
        shown: (callbacks && defined(callbacks.shown)) ? callbacks.shown : function () { Helpers.consoleLog('shown.bs.popover'); },
        hide: (callbacks && defined(callbacks.hide)) ? callbacks.hide : function () { Helpers.consoleLog('hide.bs.popover'); },
        hidden: (callbacks && defined(callbacks.hidden)) ? callbacks.hidden : function () { Helpers.consoleLog('hidden.bs.popover'); }
    }
    element.data('callbacks', callbacks);
    return element
        .on('show.bs.popover', callbacks.show)
        .on('shown.bs.popover', callbacks.shown)
        .on('hide.bs.popover', callbacks.hide)
        .on('hidden.bs.popover', callbacks.hidden);
}

PopoverHelper.getUnicPositionPopovers = function () {
    var popovers = [];
    popovers.push($('#right-navigation-panel').attr('aria-describedby'));
    popovers.push($('#placing-on-canvas-top').attr('aria-describedby'));
    popovers.push($('#placing-on-canvas-bottom').attr('aria-describedby'));
    return popovers;
}

PopoverHelper.getNonHidedPopoversIds = function () {
    var popoverIds = [];
    popoverIds.push($('#right-navigation-panel').attr('aria-describedby'));
    popoverIds.push('right-navigation-panel');
    popoverIds.push('placing-on-canvas-top');
    popoverIds.push('placing-on-canvas-bottom');
    return popoverIds;
}

PopoverHelper.hidePopovers = function (classOnly) {
    //$('*').popover('destroy');
    var nonHidedPopoverIds = PopoverHelper.getNonHidedPopoversIds();
    $("*").each(function () {
        var id = $(this).attr('id');
        var popover = $.data(this, "bs.popover");
        var checkClass = (classOnly != undefined && $('#' + id).hasClass(classOnly)) || classOnly == undefined;
        if (popover && nonHidedPopoverIds.indexOf(id) == -1 && checkClass) {
            //$('#' + id).popover('hide');
            $('#' + id).popover('destroy');
        }
    });
};;
var RangeSliderHelper = function(){}
//plugin for range slider helper implementation
RangeSliderHelper.bind = function (input, component, element, property, options, callback) {
    callback = callback || function () { };

    var defaultOptions = {
        onChange: function (obj) {
            var oldvalue = _.clone(component.getProperty(property).value);
            var newvalue = _.clone(obj.fromNumber);
            if (parseInt(oldvalue) !== newvalue) {
                var cloneOptions = _.clone(options);

                var undoredo = function(val, postfix) {
                    if (defined(postfix)) {
                        switch (postfix) {
                        case "px":
                            val = val + "px";
                            break;
                        default:
                            break;
                        };
                    };
                    component.setProperty(property, val);
                    element = $(element.selector ? element.selector : element);
                    var outerWidth = element.outerWidth();

                    if (element.hasClass('std-list')) {
                        RangeSliderHelper.sizeTextList(element, property, val);
                    } //font-size for list component

                    if (property !== COLUMNS && property !== ROWS && property !== DIVIDER_WIDTH) {
                        element.css(property, val);
                        if (component.proto.name !== STORE_GALLERY_PRODUCT_LABEL) {
                            $(element).css('width', outerWidth);
                        }
                    } else {
                        if (property === DIVIDER_WIDTH) {
                            element.find('hr').css(BORDER_WIDTH, val);
                        }
                    }
                    callback(component, element, val);
                }

                undoredo(newvalue, cloneOptions.postfix);

                var propertyName = UI.undoManagerGetLatestProperty();
                var componentid = UI.undoManagerGetLatestComponentId();

                if (propertyName != null &&
                    property == propertyName &&
                    componentid != null &&
                    componentid == component.id) {
                    UI.undoManagerAddSpecific(
                        {
                            redo: function() {
                                undoredo(newvalue, "");
                            }
                        },
                        "redo");
                } else {

                    UI.undoManagerAdd(
                        {
                            undo: function() {
                                undoredo(oldvalue, "");
                            },
                            redo: function() {
                                undoredo(newvalue, cloneOptions.postfix);
                            },
                            property: property,
                            componentId: component.id
                        });
                }
            }
        },
        step:1
    }
    options.onChange = options.onChange || defaultOptions.onChange;
    options.step = options.step || defaultOptions.step;

    var sliderParams = {
        min: options.min,
        max: options.max,
        step: options.step,
        postfix: options.postfix
    };    
    if (options.useOnFinish) {
        sliderParams.onFinish = options.onChange;
    } else {
        sliderParams.onChange = options.onChange;
    }
    $(input).ionRangeSlider(sliderParams);
}

RangeSliderHelper.update = function (input, options) {    
    $(input).ionRangeSlider('update', options);
}

RangeSliderHelper.sizeTextList = function (element, property, val) {
    $(element).find(".title, .description, .meta, .optional").css(property, val);
};
var EditorEventsFactory = function () { }

EditorEventsFactory.attachPlainEvent = function (selector, element, component, type, property, callback) {
    //simplifying events binding for simple events (e.g. width or src property changed)

    $(selector).bind(type,
        function () {
            var newvalue = _.clone($(this).val());

            if (property == "autoplay" ||
                    property == "hide" ||
                    property == "rel" ||
                    property == "loop" ||
                    property == DIVIDER ||
                    property == MAIL_HISTORY_PROPERTY ||
                    property == SHARE_TO_FACEBOOK ||
                    property == SHARE_TO_TWITTER ||
                    property == SHARE_TO_GPLUS ||
                    property == REQUIRED_FIELD ||
                    property == SHOW_OPTIMIZED ||
                    property == SHOW_OPTIMIZED_HOVER ||
                    property == SHOW_OPTIMIZED_PRESSED ||
                    property == SHOW_OPTIMIZED_PLACEHOLDER
            ) {
                newvalue = $(this).is(':checked').toString();
            } else if (property === LAYOUT) {
                newvalue = $(this).data('type');
            }


            callback = callback || function () { };

            var callbackExt = function (value) {
                element = $(element.selector ? element.selector : element);
                var pageElem = $(element).parent('.page');
                switch (property) {
                    case "text":
                        if (component.proto.name === STORE_THANK_YOU) {
                            element.find('h3').text(value);
                        } else if (component.proto.name === STORE_CART) {
                            element.find('.empty-cart-text').text(value);
                        } else if (component.proto.name === ATTACHMENT) {
                            element.find('span').text(value);
                        } else {
                            element.text(value);
                        }
                        break;
                    case "type":
                    case "src":
                    case "provider":
                    case "predefined":
                    case "mode":
                    case "autoplay":
                    case "hide":
                    case "loop":
                    case "rel":
                    case "vertical-align":
                    case SHOW_OPTIMIZED:
                    case SHOW_OPTIMIZED_HOVER:
                    case SHOW_OPTIMIZED_PRESSED:
                    case SHOW_OPTIMIZED_PLACEHOLDER:
                        break;
                    case "alt":
                        element.find('img').attr('alt', value);
                        break;
                    case "top":
                        element.css(property, value);
                        Resizer.recalculateHeaderFooterAndPageSize(pageElem);
                        break;
                    case "height":
                        element.css(property, value);
                        Resizer.recalculateHeaderFooterAndPageSize(pageElem);
                        break;

                    case "list-type":
                    case PAYPAL_EMAIL:                    
                    case DESCRIPTION:
                        if (component.proto.name === STORE_THANK_YOU) {
                            element.find('h4').text(value);
                        }
                        break;
                    case TITLE:
                        if (component.proto.name === STORE_THANK_YOU) {
                            element.find('h2').text(value);
                        } else if (component.proto.name === STORE_CART) {
                            element.find('.cart-title').text(value);
                        }
                        break;
                    case TEXT_ORDER_NUMBER:
                        if (component.proto.name === STORE_THANK_YOU) {
                            element.find('h5.text-order').text(value);
                        }
                        break;
                    case TEXT_TOTAL_COST:
                        if (component.proto.name === STORE_THANK_YOU) {
                            element.find('h5.text-total').text(value);
                        }
                        break;
                    case TEXT_SHIPPING_TO:
                        if (component.proto.name === STORE_THANK_YOU) {
                            element.find('h5.text-shipping').text(value);
                        }
                        break;

                    default:
                        element.css(property, value);
                        break;
                }

                element.highlightSelectedElement(component, true);
                callback(component, element, value);
            }


            UI.undoManagerAddSimple(component, property, newvalue, callbackExt, true);
        });
}

EditorEventsFactory.attachPlainEventNotComponent = function (selector, component, type, property) {
    $(selector).bind(type,
        function () {
            component.setProperty(property, $(this).val());
        });
}
;
var Indexer = function(){}

Indexer.indexRepository = function(repository){
    var createIndex = function(item,index){
        return item.parentComponent!=null?item.parentComponent.index+"c["+index+"].":"r["+index+"].";
    }
    var nextNodeTraversing = function(node,index){
        node.index = createIndex(node,index);
        if (defined(node.children) && node.children.any()){
            node.children.forEach(function(item,nodeIndex){
                nextNodeTraversing(item,nodeIndex);
            });
        }
    }
    repository.forEach(function(item,index){
        nextNodeTraversing(item,index);
    })
}

Indexer.restoreIndex = function(repository,indexedValue){
    var childPattern = "c[";
    var childMatch = "children[";
    var repoPattern = "r[";
    var repoMatch = "repository[";
    indexedValue = indexedValue.replaceAll(childPattern,childMatch).replaceAll(repoPattern,repoMatch);
    if (indexedValue[indexedValue.length-1]=="."){
        indexedValue = indexedValue.slice(0,-1);
    }
    return eval(indexedValue);
};
var Positioning = function(){}
//processing element coords
Positioning.createElementCoords = function(element){
    var el = $(element);
    var elTop = el.offset().top;
    var elLeft = el.offset().left;
    var elWidth = el.outerWidth();
    var elHeight = el.outerHeight();
    var leftTop = new Coord(elLeft,elTop);
    var leftBottom = new Coord(elLeft,elTop+elHeight);
    var rightTop = new Coord(elLeft+elWidth,elTop);
    var rightBottom = new Coord(elLeft+elWidth,elTop+elHeight);
    var center = new Coord(leftTop.x+(elWidth/2),leftTop.y+(elHeight/2));
    var elementCoords = new ElementCoords(leftTop,leftBottom,rightTop,rightBottom,center);
    return elementCoords;
}
//if element is above target element
Positioning.isAboveElement = function(sourceCoords,targetCoords){
    var lt = sourceCoords.center.x>targetCoords.leftTop.x && sourceCoords.center.x<targetCoords.rightTop.x;
    var rb = sourceCoords.center.y>targetCoords.leftTop.y && sourceCoords.center.y<targetCoords.leftBottom.y;
    return lt && rb;
}
var Coord = function(x,y){
    this.x = x;
    this.y = y;
}
var ElementCoords = function(leftTop,leftBottom,rightTop,rightBottom,center){
    this.leftTop = leftTop;
    this.leftBottom = leftBottom;
    this.rightTop = rightTop;
    this.rightBottom = rightBottom;
    this.center = center;
};
var Dock = function () {
}
Dock.unconditionalElementDocking = function (element, dockTo, placeBellow, isGroup, groupingOffset) {
    //if dock wrapper exists

    var isDocking = false;
    isGroup = isGroup || false;
    groupingOffset = groupingOffset || null;

    if (element != null) {

            var el = $(element);
            var previousContainer = el.closest('[dockable]');
            //if closes dockable container is not the one, that our element us currently in
            if (previousContainer.getId() != dockTo.getId()) {
                var newLeft = el.offset().left - dockTo.offset().left;
                var newTop = el.offset().top - dockTo.offset().top;
                var currentLeft = el.position().left;
                var currentTop = el.position().top;
                //checking if our element was not moved in delta
                if (!((newLeft - Dock.delta <= currentLeft && newLeft + Dock.delta >= currentLeft) &&
                       (newTop - Dock.delta <= currentTop && newTop + Dock.delta >= currentTop))
                    ) {

                    isDocking = true;

                    var prevDockId = el.parent().getId();
                    var newDockId = dockTo.getId();
                    var control = UI.siteComponentRepository.lookupData({ id: el.getId() });
                    var oldleft = control.getProperty(LEFT).value;
                    var oldtop = control.getProperty(TOP).value;
                    var oldheight = MIN_ELEMENT_HEIGHT;

                    if (control.getProperty(HEIGHT) != null) {
                        oldheight = control.getProperty(HEIGHT).value;
                    }

                    var newleft = el.offset().left - dockTo.offset().left + 'px';
                    var newtop = 0 + 'px';

                    if (placeBellow) {
                        newtop = (dockTo[0].offsetHeight - parseInt(oldheight));

                        if (newtop < 0) {
                            newtop = 0;
                        }                     
                        newtop += 'px';
                    }

                    if (groupingOffset != null) {
                        if (groupingOffset.moveup) {
                            if (dockTo[0].offsetHeight <= groupingOffset.groupHeight) {
                                newtop = 0 + groupingOffset.offsetY;
                            }
                            else {
                                newtop = dockTo[0].offsetHeight - groupingOffset.groupHeight + groupingOffset.offsetY;
                            }
                        }
                        else {
                            var newtop = 0 + groupingOffset.offsetY;
                        }
                        newtop += 'px';
                    }

                    var undoredo = function (left, top, elementid, dockToId) {
                        var element;
                        if (typeof elementid == 'string') {
                            element = $(elementid);
                        } else {
                            element = $('#' + elementid[0].id);
                        }
                        
                        UI.siteComponentRepository.move({ id: element.getId() }, { id: dockToId });

                        var component = UI.siteComponentRepository.lookupData({ id: $(element).getId() });
                        component.setProperty(LEFT, left);
                        component.setProperty(TOP, top);
                        TransformFactory.setNewPosition(component);

                        UI.actionService.addToActionData(component, true);
                        UI.actionService.runActionForComponent(component, ACTION_ADD_TO_FORM, true);

                        //dragDrop
                        if (dragDrop.draggedObject != null) {
                            dragDrop.draggedObject = $(element.selector).get(0);
                        }

                        //if element is menu
                        if (component.proto.name === MENU) {
                            UI.renderMenus();
                        }

                        //check again after docking
                        Resizer.recalculateSizeFooterContainer($('.footer')[0]);

                        if (isGroup) {                            
                            var index = _.findIndex(grouping.selectedItems(), function (item) {
                                return item.id == component.id;
                            });
                            grouping.selectedItems()[index] = $(element.selector).get(0);
                            Grouping.wrap();
                        } else {
                            $(element.selector).highlightSelectedElement(component);
                        }
                    }

                    undoredo(newleft, newtop, element, newDockId);

                    UI.undoManagerAdd(
                        {
                            undo: function () {
                                undoredo(oldleft, oldtop, element, prevDockId);
                            },
                            redo: function () {
                                undoredo(newleft, newtop, element, newDockId);
                            }
                        });
                }
            }
        }
        UI.removeDockContainer();
    
    return isDocking;
}
Dock.processElementDocking = function (element) {
    //if dock wrapper exists
    var isDocking = false;
    if (Dock.canDock()) {
        if (element != null) {
            var dockTo = $('#' + $(UI.getConfigurationValue(DOCK_WRAPPER)).data('for'));
            var el = $(element);
            var previousContainer = el.closest('[dockable]');
            //if closes dockable container is not the one, that our element us currently in
            if (previousContainer.getId() != dockTo.getId()) {
                var newLeft = el.offset().left - dockTo.offset().left;
                var newTop = el.offset().top - dockTo.offset().top;
                var currentLeft = el.position().left;
                var currentTop = el.position().top;
                //checking if our element was not moved in delta
                if (!((newLeft - Dock.delta <= currentLeft && newLeft + Dock.delta >= currentLeft) &&
                       (newTop - Dock.delta <= currentTop && newTop + Dock.delta >= currentTop))
                    ) {

                    isDocking = true;

	                var prevDockId = el.parent().getId();
                    var newDockId = dockTo.getId();
                    var control = UI.siteComponentRepository.lookupData({ id: el.getId() });
                    var oldleft = control.getProperty(LEFT).value;
                    var oldtop = control.getProperty(TOP).value;

                    var newleft = el.offset().left - dockTo.offset().left + 'px';
                    var newtop = el.offset().top - dockTo.offset().top + 'px';

                    console.log("Docking:" + isDocking);

                	//re-calculate component z-index, according to parent component
                    if (control.parentComponent != null) {

                    	var currentZ = $(control.getUISelector()).zIndex();
                    	var parentZ = $(control.parentComponent.getUISelector()).zIndex();
	                    var compoundZ = parentZ > 0 ? parentZ : currentZ;
                    	control.setProperty("z-index", compoundZ);

                    	$(control.getUISelector()).zIndex(parseInt(compoundZ, 10));
                    	console.log("new zindex: " + parseInt(compoundZ, 10));

                    }


                    var undoredo = function (left, top, element, dockToId) {                                
                        UI.siteComponentRepository.move({ id: element.getId() }, { id: dockToId });
                        var component = UI.siteComponentRepository.lookupData({ id: $(element).getId() });

                        component.setProperty(LEFT, left);
                        component.setProperty(TOP, top);

                        UI.actionService.addToActionData(component, true);
                        UI.actionService.runActionForComponent(component, ACTION_ADD_TO_FORM, true);

                        //dragDrop
                        if (dragDrop.draggedObject != null) {
                            dragDrop.draggedObject = $(element.selector).get(0);
                        }

                        //if element is menu
                        if (component.proto.name === MENU) {
                            UI.renderMenus();
                        }

                        Resizer.recalculateSizeFooterContainer($('.footer')[0]);
                    }
                                        
                    undoredo(newleft, newtop, el, newDockId);

                    UI.undoManagerAdd(
                        {
                            undo: function () {
                                undoredo(oldleft, oldtop, el, prevDockId);
                            },
                            redo: function () {
                                undoredo(newleft, newtop, el, newDockId);
                            }
                        });
                }
            }
        }
        UI.removeDockContainer();
    }
    return isDocking;
}
Dock.canDock = function () {
    return $(UI.getConfigurationValue(DOCK_WRAPPER)).length > 0;
}
Dock.delta = 2;;
var Resizer = function () { };

Resizer.cumulativeTopOffset = function (element) {
    var top = 0;
    do {
        if ($(element).hasClass('body')) {
            break;
        }
        top += element.offsetTop || 0;
        element = element.offsetParent;
    } while (element);
    return top;
};

Resizer.resizeFooterContainer = function (element, container) {

    var bottomLine = element.clientHeight + element.offsetTop;
    var mainBottomLine = container.clientHeight;
    var footer = $('.footer');

    if (bottomLine > mainBottomLine) {
        $(container).css('height', bottomLine);
    }
    return false;
}

Resizer.recalculateSizeFooterContainer = function (footerElement) {
    var index;
    
    if (footerElement != undefined) {
        var componentFooter = UI.siteComponentRepository.lookupData({ id: $('.footer').getId() });
        var height = parseInt(componentFooter.getProperty(HEIGHT).value);
        var footer = $('.footer');
        var heightHeader = $('.header').outerHeight();
        var heightMain = $('.main').outerHeight();
        var maxBottomLine = Resizer.calculateContainerBottomLine($('.footer')[0], height);

        for (index = 0; index < footerElement.children.length; index++) {
            var child = footerElement.children[index];
            var childBottomLine = child.offsetTop + child.offsetHeight;
            if (childBottomLine > maxBottomLine) {
                maxBottomLine = childBottomLine;
            }
        }
        
        footer.css('height', maxBottomLine + 'px');
        $("#bottom-body-resizer").css('top', maxBottomLine + heightHeader + heightMain);
        componentFooter.setProperty(HEIGHT, $('.footer').css('height'));

        Resizer.drawRulersTicks(footer[0].offsetTop + footer[0].offsetHeight);


    }
    return false;
}

Resizer.checkMinSizeHeaderContainer = function (headerElement) {
    var index;
    var minBottomLine = parseInt($(headerElement).css('height'));
    var main = $('.main');
    var footer = $('.footer');
    if (headerElement && main[0] && footer[0]) {
        for (index = 0; index < headerElement.children.length; index++) {
            var child = headerElement.children[index];
            var childBottomLine = child.offsetTop + child.offsetHeight;
            if (childBottomLine > minBottomLine) {
                minBottomLine = childBottomLine;
            }
        }

        $(headerElement).css('height', minBottomLine);
        var componentHeader = UI.siteComponentRepository.lookupData({ id: $(headerElement).getId() });
        if (componentHeader != null)
        {
            componentHeader.setProperty(HEIGHT, $(headerElement).css('height'));
        }        
        
        var mainTop = headerElement.offsetTop + minBottomLine;
        main.css('top', mainTop);
        var componentMain = UI.siteComponentRepository.lookupData({ id: main.getId() });
        if (componentMain != null)
        {
            componentMain.setProperty(TOP, main.css('top'));
        }
        
        var footerTop = main[0].offsetTop + main[0].offsetHeight;
        footer.css('top', footerTop);
        var componentFooter = UI.siteComponentRepository.lookupData({ id: footer.getId() });
        if (componentFooter != null) {
            componentFooter.setProperty(TOP, footer.css('top'));
        }

        Resizer.drawRulersTicks(footer[0].offsetTop + footer[0].offsetHeight);
        var mainElemenet = main[0];
        $('#page-resizer').css(TOP, mainElemenet.offsetTop + mainElemenet.offsetHeight + 'px');
        $('#header-resizer').css(TOP, minBottomLine + 'px');
    }
    return false;
}

Resizer.checkMinSizePageContainer = function (pageElement) {
    var index;
    var minBottomLine = parseInt($(pageElement).css('height'));
    if (pageElement != undefined) {
        for (index = 0; index < pageElement.children.length; index++) {
            var child = pageElement.children[index];
            var childBottomLine = child.offsetTop + child.offsetHeight;
            if (childBottomLine > minBottomLine) {
                minBottomLine = childBottomLine;
            }
        }

        $(pageElement).css('height', minBottomLine);
        var componentPage = UI.siteComponentRepository.lookupData({ id: $(pageElement).getId() });
        componentPage.setProperty(HEIGHT, $(pageElement).css('height'));
        if (!UI.getSetting('ispreview')) {
            UI.undoManagerAddSimple(componentPage,
                HEIGHT,
                $(pageElement).css('height'),
                function (val) {
                    $(pageElement).css('height', val);
                    Resizer.recalculateHeaderFooterAndPageSize(pageElement);
                }, true);
        }
        var main = $('.main');
        var footer = $('.footer');
        if (footer[0] != undefined) {
            var footerTop = main[0].offsetTop + main[0].offsetHeight;
            footer.css('top', footerTop);
            var componentFooter = UI.siteComponentRepository.lookupData({ id: $('.footer').getId() });
            componentFooter.setProperty(TOP, $('.footer').css('top'));

            Resizer.drawRulersTicks(footer[0].offsetTop + footer[0].offsetHeight);
        }
        
        var mainElemenet = main[0];
        $('#page-resizer').css(TOP, mainElemenet.offsetTop + mainElemenet.offsetHeight + 'px');
    }
    return false;
}

Resizer.recalculateHeaderFooterAndPageSize = function (pageElem) {
    Resizer.checkMinSizeHeaderContainer($('.header')[0]);
    if (pageElem && pageElem[0] != undefined) {
        Resizer.checkMinSizePageContainer(pageElem[0]);
    }
    Resizer.recalculateSizeFooterContainer($('.footer')[0]);
    if (UI.RulerGuides != undefined) {
        UI.RulerGuides.calculateRulerGuideHeight();
    }
}

Resizer.drawRulersTicks = function (height) {
    if ($('.hRule').css('display') == 'none') {
        return false;
    }

    var $hRule = $('.hRule');
    var $vRule = $('.vRule');

    $hRule.empty();
    $vRule.empty().height(0).outerHeight(height);
    var leftEdgeToShowTickLabel = 0;
    var rightEdgeToShowTickLabel = (UI.RulerGuides != undefined ? UI.RulerGuides.getSiteWidth() : 0);

    var vRuleSize = (!UI.getDevice().isDesktop() ? 0 : (UI.RulerGuides != undefined ? UI.RulerGuides.getRulerGuidePositionBeginningByType('vertical') : 0));
    vRuleSize = vRuleSize.toFixed();
    var tickLabelPos = 0;
   
    // Horizontal ruler ticks
    var newTickLabel = "";
    while (tickLabelPos <= $hRule.width()) {
        var tickValue = tickLabelPos - vRuleSize;
        if ((tickValue % 50) == 0) {
            newTickLabel = "<div class='tickLabel'>" + ((tickValue % 100) == 0 ? "<span>" + (tickValue >= leftEdgeToShowTickLabel && tickValue <= rightEdgeToShowTickLabel ? tickValue : '') + "</span>" : "") + "</div>";
            var newTickLabelCSS = { "left": tickLabelPos + "px" };
            if ((tickValue % 100) != 0) {
                newTickLabelCSS.height = '50%';
            }
            $(newTickLabel).css(newTickLabelCSS).appendTo($hRule);
            
        } else if ((tickValue % 10) == 0) {
            newTickLabel = "<div class='tickMajor'></div>";
            $(newTickLabel).css("left", tickLabelPos + "px").appendTo($hRule);
        }
        else {
            tickLabelPos = (tickLabelPos + 1);
            continue;
        }
        tickLabelPos = (tickLabelPos + 10);
    }//hz ticks

    // Vertical ruler ticks
    tickLabelPos = 0;
    newTickLabel = "";
    while (tickLabelPos <= $vRule.height()) {
        if (((tickLabelPos - 0) % 50) == 0) {
            if (tickLabelPos != 0) {
                newTickLabel = "<div class='tickLabel'>" + (((tickLabelPos - 0) % 100) == 0 ? "<span>" + (tickLabelPos - 0) + "</span>" : "") + "</div>";
                var newTickLabelCSS = { "top": tickLabelPos + "px" };
                if (((tickLabelPos - 0) % 100) != 0) {
                    newTickLabelCSS.width = '50%';
                }
                $(newTickLabel).css(newTickLabelCSS).appendTo($vRule);
            }

        } else if (((tickLabelPos - 0) % 10) == 0) {
            newTickLabel = "<div class='tickMajor'></div>";
            $(newTickLabel).css("top", tickLabelPos + "px").appendTo($vRule);
        }
        tickLabelPos = (tickLabelPos + 10);
    }//vert ticks
}

Resizer.drawRulers = function (element, resizeSide, tstartX, tstartY) {
    return dragDrop.drawRulers(element[0], 5.0, 0, 0, false, true, resizeSide, tstartX, tstartY);
}

Resizer.resizeMenu = function (resizer) {
    var resizerId = resizer[0].id;

    var offsetTop = resizer[0].offsetTop;
    var offsetHeight = resizer[0].offsetHeight;
    var mainHeight = parseInt($('.main').css('height'));
    var headerHeight = parseInt($('.header').css('height'));

    var minHeaderHeight = Resizer.calculateContainerBottomLine($('.header')[0], parseInt($('.header').css('min-height')));
    var minPageHeight = Resizer.calculateContainerBottomLine($('#' + UI.pager.getCurrentPageId())[0], parseInt($('.main').css('min-height')));
    var minFooterHeight = Resizer.calculateContainerBottomLine($('.footer')[0], parseInt($('.footer').css('min-height')));

    switch (resizerId) {
        case "header-resizer":
            if (offsetTop - offsetHeight / 2 < minHeaderHeight) {
                offsetTop = minHeaderHeight - offsetHeight / 2;
            }
            $('.header').css('height', offsetTop + offsetHeight / 2);
            $('.main').css('top', offsetTop + offsetHeight / 2);
            $('.footer').css('top', offsetTop + offsetHeight / 2 + mainHeight);
            $('.site-wrapper').css('height', offsetTop + offsetHeight);  //visual element
            break;
        case "page-resizer":
            if (offsetTop + offsetHeight / 2 - headerHeight < minPageHeight) {
                offsetTop = minPageHeight - offsetHeight / 2 + headerHeight;
            }
            $('#' + UI.pager.getCurrentPageId()).css('height', offsetTop + offsetHeight / 2 - headerHeight);
            $('.footer').css('top', offsetTop + offsetHeight / 2);
            break;
        case "bottom-body-resizer":
            if (offsetTop + offsetHeight / 2 - mainHeight - headerHeight < minFooterHeight) {
                offsetTop = mainHeight + headerHeight + minFooterHeight - offsetHeight / 2;
            }
            $('.footer').css('height', (offsetTop - headerHeight - mainHeight) + offsetHeight / 2);
        default:
            break;
    }
}

Resizer.calculateContainerBottomLine = function (element, minHeight) {
    var maxBottomLine = minHeight;
    for (index = 0; index < element.children.length; index++) {
        var child = element.children[index];
        var childBottomLine = child.offsetTop + child.offsetHeight;
        if (childBottomLine > maxBottomLine) {
            maxBottomLine = childBottomLine;
        }
    }
    return maxBottomLine;
}

Resizer.resizedMenuSave = function (resizer) {
    var componentHeader = UI.siteComponentRepository.lookupData({ id: $('.header')[0].id });
    var componentMain = UI.siteComponentRepository.lookupData({ id: $('.main')[0].id });
    var componentFooter = UI.siteComponentRepository.lookupData({ id: $('.footer')[0].id });
    var componentCurrentPage = UI.siteComponentRepository.lookupData({ id: UI.pager.getCurrentPageId() });

    var oldvalueHeaderHeight = parseInt(_.clone(componentHeader.getProperty(HEIGHT).value));
    var oldvalueMainTop = parseInt(_.clone(componentMain.getProperty(TOP).value));
    var oldvalueMainHeight = parseInt(_.clone(componentMain.getProperty(HEIGHT).value));
    var oldvalueFooterTop = parseInt(_.clone(componentFooter.getProperty(TOP).value));
    var oldvalueFooterHeight = parseInt(_.clone(componentFooter.getProperty(HEIGHT).value));
    var oldvalueCurrentPageHeight = parseInt(_.clone(componentCurrentPage.getProperty(HEIGHT).value));
    var mainHeight = parseInt($('.main').css('height'));
    var footerHeight = parseInt($('.footer').css('height'));

    var resizerId = _.clone(resizer[0].id);
    var offsetTop = resizer[0].offsetTop;
    var offsetHeight = _.clone(resizer[0].offsetHeight);

    var minHeaderHeight = Resizer.calculateContainerBottomLine($('.header')[0], parseInt($('.header').css('min-height')));
    var minPageHeight = Resizer.calculateContainerBottomLine($('#' + UI.pager.getCurrentPageId())[0], parseInt($('.main').css('min-height')));
    var minFooterHeight = Resizer.calculateContainerBottomLine($('.footer')[0], parseInt($('.footer').css('min-height')));

    var undoredo = function (component, property, value) {
        component.setProperty(property, value);
        var element = $('#' + component.id);
        element.css(property, value);
    }

    switch (resizerId) {
        case "header-resizer":
            if (offsetTop - offsetHeight / 2 < minHeaderHeight) {
                offsetTop = minHeaderHeight - offsetHeight / 2;
                $(resizer).css(TOP, offsetTop + offsetHeight / 2);
            }
            undoredo(componentHeader, HEIGHT, offsetTop + offsetHeight / 2 + 'px');
            undoredo(componentMain, TOP, offsetTop + offsetHeight / 2 + 'px');
            undoredo(componentFooter, TOP, offsetTop + offsetHeight / 2 + mainHeight + 'px');
            $('#page-resizer').css(TOP, offsetTop + offsetHeight / 2 + mainHeight + 'px');
            $('#bottom-body-resizer').css(TOP, offsetTop + offsetHeight / 2 + mainHeight + footerHeight + 'px');
            UI.undoManagerAdd(
            {
                undo: function () {
                    undoredo(componentHeader, HEIGHT, oldvalueHeaderHeight);
                    undoredo(componentMain, TOP, oldvalueMainTop);
                    undoredo(componentFooter, TOP, oldvalueFooterTop);
                    $('#header-resizer').css(TOP, oldvalueHeaderHeight + 'px');
                    $('#page-resizer').css(TOP, oldvalueHeaderHeight + mainHeight + 'px');
                    $('#bottom-body-resizer').css(TOP, oldvalueHeaderHeight + mainHeight + footerHeight + 'px');
                },
                redo: function () {
                    undoredo(componentHeader, HEIGHT, offsetTop + offsetHeight / 2 + 'px');
                    undoredo(componentMain, TOP, offsetTop + offsetHeight / 2 + 'px');
                    undoredo(componentFooter, TOP, offsetTop + offsetHeight / 2 + mainHeight + 'px');
                    $('#header-resizer').css(TOP, offsetTop + offsetHeight / 2);
                    $('#page-resizer').css(TOP, offsetTop + offsetHeight / 2 + mainHeight + 'px');
                    $('#bottom-body-resizer').css(TOP, offsetTop + offsetHeight / 2 + mainHeight + footerHeight + 'px');
                }
            });
            break;
        case "page-resizer":
            if (offsetTop + offsetHeight / 2 - oldvalueHeaderHeight < minPageHeight) {
                offsetTop = minPageHeight - offsetHeight / 2 + oldvalueHeaderHeight;
                $(resizer).css(TOP, offsetTop + offsetHeight / 2);
            }
            undoredo(componentCurrentPage, HEIGHT, offsetTop + offsetHeight / 2 - oldvalueHeaderHeight + 'px');
            undoredo(componentFooter, TOP, offsetTop + offsetHeight / 2 + 'px');
            $('#bottom-body-resizer').css(TOP, offsetTop + offsetHeight / 2 + footerHeight + 'px');
            UI.undoManagerAdd(
            {
                undo: function () {
                    undoredo(componentCurrentPage, HEIGHT, oldvalueFooterTop - oldvalueHeaderHeight);
                    undoredo(componentFooter, TOP, oldvalueFooterTop);
                    $('#page-resizer').css(TOP, oldvalueFooterTop + 'px');
                    $('#bottom-body-resizer').css(TOP, oldvalueFooterTop + footerHeight + 'px');
                },
                redo: function () {
                    undoredo(componentCurrentPage, HEIGHT, offsetTop + offsetHeight / 2 - oldvalueHeaderHeight + 'px');
                    undoredo(componentFooter, TOP, offsetTop + offsetHeight / 2 + 'px');
                    $('#page-resizer').css(TOP, offsetTop + offsetHeight / 2);
                    $('#bottom-body-resizer').css(TOP, offsetTop + offsetHeight / 2 + footerHeight + 'px');
                }
            });
            break;
        case "bottom-body-resizer":
            if (offsetTop - offsetHeight / 2 - oldvalueHeaderHeight - oldvalueCurrentPageHeight < minFooterHeight) {
                offsetTop = oldvalueHeaderHeight + oldvalueCurrentPageHeight + minFooterHeight;
                $(resizer).css(TOP, offsetTop + offsetHeight / 2);
            }
            undoredo(componentFooter, HEIGHT, offsetTop + offsetHeight / 2 - oldvalueHeaderHeight - oldvalueCurrentPageHeight + 'px');
            UI.undoManagerAdd(
            {
                undo: function () {
                    undoredo(componentFooter, HEIGHT, oldvalueFooterTop + oldvalueFooterHeight - oldvalueHeaderHeight - oldvalueCurrentPageHeight);
                    undoredo(componentFooter, TOP, oldvalueFooterTop);
                    $('#bottom-body-resizer').css(TOP, oldvalueFooterTop + 'px');
                },
                redo: function () {
                    undoredo(componentFooter, HEIGHT, offsetTop + offsetHeight / 2 - oldvalueHeaderHeight - oldvalueCurrentPageHeight + 'px');
                    undoredo(componentFooter, TOP, offsetTop - offsetHeight / 2 - footerHeight + 'px');
                    $('#bottom-body-resizer').css(TOP, offsetTop + offsetHeight / 2 + oldvalueFooterHeight + 'px');
                }
            });
        default:
            break;

    }

    var footer = $('.footer');
    Resizer.drawRulersTicks(footer[0].offsetTop + footer[0].offsetHeight);
    if (UI.RulerGuides != undefined) {
        UI.RulerGuides.calculateRulerGuideHeight();
    }
}

Resizer.checkMinMaxWidth = function (element, newWidth, returnWidth) {
    element = $(element);
    returnWidth = defined(returnWidth) ? returnWidth : false;
    if (element.hasClass('std-list')) {
        var minW = parseInt(element.find('.item').css('min-width'));
        if (minW > newWidth) {
            return returnWidth ? minW : false;
        }
    } else if (element.hasClass('std-store-gallery')) {
        var product = element.find('.std-store-gallery-product');
        var col = parseInt(element.data().col);
        var minW = 0;
        if (product) {
            minW = parseInt(product.css('min-width')) * col;
            minW += Math.ceil((minW * (col * 2)) / (100 - col * 2)) + 16;
        }        
        if (minW > newWidth) {
            return returnWidth ? minW : false;
        }
    } else {
        var minW = parseInt(element.css('min-width'));
        if (minW > newWidth) {
            return returnWidth ? minW : false;
        }

        if (element.hasClass('std-house-photo-tour')) {
            var maxW = parseInt(element.css('max-width'));
            if (maxW < newWidth) {
                return returnWidth ? maxW : false;
            }
        }
    }

    return returnWidth ? newWidth : true;
}

Resizer.findBasicContainer = function (elem) {
    while (elem) {
        elem = elem.offsetParent;
        if (!$(elem).hasClass('std-component')) {
            return elem;
            break;
        }
    }
    return null;
};

Resizer.additionalOffsetFromParent = function (element) {
    var top = 0;
    do {
        element = element.offsetParent;
        if (!$(element).hasClass('std-component')) {
            break;
        }
        top += element.offsetTop || 0;
    } while (element);
    return top;
};

var _checkItem;

function setWidthMenuComponent(elementNewWidth, checkItem, listText) {
    // bool checkItem - defines which menu : "verticar" or "simple" or "other element"
    // array listText - for example : <a>Home</a>
    //var newWidthWithPadding = elementNewWidth - 80;
    var array = new Array();

    for (var i = 0; i < listText.length; i++) {
        if ($(listText)[i].nodeName == "A") {
            array.push(listText[i]);
        }
    }

    for (var j = 0; j < array.length; j++) {
        if (checkItem) $(array[j]).css({ "overflow": "hidden", "text-overflow": "ellipsis", "text-aign": "center" });
        else $(array[j]).css({ 'width': '', "overflow": "", "text-overflow": "", "text-aign": "" });
    }
}

Resizer.checkPositionComponent = function (component, position) {

    var fixedLocation = component.getProperty(FIXED_LOCATION);

    if (position == 'centerTop') {
        return fixedLocation == null || (fixedLocation.value !== 'center-top' &&
               fixedLocation.value !== 'left-top' &&
               fixedLocation.value !== 'right-top');
    }

    else if (position == 'topLeft') {
        return fixedLocation == null ||
            (fixedLocation.value !== 'center-top' &&
            fixedLocation.value !== 'left-top' &&
            fixedLocation.value !== 'left-center' &&
            fixedLocation.value !== 'left-bottom' &&
            fixedLocation.value !== 'right-top');
    }
    
    else if (position == 'topRight') {
        return fixedLocation == null ||
               (fixedLocation.value !== 'center-top' &&
               fixedLocation.value !== 'right-top' &&
               fixedLocation.value !== 'center-top' &&
               fixedLocation.value !== 'right-center' &&
               fixedLocation.value !== 'right-bottom' &&
               fixedLocation.value !== 'left-top');
    }
    
    else if (position == 'centerBottom') {
        return fixedLocation == null ||
               (fixedLocation.value !== 'center-bottom' &&
               fixedLocation.value !== 'left-bottom' &&
               fixedLocation.value !== 'right-bottom');
    }
    
    else if (position == 'bottomLeft') {
        return fixedLocation == null ||
            (fixedLocation.value !== 'center-bottom' &&
            fixedLocation.value !== 'left-top' &&
            fixedLocation.value !== 'left-center' &&
            fixedLocation.value !== 'left-bottom' &&
            fixedLocation.value !== 'right-bottom');
    }

    else if (position == 'bottomRight') {
        return fixedLocation == null ||
            (fixedLocation.value !== 'center-bottom' &&
            fixedLocation.value !== 'right-top' &&
            fixedLocation.value !== 'right-center' &&
            fixedLocation.value !== 'right-bottom' &&
            fixedLocation.value !== 'left-bottom');
    }
    
    else if (position == 'centerLeft') {
        return fixedLocation == null ||
               (fixedLocation.value !== 'left-top' &&
               fixedLocation.value !== 'left-center' &&
               fixedLocation.value !== 'left-bottom');
    }

    else if (position == 'centerRight') {
        return fixedLocation == null ||
               (fixedLocation.value !== 'right-top' &&
               fixedLocation.value !== 'right-center' &&
               fixedLocation.value !== 'right-bottom');
    }

    return false;
};


Resizer.bind = function (element, selectWrapper, component, hasEditor) {
    console.log("Resizer.bind");
    var isList = element.hasClass('std-list');
    var isAnchor = element.hasClass('std-anchor');
    var isPhotoTour = element.hasClass('std-house-photo-tour');
    var isCheckbox = element.hasClass('std-form-checkbox');
    var isCaptcha = element.hasClass('std-form-captcha');
    var isSound = element.hasClass('std-sound');
    var isStoreProduct = element.hasClass('std-store-product');
    var isStoreGallery = element.hasClass('std-store-gallery');
    var isStoreCart = element.hasClass('std-store-cart');
    var isStoreCartLink = element.hasClass('std-store-cart-link');
    var isStoreThankYou = element.hasClass('std-store-thank-you');
    var isSignIn = element.hasClass('std-signin');
    var isParagraph = element.hasClass('std-paragraph');
    var isHeaderText = element.hasClass('std-headertext');
    var isHtmlContainer = element.hasClass('html-container');
    var isForm = element.hasClass('std-form');
    var isContactUs = element.hasClass('std-contact-us');
    var isEvaluateHome = element.hasClass('std-evaluate-home');
    var isImage = element.find('.std-img').length > 0;
    //appending resizer handles
    if (!isList && !isAnchor && !isCheckbox && !isSound && !isStoreProduct && !isStoreCart && !isStoreThankYou && !isStoreCartLink && !isCaptcha) {

        if (Resizer.checkPositionComponent(component, 'centerTop') && !isStoreGallery) {
            selectWrapper.append('<div class="resizer top"></div>');
        }

        if (Resizer.checkPositionComponent(component, 'topLeft')) {
            selectWrapper.append('<div class="resizer top-left"></div>');
        }

        if (Resizer.checkPositionComponent(component, 'topRight')) {
            selectWrapper.append('<div class="resizer top-right"></div>');
        }
        
        if (Resizer.checkPositionComponent(component, 'centerBottom') && !isStoreGallery) {
            selectWrapper.append('<div class="resizer bottom"></div>');
        }

        if (Resizer.checkPositionComponent(component, 'bottomLeft')) {
            selectWrapper.append('<div class="resizer bottom-left"></div>');
        }

        if (Resizer.checkPositionComponent(component, 'bottomRight')) {
            selectWrapper.append('<div class="resizer bottom-right"></div>');
        }       
    }
    if (!isAnchor && !isCheckbox && !isSound && !isStoreProduct && !isStoreGallery && !isStoreCart && !isStoreThankYou && !isStoreCartLink && !isCaptcha) {
        if (Resizer.checkPositionComponent(component, 'centerLeft')) {
            selectWrapper.append('<div class="resizer left"></div>');
        }

        if (Resizer.checkPositionComponent(component, 'centerRight')) {
            selectWrapper.append('<div class="resizer right"></div>');
        }  
    }

    function cumulativeTopOffset(element) {
        var top = 0;
        do {
            if ($(element).hasClass('body')) {
                break;
            }
            top += element.offsetTop || 0;
            element = element.offsetParent;
        } while (element);
        return top;
    };

    var basicParentContainer = Resizer.findBasicContainer(element[0]);
    var basicParentContainerBottomLine = 0;
    var basicParentContainerTopLine = 0;

    if (basicParentContainer != null) {
        basicParentContainerTopLine = cumulativeTopOffset(basicParentContainer);
        basicParentContainerBottomLine = basicParentContainerTopLine + basicParentContainer.offsetHeight;
    }

    var elementTopLine = cumulativeTopOffset(element[0]);
    var elementBottomLine = element[0].clientHeight + elementTopLine;
    if (!element.hasClass('std-form-subcomponent')) {
        if (UI.getDevice().isDesktop() && !isStoreProduct && !isStoreCart && !isStoreThankYou && !UI.settings.isComponentEditor) {
            if (basicParentContainerBottomLine + 25 > elementBottomLine && basicParentContainerBottomLine - 25 < elementBottomLine && !$(basicParentContainer).hasClass('footer')) {
                if ($(basicParentContainer).hasClass('page')) {                    
                    selectWrapper.find('.' + SELECT_WRAPPER_MENU).append('<li class="move-to-footer"><i class="fa fa-lg fa-chevron-down" title="Move to Footer"></i></li>');
                } else
                    if ($(basicParentContainer).hasClass('header')) {                        
                        selectWrapper.find('.' + SELECT_WRAPPER_MENU).append('<li class="move-to-page"><i class="fa fa-lg fa-chevron-down" title="Move to Page"></i></li>');
                    }
            }

            if (basicParentContainerTopLine + 25 > elementTopLine && basicParentContainerTopLine - 25 < elementTopLine && !$(basicParentContainer).hasClass('header')) {
                if ($(basicParentContainer).hasClass('page')) {                   
                    selectWrapper.find('.' + SELECT_WRAPPER_MENU).append('<li class="move-to-header"><i class="fa fa-lg fa-chevron-up" title="Move to Header"></i></li>');
                } else
                    if ($(basicParentContainer).hasClass('footer')) {                        
                        selectWrapper.find('.' + SELECT_WRAPPER_MENU).append('<li class="move-to-page-up"><i class="fa fa-lg fa-chevron-up" title="Move to Page"></i></li>');
                    }
            }
        }
        if (!isStoreProduct && !isStoreCart && !isStoreThankYou && !isSignIn && !UI.settings.isComponentEditor) {
            var isHide = component.getProperty(HIDE_COMPONENT);
            if (isHide != null && isHide.value.toBoolean()) {
                selectWrapper
                    .append(
                        '<div class="resizer show-component"><i class="fa fa-lg fa-eye" title="Show component"></i></div>');
            } else {
                selectWrapper.find('.' + SELECT_WRAPPER_MENU)
                    .append(
                        '<li class="hide-component"><i class="fa fa-lg fa-eye-slash" title="Hide component"></i></li>');
            }
        }
    }
    
    //if (pageBottomLine + 25 > elementBottomLine && pageBottomLine - 25 < elementBottomLine && element[0].parentElement.className != "footer") {
    //    selectWrapper.append('<div class="resizer move-to-footer"><i class="fa fa-sort-desc bottom-arrow" title="Move to Footer"></i></div>');
    //}
    
    if (hasEditor) {
        selectWrapper.find('.' + SELECT_WRAPPER_MENU)
            .append('<li class="edit-component" title="Edit Component"><i class="fa fa-lg fa-cog"></i></li>');
    }
    if (UI.getDevice().isDesktop() && !isStoreProduct && !isStoreCart && !isStoreThankYou && !UI.settings.isComponentEditor) {
        selectWrapper.find('.' + SELECT_WRAPPER_MENU)
            .append('<li class="delete-component" title="Delete Component"><i class="fa fa-lg fa-ban"></i></li>');
    }
    if (isParagraph || isHeaderText) {
        var mobileDevice = UI.devices.where({ type: DEVICE_MOBILE_TYPE }).firstOrDefault();
        if (mobileDevice && UI.getDevice().isDesktop()) {
            var disabledInherit = false;
            disabledInherit = component.getProperty(TEXT).value ===
                component.getProperty(TEXT, mobileDevice.getId()).value;
            selectWrapper.find('.' + SELECT_WRAPPER_MENU)
                .append('<li class="inherit-property-text' +
                    (disabledInherit ? ' disabled' : '') +
                    '" title="Copy Text to Mobile"><i class="fa fa-2x fa-mobile "></i></li>');
        }
        selectWrapper.find('.' + SELECT_WRAPPER_MENU).append('<li class="edit-text"><span>Edit Text</span></li>');
    }
    if (isHtmlContainer) {
        var text = component.getProperty(TEXT).value;
        if (new RegExp(REGEXP_CHECK_HTML_SCRIPT_AND_IFRAME_TAG).test(text)) {
            if (component.isPreviewEnabled) {
                selectWrapper.find('.' + SELECT_WRAPPER_MENU)
                    .append('<li class="preview-disable"><span>Disable Preview</span></li>');
            } else {
                selectWrapper.find('.' + SELECT_WRAPPER_MENU)
                    .append('<li class="preview-enable"><span>Preview</span></li>');
            }
        }
    }
    if (isImage) {
        var stretching = component.getProperty(IMAGE_STRETCHING);
        if (stretching != null) {
            if (stretching.value == 'cover') {
                selectWrapper.find('.' + SELECT_WRAPPER_MENU).append('<li class="image-stretching-toggle" data-value="contain"><span>Fit frame</span></li>');
            } else {
                selectWrapper.find('.' + SELECT_WRAPPER_MENU).append('<li class="image-stretching-toggle" data-value="cover"><span>Cover frame</span></li>');
            }
        }
    }
    if (isForm || isContactUs || isEvaluateHome) {
        var isMailHistoryEnable = component.getProperty(MAIL_HISTORY_PROPERTY);
        if (isMailHistoryEnable != null && isMailHistoryEnable.value.toBoolean()) {
            selectWrapper.find('.' + SELECT_WRAPPER_MENU).append('<li class="form-manage-mails"><i class="fa fa-lg fa-envelope-o" title="Inbox"></i></li>');
        }
    }

    $('body').append(selectWrapper);
    if (!UI.settings.isComponentEditor) Resizer.rescaleUIElements();

    $('.resizer').bind('click', function (event) {
        event.stopPropagation();
    });
    var elementInfo = {
        initialX: 0,
        initialY: 0,
        initialHeight: 0,
        initialWidth: 0,
        initialTop: 0,
        initialLeft: 0,
        initialScale: 0,
        getCurrent: function (event) {
            event = event || window.event;
            this.initialX = event.clientX;
            this.initialY = event.clientY;
            this.initialHeight = element.outerHeight();
            this.initialWidth = element.outerWidth();
            this.initialTop = element.position().top;
            this.initialLeft = element.position().left;
            this.initialScale = element.outerWidth() / element.outerHeight();
        }
    }

    var clientInfo = {
        clientX: 0,
        clientY: 0
    }

    var undoredo = function (left, top, width, height, element, component) {
        component.setProperty(LEFT, left);
        component.setProperty(TOP, top);
        component.setProperty(WIDTH, width);
        component.setProperty(HEIGHT, height);

        element.css(LEFT, left);
        element.css(TOP, top);
        element.css(WIDTH, width);
        element.css(HEIGHT, height);
        element.highlightSelectedElement(component, true);
    }

    var resizerFactory = {
        //unbinding all events for handels
        mouseDownEvent: function (e) {
            console.log("mouse down");

            var left = component.getProperty(LEFT).value;
            var top = component.getProperty(TOP).value;
            var width = component.getProperty(WIDTH).value;
            var height = component.getProperty(HEIGHT).value;

            UI.undoManagerAdd(
                {
                    undo: function (a) {
                        undoredo(left, top, width, height, element, component);
                    },
                    redo: function (b) { }
                });

        },
        mouseUpEvent: function () {
            console.log("mouse up");

            element.highlightSelectedElement(component, true);

            $(document).unbind('mousemove', resizerFactory.mouseMove.bottomHandle);
            $(document).unbind('mousemove', resizerFactory.mouseMove.topHandle);
            $(document).unbind('mousemove', resizerFactory.mouseMove.rightHandle);
            $(document).unbind('mousemove', resizerFactory.mouseMove.leftHandle);
            $(document).unbind('mousemove', resizerFactory.mouseMove.topLeftHandle);
            $(document).unbind('mousemove', resizerFactory.mouseMove.topRightHandle);
            $(document).unbind('mousemove', resizerFactory.mouseMove.bottomLeftHandle);
            $(document).unbind('mousemove', resizerFactory.mouseMove.bottomRightHandle);
            $(document).unbind('mouseup', resizerFactory.mouseUpEvent);

            var left = component.getProperty(LEFT).value;
            var top = component.getProperty(TOP).value;
            var width = component.getProperty(WIDTH).value;
            var height = component.getProperty(HEIGHT).value;

            UI.undoManagerAddSpecific(
                {
                    redo: function (c) {
                        undoredo(left, top, width, height, element, component);
                    }
                }, "redo");

            Resizer.recalculateSizeFooterContainer($('.footer')[0]);
            $(".objectx").css({ "display": "none" });
            $(".objecty").css({ "display": "none" });

            Resizer.rescaleUIElements();
        },
        //if some post resize operations are needed
        processSpecificOperations: function (component, element) {
            var componentType = component.proto.name;
            switch (componentType) {
                case BUTTON:
                case SIGNIN:
                case STORE_CATEGORIES:
                    ViewerFactory.calculateButtonLineHeight(component.getUISelector());
                    break;
                case HOUSE_PHOTO_TOUR:
                    if (component.children.length > 0) {
                        ViewerFactory.housePhotoTourRerender(component);
                    }
                    break;
                case MENU:
                    ViewerFactory.calculateMenuLineHeight(component, '#' + element[0].id);
                    break;
                case STORE_GALLERY:
                    EditorFactory.recalculateStoreGallerySize(component);
                    break;
                case GALLERY:
                    ViewerFactory.calculateMarginForGallery(component, '#' + element[0].id);
                    ViewerFactory.calculateHeightForGallery(component, '#' + element[0].id);
                    break;
                case LIST:
                    ViewerFactory.calculateHeightForList(component, '#' + element[0].id);
                    break;
                case FORM:
                    ViewerFactory.calculateFormMinSize(component, element);
                    break;
                default:
            }
        },
        //rescaling select wrapper after resize
        rescaleSelectWrapper: function () {
            if (element.hasClass('std-component-fixed')) {                
                $(UI.getConfigurationValue(SELECT_WRAPPER)).css(ViewerFactory.getFixedLocationPosition(component, true));                
            } else {
                $(UI.getConfigurationValue(SELECT_WRAPPER)).css({
                    top: element.offset().top + 'px',
                    left: element.offset().left + 'px'
                });   
            }
            $(UI.getConfigurationValue(SELECT_WRAPPER)).css({
                width: element.outerWidth() + 'px',
                height: element.outerHeight() + 'px'
            });
            StretcherFactory.updateComponentWrapper(component);
            Resizer.rescaleUIElements();
        },

        checkListMinWidth: function (elementNewWidth) {
            if (element.hasClass('std-list') && element.find('.item').length != 0) {
                var minListWidth = parseInt(element.find('.item').css('min-width'));
                if (elementNewWidth < minListWidth) {
                    return minListWidth;
                }
            }
            return elementNewWidth;
        },
        calcLimitedDY: function (isTopSide, event) {
            var cX = event.clientX;
            var cY = event.clientY;
            var dif = cY - elementInfo.initialY;

            var basicParentContainer = Resizer.findBasicContainer(element[0]);

            var basicParentContainerBottomLine = 0;
            var additionalOffset = Resizer.additionalOffsetFromParent(element[0]);
            if (basicParentContainer != null) {
                basicParentContainerBottomLine = basicParentContainer.offsetHeight;
            } else {
                var parent = element.offsetParent();
                basicParentContainerBottomLine = parent.length ? parent[0].offsetHeight : 0;
            }

            if (isTopSide) {
                var elementNewTop = elementInfo.initialTop + dif;
                if (elementNewTop + additionalOffset < 0 && !element.hasClass(STD_COMPONENT_FIXED)) {
                    return -(additionalOffset + elementInfo.initialTop);
                }
                else {
                    return dif;
                }
            }
            else {
                var elementNewHeight = elementInfo.initialHeight + dif;
                if (elementNewHeight + elementInfo.initialTop + additionalOffset > basicParentContainerBottomLine && !$(basicParentContainer).hasClass('footer')) {
                    return basicParentContainerBottomLine - additionalOffset - elementInfo.initialTop - elementInfo.initialHeight;
                }
                else {
                    return dif;
                }
            }
        },
        calcLimitedDX: function(isLeftSide, event) {
            var cX = event.clientX;
            var dif = cX - elementInfo.initialX;
            var basicParentContainer = Resizer.findBasicContainer(element[0]);
            var basicParentContainerRightLine = 0;

            var additionalOffset = Resizer.additionalOffsetFromParent(element[0]);

            if (basicParentContainer != null) {
                basicParentContainerRightLine = basicParentContainer.offsetWidth;
            }

            if (isLeftSide) {
                var elementNewLeft = elementInfo.initialLeft + dif;
                if (elementNewLeft + additionalOffset < 0) {
                    return -(additionalOffset + elementInfo.initialLeft);
                }
                else {
                    return dif;
                }
            }
            else {
                var elementNewWidth = elementInfo.initialWidth + dif;                
                if (elementNewWidth + elementInfo.initialLeft + additionalOffset > basicParentContainerRightLine) {
                    return basicParentContainerRightLine - additionalOffset - elementInfo.initialLeft - elementInfo.initialWidth;
                }
                else {
                    return dif;
                }
            }
        },
        mouseMove: {
            topHandle: function (event) {
                console.log("tophandle");
                Helpers.clearAllSelection();
                var cX = event.clientX;
                var cY = event.clientY;

                var isImg = element.find('img').hasClass('std-img');
                var changeOuterValues = element.hasClass('std-menu');
                var isStretched = StretcherFactory.getCurrentStretchStatus(component);

                var dif = resizerFactory.calcLimitedDY(true, event);
                var minHeightElement = parseInt(element.css('min-height'));


                var elementNewHeight = elementInfo.initialHeight - (dif);
                var negativeDifferenceBeetwenHeight = 0;
                if (elementNewHeight < minHeightElement) {
                    negativeDifferenceBeetwenHeight = minHeightElement - elementNewHeight;
                    elementNewHeight = minHeightElement;
                }

                var elementNewTop = (elementInfo.initialTop + dif - negativeDifferenceBeetwenHeight) + 'px';
             
                setNewParams();

                var move = Resizer.drawRulers(element, 't', 0, element[0].offsetTop);
                if (move.Top != 0) {
                    elementNewTop = (elementInfo.initialTop + dif) + move.Top + 'px';
                    elementNewHeight = elementInfo.initialHeight - (dif) - move.Top;
                    setNewParams();
                }

                function setNewParams() {
                    if (elementNewHeight >= minHeightElement) {                    
                        if (isImg && !isStretched) {
                            var elementNewWidth = parseInt(elementNewHeight * elementInfo.initialScale);
                            element.width(elementNewWidth);
                        }
                        if (!element.hasClass(STD_COMPONENT_FIXED)) {
                            element.css(TOP, elementNewTop);
                            component.setProperty(TOP, elementNewTop);
                        }
                        element.outerHeight(elementNewHeight);
                        component.setProperty('height', elementNewHeight + 'px');
                        resizerFactory.rescaleSelectWrapper();
                        resizerFactory.processSpecificOperations(component, element);     
                    }
                }
            },
            bottomHandle: function (event) {
                Helpers.clearAllSelection();
                console.log("bottomhandle");

                var cX = event.clientX;
                var cY = event.clientY;

                var isImg = element.find('img').hasClass('std-img');
                var changeOuterValues = element.hasClass('std-menu');
                var isStretched = StretcherFactory.getCurrentStretchStatus(component);

                var dif = resizerFactory.calcLimitedDY(false, event);
                var minHeightElement = parseInt(element.css('min-height'));

                var elementNewHeight = elementInfo.initialHeight + dif;

                if (elementNewHeight < minHeightElement) {
                    elementNewHeight = minHeightElement;
                }

                setNewParams();
                var move = Resizer.drawRulers(element, 'b');
                if (move.Top != 0) {
                    elementNewHeight = elementInfo.initialHeight + (cY - elementInfo.initialY) + move.Top;
                    setNewParams();
                }

                function setNewParams() {
                    if (elementNewHeight >= minHeightElement) {
                        if (isImg && !isStretched) {
                            var elementNewWidth = parseInt(elementNewHeight * elementInfo.initialScale);
                            element.width(elementNewWidth);
                            component.setProperty('width', elementNewWidth + 'px');
                        }

                        if (changeOuterValues) {
                            element.height(elementNewHeight);
                        }
                        else {
                            element.outerHeight(elementNewHeight);
                        }

                        component.setProperty('height', elementNewHeight + 'px');
                        resizerFactory.rescaleSelectWrapper();
                        resizerFactory.processSpecificOperations(component, element);
                    }

                    if ($(element).parent().hasClass("footer")) {
                        Resizer.resizeFooterContainer(element[0], $(".footer")[0]);
                    }

                }
            },
            rightHandle: function (event) {
                Helpers.clearAllSelection();
                console.log("righthandle");
                var cX = event.clientX;
                var cy = event.clientY;
                var dif = cX - elementInfo.initialX;
                /* limited resize in mobile editor
                if (!UI.getDevice().isDesktop()) {
                    dif = resizerFactory.calcLimitedDX(false, event);
                }
                */
                var isImg = element.find('img').hasClass('std-img');
                var changeOuterValues = element.hasClass('std-headertext') || element.hasClass('std-paragraph');
                var minWidthElement = parseInt(element.css('min-width'));
                var elementNewWidth = elementInfo.initialWidth + dif;
                var menuElement = $(element).children().hasClass("vertical");

                if (elementNewWidth < minWidthElement) {
                    elementNewWidth = minWidthElement;
                }

                setNewParams();
                var move = Resizer.drawRulers(element, 'r', element[0].offsetLeft);
                if (move.Left != 0) {
                    elementNewWidth = elementInfo.initialWidth + dif + move.Left;
                    setNewParams();
                }

                function setNewParams() {

                    if (elementNewWidth >= minWidthElement && Resizer.checkMinMaxWidth(element[0], elementNewWidth)) {
                        if (isImg) {
                            var elementNewHeight = parseInt(elementNewWidth / elementInfo.initialScale);
                            element.height(elementNewHeight);
                            component.setProperty('height', elementNewHeight + 'px');
                        }
                        elementNewWidth = resizerFactory.checkListMinWidth(elementNewWidth);
                        
                        if (menuElement) {
                            setWidthMenuComponent(elementNewWidth, _checkItem = true, $(element).children().children().children());
                        } else {
                            setWidthMenuComponent(elementNewWidth, _checkItem = false, $(element).children().children().children());
                        }

                        element.outerWidth(elementNewWidth);

                        component.setProperty('width', elementNewWidth + 'px');

                        resizerFactory.rescaleSelectWrapper();
                        resizerFactory.processSpecificOperations(component, element);
                    }
                };

            },
            leftHandle: function (event) {
                Helpers.clearAllSelection();
                console.log("lefthandle");
                var cX = event.clientX;
                var cY = event.clientY;
                var dif = cX - elementInfo.initialX;
                /* limited resize in mobile editor
                if (!UI.getDevice().isDesktop()) {
                    dif = resizerFactory.calcLimitedDX(true, event);
                }
                */
                var minWidthElement = parseInt(element.css('min-width'));
                var elementNewWidth = elementInfo.initialWidth - (dif);

                var negativeDifferenceBeetwenWidth = 0;
                if (elementNewWidth < minWidthElement) {
                    negativeDifferenceBeetwenWidth = minWidthElement - elementNewWidth;
                    elementNewWidth = minWidthElement;
                }

                var elementNewLeft = (elementInfo.initialLeft + dif - negativeDifferenceBeetwenWidth) + 'px';

                var isImg = element.find('img').hasClass('std-img');
                var changeOuterValues = element.hasClass('std-headertext') || element.hasClass('std-paragraph');
                var menuElement = $(element).children().hasClass("vertical");
                
                setNewParams();

                var move = Resizer.drawRulers(element, 'l', element[0].offsetLeft, 0);
                if (move.Left != 0) {
                    elementNewWidth = elementInfo.initialWidth - (dif) - move.Left;
                    elementNewLeft = (elementInfo.initialLeft + dif) + move.Left + 'px';
                    setNewParams();
                }

                function setNewParams() {
                    if (elementNewWidth >= minWidthElement && Resizer.checkMinMaxWidth(element[0], elementNewWidth)) {
                        if (isImg) {
                            var elementNewHeight = parseInt(elementNewWidth / elementInfo.initialScale);
                            element.height(elementNewHeight);
                            component.setProperty('height', elementNewHeight + 'px');
                        }
                        elementNewWidth = resizerFactory.checkListMinWidth(elementNewWidth);
                        if (!element.hasClass(STD_COMPONENT_FIXED)) {
                            element.css(LEFT, elementNewLeft);
                            component.setProperty(LEFT, elementNewLeft);
                        }

                        if (menuElement) {
                            setWidthMenuComponent(elementNewWidth, _checkItem = true, $(element).children().children().children());
                        } else {
                            setWidthMenuComponent(elementNewWidth, _checkItem = false, $(element).children().children().children());
                        }

                        element.outerWidth(elementNewWidth);
                        
                        component.setProperty('width', elementNewWidth + 'px');
                        resizerFactory.rescaleSelectWrapper();
                        resizerFactory.processSpecificOperations(component, element);
                    }
                }
            },
            topLeftHandle: function (event) {
                Helpers.clearAllSelection();
                console.log("toleftphandle");
                var cX = event.clientX;
                var cY = event.clientY;
                var tDif = resizerFactory.calcLimitedDY(true, event);
                var lDif = cX - elementInfo.initialX;
                /* limited resize in mobile editor
                if (!UI.getDevice().isDesktop()) {
                    lDif = resizerFactory.calcLimitedDX(true, event);
                }
                */
                var isImg = element.find('img').hasClass('std-img');
                var changeOuterValues = element.hasClass('std-menu');

                var minWidthElement = parseInt(element.css('min-width'));
                var minHeightElement = parseInt(element.css('min-height'));


                var elementNewHeight = elementInfo.initialHeight - (tDif);
                var elementNewWidth = elementInfo.initialWidth - (lDif);

                var negativeDifferenceBeetwenWidth = 0;
                if (elementNewWidth < minWidthElement) {
                    negativeDifferenceBeetwenWidth = minWidthElement - elementNewWidth;
                    elementNewWidth = minWidthElement;
                }

                var negativeDifferenceBeetwenHeight = 0;
                if (elementNewHeight < minHeightElement) {
                    negativeDifferenceBeetwenHeight = minHeightElement - elementNewHeight;
                    elementNewHeight = minHeightElement;
                }

                var elementNewTop = (elementInfo.initialTop + tDif - negativeDifferenceBeetwenHeight) + 'px';
                var elementNewLeft = (elementInfo.initialLeft + lDif - negativeDifferenceBeetwenWidth) + 'px';


                var menuElement = $(element).children().hasClass("vertical");
                setNewParams();

                var move = Resizer.drawRulers(element, 'tl', element[0].offsetLeft, element[0].offsetTop);
                if (move.Left != 0 || move.Top != 0) {

                    elementNewTop = (elementInfo.initialTop + tDif) + move.Top + 'px';
                    elementNewHeight = elementInfo.initialHeight - (tDif) - move.Top;
                    elementNewLeft = (elementInfo.initialLeft + lDif) + move.Left + 'px';
                    elementNewWidth = elementInfo.initialWidth - (lDif) - move.Left;

                    setNewParams();
                }

                function setNewParams() {
                 
                    if (elementNewHeight >= minHeightElement) {
                        if (!element.hasClass(STD_COMPONENT_FIXED)) {
                            element.css(TOP, elementNewTop);
                            component.setProperty(TOP, elementNewTop);
                        }
                        element.outerHeight(elementNewHeight);
                        component.setProperty(HEIGHT, elementNewHeight + 'px');
                        resizerFactory.rescaleSelectWrapper();
                        resizerFactory.processSpecificOperations(component, element);
                    }
                    if (elementNewWidth >= minWidthElement && Resizer.checkMinMaxWidth(element[0], elementNewWidth)) {
                        if (!element.hasClass(STD_COMPONENT_FIXED)) {
                            element.css('left', elementNewLeft);
                            component.setProperty('left', elementNewLeft);
                        }
                        if (component.proto.name == MENU) {
                            if (menuElement) {
                                setWidthMenuComponent(elementNewWidth, _checkItem = true, $(element).children().children().children());
                            } else {
                                setWidthMenuComponent(elementNewWidth, _checkItem = false, $(element).children().children().children());
                            }
                        }
                        element.outerWidth(elementNewWidth);
                        component.setProperty('width', elementNewWidth + 'px');
                        resizerFactory.rescaleSelectWrapper();
                        resizerFactory.processSpecificOperations(component, element);
                    }
                    
                }
            },
            topRightHandle: function (event) {
                Helpers.clearAllSelection();
                console.log("torightphandle");
                var cX = event.clientX;
                var cY = event.clientY;
                var tDif = resizerFactory.calcLimitedDY(true, event);
                var rDif = cX - elementInfo.initialX;
                /* limited resize in mobile editor
                if (!UI.getDevice().isDesktop()) {
                    rDif = resizerFactory.calcLimitedDX(false, event);
                }
                */
                var minWidthElement = parseInt(element.css('min-width'));
                var minHeightElement = parseInt(element.css('min-height'));

                var elementNewHeight = elementInfo.initialHeight - tDif;
                var elementNewWidth = elementInfo.initialWidth + rDif;

                var negativeDifferenceBeetwenWidth = 0;
                if (elementNewWidth < minWidthElement) {
                    negativeDifferenceBeetwenWidth = minWidthElement - elementNewWidth;
                    elementNewWidth = minWidthElement;
                }

                var negativeDifferenceBeetwenHeight = 0;
                if (elementNewHeight < minHeightElement) {
                    negativeDifferenceBeetwenHeight = minHeightElement - elementNewHeight;
                    elementNewHeight = minHeightElement;
                }


                var elementNewTop = (elementInfo.initialTop + tDif - negativeDifferenceBeetwenHeight) + 'px';
               
                var changeOuterValues = element.hasClass('std-menu');
                var menuElement = $(element).children().hasClass("vertical");


                setNewParams();

                var move = Resizer.drawRulers(element, 'tr', element[0].offsetLeft, element[0].offsetTop);
                if (move.Left != 0 || move.Top != 0) {
                    elementNewTop = (elementInfo.initialTop + tDif) + move.Top + 'px';
                    elementNewHeight = elementInfo.initialHeight - (tDif) - move.Top;
                    elementNewWidth = elementInfo.initialWidth + rDif + move.Left;                    
                    setNewParams();
                }

                function setNewParams() {
           
                    if (elementNewHeight >= minHeightElement) {
                        if (!element.hasClass(STD_COMPONENT_FIXED)) {
                            element.css(TOP, elementNewTop);
                            component.setProperty(TOP, elementNewTop);
                        }
                        element.outerHeight(elementNewHeight);                        
                        component.setProperty(HEIGHT, elementNewHeight + 'px');
                        resizerFactory.rescaleSelectWrapper();
                        resizerFactory.processSpecificOperations(component, element);
                    }
                    if (elementNewWidth >= minWidthElement && Resizer.checkMinMaxWidth(element[0], elementNewWidth)) {
                        if (component.proto.name == MENU) {
                            if (menuElement) {
                                setWidthMenuComponent(elementNewWidth, _checkItem = true, $(element).children().children().children());
                            } else {
                                setWidthMenuComponent(elementNewWidth, _checkItem = false, $(element).children().children().children());
                            }
                        }

                        element.outerWidth(elementNewWidth);

                        component.setProperty('width', elementNewWidth + 'px');
                        resizerFactory.rescaleSelectWrapper();
                        resizerFactory.processSpecificOperations(component, element);
                    }
                }
            },
            bottomRightHandle: function (event) {
                Helpers.clearAllSelection();
                console.log("bottomrightphandle");
                var cX = event.clientX;
                var cY = event.clientY;
                var rDif = cX - elementInfo.initialX;
                /* limited resize in mobile editor
                if (!UI.getDevice().isDesktop()) {
                    rDif = resizerFactory.calcLimitedDX(false, event);
                }
                */
                var bDif = resizerFactory.calcLimitedDY(false, event);
                var direction = bDif > rDif;
                var isImg = element.find('img').hasClass('std-img');
                var changeOuterValues = element.hasClass('std-menu');

                var minWidthElement = parseInt(element.css('min-width'));
                var minHeightElement = parseInt(element.css('min-height'));

                var elementNewWidth = elementInfo.initialWidth + (rDif);
                var elementNewHeight = elementInfo.initialHeight + (bDif);

                var negativeDifferenceBeetwenWidth = 0;
                if (elementNewWidth < minWidthElement) {
                    negativeDifferenceBeetwenWidth = minWidthElement - elementNewWidth;
                    elementNewWidth = minWidthElement;
                }

                var negativeDifferenceBeetwenHeight = 0;
                if (elementNewHeight < minHeightElement) {
                    negativeDifferenceBeetwenHeight = minHeightElement - elementNewHeight;
                    elementNewHeight = minHeightElement;
                }

                var menuElement = $(element).children().hasClass("vertical");

                setNewParams();

                var move = Resizer.drawRulers(element, 'br', element[0].offsetLeft, element[0].offsetTop);
                if (move.Left != 0 || move.Top != 0) {
                    elementNewWidth = elementInfo.initialWidth + (rDif) + move.Left;
                    elementNewHeight = elementInfo.initialHeight + (bDif) + move.Top;
                    setNewParams();
                }

                function setNewParams() {
                    if (elementNewWidth >= minWidthElement && Resizer.checkMinMaxWidth(element[0], elementNewWidth)) {

                        if (component.proto.name == MENU) {
                            if (menuElement) {
                                setWidthMenuComponent(elementNewWidth, _checkItem = true, $(element).children().children().children());
                            } else {
                                setWidthMenuComponent(elementNewWidth, _checkItem = false, $(element).children().children().children());
                            }
                        }

                        element.outerWidth(elementNewWidth);

                        component.setProperty('width', elementNewWidth + 'px');
                        resizerFactory.rescaleSelectWrapper();
                        resizerFactory.processSpecificOperations(component, element);
                    }

                    if (elementNewHeight >= minHeightElement) {

                        element.outerHeight(elementNewHeight);

                        component.setProperty('height', elementNewHeight + 'px');
                        resizerFactory.rescaleSelectWrapper();
                        resizerFactory.processSpecificOperations(component, element);
                    }

                    if ($(element).parent().hasClass("footer")) {
                        Resizer.resizeFooterContainer(element[0], $(".footer")[0]);
                    }
                }
            },
            bottomLeftHandle: function (event) {
                Helpers.clearAllSelection();
                console.log("bottomleftphandle");
                var cX = event.clientX;
                var cY = event.clientY;
                var lDif = cX - elementInfo.initialX;
                /* limited resize in mobile editor
                if (!UI.getDevice().isDesktop()) {
                    lDif = resizerFactory.calcLimitedDX(true, event);
                }
                */
                var bDif = resizerFactory.calcLimitedDY(false, event);

                var minWidthElement = parseInt(element.css('min-width'));
                var minHeightElement = parseInt(element.css('min-height'));

                var elementNewWidth = elementInfo.initialWidth - (lDif);
                var elementNewHeight = elementInfo.initialHeight + bDif;

                var negativeDifferenceBeetwenWidth = 0;
                if (elementNewWidth < minWidthElement) {
                    negativeDifferenceBeetwenWidth = minWidthElement - elementNewWidth;
                    elementNewWidth = minWidthElement;
                }

                var negativeDifferenceBeetwenHeight = 0;
                if (elementNewHeight < minHeightElement) {
                    negativeDifferenceBeetwenHeight = minHeightElement - elementNewHeight;
                    elementNewHeight = minHeightElement;
                }

                var elementNewLeft = (elementInfo.initialLeft + lDif - negativeDifferenceBeetwenWidth) + 'px';
                var changeOuterValues = element.hasClass('std-menu');
                var menuElement = $(element).children().hasClass("vertical");
                
                setNewParams();

                var move = Resizer.drawRulers(element, 'bl', element[0].offsetLeft, element[0].offsetTop);
                if (move.Left != 0 || move.Top != 0) {

                    elementNewWidth = elementInfo.initialWidth - (lDif) - move.Left;
                    elementNewHeight = elementInfo.initialHeight + (cY - elementInfo.initialY) + move.Top;
                    elementNewLeft = (elementInfo.initialLeft + lDif) + move.Left + 'px';

                    setNewParams();
                }

                function setNewParams() {

                    if (elementNewHeight >= minHeightElement) {

                        element.outerHeight(elementNewHeight);

                        if (changeOuterValues) {
                            element.height(elementNewHeight);
                        }
                        else {
                            element.outerHeight(elementNewHeight);
                        }

                        component.setProperty('height', elementNewHeight + 'px');
                        resizerFactory.rescaleSelectWrapper();
                        resizerFactory.processSpecificOperations(component, element);
                    }
                    if (elementNewWidth >= minWidthElement && Resizer.checkMinMaxWidth(element[0], elementNewWidth)) {
                        if (!element.hasClass(STD_COMPONENT_FIXED)) {
                            element.css(LEFT, elementNewLeft);    
                            component.setProperty(LEFT, elementNewLeft);
                        }
                        
                        if (component.proto.name == MENU) {
                            if (menuElement) {
                                setWidthMenuComponent(elementNewWidth, _checkItem = true, $(element).children().children().children());
                            } else {
                                setWidthMenuComponent(elementNewWidth, _checkItem = false, $(element).children().children().children());
                            }
                        }

                        element.outerWidth(elementNewWidth);
                        
                        component.setProperty('width', elementNewWidth + 'px');
                        resizerFactory.rescaleSelectWrapper();
                        resizerFactory.processSpecificOperations(component, element);
                    }
                    if ($(element).parent().hasClass("footer")) {
                        Resizer.resizeFooterContainer(element[0], $(".footer")[0]);
                    }
                }//end function setNewParams
            }//end bottomLeftHandle
        }
    }
    //events binding
    $('.resizer.bottom').bind('mousedown', function (event) {
        console.log(".resizer.bottom");
        resizerFactory.mouseDownEvent(event);
        PopoverHelper.hidePopovers();
        elementInfo.getCurrent(event);
        $(document).on('mousemove', resizerFactory.mouseMove.bottomHandle);
        $(document).on('mouseup', resizerFactory.mouseUpEvent);
    });
    $('.resizer.top').bind('mousedown', function (event) {
        console.log(".resizer.top");
        resizerFactory.mouseDownEvent(event);
        PopoverHelper.hidePopovers();
        elementInfo.getCurrent(event);
        $(document).on('mousemove', resizerFactory.mouseMove.topHandle);
        $(document).on('mouseup', resizerFactory.mouseUpEvent);

    });
    $('.resizer.right').bind('mousedown', function (event) {
        console.log(".resizer.right");
        resizerFactory.mouseDownEvent(event);
        PopoverHelper.hidePopovers();
        elementInfo.getCurrent(event);
        $(document).on('mousemove', resizerFactory.mouseMove.rightHandle);
        $(document).on('mouseup', resizerFactory.mouseUpEvent);
    });
    $('.resizer.left').bind('mousedown', function (event) {
        console.log(".resizer.left");
        resizerFactory.mouseDownEvent(event);
        PopoverHelper.hidePopovers();
        elementInfo.getCurrent(event);
        $(document).on('mousemove', resizerFactory.mouseMove.leftHandle);
        $(document).on('mouseup', resizerFactory.mouseUpEvent);
    });
    $('.resizer.top-left').bind('mousedown', function (event) {
        console.log(".resizer.top-left");
        resizerFactory.mouseDownEvent(event);
        PopoverHelper.hidePopovers();
        elementInfo.getCurrent(event);
        $(document).on('mousemove', resizerFactory.mouseMove.topLeftHandle);
        $(document).on('mouseup', resizerFactory.mouseUpEvent);
    });
    $('.resizer.top-right').bind('mousedown', function (event) {
        console.log(".resizer.top-right");
        resizerFactory.mouseDownEvent(event);
        PopoverHelper.hidePopovers();
        elementInfo.getCurrent(event);
        $(document).on('mousemove', resizerFactory.mouseMove.topRightHandle);
        $(document).on('mouseup', resizerFactory.mouseUpEvent);
    });
    $('.resizer.bottom-right').bind('mousedown', function (event) {
        console.log(".resizer.bottom-right");
        resizerFactory.mouseDownEvent(event);
        PopoverHelper.hidePopovers();
        elementInfo.getCurrent(event);
        $(document).on('mousemove', resizerFactory.mouseMove.bottomRightHandle);
        $(document).on('mouseup', resizerFactory.mouseUpEvent);
    });
    $('.resizer.bottom-left').bind('mousedown', function (event) {
        console.log(".resizer.bottom-left");
        resizerFactory.mouseDownEvent(event);
        PopoverHelper.hidePopovers();
        elementInfo.getCurrent(event);
        $(document).on('mousemove', resizerFactory.mouseMove.bottomLeftHandle);
        $(document).on('mouseup', resizerFactory.mouseUpEvent);
    });
    $('.' + SELECT_WRAPPER_MENU + ' .move-to-footer').bind('click', function (event) {
        Dock.unconditionalElementDocking(element, $('.footer'), false);
        dragDrop.startY = dragDrop.draggedObject.offsetTop;
        dragDrop.dYKeys = 0;
    });
    $('.' + SELECT_WRAPPER_MENU + ' .move-to-page').bind('click', function (event) {
        Dock.unconditionalElementDocking(element, $('#' + UI.pager.getCurrentPageId()), false);
        Resizer.checkMinSizePageContainer($('#' + UI.pager.getCurrentPageId())[0]);
        //var component = UI.siteComponentRepository.lookupData({ id: $(element).getId() });
        //element.highlightSelectedElement(component);
        dragDrop.startY = dragDrop.draggedObject.offsetTop;
        dragDrop.dYKeys = 0;
    });
    $('.' + SELECT_WRAPPER_MENU + ' .move-to-page-up').bind('click', function (event) {
        Dock.unconditionalElementDocking(element, $('#' + UI.pager.getCurrentPageId()), true);
        Resizer.checkMinSizePageContainer($('#' + UI.pager.getCurrentPageId())[0]);
        dragDrop.startY = dragDrop.draggedObject.offsetTop;
        dragDrop.dYKeys = 0;
    });
    $('.' + SELECT_WRAPPER_MENU + ' .move-to-header').bind('click', function (event) {
        Dock.unconditionalElementDocking(element, $('.header'), true);
        Resizer.checkMinSizeHeaderContainer($('.header')[0]);
        dragDrop.startY = dragDrop.draggedObject.offsetTop;
        dragDrop.dYKeys = 0;
    });

    function bindEditor(event) {
        event.stopPropagation();
        if (ViewerFactory.editorCanBeShown(element, event)) {
            UI.callEditor(component);
            $(element).highlightSelectedElement(component, true);
        }
    }

    $('.' + SELECT_WRAPPER_MENU + ' .edit-component').bind('click', bindEditor);
    if ($('.' + SELECT_WRAPPER_MENU + ' .edit-component').length && !isParagraph && !isHeaderText) {
        $(element).off('dblclick');
        $(element).bind('dblclick', bindEditor);
    }

    $('.' + SELECT_WRAPPER_MENU + ' .delete-component').bind('click', function (event) {
        event.stopPropagation();
        clipBoard.deleteComponent();
    });

    $('.' + SELECT_WRAPPER_MENU + ' .inherit-property-text').bind('click', function (event) {
        event.stopPropagation();
        var text = component.getProperty(TEXT);
        var mobileDevice = UI.devices.where({ type: DEVICE_MOBILE_TYPE }).firstOrDefault();
        if (mobileDevice) {
            component.setProperty(TEXT, text.value, true, mobileDevice.getId());
            $(component.getUISelector()).highlightSelectedElement(component, true);
        }
    });
    
    $('.' + SELECT_WRAPPER_MENU + ' .edit-text').bind('click', function (event) {
        event.stopPropagation();
        var isHide = component.getProperty(HIDE_COMPONENT);

        if (UI.isckeditorworking != true && (isHide == null || !isHide.value.toBoolean())) {
            component.isckeditorworking = true;
            $(element).attr('contenteditable', 'true');
            UI.callEditor(component);
            UI.isckeditorworking = true;
        }
    });

    $('.' + SELECT_WRAPPER_MENU + ' .preview-enable').bind('click', function (event) {
        event.stopPropagation();
        UI.actionService.runActionForComponent(component, ACTION_REMOVE_FROM_FORM, true);
        component.isPreviewEnabled = true;
        UI.actionService.runActionForComponent(component, ACTION_ADD_TO_FORM, true);
        $(component.getUISelector()).highlightSelectedElement(component, true);
    });

    $('.' + SELECT_WRAPPER_MENU + ' .preview-disable').bind('click', function (event) {
        event.stopPropagation();
        UI.actionService.runActionForComponent(component, ACTION_REMOVE_FROM_FORM, true);
        component.isPreviewEnabled = false;
        UI.actionService.runActionForComponent(component, ACTION_ADD_TO_FORM, true);
        $(component.getUISelector()).highlightSelectedElement(component, true);
    });
    $('.' + SELECT_WRAPPER_MENU + ' .form-manage-mails').bind('click', function (event) {
        event.stopPropagation();
        UI.componentService.addModalContentToForm(null, MANAGE_MAILS_COMPONENT, { controlId: component.id });
    });

    $('.' + SELECT_WRAPPER_MENU + ' .image-stretching-toggle').bind('click', function (event) {
        event.stopPropagation();
        var value = event.currentTarget.dataset.value;
        UI.undoManagerAddSimple(component,
            IMAGE_STRETCHING,
            value,
            function (val, com) {
                UI.actionService.runActionForComponent(com, ACTION_REMOVE_FROM_FORM, true);
                UI.actionService.runActionForComponent(com, ACTION_ADD_TO_FORM, true);
                $(com.getUISelector()).highlightSelectedElement(com, true);
            }, true);
    });

    $('.' + SELECT_WRAPPER_MENU + ' .hide-component').bind('click', function (event) {
        clipBoard.toggleShowHideComponent({}, event, true);
    });
    $('.resizer.show-component').bind('click', function (event) {
        clipBoard.toggleShowHideComponent({}, event, true);
    });

    $('.resizer.show-component').bind('mouseenter', function (event) {
        $(element).addClass('editor-blur');
    });

    $('.resizer.show-component').bind('mouseleave', function (event) {
        $(element).removeClass('editor-blur');
    });
}

Resizer.groupBind = function () {    
    var selectWrapper = $('.' + GROUP_WRAPPER + '.' + GROUP_WRAPPER_OUTER);    
    var arrowsTo = { footer: false, header: false, page: false, pageup: false, hideShow: true, deleteAll: true };
    ko.utils.arrayForEach(grouping.selectedItems(), function (item) {
        var element = $(item);
        var basicParentContainer = Resizer.findBasicContainer(element[0]);
        var basicParentContainerBottomLine = 0;
        var basicParentContainerTopLine = 0;

        if (basicParentContainer != null) {
            basicParentContainerTopLine = Resizer.cumulativeTopOffset(basicParentContainer);
            basicParentContainerBottomLine = basicParentContainerTopLine + basicParentContainer.offsetHeight;
        }

        var elementTopLine = Resizer.cumulativeTopOffset(element[0]);
        var elementBottomLine = element[0].clientHeight + elementTopLine;

        if (basicParentContainerBottomLine + 25 > elementBottomLine && basicParentContainerBottomLine - 25 < elementBottomLine && !$(basicParentContainer).hasClass('footer')) {
            if ($(basicParentContainer).hasClass('page')) {
                arrowsTo.footer = true;
            } else
                if ($(basicParentContainer).hasClass('header')) {
                    arrowsTo.page = true;
                }
        }

        if (basicParentContainerTopLine + 25 > elementTopLine && basicParentContainerTopLine - 25 < elementTopLine && !$(basicParentContainer).hasClass('header')) {
            if ($(basicParentContainer).hasClass('page')) {
                arrowsTo.header = true;
                
            } else
                if ($(basicParentContainer).hasClass('footer')) {
                    arrowsTo.pageup = true;
                }
        }

        if (element.hasClass('std-form-subcomponent') || element.hasClass('std-store-cart') || element.hasClass('std-store-product') || element.hasClass('std-store-thank-you')) {
            arrowsTo.footer = false;
            arrowsTo.page   = false;
            arrowsTo.header = false;
            arrowsTo.pageup = false;
            arrowsTo.hideShow = false;
            arrowsTo.deleteAll = false;
        }
    });

    if (UI.getDevice().isDesktop()) {
        if (arrowsTo.footer) {
            selectWrapper.find('.' + GROUP_WRAPPER_MENU)
                .append('<li class="move-to-footer"><i class="fa fa-lg fa-chevron-down" title="Move group to Footer"></i></li>');
        }
        if (arrowsTo.page) {
            selectWrapper.find('.' + GROUP_WRAPPER_MENU)
                .append('<li class="move-to-page"><i class="fa fa-lg fa-chevron-down" title="Move group to Page"></i></li>');
        }
        if (arrowsTo.header) {
            selectWrapper.find('.' + GROUP_WRAPPER_MENU)
                .append('<li class="move-to-header"><i class="fa fa-lg fa-chevron-up" title="Move group to Header"></i></li>');
        }
        if (arrowsTo.pageup) {
            selectWrapper.find('.' + GROUP_WRAPPER_MENU)
                .append('<li class="move-to-page-up"><i class="fa fa-lg fa-chevron-up" title="Move group to Page"></i></li>');
        }
    }

    if (arrowsTo.hideShow) {
        selectWrapper.find('.' + GROUP_WRAPPER_MENU).removeClass('stretched');
        if (StretcherFactory.checkOneOfSelectedElementsStretched()) {
            selectWrapper.find('.' + GROUP_WRAPPER_MENU).addClass('stretched');
        }
        selectWrapper.find('.' + GROUP_WRAPPER_MENU).append('<li class="show-component"><i class="fa fa-lg fa-eye" title="Show component"></i></li>');
        selectWrapper.find('.' + GROUP_WRAPPER_MENU).append('<li class="hide-component"><i class="fa fa-lg fa-eye-slash" title="Hide component"></i></li>');
    }

    if (arrowsTo.deleteAll && UI.getDevice().isDesktop()) {
        selectWrapper.find('.' + GROUP_WRAPPER_MENU)
            .append('<li class="delete-component" title="Delete Components"><i class="fa fa-lg fa-ban"></i></li>');
    }

    function postDockingUpdate() {
        if (dragDrop.draggedObject != null) {
            dragDrop.startY = dragDrop.draggedObject.offsetTop;
        }
        dragDrop.dYKeys = 0;
        Resizer.groupBind();
        ko.utils.arrayForEach(grouping.selectedItems(),
            function (item) {
                var element = $(item);
                element.mousemove(function () {
                    var currentElement = $(this);
                    currentElement.data('selected', true);
                    if (!currentElement.hasClass('std-component-fixed')) {
                        currentElement.addClass('drag');
                    }
                    if (grouping.ctrlIsPressed) {
                        currentElement.data('selected', false);
                        currentElement.removeClass('drag');
                    }
                });
                element.mouseleave(function () {
                    var currentElement = $(this);
                    currentElement.data('selected', false);
                    currentElement.removeClass('drag');
                });
            });
    }

    $('.' + GROUP_WRAPPER_MENU + ' .move-to-footer').bind('click', function (event) {
        var groupOffset = Grouping.getGroupFullOffset();

        ko.utils.arrayForEach(grouping.selectedItems(), function (item) {
            var offset = item.offsetTop - groupOffset.offsetY;
            var result =
                {
                    groupHeight: groupOffset.groupHeight,
                    offsetY: offset,
                    moveup: false
                };

            Dock.unconditionalElementDocking($(item), $('.footer'), false, true, result);
        });
        postDockingUpdate();
    });
    $('.' + GROUP_WRAPPER_MENU + ' .move-to-page').bind('click', function (event) {
        var groupOffset = Grouping.getGroupFullOffset();
        ko.utils.arrayForEach(grouping.selectedItems(), function (item) {
            var offset = item.offsetTop - groupOffset.offsetY;
            var result =
                {
                    groupHeight: groupOffset.groupHeight,
                    offsetY: offset,
                    moveup: false
                };

            Dock.unconditionalElementDocking($(item), $('#' + UI.pager.getCurrentPageId()), false, true, result);
        });
        
        Resizer.checkMinSizePageContainer($('#' + UI.pager.getCurrentPageId())[0]);
        postDockingUpdate();
    });
    $('.' + GROUP_WRAPPER_MENU + ' .move-to-page-up').bind('click', function (event) {
        var groupOffset = Grouping.getGroupFullOffset();

        ko.utils.arrayForEach(grouping.selectedItems(), function (item) {
            var offset = item.offsetTop - groupOffset.offsetY;
            var result =
                {
                    groupHeight: groupOffset.groupHeight,
                    offsetY: offset,
                    moveup: true
                };
            Dock.unconditionalElementDocking($(item), $('#' + UI.pager.getCurrentPageId()), true, true, result);
        });
        Resizer.checkMinSizePageContainer($('#' + UI.pager.getCurrentPageId())[0]);
       
        postDockingUpdate();
    });
    $('.' + GROUP_WRAPPER_MENU + ' .move-to-header').bind('click', function (event) {

        var groupOffset = Grouping.getGroupFullOffset();
        ko.utils.arrayForEach(grouping.selectedItems(), function (item) {
            var offset = item.offsetTop - groupOffset.offsetY;
            var result =
                {
                    groupHeight: groupOffset.groupHeight,
                    offsetY: offset,
                    moveup: true
                };
            Dock.unconditionalElementDocking($(item), $('.header'), true, true, result);
        });  
        Resizer.checkMinSizeHeaderContainer($('.header')[0]);

        postDockingUpdate();
    });

    $('.' + GROUP_WRAPPER_MENU + ' .hide-component')
        .bind('click',
            function (event) {
                Grouping.showOrHideGroupSelectedComponents(true);
            });

    $('.' + GROUP_WRAPPER_MENU + ' .show-component')
        .bind('click',
            function (event) {
                Grouping.showOrHideGroupSelectedComponents();
            });

    $('.' + GROUP_WRAPPER_MENU + ' .delete-component')
        .bind('click',
            function (event) {
                event.stopPropagation();
                clipBoard.deleteComponent();
            });
}

Resizer.cumulativeTopOffset = function(element) {
    var top = 0;
    do {
        if ($(element).hasClass('body')) {
            break;
        }
        top += element.offsetTop || 0;
        element = element.offsetParent;
    } while (element);
    return top;
};

Resizer.rescaleUIElements = function () {
    $('.resizer.show-component i').bigText();
};

Resizer.resizeComponent = function (component, newWidth) {
    if (newWidth) {
        component.setProperty(WIDTH, newWidth);
        var element = $(component.getUISelector());
        $(element).css('width', newWidth);
    }
};
/// <reference path="repositories.js" />
var MediaService = function () {
    var self = this;
    var allMedias = [];
    var vimeoPlayersReadyCount = 0;
    //var isstarted = false;

    self.addLeavePageEvent = function () {
    };

    self.startPlayVideoOnPage = function (pageId) {
        self.stopAllMedia();
        if (UI.getSetting("ispreview")) {
            $(allMedias).each(function() {
                var item = this;
                if (item.location == pageId || item.location == "header" || item.location == "footer") {
                    var autoplay = $(item.element).parent().attr("data-autoplay");
                    if (autoplay == "True" || autoplay == "true" || autoplay == "1") {
                        if (item.isStdMedia) {
                            var pause = 100;
                            if (item.pause > 0) { pause = item.pause*1000; }
                            setTimeout(function () { self.playMedia(item); }, pause);
                            return false;
                        }
                    }
                }
            });
        }
        return false;
    };

    self.getMediaThatMustPlayNext = function () {
        var result = null;

        $(allMedias).each(function () {
            if (this.location == "header" || this.location == "footer") {
                var autoplay = $(this.element).parent().attr("data-autoplay");
                if (autoplay == "True" || autoplay == "true" || autoplay == "1") {
                    if (this.isStdMedia && result == null) {
                        result = this;
                    }
                }
            }
        });
        
        return result;
    }

    self.getMediaCount = function () {
        return allMedias.where({ type: 'vimeo' }).length;
    }

    self.playMedia = function (media) {
        if (media.location != UI.pager.getCurrentPageId() && media.location != "header" && media.location != "footer") {
            console.log('media.js -> playMedia: video was stopped (page != initial page)');
            return false;
        }

        if (media.type == "youtube") {
            if (media.player != null && media.player.playVideo) {
                console.log('playMedia executed');
                media.player.playVideo();
            }
            else {
                setTimeout(function () { self.playMedia(media) }, 100);
                console.log("media id " + media.element.id + " don't have function playVideo()");
            }
        } else if (media.type == "vimeo") {
            if (self.vimeoPlayersReadyCount >= self.getMediaCount()) {
                var player = media.player;
                player.api('play');
            } else {
                setTimeout(function () { self.playMedia(media) }, 100);
                console.log("vimeo media id " + media.element.id + " not ready");
            }
        }
        else if (media.type == "jplayer") {

            if ($("#" + media.element.id).jPlayer !== undefined) {
                setTimeout(function () { self.actionJPlayer(media.element, "play") }, 100);
            }
        }
        else if (media.type == "soundcloud") {
            
            if (media.player != null && media.player.play) {
                console.log('soundcloud media is played');
                media.player.play();
            } else {
                setTimeout(function () { self.playMedia(media) }, 100);
                console.log("soundcloud media id " + media.element.id + " not ready");
            }
        }
        
    };

    self.stopAllMedia = function (type, exceptid) {
        for (var i = 0; i < allMedias.length; i++) {
            //stop video for youtobe
            if (self.getMediaThatMustPlayNext() != allMedias[i]) {
                if (allMedias[i].type == "youtube") {
                    if (exceptid == undefined || allMedias[i].player.c.id != exceptid)
                        if (allMedias[i].player && allMedias[i].player.stopVideo) {
                            allMedias[i].player.stopVideo();
                    }
                    else {
                        console.log("video id " + allMedias[i].element.id + " don't have function stopVideo()");
                    }
                }
                //stop video for vimeo
                else if (allMedias[i].type == "vimeo" && type != "vimeo") {
                    var player = $f(allMedias[i].element);
                    player.api('pause');
                }

                //stop SoundClouds
                else if (allMedias[i].type == "soundcloud") {
                    if (exceptid == undefined || exceptid != $(allMedias[i].element).parent().attr("id"))
                    if (allMedias[i].player) {
                        allMedias[i].player.pause();
                    }
                }

                //stop jplayer
                else if (allMedias[i].type == "jplayer") {
                    if (exceptid == undefined || allMedias[i].element.id != exceptid)
                    self.actionJPlayer(allMedias[i].element, "pause")
                }
            }
        }
    };

    self.onVimeoIframeAPIReady = function(playerVimeo) {
        var parent = playerVimeo.element.offsetParent ? playerVimeo.element.offsetParent : playerVimeo.element.parentNode; // if object located on the any component
        if ($("#" + parent.id).data("isevented") != 'true') {

            playerVimeo.addEvent('ready', function(e) {
                self.vimeoPlayersReadyCount += 1;
                for (var ii = 0; ii < allMedias.length; ii++) {
                    if (allMedias[ii].type == "vimeo") {
                        try {
                            var playerVimeoTmp = $f(allMedias[ii].element);
                            playerVimeoTmp.addEvent('play', self.onPlayVimeo);
                        } catch(e) {
                        }
                    }
                }
            });
        }
        $("#" + parent.id).data("isevented", 'true');
    };


    self.onYouTubeIframeAPIReady = function () {
        if (typeof (YT) == 'undefined' || typeof (YT.Player) == 'undefined') {
            window.onYouTubePlayerAPIReady = function () {
                UI.MediaService.youTubePlayerAPIReady();
            };
            $.getScript('//www.youtube.com/player_api');
        } else {
            self.youTubePlayerAPIReady();
        }
    };

    self.youTubePlayerAPIReady = function () {
        $(allMedias).each(function (item) {
            var struct = allMedias[item];

            if (struct.type == "youtube" && struct.player == null) {
                var t = new YT.Player(struct.element, {
                    events: {
                        'onStateChange': self.onPlayerStateChange
                    }
                });
                struct.player = t;
                allMedias[item] = struct;
            }
        });

    };


    self.soundCloudWidgetReady = function (item, t) {
        var struct = allMedias[item];
        struct.player = t;
        allMedias[item] = struct;
    };

    self.soundCloudPlayerAPIReady = function () {
        $(allMedias).each(function (item) {
            var struct = allMedias[item];

            if (struct.type == "soundcloud" && struct.player == null) {
                var t = new SC.Widget(struct.element);
                
                t.bind(SC.Widget.Events.READY, function () {
                    self.soundCloudWidgetReady(item,t);
                });
                
                t.bind(SC.Widget.Events.PLAY, function () {
                    self.onPlaySoundCloudPlayer(struct);
                    });                

                
            }
        });

    };
    
    self.onSoundCloudIframeAPIReady = function () {
        if (typeof (SC) == 'undefined') {
              window.onSoundCloudPlayerAPIReady = function () {
                UI.MediaService.soundCloudPlayerAPIReady();
              };
              $.getScript("https://w.soundcloud.com/player/api.js").then(function () {
                  self.soundCloudPlayerAPIReady();
              });
        } else {
            self.soundCloudPlayerAPIReady();
        }
    };
            
    //method for youtobe
    self.onPlayerStateChange = function(event) {
        if (event.data == YT.PlayerState.PAUSED) {
            //console.log("Paused");
        }
        if (event.data == YT.PlayerState.PLAYING) {
            var idElement = event.target.c.id;

            self.stopAllMedia("youtube", event.target.c.id);
        }
    };

    //method for vimeo 
    self.onPlayVimeo = function() {
        self.stopAllMedia("vimeo", "0");
   }

    //method for soundcloud 
    self.onPlaySoundCloudPlayer = function (player) {
        var componentIdParentOfEventer = player.element.offsetParent.id;
        self.stopAllMedia("soundcloud", componentIdParentOfEventer);
    };

    self.actionJPlayer = function(component, action)
    {
        if ($("#" + component.id).jPlayer != undefined)
        if ($("#" + component.id).data("ready") == "true") {
            $("#" + component.id).jPlayer(action);
        }
        else {
            setTimeout(function () { self.actionJPlayer(component, action) }, 100);
        }            
    }

    self.getPageFromClass = function (parent, iframe) {
        var location;
        if ($(iframe).closest('.header').length > 0) {
            location = "header";
        } else if ($(iframe).closest('.footer').length > 0) {
            location = "footer";
        } else {
            var page = $(iframe).closest(".page");
            if (page != null) {
                location = page[0].id;
            }
        }
        return location;
    };

    //obj - iframe
    //type - youtube, vimeo, soundcloud
    //location - page, header, footer
    //player - event to video
    self.pushIFrame = function (obj, type, location, isStdMedia) {

        var medias = allMedias.where({ element: obj });
        var struct = { element: obj, type: type, location: location, player: null, isStdMedia: isStdMedia, pause : 0 };

        if (medias.length == 0) {
            if (type == "vimeo") {
                var playerVimeo = $f(obj);
                struct.player = playerVimeo;
                self.onVimeoIframeAPIReady(playerVimeo);
            }
            if (type == "jplayer") {
                var jp = $("#" + obj.id);
                struct.pause = $("#" + jp.data("controlid")).data("pause");
                jp.bind($.jPlayer.event.play, function(event) {
                    self.stopAllMedia("jplayer", event.currentTarget.id);
                });
                
            }
            allMedias.push(struct);
            
            if (!isStdMedia) {
                var src = $(obj).attr('src');
                src = src.replace('autoplay=1','autoplay=0'); //remove manuals autostart
                $(obj).attr('src', src);
            }
        }
    }

    self.manageMedia = function () {

        self.vimeoPlayersReadyCount = 0;

        var getBodyChildren = $(".body").children(); // body page
        var parent;
        getBodyChildren.each(function () { // getBodyChildren[p] - get header, main, footer page accordingly 
            parent = this;
            $($(this).find('iframe[src*="youtube.com"]')).each(function (item) { // get all videos in page for youtobe
                var isStdMedia = $(this).parent().hasClass('std-video');
                self.pushIFrame(this, "youtube", self.getPageFromClass(parent, this), isStdMedia);

            });
            $($(this).find('iframe[src*="vimeo.com"]')).each(function () { // get all videos in page for vimeo
                var isStdMedia = $(this).parent().hasClass('std-video');
                self.pushIFrame(this, "vimeo", self.getPageFromClass(parent, this), isStdMedia);

            });

            $($(this).find('iframe[src*="soundcloud.com"]')).each(function () { // get all sounds in page for soundcloud
                var isStdMedia = $(this).parent().hasClass('std-sound');
                self.pushIFrame(this, "soundcloud", self.getPageFromClass(parent, this), isStdMedia);
            });

            $($(this).find('.jp-jplayer')).each(function () { // get all sounds in page for jplayer
                self.pushIFrame(this, "jplayer", self.getPageFromClass(parent, this), true);
            });

        });


        allMedias.sort(function (obj1, obj2) {
            var sortValue = new Array();
            sortValue["header"] = 1;
            sortValue["footer"] = 3;

            var obj1val = sortValue[obj1.location] || 2;
            var obj2val = sortValue[obj2.location] || 2;

            if (obj1val < obj2val)
                return -1;

            if (obj1val > obj2val)
                return 1;

            return 0;
        });

        self.onYouTubeIframeAPIReady();
        self.onSoundCloudIframeAPIReady();
        
        if (!UI.getSetting("ispreview")) {
            self.stopAllMedia();
        }
    };
}
;
function addEventSimple(obj,evt,fn) {
    if (obj.addEventListener)
        obj.addEventListener(evt,fn,false);
    else if (obj.attachEvent)
        obj.attachEvent('on'+evt,fn);
}

function removeEventSimple(obj,evt,fn) {
    if (obj.removeEventListener)
        obj.removeEventListener(evt,fn,false);
    else if (obj.detachEvent)
        obj.detachEvent('on'+evt,fn);
}

dragDrop = {
    keySpeed: 1, // pixels per keypress event
    prevY: undefined,
    initialMouseX: undefined,
    initialMouseY: undefined,
    startX: undefined,
    startY: undefined,
    dXKeys: undefined,
    dYKeys: undefined,
    draggedObject: undefined,
    groupDragging: false,
    releasesCountAfterDrag: 0,
    initElement: function(element) {
        if (typeof element == 'string')
            element = document.getElementById(element);
        //initializing element events
        element.onmousedown = dragDrop.startDragMouse;
        element.onclick = dragDrop.startDragKeys;

        //add drag class to draggable elements from the start
        $(element).addClass('drag');
    },
    startDragMouse: function (e) {
    	console.log("startDragMouse");
        dragDrop.prevY = e.clientY;
    	if ($(this).data('focusManagerInstance') != null) {
    		e.preventDefault();
    		e.stopPropagation();
    		return false;
    	}

    	if (grouping.ctrlIsPressed) {
    	    e.preventDefault();
    	    e.stopPropagation();
    	    return false;
    	}

    	e.stopPropagation();

        //if is not edited(e.g. headertext component)
        if ($(this).attr('contenteditable') != 'true') {
            dragDrop.startDrag(this);
            var evt = e || window.event;
            dragDrop.initialMouseX = evt.clientX;
            dragDrop.initialMouseY = evt.clientY;
            addEventSimple(document, 'mousemove', dragDrop.dragMouse);
            addEventSimple(document, 'mouseup', dragDrop.releaseElement);
            return false;
        }
    },
    groupingDragKeys: function () {
        if (Grouping.isActive()) {
            ko.utils.arrayForEach(grouping.selectedItems(), function (item) {

                $(item).data('startx', item.style.left);
                $(item).data('starty', item.style.top);

                if ($(item).data('focusManagerInstance') != null || $(item).attr('contenteditable') == 'true') {
                    e.preventDefault();
                    e.stopPropagation();
                    return false;
                }


            });

            addEventSimple(document, 'keydown', dragDrop.dragKeys);
            addEventSimple(document, 'keypress', dragDrop.switchKeyEvents);
            dragDrop.dXKeys = dragDrop.dYKeys = 0;
        }
    },
    startDragKeys: function () {

    	if ($(this).data('focusManagerInstance') != null) {
    		e.preventDefault();
    		e.stopPropagation();
    		return false;
    	}

        if ($(this).attr('contenteditable') != 'true') {
            dragDrop.startDrag(this);
            dragDrop.dXKeys = dragDrop.dYKeys = 0;
            addEventSimple(document, 'keydown', dragDrop.dragKeys);
            addEventSimple(document, 'keypress', dragDrop.switchKeyEvents);
            this.blur();
            return false;
        }
    },
    startDrag: function (obj) {
		
    	for (name in CKEDITOR.instances) {
    		if (CKEDITOR.instances.hasOwnProperty(name)) {
    			var element = CKEDITOR.document.getById(name);
    			if (element && element.$.isContentEditable && element.$.contentEditable == "true") {
    				var focusManager = $(element.$).data("focusManagerInstance");
    				if (focusManager != null) {
                        if (obj.id !== name) {
                            focusManager.blur();
                        } else {
                            return false;
                        }
				    }
    			}
    		}
    	}
		
        if ($('#' + obj.id).attr('contenteditable') != 'true') {
            console.log("startDrag");

            if (!dragDrop.draggedObject && !$('#' + obj.id).data('selected'))
            {
                UI.removeEditor();
            }
             

            if (dragDrop.draggedObject && dragDrop.draggedObject.id !== obj.id) {
                dragDrop.releaseElement();
                UI.removeEditor();
            }
               
            dragDrop.startX = obj.offsetLeft;
            dragDrop.startY = obj.offsetTop;
            dragDrop.draggedObject = obj;
            if (grouping.section == null) {
                var component = UI.siteComponentRepository.lookupData({ id: $(obj).getId() });
                var stretchToFullWidth = StretcherFactory.getCurrentStretchStatus(component);
                if (!stretchToFullWidth) {
                }
                $(obj).data('startx', obj.style.left);

                $(obj).data('starty', obj.style.top);
            }
        }

        if (Grouping.isActive()) {
            ko.utils.arrayForEach(grouping.selectedItems(), function (item) {
                $(item).data('startx', item.style.left);
                $(item).data('starty', item.style.top);
            });
        }
    },
    dragMouse: function (e) {
        dragDrop.releasesCountAfterDrag = 0;

    	for (name in CKEDITOR.instances) {
    		if (CKEDITOR.instances.hasOwnProperty(name)) {
    			var element = CKEDITOR.document.getById(name);
    			if (element && element.$.isContentEditable && element.$.contentEditable == "true") {
    				var focusManager = $(element.$).data("focusManagerInstance");
    				if (focusManager != null) {

    					focusManager.focus();

    					e.preventDefault();
    					e.stopPropagation();
						
    					return false;
    				}
    			}
    		}
    	}

        PopoverHelper.hidePopovers();
        ColorPickerHelper.hide();
        var evt = e || window.event;
        var dX = evt.clientX - dragDrop.initialMouseX;
        var dY = evt.clientY - dragDrop.initialMouseY;

        console.log("dragMouse " + dX + " " + dY);

        var dragDistance = Math.sqrt(dX * dX + dY * dY);


        console.log("dragMouse " + dX + " " + dY + " (" + dragDistance + ")");

        if (Grouping.isActive() && dragDistance > 1) {
            dragDrop.groupDragging = true;
        }
        else {
            dragDrop.groupDragging = false;
        }

        //calculation of permissible limits
 
        var dragElementArray = new Array();
        if (Grouping.isActive()) {
            ko.utils.arrayForEach(grouping.selectedItems(), function (item) {
                dragElementArray.push(item);
            });
        }
        else {
            dragElementArray.push(dragDrop.draggedObject);
        }
        var basicParentContainer = Resizer.findBasicContainer(dragDrop.draggedObject);
        var basicParentContainerBottomLine = 0;
        var basicParentContainerRightLine = 0;
        var additionalOffset = Resizer.additionalOffsetFromParent(dragDrop.draggedObject);
        if (basicParentContainer != null) {
            basicParentContainerBottomLine = basicParentContainer.offsetHeight;
            basicParentContainerRightLine = basicParentContainer.offsetWidth;
        }

        for (var i = dragElementArray.length - 1; i >= 0; i--) {
            var starty = parseInt($(dragElementArray[i]).data('starty'));
            var startx = parseInt($(dragElementArray[i]).data('startx'));

            var draggedObjBottomLine = starty + dY + dragElementArray[i].offsetHeight;
            var draggedObjRightLine = startx + dX + dragElementArray[i].offsetWidth;

            if (draggedObjBottomLine + additionalOffset > basicParentContainerBottomLine) {
                dY = dY - draggedObjBottomLine + basicParentContainerBottomLine - additionalOffset;
            }
            if (starty + dY + additionalOffset < 0) {
                dY = -(starty + additionalOffset);
            }

            /* limited drag in mobile editor
            if (!UI.getDevice().isDesktop()) {
                if (draggedObjRightLine + additionalOffset > basicParentContainerRightLine) {
                    dX = dX - draggedObjRightLine + basicParentContainerRightLine - additionalOffset;
                }

                if (startx + dX < 0) {
                    dX = -(startx);
                }
            }
            */

            //calculation of permissible limits for custom-form
            var closestParent = dragElementArray[i].offsetParent;
            var closestParentContainerBottomLine = 0;
            var closestParentContainerRightLine = 0;
            var closestParentBorderWidth = 0;
            if (closestParent != null) {
                closestParentContainerBottomLine = closestParent.offsetHeight;
                closestParentContainerRightLine = closestParent.offsetWidth;
                closestParentBorderWidth = parseInt($(closestParent).css('border-top-width')) * 2;
            }

            if ($(closestParent).hasClass('std-form')) {
                if (draggedObjBottomLine > closestParentContainerBottomLine - closestParentBorderWidth) {
                    dY = closestParentContainerBottomLine - dragElementArray[i].offsetHeight - starty - closestParentBorderWidth;
                }
                if (starty + dY < 0) {
                    dY = -(starty);
                }

                if (draggedObjRightLine > closestParentContainerRightLine - closestParentBorderWidth) {
                    dX = closestParentContainerRightLine - dragElementArray[i].offsetWidth - startx - closestParentBorderWidth;
                }

                if (startx + dX < 0) {
                    dX = -(startx);
                }
            }
            //end calculation of permissible limits for custom-form
        }

        //end calculation of permissible limits

        //calculate step to move section
        if (dragDrop.prevY !== null) {
            var step = evt.clientY - dragDrop.prevY;
        }
        dragDrop.prevY = evt.clientY;

        if (Grouping.isActive())
        {
            ko.utils.arrayForEach(grouping.selectedItems(), function (item) {
                dragDrop.setElementPosition(item, dX, dY, parseInt($(item).data('startx')), parseInt($(item).data('starty')));
            });
            Grouping.wrap();

            //move components UP or DOWN on section move
            if (grouping.section != null) {
                var direction = '';
                if (step < 0) direction = 'up';
                if (step > 0) direction = 'down';
                grouping.section.topPosition += step;
                grouping.section.bottomLine += step;
                grouping.section.height = grouping.section.bottomLine - grouping.section.topPosition;
                var currentPageId = UI.pager.getCurrentPageId();
                var currentPage = UI.siteComponentRepository.lookupData({ id: currentPageId });
                currentPage.children.forEach(component => {
                    var selectesItems = grouping.selectedItems();
                    var isComponentInGroup = selectesItems.find(item => item.id == component.id);
                    if (!isComponentInGroup) {
                        var componentTopValue = parseInt(component.getProperty(TOP).value);
                        var componentHeightValue = parseInt(component.getProperty(HEIGHT).value);
                        var componentBottomLine = componentTopValue + componentHeightValue;
                        var componentNewTopPosition = componentTopValue;
                        if (grouping.section.bottomLine > componentTopValue && grouping.section.topPosition < componentBottomLine) {
                            if (direction == 'up') {                            
                                componentNewTopPosition = componentTopValue + grouping.section.height + componentHeightValue;
                            }                        
                            if (direction == 'down') {                            
                                componentNewTopPosition = componentTopValue - grouping.section.height - componentHeightValue;
                                if (componentNewTopPosition < 0) componentNewTopPosition = 0;
                            }
                            component.setProperty(TOP, componentNewTopPosition);
                            $(component.getUISelector()).css('top', componentNewTopPosition + 'px');
                        }
                    }
                })
            }
            //end move components UP or DOWN on section move
        }
        else
        {
            dragDrop.setPosition(dX, dY);
            //looking for appropriate element to dock
            if (UI.getDevice().isDesktop()) {
                $(dragDrop.draggedObject).findUnderlyingDockableElement();
            }


            var dockTo = $('#' + $(UI.getConfigurationValue(DOCK_WRAPPER)).data('for'));

            if (dockTo.selector != "#undefined" && dockTo.hasClass("footer") && $(dragDrop.draggedObject.parentElement).hasClass("footer")) {
                Resizer.resizeFooterContainer(dragDrop.draggedObject, dockTo[0]);
            }

            dragDrop.drawRulers(dragDrop.draggedObject, 4.0, dX, dY, true, false);
        }

        if (Grouping.isActive() && UI.RulerGuides != undefined) {
            UI.RulerGuides.checkAndSnapToGuides();
        }

        return false;
    },
    dragKeys: function (e) {
        var evt = e || window.event;
        var key = evt.keyCode;
        if (dragDrop.draggedObject == null && !Grouping.isActive()) {
            console.log("dragDrop.dragKeys -> draggedObject == null");
            return false;
        }
        //console.log("dragDrop.dragKeys");
        var dragElementArray = new Array();
        var minLeft = null;
        var minTop = null;
        var maxLeft = null;
        var maxTop = null;

        if (Grouping.isActive()) {
            ko.utils.arrayForEach(grouping.selectedItems(), function (item) {
                dragElementArray.push(item);
            });
        } else {
            dragElementArray.push(dragDrop.draggedObject);
        }

        switch (key) {
            case 37: // left
            case 63234:
                dragDrop.dXKeys -= dragDrop.keySpeed;
                PopoverHelper.hidePopovers();
                ColorPickerHelper.hide();
                for (var i = dragElementArray.length - 1; i >= 0; i--) {
                    var item = dragElementArray[i];
                    var startx = parseInt($(item).data('startx'));
                    if (startx + dragDrop.dXKeys < 0) {
                        dragDrop.dXKeys = -(startx);
                        minLeft = dragDrop.dXKeys;
                    }
                    if (minLeft != null && minLeft > dragDrop.dXKeys) {
                        dragDrop.dXKeys = minLeft;
                    }
                }
                break;
            case 38: // up
            case 63232:
                dragDrop.dYKeys -= dragDrop.keySpeed;
                PopoverHelper.hidePopovers();
                ColorPickerHelper.hide();
                for (var i = dragElementArray.length - 1; i >= 0; i--) {
                    var item = dragElementArray[i];
                    var starty = parseInt($(item).data('starty'));
                    var startx = parseInt($(item).data('startx'));

                    if (starty + dragDrop.dYKeys < 0) {
                        dragDrop.dYKeys = -(starty);
                    }
                    if (minTop != null && minTop > dragDrop.dYKeys) {
                        dragDrop.dYKeys = minTop;
                    }
                }
                break;
            case 39: // right
            case 63235:
                dragDrop.dXKeys += dragDrop.keySpeed;
                PopoverHelper.hidePopovers();
                ColorPickerHelper.hide();
                for (var i = dragElementArray.length - 1; i >= 0; i--) {
                    var item = dragElementArray[i];
                    var parentObj = item.offsetParent;
                    var parentObjBottomLine = parentObj.offsetHeight;
                    var parentObjRightLine = parentObj.offsetWidth;
                    var starty = parseInt($(item).data('starty'));
                    var startx = parseInt($(item).data('startx'));

                    var draggedObjRightLine = startx + dragDrop.dXKeys + item.offsetWidth;
                    if (draggedObjRightLine > parentObjRightLine) {
                        if (item.offsetWidth <= parentObj.offsetWidth) {
                            dragDrop.dXKeys -= (draggedObjRightLine - parentObjRightLine);
                        }
                        else {
                            dragDrop.dXKeys -= dragDrop.keySpeed;
                        }
                    }
                    if (maxLeft != null && maxLeft < dragDrop.dXKeys) {
                        dragDrop.dXKeys = maxLeft;
                    }
                }
                break;
            case 40: // down
            case 63233:
                dragDrop.dYKeys += dragDrop.keySpeed;
                PopoverHelper.hidePopovers();
                ColorPickerHelper.hide();

                for (var i = dragElementArray.length - 1; i >= 0; i--) {
                    var item = dragElementArray[i];
                    var parentObj = item.offsetParent;
                    var parentObjBottomLine = parentObj.offsetHeight;
                    var parentObjRightLine = parentObj.offsetWidth;
                    var starty = parseInt($(item).data('starty'));
                    var startx = parseInt($(item).data('startx'));

                    var draggedObjBottomLine = starty + dragDrop.dYKeys + item.offsetHeight;
                    if (draggedObjBottomLine > parentObjBottomLine) {
                        if (item.offsetHeight <= parentObj.offsetHeight) {
                            dragDrop.dYKeys -= (draggedObjBottomLine - parentObjBottomLine);
                        }
                        else {
                            dragDrop.dYKeys -= dragDrop.keySpeed;
                        }
                    }
                    if (maxTop != null && maxTop < dragDrop.dYKeys) {
                        dragDrop.dYKeys = maxTop;
                    }
                }              
                break;
            case 27: // escape
                dragDrop.releaseElement();
                PopoverHelper.hidePopovers();
                ColorPickerHelper.hide();
                return false;
            default:
                return true;
        }

        for (var i = dragElementArray.length - 1; i >= 0; i--) {
            var item = dragElementArray[i];
            var starty = parseInt($(item).data('starty'));
            var startx = parseInt($(item).data('startx'));
            dragDrop.setElementPositionAndWrap(dragElementArray[i], dragDrop.dXKeys, dragDrop.dYKeys, startx, starty);
        }

        if (evt.preventDefault)
            evt.preventDefault();
        return false;
    },
    drawRulers: function (element, tolerance, dragDx, dragDy, changeXY, isResize, resizeSide, tstartX, tstartY) {
        var inst = $(".std-component");
        inst.push(getBasicContainer(element));
        var d = tolerance;
   
        $(".objectx").css({ "display": "none" });
        $(".objecty").css({ "display": "none" });

        var absoluteElementOffset = cumulativeOffset(element);

        var x1 = absoluteElementOffset.left,
            x2 = x1 + element.offsetWidth,
            y1 = absoluteElementOffset.top,
            y2 = y1 + element.offsetHeight,
            xc = (x1 + x2) / 2,
            yc = (y1 + y2) / 2;

        var xx = calcSecondAxis(element, inst, d, dragDx, true, isResize, resizeSide, tstartX, tstartY);
        var yy = calcSecondAxis(element, inst, d, dragDy, false, isResize, resizeSide, tstartX, tstartY);
         
        if (changeXY) {
            dragDrop.setPosition(xx, yy);
        }

        draw(resizeSide);

        function draw(resizeSide) {
            for (var i = inst.length - 1; i >= 0; i--) {
                if (!inst[i] || inst[i].id == element.id || $(inst[i]).hasClass(STD_COMPONENT_FIXED)) {
                    continue;
                }

                var absoluteInstOffset = cumulativeOffset(inst[i]);

                var l = absoluteInstOffset.left,
                    r = l + inst[i].offsetWidth,
                    t = absoluteInstOffset.top,
                    b = t + inst[i].offsetHeight,
                    hc = (l + r) / 2,
                    vc = (t + b) / 2;

                var ls = Math.abs(l - x2) <= d;
                var rs = Math.abs(r - x1) <= d;
                var ts = Math.abs(t - y2) <= d;
                var bs = Math.abs(b - y1) <= d;
                var hs = Math.abs(hc - xc) <= d;
                var vs = Math.abs(vc - yc) <= d;

                var ll = Math.abs(l - x1) <= d;
                var rr = Math.abs(r - x2) <= d;

                var tt = Math.abs(t - y1) <= d;
                var bb = Math.abs(b - y2) <= d;

                switch (resizeSide) {
                    case 't':
                        bb = false;
                        ts = false;
                        break;
                    case 'b':
                        bs = false;
                        tt = false;
                        break;
                    case 'l':
                        ls = false;
                        rr = false;
                        break;
                    case 'r':
                        rs = false;
                        ll = false;
                        break;
                    case 'tl':
                        ls = false;
                        ts = false;
                        rr = false;
                        bb = false;
                        break;
                    case 'tr':
                        bb = false;
                        ts = false;
                        rs = false;
                        ll = false;
                        break;
                    case 'bl':
                        bs = false;
                        tt = false;
                        ls = false;
                        rr = false;
                        break;
                    case 'br':
                        bs = false;
                        tt = false;
                        rs = false;
                        ll = false;
                        break;
                    default:
                        break;
                }

                if (element.parentElement.id != inst[i].parentElement.id) {
                    hs = false;
                    vs = false;
                }

                if (differentBasicContainers(element, inst[i])) {
                    ts = false;
                    bs = false;
                    tt = false;
                    bb = false;
                }

                var wrapperHeight = $('.siteeditor')[0].scrollHeight + 'px';
                if (ls) {
                    $(".objectx").css({ "left": l, "display": "block", "height": wrapperHeight });
                }
                if (rs) {
                    $(".objectx").css({ "left": r, "display": "block", "height": wrapperHeight });
                }

                if (ts) {
                    $(".objecty").css({ "top": t, "display": "block" });
                }
                if (bs) {
                    $(".objecty").css({ "top": b, "display": "block" });
                }
         
                if (ll) {
                    $(".objectx").css({ "left": l, "display": "block", "height": wrapperHeight });
                }
                if (rr) {
                    $(".objectx").css({ "left": r, "display": "block", "height": wrapperHeight });
                }
                if (hs && !isResize) {
                    $(".objectx").css({ "left": hc, "display": "block", "height": wrapperHeight });
                }
          
                if (tt) {
                    $(".objecty").css({ "top": t, "display": "block" });
                }
                if (bb) {
                    $(".objecty").css({ "top": b, "display": "block" });
                }
                if (vs && !isResize) {
                    $(".objecty").css({ "top": vc, "display": "block" });
                }
            };
        }

        function cumulativeOffset(element) {
            var top = 0, left = 0;
            do {
                if ($(element).hasClass('ruler-guide')) {
                    top += ($(element).hasClass('vertical-guide') ? 0 : element.offsetParent.offsetTop) || 0;
                    left += ($(element).hasClass('vertical-guide') ? element.offsetParent.offsetLeft : 0) || 0;
                    element = element.offsetParent.offsetParent;
                }
                else {
                    top += element.offsetTop || 0;
                    left += element.offsetLeft || 0;
                    element = element.offsetParent;
                }
            } while (element);

            return {
                top: top,
                left: left
            };
        };
        //getRulerGuideUnnecessaryHeightInCurrentArea
        function cumulativeOffsetForStd(element) {
            var top = 0, left = 0;
            do {
                if (!$(element).hasClass('std-component')) {
                    break;
                }
                if ($(element).hasClass('ruler-guide')) {
                    top += ($(element).hasClass('vertical-guide') ? 0 : element.offsetParent.offsetTop) || 0;
                    left += ($(element).hasClass('vertical-guide') ? element.offsetParent.offsetLeft : 0) || 0;
                    element = element.offsetParent.offsetParent;
                }
                else {
                    top += element.offsetTop || 0;
                    left += element.offsetLeft || 0;
                    element = element.offsetParent;
                }
            } while (element);

            return {
                top: top,
                left: left
            };
        };

        function getBasicContainer(element) {
            do {
                if (!$(element).hasClass('std-component')) {
                    return element;
                }
                element = element.offsetParent;
            } while (element);
        };

        function isChildElement(element, elementForCheck) {

            do {
                if (elementForCheck.id == element.id) {
                    return true;
                }
                elementForCheck = elementForCheck.offsetParent;
            } while (elementForCheck);

            return false;
        }

        function differentBasicContainers(elem1, elem2) {
            var firstBasicContainer = "";

            while (elem1) {
                elem1 = elem1.offsetParent;
                if (!$(elem1).hasClass('std-component')) {
                    firstBasicContainer = $(elem1).attr('class');
                    break;
                }
            }
            while (elem2) {
                if ($(elem2).hasClass('ruler-guide')) {
                    if (aligning.getCurrentArea() == undefined) {
                        return true;
                        break;
                    }
                    elem2 = aligning.getCurrentArea()[0];
                }
                else {
                    elem2 = elem2.offsetParent;
                }
                if (!$(elem2).hasClass('std-component')) {
                    if (firstBasicContainer == $(elem2).attr('class')) {
                        return false;
                    }
                    break;
                }
            }
            return true;
        }

        function calcSecondAxis(element, inst, tolerance, defaultValue, isAxisX, isResize, resizeSide, tstartX, tstartY) {
            var d = tolerance;
            var absoluteElementOffset = cumulativeOffset(element);

            var startX = dragDrop.startX;
            var startY = dragDrop.startY;

            //if tstartX is undefined then we need to consider dragDrop.dXKeys for LS RR and dragDrop.dYKeys TS BB
            var keysResizeX = dragDrop.dXKeys;
            var keysResizeY = dragDrop.dYKeys;

            if (tstartX != undefined) {
                startX = tstartX;
                keysResizeX = 0;
            }
            if (tstartY != undefined) {
                startY = tstartY;
                keysResizeY = 0;
            }

            if (!isResize) {
                keysResizeX = 0;
                keysResizeY = 0;
            }

            var x1 = absoluteElementOffset.left,
                x2 = x1 + element.offsetWidth,
                y1 = absoluteElementOffset.top,
                y2 = y1 + element.offsetHeight,
                xc = (x1 + x2) / 2,
                yc = (y1 + y2) / 2;

            for (var i = inst.length - 1; i >= 0; i--) {
                if (!inst[i] || inst[i].id == element.id || isChildElement(element, inst[i]) || $(inst[i]).hasClass(STD_COMPONENT_FIXED)) {
                    continue;
                }

                var absoluteInstOffset = cumulativeOffset(inst[i]);

                var l = absoluteInstOffset.left,
                    r = l + inst[i].offsetWidth,
                    t = absoluteInstOffset.top,
                    b = t + inst[i].offsetHeight,
                    hc = (l + r) / 2,
                    vc = (t + b) / 2;

                var ls = Math.abs(l - x2) <= d;
                var rs = Math.abs(r - x1) <= d;
                var ts = Math.abs(t - y2) <= d;
                var bs = Math.abs(b - y1) <= d;
                var hs = Math.abs(hc - xc) <= d;
                var vs = Math.abs(vc - yc) <= d;
                var ll = Math.abs(l - x1) <= d;
                var rr = Math.abs(r - x2) <= d;
                var tt = Math.abs(t - y1) <= d;
                var bb = Math.abs(b - y2) <= d;

                //blocking illegal sides
                switch (resizeSide) {
                    case 't':
                        bb = false;
                        ts = false;
                        break;
                    case 'b':
                        bs = false;
                        tt = false;
                        break;
                    case 'l':
                        ls = false;
                        rr = false;
                        break;
                    case 'r':
                        rs = false;
                        ll = false;
                        break;
                    case 'tl':
                        ls = false;
                        ts = false;
                        rr = false;
                        bb = false;
                        break;
                    case 'tr':
                        bb = false;
                        ts = false;
                        rs = false;
                        ll = false;
                        break;
                    case 'bl':
                        bs = false;
                        tt = false;
                        ls = false;
                        rr = false;
                        break;
                    case 'br':
                        bs = false;
                        tt = false;
                        rs = false;
                        ll = false;
                        break;
                    default:
                        break;
                }

                if (element.parentElement.id != inst[i].parentElement.id) {
                    hs = false;
                    vs = false;
                }

                if (differentBasicContainers(element, inst[i])) {
                    ts = false;
                    bs = false;
                    tt = false;
                    bb = false;
                }

                var additional_margin = 0;

                if (true) {
                    var offsetElementToStd = cumulativeOffsetForStd(element.offsetParent);
                    if (isAxisX) {
                        additional_margin -= offsetElementToStd.left;
                    }
                    else {
                        additional_margin -= ($(inst[i]).hasClass('horizontal-guide') && UI.RulerGuides != undefined ? UI.RulerGuides.getRulerGuideUnnecessaryHeightInCurrentArea() : offsetElementToStd.top);
                    }

                    if ($(element.parentElement).hasClass('footer')) {
                        additional_margin = 0;
                    }
                }

                var offsetInstToStd = cumulativeOffsetForStd(inst[i]);

                var instOffsetLeft = offsetInstToStd.left;
                var instOffsetTop = offsetInstToStd.top;


                 
                if (isAxisX) {
                    if (ls) {
                        return instOffsetLeft - element.offsetWidth - startX + additional_margin - keysResizeX;
                    }
                    if (rs) {
                        return instOffsetLeft + inst[i].offsetWidth - startX + additional_margin;
                    }
                    if (ll) {
                        return instOffsetLeft - startX + additional_margin;
                    }
                    if (rr) {
                        return instOffsetLeft + inst[i].offsetWidth - element.offsetWidth - startX + additional_margin - keysResizeX;
                    }
                    if (hs && !isResize) {
                        return instOffsetLeft + inst[i].offsetWidth / 2 - element.offsetWidth / 2 - startX + additional_margin;
                    }
                }
                else {

                    if (ts) {
                        return instOffsetTop - element.offsetHeight - startY + additional_margin - keysResizeY;
                    }
                    if (bs) {
                        return instOffsetTop + inst[i].offsetHeight - startY + additional_margin;
                    }
                    if (tt) {
                        return instOffsetTop - startY + additional_margin;
                    }
                    if (bb) {
                        return instOffsetTop + inst[i].offsetHeight - element.offsetHeight - startY + additional_margin - keysResizeY;
                    }
                    if (vs && !isResize) {
                        return instOffsetTop + inst[i].offsetHeight / 2 - element.offsetHeight / 2 - startY + additional_margin;
                    }
                }//end if-else IsAxisX
            }//end for
            return defaultValue;
        };

        return {
            Top: yy,
            Left: xx
        }
    },
    setPosition: function (dx, dy) {
        var component = UI.siteComponentRepository.lookupData({ id: $(dragDrop.draggedObject).getId() });
        var stretchToFullWidth = StretcherFactory.getCurrentStretchStatus(component);
        if (!stretchToFullWidth) {
            dragDrop.draggedObject.style.left = dragDrop.startX + dx + 'px';
        }
        dragDrop.draggedObject.style.top = dragDrop.startY + dy + 'px';
        var draggedObject = $(dragDrop.draggedObject);
        if (draggedObject.hasClass('std-component-fixed')) {            
            $(UI.getConfigurationValue(SELECT_WRAPPER)).css(ViewerFactory.getFixedLocationPosition(component, true));
        } else {
            $(UI.getConfigurationValue(SELECT_WRAPPER)).css({
                top: draggedObject.offset().top - 1,
                left: draggedObject.offset().left - 1
            });
        }
    },
    setElementPosition: function (element, dx, dy, startx, starty) {
        var component = UI.siteComponentRepository.lookupData({ id: $(element).getId() });
        var stretchToFullWidth = StretcherFactory.getCurrentStretchStatus(component);
        if (!stretchToFullWidth) {
            element.style.left = startx + dx + 'px';
        }
        element.style.top = starty + dy + 'px';
    },
    setElementPositionAndWrap: function (element, dx, dy, startx, starty) {
        var component = UI.siteComponentRepository.lookupData({ id: $(element).getId() });
        var stretchToFullWidth = StretcherFactory.getCurrentStretchStatus(component);
        if (!stretchToFullWidth) {
            element.style.left = startx + dx + 'px';
        }
        element.style.top = starty + dy + 'px';
        var wrappedElement = $(element);
        if (wrappedElement.hasClass('std-component-fixed')) {            
            $(UI.getConfigurationValue(SELECT_WRAPPER)).css(ViewerFactory.getFixedLocationPosition(component, true));
        } else {
            $(UI.getConfigurationValue(SELECT_WRAPPER)).css({
                top: wrappedElement.offset().top - 1,
                left: wrappedElement.offset().left - 1
            });
        }

            
        Grouping.wrap();
    },
    switchKeyEvents: function() {
        // for Opera and Safari 1.3
        removeEventSimple(document, 'keydown', dragDrop.dragKeys);
        removeEventSimple(document, 'keypress', dragDrop.switchKeyEvents);
        addEventSimple(document, 'keypress', dragDrop.dragKeys);
    },
    releaseElement: function (e) {

    	if ($(this).data('focusManagerInstance') != null) {
    		e.preventDefault();
    		e.stopPropagation();
    		return false;
    	}

    	dragDrop.releasesCountAfterDrag += 1;
        
    	if (dragDrop.releasesCountAfterDrag > 1) {
    	    dragDrop.groupDragging = false;
    	}

    	removeEventSimple(document, 'mousemove', dragDrop.dragMouse);
    	removeEventSimple(document, 'mouseup', dragDrop.releaseElement);
    	removeEventSimple(document, 'keypress', dragDrop.dragKeys);
    	removeEventSimple(document, 'keypress', dragDrop.switchKeyEvents);
    	removeEventSimple(document, 'keydown', dragDrop.dragKeys);
        console.log("releaseElement");

    	if (dragDrop.draggedObject != null) {
    	    var draggedComponent = UI.siteComponentRepository.lookupData({ id: dragDrop.draggedObject.id });

    	    if (StretcherFactory.isPined(draggedComponent))
    	    {
    	        dragDrop.draggedObject = null;
    	    }
    	}

    	if (Grouping.isActive()) {
    	    Grouping.wrap();
    	    dragDrop.applyGroupChanges();
    	    Resizer.groupBind();
    	}
    	else {

    	    if (dragDrop.draggedObject != null) {
    	        var isDocking = Dock.processElementDocking(dragDrop.draggedObject);

    	        if (!isDocking) {
    	            dragDrop.applyModelChanges();
    	        } else {
    	            var id = $(e.target).getId();
    	            //var component = UI.siteComponentRepository.lookupData({ id: $(dragDrop.draggedObject).getId() });
    	            if(component != null){
    	                var component = UI.siteComponentRepository.lookupData({ id: id });
    	                var element = $('#' + component.id);
    	                element.highlightSelectedElement(component);
    	            }

    	        }
    	    }
        }

    	dragDrop.draggedObject = null;
    	clipBoard.selectedItem(null);

    	$(".objectx").css({ "display": "none" });
    	$(".objecty").css({ "display": "none" });
    },
    applyGroupChanges: function () {

        var positionsIsDifferent = false;

        function ResizeMenuArea() {
            Resizer.recalculateSizeFooterContainer($('.footer')[0]);
        };

        var valuesArray = new Array();
        ko.utils.arrayForEach(grouping.selectedItems(), function (item) {
            var component = Grouping.getComponent(item.id);
            if (component != null) {
                var oldvalueleft = _.clone(component.getProperty(LEFT).value);
                var oldvaluetop = _.clone(component.getProperty(TOP).value);
                var newvalueleft = _.clone(item.style.left);
                var newvaluetop = _.clone(item.style.top);

                if (oldvalueleft != newvalueleft || oldvaluetop != newvaluetop) {
                    positionsIsDifferent = true;
                }

                valuesArray[item.id] = { oldleft: oldvalueleft, oldtop: oldvaluetop, newleft: newvalueleft, newtop: newvaluetop };
            }
            else{
                console.log("DragDrop.js->applyGroupChanges: component not found!");
            }
        });


       
        var undoredo = function (array, setNewValues) {

            Object.keys(array).forEach(function (key, index) {

                var curItem = this[key];

                    var valueTop = curItem.newtop;
                    var valueLeft = curItem.newleft;

                    if (!setNewValues) {
                        valueTop = curItem.oldtop;
                        valueLeft = curItem.oldleft;
                    }
                    var curitemComponent = Grouping.getComponent(key);
                    curitemComponent.setProperty(TOP, valueTop);
                    curitemComponent.setProperty(LEFT, valueLeft);
                    var element = $('#' + key);
                    element.css(TOP, valueTop);
                    element.css(LEFT, valueLeft);
                    console.log("APPLY GROUP VALUES: new>" + setNewValues + ", top: " + valueTop + ", left: " + valueLeft);
                    Grouping.wrap();

                
            }, array);
        }
        
        if (positionsIsDifferent) {

            undoredo(valuesArray, true);
            ResizeMenuArea();


            UI.undoManagerAdd(
            {
                undo: function () {
                    undoredo(valuesArray, false);
                    ResizeMenuArea();
                },
                redo: function () {
                    undoredo(valuesArray, true);
                    ResizeMenuArea();
                }
            });
        }
    },
    applyModelChanges: function () {
        //applying model changes
        
        if (defined(dragDrop.draggedObject) && $(dragDrop.draggedObject).length > 0) {
            var component = UI.siteComponentRepository.lookupData({ id: $(dragDrop.draggedObject).getId() });

            //var dockTo = $('#' + $(UI.getConfigurationValue(DOCK_WRAPPER)).data('for'));
            //var el = $('#' + component.id);
            //var previousContainer = el.parent();

            if (component != null) {
                
                var oldvalueleft = _.clone(component.getProperty(LEFT).value);
                var oldvaluetop = _.clone(component.getProperty(TOP).value);
                var newvalueleft = _.clone(dragDrop.draggedObject.style.left);
                var newvaluetop = _.clone(dragDrop.draggedObject.style.top);
               
                if (oldvalueleft != newvalueleft || oldvaluetop != newvaluetop)
                {
                    var undoredo = function (left, top) {
                    
                        component.setProperty(LEFT, left);
                        component.setProperty(TOP, top);

                        var element = $('#' + component.id);
                        element.css(LEFT, left);
                        element.css(TOP, top);
                    }

                    function ResizeMenuArea(component) {
                        var element = $('#' + component.id);
                        Resizer.recalculateSizeFooterContainer($('.footer')[0]);
                    };

                    undoredo(newvalueleft, newvaluetop);
                    ResizeMenuArea(component);
                    UI.undoManagerAdd(
                        {
                            undo: function () {
                                undoredo(oldvalueleft, oldvaluetop);
                                ResizeMenuArea(component);
                            },
                            redo: function () {
                                undoredo(newvalueleft, newvaluetop);
                                ResizeMenuArea(component);
                            }
                        });
                }
            }
        }
    }
}


;
var SiteSettings = function () {
    var self = this;

    self.showSettings = function () {
        Helpers.validationEngineInit("#" + SITE_SETTINGS_TEMPLATE);
        var bodyComponent = UI.getBody();

        var googleVerificationCode = _.clone(bodyComponent.getProperty(GOOGLE_VERIFICATION_CODE) ? bodyComponent.getProperty(GOOGLE_VERIFICATION_CODE).value : '');
        var bingVerificationCode = _.clone(bodyComponent.getProperty(BING_VERIFICATION_CODE) ? bodyComponent.getProperty(BING_VERIFICATION_CODE).value : '');
        var faviconImage = _.clone(bodyComponent.getProperty("favicon") ? Helpers.convertToUniversalUrl(bodyComponent.getProperty("favicon").value) : '');

        var metaTitle = _.clone(bodyComponent.getProperty("meta-title").value);
        var metaKeywords = _.clone(bodyComponent.getProperty("meta-keywords").value);
        var metaDescription = _.clone(bodyComponent.getProperty("meta-description").value);

        var metaFacebookTitle = _.clone(bodyComponent.getProperty("meta-facebook-title").value);
        var metaFacebookDescription = _.clone(bodyComponent.getProperty("meta-facebook-description").value);
        var metaFacebookImage = Helpers.convertToUniversalUrl(_.clone(bodyComponent.getProperty("meta-facebook-image").value));

        var googleAnalytics = _.clone(bodyComponent.getProperty(GOOGLE_ANALYTICS).value);
        var searchEngine = _.clone(bodyComponent.getProperty(SEARCHENGINE).value);
        var sitemapxml = _.clone(bodyComponent.getProperty(SITEMAPXML).value);

        $("#google-verification-code").val(googleVerificationCode);
        $("#bing-verification-code").val(bingVerificationCode);
        $(".site-settings-container .favicon-image img").attr('src', faviconImage);
                
        $('.site-settings-container .seo-page-title').val(metaTitle);
        $('.site-settings-container .seo-page-description').val(metaDescription);
        $('.site-settings-container .seo-page-keywords').val(metaKeywords);

        $('.site-settings-container .facebook-title').val(metaFacebookTitle);
        $('.site-settings-container .facebook-description').val(metaFacebookDescription);
        $('.site-settings-container .facebook-image img').attr('src', metaFacebookImage);
        
        H.actionOnOffButtonValueSet($('.site-settings-container .btn-toggle-googleanalytics'), googleAnalytics);
        H.actionOnOffButtonValueSet($('.site-settings-container .btn-toggle-searchengine'), searchEngine);
        H.actionOnOffButtonValueSet($('.site-settings-container .btn-toggle-sitemapxml'), sitemapxml);

        

        self.initFacebookImageUploading();
        self.initFaviconUploading();
        
        self.initSiteAccessibility();
        self.initAutoSave();
        self.initHeaderContent();

        $('.editable-tooltip').popover('destroy');
        ko.applyBindings({}, $('.site-settings-container')[0]);
    }


    self.initAutoSave = function () {
        var bodyComponent = UI.getBody();
        var autoSavePeriod = _.clone(bodyComponent.getProperty(AUTO_SAVE_PERIOD).value);
        $('.site-settings-container .site-auto-save').val(autoSavePeriod);
        Helpers.validationEngineInit("#" + SITE_AUTOSAVE_SETTINGS);
    }

    self.initHeaderContent = function () {
        var bodyComponent = UI.getBody();
        var headerContent = _.clone(bodyComponent.getProperty(SITE_SETTINGS_HEADER_CONTENT).value);
        //var headerContent = _.clone($('#site-settings-header-content').val());
        $('#site-settings-header-content').val(headerContent);
        EditorEventsFactory.attachPlainEventNotComponent('#site-settings-header-content', UI.getBody(), CHANGE, SITE_SETTINGS_HEADER_CONTENT);
       // EditorEventsFactory.attachPlainEvent('#description-input', element, component, CHANGE, DESCRIPTION);
        
    }

    self.initSiteAccessibility = function () {
        var bodyComponent = UI.getBody();
        var isprotected = _.clone(bodyComponent.getProperty(ISPROTECTED).value);
        var protectedword = _.clone(bodyComponent.getProperty(PROTECTEDWORD).value);
        var protectedemail = _.clone(bodyComponent.getProperty(PROTECTEDEMAIL).value);

        //site-accessibility
        $('#site-accessibility-email').val(protectedemail);
        $('#site-accessibility-key').val(protectedword);
        H.actionOnOffButtonValueSet($('.site-settings-container .btn-toggle-isprotected'), isprotected);

        EditorEventsFactory.attachPlainEventNotComponent('#site-accessibility-email', UI.getBody(), CHANGE, PROTECTEDEMAIL);
        EditorEventsFactory.attachPlainEventNotComponent('#site-accessibility-key', UI.getBody(), CHANGE, PROTECTEDWORD);
        Helpers.validationEngineInit("#" + SITE_SETTINGS_ACCESSIBILITY);

        $('#site-accessibility-key').bind("focus", function () {
            $(this).val("");
        });
    }

    self.saveOnOffValue = function (obj) {
        var component = UI.getBody();
        var property = obj.data("property");

        var propertyClass = '';
        if (param == GOOGLE_ANALYTICS)
            propertyClass = "btn-toggle-googleanalytics";
        if (param == SEARCHENGINE)
            propertyClass = "btn-toggle-searchengine";
        if (param == SITEMAP)
            propertyClass = "btn-toggle-sitemapxml";
        if (param == ISPROTECTED)
            propertyClass = "btn-toggle-isprotected";

        var val = _clone(H.actionOnOffButtonValueGet($('.site-settings-container .' + propertyClass)));
        var oldVal = _clone(component.getProperty(param).value);

        var undoredo = function (val) {
            callback = callback || function () { };
            component.setProperty(property, val);
            H.actionOnOffButtonValueGet($('.site-settings-container .' + propertyClass), val);
            callback(component, element, val);
        }
        UI.undoManagerAddSimple(component, property, newvalue, undoredo, false);
    }

    self.saveFavicon = function (faviconImage) {
        var id = $('.body')[0].id;
        var bodyComponent = UI.siteComponentRepository.lookupData({ id: id });
        var cfaviconImage = _.clone(faviconImage);
        var oldfaviconImage = _.clone(bodyComponent.getProperty("favicon") ? bodyComponent.getProperty("favicon").value : '');
        var undoredo = function (faviconImage1) {
            bodyComponent.getProperty("favicon");
            bodyComponent.setProperty("favicon", faviconImage1);
        }
        undoredo(cfaviconImage);
        UI.undoManagerAdd(
                    {
                        undo: function () {
                            undoredo(oldfaviconImage);
                        },
                        redo: function () {
                            undoredo(cfaviconImage);
                        }
                    });        
    }

    self.saveAutoSaveTime = function() {
        if (Helpers.formValidateEngine(SITE_SETTINGS_TEMPLATE)) {
            var id = $('.body')[0].id;
            var bodyComponent = UI.siteComponentRepository.lookupData({ id: id });

            var autoSaveTimePeriod = $('.site-settings-container .site-auto-save').val();

            var cAutoSaveTimePeriod = _.clone(autoSaveTimePeriod);
            var oAutoSaveTimePeriod = _.clone(bodyComponent.getProperty(AUTO_SAVE_PERIOD).value);

            var undoredo = function(period) {
                bodyComponent.getProperty(AUTO_SAVE_PERIOD);
                bodyComponent.setProperty(AUTO_SAVE_PERIOD, period);
                UI.initAutoSave();
            }

            undoredo(cAutoSaveTimePeriod);

            UI.undoManagerAdd(
                {
                    undo: function() {
                        undoredo(oAutoSaveTimePeriod);
                    },
                    redo: function() {
                        undoredo(cAutoSaveTimePeriod);
                    }
                });
            $('.site-popover-custom').remove();
        }
    }

    self.saveSettings = function (params) {
        var id = $('.body')[0].id;

        var googleVerificationCode = $("#google-verification-code").val();
        var bingVerificationCode = $("#bing-verification-code").val();
        if (Helpers.formValidateEngine(SITE_SETTINGS_TEMPLATE))
        {

            var metaTitle = $('.site-settings-container .seo-page-title').val();
            var metaDescription = $('.site-settings-container .seo-page-description').val().replace(/(\r\n|\n|\r)/gm, " ");
            var metaKeywords = $('.site-settings-container .seo-page-keywords').val();

            var metaFacebookTitle = $('.site-settings-container .facebook-title').val();
            var metaFacebookDescription = $('.site-settings-container .facebook-description').val().replace(/(\r\n|\n|\r)/gm, " ");
            var metaFacebookImage = $('.site-settings-container .facebook-image img').attr('src');

            var bodyComponent = UI.siteComponentRepository.lookupData({ id: id });

            var cgoogleVerificationCode = _.clone(googleVerificationCode);
            var cbingVerificationCode = _.clone(bingVerificationCode);

            var cmetaTitle = _.clone(metaTitle);
            var cmetaKeywords = _.clone(metaKeywords);
            var cmetaDescription = _.clone(metaDescription);

            var cmetaFacebookTitle = _.clone(metaFacebookTitle);
            var cmetaFacebookDescription = _.clone(metaFacebookDescription);
            var cmetaFacebookImage = _.clone(metaFacebookImage);

            

            var oldgoogleVerificationCode = _.clone(bodyComponent.getProperty(GOOGLE_VERIFICATION_CODE) ? bodyComponent.getProperty(GOOGLE_VERIFICATION_CODE).value : '');
            var oldbingVerificationCode = _.clone(bodyComponent.getProperty(BING_VERIFICATION_CODE) ? bodyComponent.getProperty(BING_VERIFICATION_CODE).value : '');

            var oldmetaTitle = _.clone(bodyComponent.getProperty("meta-title").value);
            var oldmetaKeywords = _.clone(bodyComponent.getProperty("meta-keywords").value);
            var oldmetaDescription = _.clone(bodyComponent.getProperty("meta-description").value);

            var oldMetaFacebookTitle = _.clone(bodyComponent.getProperty("meta-facebook-title").value);
            var oldMetaFacebookDescription = _.clone(bodyComponent.getProperty("meta-facebook-description").value);
            var oldMetaFacebookImage = _.clone(bodyComponent.getProperty("meta-facebook-image").value);

            //var oldHeaderContent = _.clone(bodyComponent.getProperty(SITE_SETTINGS_HEADER_CONTENT).value);

            var undoredo = function (googleVerificationCode1, bingVerificationCode1, metaTitle1, metaKeywords1, metaDescription1, metaFacebookTitle1, metaFacebookDescription1, metaFacebookImage1, headerContent) {
                bodyComponent.getProperty(GOOGLE_VERIFICATION_CODE);
                bodyComponent.setProperty(GOOGLE_VERIFICATION_CODE, googleVerificationCode1);
                bodyComponent.getProperty(BING_VERIFICATION_CODE);
                bodyComponent.setProperty(BING_VERIFICATION_CODE, bingVerificationCode1);

                bodyComponent.getProperty("meta-title");
                bodyComponent.setProperty("meta-title", metaTitle1);
                bodyComponent.getProperty("meta-keywords");
                bodyComponent.setProperty("meta-keywords", metaKeywords1);
                bodyComponent.getProperty("meta-description");
                bodyComponent.setProperty("meta-description", metaDescription1);
                
                bodyComponent.getProperty('meta-facebook-title');
                bodyComponent.setProperty('meta-facebook-title', metaFacebookTitle);
                bodyComponent.getProperty('meta-facebook-description');
                bodyComponent.setProperty('meta-facebook-description', metaFacebookDescription);
                bodyComponent.getProperty('meta-facebook-image');
                bodyComponent.setProperty('meta-facebook-image', metaFacebookImage);

                //bodyComponent.setProperty(SITE_SETTINGS_HEADER_CONTENT, headerContent);
            }

            undoredo(cgoogleVerificationCode, cbingVerificationCode, cmetaTitle, cmetaKeywords, cmetaDescription, cmetaFacebookTitle, cmetaFacebookDescription, cmetaFacebookImage);

            UI.undoManagerAdd(
                    {
                        undo: function () {
                            undoredo(oldgoogleVerificationCode, oldbingVerificationCode, oldmetaTitle, oldmetaKeywords, oldmetaDescription, oldMetaFacebookTitle, oldMetaFacebookDescription, oldMetaFacebookImage);
                        },
                        redo: function () {
                            undoredo(cgoogleVerificationCode, cbingVerificationCode, cmetaTitle, cmetaKeywords, cmetaDescription, cmetaFacebookTitle, cmetaFacebookDescription, cmetaFacebookImage);
                        }
                    });

            $('.site-popover-custom').remove();
        }
    }//end self.saveSettings

    self.initFacebookImageUploading = function () {
        var prefix = "Picture";

        upclick({
            type: UPCLICK_TYPE_PICTURE,
            element: "facebook-image-upload-btn",
            action: "/Editor/Upload" + prefix + "?templateId=" + UI.getTemplateProperty("templateId"),
            accept: ".gif, .jpg, .png, .jpeg, .bmp",
            multiple: false,
            onstart: function (filename) {
                Application.addLocker();
            },
            oncomplete: function (response) {
                var file = Helpers.ProcessUploadSingleFileCompleted(response, $('.upclick-editor-error'), $('#facebook-image-upload-btn'));
                if (file != null)
                {
                    $('.site-settings-container .facebook-image img').attr('src', file.url);
                }                
            }
        });

        $('.upclick-container').bind('click', function (e) {
            e.stopPropagation();
        });
    };

    self.initFaviconUploading = function () {
        var prefix = "Favicon";

        upclick({
            type: UPCLICK_TYPE_PICTURE,
            element: "favicon-upload-btn",
            action: "/Editor/Upload" + prefix + "?templateId=" + UI.getTemplateProperty("templateId"),
            accept: ".gif, .jpg, .png, .jpeg, .bmp, .ico",
            multiple: false,
            onstart: function (filename) {
                Application.addLocker();
            },
            oncomplete: function (response) {
                var file = Helpers.ProcessUploadSingleFileCompleted(response, $('.upclick-editor-error'), $('#favicon-upload-btn'));
                if (file != null) {                    
                    self.saveFavicon(file.url + '?' + file.id);
                    $('.site-settings-container .favicon-image img').attr('src', file.url + '?' + file.id);
                }
            }
        });

        $('.upclick-container').bind('click', function (e) {
            e.stopPropagation();
        });
    };

    
    self.showPasswordModal = function (callback) {
        var code =
            '<div class="above modal fade" style="z-index: 1300;" id="ssl-password-dialog">' +
                '<div class="modal-dialog" style="width: 400px">' +
                '<div class="modal-content">' +
                '<div class="modal-header">' +
                '<h4 class="pop-up modal-title">Ssl Certificate Password</h4>' +
                '</div>' +
                '<div class="modal-body">' +
                '<form id="SiteSettingsSsl">' +
                '<div class="form-group"><input type="password" class="form-control" id="ssl-password-dialog-input" required placeholder="Enter Password"> </div>' +
                '<div class="text-center">' +
                '<button type="submit" class="std-button">Ok</button>' +
                '</div>' +
                '</form>' +
                '</div>' +
                '</div>' +
                '</div>' +
                '</div>';

        var modal = $(code).appendTo('body').modal('show');

        $('#SiteSettingsSsl').submit(function(e) {
            e.preventDefault();
            e.stopPropagation();
            var password = $('#ssl-password-dialog-input').val();
            if (password) {
                callback(password);
            }
            if ($('.modal').length > 1) {
                modal.remove();
                $('.modal-backdrop.fade.in').length ? $('.modal-backdrop.fade.in')[0].remove() : '';
            } else {
                Application.removeModal();
            }
        });
    }

    self.resetFacebookImage = function () {
        $('.site-settings-container .facebook-image img').attr('src', '');
    }

    self.resetFavicon = function () {
        self.saveFavicon('');
        $('.site-settings-container .favicon-image img').attr('src', '');
    };
}

SiteSettings.hideSaveMenu = function () {
    $('.edit-page-container').hide();
};
	var Designer = function () {
		var self = this;
		Designer.refreshBackground();
        //init controller
		Designer.paletteController = new PaletteController('');
		//if (!UI.settings.ispreview) {
		//		viewerPaletteController();
		//}
	}

	Designer.showBackgroundMenu = function () {
		var compiledTemplate;
		var context = [];
		var component = Designer.getBody();
		var element = $('#bgsite');
		Designer.refreshBackground();
		$('.edit-background-container').show();
    
		$('.upclick-button').upclick(component,
			{
			    multiple: false,
			    onlypictures: true,
				oncomplete: function (response) {
				    var file = Helpers.ProcessUploadSingleFileCompleted(response, $('.upclick-editor-error'), $('.design-management-container #upclick-button'));
				    if (file != null)
				    {
				        var src = file.url;
				        var bgStruct = Designer.getBackgroundStruct();
				        Designer.saveBackground(bgStruct.color.value, src, bgStruct.position.value, bgStruct.size.value, bgStruct.isShowOptimized.value);
				        src = ContextFactory.prepareImgSrc(src, bgStruct.isShowOptimized.value === "true");
				        $('#bgimage').attr('src', src);
				    }				    
				}
			});

		$('.bg-optimize-img input').bind('change', function () {
		    console.log("change");
	        var bgStruct = Designer.getBackgroundStruct();
	        Designer.saveBackground(bgStruct.color.value, bgStruct.image.value, bgStruct.position.value, bgStruct.size.value, $(this).is(':checked').toString());
	    });


		$('.remove-button').bind("click", function () {
			var bgStruct = Designer.getBackgroundStruct();
			Designer.saveBackground(bgStruct.color.value, "", bgStruct.position.value, bgStruct.size.value, bgStruct.isShowOptimized.value);
		});

		$('.bgposition-container input').bind('click', function () {
			var bgStruct = Designer.getBackgroundStruct();
			Designer.saveBackground(bgStruct.color.value, bgStruct.image.value, $(this).data('position'), bgStruct.size.value, bgStruct.isShowOptimized.value);
		});

		$('.bgscalling-container input').bind('click', function () {
			var bgStruct = Designer.getBackgroundStruct();
			Designer.saveBackground(bgStruct.color.value, bgStruct.image.value, bgStruct.position.value, $(this).data('scalling'), bgStruct.isShowOptimized.value);
		});

		Designer.setDefaultElement();

		Designer.bindPalettes();
		
		Designer.applyBindings();

	}

	Designer.getBackgroundStruct = function () {
		return {
		    image: Designer.getBody().getProperty("background-image"),
		    isShowOptimized: Designer.getBody().getProperty("show-optimized-image"),
			color: Designer.getBody().getProperty("background-color"),
			size: Designer.getBody().getProperty("background-size"),
			position: Designer.getBody().getProperty("background-position")
		}
	}

	Designer.hideDesignMenu = function () {
		$('.design-popover-custom').hide();
	}

	Designer.getBody = function () {
		return UI.siteComponentRepository.lookupData({ displayName: "body" });
	}

	Designer.saveBackground = function (color, image, position, size, isShowOptimized) {
		var component = Designer.getBody();
		var structs = [
							{ component: component, property: "background-color", newvalue: color, oldvalue: '' },
							{ component: component, property: "background-image", newvalue: image, oldvalue: '' },
		                    { component: component, property: "show-optimized-image", newvalue: isShowOptimized, oldvalue: '' },
							{ component: component, property: "background-position", newvalue: position, oldvalue: '' },
							{ component: component, property: "background-size", newvalue: size, oldvalue: '' }
		];

		UI.undoManagerAddSimpleArr(structs, Designer.refreshBackground, Designer.refreshBackground, true);

    
	}

	Designer.setDefaultElement = function () {

		var bgStruct = Designer.getBackgroundStruct();	
		$(".bgposition-container input[data-position='" + bgStruct.position.value + "']").prop('checked', true);
		$(".bgscalling-container input[data-scalling='" + bgStruct.size.value + "']").prop('checked', true);
	    if (bgStruct.isShowOptimized.value === "true") {
	        $(".bg-optimize-img input").prop('checked', true);
	    }
	    console.log($(".bgscalling-container input[data-scalling='" + bgStruct.size.value + "']"));
	}

	Designer.refreshBackground = function () {
		var component = Designer.getBody();
		var element = $('#bgsite');
		var bgStruct = Designer.getBackgroundStruct();
		var size = "";
		var repeat = "";
		var position = "";
	    
        
	    if (bgStruct.color != null && bgStruct.size != null) {
			element.css('background-color', bgStruct.color.value);
		}

	    if (bgStruct.image != null && bgStruct.image.value != null) {
	        if (bgStruct.image.value != '') {
	            var srcBg = bgStruct.image.value;
	            srcBg = ContextFactory.prepareImgSrc(srcBg, bgStruct.isShowOptimized.value === "true");
	            element.css('background-image', 'url(' + srcBg + ')');
	            element.css('background-position', bgStruct.position.value);
	        }
	        else {
	            element.css('background-image', '');
	        }
		}

	    if (bgStruct.size != null) {
	        if (bgStruct.size.value == "cover") {
	            var size = "cover";
	            var repeat = "no repeat";
	            var attachment = "fixed";
	        } else if (bgStruct.size.value == "contain") {
	            var size = "contain";
	            var repeat = "no-repeat";
	            var attachment = "fixed";
	        } else if (bgStruct.size.value == "tile") {
	            var size = "auto";
	            var repeat = "repeat";
	            var attachment = "fixed";
	        } else if (bgStruct.size.value == "vertically") {
	            var size = "auto";
	            var repeat = "no-repeat repeat";
	            var attachment = "fixed";
	        } else if (bgStruct.size.value == "horizontally") {
	            var size = "auto";
	            var repeat = "repeat no-repeat";
	            var attachment = "fixed";
	        } else if (bgStruct.size.value == "normally") {
	            var size = "auto";
	            var repeat = "no-repeat";
	            var attachment = "fixed";
	        }

	        element.css('background-size', size);
	        element.css('background-repeat', repeat);
	        element.css('background-attachment', attachment);

	        $('#bodybgcolorInput').val(bgStruct.color.value);
	        ColorPickerHelper.bind(element, '#bodybgcolorInput', component, BACKGROUND_COLOR, false);

	        if (bgStruct.image.value == "") {
	            $('#bgimage').attr('src', BLANKIMAGE);
	        } else {
	            var src = bgStruct.image.value;
	            src = ContextFactory.prepareImgSrc(src, bgStruct.isShowOptimized.value === "true");
	            $('#bgimage').attr('src', src);
	        }
	    }
	}

	Designer.applyBindings = function(node) {

		if (node == null) {
		    node = $('.editor-content')[0];
		}

		var designerViewModel = {
			image: ko.computed(function () {
				return Designer.getBody().getProperty("background-image");
			}),

			isShowOptimized: ko.computed(function () {
			    return (Designer.getBody().getProperty("show-optimized-image").value);
			}),

			color: ko.computed(function () {
				return Designer.getBody().getProperty("background-color");
			}),

			size: ko.computed(function() {
				return Designer.getBody().getProperty("background-size");
			}),

			position: ko.computed(function() {
				return Designer.getBody().getProperty("background-position");
			})
		}

		ko.observable(node).extend({ cleanNode: null });
		ko.observable(node).extend({ applyBindings: designerViewModel });
		ko.utils.extend(Designer, designerViewModel);
	
		return designerViewModel;

	};

	Designer.bindPalettes = function (node) {

		if (node == null) node = $('#palette-list')[0];

		Designer.paletteController = Designer.paletteController || new PaletteController('');
		ko.cleanNode(node);
		ko.applyBindings(Designer.paletteController, node);
		return Designer.paletteController;
	}








;
var Pager = function (curpagename, additionalParam) {
    var self = this;
    self.pages = [];
    var pageManagementTemplate = "";
    var pageMenuTemplate = "";
    var pageParentPageSelect = "";
    var pageTopMenuSelect = "";

    if (!UI.getSetting("ispreview")) {
        pageManagementTemplate = Helpers.loadServiceTemplate("page-management-template");
        pageMenuTemplate = Helpers.loadServiceTemplate("page-menu-template");
        pageParentPageSelect = Helpers.loadServiceTemplate("page-parent-page-select");
        pageTopMenuSelect = Helpers.loadServiceTemplate("page-topmenu-select");
        Helpers.onChangeCutting('#page-address-input', / /g);
    }

    var getMain = function () {
        return UI.siteComponentRepository.lookupData({ displayName: "main" });
    }
    //preparing control to page class instantiation
    var preparaPageFromComponent = function (node) {
        var parentPage = node.getProperty(PARENT_PAGE).value;
        var isHome = node.getProperty(HOME).value;
        var hideFromMenu = node.getProperty(HIDE_FROM_MENU).value;
        var name = node.getProperty(NAME).value;
        name = name.replace(/[^0-9a-zA-Z_-]/g, '');
        return {
            parentPage: parentPage == "null" || String.isNullOrEmpty(parentPage) ? null : parentPage,
            isHome: !String.isNullOrEmpty(isHome) ? isHome.toBoolean() : false,
            id: node.id,
            name: node.getProperty(NAME).value,
            title: node.getProperty(TITLE).value,
            hideFromMenu: !String.isNullOrEmpty(hideFromMenu) ? hideFromMenu.toBoolean() : false,
            order: node.getProperty(ORDER).value.toInteger(),
            secure: node.getProperty(SECURE) ? node.getProperty(SECURE).value.toBoolean() : false
        }
    }
    //initializing pages
    var createPages = function (main) {
        if (main != null && defined(main.children) && Array.isArray(main.children)) {
            main.children.forEach(function (item, index) {
                try {
                    var page = new Page(preparaPageFromComponent(item));
                    //todo: fix for early store page address version
                    if (page.isService() && page.name === 'store') {
                        page.name = 'product';
                        item.setProperty(NAME, 'product');
                    }
                    self.pages.push(page);
                } catch (e) {
                    console.log('Error create page, component is', item.displayName);
                    UI.siteComponentRepository.remove({ id: item.id });
                }
            });
        }
    }
    //applying page transition
    var applyTransition = function (element, instant) {
        instant = instant || false;
        if (instant || UI.getSetting("isrenderpublish")) {
            $(element).show();
        } else {
            $(element).fadeIn('slow');
        }
        
        window.scrollTo(0, 0);
    }

    //resetting home pages to false
    var resetHomePages = function () {
        self.pages.forEach(function (item) {
            item.edit({ isHome: false });
        });
        mainSection.children.forEach(function (item) {
            item.setProperty("home", false);
        });
    }

    //resetting page if current page is active
    var resetIfCurrentPageIsActive = function () {
        var currentlyActivePage = self.getCurrentPageId();
        if (currentlyActivePage != null) {
            self.goToPage(currentlyActivePage, true);
        } else {
            self.goToPage(self.pages.firstOrDefault().id, true);
        }
    }
    self.hasPages = function () {
        return self.pages.length > 0;
    }

    self.removePage = function (id) {
        var pageToRemove = self.pages.where({ id: id }).firstOrDefault();
        //if page, that must be removed is not parent or not the last parent page in set
        if (pageToRemove.parentPage != null || (pageToRemove.parentPage == null && self.pages.where({ parentPage: null }).length > 1)) {
            
            var pageid = _.clone(id);

            var redo = function () {
                Pager.hideAddPageMenu();
                var removedPages = [];

                var removedPage = UI.siteComponentRepository.remove({ id: pageid });
                removedPages.push(removedPage);

                var mainSectionChildren = _.cloneDeep(mainSection.children);
                mainSectionChildren.forEach(function (item) {
                    if (item.getProperty("parent-page").value == id) {
                        //removing page from model
                        var removed = UI.siteComponentRepository.remove({ id: item.id });
                        removedPages.push(removed);
                    }
                });
                var pagerChildren = _.cloneDeep(self.pages);
                var copyPages = [];
                self.pages.forEach(function (item) {
                    copyPages.push(item);
                });
                var oldHash = _.clone(HashHelper.get());
                pagerChildren.forEach(function (item, index) {
                    var currentHash = HashHelper.get();
                    if (item.id == pageid) {
                        if (currentHash == HashHelper.prepare(item.name)) {
                            HashHelper.clear();
                        }
                        self.pages.remove(index);
                        var childPages = self.pages.where({ parentPage: pageid });
                        childPages.forEach(function (childItem) {
                            //removing page from pager class
                            var indexByProp = self.pages.indexOfByProperty({ id: childItem.id });
                            self.pages.remove(indexByProp);
                            $('#' + childItem.id).remove();
                        });
                    }
                });

                resetIfCurrentPageIsActive();

                $('.editor-content .page-menu-container').html('').append(Pager.renderPager(self));
                UI.getViewModel('right-navigation-panel', null, null, false).initPages();
                return { removedPage: removedPage, removedComponents: removedPages, oldHash: oldHash, pages: copyPages};
            }

            var removed = redo();
                        
            var undo = function () {
                self.pages = [];
                removed.pages.forEach(function (item) {
                    self.pages.push(item);
                });

                removed.removedComponents.forEach(function (item) {
                    var parent = UI.siteComponentRepository.lookupData({ id: item.parentComponent.id });
                    UI.siteComponentRepository.appendTo(item, parent);

                    UI.actionService.addToActionData(item, true);
                    UI.actionService.runActionForComponent(item, ACTION_ADD_TO_FORM, true);                    
                });

                HashHelper.set(removed.oldHash);

                $('.editor-content .page-menu-container').html('').append(Pager.renderPager(self));
                UI.getViewModel('right-navigation-panel', null, null, false).initPages();
            }

            UI.undoManagerAdd(
                   {
                       undo: function () {
                           undo();
                           Helpers.refreshAllBloggingComponents();
                       },
                       redo: function (pageid) {
                           redo();
                           Helpers.refreshAllBloggingComponents();
                       }
                   });

        }
    }

    self.getTopLevelPagesCount = function() {
        var count = 0;
        _.forEach(self.pages.where({isService: false}), function(item) {
            if (item.parentPage == null && !item.hideFromMenu) {
                count += 1;
            }
        });
        return count;
    };
    
    self.savePage = function (params) {
        var id = $('.save-page').data('id');
        var name = $('.edit-page-container .page-name').val();
        var title = $('.edit-page-container .page-title').val();

        var description = $('.edit-page-container .page-description').val().replace(/(\r\n|\n|\r)/gm, " ");
        var keywords = $('.edit-page-container .page-keywords').val();
        var date = $('.edit-page-container .page-date').val();

        var isHome = $('.edit-page-container .page-is-home').prop('checked');
        var hideFromMenu = $('.edit-page-container .hide-from-menu').prop('checked');
        var secure = $('.edit-page-container .page-secure').prop('checked');
        var parentPage = $('.parent-page-select').val();
        parentPage = parentPage.length > 0 ? parentPage : null;
        var page;
        var pageComponent;
        var pageComponentHeaderFooter;

        //if required fields are not empty
        if (Helpers.formValidateEngine(PAGE_MANAGEMENT_SETTINGS)) {
            var existsIndex = _.findIndex(self.pages, function (item) { return item.name.toLowerCase() == name.toLowerCase(); });
            var exists = existsIndex != -1 ? self.pages[existsIndex] : null;

            if (exists != null && exists.id != id) {
                $('#page-management-settings').validationEngine('hideAll');
                setTimeout(function () {
                    $('#page-address-input').validationEngine('showPrompt', 'Address already exists!');
                }, 400);
                
                return false;
            }

            var reservedServicePages = ['product', 'cart', 'thank-you'];
            if (reservedServicePages.indexOf(name.toLowerCase()) !== -1) {
                $('#page-management-settings').validationEngine('hideAll');
                setTimeout(function () {
                    $('#page-address-input').validationEngine('showPrompt', 'Address is reserved for service page!');
                }, 400);

                return false;
            }

            if (isHome) {
                resetHomePages();
            }
            if (id == null) {

                var isAddNewPage = true; //if add new page
                var clonedid = $('.save-page').data('clonedid');

                if (clonedid != null) { 
                    isAddNewPage = false; //if duplicate page
                }

                var nextOrder;
                var maxOrderedPage = self.pages.where({ parentPage: parentPage }).max("order");
                nextOrder = maxOrderedPage != null ? maxOrderedPage.order + 1 : 0;
                //getting next order for page

                var componentService = new ComponentService("#" + MAIN);  //WRAPPER as root
                var component = null; // model page

                if (isAddNewPage) {
                    pageComponent = UI.basicComponentRepository.getAll().where({ name: "page" }).firstOrDefault();
                    component = new Component().createNew(pageComponent, true);

                } else { 
                    var clonedcomponent = UI.siteComponentRepository.lookupData({ id: clonedid }); // get model page
                    component = new Component().createFromExisting(clonedcomponent, true);
                    componentService.correctComponentsBeforeCloned(new Array(component), getMain());
                }

                component.setProperty("parent-page", parentPage, true);
                component.setProperty("name", name, true);
                component.setProperty("title", title, true);
                component.setProperty("home", isHome, true);
                component.setProperty("hide-from-menu", hideFromMenu, true);
                component.setProperty("order", nextOrder);
                component.setProperty(PAGE_SECURE_PROPERTY, secure);

                component.setProperty(META_DESCRIPTION, description, true);
                component.setProperty(META_KEYWORDS, keywords, true);
                component.setProperty(META_DATE, date, true);
               
                if (isAddNewPage) {
                    page = new Page({
                        parentPage: parentPage,
                        isHome: isHome,
                        id: component.id,
                        name: name,
                        title: title,
                        hideFromMenu: hideFromMenu,
                        order: nextOrder,
                        secure: secure
                    });
                } else {
                    page = new Page(preparaPageFromComponent(component));
                }               

                var redo = function(isRedo) {
                    UI.siteComponentRepository.appendTo(component,
                        UI.siteComponentRepository.lookupData({ name: MAIN }));

                    //add to actionData
                    if (isRedo) {
                        UI.actionService.addToActionData(component, true);
                    }
                    UI.actionService.runActionForComponent(component, ACTION_ADD_TO_FORM, true);

                    self.pages.push(page);
                    $('.editor-content .page-menu-container').html('').append(Pager.renderPager(self));

                    $(component.getUISelector()).hide();
                    Helpers.refreshAllBloggingComponents();
                    Resizer.recalculateHeaderFooterAndPageSize($(self.getCurrentPageId()));
                    UI.getViewModel('right-navigation-panel', null, null, false).initPages();
                }
                
                redo();
                Pager.hideAddPageMenu();
                
                var undo = function () {
                    self.removePage(component.id);
                    $('.editor-content .page-menu-container').html('').append(Pager.renderPager(self));
                    Helpers.refreshAllBloggingComponents();
                    Resizer.recalculateHeaderFooterAndPageSize($(self.getCurrentPageId()));
                    UI.getViewModel('right-navigation-panel', null, null, false).initPages();
                }

                UI.undoManagerAdd(
                       {
                           undo: function () {
                               undo();
                           },
                           redo: function () {
                               redo(true);
                           }
                       });

            } else {
                page = self.pages.where({ id: id });
                if (page != null) {
                    page = page.firstOrDefault();
                    if (self.getCurrentPageId() == id) {
                        HashHelper.set(name);
                    }
                    var nextOrder = page.order;
                    if (page.parentPage != parentPage) {
                        var maxOrderedPage = self.pages.where({ parentPage: parentPage }).max("order");
                        nextOrder = maxOrderedPage != null ? maxOrderedPage.order + 1 : 0;
                    }
                    page.edit({
                        name: name,
                        title: title,
                        isHome: isHome,
                        hideFromMenu: hideFromMenu,
                        parentPage: parentPage,
                        order: nextOrder,
                        secure: secure
                    });
                    pageComponent = UI.siteComponentRepository.lookupData({ id: id });
                }
                var cname = _.clone(name);
                var ctitle = _.clone(title);
                var chome = _.clone(isHome);
                var chideFromMenu = _.clone(hideFromMenu);
                var cparentPage = _.clone(parentPage);
                var cnextOrder = _.clone(nextOrder);
                var cdescription = _.clone(description);
                var ckeywords = _.clone(keywords);
                var cdate = _.clone(date);
                var csecure = _.clone(secure);

                var oldname = _.clone(pageComponent.getProperty("name").value);
                var oldtitle = _.clone(pageComponent.getProperty("title").value);
                var oldhome = _.clone(pageComponent.getProperty("home").value);
                oldhome = !String.isNullOrEmpty(oldhome) ? oldhome.toBoolean() : false;
                var oldhideFromMenu = _.clone(pageComponent.getProperty("hide-from-menu").value);
                var oldparentPage = _.clone(pageComponent.getProperty("parent-page").value);
                var oldnextOrder = _.clone(pageComponent.getProperty("order").value);
                var olddescription = _.clone(pageComponent.getProperty(META_DESCRIPTION).value);
                var oldkeywords = _.clone(pageComponent.getProperty(META_KEYWORDS).value);
                var olddate = _.clone(pageComponent.getProperty(META_DATE).value);
                var oldsecure = _.clone(pageComponent.getProperty(PAGE_SECURE_PROPERTY).value);


                var undoredo = function (name1, title1, home1, hideFromMenu1, parentPage1, nextOrder1, secure1, description1, keywords1, date1) {

                    page.edit({
                        name: name1,
                        title: title1,
                        isHome: home1,
                        hideFromMenu: hideFromMenu1,
                        parentPage: parentPage1,
                        order: nextOrder1,
                        secure: secure1
                    });

                    pageComponent.setProperty("name", name1, true);
                    pageComponent.setProperty("title", title1, true);
                    pageComponent.setProperty("home", home1, true);
                    pageComponent.setProperty("hide-from-menu", hideFromMenu1, true);
                    pageComponent.setProperty("parent-page", parentPage1, true);
                    pageComponent.setProperty("order", nextOrder1, true);
                    pageComponent.setProperty(PAGE_SECURE_PROPERTY, secure1, true);
                    pageComponent.setProperty(META_DESCRIPTION, description1, true);
                    pageComponent.setProperty(META_KEYWORDS, keywords1, true);
                    pageComponent.setProperty(META_DATE, date1, true);

                    $('.editor-content .page-menu-container').html('').append(Pager.renderPager(self));
                    Helpers.refreshAllBloggingComponents();
                    Pager.hideAddPageMenu();
                    UI.getViewModel('right-navigation-panel', null, null, false).initPages();
                }    

                undoredo(cname, ctitle, chome, chideFromMenu, cparentPage, cnextOrder, csecure, cdescription, ckeywords, cdate);

                UI.undoManagerAdd(
                        {
                            undo: function () {
                                undoredo(oldname, oldtitle, oldhome, oldhideFromMenu, oldparentPage, oldnextOrder, oldsecure, olddescription, oldkeywords, olddate);
                            },
                            redo: function () {
                                undoredo(cname, ctitle, chome, chideFromMenu, cparentPage, cnextOrder, csecure, cdescription, ckeywords, cdate);
                            }
                        });
            }
        }
    }

    self.getPageManagementTemplate = function () {
        return pageManagementTemplate;
    }

    self.getPageMenuTemplate = function () {
        return pageMenuTemplate;
    }
    self.getPageParentPageSelect = function () {
        return pageParentPageSelect;
    }

    self.getPageTopMenuSelect = function () {
        return pageTopMenuSelect;
    }

    self.addPageToForm = function(pageid) {
        var component = UI.siteComponentRepository.lookupData({ id: pageid });
        
        if (UI.getSetting("ispreview") && ($(component.getUISelector()).html() === '' || component.getProperty(ISSERVICE).value.toBoolean())) {
            //delete page
            $(component.getUISelector()).remove();
            //add page
            UI.actionService.runActionForComponent(component, ACTION_ADD_TO_FORM, true);

            MenuHelper.renderMenus();
            UI.MediaService.manageMedia();
            PostLoadInit.InitTextLinksToPages(self);
        } else {
            MenuHelper.renderMenuSizes(self.getTopLevelPagesCount());
            $('.std-component-fixed').each(function () {
                if (UI.siteComponentRepository.lookupData({ id: this.id }).parentComponent.id === pageid) {
                    $(this).show();
                }
            });
        }
    }

    self.goToHomePage = function (ignoreChangeUrl) {
        var homePage = self.pages.where({ isHome: true });
        UI.MediaService.stopAllMedia();
        if (homePage.any()) {
            homePage = homePage.firstOrDefault();
            self.hideAllPage();
            self.hideFixedElementsInPages();
            applyTransition('#' + homePage.id);
            if (!ignoreChangeUrl) {
                HashHelper.clear();
            }
            homePage.setMeta();
            self.addPageToForm(homePage.id);
            self.setPageHeight(homePage.id);
            if (!UI.getSetting("ispreview") && (homePage.isService() || !UI.getDevice().isDesktop())) {
                $('.position-add-new-com').hide();
            } else {
                $('.position-add-new-com').show();
            }
        } else {
            self.goToPage(self.pages.firstOrDefault().id, true);
        }
        UI.MediaService.startPlayVideoOnPage(homePage.id);
    }

    self.getHomePageId = function () {
        var homePage = self.pages.where({ isHome: true });
        if (homePage.any()) {
            homePage = homePage.firstOrDefault();
            return homePage.id;
        }
        return null;
    }
    self.getCurrentPageId = function () {
        var currentPage = $('.main .page:visible');
        if (currentPage.length > 0) {
            return currentPage.attr('id');
        }
        return self.getHomePageId();
    }

    self.otherPagesAreHidden = function () {
        var currentPage = $('.main .page:visible');
        if (currentPage.length > 1) {
            return false;
        }
        return true;
    }

    self.hideAllPage = function() {
        $(".page").hide();
    };

    self.hideFixedElementsInPages = function() {
        $('.std-component-fixed').each(function() {            
            if (UI.siteComponentRepository.lookupData({ id: this.id }).parentComponent.proto.name === 'page') {
                $(this).hide();
            }
        });
    }

    self.changeVisible = function (id) {
        var page = self.pages.where({ id: id });
        if (page.any()) {
            page = page.firstOrDefault();
            var pageComponent = UI.siteComponentRepository.lookupData({ id: page.id });
            var oldsecure = pageComponent.getProperty(PAGE_SECURE_PROPERTY).value.toBoolean();
            var csecure = !oldsecure;
            
            var undoredo = function (secure1) {
                page.edit({ secure: secure1 });

                pageComponent.setProperty(PAGE_SECURE_PROPERTY, secure1.toString(), true);

                $('.editor-content .page-menu-container').html('').append(Pager.renderPager(self));
            }

            undoredo(csecure);

            UI.undoManagerAdd(
                    {
                        undo: function () {
                            undoredo(oldsecure);
                        },
                        redo: function () {
                            undoredo(csecure);
                        }
                    });
        }
    }

    self.goToPage = function (id, ignoreCurrent, ignoreChangeUrl, additionalParam) {
        ignoreCurrent = ignoreCurrent || false;
        if (self.getCurrentPageId() != id || ignoreCurrent || !self.otherPagesAreHidden()) {          
            var page = self.pages.where({ id: id });
            if (page.any()) {
                UI.MediaService.stopAllMedia();
                page = page.firstOrDefault();
                if (!page.isServiceTech()) {
                    if (!UI.getSetting("ispreview") && (page.isService() || !UI.getDevice().isDesktop())) {
                        $('.position-add-new-com').hide();
                    } else {
                        $('.position-add-new-com').show();
                    }
                    MenuHelper.updateTopMenuSelect($(".topmenu-page-select"), page);
                    self.hideAllPage();
                    self.hideFixedElementsInPages();
                    if (!ignoreChangeUrl) {
                        if (!page.isHome) {
                            HashHelper.set(page.name, additionalParam);
                        } else {
                            HashHelper.clear();
                        }
                    }
                    if (!(UI.getSetting("ispreview") && page.secure && UI.authService && !UI.authService.isAuth())) {
                        applyTransition('#' + page.id);
                        self.addPageToForm(page.id);
                        page.setMeta();
                        self.setPageHeight(page.id);
                        UI.MediaService.startPlayVideoOnPage(page.id);
                        if (UI.getSetting("ispreview")) {
                            $('.carousel').carousel();
                            if (UI.autocompleteAddress) {
                                //settimeout is needed to run the function after the script is loaded. 
                                //To prevent error 'google is not defined' after reload
                                setTimeout(function () { 
                                    initAutocomplete();
                                }, 1000); 
                            }                                                     
                        }
                    } else {
                        //show modal
                        UI.componentService.addModalContentToForm(null, '#signin-secure-page');
                    }
                    if (UI.RulerGuides != undefined) {
                        UI.RulerGuides.calculateRulerGuideHeight();
                    }
                }
            }
        }
        Grouping.dropItems();
    }

    self.setPageHeight = function (pageId) {
        var main = $('.main')[0];
        var pageHeight = parseInt(UI.siteComponentRepository.lookupData({ id: pageId }).getProperty(HEIGHT).value);
        var footerTop = main.offsetTop + pageHeight;
        var footer = $(".footer");
        footer.css('top', footerTop + 'px');
        $('#page-resizer').css('top', footerTop + 'px');
        
        $('#wrapper-footer').css("top", (footer[0].offsetTop + footer[0].offsetHeight + 10) + "px");
        $('#bottom-body-resizer').css("top", (footer[0].offsetTop + footer[0].offsetHeight) + "px");
        var header = $('.header')[0];
        
        //close all dropdowns
        $('.dropdown.open').removeClass('open');
        //reset bg height
        $('#bgpreview #bgsite').css('height', '100%');
        //reset site-wrapper height
        $('.site-wrapper').css('height', header ? header.offsetHeight + 'px' : '420px');       
        //set new site-wrapper mobile height
        Helpers.checkSiteWrapperHeightMobile();
        //draw ruler
        if (!UI.getSetting('ispreview') && $('.hRule').css('display') !== 'none') {
            Resizer.drawRulersTicks(footer[0].offsetTop + footer[0].offsetHeight);
        }        

        return false;
    }

    self.getPageName = function (id) {
        var loc = location.href;
        if (loc.indexOf('#') != -1) {
            loc = loc.substring(0, loc.indexOf('#'));
        }

        var page = this.pages.where({ id: id }).firstOrDefault();
        var pageName = page.name;
        var result = loc + '#' + pageName;
        if (page.isHome) {
            result = loc;
        }
        return result;
    }

    self.getPageByName = function (name) {
        var page = null;
        if (defined(name)) {
            self.pages.forEach(function (item) {
                if (HashHelper.prepare(item.name.toLowerCase()) == HashHelper.prepare(name.toLowerCase())) {
                    page = item;
                }
            });
        }
        return page;
    }

    self.getPage = function (id) {
        return UI.pager.pages.where({ id: id }).firstOrDefault();
    };

    self.getPageId = function (pagename) {
        var id = self.getHomePageId();
        self.pages.forEach(function (item) {
            if (HashHelper.prepare(item.name.toLowerCase()) == HashHelper.prepare(pagename.toLowerCase())) {
                id = item.id;
            }
        });
        return id;
    }

    self.goToCurrentPage = function () {
        
        var currentHash = HashHelper.get();
        var currentPage = null;
        self.pages.forEach(function (item) {
            if (HashHelper.prepare(item.name).toLowerCase() == currentHash.toLowerCase()) {
                currentPage = item;
            }
        });
        if (currentPage != null) {
            self.goToPage(currentPage.id);
        } else {
            self.goToHomePage();
        }
        
    }
    //changing pages order
    self.changeOrder = function (id, direction) {
        var page = self.pages.where({ id: id });
        if (page.any()) {
            page = page.firstOrDefault();
            var filteredPages, currentPageIndex, currentOrder, currentPageModel;
            var setPageOrder = function (page1, pageorder1, page2, pageorder2) {
                page1.order = pageorder1;
                page2.order = pageorder2;
            };
            switch (direction) {
                case 'up':
                    filteredPages = self.pages.where({ parentPage: page.parentPage }).orderBy("order");
                    currentPageIndex = filteredPages.indexOfByProperty({ id: page.id });
                    if (currentPageIndex - 1 >= 0) {
                        var previousPage = filteredPages[currentPageIndex - 1];
                        var previousOrder = previousPage.order;
                        currentOrder = page.order;
                        
                        currentPageModel = UI.siteComponentRepository.lookupData({ id: page.id });
                        var previousPageModel = UI.siteComponentRepository.lookupData({ id: previousPage.id });

                        var clonePreviousOrder = _.clone(previousOrder);
                        var cloneCurrentOrder = _.clone(currentOrder);

                        var structs = [
                            { component: currentPageModel, property: "order", newvalue: clonePreviousOrder, oldvalue: '' },
                            { component: previousPageModel, property: "order", newvalue: cloneCurrentOrder, oldvalue: '' }
                        ];
                                               

                        UI.undoManagerAddSimpleArr(structs, function () { setPageOrder(previousPage, cloneCurrentOrder, page, clonePreviousOrder); Pager.renderPager(UI.pager); },
                                                            function () { setPageOrder(previousPage, clonePreviousOrder, page, cloneCurrentOrder); Pager.renderPager(UI.pager); }, true);

                        
                    }
                    break;
                case 'down':
                    filteredPages = self.pages.where({ parentPage: page.parentPage }).orderBy("order");
                    currentPageIndex = filteredPages.indexOfByProperty({ id: page.id });
                    if (currentPageIndex + 1 <= filteredPages.length - 1) {
                        var nextPage = filteredPages[currentPageIndex + 1];
                        var nextOrder = nextPage.order;
                        currentOrder = page.order;
                        
                        currentPageModel = UI.siteComponentRepository.lookupData({ id: page.id });
                        var nextPageModel = UI.siteComponentRepository.lookupData({ id: nextPage.id });

                        var cloneNextOrder = _.clone(nextOrder);
                        var cloneCurrentOrder = _.clone(currentOrder);

                        var structs = [
                            { component: currentPageModel, property: "order", newvalue: cloneNextOrder, oldvalue: '' },
                            { component: nextPageModel, property: "order", newvalue: cloneCurrentOrder, oldvalue: '' }
                        ];


                        UI.undoManagerAddSimpleArr(structs, function () { setPageOrder(nextPage, cloneCurrentOrder, page, cloneNextOrder); Pager.renderPager(UI.pager); },
                                                            function () { setPageOrder(nextPage, cloneNextOrder, page, cloneCurrentOrder); Pager.renderPager(UI.pager); }, true);

                                                
                        Pager.renderPager(UI.pager);
                    }
                    break;
            }
        }
    }

    var mainSection = getMain();
    createPages(mainSection);

    var objCurPage = self.getPageByName(curpagename);
    if (objCurPage != null) {
        var param = {};
        if ((objCurPage.name === 'product' || objCurPage.name === 'thank-you') && additionalParam) {
            param = {
                key: objCurPage.name === 'product' ? 'productId' : 'orderId',
                value: additionalParam
            }
        }
        self.goToPage(objCurPage.id, false, false, param);
    }
    else {
        self.goToCurrentPage();
    }    
}

var Page = function (source) {
    var self = this;

    self.init = function(source) {
        self.parentPage = source.parentPage;
        self.isHome = source.isHome;
        self.id = source.id;
        self.name = source.name.replace(/\s/g, '');
        if (/\s/g.test(source.name)) {
            self.setName();
        }
        self.title = source.title;
        self.hideFromMenu = source.hideFromMenu;
        self.order = source.order;
        self.secure = source.secure || false;
    }
    
    self.isService = function() {
        return self.id ? self.getComponent().getProperty(ISSERVICE).value.toBoolean() : false;
    }

    self.isServiceTech = function () {
        return self.isService() && self.id && self.name && self.id === self.name;
    }

    self.edit = function (properties) {
        if (defined(properties.isHome)) {
            self.isHome = properties.isHome;
        }
        if (defined(properties.parentPage) || properties.parentPage === null) {
            self.parentPage = properties.parentPage;
        }
        if (defined(properties.name)) {
            self.name = properties.name;
        }
        if (defined(properties.title)) {
            self.title = properties.title;
        }
        if (defined(properties.hideFromMenu)) {
            self.hideFromMenu = properties.hideFromMenu;
        }
        if (defined(properties.order)) {
            self.order = properties.order;
        }
        if (defined(properties.secure)) {
            self.secure = properties.secure;
        }
    }
    self.setMeta = function (title) {
        title = defined(title) ? title : this.title;
        if ($('head title').length == 0)
            $('head').prepend('<title></title>');
        $('title').html(title);
    }
    self.getComponent = function() {
        return self.id ? UI.siteComponentRepository.lookupData({ id: self.id }) : null;
    }
    self.setName = function (name) {
        name = defined(name) ? name : self.name;
        self.getComponent().setProperty(NAME, name);
        self.name = name;
    }

    self.init(source);
}

Pager.showAddPageMenu = function (pageId) {
    Helpers.reInitValidationEngine("#" + PAGE_MANAGEMENT_SETTINGS);
    var parentPages = _.filter(UI.pager.pages.where({ parentPage: null }),
        function(item) {
            return !item.isService() || item.isServiceTech();
        });
    var compiledTemplate;
    pageId = pageId || null;
    var context = [];
    if (pageId == null) {
        context.pages = [];
        context.pages.push({ id: null, title: "None", selected: true });
        context.pages.addRange(parentPages);
        compiledTemplate = HandlebarHelper.compileTemplate(UI.pager.getPageParentPageSelect(), context);
        $('.parent-page').html(compiledTemplate);
        $('.edit-page-container .page-name').val('');
        $('.edit-page-container .page-title').val('');

        $('.edit-page-container .page-description').val('');
        $('.edit-page-container .page-keywords').val('');
        $('.edit-page-container .page-date').val('');

        $('.edit-page-container  .page-is-home').prop('checked', false);
        $('.edit-page-container  .hide-from-menu').prop('checked', false);
        $('.edit-page-container  .page-secure').prop('checked', false);

    } else {
        var page = UI.pager.pages.where({ id: pageId });
        if (page != null) {
            page = page.firstOrDefault();
            context.pages = [];
            context.pages.push({ id: null, title: "None", selected: false });
            var hasChildPages = UI.pager.pages.where({ parentPage: page.id }).any();
            var preparedPages = [];
            if (parentPages.any()) {
                parentPages = parentPages.whereNot({ id: page.id });
            }
            parentPages.forEach(function (item) {
                preparedPages.push({
                    id: item.id,
                    title: item.title,
                    selected: item.id == page.parentPage
                });
            });
            context.pages.addRange(preparedPages);
            context.disabled = hasChildPages;
            compiledTemplate = HandlebarHelper.compileTemplate(UI.pager.getPageParentPageSelect(), context);
            var pageComponent = UI.siteComponentRepository.lookupData({ id: pageId });

            $('.parent-page').html(compiledTemplate);
            $('.edit-page-container .page-name').val(page.name);
            $('.edit-page-container .page-title').val(page.title);

            $('.edit-page-container .page-description').val(pageComponent.getProperty(META_DESCRIPTION).value);
            $('.edit-page-container .page-keywords').val(pageComponent.getProperty(META_KEYWORDS).value);
            $('.edit-page-container .page-date').val(pageComponent.getProperty(META_DATE).value);

            $('.edit-page-container  .page-is-home').prop('checked', page.isHome);
            $('.edit-page-container  .hide-from-menu').prop('checked', page.hideFromMenu);
            $('.edit-page-container  .page-secure').prop('checked', page.secure);
            if (page.isHome) {
                $('.edit-page-container  .page-secure').prop('disabled', true);
            }

        }
    }
    //subscribe for changed isHome checkbox
    $('.edit-page-container  .page-is-home').change(function () {
        $('.edit-page-container  .page-secure').prop('disabled', $(this).is(":checked"));
        $('.edit-page-container  .page-secure').prop('checked', false);
    });

    $('.edit-page-container').show();

    $(".editable-tooltip").popover("destroy");
    ko.cleanNode($(".edit-page-container")[0]);
    ko.applyBindings({}, $(".edit-page-container")[0]);

    $('.save-page').data('id', pageId);
    $('.save-page').text("Save page");
    $('.save-page').data('clonedid', null);
    Helpers.initDatePicker('#seo-custom-date-input');
}

Pager.showDuplicationMenu = function (pageId) {
    Helpers.reInitValidationEngine("#" + PAGE_MANAGEMENT_SETTINGS);
    var parentPages = UI.pager.pages.where({ parentPage: null });
    var compiledTemplate;
    var page = UI.pager.pages.where({ id: pageId });
    if (page != null) {
        page = page.firstOrDefault();
        var context = [];
        context.pages = [];
        context.pages.push({ id: null, title: "None", selected: true });
        context.pages.addRange(parentPages);
        compiledTemplate = HandlebarHelper.compileTemplate(UI.pager.getPageParentPageSelect(), context);
        var suffix = Pager.getLatestDuplicateSuffix();

        var pageComponent = UI.siteComponentRepository.lookupData({ id: pageId });

        $('.parent-page').html(compiledTemplate);
        $('.edit-page-container .page-name').val(page.name + '' + suffix);
        $('.edit-page-container .page-title').val(page.title + '' + suffix);

        $('.edit-page-container .page-description').val(pageComponent.getProperty(META_DESCRIPTION).value);
        $('.edit-page-container .page-keywords').val(pageComponent.getProperty(META_KEYWORDS).value);
        $('.edit-page-container .page-date').val(pageComponent.getProperty(META_DATE).value);

        $('.edit-page-container  .page-is-home').prop('checked', false);
        $('.edit-page-container  .hide-from-menu').prop('checked', page.hideFromMenu);
        $('.edit-page-container  .page-secure').prop('checked', page.secure);
        $('.save-page').data('id', null);
        $('.save-page').data('clonedid', pageId);
        $('.edit-page-container').show();
        $('.save-page').text("Duplicate");
        Helpers.initDatePicker('#seo-custom-date-input');
    }
};

Pager.getLatestDuplicateSuffix = function() {
    var suffix = 1;
    if (UI.pager.hasPages) {
        for (var i = 1; i < 1000; i++) {
            suffix = i;
            var isExists = UI.pager.pages.every(function (item) {
                return item.name.slice(-2) != "-" + i;
            });
            if (isExists) {
                break;
            }
            
        }
        return "-" + suffix;
    }
}

Pager.hideAddPageMenu = function() {
    $('.edit-page-container').hide();
};

Pager.hideSettingSuccessPage = function() {
    $('.settings-success-page').hide();
};

Pager.renderPager = function (pager) {
    var bindToDom = function (dom) {
        $('.editor-content .page-menu-container').html('').append(dom);
    }
    var template = pager.getPageMenuTemplate();
    var context = {};
    context.pages = [];
    if (pager.hasPages) {
        _.forEach(pager.pages, function (item, index) {
            if (item.parentPage == null) {
                var childPages = pager.pages.where({ parentPage: item.id });
                item.childPages = [];
                if (childPages.any()) {
                    item.childPages.addRange(childPages.where({ isService: false }).orderBy("order"));                    
                }
                if (!item.isService() || item.isServiceTech()) {
                    context.pages.push(item);
                }
            }
        });
        context.pages = context.pages.orderBy("order");
    }
    var compiledTemplate = HandlebarHelper.compileTemplate(template, context);
    bindToDom(compiledTemplate);
    UI.renderMenus();
    Pager.renderTopMenuSelect();
}

Pager.renderTopMenuSelect = function () {

    var bindToDom = function (dom) {
        $('.editor-pages').html(dom);
    }

    var template = UI.pager.getPageTopMenuSelect();
    var pages = _.cloneDeep(UI.pager.pages);
    var currentPageId = UI.pager.getCurrentPageId();
    var context = {};
    context.pages = [];
    if (pages.any()) {
        _.forEach(pages, function (item, index) {
            if (item.parentPage == null) {
                var childPages = pages.where({ parentPage: item.id });
                item.childPages = [];
                if (childPages.any()) {
                    item.childPages.addRange(childPages.orderBy("order"));
                }
                context.pages.push(item);
            }
        });
        context.pages = context.pages.orderBy("order");
    }

    var compiledTemplate = HandlebarHelper.compileTemplate(template, context);
    bindToDom(compiledTemplate);
    Helpers.initCustomTreeView($(".topmenu-page-select").find('.tree-view'), function(id) {
        var page = UI.pager.getPage(id);
        UI.pager.goToPage(page.id);
    });
    MenuHelper.updateTopMenuSelect($(".topmenu-page-select"), UI.pager.getPage(currentPageId));
}

Pager.getMinHeightMenu = function (sefl, pages) {
    var minHeight = $("#" + sefl + " a:first").innerHeight();
    
    if (minHeight == 0 && pages > 1) {
        minHeight = $("#" + sefl).css("min-height").toNumber() / (pages - 1);
    }
    return minHeight;
};

// for two and more checkbox - only one in array checkbox can be active
Pager.activeCheckBox = function(arrayElements, arrayProperties, activeElement, activeProperty, component) {

    if (arrayElements.length > 0) {
        for (var i = 0; i < arrayElements.length; i++) {
            if (activeElement != arrayElements[i]) {
                $("#" + arrayElements[i]).prop("checked", false);

            }
        }
    }

    if (arrayProperties == false && activeProperty == false) {
        arrayProperties = arrayElements;
        activeProperty = activeElement;
    }
    
    if (arrayProperties.length > 0) {
        for (var j = 0; j < arrayProperties.length; j++) {
            if (activeProperty != arrayProperties[j]) {
                component.setProperty(arrayProperties[j], false);
            }
        }
    }
};


;
var MenuHelper = function () { }

MenuHelper.renderMenus = function () {
    var context = [];
    context.pages = [];
    //need to populate if preview to true, then we render pager links
    var viewer = UI.getSetting("ispreview");
    if (defined(UI.pager)) {
        if (UI.pager.hasPages) {
            //creating pages context for menus, processing root elements first, while adding child elements to them
            var pages = _.cloneDeep(UI.pager.pages).where({ isService: false });
            _.forEach(pages, function (item) {
                if (item.parentPage == null && !item.hideFromMenu) {
                    var childPages = pages.where({ parentPage: item.id });
                    item.childPages = [];
                    if (childPages.any()) {
                        childPages.where({ hideFromMenu: false }).forEach(function (child) {
                            child.href = Helpers.generateLinkToPage(child.name);
                            item.childPages.push(child);
                        });
                    }
                    item.childPages = item.childPages.orderBy("order");
                    item.href = Helpers.generateLinkToPage(item.name);
                    context.pages.push(item);
                }
            });
            context.pages = context.pages.orderBy("order");
            var basicComponent = UI.basicComponentRepository.getAll().where({ name: "menu" });
            //processing all menu controls to reflect pager changes
            if (basicComponent.any()) {
                var basicMenuComponent = basicComponent.firstOrDefault();
                var menuSet = UI.siteComponentRepository.lookupDataSet({ componentId: basicMenuComponent.id });
                menuSet.forEach(function (menu) {
                    context.id = menu.id;
                    context.viewer = viewer;
                    context.styles = UI.getDevice().getPropertiesList(menu).where({ group: "style" });
                    var predefined = menu.getProperty(PREDEFINED).value;
                    context.predefined = predefined;
                    context.borderradius = menu.getProperty(BORDER_RADIUS).value;

                    //todo: choose template
                    var source = (!UI.getDevice().isDesktop() && menu.proto.slaveTemplate)
                        ? menu.proto.slaveTemplate
                        : menu.proto.template;
                    var template = HandlebarHelper.compileTemplate(source, context);
                    
                    $(menu.getUISelector()).html($(template).html());

                    MenuHelper.renderSubstringListMenu(menu.getUISelector());

                    //set min height and width
                    MenuHelper.setMenuMinSize(menu, context.pages.length);

                });
            }
        }
    }
}

MenuHelper.renderSubstringListMenu = function (menu) {
    if ($(menu).find("ul").hasClass("vertical")) {
        var lists = $(menu).find("a");
        //var newWidthWithPadding = $(menu).width() - 80; // distance from list element to edges menu (padding)

        for (var j = 0; j < lists.length; j++) {
            if ($(lists)[j].nodeName == "A") {
                $(lists[j]).css({ "overflow": "hidden", "text-overflow": "ellipsis", "text-align": "center", "white-space": "nowrap" });
            }
        }
    }
}

MenuHelper.renderMenuSizes = function (pagesCount) {
    var basicComponent = UI.basicComponentRepository.getAll().where({ name: "menu" });
    if (basicComponent.any()) {
        var basicMenuComponent = basicComponent.firstOrDefault();
        var menuSet = UI.siteComponentRepository.lookupDataSet({ componentId: basicMenuComponent.id });
        menuSet.forEach(function (menu) {
            MenuHelper.setMenuMinSize(menu, pagesCount);
        });
    }
}

MenuHelper.calculateMinWidthForSimpleMenu = function(menu)
{
    var fontSize = parseInt(menu.getProperty(FONT_SIZE).value);
    var fontFamily = menu.getProperty(FONT_FAMILY).value;
    var width = parseInt(menu.getProperty(WIDTH).value);

    var menuUL = $(menu.getUISelector()).find('ul.simple');
    var minWidth = 0;

    if (menuUL[0]) {
        //width of padding
        var padding = menuUL.innerWidth() - menuUL.width();
        minWidth += padding;

        //width of li elements
        $(menuUL).find('li').each(function (index, element) {
            var li = $(element);

            var liText = li.text() || "";
            var liWidth = Helpers.getTextWidth(liText.trim(), fontSize, fontFamily);
            var liMinWidth = parseFloat(li.css('min-width'));

            minWidth += liMinWidth > liWidth ? liMinWidth : liWidth;
        });
    }

    return minWidth > width ? width : minWidth;
}

MenuHelper.setMenuMinSize = function (menu, pagesCount) {
    if (UI.getDevice().isDesktop()) {
        //set min height and width
        var minHeight = Pager.getMinHeightMenu(menu.id, pagesCount);
        var predefined = menu.getProperty(PREDEFINED).value;
        var fontSize = parseInt(menu.getProperty(FONT_SIZE).value);
        var fontFamily = menu.getProperty(FONT_FAMILY).value;
        var borderWidth = parseInt(menu.getProperty(BORDER_WIDTH).value);
        var width = parseInt(menu.getProperty(WIDTH).value);

        if (predefined == 'simple') {

            //we need min-width only in editor
            if (!UI.getSetting("ispreview"))
            {
                //wait for state "Complete"
                //because some browsers (safari) can't calculate width correcly earlier
                var intervalToSetupMinWidth = setInterval(function () {
                    if (document.readyState !== DOC_READY_STATE_COMPLETE) return;
                    clearInterval(intervalToSetupMinWidth);

                    //get and set min-width for menu
                    var minwidth = MenuHelper.calculateMinWidthForSimpleMenu(menu);
                    $(menu.getUISelector()).css("min-width", minwidth + 'px');
                    console.log("menu minwidth: " + minwidth);
                }, 50);           
            }

            minHeight = '20px';
            if (fontSize + borderWidth * 2 > 20) {
                minHeight = (fontSize + borderWidth * 2) + 'px';
            }
            
            $(menu.getUISelector()).css("min-height", minHeight);
            var stretchedToFullWidth = StretcherFactory.getCurrentStretchStatus(menu);
            if (!stretchedToFullWidth) {
                $(menu.getUISelector()).css("width", width);
            }
        } else {

            var pageCount = 0;
            if (UI.pager != undefined) {
                pageCount = UI.pager.getTopLevelPagesCount();
            }
            var heightModificator = 1.42;
            var defaultMinHeight = 30;
            var minHeightValue =
                parseInt(fontSize * heightModificator + defaultMinHeight) * pageCount + borderWidth * 2;
            minHeight = minHeightValue + "px";
            $(menu.getUISelector()).css("min-width", "80px");
            $(menu.getUISelector()).css("min-height", minHeight);
        }
        ViewerFactory.calculateMenuLineHeight(menu, '#' + menu.id);
    }
}

MenuHelper.updateTopMenu = function(menu, page) {
    menu.each(function() {
        if ($(this).data().id === page.id) {
            $(this).addClass("active");
        } else {
            $(this).removeClass("active");
        }
    });
}

MenuHelper.updateTopMenuSelect = function (select, page) {
    if (!page.id) {
        page = UI.pager.getPage(UI.pager.getHomePageId());
    }
    select.find('span').text(page.title);
    MenuHelper.updateTopMenu(select.find('.tree-view-item-link'), page);
};
var Helpers = function(){};
var H = Helpers;



var defined = function(context){
    return typeof context !== 'undefined' && context !== null;
}

var isDefinedAndNotEmpty = function (context) {
    if (defined(context)) {
        if (context != null && context !== '') {
            return true;
        }
    }
    return false;
}

//crossbrowser helper to clear selection in case of resize and drag'n'drop
Helpers.clearAllSelection = function(){
    if (window.getSelection) {
        if (window.getSelection().empty) {  // Chrome
            window.getSelection().empty();
        } else if (window.getSelection().removeAllRanges) {  // Firefox
            window.getSelection().removeAllRanges();
        }
    } else if (document.selection) {  // IE?
        document.selection.empty();
    }
}

//for OnOff button
//---------------------
Helpers.actionOnOffButtonValueSet = function (obj, value) {
    val = null;
    if (defined(value))
    {
        val = value;
    }

    if (H.actionOnOffButtonValueGet(obj) != val || val == null)
    {
        $(obj).find('.btn').toggleClass('active');

        if ($(obj).find('.btn-primary').size() > 0) {
            $(obj).find('.btn').toggleClass('btn-primary');
        }

        $(obj).find('.btn').toggleClass('btn-default');
        var curVal = H.actionOnOffButtonValueGet(obj);
        UI.getBody().setProperty($(obj).data("property"), curVal, true);
    }

    var action = $(obj).data(CALLBACK_ACTION);
    var form = $(obj).closest('form');
    if (jQuery.isFunction(callbackActions[action]) && form.length > 0)
    {
        callbackActions[action](form, H.actionOnOffButtonValueGet(obj));
    }

    switch ($(form).prop('id')) {
        case SITE_SETTINGS_ACCESSIBILITY:
            var property = UI.getBody().getProperty($(obj).data("property"));

            if (property != null && property.value == 0) {
                Helpers.hideValidationEngine("#site-accessibility-key, #site-accessibility-email");
            }

            break;
        default:
            break;
    }
}

Helpers.actionOnOffButtonValueGet = function (obj) {
    var el = $(obj).find('.active');

    if (el.length > 0) {
        return el.data("value");
    }
    return "false";
}
//for onoff button

Helpers.consoleLog = function () {
    if (ENABLE_LOGGING) {
        console.log.apply(console, arguments);
    }
}

Helpers.changeUrl = function (title, url) {
    if (typeof (history.pushState) != "undefined") {
        var obj = { Title: title, Url: url };
        history.pushState(obj, obj.Title, obj.Url);
    }
}

Helpers.addQueryParam = function (src, param) {
    var addQueryParam = "";
    if (src.lastIndexOf('?') == -1) {
        addQueryParam = "?" + param;
    }
    else {
        addQueryParam = "&" + param;
    }
    return src + addQueryParam;
}

Helpers.showModalDialog = function(title, message, url) {
    Application.getModal('/Editor/CustomModal/")', { title: title, message: message, url: url });
}

//loading service template for pager, can be used for other templates as well, also restores used ajax settings to default
Helpers.loadServiceTemplate = function (name) {
    var template;
    var ajaxAsync = $.ajaxSettings.async;
    var ajaxCache = $.ajaxSettings.cache;
    $.ajaxSetup({ async: false,cache:false });
    $.get("/Content/Editor/service-templates/" + name + ".html", function(response) {
        template = response;
    });
    $.ajaxSetup({ async: ajaxAsync,cache:ajaxCache });
    return template;
}
String.empty = "";

Helpers.isCkEditorComponent = function (component) {
    return component.proto.name == PARAGRAPH || component.proto.name == HEADERTEXT;
}
Helpers.isGalleryComponent = function (component) {
    return component.proto.name == GALLERY || component.proto.name == SLIDESHOW || component.proto.name == LIST || component.proto.name == HOUSE_PHOTO_TOUR || component.proto.name == HTML_CONTAINER;
}
Helpers.isSpecificComponent = function(component) {
    return (Helpers.isCkEditorComponent(component) && component.isckeditorworking) || (Helpers.isGalleryComponent(component) && UI.getDevice().isDesktop());
}

Helpers.getExt = function (filename) {
        var ext = filename.split('.').pop();
        if (ext == filename) return "";
        return ext;
}

Helpers.ProcessUploadSingleFileCompleted = function (response, errorobj, upclickbuttonobj) {
    Application.removeLocker();
    var wrapper = JSON.parse(response);
    var errorMessage = Helpers.GetUploadingSingleFileError(wrapper);
    errorobj.remove();
    if (errorMessage == "") {
        return wrapper.files[0];
    }
    else {
		errorMessage = "<div class=\"property-editor upclick-editor-error\">" + errorMessage + "</div>";
        upclickbuttonobj.before(errorMessage);
        return null;
    };
}

Helpers.ProcessUploadMultipleFilesCompleted = function (response, errorobj, upclickbuttonobj) {
    Application.removeLocker();
    var wrapper = JSON.parse(response);
    var errorMessage = Helpers.GetUploadingSingleFileError(wrapper);
    errorobj.remove();
    if (errorMessage == "") {
        return wrapper.files;
    }
    else {
        errorMessage = "<div class=\"property-editor upclick-editor-error\">" + errorMessage + "</div>";
        upclickbuttonobj.before(errorMessage);
        return null;
    };
}

Helpers.ProcessUploadSingleFileWithErrorLabelCompleted = function (response, errorLabel) {
    Application.removeLocker();
    var wrapper = JSON.parse(response);
    var errorMessage = Helpers.GetUploadingSingleFileError(wrapper);

    if (errorMessage == "") {
        return wrapper.files[0];
    }
    else {
        errorLabel.html(errorMessage);
        return null
    };
}

Helpers.GetUploadingSingleFileError = function (wrapper) {
    var errorMessage = "";
    if (wrapper.errorMessage != "" || wrapper.files == null || wrapper.files.length == 0 || wrapper.files[0].bannedExtension) {
        if (wrapper.errorMessage != "") {
            errorMessage = wrapper.errorMessage;
        }
        else if (wrapper.files == null || wrapper.files.length == 0) {
            errorMessage = "Files was not stored.";
        } else if (wrapper.files[0].bannedExtension) {
            errorMessage = "The file content you have provided is not a supported image format."
        }
    }
    return errorMessage;
}

Helpers.getPdfThumbnail = function (pdfUrl, callback) {
    PDFJS.workerSrc = '\\Content\\Editor\\js\\libs\\pdfjs\\build\\pdf.worker.js';
    PDFJS.getDocument(pdfUrl).then(function (pdf) {
        pdf.getPage(1).then(function (page) {

            var viewport = page.getViewport(0.5);
            var canvas = document.createElement('canvas');
            var ctx = canvas.getContext('2d');
            canvas.height = viewport.height;
            canvas.width = viewport.width;

            var renderContext = {
                canvasContext: ctx,
                viewport: viewport
            };

            page.render(renderContext).then(function () {
                //set to draw behind current content
                ctx.globalCompositeOperation = "destination-over";
                //set background color
                ctx.fillStyle = "#ffffff";
                //draw background / rect on entire canvas
                ctx.fillRect(0, 0, canvas.width, canvas.height);
                var img = canvas.toDataURL();
                callback(img);
            });
        });
    });
};

Helpers.showPdfInsideModal = function (src, name, width, height) {
    name = name || "PDF";
    width = width || 1200 ;
    height = height || 600;
    if (typeof (src) == 'string' && src.length > 0) {
        var content = '<iframe src="' + UI.getSetting("adminSiteDomain") + '/Content/Editor/js/libs/pdfjs/web/viewer.html?file=' + src + '" width="100%" height="' + height + 'px" />';
        Application.showEmptyDialog(name, content, width, 'pdf');
    }
}

Helpers.duplicateNameValue = function (name) {
    var result = name;
    var index = name.match(/\((.*?)\)/);
    if (index != null) {
        result = result.replace(index[0], "(" + (index[1].toNumber() + 1) + ")")
    }
    else {
        result += " (1)" 
    }
    return result;
}

$.fn.extend({
    highlightSelectedElement: function(component, hasEditor) {
        //element selection hightlight functionality
    	var element = $(this);

		//no need to highlight... when ckeditor is opened...
    	if (element.data('ckeditorInstance') != null || UI.isckeditorworking || (EditorFactory.findAssignedParent(component, STORE_PRODUCT) !== null && component.proto.name !== STORE_PRODUCT) || (EditorFactory.findAssignedParent(component, STORE_GALLERY) !== null && component.proto.name !== STORE_GALLERY) || (EditorFactory.findAssignedParent(component, STORE_CART) !== null && component.proto.name !== STORE_CART)) {
		    return false;
	    };

        //setting data attribute, that element is currently selected
    	element.data('selected', true);
        
    	if (component.isDraggable && !element.hasClass('std-component-fixed')) {
            element.addClass('drag');
        }
        $(UI.getConfigurationValue(SELECT_WRAPPER)).remove();

        var elementWidth = element.outerWidth() + 2;
        var elementHeight = element.outerHeight() + 2;
        var elementTop = element.offset().top - 1;
        var elementLeft = element.offset().left - 1;

        var selectWrapper = $('<div class="' + SELECT_WRAPPER + '"><ul class="' + SELECT_WRAPPER_MENU + '"></ul></div>');
        if (element.hasClass('std-component-fixed')) {
            selectWrapper.addClass(SELECT_WRAPPER_FIXED);
            if (component) {
                selectWrapper.css(ViewerFactory.getFixedLocationPosition(component, true));
            } else {
                selectWrapper.css(ViewerFactory.getFixedLocationPosition(UI.siteComponentRepository.lookupData({id: this.id }), true));
            }
        } else {
            selectWrapper.css({ top: elementTop + 'px', left: elementLeft + 'px' });
        }

        if (element.hasClass('std-headertext') || element.hasClass('std-paragraph')) {
            selectWrapper.css({ background: 'rgba(39, 219, 247, 0.1)' });
        }
        selectWrapper.css({ width: elementWidth + 'px', height: elementHeight + 'px', pointerEvents: 'none' });
        
        //setting attribute for select wrapper
        selectWrapper.data('for', element.getId());

        Resizer.bind(element, selectWrapper, component, hasEditor);

        clipBoard.selectedItem(component);

        Grouping.dropItems();

        StretcherFactory.updateComponentWrapper(component);
		
    },
    highlightGroupElement: function (component) {
        //element selection hightlight functionality
        var element = $(this);

        //no need to highlight... when ckeditor is opened...
        if (element.data('ckeditorInstance') != null) {
            return false;
        };

        Grouping.wrap();
        UI.removeEditor();
        $(UI.getConfigurationValue(SELECT_WRAPPER)).remove();
        
        //setting data attribute, that element is currently selected
        element.data('selected', true);

        if (component.isDraggable && !element.hasClass('std-component-fixed')) {
            element.addClass('drag');
        }
    },
    getId: function() {
        return $(this).attr('id');
    },
    findUnderlyingDockableElement: function () {
        var el = $(this);
        var elementCoords = Positioning.createElementCoords(el);
        var dockable = $('[dockable]');
        if (dockable.length > 0) {
            var atLeastOneResult = false;
            $(dockable).each(function (item) {
                //filtering out same element,elements, that are inside docked element and are visible
                if ($(this).getId() != el.getId() && el.has($(this)).length == 0 && $(this).css('display') != 'none' && $(this).parent('.page').css('display') != 'none' && !$(this).hasClass('page') && !$(this).hasClass('main')) {
                    var result = Positioning.isAboveElement(elementCoords, Positioning.createElementCoords(this));
                    if (result) {
                        atLeastOneResult = true;
                        $(this).createDockContainer();
                    }
                }
            });
            if (!atLeastOneResult) {
                //$('#' + UI.pager.getCurrentPageId()).createDockContainer();
            }
        } else {
            return null;
        }
    },
    createDockContainer: function() {
        UI.removeDockContainer();
        var element = $(this);
        var elementWidth = element.outerWidth() + 2;
        var elementHeight = element.outerHeight() + 2;
        var elementTop = element.offset().top - 1;
        var elementLeft = element.offset().left - 1;
        var dockWrapper = $('<div class="' + DOCK_WRAPPER + '"></div>');
        dockWrapper.css({ width: elementWidth + 'px', height: elementHeight + 'px', pointerEvents: 'none', top: elementTop + 'px', left: elementLeft + 'px' });
        dockWrapper.data('for', element.getId());
        $('body').append(dockWrapper);
    },

    //overriding upclick plugin to work instantiate on jquery element
    upclick: function (component, params) {
        params = params || {};
        var defaultParams = {
            accept: ".gif, .jpg, .png, .jpeg, .bmp",
            onstart: function () { },
            oncomplete: function () { },
            multiple: false,
            type: UPCLICK_TYPE_PICTURE,
            action: "",
            action_params: {}
        }
        params.accept = params.accept || defaultParams.accept;
        params.onstart = params.onstart || defaultParams.onstart;
        params.oncomplete = params.oncomplete || defaultParams.oncomplete;
        params.multiple = params.multiple || defaultParams.multiple;
        params.type = params.type || defaultParams.type;
        params.action = "/Editor/Upload" + params.type + "?templateId=" + UI.getTemplateProperty("templateId");
        params.action_params = params.action_params || defaultParams.action_params;

        upclick({
            element: $(this).getId(),
            action: params.action,
            accept: params.accept,
            multiple: params.multiple,
            action_params: params.action_params,
            onstart: function (filename) {
                Application.addLocker();
                params.onstart(filename, component);
            },
            oncomplete: function (response) {
                params.oncomplete(response, component);
                Application.removeLocker();
            }
        });
        $('.upclick-container').bind('click', function(e) {
            e.stopPropagation();
        });
    }
});

$.fn.isElementInViewport = function() {

	if ($(this)[0] == null) return false;
	var rect = $(this)[0].getBoundingClientRect();

	return (
		rect.top >= 0 &&
			rect.left >= 0 &&
			rect.bottom <= (window.innerHeight || $(window).height()) &&
			rect.right <= (window.innerWidth || $(window).width())
	);
};


/*

small pub-sub to decouple deps, or extend eventing...
usage:   

	* publish data on topic (string):		
		eventsystem.publish('/some/topic/', params, not, restricted,... )

	* subscribe for topic:	 
		var cleaningDelegate = eventsystem.subscribe('/some/topic/', function(params, not, restricted){ ... ) 

	* when finished with subscription - do cleaningDelegate() call

*/

var eventsystem = (function (eventsystem) {
	var topics = {};

	eventsystem = eventsystem || {
		subscribe: function (topic, listener) {
			// Create the topic's object if not yet created
			if (!topics[topic]) topics[topic] = { queue: [] };

			// Add the listener to queue
			var index = topics[topic].queue.push(listener) - 1;

			// Provide handle back for removal of topic
			return {
				remove: function () {
					delete topics[topic].queue[index];
				}
			};
		},
		publish: function (topic, info) {
			// If the topic doesn't exist, or there's no listeners in queue, just leave
			if (!topics[topic] || !topics[topic].queue.length) return;

			// Cycle through topics queue, fire!
			var items = topics[topic].queue;
			items.forEach(function (item) {
				item(info || {});
			});
		}
	};

	return eventsystem;
})(eventsystem);

flatten = function (x, result, prefix) {
	if (_.isObject(x)) {
		_.each(x, function (v, k) {
			flatten(v, result, prefix ? prefix + '.' + k : k);
		});
	} else {
		result[prefix] = x;
	}
	return result;
};

function validateLink() {
    if ($("#linkInput").val() != "") {
        var patternName = $("#linkInput").data().val,
        regular = window[patternName],
        value = $("#linkInput").val();

        var res = value.search(regular);
        if (res == -1) $("#linkInput").css("backgroundColor", "red");
        else $("#linkInput").css("backgroundColor", "white");
    } else {
        $("#linkInput").css("backgroundColor", "white");
    }
};

Helpers.onChangeCutting = function(id, regex) {
    $(document).on('change', id, function (evt) {
        $(this).val($(this).val().replace(regex, ''));
    });
}

Helpers.findFreePositionOnCustomForm = function (customFormElementId, currentElementWidth, currentElementHeight) {

    function isFreePosition(formElement, position) {
        var result = { isFree: true, collisionObjectX: 0, collisionObjectY: 0, collisionObjectW: 0, collisionObjectH: 0 };
        formElement.children().each(function (index, item) {
            var com = UI.siteComponentRepository.lookupData({ id: item.id });
            var itemPosition = { x: parseInt(com.getProperty(LEFT).value), y: parseInt(com.getProperty(TOP).value), w: parseInt(com.getProperty(WIDTH).value), h: parseInt(com.getProperty(HEIGHT).value) };
            //rectangles crossing
            if ((position.x <= itemPosition.x && position.x + position.w >= itemPosition.x || itemPosition.x <= position.x && itemPosition.x + itemPosition.w >= position.x)
                && ( position.y <= itemPosition.y && position.y + position.h >= itemPosition.y || itemPosition.y <= position.y && itemPosition.y + itemPosition.h >= position.y ))
            {
                result.isFree = false;
                result.collisionObjectX = itemPosition.x;
                result.collisionObjectY = itemPosition.y;
                result.collisionObjectW = itemPosition.w;
                result.collisionObjectH = itemPosition.h;
            }
        });
        return result;
    }

    var formElement = $('#' + customFormElementId);
    var com = UI.siteComponentRepository.lookupData({ id: customFormElementId });
    var formHeight = parseInt(com.getProperty(HEIGHT).value);
    var formWidth = parseInt(com.getProperty(WIDTH).value);
    var iterationLimit = 10000;
    var indents = new Array();
    var indentNumber = 0;

    formElement.children().each(function (index, item) {
        var value = item.offsetLeft + item.offsetWidth;
        if (indents.indexOf(value) < 0) {
            indents.push(value);
        }
    });
    indents.sort(function(a, b) {
        return a - b;
    });

    if (isFreePosition(formElement, { x: 0, y: 0, w: currentElementWidth, h: currentElementHeight }).isFree == false) {

        var freePosition = { x: 0, y: 0, w: currentElementWidth, h: currentElementHeight };
        
        while (iterationLimit-- > 0) {
            var searchResult = isFreePosition(formElement, freePosition);
            if (searchResult.isFree && freePosition.y + freePosition.h < formHeight) {
                return freePosition;
            }
            else {
                if (searchResult.isFree == false) {         
                    freePosition.y = searchResult.collisionObjectY + searchResult.collisionObjectH + 1;
                }
                if (freePosition.y + freePosition.h >= formHeight) {
                    freePosition.y = 0;
                    freePosition.x = indents[indentNumber++] + 1;
                }
            }
            if (freePosition.x + freePosition.w > formWidth) {
                break;
            }
        }//end while
    }

    return { x: 0, y: 0 };
};

Helpers.setNewGuid = function (component) {
    if (component == null)
        throw new Error("component is null");

    component.id = Guid.new();
    component.children.forEach(function (children) {
        Helpers.setNewGuid(children);
    });
};

Helpers.generateLinkToPage = function(page, onlyLink, additionalParam) {    
    var link = location.origin;
    if (!UI.getSetting('ispublished')) {
        link += location.pathname + location.search + (additionalParam ? (location.search ? '&' + additionalParam.key + '=' + additionalParam.value : '?' + additionalParam.key + '=' + additionalParam.value) : '') + '#' + page;;
    } else {
        link += '/' + page + (additionalParam ? '/' + additionalParam.value : '');
    }

    return onlyLink ? link : 'href="' + link + '"';
}

Helpers.generateEventGoToPage = function (e) {
    e.preventDefault();
    var pageAddress = Helpers.parseURL(e.currentTarget.href).pathname.replace("\\", "").replace("/", "").replace(/ /g, '').split('/')[0];
    if (pageAddress !== location.host) {
        if (pageAddress) {
            UI.pager.goToPage(UI.pager.getPageId(pageAddress));
        } else {
            UI.pager.goToHomePage();
        }
        
    }
}

Helpers.validationEngineInit = function(form) {
    if (isDefinedAndNotEmpty(form)) {
        $(form).validationEngine();
    }
}

Helpers.hideValidationEngine = function(fields) {
    if (isDefinedAndNotEmpty(fields)) {
        $(fields).validationEngine('hide');
    }
}

Helpers.reInitValidationEngine = function(form) {
    if (isDefinedAndNotEmpty(form)) {
        Helpers.hideValidationEngine(form);
        Helpers.validationEngineInit(form);
    }
}

Helpers.formValidateEngine = function (form) {
    if (isDefinedAndNotEmpty(form)) {
        return jQuery('#' + form).validationEngine('validate');
    }
    return true;
}

Helpers.getQueryParamValue = function (variable) {
    return decodeURIComponent((new RegExp('[?|&]' + variable + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search) || [, ""])[1].replace(/\+/g, '%20')) || null;
}

Helpers.getStoreParamFromURL = function (name) {
    if (UI.getSetting("ispreview")) {
        if (UI.getSetting("ispublished")) {
            //from path
            var names = location.pathname.split('/');
            return names[2] ? names[2] : null;
        } else {
            //from session storage
            return sessionStorage.getItem(name);
        }
    } else {
        return null;
    }
}

Helpers.setQueryParamValue = function (key, value, isRun) {
    var uri = location.search;
    var re = new RegExp("([?&])" + key + "=.*?(&|$)", "i");
    var separator = uri.indexOf('?') !== -1 ? "&" : "?";
    var result = '';
    if (uri.match(re)) {
        //if exist
        result = uri.replace(re, '$1' + key + "=" + value + '$2');
    }
    else {
        //if not exist
        result = uri + separator + key + "=" + value;
    }    
    if (isRun) {
        location.search = result;
    } else {
        return result;
    }
}

Helpers.changeQueryWithoutReload = function (search) {    
    history.replaceState("", document.title, location.pathname + search);
}

Helpers.removeQueryParam = function(key, isRun) {
    var uri = location.search;
    var result = '';
    var re = new RegExp("([?&])" + key + "=.*?(&|$)", "i");    
    if (uri.match(re)) {
        //if exist
        result = uri.replace(re, '$1').replace(/[?&]$/, "");
    }
    if (isRun) {
        location.search = result;
    } else {
        return result;
    }
}

//todo: use userAgent
Helpers.changeViewport = function (width, deviceWidth) {
    //if (/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(userAgent))
    var ratio = width / deviceWidth;
    var viewport = document.getElementById('viewport');
    var content = '';
    if (UI.getDevice().isDesktop()) {
        if (deviceWidth > width) {
            content = 'width=' + deviceWidth + ',initial-scale=' + ratio;
        } else {
            content = 'width=device-width,initial-scale=1.0';
        }
    } else {
        content = 'width=' +
            deviceWidth +
            ',initial-scale=' +
            ratio +
            ',maximum-scale=' +
            ratio +
            ',minimum-scale=' +
            ratio +
            ',user-scalable=no';
    }
       
    if (viewport) {
        viewport.setAttribute('content', content);
    } else {
        //if viewport not exist
        var meta = document.createElement('meta');
        meta.setAttribute('name', 'viewport');
        meta.id = 'viewport';
        meta.setAttribute('content', content);
        $('head').append(meta);
    }
}

Helpers.bindViewportToResize = function (width, deviceWidth) {
    Helpers.changeViewport(width, deviceWidth);
    var handler = function () {        
        Helpers.changeViewport(Helpers.getWindowWidth(), UI.getDevice().getWidth());
		Helpers.checkSiteWrapperHeightMobile();
    };
    $(window).off("resize", handler).on("resize", handler);
    $(window).off("orientationchange", handler).on("orientationchange", handler);
}

Helpers.getWindowWidth = function () {    
    return ((window.orientation === 90 || window.orientation === -90) && screen.width < screen.height) ? screen.height : screen.width;
}

Helpers.isComponentInsideComponent = function(component, componentParent) {
    var x1 = parseInt(component.getProperty(TOP, UI.getDefaultDeviceId()).value);
    var y1 = parseInt(component.getProperty(LEFT, UI.getDefaultDeviceId()).value);

    var componentHeight = component.getProperty(HEIGHT, UI.getDefaultDeviceId());
    var x2 = parseInt(componentHeight != null ? componentHeight.value : 0) + x1;
    var componentWidth = component.getProperty(WIDTH, UI.getDefaultDeviceId());
    var y2 = parseInt(componentWidth != null ? componentWidth.value : 0) + y1;

    var x3 = parseInt(componentParent.getProperty(TOP, UI.getDefaultDeviceId()).value);
    var y3 = parseInt(componentParent.getProperty(LEFT, UI.getDefaultDeviceId()).value);

    componentHeight = componentParent.getProperty(HEIGHT, UI.getDefaultDeviceId());
    var x4 = parseInt(componentHeight != null ? componentHeight.value : 0) + x3;
    componentWidth = componentParent.getProperty(WIDTH, UI.getDefaultDeviceId());
    var y4 = parseInt(componentWidth != null ? componentWidth.value : 0) + y3;

    if (x1 >= x3 && x1 <= x4 && x2 >= x3 && x2 <= x4 && y1 >= y3 && y1 <= y4 && y2 >= y3 && y2 <= y4) {
        return true;
    } else {
        return false;
    }
}

Helpers.isExistsByClassName = function (classname) {
    return $('.' + classname).length > 0;
}

Helpers.cleanSelected = function () {
    clipBoard.selectedItem(null);
    clipBoard.itemForClipboard(null);
    while (grouping.selectedItems().length > 0) {
        Grouping.dropItem(grouping.selectedItems()[0]);
    }
    $('.' + GROUP_WRAPPER).remove();
}

Helpers.checkSiteWrapperHeightMobile = function (height) {
    if (UI.getSetting('ispreview')) {
        if (height == null) {
            height = $('.site-wrapper')[0].scrollHeight;
        }
        $('.site-wrapper').css('height', height + 'px');
        var bgHeight = height + $('.adv').height() + 1;
        if (!UI.getDevice().isDesktop() || $('#bgpreview #bgsite').height() < bgHeight ) {            
            $('#bgpreview #bgsite').css('height', bgHeight + 'px');
        }
        return height;
    } else {
        return null;
    }
}

Helpers.removeSelectWrapper = function() {
    $('.' + SELECT_WRAPPER).remove();
}

Helpers.hideRuler = function() {
    $('.hRule').css('display', 'none');
    $('.vRule').css('display', 'none');
    $('.ruler.corner').css('display', 'none');
}

Helpers.createUserBar = function(callbacks) {
    var dropdown = document.createElement('span');
    dropdown.className = 'dropdown';
    var a = document.createElement('a');
    a.dataset.toggle = 'dropdown';
    a.dataset.bind = 'text: user().fullName';
    var ul = document.createElement('ul');
    ul.className = 'dropdown-menu dropdown-menu-right';
    var liProfile = document.createElement('li');
    var liProfileA = document.createElement('a');
    liProfileA.innerHTML = 'Profile';
    liProfileA.addEventListener('click', callbacks.profile);
    liProfile.appendChild(liProfileA);
    var liSignOut = document.createElement('li');
    var liSignOutA = document.createElement('a');
    liSignOutA.innerHTML = 'Sign Out';
    liSignOutA.addEventListener('click', callbacks.signout);
    liSignOut.appendChild(liSignOutA);

    ul.appendChild(liProfile);
    ul.appendChild(liSignOut);

    dropdown.appendChild(a);
    dropdown.appendChild(ul);

    var block = $('#sign-out-block-wrapper');
    block.html(dropdown);
    setTimeout(function() {
            ko.cleanNode(block[0]);
            ko.applyBindings({ user: UI.authService.user }, block[0]);
        },
        0);
}

Helpers.allowDelete = function (component) {
    return component.proto.name !== STORE_CART &&
        component.proto.name !== STORE_THANK_YOU &&
        component.proto.name !== STORE_PRODUCT;
}

Helpers.allowHide = function (component) {
    return component.proto.name !== STORE_CART &&
        component.proto.name !== STORE_THANK_YOU &&
        component.proto.name !== STORE_PRODUCT &&
        component.proto.name !== SIGNIN;
}

Helpers.parseURL = function (urlString) {
    var url = {
        protocol: '',
        host: '',
        hostname: '',
        port: '',
        pathname: '',
        origin: '',
        search: '',
        hash: ''
    };
    var splitDouble = urlString.split('//');    
    var path = '';
    if (splitDouble[1]) {
        url.protocol = splitDouble[0];
        var splitSingle = splitDouble[1].split('/');
        url.host = splitSingle[0];
        url.hostname = url.host.split(':')[0];
        url.port = url.host.split(':')[1];
        url.origin = url.protocol + '//' + url.host;
        path = splitSingle[1];
        
    } else {
        //else this is path
        path = splitDouble[0];
        if (path[0] === '/') {
            path = path.substr(1);
        }
    }

    if (path) {
        var splitQ = path.split('?');
        url.pathname = '/' + splitQ[0];
        if (splitQ[1]) {
            url.search = '?' + splitQ[1].split('#')[0];
            url.hash = '#' + splitQ[1].split('#')[1];
        }
    }

    return url;
}

Helpers.getTextWidth = function (text, fontSize, fontFamily, fontWeight) {
    fontWeight = fontWeight || "normal";

    // if given, use cached canvas for better performance
    // else, create new canvas     
    var canvas = Helpers.textWidthCanvas || (Helpers.textWidthCanvas = document.createElement("canvas"));   
    var context = canvas.getContext("2d");
    var font = fontWeight + ' ' + fontSize + 'px ' + fontFamily;

    context.font = font;

    var metrics = context.measureText(text);
    return metrics.width;
};

Helpers.hasInputClass = function (target) {
    var hasStdInput = $(target).closest('.std-input').length > 0;
    var hasStdTextArea = $(target).closest('.std-textarea').length > 0;
    var hasAceTextInput = $(target).closest('.ace_text-input').length > 0;
    var hasCkeFocus = $(target).closest('.cke_focus').length > 0;

    return hasStdInput || hasStdTextArea || hasAceTextInput || hasCkeFocus;
};

Helpers.findElementsUnder = function (element, container) {
    var left = parseInt(element.css(LEFT));
    var right = left + element.outerWidth(true);
    var bottom = parseInt(element.css(TOP)) + element.outerHeight(true);
    var elements = [];
    container.children().each(function (index, child) {
        child = $(child);
        if (child.getId() !== element.getId()) {
            var cleft = parseInt(child.css(LEFT));
            var cright = cleft + child.outerWidth(true);
            var ctop = parseInt(child.css(TOP));
            if (ctop >= bottom && ((cleft >= left && cright <= right) || (cleft <= left && cright >= right))) {
                elements.push(child);
            }
        }
    });
    return elements;
}

Helpers.gridLoadPage = function() {
    if ($(".grid-mvc").length) {
        $(".grid-mvc").gridmvc().loadPage();
    }
}

Helpers.getMaxZIndex = function (component) {   
    var result = 0;
    if (component.parentComponent) {
        component.parentComponent.children.forEach(function (child) {
            var zIndex = child.getProperty(Z_INDEX);
            if (zIndex) {
                result = Math.max(result, Number(zIndex.value));
            }
        });
    }
    return result;
}

Helpers.showToastMessage = function (type, title, message) {
    if (type) {
        new PNotify({
            title: title || (type.capitalize() + ' message!'),
            text: message || '',
            icon: false,
            type: type
        });
    }
}

Helpers.convertToUniversalUrl = function (url) {
    if (url && url.replace) {
        return url.replace("https:", "").replace("http:", "");
    }
    return url;
}

Helpers.initCustomTreeView = function (tree, callback) {
    tree.find('.tree-view-item-link').find('i').bind('click',
        function (e) {
            e.stopPropagation();
            //toogle collapse
            $(this).closest('.tree-view-item').find('.tree-view-sub').toggle();
            if ($(this).hasClass('fa-chevron-right')) {
                $(this).removeClass('fa-chevron-right');
                $(this).addClass('fa-chevron-down');
            } else {
                $(this).addClass('fa-chevron-right');
                $(this).removeClass('fa-chevron-down');
            }
        });
    tree.find('.tree-view-item-link').bind('click',
        function(e) {
            callback(e.currentTarget.dataset.id);
        });
}

Helpers.initDatePicker = function (selector, options) {
    options = defined(options) ? options : {};
    $(selector).removeClass('hasDatepicker');
    $(selector).datepicker(options);
    $('.ui-datepicker').on('click', function (e) {
        e.preventDefault();
        e.stopPropagation();
    });
}

Helpers.bloggingPostCreate = function(component) {
    if (component.displayName === BLOGGING) {
        var bloggingServicePage = new Component().createNew(UI.basicComponentRepository.lookupData({ name: PAGE_COMPONENT }), true);
        var savedPageId = component.getProperty(PARENT_PAGE).value;
        if (savedPageId && !UI.pager.pages.where({ id: savedPageId }).any()) {
            bloggingServicePage.id = savedPageId;
        }
        var title = 'Blog ' + UI.siteComponentRepository.lookupDataSet({ displayName: BLOGGING }).length;
        bloggingServicePage.setProperty(TITLE, title, true);
        bloggingServicePage.setProperty(ISSERVICE, 'true', true);
        bloggingServicePage.setProperty(NAME, bloggingServicePage.id, true);
        UI.siteComponentRepository.appendTo(bloggingServicePage, UI.getMain());
        var pagerBloggingServicePage = new Page({
            id: bloggingServicePage.id,
            title: title,
            name: bloggingServicePage.id,
            parentPage: null
        });
        UI.pager.pages.push(pagerBloggingServicePage);
        component.setProperty(PARENT_PAGE, bloggingServicePage.id);
        Pager.renderTopMenuSelect();
    } else {
        UI.siteComponentRepository.lookupDataSet({ displayName: BLOGGING }, component).forEach(function(item) {
            Helpers.bloggingPostCreate(item);
        });
    }
}

Helpers.bloggingPostDelete = function (component) {
    if (component.displayName === BLOGGING) {
        var bloggingServicePageId = component.getProperty(PARENT_PAGE).value;
        if (bloggingServicePageId) {
            _.remove(UI.pager.pages, function (item) { return bloggingServicePageId === item.id; });
            UI.siteComponentRepository.remove({ id: bloggingServicePageId });
            UI.pager.pages.where({ parentPage: bloggingServicePageId }).forEach(function (page) {
                page.parentPage = null;
                var pc = page.getComponent();
                pc.setProperty(PARENT_PAGE, null);
            });
            Pager.renderTopMenuSelect();
        }
    } else {
        UI.siteComponentRepository.lookupDataSet({ displayName: BLOGGING }, component).forEach(function (item) {
            Helpers.bloggingPostDelete(item);
        });
    }
}

Helpers.refreshAllBloggingComponents = function() {
    UI.siteComponentRepository.lookupDataSet({ displayName: BLOGGING }).forEach(function (item) {
        UI.actionService.runActionForComponent(item, ACTION_REMOVE_FROM_FORM, true);
        UI.actionService.runActionForComponent(item, ACTION_ADD_TO_FORM, true);
    });
};
var TemplateFactory = function(){}

TemplateFactory.templateFor = function (component, type, relativeComponent) {
    if (type === EDITOR_TEMPLATE) {
        if (!UI.getDevice().isDesktop() && component.proto.slaveEditorTemplate) {
            switch (component.proto.name) {
                default:
                    return new DefaultSlaveEditorTemplate(component);
            }
        } else {
        switch (component.proto.name) {
            default:
                return new DefaultEditorTemplate(component);
        }
        }
    } else if (type === VIEWER_TEMPLATE) {
        if (!UI.getDevice().isDesktop() && component.proto.slaveTemplate) {
            switch (component.proto.name) {
                default:
                    return new DefaultSlaveViewerTemplate(component);
            }
        } else {
        switch (component.proto.name) {
            case '#signin-login':
            case '#signin-signup':
            case '#signin-signup-success':
            case '#signin-reset-password':
            case '#signin-change-password':
            case '#signin-profile':
            case '#signin-secure-page':
            case '#signin-manage-users':
            case '#signin-manage-users-current':
            case '#signin-send-email':
                return new SignInModalViewerTemplate(component, relativeComponent);
            default:
                return new DefaultViewerTemplate(component);
        }
    }
}
}

var DefaultEditorTemplate = function(component){
    var self = this;
    self.compiledTemplate = "";
    self.context = ContextFactory.contextFor(component, EDITOR_CONTEXT);    
    self.source = component.proto.editorTemplate;
    self.compiledTemplate = HandlebarHelper.compileTemplate(self.source, self.context);
}

var DefaultViewerTemplate  = function(component){
    var self = this;
    self.compiledTemplate = "";
    self.context = ContextFactory.contextFor(component, VIEWER_CONTEXT);
    self.source = component.proto.template;
    self.compiledTemplate = HandlebarHelper.compileTemplate(self.source, self.context);
}

var DefaultSlaveEditorTemplate = function (component) {
    var self = this;
    self.compiledTemplate = "";
    self.context = ContextFactory.contextFor(component,EDITOR_CONTEXT);
    self.source = component.proto.slaveEditorTemplate;
    self.compiledTemplate = HandlebarHelper.compileTemplate(self.source, self.context);
}

var DefaultSlaveViewerTemplate = function (component) {
    var self = this;
    self.compiledTemplate = "";
    self.context = ContextFactory.contextFor(component, VIEWER_CONTEXT);
    self.source = component.proto.slaveTemplate;
    self.compiledTemplate = HandlebarHelper.compileTemplate(self.source, self.context);
}

var SignInModalViewerTemplate = function (component, componentSignIn) {
    var self = this;
    self.compiledTemplate = "";

    var context = {};
    if (componentSignIn) {
        context = ContextFactory.contextFor(componentSignIn, VIEWER_CONTEXT);
    }    

    var source = component.proto.template;
    self.compiledTemplate = HandlebarHelper.compileTemplate(source, context);
}
;
var EditorFactory = function () { }

EditorFactory.proxy = function () {
    var component = this;
    var name = component.proto.name;
    if (!UI.getDevice().isDesktop() && EditorFactory.editorForComponent[name]) {        
        return EditorFactory.editorForComponent['default' + SLAVE](component);
    }
    return EditorFactory.editorForComponent[name] ? EditorFactory.editorForComponent[name](component) : EditorFactory.editorForComponent.default(component);
}

EditorFactory.editorForComponent = {};

EditorFactory.editorForComponent[MENU] = function (component) { return new MenuEditor(component); }
EditorFactory.editorForComponent[IMAGE] = function (component) { return new ImageEditor(component); }
EditorFactory.editorForComponent['frame'] = function (component) { return new FrameEditor(component); }
EditorFactory.editorForComponent[PANEL] = function (component) { return new PanelEditor(component); }
EditorFactory.editorForComponent[FORM] = function (component) { return new FormEditor(component); }
EditorFactory.editorForComponent[LABEL] = function (component) { return new LabelEditor(component); }

EditorFactory.editorForComponent[TEXTBOX] = function (component) { return new TextboxEditor(component); }
EditorFactory.editorForComponent[TEXTAREA] = function (component) { return new TextboxEditor(component); }

EditorFactory.editorForComponent[CHECKBOX] = function (component) { return new CheckboxEditor(component); }
EditorFactory.editorForComponent[CAPTCHA] = function (component) { return new CaptchaEditor(component); }
EditorFactory.editorForComponent[RADIOLIST] = function (component) { return new RadiolistEditor(component); }
EditorFactory.editorForComponent[SELECTLIST] = function (component) { return new SelectlistEditor(component); }
EditorFactory.editorForComponent[SUBMIT] = function (component) { return new SubmitEditor(component); }
EditorFactory.editorForComponent[ATTACHMENT] = function (component) { return new AttachmentEditor(component); }
EditorFactory.editorForComponent[CANCEL] = function (component) { return new CancelEditor(component); }
EditorFactory.editorForComponent[BUTTON] = function (component) { return new ButtonEditor(component); }
EditorFactory.editorForComponent[AUTOCOMPLETE_ADDRESS] = function (component) { return new AutocompleteAddressEditor(component); }

EditorFactory.editorForComponent[BLOGGING] = function (component) { return new BloggingEditor(component); }
EditorFactory.editorForComponent[MAP] = function (component) { return new MapEditor(component); }
EditorFactory.editorForComponent[PARAGRAPH] = function (component) { return new ParagraphEditor(component); }
EditorFactory.editorForComponent[HEADERTEXT] = function (component) { return new HeadertextEditor(component); }
EditorFactory.editorForComponent[GALLERY] = function (component) { return new GalleryEditor(component); }
EditorFactory.editorForComponent[HOUSE_PHOTO_TOUR] = function (component) { return new HousePhotoTourEditor(component); }
EditorFactory.editorForComponent[CONTACT_US] = function (component) { return new ContactUsEditor(component); }
EditorFactory.editorForComponent[HTML_CONTAINER] = function (component) { return new HtmlContainerEditor(component); }
EditorFactory.editorForComponent[SLIDESHOW] = function (component) { return new SlideshowEditor(component); }
EditorFactory.editorForComponent[VIDEO] = function (component) { return new VideoEditor(component); }
EditorFactory.editorForComponent[SOUND] = function (component) { return new SoundEditor(component); }
EditorFactory.editorForComponent[LIST] = function (component) { return new ListEditor(component); }
EditorFactory.editorForComponent[MORTGAGE_CALCULATOR] = function (component) { return MortgageCalculatorEditor(component); }
EditorFactory.editorForComponent[EVALUATE_HOME] = function (component) { return EvaluateHomeEditor(component); }
EditorFactory.editorForComponent['anchor'] = function (component) { return new AnchorEditor(component); }
EditorFactory.editorForComponent[PDF] = function (component) { return new PdfEditor(component); }
EditorFactory.editorForComponent[SIGNIN] = function (component) { return new SignInEditor(component); }
EditorFactory.editorForComponent[STORE_CART_LINK] = function (component) { return new StoreCartLinkEditor(component); }
EditorFactory.editorForComponent[STORE_CATEGORIES] = function (component) { return new StoreCategoriesEditor(component); }
EditorFactory.editorForComponent[STORE_CART] = function (component) { return new StoreCartEditor(component); }
EditorFactory.editorForComponent[STORE_CART_CHECKOUT] = function (component) { return new StoreCartCheckoutEditor(component); }
EditorFactory.editorForComponent[STORE_THANK_YOU] = function (component) { return new StoreThankYouEditor(component); }
EditorFactory.editorForComponent[STORE_PRODUCT] = function (component) { return new StoreProductEditor(component); }
EditorFactory.editorForComponent[STORE] = function (component) { return new StoreEditor(component); }

EditorFactory.editorForComponent[STORE_PRODUCT_IMAGES] = function (component) { return new StoreProductImagesEditor(component); }
EditorFactory.editorForComponent[STORE_PRODUCT_TITLE] = function (component) { return new StoreProductTitleEditor(component); }
EditorFactory.editorForComponent[STORE_PRODUCT_PRICE] = function (component) { return new StoreProductPriceEditor(component); }
EditorFactory.editorForComponent[STORE_PRODUCT_SKU] = function (component) { return new StoreProductSkuEditor(component); }
EditorFactory.editorForComponent[STORE_PRODUCT_DESCRIPTION] = function (component) { return new StoreProductDescriptionEditor(component); }
EditorFactory.editorForComponent[STORE_PRODUCT_OPTIONS] = function (component) { return new StoreProductOptionsEditor(component); }
EditorFactory.editorForComponent[STORE_PRODUCT_QUANTITY] = function (component) { return new StoreProductQuantityEditor(component); }
EditorFactory.editorForComponent[STORE_PRODUCT_ADD_TO_CART] = function (component) { return new StoreProductAddToCartEditor(component); }
EditorFactory.editorForComponent[STORE_PRODUCT_SOCIAL] = function (component) { return new StoreProductSocialEditor(component); }

EditorFactory.editorForComponent[STORE_GALLERY] = function (component) { return new StoreGalleryEditor(component); }
EditorFactory.editorForComponent[STORE_GALLERY_SHOW_MORE] = function (component) { return new StoreGalleryShowMoreEditor(component); }
EditorFactory.editorForComponent[STORE_GALLERY_PRODUCT] = function (component) { return new StoreGalleryProductEditor(component); }
EditorFactory.editorForComponent[STORE_GALLERY_PRODUCT_LABEL] = function (component) { return new StoreGalleryProductLabelEditor(component); }
EditorFactory.editorForComponent[STORE_GALLERY_PRODUCT_TITLE] = function (component) { return new StoreGalleryProductTitleEditor(component); }
EditorFactory.editorForComponent[STORE_GALLERY_PRODUCT_DESCRIPTION] = function (component) { return new StoreGalleryProductDescriptionEditor(component); }
EditorFactory.editorForComponent[STORE_GALLERY_PRODUCT_PRICE] = function (component) { return new StoreGalleryProductPriceEditor(component); }
EditorFactory.editorForComponent[STORE_GALLERY_PRODUCT_IMAGE] = function (component) { return new StoreGalleryProductImageEditor(component); }
EditorFactory.editorForComponent.default = function (component) { return new DivEditor(component); }

EditorFactory.editorForComponent['default' + SLAVE] = function (component) { return new SlaveEditor(component); }

var SlaveEditor = function (component) {
    var element = $(component.getUISelector());
    if (!component.isckeditorworking) {
        EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
        EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT);
        EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
        EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
        EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);

        ColorPickerHelper.bind(element, '#colorInput', component, COLOR);
        ColorPickerHelper.bind(element, '#brcolorInput', component, BORDER_COLOR);
        ColorPickerHelper.bind(element, '#backgroundColorInput', component, BACKGROUND_COLOR);
 
        ColorPickerHelper.bind(element, '#colorHoverInput', component, TEXT_COLOR_HOVER);
        ColorPickerHelper.bind(element, '#brcolorHoverInput', component, BORDER_COLOR_HOVER);
        ColorPickerHelper.bind(element, '#backgroundColorHoverInput', component, BACKGROUND_COLOR_HOVER);

        RangeSliderHelper.bind('#borderRadius', component, element, BORDER_RADIUS, {
            min: 0,
            max: 50,
            postfix: "px"
        }, UI.renderMenus);

        RangeSliderHelper.bind('#borderWidth', component, element, BORDER_WIDTH, {
            min: 0,
            max: 15,
            postfix: "px"
            }, function () {
                if (component.proto.name === BUTTON) {
                    ViewerFactory.calculateButtonLineHeight(component.getUISelector());
                }
        });

        RangeSliderHelper.bind('#fontSize',
            component,
            element,
            FONT_SIZE,
            {
                min: 6,
                max: 50,
                postfix: "px"
            }, function () {
                if (component.proto.name === LIST) {
                    ViewerFactory.calculateHeightForList(component, element);
                }
            });

        EditorEventsFactory.attachPlainEvent('#captionPositionInput',
            element,
            component,
            CHANGE,
            CAPTION_POSITION,
            function(com, el, val) {
                UI.actionService.runActionForComponent(com, ACTION_REMOVE_FROM_FORM, true);
                com.setProperty(CAPTION_POSITION, val);
                UI.actionService.runActionForComponent(com, ACTION_ADD_TO_FORM, true);
                $(com.getUISelector()).highlightSelectedElement(com, true);
            });

        EditorEventsFactory.attachPlainEvent('#scalingInput',
            element,
            component,
            CHANGE,
            IMAGE_STRETCHING,
            function (com, el, val) {
                UI.actionService.runActionForComponent(com, ACTION_REMOVE_FROM_FORM, true);
                com.setProperty(IMAGE_STRETCHING, val);
                UI.actionService.runActionForComponent(com, ACTION_ADD_TO_FORM, true);
                $(com.getUISelector()).highlightSelectedElement(com, true);
            });

    } else {
        var node = element[0];

        if (element.data('ckeditorInstance') != null) {
            console.log('component is allready in edit mode...');
            return;
        }

        //get saved property value
        var property = component.getProperty(TEXT);        
        var text = property != null ? property.value : DEFAULT_PARAGRAPH_TEXT;

        //extend bindings for template, note: there is allready other bindings applied,
        //so need merge them...
        var bindingModel = {
            ckEditor: text
        };

        // bind to ckEditor, you can subscribe here to react on changes..
        var ptext = ko.observable(text);

        ko.observable(node).extend({
            applyBindingsToNode: {
                model: bindingModel,
                extension: ko.utils.extend(ko.dataFor(node), { paragraphtext: ptext() })
            }
        });
    }

}

var StoreCategoriesEditor = function (component) {
    var element = $(component.getUISelector());
    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT);
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
    EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);

    EditorEventsFactory.attachPlainEvent('#fontFamilyInput', element, component, CHANGE, FONT_FAMILY);
    EditorEventsFactory.attachPlainEvent('#textAlignInput', element, component, CHANGE, TEXT_ALIGN);

    ColorPickerHelper.bind(element, '#colorInput', component, COLOR);
    ColorPickerHelper.bind(element, '#brcolorInput', component, BORDER_COLOR);
    ColorPickerHelper.bind(element, '#backgroundColorInput', component, BACKGROUND_COLOR);

    ColorPickerHelper.bind(element, '#colorHoverInput', component, TEXT_COLOR_HOVER);
    ColorPickerHelper.bind(element, '#brcolorHoverInput', component, BORDER_COLOR_HOVER);
    ColorPickerHelper.bind(element, '#backgroundColorHoverInput', component, BACKGROUND_COLOR_HOVER);

    RangeSliderHelper.bind('#borderRadius', component, element, BORDER_RADIUS, {
        min: 0,
        max: 50,
        postfix: "px"
    });

    RangeSliderHelper.bind('#borderWidth', component, element, BORDER_WIDTH, {
        min: 0,
        max: 15,
        postfix: "px"
    }, function () {
        ViewerFactory.calculateButtonLineHeight(component.getUISelector());
    });

    RangeSliderHelper.bind('#fontSize',
        component,
        element,
        FONT_SIZE,
        {
            min: 6,
            max: 50,
            postfix: "px"
        });


}

var StoreCartLinkEditor = function (component) {
    var element = $(component.getUISelector());
    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT);
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
    EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);
    RangeSliderHelper.bind('#fontSize',
            component,
            element,
            FONT_SIZE,
            {
                min: 6,
                max: 50,
                postfix: "px"
            });

    ColorPickerHelper.bind(element, '#color', component, COLOR);

    ColorPickerHelper.bind(element, '#secondary-color', component, SECONDARY_COLOR, function (comp, el, val) {
        el.find('.cart-count').css(COLOR, val);
    });
}

var StoreCartEditor = function (component) {
    var element = $(component.getUISelector());
    ColorPickerHelper.bind(element, '#color', component, COLOR);
    ColorPickerHelper.bind(element, '#bg-color', component, BACKGROUND_COLOR);
    ColorPickerHelper.bind(element, '#divider-color', component, DIVIDER_COLOR);
    EditorEventsFactory.attachPlainEvent('#font-family', element, component, CHANGE, FONT_FAMILY);
    EditorEventsFactory.attachPlainEvent('#text-input', element, component, CHANGE, TEXT);
    EditorEventsFactory.attachPlainEvent('#title-input', element, component, CHANGE, TITLE);
    EditorEventsFactory.attachPlainEvent('#description-input', element, component, CHANGE, DESCRIPTION);
    EditorFactory.runChildrenEditors(component);
}

var StoreCartCheckoutEditor = function (component) {
    var element = $(component.getUISelector());
    ColorPickerHelper.bind(element, '#checkout-color', component, COLOR);
    ColorPickerHelper.bind(element, '#checkout-bg', component, BACKGROUND_COLOR);
    ColorPickerHelper.bind(element, '#checkout-border-color', component, BORDER_COLOR);
    RangeSliderHelper.bind('#checkout-border-width', component, element, BORDER_WIDTH, {
        min: 0,
        max: 16,
        postfix: "px"
    });
    EditorEventsFactory.attachPlainEvent('#checkout-font-family', element, component, CHANGE, FONT_FAMILY);
    RangeSliderHelper.bind('#checkout-border-radius',
        component,
        element,
        BORDER_RADIUS,
        {
            min: 0,
            max: 50,
            postfix: "px"
        });
    EditorEventsFactory.attachPlainEvent('#checkout-text', element, component, CHANGE, TEXT);
}
var StoreThankYouEditor = function (component) {
    var element = $(component.getUISelector());
    ColorPickerHelper.bind(element, '#color', component, COLOR);
    ColorPickerHelper.bind(element, '#bg-color', component, BACKGROUND_COLOR);
    ColorPickerHelper.bind(element, '#divider-color', component, DIVIDER_COLOR);
    EditorEventsFactory.attachPlainEvent('#font-family', element, component, CHANGE, FONT_FAMILY);

    EditorEventsFactory.attachPlainEvent('#title-input', element, component, CHANGE, TITLE);
    EditorEventsFactory.attachPlainEvent('#text-input', element, component, CHANGE, TEXT);
    EditorEventsFactory.attachPlainEvent('#description-input', element, component, CHANGE, DESCRIPTION);
    EditorEventsFactory.attachPlainEvent('#text-order-number', element, component, CHANGE, TEXT_ORDER_NUMBER);
    EditorEventsFactory.attachPlainEvent('#text-total-cost', element, component, CHANGE, TEXT_TOTAL_COST);
    EditorEventsFactory.attachPlainEvent('#text-shipping-to', element, component, CHANGE, TEXT_SHIPPING_TO);
}

EditorFactory.findAssignedParent = function (component, parentName) {
    if (component != null) {
        if (component.proto.name !== parentName) {
            if (component.parentComponent != null) {
                return EditorFactory.findAssignedParent(component.parentComponent, parentName);
            } else {
                return null;
            }
        } else {
            return component;
        }
    } else {
        return component;
    }
}

EditorFactory.refreshStoreGalleryProductLayout = function (product) {
    if (product != null && product.proto.name === STORE_GALLERY_PRODUCT) {
        var gallery = EditorFactory.findAssignedParent(product, STORE_GALLERY);
        if (gallery != null) {
            var productItemsOrderList = {};
            productItemsOrderList[STORE_GALLERY_PRODUCT_IMAGE] = {
                first: 1,
                second: 3
            };
            productItemsOrderList[STORE_GALLERY_PRODUCT_TITLE] = {
                first: 2,
                second: 1
            };
            productItemsOrderList[STORE_GALLERY_PRODUCT_PRICE] = {
                first: 3,
                second: 2
            };
            productItemsOrderList[STORE_GALLERY_PRODUCT_DESCRIPTION] = {
                first: 4,
                second: 4
            };

            var productLayout = product.getProperty(LAYOUT).value;
            product.children.sort(function (a, b) {
                return productItemsOrderList[a.proto.name][productLayout] > productItemsOrderList[b.proto.name][productLayout];
            });

            EditorFactory.refreshStoreGallery(gallery);
        }
    }
}

EditorFactory.refreshStoreGallery = function (component, callback) {
    callback = defined(callback) ? callback : function() {};
    var gallery = EditorFactory.findAssignedParent(component, STORE_GALLERY);
    if (gallery != null) {
        Helpers.removeSelectWrapper();
        var element = $(component.getUISelector());
        var popoverCallbacks = element.data('callbacks');
        var lastElementDiff = element.data('lastElementDiff');
        UI.actionService.runActionForComponent(gallery, ACTION_REMOVE_FROM_FORM, true);
        UI.actionService.runActionForComponent(gallery, ACTION_ADD_TO_FORM, true);
        setTimeout(function () {
            $(element.selector).data('lastElementDiff', lastElementDiff);
            PopoverHelper.bindEvents(element, popoverCallbacks);
            EditorFactory.recalculateStoreGallerySize(gallery);
            callback();
        }, 0);        
    }
}

EditorFactory.runChildrenEditors = function (component) {
    if (component.children.length) {
        _.forEach(component.children,
            function(child) {
                child.editor();
                if (child.children.length) {
                    EditorFactory.runChildrenEditors(child);
                }
            });
    }
}

EditorFactory.resetProperties = function (component, isWithChildren, ignoreList) {
    ignoreList = ignoreList ? ignoreList : [];
    var structs = [];
    var resetProperties = function(comp) {
        _.forEach(comp.properties,
            function (prop) {
                if (ignoreList.indexOf(prop.name) === -1) {
                    var struct = {
                        component: comp,
                        property: prop.name,
                        oldvalue: comp.getProperty(prop.name).value
                    };
                    comp.resetProperty(prop.name);
                    struct.newvalue = comp.getProperty(prop.name).value;
                    structs.push(struct);
                    
                }
            });
    }
    resetProperties(component);
    if (isWithChildren) {
        _.forEach(component.children,
            function(child) {
                structs.addRange(EditorFactory.resetProperties(child, isWithChildren, ignoreList));
            });
    }
    return structs;
}

EditorFactory.recalculateStoreGallerySize = function (component) {
    var element = $(component.getUISelector());
    var products = element.find('.std-store-gallery-product');
    if (products.length > 0) {
        var showMore = element.find('.std-store-gallery-show-more');
        var hrWidth = parseInt(element.find('hr').css('border-top-width'));
        
        var rows = parseInt(element.data().row);
        var cols = parseInt(element.data().col);
        var border = parseInt(element.css(BORDER_WIDTH));

        var productsHeight = 0;
        var rowMaxHeight = 0;
        products.each(function (index, item) {
            if (index % cols === 0) {
                productsHeight += rowMaxHeight;
                rowMaxHeight = $(item).outerHeight(true);
            } else {
                if ($(item).outerHeight(true) > rowMaxHeight) {
                    rowMaxHeight = $(item).outerHeight(true);
                }
            }            
        });
        productsHeight += rowMaxHeight;
        var sortingBoxHeight = element.find('.sorting-control').outerHeight(true);
        var width = Math.ceil(products.outerWidth(true) * cols) + border * 2;
        var height = Math.ceil(productsHeight + hrWidth * (rows - 1) + border * 2 +
            (showMore.length ? 70 : 0) + sortingBoxHeight);

        component.setProperty(HEIGHT, height + 'px');
        element.outerHeight(height);

        var minWidth = Resizer.checkMinMaxWidth(element[0], width, true);        
        component.setProperty(WIDTH, minWidth + 'px');
        element.outerWidth(minWidth);
        //recalculate page size
        Resizer.recalculateHeaderFooterAndPageSize(element.parent('.page'));
    } else {
        return null;
    }
}

var StoreGalleryEditor = function (component) {
    var element = $(component.getUISelector());
    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH, function() {        
        EditorFactory.refreshStoreGallery(component, function() {
            $('#heightInput').val(component.getProperty(HEIGHT).value);
        });
    });
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT, function() {        
        EditorFactory.refreshStoreGallery(component, function () {
            $('#heightInput').val(component.getProperty(HEIGHT).value);
        });
    });
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT, function() {        
        EditorFactory.refreshStoreGallery(component);
    });
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP, function() {        
        EditorFactory.refreshStoreGallery(component);
    });
    EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX, function() {        
        EditorFactory.refreshStoreGallery(component);
    });

    RangeSliderHelper.bind('#rows', component, element, ROWS, {
        min: 1,
        max: 8,
        useOnFinish: true
    }, function () {
        EditorFactory.refreshStoreGallery(component);        
    });
    RangeSliderHelper.bind('#columns', component, element, COLUMNS, {
        min: 1,
        max: 6,
        useOnFinish: true
    }, function () {
        EditorFactory.refreshStoreGallery(component);        
    });
    ColorPickerHelper.bind(element, '#bg-color', component, BACKGROUND_COLOR);
    ColorPickerHelper.bind(element, '#border-color', component, BORDER_COLOR);
    RangeSliderHelper.bind('#border-width', component, element, BORDER_WIDTH, {
        min: 0,
        max: 16,
        postfix: "px"
    });
    
    ColorPickerHelper.bind(element, '#divider-color', component, DIVIDER_COLOR);
    RangeSliderHelper.bind('#divider-width', component, element, DIVIDER_WIDTH, {
        min: 0,
        max: 16,
        postfix: "px"
    });
    
    EditorFactory.runChildrenEditors(component);        
    
    $('.add-store-gallery-item').click(function (event) {
        var name = event.target.dataset.name;
        var undo, redo, undoredo;
        var product = _.find(component.children, function (child) { return child.proto.name === STORE_GALLERY_PRODUCT });
        if (product) {
            if (event.target.checked || event.target.dataset.type === 'add') {
                //add
                var basicComponent = UI.basicComponentRepository.lookupData({ name: name });
                if (basicComponent) {
                    var newComponent = new Component().createNew(basicComponent, true);
                    undo = function() {
                        UI.siteComponentRepository.remove({id: newComponent.id});
                    }
                    redo = function() {
                        UI.siteComponentRepository.appendTo(newComponent, product);
                    }

                    undoredo = function(action) {
                        action();
                        EditorFactory.refreshStoreGalleryProductLayout(product);
                        
                        setTimeout(function () {                            
                            //todo: change strategy for update UI (hide/show current settings)
                            UI.removeEditor();
                            $(element.selector).click();
                        }, 0);
                    }

                    UI.undoManagerAdd(
                        {
                            undo: function () {
                                undoredo(undo);
                            },
                            redo: function () {
                                undoredo(redo);
                            }
                        });
                    undoredo(redo);
                }
            } else {
                //remove (filter for future if this element not one)
                var children = _.filter(product.children, function(child) { return child.proto.name === name });
                
                undo = function () {
                    _.forEach(children,
                        function (child) {
                            UI.siteComponentRepository.appendTo(child, product);
                        });
                    EditorFactory.refreshStoreGalleryProductLayout(product);
                }
                redo = function () {                    
                    _.forEach(children,
                        function (child) {
                            UI.siteComponentRepository.remove({ id: child.id });
                        });
                }

                undoredo = function (action) {
                    action();
                    EditorFactory.refreshStoreGallery(product);
                    
                    setTimeout(function() {                        
                        //todo: change strategy for update UI (hide/show current settings)
                        UI.removeEditor();
                        $(element.selector).click();
                    }, 0);
                }

                UI.undoManagerAdd(
                    {
                        undo: function () {
                            undoredo(undo);
                        },
                        redo: function () {
                            undoredo(redo);
                        }
                    });
                undoredo(redo);

            }            
        }
    });

    $('#reset-style > .service-button').click(function (event) {
        //get store components and set default prop
        var structs = EditorFactory.resetProperties(component, true, [TOP, LEFT, Z_INDEX]);
        var callback = function(value, comp, prop, index, count) {
            if (index === count - 1) {
                UI.removeEditor();
                EditorFactory.refreshStoreGallery(component);                
            }
        };
        UI.undoManagerAddSimpleArr(structs, callback, callback, true);
    });
    $('#style-setting').hide();
}
var StoreGalleryShowMoreEditor = function (component) {
    var element = $(component.getUISelector());
    ColorPickerHelper.bind(element, '#show-more-bg-color', component, BACKGROUND_COLOR);
    ColorPickerHelper.bind(element, '#show-more-bg-color-hover', component, BACKGROUND_COLOR_HOVER);
    ColorPickerHelper.bind(element, '#show-more-color', component, COLOR);
    ColorPickerHelper.bind(element, '#show-more-color-hover', component, TEXT_COLOR_HOVER);
    ColorPickerHelper.bind(element, '#show-more-border-color', component, BORDER_COLOR);
    ColorPickerHelper.bind(element, '#show-more-border-color-hover', component, BORDER_COLOR_HOVER);
    EditorEventsFactory.attachPlainEvent('#show-more-text-align', element, component, CHANGE, TEXT_ALIGN);
    EditorEventsFactory.attachPlainEvent('#show-more-font-family', element, component, CHANGE, FONT_FAMILY);
    RangeSliderHelper.bind('#show-more-font-size',
        component,
        element,
        FONT_SIZE,
        {
            min: 6,
            max: 40,
            postfix: "px"
        }, function (component) {
            ViewerFactory.calculateButtonLineHeight(component.getUISelector());
        });
    RangeSliderHelper.bind('#show-more-border-radius',
        component,
        element,
        BORDER_RADIUS,
        {
            min: 0,
            max: 50,
            postfix: "px"
        });
    RangeSliderHelper.bind('#show-more-border-width',
        component,
        element,
        BORDER_WIDTH,
        {
            min: 0,
            max: 15,
            postfix: "px"
        }, function(component) {
            ViewerFactory.calculateButtonLineHeight(component.getUISelector());
        });
    EditorEventsFactory.attachPlainEvent('#show-more-text', element, component, CHANGE, TEXT);
}
var StoreGalleryProductEditor = function (component) {
    var element = $(component.getUISelector());
    EditorEventsFactory.attachPlainEvent('#product-text-align', element, component, CHANGE, TEXT_ALIGN, function() {
        EditorFactory.refreshStoreGallery(component);
    });
    SwitcherHelper.bind(element, '#hover-style', component, HOVER_STYLE, function() {
        EditorFactory.refreshStoreGallery(component);
    });
    SwitcherHelper.bind(element, '#product-layout', component, LAYOUT, function () {
        EditorFactory.refreshStoreGalleryProductLayout(component);
    });
}
var StoreGalleryProductTitleEditor = function (component) {
    var element = $(component.getUISelector());
    ColorPickerHelper.bind(element, '#title-color', component, COLOR, function() {
        EditorFactory.refreshStoreGallery(component);
    });
    EditorEventsFactory.attachPlainEvent('#title-font-family', element, component, CHANGE, FONT_FAMILY, function() {
        EditorFactory.refreshStoreGallery(component);
    });
    RangeSliderHelper.bind('#title-font-size',
        component,
        element,
        FONT_SIZE,
        {
            min: 6,
            max: 40,
            postfix: "px"
        }, function() {
            EditorFactory.refreshStoreGallery(component);
        });
}
var StoreGalleryProductDescriptionEditor = function (component) {
    var element = $(component.getUISelector());
    ColorPickerHelper.bind(element, '#description-color', component, COLOR, function() {
        EditorFactory.refreshStoreGallery(component);
    });
    EditorEventsFactory.attachPlainEvent('#description-font-family', element, component, CHANGE, FONT_FAMILY, function() {
        EditorFactory.refreshStoreGallery(component);
    });
    RangeSliderHelper.bind('#description-font-size',
        component,
        element,
        FONT_SIZE,
        {
            min: 6,
            max: 40,
            postfix: "px"
        }, function() {
            EditorFactory.refreshStoreGallery(component);
        });
}
var StoreGalleryProductPriceEditor = function (component) {
    var element = $(component.getUISelector());
    ColorPickerHelper.bind(element, '#price-color', component, COLOR, function() {
        EditorFactory.refreshStoreGallery(component);
    });
    ColorPickerHelper.bind(element, '#price-secondary-color', component, SECONDARY_COLOR, function() {
        EditorFactory.refreshStoreGallery(component);
    });
    EditorEventsFactory.attachPlainEvent('#price-font-family', element, component, CHANGE, FONT_FAMILY, function() {
        EditorFactory.refreshStoreGallery(component);
    });
    RangeSliderHelper.bind('#price-font-size',
        component,
        element,
        FONT_SIZE,
        {
            min: 6,
            max: 40,
            postfix: "px"
        }, function() {
            EditorFactory.refreshStoreGallery(component);
        });
}
var StoreGalleryProductLabelEditor = function (component) {    
    var element = $(component.getUISelector());
    ColorPickerHelper.bind(element, '#label-color', component, COLOR, function() {
        EditorFactory.refreshStoreGallery(component);
    });
    EditorEventsFactory.attachPlainEvent('#label-font-family', element, component, CHANGE, FONT_FAMILY, function() {
        EditorFactory.refreshStoreGallery(component);
    });
    RangeSliderHelper.bind('#label-font-size',
        component,
        element,
        FONT_SIZE,
        {
            min: 6,
            max: 40,
            postfix: "px"
        }, function() {
            EditorFactory.refreshStoreGallery(component);
        });
    ColorPickerHelper.bind(element, '#label-bgcolor', component, BACKGROUND_COLOR, function() {
        EditorFactory.refreshStoreGallery(component);
    });
    SwitcherHelper.bind(element, '#label-position', component, CAPTION_POSITION, function() {
        EditorFactory.refreshStoreGallery(component);
    });    
}
var StoreGalleryProductImageEditor = function (component) {
    var element = $(component.getUISelector());
    SwitcherHelper.bind(element, '#image-ratio', component, IMAGE_RATIO, function() {
        EditorFactory.refreshStoreGallery(component);
    });
}
var StoreEditor = function (component) {
    var element = $(component.getUISelector());
    $('.editable-tooltip').popover('destroy');
    ko.applyBindings({}, $(UI.getConfigurationValue(EDITOR))[0]);
}

var StoreProductEditor = function(component) {
    var element = $(component.getUISelector());    
    $('.add-store-product-item').click(function (event) {
        var name = event.target.dataset.name;
        var undo, redo, undoredo;
        if (event.target.checked || event.target.dataset.type === 'add') {
            //add
            var basicComponent = UI.basicComponentRepository.lookupData({ name: name });
            if (basicComponent) {
                var newComponent = new Component().createNew(basicComponent, true);

                undo = function () {
                    UI.siteComponentRepository.remove({ id: newComponent.id });
                }
                redo = function () {
                    UI.siteComponentRepository.appendTo(newComponent, component);
                    var position = getChildPosition(newComponent, component.getProperty(LAYOUT).value);
                    if (position.top) { newComponent.setProperty(TOP, position.top); }
                    if (position.left) { newComponent.setProperty(LEFT, position.left); }
                    if (position.height) { newComponent.setProperty(HEIGHT, position.height); }
                    if (position.width) { newComponent.setProperty(WIDTH, position.width); }
                    UI.actionService.runActionForComponent(newComponent, ACTION_ADD_TO_FORM, true);                    
                    ko.observable($(newComponent.getUISelector())[0]).extend({
                        applyBindings: UI.getViewModel(component.proto.name, component)
                    });                    
                }

                undoredo = function (action) {
                    action();
                    TransformFactory.refreshStoreProductContainer(component, newComponent);

                    setTimeout(function () {
                        //todo: change strategy for update UI (hide/show current settings)
                        $(element.selector).click();
                        $(UI.getConfigurationValue(EDITOR) + " .accordion .ui-accordion-content")
                            .removeClass("ui-accordion-content-active").hide();
                        $(UI.getConfigurationValue(EDITOR) + " .accordion #store-product-components").show();
                    }, 0);
                }

                UI.undoManagerAdd(
                    {
                        undo: function () {
                            undoredo(undo);
                        },
                        redo: function () {
                            undoredo(redo);
                        }
                    });
                undoredo(redo);
            }
        } else {
            //remove (filter for future if this element not one)
            var children = _.filter(component.children, function(child) { return child.proto.name === name });

            undo = function () {
                _.forEach(children,
                    function (child) {
                        UI.siteComponentRepository.appendTo(child, component);
                        var position = getChildPosition(child, component.getProperty(LAYOUT).value);
                        if (position.top) { child.setProperty(TOP, position.top); }
                        if (position.left) { child.setProperty(LEFT, position.left); }
                        if (position.height) { child.setProperty(HEIGHT, position.height); }
                        if (position.width) { child.setProperty(WIDTH, position.width); }
                        UI.actionService.runActionForComponent(child, ACTION_ADD_TO_FORM, true);
                        ko.observable($(child.getUISelector())[0]).extend({
                            applyBindings: UI.getViewModel(component.proto.name, component)
                        });
                    });
            }
            redo = function () {
                _.forEach(children,
                    function (child) {
                        UI.siteComponentRepository.remove({ id: child.id });
                    });
            }

            undoredo = function (action) {
                action();
                TransformFactory.refreshStoreProductContainer(component);
                setTimeout(function () {
                    //todo: change strategy for update UI (hide/show current settings)
                    $(element.selector).click();
                    $(UI.getConfigurationValue(EDITOR) + " .accordion .ui-accordion-content")
                        .removeClass("ui-accordion-content-active").hide();
                    $(UI.getConfigurationValue(EDITOR) + " .accordion #store-product-components").show();
                }, 0);
            }

            UI.undoManagerAdd(
                {
                    undo: function () {
                        undoredo(undo);
                    },
                    redo: function () {
                        undoredo(redo);
                    }
                });
            undoredo(redo);
        }
    });
    SwitcherHelper.bind(element, '#product-layout', component, LAYOUT, function (com,
        el,
        val) {
        changeLayout(val);
    });
    
    ColorPickerHelper.bind(element, '#bgcolorInput', component, BACKGROUND_COLOR);
    _.forEach(component.children, function(child) {
        child.editor();
    });

    function getChildPosition(child, layout) {
        switch (child.proto.name) {
        case STORE_PRODUCT_IMAGES:
            switch (layout) {
                case 'first':
                    return {
                        top: '35px',
                        left: '0px',
                        width: '640px',
                        height: '450px'
                    }
                case 'second':
                    return {
                        top: '35px',
                        left: '0px',
                        width: '960px',
                        height: '675px'
                    }                
            }
            break;
        case STORE_PRODUCT_TITLE:
            switch (layout) {
                case 'first':
                    return {
                        top: '35px',
                        left: '640px',
                        width: '320px'
                    }
                case 'second':
                    return {
                        top: '710px',
                        left: '0px',
                        width: '640px'
                    }
            }
            break;
        case STORE_PRODUCT_PRICE:
            switch (layout) {
                case 'first':
                    return {
                        top: '105px',
                        left: '640px'
                    }
                case 'second':
                    return {
                        top: '710px',
                        left: '640px'
                    }
            }
            break;
        case STORE_PRODUCT_SKU:
            switch (layout) {
                case 'first':
                    return {
                        top: '85px',
                        left: '640px',
                        width: '320px'
                    }
                case 'second':
                    return {
                        top: '760px',
                        left: '0px',
                        width: '640px'
                    }
            }
            break;
        case STORE_PRODUCT_DESCRIPTION:
            switch (layout) {
                case 'first':
                    return {
                        top: '485px',
                        left: '0px',
                        width: '640px',
                        height: '205px'
                    }
                case 'second':
                    return {
                        top: '780px',
                        left: '0px',
                        width: '640px',
                        height: '285px'
                    }
            }
            break;
        case STORE_PRODUCT_OPTIONS:
            switch (layout) {
                case 'first':
                    return {
                        top: '145px',
                        left: '640px'
                    }
                case 'second':
                    return {
                        top: '740px',
                        left: '640px'
                    }
            }
            break;
        case STORE_PRODUCT_QUANTITY:
            switch (layout) {
                case 'first':
                    return {
                        top: '205px',
                        left: '640px'
                    }
                case 'second':
                    return {
                        top: '790px',
                        left: '640px'
                    }
            }
            break;
        case STORE_PRODUCT_ADD_TO_CART:
            switch (layout) {
                case 'first':
                    return {
                        top: '265px',
                        left: '650px'
                    }
                case 'second':
                    return {
                        top: '850px',
                        left: '650px'
                    }
            }
            break;
        case STORE_PRODUCT_SOCIAL:
            switch (layout) {
                case 'first':
                    return {
                        top: '315px',
                        left: '640px'
                    }
                case 'second':
                    return {
                        top: '900px',
                        left: '640px'
                    }
            }
            break;
        }
    };

    function changeLayout(value) {        
        var structs = [
            {
                component: component,
                property: LAYOUT,
                newvalue: value,
                oldvalue: component.getProperty(LAYOUT).value
            }];
        var structPush = function (child, top, left, width, height) {
            if (top) {
                structs.push({
                    component: child,
                    property: TOP,
                    newvalue: top,
                    oldvalue: child.getProperty(TOP).value
                });
            }
            if (left) {
                structs.push({
                    component: child,
                    property: LEFT,
                    newvalue: left,
                    oldvalue: child.getProperty(LEFT).value
                });
            }
            if (width) {
                structs.push({
                    component: child,
                    property: WIDTH,
                    newvalue: width,
                    oldvalue: child.getProperty(WIDTH).value
                });
            }
            if (height) {
                structs.push({
                    component: child,
                    property: HEIGHT,
                    newvalue: height,
                    oldvalue: child.getProperty(HEIGHT).value
                });
            }
        }
        //todo: calculate max-height        
        switch (value) {
            case 'first':                
                structs.push({
                    component: component,
                    property: HEIGHT,
                    newvalue: '690px',
                    oldvalue: component.getProperty(HEIGHT).value
                });
                break;
            case 'second':                
                structs.push({
                    component: component,
                    property: HEIGHT,
                    newvalue: '1065px',
                    oldvalue: component.getProperty(HEIGHT).value
                });
                break;
        }
        _.forEach(component.children,
            function(child) {
                var position = getChildPosition(child, value);
                structPush(child, position.top, position.left, position.width, position.height);
            });
        UI.undoManagerAddSimpleArr(structs, function (val, com, prop, index, length) {
            if (index === length - 1) {
                UI.actionService.runActionForComponent(component, ACTION_REMOVE_FROM_FORM, true);
                UI.actionService.runActionForComponent(component, ACTION_ADD_TO_FORM, true);
                Helpers.removeSelectWrapper();
                Resizer.recalculateHeaderFooterAndPageSize($(element.selector).parent('.page'));
            }
        }, function (val, com, prop, index, length) {
            if (index === length - 1) {
                UI.actionService.runActionForComponent(component, ACTION_REMOVE_FROM_FORM, true);
                UI.actionService.runActionForComponent(component, ACTION_ADD_TO_FORM, true);
                Helpers.removeSelectWrapper();
                Resizer.recalculateHeaderFooterAndPageSize($(element.selector).parent('.page'));
            }
        }, true);
    }

    $('#display-setting').hide();
}

var StoreProductImagesEditor = function (component) {
    var element = $(component.getUISelector());
    EditorEventsFactory.attachPlainEvent('#images-scalling', element, component, CHANGE, IMAGE_STRETCHING, function (com, el, val) {
        var product = EditorFactory.findAssignedParent(component, STORE_PRODUCT);
        if (product != null) {
            UI.actionService.runActionForComponent(product, ACTION_REMOVE_FROM_FORM, true);
            UI.actionService.runActionForComponent(product, ACTION_ADD_TO_FORM, true);
        }
    });
    ColorPickerHelper.bind(element, '#images-border-color', component, BORDER_COLOR);
    RangeSliderHelper.bind('#images-border-width', component, element, BORDER_WIDTH,
        {
            min: 6,
            max: 50,
            postfix: "px"
        });
}
var StoreProductTitleEditor = function(component) {
    var element = $(component.getUISelector());    
    ColorPickerHelper.bind(element, '#title-color', component, COLOR);
    EditorEventsFactory.attachPlainEvent('#title-font-family', element, component, CHANGE, FONT_FAMILY);
    EditorEventsFactory.attachPlainEvent('#title-text-align', element, component, CHANGE, TEXT_ALIGN);
}

var StoreProductPriceEditor = function(component) {
    var element = $(component.getUISelector());
    ColorPickerHelper.bind(element, '#price-color', component, COLOR);
    ColorPickerHelper.bind(element, '#price-secondary-color', component, SECONDARY_COLOR);
    EditorEventsFactory.attachPlainEvent('#price-font-family', element, component, CHANGE, FONT_FAMILY);
    EditorEventsFactory.attachPlainEvent('#price-text-align', element, component, CHANGE, TEXT_ALIGN);
}

var StoreProductSkuEditor = function(component) {
    var element = $(component.getUISelector());
    ColorPickerHelper.bind(element, '#sku-color', component, COLOR);
    EditorEventsFactory.attachPlainEvent('#sku-font-family', element, component, CHANGE, FONT_FAMILY);
    EditorEventsFactory.attachPlainEvent('#sku-text-align', element, component, CHANGE, TEXT_ALIGN);
}

var StoreProductDescriptionEditor = function(component) {
    var element = $(component.getUISelector());
    ColorPickerHelper.bind(element, '#description-color', component, COLOR);
    EditorEventsFactory.attachPlainEvent('#description-font-family', element, component, CHANGE, FONT_FAMILY);
    EditorEventsFactory.attachPlainEvent('#description-text-align', element, component, CHANGE, TEXT_ALIGN);
}

var StoreProductOptionsEditor = function(component) {
    var element = $(component.getUISelector());
    ColorPickerHelper.bind(element, '#options-color', component, COLOR);
    EditorEventsFactory.attachPlainEvent('#options-font-family', element, component, CHANGE, FONT_FAMILY);
    EditorEventsFactory.attachPlainEvent('#options-text-align', element, component, CHANGE, TEXT_ALIGN);
}

var StoreProductQuantityEditor = function(component) {
    var element = $(component.getUISelector());
    ColorPickerHelper.bind(element, '#quantity-color', component, COLOR);
    EditorEventsFactory.attachPlainEvent('#quantity-font-family', element, component, CHANGE, FONT_FAMILY);
    EditorEventsFactory.attachPlainEvent('#quantity-text-align', element, component, CHANGE, TEXT_ALIGN);
}

var StoreProductAddToCartEditor = function(component) {
    var element = $(component.getUISelector());
    EditorEventsFactory.attachPlainEvent('#add-to-cart-text', element, component, CHANGE, TEXT);
    ColorPickerHelper.bind(element, '#add-to-cart-color', component, COLOR);
    EditorEventsFactory.attachPlainEvent('#add-to-cart-font-family', element, component, CHANGE, FONT_FAMILY);
    EditorEventsFactory.attachPlainEvent('#add-to-cart-text-align', element, component, CHANGE, TEXT_ALIGN);
    ColorPickerHelper.bind(element, '#add-to-cart-bg-color', component, BACKGROUND_COLOR);
    ColorPickerHelper.bind(element, '#add-to-cart-border-color', component, BORDER_COLOR);
    RangeSliderHelper.bind('#add-to-cart-border-radius', component, element, BORDER_RADIUS, {
        min: 0,
        max: 50,
        postfix: "px"
    });
}

var StoreProductSocialEditor = function(component) {
    var element = $(component.getUISelector());
    EditorEventsFactory.attachPlainEvent('#social-text-align', element, component, CHANGE, TEXT_ALIGN);
    EditorEventsFactory.attachPlainEvent('#social-share-to-facebook', element, component, CHANGE, SHARE_TO_FACEBOOK, function(com, el, val) {
        UI.actionService.runActionForComponent(com, 'remove-component-from-form', true);        
        UI.actionService.runActionForComponent(com, 'add-component-to-form', true);
    });
    EditorEventsFactory.attachPlainEvent('#social-share-to-twitter', element, component, CHANGE, SHARE_TO_TWITTER, function (com, el, val) {
        UI.actionService.runActionForComponent(com, 'remove-component-from-form', true);
        UI.actionService.runActionForComponent(com, 'add-component-to-form', true);
    });
    EditorEventsFactory.attachPlainEvent('#social-share-to-gplus', element, component, CHANGE, SHARE_TO_GPLUS, function (com, el, val) {
        UI.actionService.runActionForComponent(com, 'remove-component-from-form', true);
        UI.actionService.runActionForComponent(com, 'add-component-to-form', true);
    });
}

EditorFactory.correctRedirectSuccessLink = function (component, page) {
    var link = component.getProperty(SUCCESS_PAGE_LINK_REDIRECT_BUTTON).value;

    if (page == EDITOR_CONTEXT) {
        if (link == "" && UI.pager != null) {
            component.setProperty(SUCCESS_PAGE_LINK_REDIRECT_BUTTON, UI.pager.pages[0].id);
        }
    } else if (page == VIEWER_CONTEXT) {
        if (UI.pager != null) {
            return UI.pager.pages[0].id;
        }
    }
};

EditorFactory.bindEventsSuccessPage = function (component) {
    $("#description-success-page").bind("keyup", function () {
        component.setProperty(SUCCESS_PAGE_MESSAGE, $("#description-success-page").val());
    });

    $("#headerText").bind("keyup", function () {
        component.setProperty(SUCCESS_PAGE_HEADER_TEXT, $("#headerText").val());
    });

    $("#buttonText").bind("keyup", function () {
        component.setProperty(SUCCESS_PAGE_BUTTON_TEXT, $("#buttonText").val());
    });

    $("#list-pages").bind("change", function () {
        component.setProperty(SUCCESS_PAGE_LINK_REDIRECT_BUTTON, $("#list-pages").find("option:selected").val());
    });

    $("#ffOptionInputPage").bind("change", function () {
        component.setProperty(SUCCESS_PAGE_TEXT_FONT_FAMILY, $("#ffOptionInputPage option:selected").val());
    });

    $("#preview-success-page").bind("click",
        function () {
            ViewerFactory.createModalWindow(component);
        });

    $("#set-default-values").bind("click", function () {
        if (Application.confirmMessage("You really want to reset settings success page?")) {
            EditorFactory.defaultValuesSuccessPage(component);
        }
    });
};

EditorFactory.setEventsToComponent = function (component) {
    ColorPickerHelper.bind("", '#textColor', component, SUCCESS_PAGE_TEXT_COLOR);
    ColorPickerHelper.bind("", '#contentColor', component, SUCCESS_PAGE_CONTENT_COLOR);
    ColorPickerHelper.bind("", '#headerColor', component, SUCCESS_PAGE_HEADER_COLOR);
    ColorPickerHelper.bind("", '#buttonColor', component, SUCCESS_PAGE_BUTTON_COLOR);

    EditorEventsFactory.attachPlainEvent('#ffOptionInputPage', "", component, "", SUCCESS_PAGE_TEXT_FONT_FAMILY);

    RangeSliderHelper.bind('#fontSizePage', component, "", SUCCESS_PAGE_TEXT_FONT_SIZE, {
        min: 20,
        max: 35,
        postfix: "px"
    });
};

EditorFactory.upclickPicture = function (component, ctx, deleteGalleryItem, removePreviewHighlighting, addPreviewHighlighting, showItemSettings, rerenderViewer, changeOrder) {
    $('.upclick-button').upclick(component,
        {
            type: UPCLICK_TYPE_PICTURE,
            multiple: true,
            oncomplete: function (response) {
                var wrapper = JSON.parse(response);
                var files = wrapper.files;

                var index;
                var galleryItems = [];
                var showError = false;
                for (index = 0; index < files.length; ++index) {

                    var src = files[index];
                    if (src.bannedExtension) {
                        showError = true;
                        continue;
                    }
                    //TODO change this to appropriate implementation
                    var basicComponentsFiltered = UI.basicComponentRepository.getAll().where({ name: "gallery-item" });
                    if (basicComponentsFiltered.any()) {

                        var basicComponent = basicComponentsFiltered.firstOrDefault();
                        var galleryItemComponent = new Component().createNew(basicComponent, true);
                        galleryItems.push(galleryItemComponent);
                        var nextOrder;
                        var galleryOrders = [];
                        component.children.forEach(function (item) {
                            galleryOrders.push(item.getProperty("order").value.toInteger());
                        });
                        if (galleryOrders.any()) {
                            var maxOrderedGalleryItem = _.max(galleryOrders);
                            nextOrder = maxOrderedGalleryItem + 1;
                        } else {
                            nextOrder = 0;
                        }
                        galleryItemComponent.setProperty("src", src.url);
                        galleryItemComponent.setProperty("order", nextOrder);
                        UI.siteComponentRepository.appendTo(galleryItemComponent, component);
                        var context = {
                            id: galleryItemComponent.id,
                            src: src.url
                        }
                        var compiledTemplate = HandlebarHelper.compileTemplate(basicComponent.editorTemplate, context);

                        $('#gallery-editor .gallery-items-list').append(compiledTemplate);

                        //apply binding individually each added galleryItemComponent...	context binded to newly added
                        ko.observable($(galleryItemComponent.getUISelector())[0]).extend({ applyBindings: ctx });

                        $(galleryItemComponent.getUISelector()).find('.remove-button').bind('click', function (e) {
                            e.stopPropagation();
                            deleteGalleryItem($(this).closest('.gallery-item').getId());
                        });
                        $(galleryItemComponent.getUISelector()).bind('click', function () {                            
                            removePreviewHighlighting();
                            addPreviewHighlighting($(this).getId());
                            var component = UI.siteComponentRepository.lookupData({ id: $(this)[0].id });
                            showItemSettings(component);
                            $("#linkInput").bind("change", validateLink);
                            validateLink();
                        });
                        $("#linkInput").bind("change", validateLink);

                        $("#linkInput").css("backgroundColor", "white");
                        $(galleryItemComponent.getUISelector()).find('.order-left').bind('click', function (e) {
                            e.stopPropagation();
                            var component = UI.siteComponentRepository.lookupData({ id: $(this).parent()[0].id });
                            changeOrder(component, 'left');
                        });
                        $(galleryItemComponent.getUISelector()).find('.order-right').bind('click', function (e) {
                            e.stopPropagation();
                            var component = UI.siteComponentRepository.lookupData({ id: $(this).parent()[0].id });
                            changeOrder(component, 'right');
                        });
                        removePreviewHighlighting();
                        addPreviewHighlighting($(galleryItemComponent.getUISelector()).getId());
                        showItemSettings(galleryItemComponent);

                        rerenderViewer();

                    }

                }//end for
                UI.undoManagerAddAddElements(galleryItems, rerenderViewer);

                if (showError) {
                    alert('One or more files has unsupported image format. They were not loaded.');
                }

            }
        });
};

EditorFactory.logoImageUploading = function (component) {
    var prefix = "Picture";
    upclick({
        type: UPCLICK_TYPE_PICTURE,
        element: "logo-image-upload-btn",
        action: "/Editor/Upload" + prefix + "?templateId=" + UI.getTemplateProperty("templateId"),
        accept: ".gif, .jpg, .png, .jpeg, .bmp",
        multiple: false,
        onstart: function () {
            Application.addLocker();
        },
        oncomplete: function (response) {
            Application.removeLocker();
            var file = Helpers.ProcessUploadSingleFileCompleted(response, $('#upclick-editor-error'), $('#logo-image-upload-btn'));

            if (file != null) {
                $('.logo-image').attr('src', file.url);
                component.setProperty(SUCCESS_PAGE_IMAGE_LOGO, file.url);
            }
        }
    });

    $('.upclick-container').bind('click', function (e) {
        e.stopPropagation();
    });
};

EditorFactory.defaultValuesSuccessPage = function (component) {
    $("#description-success-page").val(MEESSAGE_EMAIL_SUCCESS_DOMANIAN_NAME);
    component.setProperty(SUCCESS_PAGE_MESSAGE, $("#description-success-page").val());
        
    $("#headerText").val(SECCESS_TITLE_PAGE);
    component.setProperty(SUCCESS_PAGE_HEADER_TEXT, $("#headerText").val());
        
    $("#buttonText").val(SECCESS_BUTTON_TEXT);
    component.setProperty(SUCCESS_PAGE_BUTTON_TEXT, $("#buttonText").val());

    var context = [];
    context.pages = ContextFactory.pages("");
    var compiledTemplate = HandlebarHelper.compileTemplate(UI.pager.getPageParentPageSelect(), context);
    $("#list-pages").html(compiledTemplate);
    component.setProperty(SUCCESS_PAGE_LINK_REDIRECT_BUTTON, $("#list-pages").find(":selected").val());

    $("#headerColor").val(SECCESS_HEADER_COLOR);
    $("#headerColor").change();
    component.setProperty(SUCCESS_PAGE_HEADER_COLOR, $("#headerColor").val());
        
    $("#buttonColor").val(SECCESS_BUTTON_COLOR);
    $("#buttonColor").change();
    component.setProperty(SUCCESS_PAGE_BUTTON_COLOR, $("#buttonColor").val());
        
    $("#textColor").val(SECCESS_TEXT_COLOR);
    $("#textColor").change();
    component.setProperty(SUCCESS_PAGE_TEXT_COLOR, $("#textColor").val());
        
    $("#contentColor").val(SECCESS_CONTENT_COLOR);
    $("#contentColor").change();
    component.setProperty(SUCCESS_PAGE_CONTENT_COLOR, $("#contentColor").val());
    
    $("#fontSizePage").val(SECCESS_FONT_SIZE);
    $("#fontSizePage").change();
    component.setProperty(SUCCESS_PAGE_TEXT_FONT_SIZE, SECCESS_FONT_SIZE);
    RangeSliderHelper.update($("#fontSizePage"),
        {
            from: parseInt(SECCESS_FONT_SIZE)
        });

    $("#ffOptionInputPage").find(":selected").attr("selected", false);
    $("#ffOptionInputPage").find('[value="' + SECCESS_FONT_FAMILY + '"]').attr("selected", true);
    component.setProperty(SUCCESS_PAGE_TEXT_FONT_FAMILY, $("#ffOptionInputPage").find(":selected").val());

    $(".logo-image").attr("src", SUCCESS_LOGO_IMAGE);
    component.setProperty(SUCCESS_PAGE_IMAGE_LOGO, $(".logo-image").attr("src"));

    //EditorFactory.setEventsToComponent(component);
};

// scaling for list component
function scallingItemList(item, change) {
    var image = new Image();
    if (item.length < 1) return;
    item.each(function () {
        var img = $(this).find("img");
        image.src = img.attr("src");
        if (change == "fill") {
            $(this).css("display", "table-cell");
            if (image.naturalWidth > image.naturalHeight)
                img.css({ "max-height": "100%", "max-width": "100%", "height": "auto", "width": "100%" });
            else 
                img.css({ "max-height": "100%", "max-width": "100%", "width": "auto", "height": "100%" });
        } else {
            if (image.naturalWidth > image.naturalHeight)
                img.css({ "max-height": "none", "max-width": "none", "width": "auto", "height": "150px" });
            else 
                img.css({ "max-height": "none", "max-width": "none", "width": "150px", "height": "auto" });
        }
    });
}

var DivEditor = function (component) {
    var element = component.getUISelector();
    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT);
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);

    ColorPickerHelper.bind(element, '#colorInput', component, COLOR);
    ColorPickerHelper.bind(element, '#bgcolorInput', component, BACKGROUND_COLOR);


}

var AnchorEditor = function (component) {
    var element = component.getUISelector();
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
    EditorEventsFactory.attachPlainEvent('#titleInput', element, component, CHANGE, 'name');

    $('#titleInput').keyup(function (e) {
        if (this.value.length < 6) {
            this.value = '#anchor';
        }
        else if (this.value.indexOf('#anchor') !== 0) {
            this.value = '#anchor' + String.fromCharCode(e.which);
        }
    });

}

var FrameEditor = function (component) {
    var element = component.getUISelector();
    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT);
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);

    EditorEventsFactory.attachPlainEvent('#linkInput', element, component, CHANGE, MODE_VALUE, function(com) {
        UI.actionService.runActionForComponent(com, ACTION_REMOVE_FROM_FORM, true);
        UI.actionService.runActionForComponent(com, ACTION_ADD_TO_FORM, true);
    });

    $("#linkInput").on(CHANGE, validateLink);
    $("#linkInput").tooltip({
        placement: 'bottom'
    });
}

var PdfEditor = function (component) {
    var element = component.getUISelector();
    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT);
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);


    EditorEventsFactory.attachPlainEvent('#isShowOptimizedPlaceholder',
        element,
        component,
        CHANGE,
        SHOW_OPTIMIZED_PLACEHOLDER,
        function (component, element, value) {
            value = (value === 'true');
            var src = component.getProperty(IMAGE_PROPERTY).value;
            var newSrc = ContextFactory.prepareImgSrc(src, value);
            UI.actionService.runActionForComponent(component, ACTION_REMOVE_FROM_FORM);
            showPreview(newSrc);
            UI.actionService.runActionForComponent(component, ACTION_ADD_TO_FORM);
        });

    $('#upclick-button').upclick(component,
    {
        type: UPCLICK_TYPE_PDF,
        accept: '.pdf',
        oncomplete: function (response) {
            var file = Helpers.ProcessUploadSingleFileWithErrorLabelCompleted(response, $('.std-pdf-file-uploading-label'), $('#editor #upclick-button'));

            if (file != null) {


                var oldvalue = _.clone(component.getProperty(MODE_VALUE).value);
                var newvalue = _.clone(file.url);
                var oldname = _.clone(component.getProperty(NAME).value);
                var newname = _.clone(file.nativeName);

                var undoredo = function (url, name) {
                    component.setProperty(MODE_VALUE, url);
                    component.setProperty(NAME, name);
                    
                    $('.std-pdf-file-uploading-label').html(name.length ? name : PDF_NOT_UPLOADED);                                       

                    var image = component.getProperty(IMAGE_PROPERTY).value;
                    if (!image || image.indexOf('data:image') !== -1) {
                        Application.addSpinner('pdf-spinner', component.getUISelector());
                        Helpers.getPdfThumbnail(url, function (imgBase64) {
                            UI.actionService.runActionForComponent(component, ACTION_REMOVE_FROM_FORM);
                            component.setProperty(IMAGE_PROPERTY, imgBase64);
                            UI.actionService.runActionForComponent(component, ACTION_ADD_TO_FORM);
                        });
                    }
                }

                undoredo(newvalue, newname);

                UI.undoManagerAdd(
                {
                    undo: function () {
                        undoredo(oldvalue, oldname);
                    },
                    redo: function () {
                        undoredo(newvalue, oldname);
                    }
                });
            }

        }
    });

    var showPreview = function (image) {
        $('#editor-preview-single-image').attr('src', image);
    }

    showPreview(ContextFactory.prepareImgSrc(component.getProperty(IMAGE_PROPERTY).value, component.getProperty(SHOW_OPTIMIZED_PLACEHOLDER).value));

    $('#upload-pdf-placeholder').upclick(component,
        {
            type: UPCLICK_TYPE_PICTURE,
            oncomplete: function (response) {
                var file = Helpers.ProcessUploadSingleFileCompleted(response, $('.upclick-editor-error'), $('#editor #upload-pdf-placeholder'));

                if (file != null) {
                    var oldvalue = _.clone(component.getProperty(IMAGE_PROPERTY).value);
                    var newvalue = _.clone(file.url);

                    var undoredo = function (val) {
                        var newSrc = ContextFactory.prepareImgSrc(val, component.getProperty(SHOW_OPTIMIZED_PLACEHOLDER).value);
                        $('#editor-preview-single-image-hover').attr('src', newSrc);

                        showPreview(newSrc);

                        UI.actionService.runActionForComponent(component, ACTION_REMOVE_FROM_FORM);
                        component.setProperty(IMAGE_PROPERTY, val);
                        UI.actionService.runActionForComponent(component, ACTION_ADD_TO_FORM);
                    }

                    undoredo(newvalue);

                    UI.undoManagerAdd(
                        {
                            undo: function () {
                                undoredo(oldvalue);
                            },
                            redo: function () {
                                undoredo(newvalue);
                            }
                        });
                }

            }
        });

    $(".close.remove-pdf-placeholder").click(function (event) {
        if ($('#editor-preview-single-image').attr('src').indexOf('data:image') === -1) {
            var oldvalue = _.clone(component.getProperty(IMAGE_PROPERTY).value);
            var newvalue;

            var undoredo = function(val) {
                showPreview(val);
                UI.actionService.runActionForComponent(component, ACTION_REMOVE_FROM_FORM);
                component.setProperty(IMAGE_PROPERTY, val);
                UI.actionService.runActionForComponent(component, ACTION_ADD_TO_FORM);
            }

            if (component.getProperty(MODE_VALUE).value) {
                Application.addSpinner('pdf-spinner', component.getUISelector());
                Helpers.getPdfThumbnail(component.getProperty(MODE_VALUE).value,
                    function(imgBase64) {
                        newvalue = _.clone(imgBase64);
                        undoredo(newvalue);
                    });
            } else {
                newvalue = '';
                undoredo(newvalue);
            }

            UI.undoManagerAdd(
                {
                    undo: function() {
                        undoredo(oldvalue);
                    },
                    redo: function() {
                        undoredo(newvalue);
                    }
                });
        }
    });

    ko.applyBindings({}, $('#editor')[0]);
}

var MortgageCalculatorEditor = function (component) {
    var element = component.getUISelector();

    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT);
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
    EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);


    ColorPickerHelper.bind(element, '#colorInput', component, COLOR);
    ColorPickerHelper.bind(element, '#brcolorInput', component, BORDER_COLOR);


    EditorEventsFactory.attachPlainEvent('#fontOptionInput', element, component, CHANGE, FONT_FAMILY);
    RangeSliderHelper.bind('#fontSize', component, element, FONT_SIZE, {
        min: 6,
        max: 50,
        postfix: "px"
    }, function () {
        var height = 0;
        var lastChild = $(element).children(':last')[0];
        height += lastChild.offsetTop;
        height += lastChild.offsetHeight;

        $(element).css(HEIGHT, height);
        component.setProperty(HEIGHT, height + "px");
        $(element).highlightSelectedElement(component, true);
        var pageElem = $(element).parent('.page');
        Resizer.recalculateHeaderFooterAndPageSize(pageElem);
    });


    RangeSliderHelper.bind('#borderWidth', component, element, BORDER_WIDTH, {
        min: 0,
        max: 15,
        postfix: "px"
    });
    RangeSliderHelper.bind('#borderRadius', component, element, BORDER_RADIUS, {
        min: 0,
        max: 50,
        postfix: "px"
    });

    EditorEventsFactory.attachPlainEvent('#titleInput', element, component, CHANGE, "title", function () {
        rerenderViewer();
    });

    var rerenderViewer = function () {
        var template = TemplateFactory.templateFor(component, VIEWER_TEMPLATE).compiledTemplate;
        $(component.getUISelector()).html($(template).html());
    }
}

var EvaluateHomeEditor = function (component) {
    var element = component.getUISelector();

    EditorFactory.addedTooltip(["dataToogleComponent"], "bottom");

    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT);
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
    EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);

    EditorEventsFactory.attachPlainEvent('#titleInput', element, component, CHANGE, "title", function () {
        rerenderViewer();
    });

    EditorEventsFactory.attachPlainEvent('#mail-history',
        element,
        component,
        CHANGE,
        MAIL_HISTORY_PROPERTY,
        function(component, element, value) {});

    EditorEventsFactory.attachPlainEvent('#formSubject', element, component, CHANGE, FORM_SUBJECT);

    $('#required-fields').find("input[type='checkbox']").bind(CHANGE,
        function (event) {
            var checked = event.target.checked;
            var value = event.target.value;
            var newvalue = component.getProperty(REQUIRED_FIELD).value.split(',');
            var index = newvalue.indexOf(value);
            if (checked) {
                newvalue.push(value);
                newvalue = _.uniq(newvalue);
            } else {
                if (index !== -1) {
                    newvalue.splice(index, 1);
                }
            }
            UI.undoManagerAddSimple(component, REQUIRED_FIELD, newvalue.join(','), function () { }, true);
        });

    $("#useCaptcha").bind(CHANGE,
        function (event) {
            var value = event.target.checked;
            var defaultHeight = component.proto.getProperty(HEIGHT).value;
            var structs = [
                {
                    component: component,
                    property: USE_CAPTCHA,
                    newvalue: value.toString(),
                    oldvalue: _.clone(component.getProperty(USE_CAPTCHA).value)
                },
                {
                    component: component,
                    property: HEIGHT,
                    newvalue: value ? parseInt(defaultHeight) + 55 + 'px' : defaultHeight,
                    oldvalue: _.clone(component.getProperty(HEIGHT).value)
                }
            ];
            var callback = function (val, com, prop, index, length) {
                if (index === length - 1) {
                    UI.actionService.runActionForComponent(com, ACTION_REMOVE_FROM_FORM, true);
                    UI.actionService.runActionForComponent(com, ACTION_ADD_TO_FORM, true);
                    $(element).highlightSelectedElement(com, true);
                }
            }
            UI.undoManagerAddSimpleArr(structs, callback, callback, true);
        });

    EditorEventsFactory.attachPlainEvent('#typeInput', element, component, CHANGE, "type");

    EditorEventsFactory.attachPlainEvent('#emailInput', element, component, CHANGE, "mode-value");

    ColorPickerHelper.bind(element, '#colorInput', component, COLOR);
    ColorPickerHelper.bind(element, '#brcolorInput', component, BORDER_COLOR);
    ColorPickerHelper.bind(element, '#backgroundColorInput', component, BACKGROUND_COLOR);
    
    EditorEventsFactory.attachPlainEvent('#fontOptionInput', element, component, CHANGE, FONT_FAMILY);

    RangeSliderHelper.bind('#fontSize', component, element, FONT_SIZE, {
        min: 6,
        max: 50,
        postfix: "px"
    }, function () {
        var height = 0;
        var lastChild = $(element).children(':last')[0];
        height += lastChild.offsetTop;
        height += lastChild.offsetHeight;

        $(element).css(HEIGHT, height);
        component.setProperty(HEIGHT, height + "px");
        $(element).highlightSelectedElement(component, true);
        var pageElem = $(element).parent('.page');
        Resizer.recalculateHeaderFooterAndPageSize(pageElem);
    });

    RangeSliderHelper.bind('#borderWidth', component, element, BORDER_WIDTH, {
        min: 0,
        max: 15,
        postfix: "px"
    });

    RangeSliderHelper.bind('#borderRadius', component, element, BORDER_RADIUS, {
        min: 0,
        max: 50,
        postfix: "px"
    });

    var rerenderViewer = function () {
        var template = TemplateFactory.templateFor(component, VIEWER_TEMPLATE).compiledTemplate;
        $(component.getUISelector()).html($(template).html());
    };

    //success page view
    var successPageMasterLink = component.getProperty(SUCCESS_PAGE_MASTER_LINK).value;
    var successPageComponent = (successPageMasterLink && successPageMasterLink !== component.id)
        ? UI.siteComponentRepository.lookupData({ id: successPageMasterLink })
        : component;
    EditorFactory.bindEventsSuccessPage(successPageComponent);
    EditorFactory.setEventsToComponent(successPageComponent);
    EditorFactory.logoImageUploading(successPageComponent);
    EditorFactory.correctRedirectSuccessLink(successPageComponent, EDITOR_CONTEXT);

    SwitcherHelper.bind(element, '#useSuccessPageAnyway', component, SUCCESS_PAGE_MASTER_LINK, function (com, prop, value) {
        UI.callEditor(com);
        $(UI.getConfigurationValue(EDITOR) + " .accordion .ui-accordion-content").removeClass("ui-accordion-content-active").hide();
        $(UI.getConfigurationValue(EDITOR) + " .accordion .content-success-settings").addClass("ui-accordion-content-active").show();
        $(UI.getConfigurationValue(EDITOR) + " .accordion .header-success-settings").addClass("ui-accordion-header-active").show();
    });
};

var MenuEditor = function (component) {
    MenuHelper.renderSubstringListMenu(component.getUISelector()); // for nodeName == A
    var element = component.getUISelector();
    $(element).css({ "text-overflow": "ellipsis", "white-space": "nowrap" });
    
    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT, function (com, el, val) {
        ViewerFactory.calculateMenuLineHeight(component, element);
        $(element).height(val);
        $(el).highlightSelectedElement(com, true);
    });
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
    ColorPickerHelper.bind(element, '#bgcolorhoverInput', component, BACKGROUND_COLOR_HOVER);
    ColorPickerHelper.bind(element, '#txtcolorhoverInput', component, TEXT_COLOR_HOVER);
    ColorPickerHelper.bind(element, '#borderColorInput', component, BORDER_COLOR);
    ColorPickerHelper.bind(element, '#brcolorhoverInput', component, BORDER_COLOR_HOVER);
    ColorPickerHelper.bind(element, '#colorInput', component, COLOR, UI.renderMenus);
    ColorPickerHelper.bind(element, '#bgcolorInput', component, BACKGROUND_COLOR);
    EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);
    EditorEventsFactory.attachPlainEvent('#fontOptionInput', element, component, CHANGE, FONT_FAMILY);
    
    SwitcherHelper.bind(element, '#stretchToFullWidth', component, STRETCH_TO_FULL_WIDTH);
    StretcherFactory.onStretchChange(component);
    RangeSliderHelper.bind('#marginsWidth', component, element, MARGINS_WIDTH, {
        min: 0,
        max: 80,
        postfix: "px"
    },
        function (component, element, value) {
            StretcherFactory.enableMarginsToStretchedComponent(component, element, value)
        }
    );

    EditorFactory.changeFixPosition('#pinnedPosition', 'input', component);
    SwitcherHelper.bind(element, '#isPinedSwitcher', component, IS_PINED);    

    EditorEventsFactory.attachPlainEvent('#styleInput', element, component, CHANGE, "predefined", function (com, el, val) {
        if (CHANGE) {
            if (val == "vertical") {
                $(element).css("line-height", "");
            }
        }
        com.setProperty("predefined", val);
        UI.renderMenus();

        $(el).highlightSelectedElement(component, true);

        if (val == "vertical") {
            var height = 0;
            $(el + " ul").first().children().each(function () {
                height += $(this)[0].offsetHeight;
            });
            $('#heightInput').val(height).change();
        }
        else {
            var height = undefined;
            var basicMenuComponentsFiltered = UI.basicComponentRepository.getAll().where({ name: component.proto.name });

            if (basicMenuComponentsFiltered.any()) {
                var basicMenuComponent = basicMenuComponentsFiltered.firstOrDefault();
                var height = basicMenuComponent.getProperty(HEIGHT).value;
            }
            if (height != undefined) {
                $('#heightInput').val(height).change();
            }
        }
        ViewerFactory.calculateMenuLineHeight(com, el);
    });

    RangeSliderHelper.bind('#offsetX', component, element, OFFSET_X, {
        min: EditorFactory.checkMinRange(component, true),
        max: 600,
        postfix: "px"
    }, function () {
        UI.actionService.runActionForComponent(component, 'remove-component-from-form', true);
        UI.actionService.runActionForComponent(component, 'add-component-to-form', true);
        Helpers.removeSelectWrapper();
    });

    RangeSliderHelper.bind('#offsetY', component, element, OFFSET_Y, {
        min: EditorFactory.checkMinRange(component, false),
        max: 600,
        postfix: "px"
    }, function () {
        UI.actionService.runActionForComponent(component, 'remove-component-from-form', true);
        UI.actionService.runActionForComponent(component, 'add-component-to-form', true);
        Helpers.removeSelectWrapper();
    });



    function SetMenuSize(style) {
        if (style == "vertical") {
            var liHeight = parseInt($(element + ' > ul > li').css(HEIGHT));
            var liCount = $(element).find('li').length;
            var result = liCount * liHeight;
            if (result < 20) {
                result = 20;
            }
            $('#heightInput').val(result).change();
        }
        else {
            var liHeight = parseInt($(element + ' > ul > li > a').css(HEIGHT));
            var result = liHeight;
            if (result < 20) {
                result = 20;
            }
            $('#heightInput').val(result).change();
        }
    }

    RangeSliderHelper.bind('#fontSize', component, element, FONT_SIZE, {
        min: 6,
        max: 50,
        postfix: "px"
    }, function () {
        UI.renderMenus();
        component.setProperty(WIDTH, $(element).css('width'));
    });
    RangeSliderHelper.bind('#borderRadius', component, element, BORDER_RADIUS, {
        min: 0,
        max: 50,
        postfix: "px"
    }, UI.renderMenus);
    RangeSliderHelper.bind('#borderWidth', component, element, BORDER_WIDTH, {
        min: 0,
        max: 15,
        postfix: "px"
    }, UI.renderMenus);

    ko.applyBindings({}, $('#editor')[0]);
}

var ContactUsEditor = function (component) {
    var element = component.getUISelector();

    EditorFactory.addedTooltip(["dataToogleComponent"], "bottom");

    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT);
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
    EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);

    EditorEventsFactory.attachPlainEvent('#formSubject', element, component, CHANGE, FORM_SUBJECT);

    EditorEventsFactory.attachPlainEvent('#typeInput', element, component, CHANGE, "type", function (com, el, val) {
        rerenderViewer();
    });

    EditorEventsFactory.attachPlainEvent('#titleInput', element, component, CHANGE, "title", function () {
        rerenderViewer();
    });

    $('#required-fields').find("input[type='checkbox']").bind(CHANGE,
        function(event) {
            var checked = event.target.checked;
            var value = event.target.value;
            var newvalue = component.getProperty(REQUIRED_FIELD).value.split(',');
            var index = newvalue.indexOf(value);
            if (checked) {
                newvalue.push(value);
                newvalue = _.uniq(newvalue);
            } else {
                if (index !== -1) {
                    newvalue.splice(index, 1);
                }
            }
            UI.undoManagerAddSimple(component, REQUIRED_FIELD, newvalue.join(','), function() {}, true);
        });

    $("#useCaptcha").bind(CHANGE,
        function(event) {
            var value = event.target.checked; 
            var defaultHeight = component.getProperty(TYPE).value !== "Request a quote"? component.proto.getProperty(HEIGHT).value : '390px';
            var structs = [
                {
                    component: component,
                    property: USE_CAPTCHA,
                    newvalue: value.toString(),
                    oldvalue: _.clone(component.getProperty(USE_CAPTCHA).value)
                },
                {
                    component: component,
                    property: HEIGHT,
                    newvalue: value ? parseInt(defaultHeight) + 75 + 'px' : defaultHeight,
                    oldvalue: _.clone(component.getProperty(HEIGHT).value)
                }
            ];
            var callback = function (val, com, prop, index, length) {
                if (index === length - 1) {
                    UI.actionService.runActionForComponent(com, ACTION_REMOVE_FROM_FORM, true);
                    UI.actionService.runActionForComponent(com, ACTION_ADD_TO_FORM, true);
                    $(element).highlightSelectedElement(com, true);
                }
            }
            UI.undoManagerAddSimpleArr(structs, callback, callback, true);
        });

    EditorEventsFactory.attachPlainEvent('#emailInput', element, component, CHANGE, "mode-value");

    EditorEventsFactory.attachPlainEvent('#mail-history',
        element,
        component,
        CHANGE,
        MAIL_HISTORY_PROPERTY,
        function (component, element, value) { });

    ColorPickerHelper.bind(element, '#colorInput', component, COLOR);
    ColorPickerHelper.bind(element, '#brcolorInput', component, BORDER_COLOR);
    ColorPickerHelper.bind(element, '#backgroundColorInput', component, BACKGROUND_COLOR);

    EditorEventsFactory.attachPlainEvent('#fontOptionInput', element, component, CHANGE, FONT_FAMILY, function () {
    });

    RangeSliderHelper.bind('#fontSize', component, element, FONT_SIZE, {
        min: 6,
        max: 50,
        postfix: "px"
    }, function () {

        var height = 0;
        var lastChild = $(element).children(':last')[0];
        height += lastChild.offsetTop;
        height += lastChild.offsetHeight;

        $(element).css(HEIGHT, height);
        component.setProperty(HEIGHT, height + "px");
        $(element).highlightSelectedElement(component, true);
        var pageElem = $(element).parent('.page');
        Resizer.recalculateHeaderFooterAndPageSize(pageElem);
    });


    RangeSliderHelper.bind('#borderWidth', component, element, BORDER_WIDTH, {
        min: 0,
        max: 15,
        postfix: "px"
    });
    RangeSliderHelper.bind('#borderRadius', component, element, BORDER_RADIUS, {
        min: 0,
        max: 50,
        postfix: "px"
    });

    var rerenderViewer = function () {
        var template = TemplateFactory.templateFor(component, VIEWER_TEMPLATE).compiledTemplate;
        $(component.getUISelector()).html($(template).html());
    }

    //pre-editor
    $('.list-type-option').bind("click", function () {
        var listItemValue = $(this).text();
        $('#typeInput').val(listItemValue).change();
        setPreeditorImg();
        $('.list-type-option').removeClass('active');
        $(this).addClass('active');
        Helpers.removeSelectWrapper();
    });

    function setPreeditorImg() {
        var listItemValue = $('#typeInput').val();
        var image = "<img src=\"/Images/contact-us-types/cu-" + listItemValue.split(" ").join("") + ".png\" />";
        $('#type').empty();
        $('#type').prepend(image);
    }

    setPreeditorImg();

    $('.save-type').bind('click', function () {
        if ($('#typeInput').val() == "Make an Appointment") {
            $('#titleInput').val("Make An Appointment").change();
        } else {
            $('#titleInput').val("Request a Quote").change();
            $('#heightInput').val('390px').change();
        }
        component.isNotApproved = false;

        //publish component added 
        eventsystem.publish('/component/create/', component, element);

        UI.removeEditor();
    });
    //end pre-editor

    //success page view
    var successPageMasterLink = component.getProperty(SUCCESS_PAGE_MASTER_LINK).value;
    var successPageComponent = (successPageMasterLink && successPageMasterLink !== component.id)
        ? UI.siteComponentRepository.lookupData({ id: successPageMasterLink })
        : component;
    EditorFactory.bindEventsSuccessPage(successPageComponent);
    EditorFactory.setEventsToComponent(successPageComponent);
    EditorFactory.logoImageUploading(successPageComponent);
    EditorFactory.correctRedirectSuccessLink(successPageComponent, EDITOR_CONTEXT);

    SwitcherHelper.bind(element, '#useSuccessPageAnyway', component, SUCCESS_PAGE_MASTER_LINK, function (com, prop, value) {        
        UI.callEditor(com);
        $(UI.getConfigurationValue(EDITOR) + " .accordion .ui-accordion-content").removeClass("ui-accordion-content-active").hide();
        $(UI.getConfigurationValue(EDITOR) + " .accordion .content-success-settings").addClass("ui-accordion-content-active").show();
        $(UI.getConfigurationValue(EDITOR) + " .accordion .header-success-settings").addClass("ui-accordion-header-active").show();
    });

    ko.applyBindings({}, $('#editor')[0]);  
}

var VideoEditor = function (component) {
    var element = component.getUISelector();

    EditorFactory.addedTooltip(["rel"], "bottom");

    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT);
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
    EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);

    $('#keep-aspect-button').on('click', function () {
        var width = parseInt($('#widthInput').val());
        var newHeight = parseInt(width * 9 / 16);
        $('#heightInput').val(newHeight + 'px').change();
    });

    EditorEventsFactory.attachPlainEvent('#src', element, component, CHANGE, "src",
		function (com, el, val) {
		    /* 'Refused to display in a frame because 
			 * 	it set 'X-Frame-Options' to 'SAMEORIGIN' workaround' */
		    (function validateUrl(url) {
		        url = (url.lastIndexOf('/') === url.length - 1) ? url.substring(0, url.length - 1) : url;

		        var lastsegment = url.replace(/http:\/\//, "").split("/").pop();
		        lastsegment = lastsegment.replace('watch?v=', '');

		        var prefix = /vimeo/.test(url) ? "http://player.vimeo.com/video/" :
							(/youtu/.test(url) ? "https://www.youtube.com/embed/" : "");

		        if (prefix.length > 0) {
		            $(el).val(prefix + lastsegment);
		            com.setProperty("src", prefix + lastsegment);
		            checkProvider(url, com);
		        } else {
		            //TODO: special cases here - validation error, or url normalization
		            console.log("rejected video url: " + url);
		            $(el).val("");
		            com.setProperty("src", "");
		        }

		    })(val);

		    renderVideo();
    });

    
    EditorEventsFactory.attachPlainEvent('#providerInput', element, component, CHANGE, "provider", function (com, el, val) {
        checkProvider(Helpers.convertToUniversalUrl(component.getProperty(SRC).value), com);
        renderVideo();
    });


    EditorEventsFactory.attachPlainEvent('#autoplay', element, component, CHANGE, "autoplay",
    function (com, el, val) {
        com.setProperty("autoplay", val);
        renderVideo();
    });

    EditorEventsFactory.attachPlainEvent('#loop', element, component, CHANGE, "loop",
     function (com, el, val) {
         com.setProperty("loop", val);
         if (val) {
             $("#rel").prop("checked", false);
             com.setProperty("rel", false);
         }
         renderVideo();
     });


    EditorEventsFactory.attachPlainEvent('#rel', element, component, CHANGE, "rel",
    function (com, el, val) {
        com.setProperty("rel", val);
        if (val) {
            $("#loop").prop("checked", false);
            com.setProperty("loop", false);
        }
        renderVideo();
    });


    var renderVideo = function () {
        var src = component.getProperty(SRC).value;
        ViewerFactory.replacementImageInsteadVideo(element, Helpers.convertToUniversalUrl(src));

    }

    var checkProvider = function (url, com) {
        var providers = ['vimeo', 'youtube'];
        var i;
        for (i = 0; i < providers.length; i++) {
            if (url.toLowerCase().indexOf(providers[i]) >= 0) {
                $('#providerInput').val(providers[i]);
                com.setProperty("provider", providers[i]);
            }
        }
    };


}

var SoundEditor = function (component) {

    var element = component.getUISelector();

    $("#rel").tooltip({placement: 'bottom'});

    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
    EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);
    EditorEventsFactory.attachPlainEvent('#soundautoplay', element, component, CHANGE, AUTOPLAY,
     function (com, el, val) {
         com.setProperty(AUTOPLAY, val);
     });

    EditorEventsFactory.attachPlainEvent('#soundhide', element, component, CHANGE, HIDE,
     function (com, el, val) {
         com.setProperty(HIDE, val);
         if (val) {
             $(element).css("opacity", "0.2");
         } else {
             $(element).css("opacity", "1");
         }
     });

    RangeSliderHelper.bind('#soundpause', component, element, "pause", {
        min: 0,
        max: 60,
        postfix: ""
    });

    var soundautoplaychange = function (element) {
        var autoplay = component.getProperty(AUTOPLAY).value.toBoolean(); 
        if (element != undefined)
        {
            autoplay = element.target.checked;
        }
        
        if (autoplay == true)
            $("#soundpausediv").show();
        else
            $("#soundpausediv").hide();
    }
    $('#soundautoplay').bind("change", soundautoplaychange);
    soundautoplaychange();
    
    var getCalculatedObj = function (obj)
    {
        var exceptid = obj.getProperty('id').value;
        var result = { data: "" };
        var controls = UI.siteComponentRepository.lookupDataSet({ componentId: SOUNDID });
        if (controls.length > 0)
        controls.forEach(function (item) {
            var id = item.getProperty("id").value;
            if (id != exceptid) {
                result.data = result.data + (result.data != "" ? "," : "") + id;
            }            
        });
        return result;
    }

    $('#sound-upload-button').upclick(component,
        {
            type: UPCLICK_TYPE_SOUND,
            accept: ".mp3, .mp4, .m4a, .m4v",
            action_params: getCalculatedObj(component),
            oncomplete: function (response) {
                var file = Helpers.ProcessUploadSingleFileCompleted(response, $('.upclick-editor-error'), $('#editor #sound-upload-button'));
                                
                if (file != null) {
                    var reloadupload = function () {
                        $("#sound_file").text(component.getProperty('title').value);
                        $("#" + component.id).find('.jp-title').html(component.getProperty('title').value);
                        var heightJPlayer = $(component.getUISelector() + JPLAYER_CONTAINER_SUFFIX).outerHeight();
                        component.setProperty(HEIGHT, heightJPlayer + 'px');
                        $(element).css(HEIGHT, heightJPlayer);
                        $(element).highlightSelectedElement(component, true);
                    };

                    var structs = [
                    {
                        component: component,
                        property: "src",
                        newvalue: _.clone(file.url),
                        oldvalue: _.clone(component.getProperty('src').value)
                    },
                    {
                        component: component,
                        property: "title",
                        newvalue: _.clone(file.nativeName),
                        oldvalue: _.clone(component.getProperty('title').value)
                    },
                    {
                        component: component,
                        property: "id",
                        newvalue: _.clone(file.id),
                        oldvalue: _.clone(component.getProperty('id').value)
                    }
                    ];

                    UI.undoManagerAddSimpleArr(structs, reloadupload, reloadupload, true);
                }
            }
        });
}


var PanelEditor = function (component) {
    var element = component.getUISelector();

    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT);
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
    EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);

    EditorFactory.changeFixPosition('#pinnedPosition', 'input', component);
    SwitcherHelper.bind(element, '#isPinedSwitcher', component, IS_PINED);

    ColorPickerHelper.bind(element, '#bgcolorInput', component, BACKGROUND_COLOR);
    ColorPickerHelper.bind(element, '#brcolorInput', component, BORDER_COLOR);

    RangeSliderHelper.bind('#borderWidth', component, element, BORDER_WIDTH, {
        min: 0,
        max: 15,
        postfix: "px"
    });
    RangeSliderHelper.bind('#borderRadius', component, element, BORDER_RADIUS, {
        min: 0,
        max: 50,
        postfix: "px"
    });
    SwitcherHelper.bind(element, '#stretchToFullWidth', component, STRETCH_TO_FULL_WIDTH);
    StretcherFactory.onStretchChange(component);
    RangeSliderHelper.bind('#marginsWidth', component, element, MARGINS_WIDTH, {
        min: 0,
        max: 80,
        postfix: "px"
    },
        function (component, element, value) {
            StretcherFactory.enableMarginsToStretchedComponent(component, element, value)
        }
    );

	RangeSliderHelper.bind('#offsetX', component, element, OFFSET_X, {
        min: EditorFactory.checkMinRange(component, true),
        max: 600,
        postfix: "px"
    }, function () {
        UI.actionService.runActionForComponent(component, 'remove-component-from-form', true);
        UI.actionService.runActionForComponent(component, 'add-component-to-form', true);
        Helpers.removeSelectWrapper();
    });

    RangeSliderHelper.bind('#offsetY', component, element, OFFSET_Y, {
        min: EditorFactory.checkMinRange(component, false),
        max: 600,
        postfix: "px"
    }, function () {
        UI.actionService.runActionForComponent(component, 'remove-component-from-form', true);
        UI.actionService.runActionForComponent(component, 'add-component-to-form', true);
        Helpers.removeSelectWrapper();
    });


    this.layering = this.layering || new LayeringController(component);
    
    ko.applyBindings({}, $('#editor')[0]);
}

var ImageEditor = function (component) {
    var element = component.getUISelector();

    EditorFactory.linkManagementEditor(component, element);
    EditorEventsFactory.attachPlainEvent('#altInput', element, component, CHANGE, ALT);
    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT);
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
    EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);

    $('#primaryImageInput').bind(CHANGE,
        function() {
            var newvalue = $(this).is(':checked');
            var struct = [
                {
                    component: component,
                    property: IS_IMAGE_PRIMARY,
                    newvalue: newvalue.toString(),
                    oldvalue: component.getProperty(IS_IMAGE_PRIMARY).value
                }
            ];
            if (newvalue) {
                //change all images to false on this page
                var page = UI.siteComponentRepository.lookupData({ id: component.parentComponent.id });
                var images = UI.siteComponentRepository.lookupDataSet({ displayName: IMAGE }, page).whereNot({id: component.id});
                struct.addRange(images.map(function(image) {
                    return {
                        component: image,
                        property: IS_IMAGE_PRIMARY,
                        newvalue: false.toString(),
                        oldvalue: image.getProperty(IS_IMAGE_PRIMARY).value
                    }
                }));
            }
            var callback = function (v, c, p, index, length) {
                if (index === length - 1) {
                    Helpers.refreshAllBloggingComponents();
                }
            };
            UI.undoManagerAddSimpleArr(struct, callback, callback, true);
        });

    EditorFactory.changeFixPosition('#pinnedPosition', 'input', component);
    SwitcherHelper.bind(element, '#isPinedSwitcher', component, IS_PINED);


    RangeSliderHelper.bind('#offsetX', component, element, OFFSET_X, {
        min: EditorFactory.checkMinRange(component, true),
        max: 600,
        postfix: "px"
    }, function () {
        UI.actionService.runActionForComponent(component, 'remove-component-from-form', true);
        UI.actionService.runActionForComponent(component, 'add-component-to-form', true);
        Helpers.removeSelectWrapper();
    });

    RangeSliderHelper.bind('#offsetY', component, element, OFFSET_Y, {
        min: EditorFactory.checkMinRange(component, false),
        max: 600,
        postfix: "px"
    }, function () {
        UI.actionService.runActionForComponent(component, 'remove-component-from-form', true);
        UI.actionService.runActionForComponent(component, 'add-component-to-form', true);
        Helpers.removeSelectWrapper();
    });

    EditorEventsFactory.attachPlainEvent('#styleInput', element, component, CHANGE, "predefined", function (com, el, value) {
        $(element).find('img').attr('class', 'std-img ' + value);
    });

    SwitcherHelper.bind(element, '#stretchToFullWidth', component, STRETCH_TO_FULL_WIDTH);
    StretcherFactory.onStretchChange(component);
    RangeSliderHelper.bind('#marginsWidth', component, element, MARGINS_WIDTH, {
        min: 0,
        max: 80,
        postfix: "px"
    },
        function (component, element, value) {
            StretcherFactory.enableMarginsToStretchedComponent(component, element, value)
        }
    );

    $('#upclick-button').upclick(component,
        {
            type: UPCLICK_TYPE_PICTURE,
            oncomplete: function (response) {
                var file = Helpers.ProcessUploadSingleFileCompleted(response, $('.upclick-editor-error'), $('#editor #upclick-button'));
                
                if (file != null) {
                    var img = new Image();
                    img.onload = function () {
                        var height = img.height;
                        var width = img.width;
                        if (height > 800 || width > 800) {
                            if (height > width) {
                                var fractionX = height / width;
                                var rezultX = (~~(700 / fractionX));
                                width = rezultX;
                                height = "700";
                            } else if (height < width) {
                                var fractionY = width / height;
                                var rezultY = (~~(700 / fractionY));
                                width = "700";
                                height = rezultY;
                            }
                        } else {
                            height = img.height;
                            width = img.width;
                        }
                        $(element).css(HEIGHT, height);
                        $(element).css(WIDTH, width);
                        component.setProperty(WIDTH, width + "px");
                        component.setProperty(HEIGHT, height + "px");
                        $(element).highlightSelectedElement(component, true);

                        for (var i = 0; i < component.properties.length; i++) {
                            if (component.properties[i].name == "height") $("#heightInput").val(height + "px");
                            else if (component.properties[i].name == "width") $("#widthInput").val(width + "px");
                        }

                        var pageElem = $(element).parent('.page');
                        Resizer.recalculateHeaderFooterAndPageSize(pageElem);

                    }
                    img.src = file.url;

                    var oldvalue = _.clone(component.getProperty('src').value);
                    var newvalue = _.clone(img.src);

                    var undoredo = function (val) {
                        //TODO change this to appropriate implementation
                        component.setProperty('src', val);
                        var newSrc = ContextFactory.prepareImgSrc(val, component.getProperty(SHOW_OPTIMIZED).value);
                        SetSingleImage(element, newSrc);
                        Helpers.refreshAllBloggingComponents();
                    }

                    undoredo(newvalue);

                    UI.undoManagerAdd(
                        {
                            undo: function () {
                                undoredo(oldvalue);
                            },
                            redo: function () {
                                undoredo(newvalue);
                            }
                        });
                }

            }
        });

    $('#upclick-button-hover').upclick(component,
    {
        type: UPCLICK_TYPE_PICTURE,
        oncomplete: function (response) {
            var file = Helpers.ProcessUploadSingleFileCompleted(response, $('.upclick-editor-error'), $('#editor #upclick-button-hover'));
            if (file != null)
            {
                try {

                    var oldvalue = _.clone(component.getProperty('image-on-hover').value);
                    var newvalue = _.clone(file.url);

                    var undoredo = function (val) {
                        var newSrc = ContextFactory.prepareImgSrc(val, component.getProperty(SHOW_OPTIMIZED_HOVER).value);
                        $('#editor-preview-single-image-hover').attr('src', newSrc);
                        component.setProperty('image-on-hover', val);
                    }

                    undoredo(newvalue);

                    UI.undoManagerAdd(
                        {
                            undo: function () {
                                undoredo(oldvalue);
                            },
                            redo: function () {
                                undoredo(newvalue);
                            }
                        });
                }
                catch (e) {
                    var errorMessage = "<div class=\"property-editor upclick-editor-error\">Old images do not have this property.</div>";
                    $('#editor #upclick-button-hover').prev().after(errorMessage);
                }
            }            
        }
    });

    $('#upclick-button-pressed').upclick(component,
    {
       type: UPCLICK_TYPE_PICTURE,
       oncomplete: function (response) {
           var file = Helpers.ProcessUploadSingleFileCompleted(response, $('.upclick-editor-error'), $('#editor #upclick-button-pressed'));
           
           if (file != null)
           {
               try {

                   var oldvalue = _.clone(component.getProperty('image-on-pressed').value);
                   var newvalue = _.clone(file.url);

                   var undoredo = function (val) {
                       var newSrc = ContextFactory.prepareImgSrc(val, component.getProperty(SHOW_OPTIMIZED_PRESSED).value);
                       $('#editor-preview-single-image-pressed').attr('src', newSrc);
                       component.setProperty('image-on-pressed', val);
                   }

                   undoredo(newvalue);

                   UI.undoManagerAdd(
                       {
                           undo: function () {
                               undoredo(oldvalue);
                           },
                           redo: function () {
                               undoredo(newvalue);
                           }
                       });
               }
               catch (e) {
                   var errorMessage = "<div class=\"property-editor upclick-editor-error\">Old images do not have this property.</div>";
                   $('#editor #upclick-button-pressed').prev().after(errorMessage);
               }
           }
       }
    });

    $(".close" + ".remove-button").click(function (event) {
        var datasetVal = event.currentTarget.dataset.button;
        var imageBox = "";
        var imageProperty = "";
        var srcImg = $(event.currentTarget).parent().find("img").attr("src");
        var blankImage = BLANKIMAGE;
        if (srcImg == "")
            return;

        switch (datasetVal) {
            case IMAGE:
                imageBox = "editor-preview-single-image";
                imageProperty = "src";
                break;

            case HOVER:
                imageBox = "editor-preview-single-image-hover";
                imageProperty = "image-on-hover";
                blankImage = "";
                break;

            case IMAGE_SWAP:
                imageBox = "editor-preview-single-image-pressed";
                imageProperty = "image-on-pressed";
                blankImage = "";
                break;

            default:
                var errorMessage = "<div class=\"property-editor upclick-editor-error\">Error: can not remove image</div>";
                $(event.currentTarget.parentElement.parentElement).find('[type="button"]' + '.service-button').prev().after(errorMessage);

                return;
        }

        var oldvalue = _.clone(component.getProperty(imageProperty).value);
        var newvalue = _.clone(blankImage);

        var undoredo = function (val) {
            $('#' + imageBox).attr('src', val);
            component.setProperty(imageProperty, val);
            if (datasetVal == IMAGE) {
                $(element).find('.std-img').attr('src', val);
            }
        }

        undoredo(newvalue);

        UI.undoManagerAdd(
            {
                undo: function () {
                    undoredo(oldvalue);
                },
                redo: function () {
                    undoredo(newvalue);
                }
            });
    });

    EditorEventsFactory.attachPlainEvent('#isShowOptimized',
                    element,
                    component,
                    CHANGE,
                    SHOW_OPTIMIZED,
                    function (component, element, value) {
                            value = (value === 'true');
                            var src = component.getProperty(SRC).value;
                            var newSrc = ContextFactory.prepareImgSrc(src, value);
                            SetSingleImage(element, newSrc);
                    });

    EditorEventsFactory.attachPlainEvent('#isShowOptimizedHover',
        element,
        component,
        CHANGE,
        SHOW_OPTIMIZED_HOVER,
        function (component, element, value) {
            value = (value === 'true');
            var src = component.getProperty(IMAGE_ON_HOVER).value;
            var newSrc = ContextFactory.prepareImgSrc(src, value);
            $('#editor-preview-single-image-hover').attr('src', newSrc);
        });


    EditorEventsFactory.attachPlainEvent('#isShowOptimizedPressed',
        element,
        component,
        CHANGE,
        SHOW_OPTIMIZED_PRESSED,
        function (component, element, value) {
            value = (value === 'true');
            var src = component.getProperty(IMAGE_ON_PRESSED).value;
            var newSrc = ContextFactory.prepareImgSrc(src, value);
            $('#editor-preview-single-image-pressed').attr('src', newSrc);
        });

    

    ko.applyBindings({}, $('#editor')[0]);

    this.layering = this.layering || new LayeringController(component);

}

var SetSingleImage = function (element, newSrc) {
    $(element).find('.std-img').attr('src', newSrc);
    $('#editor-preview-single-image').attr('src', newSrc);
    $(element).css('src', newSrc);
}

var ParagraphEditor = function (component) {
    var element = component.getUISelector();
    if (!component.isckeditorworking) {
        EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
        EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT);
        EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
        EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
        EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);
        ColorPickerHelper.bind(element, '#brcolorInput', component, BORDER_COLOR);
        ColorPickerHelper.bind(element, '#bgcolorInput', component, BACKGROUND_COLOR);

        RangeSliderHelper.bind('#borderWidth', component, element, BORDER_WIDTH, {
            min: 0,
            max: 15,
            postfix: "px"
        });
        RangeSliderHelper.bind('#borderRadius', component, element, BORDER_RADIUS, {
            min: 0,
            max: 50,
            postfix: "px"
        });
        EditorFactory.linkManagementEditor(component, element);
    }
    else
    {
        var node = $(component.getUISelector())[0];

        if ($(component.getUISelector()).data('ckeditorInstance') != null) {
            console.log('component is allready in edit mode...');
            return;
        }

        //get saved property value, create if not exists..
        var property = component.getProperty(TEXT);
        if (property == null) {
            component.setProperty(TEXT, DEFAULT_PARAGRAPH_TEXT, true);
            property = component.getProperty(TEXT);
        };
        if ($(component.getUISelector()).data('ckeditorInstance') != null) {
            console.log('component is allready in edit mode...');
            return;
        }

        // ReSharper disable once QualifiedExpressionMaybeNull
        var text = property.hasOwnProperty('value') === true ? property.value : DEFAULT_PARAGRAPH_TEXT;

        //extend bindings for template, note: there is allready other bindings applied,
        //so need merge them...
        var bindingModel = {
            ckEditor: text
        };

        // bind to ckEditor, you can subscribe here to react on changes..
        var ptext = ko.observable(text);

        ko.observable(node).extend({
                        applyBindingsToNode: {
                            model: bindingModel,
                            extension: ko.utils.extend(ko.dataFor(node), { paragraphtext: ptext() })
                        }
                });

    }

    this.layering = this.layering || new LayeringController(component);
}

var HeadertextEditor = function (component) {

    var element = component.getUISelector();
    if (!component.isckeditorworking)
    {
        EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
        EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT);
        EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
        EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
        EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);
        ColorPickerHelper.bind(element, '#brcolorInput', component, BORDER_COLOR);
        ColorPickerHelper.bind(element, '#bgcolorInput', component, BACKGROUND_COLOR);

        RangeSliderHelper.bind('#borderWidth', component, element, BORDER_WIDTH, {
            min: 0,
            max: 15,
            postfix: "px"
        });
        RangeSliderHelper.bind('#borderRadius', component, element, BORDER_RADIUS, {
            min: 0,
            max: 50,
            postfix: "px"
        });
        EditorFactory.linkManagementEditor(component, element);
    }
    else
    {
        var node = $(component.getUISelector())[0];

        if ($(component.getUISelector()).data('ckeditorInstance') != null) {
            console.log('component is allready in edit mode...');
            return;
        }

        //get saved property value, create if not exists..
        var property = component.getProperty(TEXT);
        if (property == null) {
            component.setProperty(TEXT, "Heading", true);
            property = component.getProperty(TEXT);
        };

        // ReSharper disable once QualifiedExpressionMaybeNull
        var text = property.hasOwnProperty('value') === true ? property.value : "Heading";

        //extend bindings for template, note: there is allready other bindings applied,
        //so need merge them...
        var bindingModel = {
            ckEditor: text
        };

        // bind to ckEditor, you can subscribe here to react on changes..
        var ptext = ko.observable(text);

        //extend and apply bindings... process ' ckEditor: text '
        ko.observable(node).extend({
                        applyBindingsToNode: {
                            model: bindingModel,
                            extension: ko.utils.extend(ko.dataFor(node), { headertexttext: ptext() })
                        }
                });
    }

    this.layering = this.layering || new LayeringController(component);
}

var BloggingEditor = function(component) {
    var element = component.getUISelector();
    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT);
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
    EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);

    EditorEventsFactory.attachPlainEvent('#fontOptionInput', element, component, CHANGE, FONT_FAMILY);

    var servicePageId = component.getProperty(PARENT_PAGE).value;
    if (servicePageId) {
        var servicePage = UI.siteComponentRepository.lookupData({ id: servicePageId });
        if (servicePage) {
            $('#titleInput').bind(CHANGE,
                function() {
                    var newvalue = _.clone($(this).val());
                    UI.undoManagerAddSimple(servicePage, TITLE, newvalue, function (val, com) {
                        UI.pager.getPage(com.id).title = val;
                        Pager.renderTopMenuSelect();
                    }, true);
                });
        }
    }

    RangeSliderHelper.bind('#borderWidth', component, element, BORDER_WIDTH, {
        min: 0,
        max: 15,
        postfix: "px"
    });
    RangeSliderHelper.bind('#borderRadius', component, element, BORDER_RADIUS, {
        min: 0,
        max: 50,
        postfix: "px"
    });
    RangeSliderHelper.bind('#fontSize', component, element, FONT_SIZE, {
        min: 6,
        max: 50,
        postfix: "px"
    });

    ColorPickerHelper.bind(element, '#colorInput', component, COLOR);
    ColorPickerHelper.bind(element, '#bgcolorInput', component, BACKGROUND_COLOR);
    ColorPickerHelper.bind(element, '#brcolorInput', component, BORDER_COLOR);

    SwitcherHelper.bind(element, '#page-layout', component, LAYOUT);
}

var ButtonEditor = function (component) {
    var element = component.getUISelector();
    $(element).css({ "text-overflow": "ellipsis", "overflow": "hidden" });
    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT,
        function (component, element) {
            ViewerFactory.calculateButtonLineHeight(component.getUISelector());
        });
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
    EditorEventsFactory.attachPlainEvent('#textalign', element, component, CHANGE, TEXT_ALIGN);
    EditorEventsFactory.attachPlainEvent('#contentInput', element, component, CHANGE, TEXT);
    EditorEventsFactory.attachPlainEvent('#fontOptionInput', element, component, CHANGE, FONT_FAMILY);
    EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);

    EditorFactory.changeFixPosition('#pinnedPosition', 'input', component);
    SwitcherHelper.bind(element, '#isPinedSwitcher', component, IS_PINED);

    ColorPickerHelper.bind(element, '#colorInput', component, COLOR);
    ColorPickerHelper.bind(element, '#bgcolorInput', component, BACKGROUND_COLOR);
    ColorPickerHelper.bind(element, '#bgcolorhoverInput', component, BACKGROUND_COLOR_HOVER);
    ColorPickerHelper.bind(element, '#brcolorhoverInput', component, BORDER_COLOR_HOVER);
    ColorPickerHelper.bind(element, '#txtcolorhoverInput', component, TEXT_COLOR_HOVER);
    ColorPickerHelper.bind(element, '#brcolorInput', component, BORDER_COLOR);

    RangeSliderHelper.bind('#borderWidth', component, element, BORDER_WIDTH, {
        min: 0,
        max: 15,
        postfix: "px"
    }, function () {
        ViewerFactory.calculateButtonLineHeight(component.getUISelector());
    });
    RangeSliderHelper.bind('#borderRadius', component, element, BORDER_RADIUS, {
        min: 0,
        max: 50,
        postfix: "px"
    });
    RangeSliderHelper.bind('#fontSize', component, element, FONT_SIZE, {
        min: 6,
        max: 50,
        postfix: "px"
    });

    RangeSliderHelper.bind('#offsetX', component, element, OFFSET_X, {
        min: EditorFactory.checkMinRange(component, true),
        max: 600,
        postfix: "px"
    }, function () {
        UI.actionService.runActionForComponent(component, 'remove-component-from-form', true);
        UI.actionService.runActionForComponent(component, 'add-component-to-form', true);
        Helpers.removeSelectWrapper();
    });

    RangeSliderHelper.bind('#offsetY', component, element, OFFSET_Y, {
        min: EditorFactory.checkMinRange(component, false),
        max: 600,
        postfix: "px"
    }, function () {
        UI.actionService.runActionForComponent(component, 'remove-component-from-form', true);
        UI.actionService.runActionForComponent(component, 'add-component-to-form', true);
        Helpers.removeSelectWrapper();
    });

    EditorFactory.linkManagementEditor(component, element);
    ko.applyBindings({}, $('#editor')[0]);
    this.layering = this.layering || new LayeringController(component);
}

var MapEditor = function (component) {
    var element = component.getUISelector();
    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT);
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
    EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);

    function refreshComponent (com) {
        UI.actionService.runActionForComponent(com, ACTION_REMOVE_FROM_FORM, true);
        UI.actionService.runActionForComponent(com, ACTION_ADD_TO_FORM, true);
    }

    function updateAddress(address, lat, lng) {
        if (address && lat && lng) {
            var struct = [
                {
                    component: component,
                    property: TEXT,
                    newvalue: address,
                    oldvalue: component.getProperty(TEXT).value
                },
                {
                    component: component,
                    property: LATITUDE,
                    newvalue: lat,
                    oldvalue: component.getProperty(LATITUDE).value
                },
                {
                    component: component,
                    property: LONGITUDE,
                    newvalue: lng,
                    oldvalue: component.getProperty(LONGITUDE).value
                }
            ];
            var callback = function (val, com, prop, index, length) {
                if (prop === TEXT) {
                    $('#textInput').val(val);
                } else if (prop === LATITUDE) {
                    $('#latInput').val(val);
                } else if (prop === LONGITUDE) {
                    $('#lngInput').val(val);
                }
                if (index === length - 1) {
                    refreshComponent(com);
                }
            };
            UI.undoManagerAddSimpleArr(struct, callback, callback, true);
        }
    }

    $('#textInput').bind(CHANGE,
        function() {
            var inputText = $(this).val();

            var map = $(element).find('.map').get(0);
            if (map) {
                var contentWindow = map.contentWindow;
                if (contentWindow) {
                    var geocoder = contentWindow.googleMapsGeocoder;
                    if (geocoder) {
                        geocoder.geocode({ 'address': inputText }, function (results, status) {
                            if (status === 'OK') {
                                if (results[0]) {
                                    updateAddress(results[0].formatted_address, results[0].geometry.location.lat(), results[0].geometry.location.lng());
                                } else {
                                    Application.showOkDialog('Error', 'No results found');
                                }
                            } else {
                                console.log('Geocoder failed due to: ' + status);
                            }
                        });
                    }
                }
            }
        });

    EditorEventsFactory.attachPlainEvent('#titleInput', element, component, CHANGE, TITLE, refreshComponent);

    SwitcherHelper.bind(element, '#showMapTypeSwitcher', component, SHOW_MAP_TYPE, refreshComponent);
    SwitcherHelper.bind(element, '#showZoomSwitcher', component, SHOW_ZOOM, refreshComponent);
    SwitcherHelper.bind(element, '#mapInteractiveSwitcher', component, MAP_INTERACTIVE, refreshComponent);
    SwitcherHelper.bind(element, '#showStreetViewSwitcher', component, SHOW_STREET_VIEW, refreshComponent);
}

EditorFactory.checkMinRange = function(component, isOffsetX) {
    var fixedLocation = component.getProperty(FIXED_LOCATION).value;
    var minRangeX = 0;
    var minRangeY = 0;

    if (isOffsetX && (fixedLocation === 'center-top' || fixedLocation === 'center-bottom')) {
        minRangeX = -600;
    } else if (!isOffsetX && (fixedLocation === 'left-center' || fixedLocation === 'right-center')) {
        minRangeY = -600;
    }
    return isOffsetX ? minRangeX : minRangeY;
};

EditorFactory.changeFixPosition = function (element, childNode, component) {
    $(element).on('click', childNode, function (e) {
        e.preventDefault();
        var dataClass = $(this).data().position;
        $(this).addClass(dataClass);
        $('#pinnedOffsetXY').show('fast');

        $('#pinnedPosition label.elemposition-selected').removeClass('elemposition-selected');
        $(this).next().addClass('elemposition-selected');

        var callback = function (newValue, com, property) {
            UI.actionService.runActionForComponent(com, 'remove-component-from-form', true);
            UI.actionService.runActionForComponent(com, 'add-component-to-form', true);
            Helpers.removeSelectWrapper();
        };

        var offsetX = '20px';
        if (dataClass === 'center-top' || dataClass === 'center-bottom') {
            offsetX = '0px';
        }
        var offsetY = '20px';
        if (dataClass === 'left-center' || dataClass === 'right-center') {
            offsetY = '0px';
        }

        var struct = [
            {
                component: component,
                property: IS_PINED,
                newvalue: true.toString(),
                oldvalue: component.getProperty(IS_PINED).value
            },
            {
                component: component,
                property: FIXED_LOCATION,
                newvalue: dataClass,
                oldvalue: component.getProperty(FIXED_LOCATION).value
            },
            {
                component: component,
                property: OFFSET_X,
                newvalue: offsetX,
                oldvalue: component.getProperty(OFFSET_X).value
            },
            {
                component: component,
                property: OFFSET_Y,
                newvalue: offsetY,
                oldvalue: component.getProperty(OFFSET_Y).value
            }
        ];

        UI.undoManagerAddSimpleArr(struct, callback, callback, true);
        RangeSliderHelper.update('#offsetX',
            {
                from: parseInt(offsetX),
                min: EditorFactory.checkMinRange(component, true)
            });
        RangeSliderHelper.update('#offsetY',
        {
            from: parseInt(offsetY),
            min: EditorFactory.checkMinRange(component, false)
        });
    });
};

var SignInEditor = function (component) {
    var element = $(component.getUISelector());
    element.css({ "text-overflow": "ellipsis", "overflow": "hidden" });

    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT,
        function (component, element) {
            ViewerFactory.calculateButtonLineHeight(component.getUISelector());
        });

    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
    EditorEventsFactory.attachPlainEvent('#textalign', element, component, CHANGE, TEXT_ALIGN);
    EditorEventsFactory.attachPlainEvent('#contentInput', element, component, CHANGE, TEXT);
    EditorEventsFactory.attachPlainEvent('#fontOptionInput', element, component, CHANGE, FONT_FAMILY);
    EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);

    ColorPickerHelper.bind(element, '#colorInput', component, COLOR);
    ColorPickerHelper.bind(element, '#bgcolorInput', component, BACKGROUND_COLOR);
    ColorPickerHelper.bind(element, '#bgcolorhoverInput', component, BACKGROUND_COLOR_HOVER);
    ColorPickerHelper.bind(element, '#brcolorhoverInput', component, BORDER_COLOR_HOVER);
    ColorPickerHelper.bind(element, '#txtcolorhoverInput', component, TEXT_COLOR_HOVER);
    ColorPickerHelper.bind(element, '#brcolorInput', component, BORDER_COLOR);

    RangeSliderHelper.bind('#borderWidth', component, element, BORDER_WIDTH, {
        min: 0,
        max: 15,
        postfix: "px"
    }, function () {
        ViewerFactory.calculateButtonLineHeight(component.getUISelector());
    });
    RangeSliderHelper.bind('#borderRadius', component, element, BORDER_RADIUS, {
        min: 0,
        max: 50,
        postfix: "px"
    });
    RangeSliderHelper.bind('#fontSize', component, element, FONT_SIZE, {
        min: 6,
        max: 50,
        postfix: "px"
    });

    $('#manage-users-btn').on('click', function () {
        UI.removeEditor();
        UI.componentService.addModalContentToForm(component, '#signin-manage-users');
    });

    //custom user fields
    var model = UI.getViewModel('#signin-user-fields');
    ko.applyBindings(model, $('#switch-user-profile-fields')[0]);
    
    this.layering = this.layering || new LayeringController(component);
};

var GalleryEditor = function (component) {
    EditorFactory.addedTooltip(["linkInput"], "bottom");
    var element = component.getUISelector();
    var ctx = StretchingController.shareInstanceBetweenNodes($(element).children()[0], $("#gallery-editor")[0]);

    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT, function (com, el, val) {
        ViewerFactory.calculateHeightForGallery(com, el);
    });
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
    EditorEventsFactory.attachPlainEvent('#typeInput', element, component, CHANGE, "type", function (com, el, val) {
        rerenderViewer();
    });
    EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);

    ColorPickerHelper.bind(element, '#headercolorInput', component, IMAGE_SETTINGS_HEADERCOLOR, function (com, el, val) {
        $(el).find('.title').css('color', val);
    });

    ColorPickerHelper.bind(element, '#colorInput', component, IMAGE_SETTINGS_COLOR, function (com, el, val) {
        $(el).find('.descr').css('color', val);
    });
    ColorPickerHelper.bind(element, '#bgcolorInput', component, IMAGE_SETTINGS_BGCOLOR, function (com, el, val) {
        $(el).find('.info').css('background-color', val);
    });

    EditorEventsFactory.attachPlainEvent('#styleInput', element, component, CHANGE, "predefined", function (com, el, value) {
        styleShadowOrPlain(element, value);
    });

    EditorEventsFactory.attachPlainEvent('#fontOptionInput', element, component, CHANGE, "font-family", function (com, el, value) {
        rerenderViewer();
    });

    EditorEventsFactory.attachPlainEvent('#isShowOptimized',
        element,
        component,
        CHANGE,
        SHOW_OPTIMIZED,
        function (component, element, value) {
            rerenderViewer();
        });

    SwitcherHelper.bind(element, '#stretchToFullWidth', component, STRETCH_TO_FULL_WIDTH);
    StretcherFactory.onStretchChange(component);
    RangeSliderHelper.bind('#marginsWidth', component, element, MARGINS_WIDTH, {
        min: 0,
        max: 80,
        postfix: "px"
    },
        function (component, element, value) {
            StretcherFactory.enableMarginsToStretchedComponent(component, element, value)
        }
    );

    $('#scalingInput').bind(CHANGE, function () {
        var value = $("#styleInput").val();
        rerenderViewer();
        styleShadowOrPlain(element, value);
    });

    function styleShadowOrPlain(element, value) {
        $(element).find('.holder').children().each(function () {
            if ($(this).parent().hasClass('stretch')) {
                $(this).attr('class', 'std-gallery ' + value);
                if (value == "plain") {
                    $(this).removeClass('std-gallery ' + value);
                } else if (value == "shadow") {
                    $(this).addClass('std-gallery ' + value);
                }
            } else {
                $(this).children().attr('class', 'std-gallery ' + value);
                if (value == "plain") {
                    $(this).removeClass('std-gallery ' + value);
                }
            }
        });
    }

    function bindStretching() {
        var stretchingVal = $('#scalingInput').val() || "fill";
        var stretchingController = new StretchingController(['fill', 'crop'], stretchingVal, component);
        ko.observable($(element).find('.gallery-items-list')[0]).extend({ applyBindings: stretchingController });
    }

    RangeSliderHelper.bind('#fontSize', component, element, FONT_SIZE, {
        min: 6,
        max: 50,
        postfix: "px"
    }, function () { rerenderViewer(); });

    $('#expandCheckbox').change(function () {
        if ($(this).is(":checked")) {
            $('#property-link').show();
        } else {
            $('#property-link').hide();
        }
    });

    showImageSettings(component, element.selector);

    function showImageSettings(com, el) {
        var withCaptions = $('.with-captions');
        var galleryEditor = $('#gallery-editor');
        if (component.getProperty(TYPE).value == 'with-captions') {
            if (!withCaptions.is(':visible')) {
                galleryEditor.css("height", galleryEditor.innerHeight() + 60 + 'px');
            }
            withCaptions.show();
            $(element).find('.title').css('color', component.getProperty(IMAGE_SETTINGS_HEADERCOLOR).value);
            $(element).find('.descr').css('color', component.getProperty(IMAGE_SETTINGS_COLOR).value);
            $(element).find('.info').css('background-color', component.getProperty(IMAGE_SETTINGS_BGCOLOR).value);
        } else {
            if (withCaptions.is(':visible')) {
                galleryEditor.css("height", galleryEditor.innerHeight() - 60 + 'px');
            }
            withCaptions.hide();
        }
    }

    RangeSliderHelper.bind('#columnCount', component, element, "columns", {
        min: 1,
        max: 10,
        onChange: function (obj) {


            var oldvalue = _.clone(component.getProperty("columns").value);
            var newvalue = _.clone(obj.fromNumber);

            var undoredo = function (val) {
                component.setProperty("columns", val);
                rerenderViewer();
            }

            undoredo(newvalue);

            var propertyName = "columns";
            var componentid = UI.undoManagerGetLatestComponentId();

            if (propertyName != null && "columns" == propertyName
                && componentid != null && componentid == component.id) {
                UI.undoManagerAddSpecific(
                    {
                        redo: function () {
                            undoredo(newvalue, "");
                        }
                    }, "redo");
            }
            else {
                UI.undoManagerAdd(
                {
                    undo: function () {
                        undoredo(oldvalue);
                    },
                    redo: function () {
                        undoredo(newvalue);
                    },
                    property: propertyName,
                    componentId: component.id
                });
            }

        }
    });
    $('#expandCheckbox').bind('change', function () {
        UI.undoManagerAddSimple(component, "expand", $(this).prop('checked'), rerenderViewer, true);
    });

    var changeOrder = function (source, direction) {
        var switchElements = function (sourceEl, prevEl) {
            sourceElement.detach();
            sourceElement.insertBefore(previousElement);
        };
        var sourceElement = $('#' + source.id);
        var currentOrder;
        if (direction == 'left') {
            var previousElement = sourceElement.prev();
            if (previousElement.length > 0) {
                var previousComponent = UI.siteComponentRepository.lookupData({ id: previousElement.getId() });
                if (previousComponent != null) {

                    var cloneCurrentOrder = _.clone(source.getProperty("order").value.toInteger());
                    var clonePreviousOrder = _.clone(previousComponent.getProperty("order").value.toInteger());

                    var structs = [
                        { component: source, property: "order", newvalue: clonePreviousOrder, oldvalue: '' },
                        { component: previousComponent, property: "order", newvalue: cloneCurrentOrder, oldvalue: '' }
                    ];

                    UI.undoManagerAddSimpleArr(structs, function () { switchElements(sourceElement, previousElement); rerenderViewer(); },
                                                        function () { switchElements(previousElement, sourceElement); rerenderViewer(); }, true);

                }
            }
        } else if (direction == 'right') {
            var nextElement = sourceElement.next();
            if (nextElement.length > 0) {
                var nextComponent = UI.siteComponentRepository.lookupData({ id: nextElement.getId() });
                if (nextComponent != null) {
                    sourceElement.detach();
                    sourceElement.insertAfter(nextElement);
                    currentOrder = source.getProperty("order").value.toInteger();
                    var nextOrder = nextComponent.getProperty("order").value.toInteger();
                    source.setProperty("order", nextOrder);
                    nextComponent.setProperty("order", currentOrder);
                    rerenderViewer();
                }
            }
        }
    }
    var removePreviewHighlighting = function () {
        $('#gallery-editor .gallery-items-list .gallery-item').removeClass('active');
    }
    var addPreviewHighlighting = function (id) {
        $('#gallery-editor .gallery-items-list #' + id).addClass('active');
    }

    EditorFactory.bindEvents(component);

    var saveItemSettings = function () {
        var id = $('.save-gallery-item').data('id');
        var item = component.children.where({ id: id });
        if (item.any()) {
            item = item.firstOrDefault();
            
            var structs = [
                {
                    component: item,
                    property: "title",
                    newvalue: $('#gallery-editor .gallery-item-title').val(),
                    oldvalue: ''
                },
                {
                    component: item,
                    property: "description",
                    newvalue: $('#gallery-editor .gallery-item-description').val(),
                    oldvalue: ''
                }
            ];

            UI.undoManagerAddSimpleArr(structs, rerenderViewer, rerenderViewer, true);


        };
    }
    $('#gallery-editor .close-button').bind('click', function () {
        UI.removeEditor();
    });
    var deleteGalleryItem = function (id) {
        UI.undoManagerAddRemoving(id, '#gallery-editor .gallery-items-list #' + id, rerenderViewer);
    }


    var rerenderViewer = function () {
        var template = TemplateFactory.templateFor(component, VIEWER_TEMPLATE).compiledTemplate;
        $(component.getUISelector()).html($(template).html());

        var element = component.getUISelector();
        if (component.children.length == 0) {
            var index;
            for (index = 0; index < 9; index += 1) {
                var fill = "<div class=\"gallery-item c3\"><div class=\"holder\"><a class=\"fancybox\" rel=\"gallery\" href=\"/Images/gallery_predefined.png\"><img src=\"/Images/gallery_predefined.png\"></a></div></div>";
                var stretch = "<div class=\"gallery-item c3\"><div class=\"holder stretch\" data-bind=\"css: classToApply\"><div style=\"width: 100%; height: 100%; background-image: url(/Images/gallery_predefined.png); background-size: cover;\" data-bind=\"style: stretchClass\" class=\"std-gallery shadow\"><a class=\"fancybox\" rel=\"gallery\" href=\"/Images/gallery_predefined.png\"></a></div></div></div>";

                if (component.getProperty(IMAGE_STRETCHING).value == "crop") {
                    $(element).find('.gallery-items-list').append(stretch);
                }
                else {
                    $(element).find('.gallery-items-list').append(fill);
                }
                
            }
        }
        
        showImageSettings(component, element);

        // context binded to newly added items of viewer
        bindStretching();
        //ko.applyBindingsToDescendants(ctx, $(element).find('.gallery-items-list')[0]); //reapply same context to promote children           

        var isShowOptimized = component.getProperty(SHOW_OPTIMIZED).value.toBoolean();
        component.children.forEach(function (item) {
            $('#gallery-editor .gallery-items-list .gallery-item#' + item.id + ">div>div").css('background-image', 'url("' + ContextFactory.prepareImgSrc(item.getProperty(SRC).value, isShowOptimized) + '")');
            $('#gallery-editor .gallery-items-list .gallery-item#' + item.id + " img").attr('src',  ContextFactory.prepareImgSrc(item.getProperty(SRC).value, isShowOptimized));
        });

        ViewerFactory.calculateMarginForGallery(component, element);
        ViewerFactory.calculateHeightForGallery(component, element);
    }

    component.children.forEach(function (item) {

        $(item.getUISelector()).find('.remove-button').bind('click', function (e) {
            e.stopPropagation();
            deleteGalleryItem($(this).closest('.gallery-item').getId());
        });
        $(item.getUISelector()).find('.order-left').bind('click', function (e) {
            e.stopPropagation();
            changeOrder(item, 'left');
        });
        $(item.getUISelector()).find('.order-right').bind('click', function (e) {
            e.stopPropagation();
            changeOrder(item, 'right');
        });
        $(item.getUISelector()).bind('click', function () {
            removePreviewHighlighting();
            addPreviewHighlighting($(this).getId());
            EditorFactory.showItemSettings(item);
            Helpers.hideValidationEngine("#linkInput");
        });
    });

    $('.gallery-item-title, .gallery-item-description').bind('change', function () {
        saveItemSettings();
    });

    $('.save-gallery-item').hide();

    EditorFactory.hideItemSettings();

    EditorFactory.upclickPicture(component, ctx, deleteGalleryItem, removePreviewHighlighting, addPreviewHighlighting, EditorFactory.showItemSettings, rerenderViewer, changeOrder); // multiple true

    this.layering = this.layering || new LayeringController(component);
}

var HousePhotoTourEditor = function (component) {
    var element = component.getUISelector();
    var ctx = StretchingController.shareInstanceBetweenNodes($(element).find('.std-house-photo-tour-predefined-items')[0], $("#gallery-editor")[0]);

    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH, function (com, el, val) {
        var componentWidth = com.getProperty(WIDTH).value;

        if (parseInt(componentWidth) < 960) {
            com.setProperty(WIDTH, '960px');
        }

        rerenderViewer();
    });
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT, function (com, el, val) {
        rerenderViewer();
    });
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
    EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);

    EditorEventsFactory.attachPlainEvent('#scalingInput', element, component, CHANGE, IMAGE_STRETCHING, function (com, el, val) {
        rerenderViewer();
    });

    EditorEventsFactory.attachPlainEvent('#fontOptionInput', element, component, CHANGE, "font-family", function (com, el, value) {
        rerenderViewer();
    });

    EditorEventsFactory.attachPlainEvent('#taInput', element, component, CHANGE, "text-align", function (com, el, value) {
        rerenderViewer();
    });

    EditorEventsFactory.attachPlainEvent('#isShowOptimized',
        element,
        component,
        CHANGE,
        SHOW_OPTIMIZED,
        function (component, element, value) {
            rerenderViewer();
        });



    EditorEventsFactory.attachPlainEvent('#locationInput', element, component, CHANGE, "location", function (com, el, value) {
        if (value == "left") {
            $(el).find('.house_photo_tour_div').css('right', "480px");
            $(el).find('.house_photo_tour_editor_div').css('right', "480px");
        } else {
            $(el).find('.house_photo_tour_div').css('right', "0px");
            $(el).find('.house_photo_tour_editor_div').css('right', "0px");
        }
    });

    ColorPickerHelper.bind(element, '#headercolorInput', component, IMAGE_SETTINGS_HEADERCOLOR, function (com, el, val) {
        $(el).find('.title').css('color', val);
    });

    ColorPickerHelper.bind(element, '#textbgcolorInput', component, PHOTO_TOUR_TEXT_COLOR, function (com, el, val) {
        $(el).find('.house_photo_tour_div').css('background-color', val);
        $(el).find('.house_photo_tour_editor_div').css('background-color', val);
    });

    ColorPickerHelper.bind(element, '#colorInput', component, IMAGE_SETTINGS_COLOR, function (com, el, val) {
        $(el).find('.hpt_descr').css('color', val);
    });
    ColorPickerHelper.bind(element, '#bgcolorInput', component, BACKGROUND_COLOR);

    RangeSliderHelper.bind('#fontSize', component, element, FONT_SIZE, {
        min: 10,
        max: 26,
        postfix: "px"
    });

    $('#expandCheckbox').change(function () {
        if ($(this).is(":checked")) {
            $('#property-link').show();
        } else {
            $('#property-link').hide();
        }
    });
    $('#expandCheckbox').bind('change', function () {
        UI.undoManagerAddSimple(component, "expand", $(this).prop('checked'), rerenderViewer, true);
    });

    SwitcherHelper.bind(element, '#stretchToFullWidth', component, STRETCH_TO_FULL_WIDTH);
    StretcherFactory.onStretchChange(component);
    RangeSliderHelper.bind('#marginsWidth', component, element, MARGINS_WIDTH, {
        min: 0,
        max: 80,
        postfix: "px"
    },
        function (component, element, value) {
            StretcherFactory.enableMarginsToStretchedComponent(component, element, value);
            rerenderViewer();
        }
    );

    function bindStretching() {
        var stretchingVal = $('#scalingInput').val() || "fill";
        var stretchingController = new StretchingController(['fill', 'crop'], stretchingVal, component);
        ko.observable($(element).find('.std-house-photo-tour-predefined-items')[0]).extend({ applyBindings: stretchingController });
    }

    EditorFactory.bindEvents(component);

    //var showItemSettings = function (item) {

    //    $('.item-edit-container').show();
    //    $('.save-gallery-item').data('id', item.id);
    //    $('.item-edit-container .gallery-item-title').val(item.getProperty("title").value);
    //    $('.item-edit-container .gallery-item-description').val(item.getProperty("description").value);
    //}

    var changeOrder = function (source, direction) {
        var sourceElement = $('#' + source.id);
        var currentOrder;
        if (direction == 'left') {
            var previousElement = sourceElement.prev();
            if (previousElement.length > 0) {
                var previousComponent = UI.siteComponentRepository.lookupData({ id: previousElement.getId() });
                if (previousComponent != null) {
                    sourceElement.detach();
                    sourceElement.insertBefore(previousElement);
                    currentOrder = source.getProperty("order").value.toInteger();
                    var previousOrder = previousComponent.getProperty("order").value.toInteger();
                    source.setProperty("order", previousOrder);
                    previousComponent.setProperty("order", currentOrder);
                    rerenderViewer();
                }
            }
        } else if (direction == 'right') {
            var nextElement = sourceElement.next();
            if (nextElement.length > 0) {
                var nextComponent = UI.siteComponentRepository.lookupData({ id: nextElement.getId() });
                if (nextComponent != null) {
                    sourceElement.detach();
                    sourceElement.insertAfter(nextElement);
                    currentOrder = source.getProperty("order").value.toInteger();
                    var nextOrder = nextComponent.getProperty("order").value.toInteger();
                    source.setProperty("order", nextOrder);
                    nextComponent.setProperty("order", currentOrder);
                    rerenderViewer();
                }
            }
        }
    }
    var removePreviewHighlighting = function () {
        $('#gallery-editor .gallery-items-list .gallery-item').removeClass('active');
    }
    var addPreviewHighlighting = function (id) {
        $('#gallery-editor .gallery-items-list #' + id).addClass('active');
    }
    //var hideItemSettings = function () {
    //    $('.save-gallery-item').data('id', null);
    //    $('.item-edit-container').hide();
    //    $('.item-edit-container .gallery-item-title').val('');
    //    $('.item-edit-container .gallery-item-description').val('');
    //}

    var saveItemSettings = function () {
        var id = $('.save-gallery-item').data('id');
        var item = component.children.where({ id: id });
        if (item.any()) {
            item = item.firstOrDefault();

            var structs = [
                { component: item, property: "title", newvalue: _.clone($('.item-edit-container .gallery-item-title').val()), oldvalue: '' },
                { component: item, property: "description", newvalue: _.clone($('.item-edit-container .gallery-item-description').val()), oldvalue: '' }
            ];

            UI.undoManagerAddSimpleArr(structs, rerenderViewer, rerenderViewer, true);

        };
    }
    $('#gallery-editor .close-button').bind('click', function () {
        UI.removeEditor();
    });
    var deleteGalleryItem = function (id) {
        UI.undoManagerAddRemoving(id, "#gallery-editor .gallery-items-list #" + id, rerenderViewer);
    }
    var rerenderViewer = function () {
        var template = TemplateFactory.templateFor(component, VIEWER_TEMPLATE).compiledTemplate;
        $(component.getUISelector()).html($(template).html());
        ViewerFactory.housePhotoTourPostRender(component);
        bindStretching();
        
        var isShowOptimized = component.getProperty(SHOW_OPTIMIZED).value.toBoolean();

        component.children.forEach(function (item) {
            $('#gallery-editor .gallery-items-list .gallery-item#' + item.id + " img")
                .attr("src", ContextFactory.prepareImgSrc(item.getProperty(SRC).value, isShowOptimized));
            $('#gallery-editor .gallery-items-list .gallery-item#' + item.id + ">div>div").css('background-image',
                'url("' + ContextFactory.prepareImgSrc(item.getProperty(SRC).value, isShowOptimized) + '")');
        });
    }

    component.children.forEach(function (item) {
        $(item.getUISelector()).find('.remove-button').bind('click', function (e) {
            e.stopPropagation();
            deleteGalleryItem($(this).closest('.gallery-item').getId());
        });
        $(item.getUISelector()).find('.order-left').bind('click', function (e) {
            e.stopPropagation();
            changeOrder(item, 'left');
        });
        $(item.getUISelector()).find('.order-right').bind('click', function (e) {
            e.stopPropagation();
            changeOrder(item, 'right');
        });
        $(item.getUISelector()).bind('click', function () {
            removePreviewHighlighting();
            addPreviewHighlighting($(this).getId());
            EditorFactory.showItemSettings(item);
            Helpers.hideValidationEngine("#linkInput");
        });
    });

    $('.gallery-item-title, .gallery-item-description').bind('change', function () {
        saveItemSettings();
    });

    $('.save-gallery-item').hide();

    EditorFactory.hideItemSettings();

    EditorFactory.upclickPicture(component, ctx, deleteGalleryItem, removePreviewHighlighting, addPreviewHighlighting, EditorFactory.showItemSettings, rerenderViewer, changeOrder); // multiple true

    this.layering = this.layering || new LayeringController(component);
}


var SlideshowEditor = function (component) {
    var element = component.getUISelector();

    EditorFactory.addedTooltip(["linkInput"], "bottom");

    var ctx = StretchingController.shareInstanceBetweenNodes($(element).find('.carousel-inner')[0], $("#gallery-editor")[0]);

    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT);
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);

    EditorEventsFactory.attachPlainEvent('#stretchInput', element, component, CHANGE, IMAGE_STRETCHING);

    EditorEventsFactory.attachPlainEvent('#captionPositionInput', element, component, CHANGE, "caption-position", function (com, el, value) {
        rerenderViewer();
    });

    ColorPickerHelper.bind(element, '#headercolorInput', component, IMAGE_SETTINGS_HEADERCOLOR, function (com, el, val) {
        $(el).find('.title').css('color', val);
    });

    ColorPickerHelper.bind(element, '#colorInput', component, IMAGE_SETTINGS_COLOR, function (com, el, val) {
        $(el).find('.descr').css('color', val);
    });
    ColorPickerHelper.bind(element, '#bgcolorInput', component, IMAGE_SETTINGS_BGCOLOR, function (com, el, val) {
        $(el).find('.carousel-caption').css('background-color', val);
    });

    RangeSliderHelper.bind('#fontSize', component, element, FONT_SIZE, {
        min: 6,
        max: 50,
        postfix: "px"
    });

    EditorEventsFactory.attachPlainEvent('#fontOptionInput', element, component, CHANGE, "font-family", function (com, el, value) {
        rerenderViewer();
    });

    EditorEventsFactory.attachPlainEvent('#isShowOptimized',
        element,
        component,
        CHANGE,
        SHOW_OPTIMIZED,
        function (component, element, value) {
            rerenderViewer();
        });


    SwitcherHelper.bind(element, '#stretchToFullWidth', component, STRETCH_TO_FULL_WIDTH);
    StretcherFactory.onStretchChange(component);
    RangeSliderHelper.bind('#marginsWidth', component, element, MARGINS_WIDTH, {
        min: 0,
        max: 80,
        postfix: "px"
    },
        function (component, element, value) {
            StretcherFactory.enableMarginsToStretchedComponent(component, element, value)
        }
    );

    showImageSettings(component, element.selector);

    function showImageSettings(com, el) {
        if (component.getProperty(CAPTION).value.toBoolean()) {
            $('.image-settings').show();
            $(element).find('.title').css('color', component.getProperty(IMAGE_SETTINGS_HEADERCOLOR).value);
            $(element).find('.descr').css('color', component.getProperty(IMAGE_SETTINGS_COLOR).value);
            $(element).find('.carousel-caption').css('background-color', component.getProperty(IMAGE_SETTINGS_BGCOLOR).value);
        } else {
            $('.image-settings').hide();
            $(element).find('.carousel-caption').css('background-color', 'transparent');
        }
    }


    $('#expandCheckbox').change(function () {
        if ($(this).is(":checked")) {
            $('#property-link').show();
        } else {
            $('#property-link').hide();
        }
    });


    RangeSliderHelper.bind('#interval', component, element, "interval", {
        min: 1,
        max: 10,
        postfix: "sec",
        onChange: function (obj) {
           
            var oldvalue = _.clone(component.getProperty("interval").value);
            var newvalue = _.clone(obj.fromNumber);

            var undoredo = function (val) {
                component.setProperty("interval", val);
                rerenderViewer();
            }

            undoredo(newvalue);

            var propertyName = "interval";
            var componentid = UI.undoManagerGetLatestComponentId();

            if (propertyName != null && "interval" == propertyName
                && componentid != null && componentid == component.id) {
                UI.undoManagerAddSpecific(
                    {
                        redo: function () {
                            undoredo(newvalue, "");
                        }
                    }, "redo");
            }
            else {
                UI.undoManagerAdd(
                {
                    undo: function () {
                        undoredo(oldvalue);
                    },
                    redo: function () {
                        undoredo(newvalue);
                    },
                    property: propertyName,
                    componentId: component.id
                });
            }


        }
    });
    $('#pauseCheckbox').bind('change', function () {
        UI.undoManagerAddSimple(component, "pause", $(this).prop('checked'), rerenderViewer, true);
    });
    $('#navCheckbox').bind('change', function () {
        UI.undoManagerAddSimple(component, "nav", $(this).prop('checked'), rerenderViewer, true);
    });
    $('#dotCheckbox').bind('change', function () {
        UI.undoManagerAddSimple(component, "dot", $(this).prop('checked'), rerenderViewer, true);
    });
    $('#captionCheckbox').bind('change', function () {
        UI.undoManagerAddSimple(component, "caption", $(this).prop('checked'), rerenderViewer, true);
    });
    $('#expandCheckbox').bind('change', function () {
        UI.undoManagerAddSimple(component, "expand", $(this).prop('checked'), rerenderViewer, true);
    });

    var changeOrder = function (source, direction) {
        var sourceElement = $('#' + source.id);
        var currentOrder;
        if (direction == 'left') {
            var previousElement = sourceElement.prev();
            if (previousElement.length > 0) {
                var previousComponent = UI.siteComponentRepository.lookupData({ id: previousElement.getId() });
                if (previousComponent != null) {
                    sourceElement.detach();
                    sourceElement.insertBefore(previousElement);
                    currentOrder = source.getProperty("order").value.toInteger();
                    var previousOrder = previousComponent.getProperty("order").value.toInteger();
                    source.setProperty("order", previousOrder);
                    previousComponent.setProperty("order", currentOrder);
                    rerenderViewer();
                }
            }
        } else if (direction == 'right') {
            var nextElement = sourceElement.next();
            if (nextElement.length > 0) {
                var nextComponent = UI.siteComponentRepository.lookupData({ id: nextElement.getId() });
                if (nextComponent != null) {
                    sourceElement.detach();
                    sourceElement.insertAfter(nextElement);
                    currentOrder = source.getProperty("order").value.toInteger();
                    var nextOrder = nextComponent.getProperty("order").value.toInteger();
                    source.setProperty("order", nextOrder);
                    nextComponent.setProperty("order", currentOrder);
                    rerenderViewer();
                }
            }
        }
    }
    var removePreviewHighlighting = function () {
        $('#gallery-editor .gallery-items-list .gallery-item').removeClass('active');
    }
    var addPreviewHighlighting = function (id) {
        $('#gallery-editor .gallery-items-list #' + id).addClass('active');
    }

    EditorFactory.bindEvents(component);

    var saveItemSettings = function () {
        var id = $('.save-gallery-item').data('id');
        var item = component.children.where({ id: id });
        if (item.any()) {
            item = item.firstOrDefault();

            var structs = [
                { component: item, property: "title", newvalue: _.clone($('.item-edit-container .gallery-item-title').val()), oldvalue: '' },
                { component: item, property: "description", newvalue: _.clone($('.item-edit-container .gallery-item-description').val()), oldvalue: '' }
            ];

            UI.undoManagerAddSimpleArr(structs, rerenderViewer, rerenderViewer, true);

        };
    }
    $('#gallery-editor .close-button').bind('click', function () {
        UI.removeEditor();
    });
    var deleteGalleryItem = function (id) {
        UI.undoManagerAddRemoving(id, '#gallery-editor .gallery-items-list #' + id, rerenderViewer);
    }
    var rerenderViewer = function () {
        
        var template = TemplateFactory.templateFor(component, VIEWER_TEMPLATE).compiledTemplate;
        $(component.getUISelector()).html($(template).html());
        var element = component.getUISelector();
        showImageSettings(component, element);
        bindStretching();
        ViewerFactory.tryUpdatePredefinedView(component);

        var isShowOptimized = component.getProperty(SHOW_OPTIMIZED).value.toBoolean();
        component.children.forEach(function (item) {
            $('#gallery-editor .gallery-items-list .gallery-item#' + item.id + ">div>div").css('background-image', 'url("' + ContextFactory.prepareImgSrc(item.getProperty(SRC).value, isShowOptimized) + '")');
            $('#gallery-editor .gallery-items-list .gallery-item#' + item.id + " img").attr('src', ContextFactory.prepareImgSrc(item.getProperty(SRC).value, isShowOptimized));
        });
    }


    function bindStretching() {
        var stretchingVal = $('#scalingInput').val() || "fill";
        var stretchingController = new StretchingController(['fill', 'crop'], stretchingVal, component);
        ko.observable($(element).find('.carousel-inner')[0]).extend({ applyBindings: stretchingController });
    }

    component.children.forEach(function (item) {
        $(item.getUISelector()).find('.remove-button').bind('click', function (e) {
            e.stopPropagation();
            deleteGalleryItem($(this).closest('.gallery-item').getId());
        });
        $(item.getUISelector()).find('.order-left').bind('click', function (e) {
            e.stopPropagation();
            changeOrder(item, 'left');
        });
        $(item.getUISelector()).find('.order-right').bind('click', function (e) {
            e.stopPropagation();
            changeOrder(item, 'right');
        });
        $(item.getUISelector()).bind('click', function () {
            removePreviewHighlighting();
            addPreviewHighlighting($(this).getId());
            EditorFactory.showItemSettings(item);
            Helpers.hideValidationEngine("#linkInput");
        });
    });
    $('.gallery-item-title, .gallery-item-description').bind('change', function () {
        saveItemSettings();
    });

    $('.save-gallery-item').hide();
    EditorFactory.hideItemSettings();

    EditorFactory.upclickPicture(component, ctx, deleteGalleryItem, removePreviewHighlighting, addPreviewHighlighting, EditorFactory.showItemSettings, rerenderViewer, changeOrder); // multiple true

    this.layering = this.layering || new LayeringController(component);
}


var HtmlContainerEditor = function (component) {

    var element = component.getUISelector();

    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT);
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
    ColorPickerHelper.bind(element, '#bgcolorInput', component, BACKGROUND_COLOR, function (com, el, val) {
    });
    ColorPickerHelper.bind(element, '#brcolorInput', component, BORDER_COLOR, function (com, el, val) {
    });

    RangeSliderHelper.bind('#borderWidth', component, element, BORDER_WIDTH, {
        min: 0,
        max: 10,
        postfix: "px"
    });

    var text = component.getProperty(TEXT).value;
    $('#htmleditor').text(text);

    var editor = ace.edit("htmleditor");
    editor.setTheme("ace/theme/monokai");
    editor.getSession().setMode("ace/mode/html");
    editor.setShowPrintMargin(false);
    editor.on('blur', function (e) {
        UI.undoManagerAddSimple(component, "text", editor.getValue(), rerenderViewer, false);
        component.setProperty("text", editor.getValue());
        rerenderViewer();
    });

    function rerenderViewer() {
        UI.actionService.runActionForComponent(component, ACTION_REMOVE_FROM_FORM, true);
        UI.actionService.runActionForComponent(component, ACTION_ADD_TO_FORM, true);
    }
    $('#gallery-editor .close-button').bind('click', function () {
        UI.removeEditor();
    });

    this.layering = this.layering || new LayeringController(component);
}

var ListEditor = function (component) {
    var element = component.getUISelector();

    EditorFactory.addedTooltip(["linkInput"], "bottom");

    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT);
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
    EditorEventsFactory.attachPlainEvent('#typeInput', element, component, CHANGE, "type", function (com, el, val) {
        rerenderViewer();
    });
    EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);
    
    EditorEventsFactory.attachPlainEvent('#fontOptionInput', element, component, CHANGE, "font-family", function (com, el, value) {
        rerenderViewer();
    });

    EditorEventsFactory.attachPlainEvent('#isShowOptimized',
        element,
        component,
        CHANGE,
        SHOW_OPTIMIZED,
        function (component, element, value) {
            rerenderViewer();
        });


    if ($("#gallery-editor").css("display") == "block") {
        var ctx = StretchingController.shareInstanceBetweenNodes($(element).find('.list-stretch-binding-node')[0], $("#gallery-editor")[0]);
    }

    scallingItemList($("#gallery-editor").find(".holder"), $("#scalingInput").val());

    $('#scalingInput').bind(CHANGE, function () {
        rerenderViewer();
        scallingItemList($("#gallery-editor").find(".holder"), $(this).val());
    });

    ColorPickerHelper.bind(element, '#headercolorInput', component, IMAGE_SETTINGS_HEADERCOLOR, function (com, el, val) {
        $(el).find('.title').css('color', val);
    });

    ColorPickerHelper.bind(element, '#colorInput', component, IMAGE_SETTINGS_COLOR, function (com, el, val) {
        $(el).find('.description').css('color', val);
    });

    ColorPickerHelper.bind(element, '#bgcolorInput', component, IMAGE_SETTINGS_BGCOLOR, function (com, el, val) {
        if ($(el).find(".vertical").hasClass("vertical")) { $(el).find('.title').css('background-color', val); $(el).find('.description').css('background-color', val); }
        if ($(el).find(".general").hasClass("general")) { $(el).find('.title').css('background-color', val); $(el).find('.description').css('background-color', val); $(el).find('.meta').css('background-color', val); }
        if ($(el).find(".lines").hasClass("lines")) { $(el).find(".p5").css('background-color', val); }
        if ($(el).find(".thumbnails").hasClass("thumbnails")) { $(el).find('.title').css('background-color', val); }
        $(el).find('.content').css('background-color', val);
    });
    
    ColorPickerHelper.bind(element, '#optionalcolorInput', component, IMAGE_SETTING_OPTIONALCOLOR, function (com, el, val) {
            $(el).find('.meta').css('color', val);
    });

    RangeSliderHelper.bind('#fontSize', component, element, FONT_SIZE, {
        min: 6,
        max: 40,
        postfix: "px"
    }, function () {
        saveItemSettings();
        ViewerFactory.calculateHeightForList(component, element);
    });

    function bindStretching() {

        var stretchingVal = $('#scalingInput').val() || "fill";
        var stretchingController = new StretchingController(['fill', 'crop'], stretchingVal, component);

        ko.observable($(element).find('.list-stretch-binding-node')[0]).extend({ applyBindings: stretchingController });
    }

    //pre-editor
    $('.list-type-option').bind("click", function () {
        var listItemValue = $(this).text();
        $('#typeInput').val(listItemValue).change();
        setPreeditorImg();
        $('.list-type-option').removeClass('active');
        $(this).addClass('active');
        Helpers.removeSelectWrapper();
    });

    function setPreeditorImg() {
        var listItemValue = $('#typeInput').val();
        var image = "<img src=\"/Images/list-types/list-" + listItemValue.split(" ").join("") + ".png\" />";
        $('#list-type').empty();
        $('#list-type').prepend(image);
    }

    setPreeditorImg();

    $('.save-type').bind('click', function () {
        component.isNotApproved = false;
        AddItem();
        AddItem();

        //publish component added 
        eventsystem.publish('/component/create/', component, element);
        $('#heightInput').val(calculateHeight()).change();

        UI.removeEditor();
        //add 2 items
    });
    //end pre-editor

    var type = component.getProperty(TYPE).value;
    if (type == "only text" || type == "lines") {
        $('#expandLabel').hide();
        $('#expandCheckbox').hide();
    }
    else {
        $('#expandCheckbox').change(function () {
            if ($(this).is(":checked")) {
                $('#property-link').show();
            } else {
                $('#property-link').hide();
            }
        }).change();


        $('#expandCheckbox').bind('change', function () {
            UI.undoManagerAddSimple(component, "expand", $(this).prop('checked'), rerenderViewer, true);
        });
    }

    function calculateHeight() {
        var height = 0;
        var listItems = $(element).find('.item');
        var index;
        for (index = 0; index < listItems.length; index++) {
            height += parseInt($(listItems[index]).css('height'));
            if (index != listItems.length - 1) {
                height += parseInt($(listItems[index]).css('margin-bottom'));
            }
        }
        if (height < 25) {
            height = 300;
        }
        return height;
    }

    function checkType() {
        var type = component.getProperty(TYPE).value;

        switch (type) {
            case "simple":
                break;
            case "vertical":
                $('#list-item-optional').hide();
                break;
            case "only text":
                $('#list-item-optional').hide();
                $('#list-item-upload').hide();
                break;
            case "lines":
                $('#list-item-optional').hide();
                $('#list-item-upload').hide();
                break;
            case "general":
                break;
            case "thumbnails":
                $('#list-item-optional').hide();
                $('#list-item-description').hide();
                break;
        }

    }
    checkType();

    var changeOrder = function (source, direction) {
        var sourceElement = $('#' + source.id);
        var currentOrder;
        if (direction == 'left') {
            var previousElement = sourceElement.prev();
            if (previousElement.length > 0) {
                var previousComponent = UI.siteComponentRepository.lookupData({ id: previousElement.getId() });
                if (previousComponent != null) {
                    sourceElement.detach();
                    sourceElement.insertBefore(previousElement);
                    currentOrder = source.getProperty("order").value.toInteger();
                    var previousOrder = previousComponent.getProperty("order").value.toInteger();
                    source.setProperty("order", previousOrder);
                    previousComponent.setProperty("order", currentOrder);
                    rerenderViewer();
                }
            }
        } else if (direction == 'right') {
            var nextElement = sourceElement.next();
            if (nextElement.length > 0) {
                var nextComponent = UI.siteComponentRepository.lookupData({ id: nextElement.getId() });
                if (nextComponent != null) {
                    sourceElement.detach();
                    sourceElement.insertAfter(nextElement);
                    currentOrder = source.getProperty("order").value.toInteger();
                    var nextOrder = nextComponent.getProperty("order").value.toInteger();
                    source.setProperty("order", nextOrder);
                    nextComponent.setProperty("order", currentOrder);
                    rerenderViewer();
                }
            }
        }
    }
    var removePreviewHighlighting = function () {
        $('#gallery-editor .gallery-items-list .gallery-item').removeClass('active');
    }
    var addPreviewHighlighting = function (id) {
        $('#gallery-editor .gallery-items-list #' + id).addClass('active');
    }

    EditorFactory.bindEvents(component);

    var saveItemSettings = function () {
        var id = $('.save-gallery-item').data('id');
        var item = component.children.where({ id: id });
        if (item.any()) {
            item = item.firstOrDefault();
            
            var structs = [
                { component: item, property: "title", newvalue: _.clone($('.item-edit-container .gallery-item-title').val()), oldvalue: '' },
                { component: item, property: "description", newvalue: _.clone($('.item-edit-container .gallery-item-description').val()), oldvalue: '' },
                { component: item, property: "optional", newvalue: _.clone($('.item-edit-container .gallery-item-optional').val()), oldvalue: '' }
            ]

            UI.undoManagerAddSimpleArr(structs, rerenderViewer, rerenderViewer, true);
        };
    }
    $('#gallery-editor .close-button').bind('click', function () {
        UI.removeEditor();
    });
    var deleteGalleryItem = function (id) {
        UI.undoManagerAddRemoving(id, '#gallery-editor .gallery-items-list #' + id, rerenderViewer);
    }
    
    function showSettingItem(com, el) {
        $(element).find('.title').css('color', component.getProperty(IMAGE_SETTINGS_HEADERCOLOR).value);
        $(element).find('.meta').css('color', component.getProperty(IMAGE_SETTING_OPTIONALCOLOR).value);
        $(element).find('.description').css('color', component.getProperty(IMAGE_SETTINGS_COLOR).value);
        var imageBgColor = component.getProperty(IMAGE_SETTINGS_BGCOLOR).value;
        $(element).find('.content').css('background-color', imageBgColor);

        $(element).find('.title, .description, .meta, .optional').css('font-size', component.getProperty(FONT_SIZE).value);

        if ($(element).find(".vertical").hasClass("vertical")) {
            $(element).find('.title, .description').css('background-color', imageBgColor);
        }
        if ($(element).find(".general").hasClass("general")) {
            $(element).find('.title, .description, .meta').css('background-color', imageBgColor);
        }
        if ($(element).find(".lines").hasClass("lines")) {
            $(element).find(".p5").css('background-color', imageBgColor);
        }
        if ($(element).find(".thumbnails").hasClass("thumbnails")) {
            $(element).find('.title').css('background-color', imageBgColor);
        }

        if ($(element).css("display") == "block") {
            ViewerFactory.correctShowListComponent(com);
        }
    }

    var rerenderViewer = function () {
        var template = TemplateFactory.templateFor(component, VIEWER_TEMPLATE).compiledTemplate;
        $(component.getUISelector()).html($(template).html());

        showSettingItem(); // get in bd setting font management;
        bindStretching();
        
        $('#heightInput').val(calculateHeight()).change();

        var isShowOptimized = component.getProperty(SHOW_OPTIMIZED).value.toBoolean();
        component.children.forEach(function (item) {
            $('#gallery-editor .gallery-items-list .gallery-item#' + item.id + " img").attr('src', ContextFactory.prepareImgSrc(item.getProperty(SRC).value, isShowOptimized));
        });
    }

    component.children.forEach(function (item) {
        $(item.getUISelector()).find('.remove-button').bind('click', function (e) {
            e.stopPropagation();
            deleteGalleryItem($(this).closest('.gallery-item').getId());
        });
        $(item.getUISelector()).find('.order-left').bind('click', function (e) {
            e.stopPropagation();
            changeOrder(item, 'left');
        });
        $(item.getUISelector()).find('.order-right').bind('click', function (e) {
            e.stopPropagation();
            changeOrder(item, 'right');
        });
        $(item.getUISelector()).bind('click', function (e) {
            removePreviewHighlighting();
            addPreviewHighlighting($(this).getId());
            EditorFactory.showItemSettings(item);
            Helpers.hideValidationEngine("#linkInput");
        });
    });
    $('.gallery-item-title, .gallery-item-description, .gallery-item-optional').bind('change', function () {
        saveItemSettings();
    });

    $('.save-gallery-item').hide();

    EditorFactory.hideItemSettings();

    $('#add-button').bind('click', function () {
        AddItem();
    });

    function AddItem() {
        var src = BLANKIMAGE;
        var basicComponentsFiltered = UI.basicComponentRepository.getAll().where({ name: "list-item" });
        if (basicComponentsFiltered.any()) {
            var basicComponent = basicComponentsFiltered.firstOrDefault();
            var galleryItemComponent = new Component().createNew(basicComponent, true);
            var nextOrder;
            var galleryOrders = [];
            component.children.forEach(function (item) {
                galleryOrders.push(item.getProperty("order").value.toInteger());
            });
            if (galleryOrders.any()) {
                var maxOrderedGalleryItem = _.max(galleryOrders);
                nextOrder = maxOrderedGalleryItem + 1;
            } else {
                nextOrder = 0;
            }
            galleryItemComponent.setProperty("src", src);
            galleryItemComponent.setProperty("order", nextOrder);
            UI.siteComponentRepository.appendTo(galleryItemComponent, component);
            var context = {
                id: galleryItemComponent.id,
                src: src
            }
            var compiledTemplate = HandlebarHelper.compileTemplate(basicComponent.editorTemplate, context);
            $('#gallery-editor .gallery-items-list').append(compiledTemplate);
            
            $(galleryItemComponent.getUISelector()).find('.remove-button').bind('click', function (e) {
                e.stopPropagation();
                deleteGalleryItem($(this).closest('.gallery-item').getId());
            });
            $(galleryItemComponent.getUISelector()).bind('click', function () {
                removePreviewHighlighting();
                addPreviewHighlighting($(this).getId());
                EditorFactory.showItemSettings(galleryItemComponent);
            });
            $(galleryItemComponent.getUISelector()).find('.order-left').bind('click', function (e) {
                e.stopPropagation();
                changeOrder(galleryItemComponent, 'left');
            });
            $(galleryItemComponent.getUISelector()).find('.order-right').bind('click', function (e) {
                e.stopPropagation();
                changeOrder(galleryItemComponent, 'right');
            });
            removePreviewHighlighting();
            addPreviewHighlighting($(galleryItemComponent.getUISelector()).getId());
            EditorFactory.showItemSettings(galleryItemComponent);
            rerenderViewer();

            UI.undoManagerAddAddElements([galleryItemComponent], rerenderViewer);

        }
    }

    $('.upclick-button').upclick(component,
        {
            type: UPCLICK_TYPE_PICTURE,
            oncomplete: function (response) {
                var file = Helpers.ProcessUploadSingleFileCompleted(response, $('.upclick-editor-error'), $('.upclick-button'));
                
                if (file != null)
                {
                    var id = $('.save-gallery-item').data('id');
                    $('#' + id).find('.holder img').attr('src', file.url);
                    var galleryItemComponent = UI.siteComponentRepository.lookupData({ id: id });
                    galleryItemComponent.setProperty("src", file.url);

                    UI.undoManagerAddSimple(galleryItemComponent, "src", file.url, rerenderViewer, false);
                    rerenderViewer();

                    var img = new Image();
                    img.onload = function () {
                        scallingItemList($("#gallery-editor").find(".holder"), $("#scalingInput").val());
                    }
                    img.src = file.url;
                }
                
            }
        });

    this.layering = this.layering || new LayeringController(component);
}

/*Form Components*/


var FormEditor = function (component) {
    var element = component.getUISelector();

    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT);
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
    EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);

    ColorPickerHelper.bind(element, '#bgcolorInput', component, BACKGROUND_COLOR);
    ColorPickerHelper.bind(element, '#brcolorInput', component, BORDER_COLOR);

    RangeSliderHelper.bind('#borderWidth', component, element, BORDER_WIDTH, {
        min: 0,
        max: 15,
        postfix: "px"
    });
    RangeSliderHelper.bind('#borderRadius', component, element, BORDER_RADIUS, {
        min: 0,
        max: 50,
        postfix: "px"
    });

    var event = function () { };
    $('#add-label-to-form').on('click', function () {
        app.addNewComponent('_label', event, component);
    });

    $('#add-textbox-to-form').on('click', function () {
        app.addNewComponent('_textbox', event, component);
    });

    $('#add-textarea-to-form').on('click', function () {
        app.addNewComponent('_textarea', event, component);
    });

    $('#add-radio-list-to-form').on('click', function () {
        app.addNewComponent('_radio-list', event, component);
    });

    $('#add-select-list-to-form').on('click', function () {
        app.addNewComponent('_select-list', event, component);
    });

    $('#add-checkbox-to-form').on('click', function () {
        app.addNewComponent('_checkbox', event, component);
    });

    $('#add-submit-to-form').on('click', function () {
        app.addNewComponent('_submit', event, component);
    });

    $('#add-attachment-to-form').on('click', function () {
        app.addNewComponent('_attachment', event, component);
    });

    $('#add-captcha-to-form').on('click', function () {
        app.addNewComponent(CAPTCHA, event, component);
    });

    $('#add-cancel-to-form').on('click', function () {
        app.addNewComponent('_cancel', event, component);
    });

    $('#add-autocomplete-address-to-form').on('click', function () {
        app.addNewComponent('_autocomplete-address', event, component);
    });

    EditorEventsFactory.attachPlainEvent('#mail-history',
        element,
        component,
        CHANGE,
        MAIL_HISTORY_PROPERTY,
        function (component, element, value) {});

    this.layering = this.layering || new LayeringController(component);

}

var LabelEditor = function (component) {

    var element = component.getUISelector();


    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT);
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
    ColorPickerHelper.bind(element, '#bgcolorInput', component, BACKGROUND_COLOR);
    EditorEventsFactory.attachPlainEvent('#textInput', element, component, CHANGE, "text", function () {
        rerenderViewer();
    });

    EditorEventsFactory.attachPlainEvent('#textalign', element, component, CHANGE, TEXT_ALIGN);
    EditorEventsFactory.attachPlainEvent('#fontOptionInput', element, component, CHANGE, FONT_FAMILY);
    ColorPickerHelper.bind(element, '#colorInput', component, COLOR);

    RangeSliderHelper.bind('#fontSize', component, element, FONT_SIZE, {
        min: 6,
        max: 50,
        postfix: "px"
    });

    var rerenderViewer = function () {
        var template = TemplateFactory.templateFor(component, VIEWER_TEMPLATE).compiledTemplate;
        $(component.getUISelector()).html($(template).html());
    }

}


var TextboxEditor = function (component) {

    var element = component.getUISelector();

    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT);
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);

    EditorEventsFactory.attachPlainEvent('#isRequired', element, component, CHANGE, REQUIRED_FIELD);

    EditorEventsFactory.attachPlainEvent('#contentInput', element, component, CHANGE, "placeholder", function (com, el, val) {
        $(el).attr('placeholder', val);
    });
    EditorEventsFactory.attachPlainEvent('#textboxtype', element, component, CHANGE, "text-box-type");
    EditorEventsFactory.attachPlainEvent('#fontOptionInput', element, component, CHANGE, FONT_FAMILY);
    EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);

    ColorPickerHelper.bind(element, '#colorInput', component, COLOR);
    ColorPickerHelper.bind(element, '#bgcolorInput', component, BACKGROUND_COLOR);
    ColorPickerHelper.bind(element, '#brcolorInput', component, BORDER_COLOR);

    RangeSliderHelper.bind('#borderWidth', component, element, BORDER_WIDTH, {
        min: 0,
        max: 15,
        postfix: "px"
    });
    RangeSliderHelper.bind('#borderRadius', component, element, BORDER_RADIUS, {
        min: 0,
        max: 50,
        postfix: "px"
    });
    RangeSliderHelper.bind('#fontSize', component, element, FONT_SIZE, {
        min: 6,
        max: 50,
        postfix: "px"
    });

    $('#formSpaceAfterItem').bind('change', function () {
        UI.undoManagerAddSimple(component, FORM_SPACE_AFTER_ITEM, $(this).prop('checked'), function () { }, true);
    });

    EditorEventsFactory.attachPlainEvent('#labeltype', element, component, CHANGE, SELECTEDLABELTYPE, function (component, element, value) {
        EditorFactory.switchLabelType(component)
    });

    $('#labelselect,#labelname').bind('change', function () {
        EditorFactory.switchLabelType(component);
    });
    EditorFactory.switchLabelType(component);

    var rerenderViewer = function () {
        var template = TemplateFactory.templateFor(component, VIEWER_TEMPLATE).compiledTemplate;
        $(component.getUISelector()).html($(template).html());
    }

    this.layering = this.layering || new LayeringController(component);
}

var AutocompleteAddressEditor = function (component) {

    var element = component.getUISelector();

    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT);
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);

    EditorEventsFactory.attachPlainEvent('#contentInput', element, component, CHANGE, "placeholder", function (com, el, val) {
        $(el.selector + ' #street1').attr('placeholder', val);
    });
    EditorEventsFactory.attachPlainEvent('#fontOptionInput', element, component, CHANGE, FONT_FAMILY);
    EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);

    ColorPickerHelper.bind(element, '#colorInput', component, COLOR);
    ColorPickerHelper.bind(element, '#bgcolorInput', component, BACKGROUND_COLOR);
    ColorPickerHelper.bind(element, '#brcolorInput', component, BORDER_COLOR);

    RangeSliderHelper.bind('#borderWidth', component, element, BORDER_WIDTH, {
        min: 0,
        max: 15,
        postfix: "px"
    });
    RangeSliderHelper.bind('#borderRadius', component, element, BORDER_RADIUS, {
        min: 0,
        max: 50,
        postfix: "px"
    });
    RangeSliderHelper.bind('#fontSize', component, element, FONT_SIZE, {
        min: 6,
        max: 50,
        postfix: "px"
    });

    $('#formSpaceAfterItem').bind('change', function () {
        UI.undoManagerAddSimple(component, FORM_SPACE_AFTER_ITEM, $(this).prop('checked'), function () { }, true);
    });

    EditorEventsFactory.attachPlainEvent('#labeltype', element, component, CHANGE, SELECTEDLABELTYPE, function (component, element, value) {
        EditorFactory.switchLabelType(component)
    });

    $('#labelselect,#labelname').bind('change', function () {
        EditorFactory.switchLabelType(component);
    });
    EditorFactory.switchLabelType(component);

    EditorEventsFactory.attachPlainEvent('#isRequired', element, component, CHANGE, REQUIRED_FIELD);    

    this.layering = this.layering || new LayeringController(component);
}


var RadiolistEditor = function (component) {

    var element = component.getUISelector();

    var deleteRadioItem = function (id) {
        UI.undoManagerAddRemoving(id, '#editor #radio-item-list #' + id, rerenderViewer);
    }

    var changeRadioItem = function (input) {
        var comId = $(input).data('id');
        var com = UI.siteComponentRepository.lookupData({ id: comId });
        var newValue = $(input).val();
        com.setProperty('text', newValue, false);
        var parentElement = $(com.parentComponent.getUISelector());
        parentElement.find('label[for="' + comId + '"]').html(newValue);
    }

    $('#editor #radio-item-list .remove-button ').on('click', function (e) {
        e.stopPropagation();
        deleteRadioItem($(this).data('id'));
    });

    $('#editor #radio-item-list input').on('change', function (e) {
        e.stopPropagation();
        changeRadioItem(this);
    });

    $('#formSpaceAfterItem').bind('change', function () {
        UI.undoManagerAddSimple(component, FORM_SPACE_AFTER_ITEM, $(this).prop('checked'), function () { }, true);
    });

    $('#add-radio-button').on('click', function () {
        var basicComponentsFiltered = UI.basicComponentRepository.getAll().where({ name: "_editor-item" });
        if (basicComponentsFiltered.any()) {

            var basicComponent = basicComponentsFiltered.firstOrDefault();
            var radioItemComponent = new Component().createNew(basicComponent, true);
            var nextOrder;
            var radioOrders = [];

            component.children.forEach(function (item) {
                radioOrders.push(item.getProperty("order").value.toInteger());
            });
            if (radioOrders.any()) {
                var maxOrderedItem = _.max(radioOrders);
                nextOrder = maxOrderedItem + 1;
            } else {
                nextOrder = 0;
            }
            radioItemComponent.setProperty("order", nextOrder);
            UI.siteComponentRepository.appendTo(radioItemComponent, component);
            var context = {
                id: radioItemComponent.id,
                text: radioItemComponent.getProperty("text").value
            }
            var compiledTemplate = HandlebarHelper.compileTemplate(basicComponent.editorTemplate, context);

            $('#editor #radio-item-list').append(compiledTemplate);

            $('#editor #radio-item-list #' + radioItemComponent.id + ' .remove-button ').on('click', function (e) {
                e.stopPropagation();
                deleteRadioItem($(this).data('id'));
            });

            $('#editor #radio-item-list #' + radioItemComponent.id + ' input').on('change', function (e) {
                e.stopPropagation();
                changeRadioItem(this);
            });

            rerenderViewer();
        }

    });//end onclick


    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT);
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);

    EditorEventsFactory.attachPlainEvent('#isRequired', element, component, CHANGE, REQUIRED_FIELD);

    EditorEventsFactory.attachPlainEvent('#fontOptionInput', element, component, CHANGE, FONT_FAMILY);
    EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);

    ColorPickerHelper.bind(element, '#colorInput', component, COLOR);
    ColorPickerHelper.bind(element, '#bgcolorInput', component, BACKGROUND_COLOR);
    ColorPickerHelper.bind(element, '#brcolorInput', component, BORDER_COLOR);

    EditorEventsFactory.attachPlainEvent('#styleInput', element, component, CHANGE, "predefined", function (com, el, val) {
        rerenderViewer();
    });


    RangeSliderHelper.bind('#borderWidth', component, element, BORDER_WIDTH, {
        min: 0,
        max: 15,
        postfix: "px"
    });
    RangeSliderHelper.bind('#borderRadius', component, element, BORDER_RADIUS, {
        min: 0,
        max: 50,
        postfix: "px"
    });
    RangeSliderHelper.bind('#fontSize', component, element, FONT_SIZE, {
        min: 6,
        max: 50,
        postfix: "px"
    });

    EditorEventsFactory.attachPlainEvent('#labeltype', element, component, CHANGE, SELECTEDLABELTYPE, function (component, element, value) {
        EditorFactory.switchLabelType(component)
    });

    $('#labelselect,#labelname').bind('change', function () {
        EditorFactory.switchLabelType(component);
    });
    EditorFactory.switchLabelType(component);

    var rerenderViewer = function () {
        var template = TemplateFactory.templateFor(component, VIEWER_TEMPLATE).compiledTemplate;
        $(component.getUISelector()).html($(template).html());
    }

    this.layering = this.layering || new LayeringController(component);
}


var SelectlistEditor = function (component) {

    var element = component.getUISelector();

    var deleteItem = function (id) {
        UI.undoManagerAddRemoving(id, '#editor #select-item-list #' + id, rerenderViewer);
    }

    var changeItem = function (input) {
        var comId = $(input).data('id');
        var com = UI.siteComponentRepository.lookupData({ id: comId });
        var newValue = $(input).val();
        com.setProperty('text', newValue, false);
        rerenderViewer();
    }

    $('#editor #select-item-list .remove-button ').on('click', function (e) {
        e.stopPropagation();
        deleteItem($(this).data('id'));
    });

    $('#editor #select-item-list input').on('change', function (e) {
        e.stopPropagation();
        changeItem(this);
    });

    $('#formSpaceAfterItem').bind('change', function () {
        UI.undoManagerAddSimple(component, FORM_SPACE_AFTER_ITEM, $(this).prop('checked'), function () { }, true);
    });

    $('#add-select-button').on('click', function () {
        var basicComponentsFiltered = UI.basicComponentRepository.getAll().where({ name: "_editor-item" });
        if (basicComponentsFiltered.any()) {

            var basicComponent = basicComponentsFiltered.firstOrDefault();
            var itemComponent = new Component().createNew(basicComponent, true);
            var nextOrder;
            var radioOrders = [];

            component.children.forEach(function (item) {
                radioOrders.push(item.getProperty("order").value.toInteger());
            });
            if (radioOrders.any()) {
                var maxOrderedItem = _.max(radioOrders);
                nextOrder = maxOrderedItem + 1;
            } else {
                nextOrder = 0;
            }
            itemComponent.setProperty("order", nextOrder);
            UI.siteComponentRepository.appendTo(itemComponent, component);

            var context = {
                id: itemComponent.id,
                text: itemComponent.getProperty("text").value
            }
            var compiledTemplate = HandlebarHelper.compileTemplate(basicComponent.editorTemplate, context);

            $('#editor #select-item-list').append(compiledTemplate);

            $('#editor #select-item-list #' + itemComponent.id + ' .remove-button ').on('click', function (e) {
                e.stopPropagation();
                deleteItem($(this).data('id'));
            });

            $('#editor #select-item-list #' + itemComponent.id + ' input').on('change', function (e) {
                e.stopPropagation();
                changeItem(this);
            });

            rerenderViewer();
        }

    });//end onclick


    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT);
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
    EditorEventsFactory.attachPlainEvent('#fontOptionInput', element, component, CHANGE, FONT_FAMILY);
    EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);

    EditorEventsFactory.attachPlainEvent('#isRequired', element, component, CHANGE, REQUIRED_FIELD);

    ColorPickerHelper.bind(element, '#colorInput', component, COLOR);
    ColorPickerHelper.bind(element, '#bgcolorInput', component, BACKGROUND_COLOR);
    ColorPickerHelper.bind(element, '#brcolorInput', component, BORDER_COLOR);

    RangeSliderHelper.bind('#borderWidth', component, element, BORDER_WIDTH, {
        min: 0,
        max: 15,
        postfix: "px"
    });
    RangeSliderHelper.bind('#borderRadius', component, element, BORDER_RADIUS, {
        min: 0,
        max: 50,
        postfix: "px"
    });
    RangeSliderHelper.bind('#fontSize', component, element, FONT_SIZE, {
        min: 6,
        max: 50,
        postfix: "px"
    });

    EditorEventsFactory.attachPlainEvent('#labeltype', element, component, CHANGE, SELECTEDLABELTYPE, function (component, element, value) {
        EditorFactory.switchLabelType(component)
    });

    $('#labelselect,#labelname').bind('change', function () {
        EditorFactory.switchLabelType(component);
    });
    EditorFactory.switchLabelType(component);

    var rerenderViewer = function () {
        var template = TemplateFactory.templateFor(component, VIEWER_TEMPLATE).compiledTemplate;
        $(component.getUISelector()).html($(template).html());
    }

    this.layering = this.layering || new LayeringController(component);
}



var CheckboxEditor = function (component) {

    var element = component.getUISelector();

    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
    EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);

    EditorEventsFactory.attachPlainEvent('#labeltype', element, component, CHANGE, SELECTEDLABELTYPE, function (component, element, value) {
        EditorFactory.switchLabelType(component)
    });
    $('#labelselect,#labelname').bind('change', function () {
        EditorFactory.switchLabelType(component);
    });
    EditorFactory.switchLabelType(component);

    $('#formSpaceAfterItem').bind('change', function () {
        UI.undoManagerAddSimple(component, FORM_SPACE_AFTER_ITEM, $(this).prop('checked'), function () { }, true);
    });

    EditorEventsFactory.attachPlainEvent('#isRequired', element, component, CHANGE, REQUIRED_FIELD);


    this.layering = this.layering || new LayeringController(component);
}

var CaptchaEditor = function(component) {
    var element = component.getUISelector();

    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
    EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);

    this.layering = this.layering || new LayeringController(component);
}

var SubmitEditor = function (component) {
    var element = component.getUISelector();
    
    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT,
        function (component, element) {
            ViewerFactory.calculateButtonLineHeight(component.getUISelector());
        });
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
    EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);

    EditorEventsFactory.attachPlainEvent('#formSubject', element, component, CHANGE, FORM_SUBJECT);

    EditorEventsFactory.attachPlainEvent('#textalign', element, component, CHANGE, TEXT_ALIGN);
    EditorEventsFactory.attachPlainEvent('#contentInput', element, component, CHANGE, TEXT);
    EditorEventsFactory.attachPlainEvent('#fontOptionInput', element, component, CHANGE, FONT_FAMILY);
    RangeSliderHelper.bind('#fontSize', component, element, FONT_SIZE, {
        min: 6,
        max: 50,
        postfix: "px"
    });

    ColorPickerHelper.bind(element, '#colorInput', component, COLOR);
    ColorPickerHelper.bind(element, '#txtcolorhoverInput', component, TEXT_COLOR_HOVER);
    ColorPickerHelper.bind(element, '#brcolorInput', component, BORDER_COLOR);
    ColorPickerHelper.bind(element, '#brcolorhoverInput', component, BORDER_COLOR_HOVER);
    ColorPickerHelper.bind(element, '#bgcolorInput', component, BACKGROUND_COLOR);
    ColorPickerHelper.bind(element, '#bgcolorhoverInput', component, BACKGROUND_COLOR_HOVER);

    RangeSliderHelper.bind('#borderWidth', component, element, BORDER_WIDTH, {
        min: 0,
        max: 15,
        postfix: "px"
    }, function () {
        ViewerFactory.calculateButtonLineHeight(component.getUISelector());
    });
    RangeSliderHelper.bind('#borderRadius', component, element, BORDER_RADIUS, {
        min: 0,
        max: 50,
        postfix: "px"
    });

    EditorEventsFactory.attachPlainEvent('#emailInput', element, component, CHANGE, "mode-value");

    this.layering = this.layering || new LayeringController(component);

    //success page view
    var successPageMasterLink = component.getProperty(SUCCESS_PAGE_MASTER_LINK).value;
    var successPageComponent = (successPageMasterLink && successPageMasterLink !== component.id)
        ? UI.siteComponentRepository.lookupData({ id: successPageMasterLink })
        : component;
    EditorFactory.bindEventsSuccessPage(successPageComponent);
    EditorFactory.setEventsToComponent(successPageComponent);
    EditorFactory.logoImageUploading(successPageComponent);
    EditorFactory.correctRedirectSuccessLink(successPageComponent, EDITOR_CONTEXT);

    SwitcherHelper.bind(element, '#useSuccessPageAnyway', component, SUCCESS_PAGE_MASTER_LINK, function (com, prop, value) {        
        UI.callEditor(com);
        $(UI.getConfigurationValue(EDITOR) + " .accordion .ui-accordion-content").removeClass("ui-accordion-content-active").hide();
        $(UI.getConfigurationValue(EDITOR) + " .accordion .content-success-settings").addClass("ui-accordion-content-active").show();
        $(UI.getConfigurationValue(EDITOR) + " .accordion .header-success-settings").addClass("ui-accordion-header-active").show();
    });

    ko.applyBindings({}, $('#editor')[0]);
}

var AttachmentEditor = function (component) {
    var element = component.getUISelector();

    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT,
        function (component, element) {
            ViewerFactory.calculateButtonLineHeight(component.getUISelector());
        });
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
    EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);
    
    EditorEventsFactory.attachPlainEvent('#textalign', element, component, CHANGE, TEXT_ALIGN);
    EditorEventsFactory.attachPlainEvent('#contentInput', element, component, CHANGE, TEXT);
    EditorEventsFactory.attachPlainEvent('#fontOptionInput', element, component, CHANGE, FONT_FAMILY);
    RangeSliderHelper.bind('#fontSize', component, element, FONT_SIZE, {
        min: 6,
        max: 50,
        postfix: "px"
    });

    ColorPickerHelper.bind(element, '#colorInput', component, COLOR);
    ColorPickerHelper.bind(element, '#txtcolorhoverInput', component, TEXT_COLOR_HOVER);
    ColorPickerHelper.bind(element, '#brcolorInput', component, BORDER_COLOR);
    ColorPickerHelper.bind(element, '#brcolorhoverInput', component, BORDER_COLOR_HOVER);
    ColorPickerHelper.bind(element, '#bgcolorInput', component, BACKGROUND_COLOR);
    ColorPickerHelper.bind(element, '#bgcolorhoverInput', component, BACKGROUND_COLOR_HOVER);

    RangeSliderHelper.bind('#borderWidth', component, element, BORDER_WIDTH, {
        min: 0,
        max: 15,
        postfix: "px"
    }, function () {
        ViewerFactory.calculateButtonLineHeight(component.getUISelector());
    });
    RangeSliderHelper.bind('#borderRadius', component, element, BORDER_RADIUS, {
        min: 0,
        max: 50,
        postfix: "px"
    });

    EditorEventsFactory.attachPlainEvent('#isRequired', element, component, CHANGE, REQUIRED_FIELD);

    this.layering = this.layering || new LayeringController(component);

    ko.applyBindings({}, $('#editor')[0]);
}

var CancelEditor = function (component) {
    var element = component.getUISelector();

    EditorEventsFactory.attachPlainEvent('#widthInput', element, component, CHANGE, WIDTH);
    EditorEventsFactory.attachPlainEvent('#heightInput', element, component, CHANGE, HEIGHT,
        function (component, element) {
            ViewerFactory.calculateButtonLineHeight(component.getUISelector());
        });
    EditorEventsFactory.attachPlainEvent('#leftInput', element, component, CHANGE, LEFT);
    EditorEventsFactory.attachPlainEvent('#topInput', element, component, CHANGE, TOP);
    EditorEventsFactory.attachPlainEvent('#zindex', element, component, CHANGE, Z_INDEX);

    EditorEventsFactory.attachPlainEvent('#textalign', element, component, CHANGE, TEXT_ALIGN);
    EditorEventsFactory.attachPlainEvent('#contentInput', element, component, CHANGE, TEXT);
    EditorEventsFactory.attachPlainEvent('#fontOptionInput', element, component, CHANGE, FONT_FAMILY);
    RangeSliderHelper.bind('#fontSize', component, element, FONT_SIZE, {
        min: 6,
        max: 50,
        postfix: "px"
    });

    ColorPickerHelper.bind(element, '#colorInput', component, COLOR);
    ColorPickerHelper.bind(element, '#txtcolorhoverInput', component, TEXT_COLOR_HOVER);
    ColorPickerHelper.bind(element, '#brcolorInput', component, BORDER_COLOR);
    ColorPickerHelper.bind(element, '#brcolorhoverInput', component, BORDER_COLOR_HOVER);
    ColorPickerHelper.bind(element, '#bgcolorInput', component, BACKGROUND_COLOR);
    ColorPickerHelper.bind(element, '#bgcolorhoverInput', component, BACKGROUND_COLOR_HOVER);

    RangeSliderHelper.bind('#borderWidth', component, element, BORDER_WIDTH, {
        min: 0,
        max: 15,
        postfix: "px"
    }, function () {
        ViewerFactory.calculateButtonLineHeight(component.getUISelector());
    });
    RangeSliderHelper.bind('#borderRadius', component, element, BORDER_RADIUS, {
        min: 0,
        max: 50,
        postfix: "px"
    });    

    this.layering = this.layering || new LayeringController(component);

    ko.applyBindings({}, $('#editor')[0]);
}

EditorFactory.switchLabelType = function (component) {
    var type = component.getProperty(SELECTEDLABELTYPE).value;
    var value = component.getProperty(SELECTEDLABEL).value;

    var newvalue = "";
    var oldvalue = _.clone(value);

    switch (type) {
        case "Label":
            $('#labelselect').show();
            $('#labelname').hide();
            $('#labelname').val('');
            newvalue = _.clone($('#labelselect').val());
            break;
        case "Name":
            $('#labelname').show();
            $('#labelselect').hide();
            $('#labelselect').val('');
            newvalue = _.clone($('#labelname').val());
            break;
        default:
            break;
    }
    UI.undoManagerAddSimple(component, SELECTEDLABEL, newvalue, null, true);
}
/*End Form Components*/

EditorFactory.linkManagementEditor = function (component, element)
{
    EditorFactory.addedTooltip(["linkInput"], "bottom");

    $('#blankPage').bind('change', function () {
        UI.undoManagerAddSimple(component, "open-link-on-blank", $(this).prop('checked'), function () { }, true);
    });

    EditorEventsFactory.attachPlainEvent('#modeInput', element, component, CHANGE, "mode", function (com, el, value) {
        EditorFactory.switchModes(com);
    });

    $('#linkInput,#anchorInputSelect,#pageInput,#callInput').bind('change', function () {
        EditorFactory.switchModes(component);
    });

    EditorFactory.switchModes(component);
}

EditorFactory.hideItemSettings = function() {
    $('.save-gallery-item').data('id', null);
    $('.item-edit-container').hide();
    $('.save-gallery-item').data('id', null);
    $('#gallery-editor .gallery-item-title').val('');
    $('#gallery-editor .gallery-item-description').val('');
    $('.item-edit-container .gallery-item-title').val('');
    $('.item-edit-container .gallery-item-optional').val('');
    $('.item-edit-container .gallery-item-description').val('');
}

EditorFactory.showItemSettings = function (item) {
    $('.item-edit-container').show();
    $('.save-gallery-item').data('id', item.id);
    $('.item-edit-container .gallery-item-title, #gallery-editor .gallery-item-title').val(item.getProperty(TITLE).value);

    var optional = item.getProperty(OPTIONAL);
    if (optional != null) {
        $('.item-edit-container .gallery-item-optional').val(optional.value);
    }

    $('.item-edit-container .gallery-item-description, #gallery-editor .gallery-item-description').val(item.getProperty(DESCRIPTION).value);

    $('#modeInput').val(item.getProperty(MODE).value);

    $('#linkInput, #anchorInputSelect, #pageInput, #anchorInput, #callInput').val(item.getProperty(MODE_VALUE).value);
    
    $('#blankPage').prop('checked', item.getProperty(OPEN_LINK_ON_BLANK).value.toBoolean());

    if ($('#expandCheckbox').is(":checked")) {
        $('#property-link').show();
    } else {
        $('#property-link').hide();
    }

    EditorFactory.switchModes(item);
}

EditorFactory.bindEvents = function (component) {
    $('#blankPage').bind('change', function () {
        var id = $('.save-gallery-item').data('id');
        var item = component.children.where({ id: id });
        if (item.any()) {
            item = item.firstOrDefault();
            item.setProperty("open-link-on-blank", $(this).prop('checked'));
        };
    });

    $('#modeInput').bind('change', function () {
        var id = $('.save-gallery-item').data('id');
        var item = component.children.where({ id: id });
        if (item.any()) {
            item = item.firstOrDefault();
            item.setProperty("mode", $('#modeInput').val());
            EditorFactory.switchModes(item);
        };
    });

    $('#linkInput,#anchorInputSelect,#pageInput,#callInput').bind('change', function () {
        var id = $('.save-gallery-item').data('id');
        var item = component.children.where({ id: id });
        if (item.any()) {
            item = item.firstOrDefault();
            EditorFactory.switchModes(item);
        };
    });
}


EditorFactory.switchModes = function (com) {
    var mode = com.getProperty(MODE).value;
    var modeValue = com.getProperty(MODE_VALUE).value;

    var newvalue = "";
    var oldvalue = _.clone(modeValue);

    $('#linkInput').removeAttr('disabled');
    $('#anchorInput').removeAttr('disabled');
    $('#anchorInputSelect').removeAttr('disabled');
    $('#pageInput').removeAttr('disabled');
    $('#callInput').removeAttr('disabled');
    switch (mode) {
        case "none":
            $('.mode-value').hide();
            $('#linkInput').attr('disabled', 'disabled');
            $('#linkInput').hide();
            $('#callInput').hide();
            $('#anchorInput').hide();
            $('#callInput').attr('disabled', 'disabled');
            $('#anchorInput').attr('disabled', 'disabled');
            $('#anchorInputSelect').attr('disabled', 'disabled');
            $('#pageInput').attr('disabled', 'disabled');
            newvalue = "";
            break;
        case "link":
            $('.mode-value').show();
            $('#linkInput').show();
            $('#callInput').hide();
            $('#callInput').val('');
            $('#pageInput').hide();
            $('#anchorInput').hide();
            $('#anchorInputSelect').hide();
            $('#anchorInput').val('');
            $('#pageInput option[value="None"]').prop('selected', true);
            newvalue = _.clone($('#linkInput').val());
            break;
        case "page":
            $('.mode-value').show();
            $('#linkInput').hide();
            $('#callInput').hide();
            $('#callInput').val('');
            $('#pageInput').show();
            $('#linkInput').val('');
            $('#anchorInput').hide();
            $('#anchorInputSelect').hide();
            $('#anchorInput').val('');
            newvalue = _.clone($('#pageInput').val());
            break;
        case "anchor":
            $('.mode-value').show();
            $('#anchorInput').hide();
            $('#anchorInputSelect').show();
            $('#callInput').hide();
            $('#callInput').val('');
            $('#linkInput').hide();
            $('#pageInput').hide();
            $('#linkInput').val('');
            $('#pageInput option[value="None"]').prop('selected', true);
            newvalue = _.clone($('#anchorInputSelect').val());
            break;
        case "call":
            $('.mode-value').show();
            $('#callInput').show();
            $('#anchorInput').hide();
            $('#anchorInputSelect').hide();
            $('#linkInput').hide();
            $('#pageInput').hide();
            $('#linkInput').val('');
            $('#pageInput option[value="None"]').prop('selected', true);
            newvalue = _.clone($('#callInput').val());
            break;
        default:
            break;
    }
    UI.undoManagerAddSimple(com, MODE_VALUE, newvalue, null, true);
}


// set tooltip to one or more elements
EditorFactory.addedTooltip = function (elements, place) {
    if (elements.length > 0) {
        for (var i = 0; i < elements.length; i++) {
            $("#" + elements[i]).tooltip({
                placement: place
            });
        }
    }
};
;
var ViewerFactory = function () { }

ViewerFactory.proxy = function () {
    var component = this;
    var element = $(component.getUISelector());
    if (component.getProperty(BACKGROUND_COLOR) != null) {
        element.css('background-color', component.getProperty(BACKGROUND_COLOR).value);
    }
    if (component.getProperty(COLOR) != null) {
        element.css('color', component.getProperty(COLOR).value);
    }
    var isHide = component.getProperty(HIDE_COMPONENT);

    if (isHide != null && isHide.value.toBoolean()) {
        element.addClass('disabled-component');
    }

    var name = component.proto.name;
    if (!UI.getDevice().isDesktop() && ViewerFactory.viewerForComponent[name + SLAVE]) {
        name += SLAVE;
    }
    return ViewerFactory.viewerForComponent[name] ? ViewerFactory.viewerForComponent[name](component) : ViewerFactory.viewerForComponent.default(component);
}

ViewerFactory.viewerForComponent = {};

ViewerFactory.viewerForComponent[MENU] = function (component) { return new MenuComponentViewer(component); },
ViewerFactory.viewerForComponent[BUTTON] = function (component) { return new ButtonComponentViewer(component); },
ViewerFactory.viewerForComponent[MAP] = function (component) { return new MapComponentViewer(component); },
ViewerFactory.viewerForComponent[BLOGGING] = function (component) { return new BloggingComponentViewer(component); },
ViewerFactory.viewerForComponent[ANCHOR] = function (component) { return new AnchorComponentViewer(component); },
ViewerFactory.viewerForComponent[IMAGE] = function (component) { return new ImageComponentViewer(component); },
ViewerFactory.viewerForComponent[FRAME] = function (component) { return new FrameComponentViewer(component); },
ViewerFactory.viewerForComponent[PANEL] = function (component) { return new PanelComponentViewer(component); },
ViewerFactory.viewerForComponent[FORM] = function (component) { return new FormComponentViewer(component); },

ViewerFactory.viewerForComponent[TEXTBOX] = function (component) { return new FormSubComponentViewer(component); },
ViewerFactory.viewerForComponent[AUTOCOMPLETE_ADDRESS] = function (component) { return new AutocompleteAddressComponentViewer(component); },
ViewerFactory.viewerForComponent[LABEL] = function (component) { return new FormSubComponentViewer(component); },
ViewerFactory.viewerForComponent[TEXTAREA] = function (component) { return new FormSubComponentViewer(component); },
ViewerFactory.viewerForComponent[SELECTLIST] = function (component) { return new FormSubComponentViewer(component); },
ViewerFactory.viewerForComponent[CHECKBOX] = function (component) { return new FormSubComponentViewer(component); },

ViewerFactory.viewerForComponent[RADIOLIST] = function (component) { return new RadiolistComponentViewer(component); },
ViewerFactory.viewerForComponent[SUBMIT] = function (component) { return new FormSubmitComponentViewer(component); },
ViewerFactory.viewerForComponent[ATTACHMENT] = function (component) { return new FormAttachmentComponentViewer(component); },
ViewerFactory.viewerForComponent[CANCEL] = function (component) { return new FormCancelComponentViewer(component); },
ViewerFactory.viewerForComponent[CAPTCHA] = function (component) { return new FormCaptchaComponentViewer(component); },
ViewerFactory.viewerForComponent[PARAGRAPH] = function (component) { return new ParagraphComponentViewer(component); },
ViewerFactory.viewerForComponent[GALLERY] = function (component) { return new GalleryComponentViewer(component); },
ViewerFactory.viewerForComponent[CONTACT_US] = function (component) { return new ContactUsComponentViewer(component); },
ViewerFactory.viewerForComponent[HTML_CONTAINER] = function (component) { return new HtmlContainerComponentViewer(component); },
ViewerFactory.viewerForComponent[SLIDESHOW] = function (component) { return new SlideshowComponentViewer(component); },
ViewerFactory.viewerForComponent[HEADERTEXT] = function (component) { return new HeadertextComponentViewer(component); },
ViewerFactory.viewerForComponent[VIDEO] = function (component) { return new VideoComponentViewer(component); },
ViewerFactory.viewerForComponent[SOUND] = function (component) { return new SoundComponentViewer(component); },
ViewerFactory.viewerForComponent[LIST] = function (component) { return new ListComponentViewer(component); },
ViewerFactory.viewerForComponent[HOUSE_PHOTO_TOUR] = function (component) { return new HousePhotoTourComponentViewer(component); },
ViewerFactory.viewerForComponent[EVALUATE_HOME] = function (component) { return new EvaluateHomeComponentViewer(component); },
ViewerFactory.viewerForComponent[MORTGAGE_CALCULATOR] = function (component) { return new MortgageCalculatorComponentViewer(component); },
ViewerFactory.viewerForComponent[PDF] = function (component) { return new PdfComponentViewer(component); },
ViewerFactory.viewerForComponent[SIGNIN] = function (component) { return new SigninComponentViewer(component); },

ViewerFactory.viewerForComponent[STORE_CART_LINK] = function (component) { return new StoreCartLinkComponentViewer(component); },
ViewerFactory.viewerForComponent[STORE_CATEGORIES] = function (component) { return new StoreCategoriesComponentViewer(component); },
ViewerFactory.viewerForComponent[STORE_CART] = function (component) { return new StoreCartComponentViewer(component); },
ViewerFactory.viewerForComponent[STORE_THANK_YOU] = function (component) { return new StoreThankYouComponentViewer(component); },

ViewerFactory.viewerForComponent[STORE_PRODUCT] = function (component) { return new StoreProductComponentViewer(component); },

ViewerFactory.viewerForComponent[STORE_PRODUCT_PRICE] = function (component) { return new StoreProductItemComponentViewer(component); },
ViewerFactory.viewerForComponent[STORE_PRODUCT_SKU] = function (component) { return new StoreProductItemComponentViewer(component); },
ViewerFactory.viewerForComponent[STORE_PRODUCT_QUANTITY] = function (component) { return new DefaultComponent(component); },
ViewerFactory.viewerForComponent[STORE_PRODUCT_ADD_TO_CART] = function (component) { return new StoreProductItemComponentViewer(component); },
ViewerFactory.viewerForComponent[STORE_PRODUCT_TITLE] = function (component) { return new StoreProductItemComponentViewer(component); },

ViewerFactory.viewerForComponent[STORE_PRODUCT_IMAGES] = function (component) { return new StoreProductImagesComponentViewer(component); },

ViewerFactory.viewerForComponent[STORE_GALLERY] = function (component) { return new StoreGalleryComponentViewer(component); },
ViewerFactory.viewerForComponent[STORE_GALLERY_SHOW_MORE] = function (component) { return new StoreGalleryItemComponentViewer(component); },

ViewerFactory.viewerForComponent['default'] = function (component) { return new DefaultComponentViewer(component); }

ViewerFactory.viewerForComponent[HOUSE_PHOTO_TOUR + SLAVE] = function (component) { return new HousePhotoTourSlaveComponentViewer(component); },
ViewerFactory.viewerForComponent[GALLERY + SLAVE] = function (component) { return new HousePhotoTourSlaveComponentViewer(component); },
ViewerFactory.viewerForComponent[SLIDESHOW + SLAVE] = function (component) { return new SlideshowSlaveComponentViewer(component); },
ViewerFactory.viewerForComponent[MENU + SLAVE] = function (component) { return new MenuSlaveComponentViewer(component); }

var StoreProductItemComponentViewer = function (component) {
    var selector = '.std-' + component.proto.name;
    ViewerFactory.calculateButtonLineHeight(selector);
}

var StoreProductImagesComponentViewer = function (component) {
    var element = $(component.getUISelector());
    var componentSelector = "[id='" + component.id + "']";
    var swiperTop = new Swiper(componentSelector + ' .gallery-top',
        {
            // Optional parameters
            direction: 'horizontal',
            loop: true,
            loopedSlides: 5, //looped slides should be the same     

            // Navigation arrows
            nextButton: componentSelector + ' .swiper-button-next',
            prevButton: componentSelector + ' .swiper-button-prev',
            centeredSlides: true

        });
    var galleryThumbs = new Swiper(componentSelector + ' .gallery-thumbs', {
        loopedSlides: 6, //looped slides should be the same     
        slidesPerView: 6,
        touchRatio: 0.2,
        loop: true,
        slideToClickedSlide: true,
        centeredSlides: true
    });
    swiperTop.params.control = galleryThumbs;
    galleryThumbs.params.control = swiperTop;
    if (!UI.getSetting('ispreview')) {
        swiperTop.lockSwipes();
        galleryThumbs.lockSwipes();
        //todo: temp fix
        //$(componentSelector + ' .gallery-thumbs .swiper-slide').width(100);
    }
}

var StoreCartLinkComponentViewer = function (component) {
    var element = $(component.getUISelector());
    if (!UI.getSetting('ispreview')) {
        if (component.isSelectable) {
            element.bind('click',
                function (event) {
                    if (ViewerFactory.editorCanBeShown(element, event)) {
                        event.stopPropagation();
                        $(this).highlightSelectedElement(component, true);
                    }
                });
        }
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    } else {
        if (UI.getSetting("ispublished")) {
            element.bind('click', Helpers.generateEventGoToPage);
        }
        setTimeout(function () {
            ko.applyBindings(UI.getViewModel(component.proto.name, component), element[0]);
        }, 0);
    }
    ViewerFactory.calculateButtonLineHeight(component.getUISelector());
}

var StoreCartComponentViewer = function (component) {
    var element = $(component.getUISelector());
    if (!UI.getSetting('ispreview')) {
        if (component.isSelectable) {
            element.bind('click',
                function(event) {
                    if (ViewerFactory.editorCanBeShown(element, event)) {
                        event.stopPropagation();
                        $(this).highlightSelectedElement(component, UI.getDevice().isDesktop());
                    }
                });
        }
    } else {
        $(element.selector).on('click', 'a.product-item', function (e) {
            e.preventDefault();
            UI.pager.goToPage(UI.pager.getPageId('product'), false, false, { key: 'productId', value: e.currentTarget.dataset.id });
        });
        setTimeout(function () {
            ko.applyBindings(UI.getViewModel(component.proto.name, component), element[0]);
        }, 0);
    }
}

var StoreThankYouComponentViewer = function (component) {
    var element = $(component.getUISelector());
    if (!UI.getSetting('ispreview')) {
        if (component.isSelectable) {
            element.bind('click',
                function (event) {
                    if (ViewerFactory.editorCanBeShown(element, event)) {
                        event.stopPropagation();
                        $(this).highlightSelectedElement(component, UI.getDevice().isDesktop());
                    }
                });
        }
    } else {
        setTimeout(function () {
            ko.applyBindings(UI.getViewModel(component.proto.name, component), element[0]);
        }, 0);
    }
}

var StoreProductComponentViewer = function (component) {    
    var element = $(component.getUISelector());
   
    if (!UI.getSetting('ispreview')) {    
        if (component.isSelectable) {
            element.bind('click',
                function (event) {
                    if (ViewerFactory.editorCanBeShown(element, event)) {
                        event.stopPropagation();                        
                        $(this).highlightSelectedElement(component, UI.getDevice().isDesktop());
                    }
                });
        }        
    } else {
        $(element.selector).on('click', '.breadcrumb > li > a', Helpers.generateEventGoToPage);
    }
    setTimeout(function () {
        ko.applyBindings(UI.getViewModel(component.proto.name, component), element[0]);
    }, 0);
    ViewerFactory.addLayeringController(this, component);
}

var StoreGalleryItemComponentViewer = function(component) {
    var element = $(component.getUISelector());
    $(element).css({ "text-overflow": "ellipsis", "overflow": "hidden" });
    $(element).hover(function () {
            if (component.getProperty(BACKGROUND_COLOR_HOVER) != null) {
                $(this).css('background-color', component.getProperty(BACKGROUND_COLOR_HOVER).value);
            }
            if (component.getProperty(BORDER_COLOR_HOVER) != null) {
                $(this).css('border-color', component.getProperty(BORDER_COLOR_HOVER).value);
            }
            if (component.getProperty(TEXT_COLOR_HOVER) != null) {
                $(this).css('color', component.getProperty(TEXT_COLOR_HOVER).value);
            }
        },
        function () {
            $(this).css('background-color', component.getProperty(BACKGROUND_COLOR).value);
            $(this).css('border-color', component.getProperty(BORDER_COLOR).value);
            $(this).css('color', component.getProperty(COLOR).value);
        });

    ViewerFactory.calculateButtonLineHeight(component.getUISelector());    
}

var StoreGalleryComponentViewer = function (component) {
    var element = $(component.getUISelector());
    if (!UI.getSetting('ispreview')) {
        if (component.isSelectable) {
            element.bind('click',
                function (event) {
                    if (ViewerFactory.editorCanBeShown(element, event)) {
                        event.stopPropagation();
                        $(this).highlightSelectedElement(component, true);
                    }
                });
        }
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    } else {
        $(element.selector).on('click', '.std-store-gallery-product > a', function(e) {
            e.preventDefault();
            UI.pager.goToPage(UI.pager.getPageId('product'), false, false, { key: 'productId', value: e.currentTarget.dataset.id });
        });
    }
    setTimeout(function () {
        ko.applyBindings(UI.getViewModel(component.proto.name, component, component.id), element[0]);
    }, 0);
}

var HousePhotoTourSlaveComponentViewer = function (component) {
    var element = $(component.getUISelector());

    if (component.children.length == 0) {
        var stretchingVal = component.getProperty(IMAGE_STRETCHING).value;
        if (stretchingVal === 'crop') {
            $(element.selector).find('.gallery-top .swiper-wrapper').append('<div class="swiper-slide"><div style="background-image: url(/Images/gallery_predefined.png);" class="stretch-image"></div></div>');
            $(element.selector).find('.gallery-thumbs .swiper-wrapper').append('<div class="swiper-slide"><div style="background-image: url(/Images/gallery_predefined.png);" class="stretch-image"></div></div>');
        } else {
            $(element.selector).find('.gallery-top .swiper-wrapper').append('<div class="swiper-slide"><div class="fitwidth-image"><img alt="" src="/Images/gallery_predefined.png" /></div></div>');
            $(element.selector).find('.gallery-thumbs .swiper-wrapper').append('<div class="swiper-slide"><div class="fitwidth-image"><img alt="" src="/Images/gallery_predefined.png" /></div></div>');
        }
    }

    var componentSelector = "[id = '" + component.id + "']";
    var swiperTop = new Swiper(componentSelector + ' .gallery-top',
    {
        // Optional parameters
        direction: 'horizontal',
        loop: true,
        loopedSlides: 5, //looped slides should be the same     

        // Navigation arrows
        nextButton: componentSelector + ' .swiper-button-next',
        prevButton: componentSelector + ' .swiper-button-prev',
        centeredSlides: true

    });    
    var galleryThumbs = new Swiper(componentSelector + ' .gallery-thumbs', {
        loopedSlides: 5, //looped slides should be the same     
        slidesPerView: 4,
        touchRatio: 0.2,
        loop: true,
        slideToClickedSlide: true,
        centeredSlides: true
    });
    swiperTop.params.control = galleryThumbs;
    galleryThumbs.params.control = swiperTop;
    if (!UI.getSetting('ispreview')) {
        swiperTop.lockSwipes();
        galleryThumbs.lockSwipes();

        if (component.isSelectable) {
            element.bind('click',
                function (event) {
                    if (ViewerFactory.editorCanBeShown(element, event)) {
                        event.stopPropagation();
                        $(this).highlightSelectedElement(component, true);
                    }
                });
        }
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    } else {
        if (UI.getSetting("ispublished") && component.proto.name === GALLERY) {
            _.forEach(component.children, function (child) {
                ViewerFactory.bindEventGoToPage(child, $(child.getUISelector() + '_view').find('a'));
            });
        }
    }
    ViewerFactory.addLayeringController(this, component);
}

var SlideshowSlaveComponentViewer = function (component) {
    var element = $(component.getUISelector());

    if (component.children.length == 0) {
        var stretchingVal = component.getProperty(IMAGE_STRETCHING).value;
        if (stretchingVal === 'crop') {
            $(element.selector)
                .find('.swiper-wrapper')
                .append('<div class="swiper-slide"><div style="background-image: url(/Images/gallery_predefined.png);" class="stretch-image"></div></div>');
        } else {
            $(element.selector)
                .find('.swiper-wrapper')
                .append('<div class="swiper-slide"><div class="fitwidth-image"><img alt="" src="/Images/gallery_predefined.png" /></div></div>');
        }
    }

    var componentSelector = "[id = '" + component.id + "']";
    var interval = component.getProperty(INTERVAL).value.toInteger();
    var nav = component.getProperty(NAV).value.toBoolean();
    var dot = component.getProperty(DOT).value.toBoolean();
    var pauseOnHover = component.getProperty(PAUSE).value.toBoolean();

    var swiperParams = {
        // Optional parameters
        direction: 'horizontal',
        loop: true,        
        autoplay: interval * 1000,
        centeredSlides: true,
        autoplayDisableOnInteraction: false
    }

    if (nav) {
        // Navigation arrows
        swiperParams.nextButton = componentSelector + ' .swiper-button-next';
        swiperParams.prevButton = componentSelector + ' .swiper-button-prev';        
    }
    if (dot) {
        // If we need pagination
        swiperParams.paginationClickable = true;
        swiperParams.pagination = componentSelector + ' .swiper-pagination';
    }

    var swiper = new Swiper(componentSelector + ' .swiper-container', swiperParams);

    if (!UI.getSetting('ispreview')) {
        swiper.stopAutoplay();
        swiper.lockSwipes();        

        if (component.isSelectable) {
            element.bind('click',
                function(event) {
                    if (ViewerFactory.editorCanBeShown(element, event)) {
                        event.stopPropagation();
                        $(this).highlightSelectedElement(component, true);
                    }
                });
        }
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    } else {
        if (UI.getSetting("ispublished")) {
            _.forEach(component.children, function (child) {
                ViewerFactory.bindEventGoToPage(child, $(child.getUISelector() + '_view').find('a'));
            });
        }
        if (pauseOnHover) {
            $(element)
                .hover(function () {
                    swiper.stopAutoplay();
                },
                    function () {
                        swiper.startAutoplay();
                    });
        }
    }
    ViewerFactory.addLayeringController(this, component);
}

var MenuSlaveComponentViewer = function (component) {
    var element = $(component.getUISelector());    

    if (!UI.getSetting("ispreview")) {
        if (component.isSelectable) {
            element.bind('click',
                function(event) {
                    if (ViewerFactory.editorCanBeShown(element, event)) {
                        event.stopPropagation();
                        $(this).highlightSelectedElement(component, true);
                    }
                });
        }
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    } else {
        var lastValues = {
            left: parseInt(component.getProperty(LEFT).value)
        };
        element.on('show.bs.dropdown',
            function() {
                lastValues.height = $('.site-wrapper.mobile')[0].scrollHeight;
            });
        element.on('shown.bs.dropdown',
            function () {                
                $(this).find('.dropdown-menu').css(LEFT, 20 - lastValues.left + 'px');
                Helpers.checkSiteWrapperHeightMobile();
            });
        element.on('hidden.bs.dropdown',
            function () {
                Helpers.checkSiteWrapperHeightMobile(lastValues.height);
            });
        if (UI.getSetting("ispublished")) {
            $(element.selector).on('click', 'ul li a', Helpers.generateEventGoToPage);
        }
    }

    ViewerFactory.addLayeringController(this, component);
};

var StoreCategoriesComponentViewer = function(component) {
    var element = $(component.getUISelector());

    $(element).hover(function () {
        if (!$(this).find('.dropdown-container').hasClass('open')) {
            if (component.getProperty(BACKGROUND_COLOR_HOVER) != null) {
                $(this).css('background-color', component.getProperty(BACKGROUND_COLOR_HOVER).value);
            }
            if (component.getProperty(BORDER_COLOR_HOVER) != null) {
                $(this).css('border-color', component.getProperty(BORDER_COLOR_HOVER).value);
            }
            if (component.getProperty(TEXT_COLOR_HOVER) != null) {
                $(this).css('color', component.getProperty(TEXT_COLOR_HOVER).value);
            }
        }
    }, function () {
        $(this).css('background-color', component.getProperty(BACKGROUND_COLOR).value);
        $(this).css('border-color', component.getProperty(BORDER_COLOR).value);
        $(this).css('color', component.getProperty(COLOR).value);
    });

    $(element).click(function() {
        if (!$(this).find('.dropdown-container').hasClass('open')) {
            $(this).css('background-color', component.getProperty(BACKGROUND_COLOR).value);
            $(this).css('border-color', component.getProperty(BORDER_COLOR).value);
            $(this).css('color', component.getProperty(COLOR).value);
        }
    });


    if (!UI.getSetting("ispreview")) {
        if (component.isSelectable) {
            element.bind('click',
                function (event) {
                    if (ViewerFactory.editorCanBeShown(element, event)) {
                        event.stopPropagation();
                        $(this).highlightSelectedElement(component, true);
                    }
                });
        }
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    } else {
        if (!UI.getDevice().isDesktop()) {
            var lastValues = {
                left: parseInt(component.getProperty(LEFT).value)
            };
            element.on('show.bs.dropdown',
                function() {
                    lastValues.height = $('.site-wrapper.mobile')[0].scrollHeight;
                });
            element.on('shown.bs.dropdown',
                function() {
                    $(this).find('.dropdown-menu').css(LEFT, 20 - lastValues.left + 'px');
                    Helpers.checkSiteWrapperHeightMobile();
                });
            element.on('hidden.bs.dropdown',
                function() {
                    Helpers.checkSiteWrapperHeightMobile(lastValues.height);
                });            
        }

        setTimeout(function () {
            ko.applyBindings(UI.getViewModel(component.proto.name, component, component.id), element.find('.dropdown-container')[0]);
        }, 0);
    }

    ViewerFactory.calculateButtonLineHeight(component.getUISelector());

    ViewerFactory.addLayeringController(this, component);
}

ViewerFactory.addLayeringController = function (elem, component) {
    if (!UI.getSetting("ispublished")) {
        elem.layering = elem.layering || new LayeringController(component);
    }

};

ViewerFactory.correctShowListComponent = function (component) {
    if (component != null) {

        var type = component.getProperty(TYPE).value;

        switch (type) {
            case VERTICAL:
                $(component.getUISelector()).find('.title').css('background-color', component.getProperty(IMAGE_SETTINGS_BGCOLOR).value);
                $(component.getUISelector()).find('.description').css('background-color', component.getProperty(IMAGE_SETTINGS_BGCOLOR).value);
                break;

            case GENERAL:
                $(component.getUISelector()).find('.title').css('background-color', component.getProperty(IMAGE_SETTINGS_BGCOLOR).value);
                $(component.getUISelector()).find('.description').css('background-color', component.getProperty(IMAGE_SETTINGS_BGCOLOR).value);
                $(component.getUISelector()).find('.meta').css('background-color', component.getProperty(IMAGE_SETTINGS_BGCOLOR).value);
                $(component.getUISelector()).find('.meta').css('color', component.getProperty(IMAGE_SETTING_OPTIONALCOLOR).value);
                break;

            case LINES:
                $(component.getUISelector()).find(".p5").css('background-color', component.getProperty(IMAGE_SETTINGS_BGCOLOR).value);
                break;

            case THUMBNAILS:
                $(component.getUISelector()).find('.title').css('background-color', component.getProperty(IMAGE_SETTINGS_BGCOLOR).value);
                break;

            case SIMPLE:
                $(component.getUISelector()).find('.meta').css('color', component.getProperty(IMAGE_SETTING_OPTIONALCOLOR).value);
                break;
            default:
                break;
        }

    }

}
ViewerFactory.correctMessageSuccessPage = function (component, customerEmail) {
    var message = component.getProperty(SUCCESS_PAGE_MESSAGE);
    message = message != null ? message.value : MEESSAGE_EMAIL_SUCCESS_DOMANIAN_NAME;
    var recipientEmail = component.getProperty('mode-value').value;

    var domenName = window.location.origin.split("/");

    domenName[2] = (domenName[2].length > 20) ? domenName[2].substr(0, 20) + "..." : domenName[2];
    message = message.replace(/<DomainName>/g, domenName[2]);
    message = message.replace(/<CustomerEmail>/g, customerEmail);
    message = message.replace(/<RecipientEmail>/g, "<a href='mailto:" + recipientEmail + "'>" + recipientEmail + "</a>");

    return message;
};

ViewerFactory.createModalWindow = function (component, customerEmail) {
    var contextProp = ContextFactory.contextFor(component, VIEWER_CONTEXT);
    contextProp.link = (contextProp.link == "") ? EditorFactory.correctRedirectSuccessLink(component, VIEWER_CONTEXT) : contextProp.link;
    contextProp.message = ViewerFactory.correctMessageSuccessPage(component, customerEmail);
    Application.showSuccessPage(contextProp.message, contextProp.link, contextProp.contentColor, contextProp.textColor, contextProp.ff,
        contextProp.fs, contextProp.image, contextProp.headerColor, contextProp.buttonColor, contextProp.headerText, contextProp.buttonText);
};

ViewerFactory.setJPlayerForComponent = function (component, src, title) {
    var selector = component.getUISelector() + JPLAYER_SUFFIX;
    var selector_container = component.getUISelector() + JPLAYER_CONTAINER_SUFFIX;
    $(selector).data("ready", "false");
    $(selector).jPlayer({
        ready: function () {
            $(this).jPlayer("setMedia", {
                title: title,
                m4a: src
            });
            $(this).data("ready", "true");
        },
        cssSelectorAncestor: selector_container,
        swfPath: "/content/image/jquery.jplayer.swf",
        supplied: "m4a,mp3",
        solution: "html,flash",
        useStateClassSkin: true,
        autoBlur: false,
        smoothPlayBar: true,
        keyEnabled: true,
        remainingDuration: true,
        toggleDuration: true,
    });
}

var DefaultComponentViewer = function (component) {
    var element = $(component.getUISelector());
    if ($(element).hasClass("header")) {
        var headerHeight = $(element)[0].offsetHeight;
        $('#header-resizer').css('top', headerHeight);
        $('.site-wrapper').css('height', headerHeight);
    }
    if ($(element).hasClass("main")) {
        var headerHeight = $(element)[0].offsetHeight + $(element)[0].offsetTop;
        $('#page-resizer').css('top', headerHeight);
    }
    if ($(element).hasClass("footer")) {
        var footerHeight = $(element)[0].offsetHeight + $(element)[0].offsetTop;
        $('#bottom-body-resizer').css('top', footerHeight);
    }
}

var MenuComponentViewer = function (component) {
    var element = $(component.getUISelector());
      
    ViewerFactory.initComponentPosition(component, element);

        var navmenu = $(element).children("ul");
        if (navmenu.length != 0) {
            ViewerFactory.calculateMenuLineHeight(component, element.selector);
            $(element.selector).height(component.getProperty(HEIGHT).value);

            $(element.selector).on('mouseenter', 'ul li', function () {
                if (component.getProperty(BACKGROUND_COLOR_HOVER) != null) {
                    $(this).css('background-color', component.getProperty(BACKGROUND_COLOR_HOVER).value);
                }
                if (component.getProperty(BACKGROUND_COLOR) != null) {
                    $(element.selector).find('ul li ul').css('background-color', component.getProperty(BACKGROUND_COLOR).value);
                }
                //$(this).css('border-radius', component.getProperty(BORDER_RADIUS).value);
            });


            $(element.selector).on('mouseenter', 'ul li:first-child', function () {
                var predefined = component.getProperty(PREDEFINED).value;
                var firstSide = 'border-bottom-left-radius';
                var secondSide = 'border-top-left-radius';
                if (predefined != "simple") {
                    firstSide = 'border-top-left-radius';
                    secondSide = 'border-top-right-radius';
                }
                $(this).css(firstSide, component.getProperty(BORDER_RADIUS).value);
                $(this).css(secondSide, component.getProperty(BORDER_RADIUS).value);
            });

            $(element.selector).on('mouseenter', 'ul li:last-child', function () {
                var predefined = component.getProperty(PREDEFINED).value;
                var firstSide = 'border-bottom-right-radius';
                var secondSide = 'border-top-right-radius';
                if (predefined != "simple") {
                    firstSide = 'border-bottom-left-radius';
                    secondSide = 'border-bottom-right-radius';
                }
                $(this).css(firstSide, component.getProperty(BORDER_RADIUS).value);
                $(this).css(secondSide, component.getProperty(BORDER_RADIUS).value);
            });

            $(element.selector).on('mouseenter', 'ul li ul li', function () {
                $(this).css('border-radius', '0px');
            });

            $(element.selector).on('mouseenter', 'ul li a', function () {
                if (component.getProperty(TEXT_COLOR_HOVER) != null) {
                    $(this).css('color', component.getProperty(TEXT_COLOR_HOVER).value);
                }
            });


            $(element.selector).on('mouseleave', 'ul li', function () {
                $(this).css('background-color', 'transparent');
            });

            $(element.selector).on('mouseleave', 'ul li a', function () {
                if (component.getProperty(COLOR) != null) {
                    $(this).css('color', component.getProperty(COLOR).value);
                }
            });

            $(element.selector).on('mouseenter', function () {
                if (component.getProperty(BORDER_COLOR_HOVER) != null) {
                    $(this).css('border-color', component.getProperty(BORDER_COLOR_HOVER).value);
                }
            });
            $(element.selector).on('mouseleave', function () {
                $(this).css('border-color', component.getProperty('border-color').value);
            });

            if (UI.getSetting("ispublished")) {
                $(element.selector).on('click', 'ul li a', Helpers.generateEventGoToPage);
            }
        }
    
    if (!UI.getSetting("ispreview")) {
        if (component.isSelectable) {
            element.bind('click', function (event) {
                if (ViewerFactory.editorCanBeShown(element, event)) {
                    event.stopPropagation();
                    $(this).highlightSelectedElement(component, true);
                }
            });
        }
        $(element).find('a').bind('click', function (e) {
            e.preventDefault();
        });
        if (component.isDraggable && !component.getProperty(IS_PINED).value.toBoolean()) {
            dragDrop.initElement(component.id);
        }
    }

    component.stretcher();

    ViewerFactory.addLayeringController(this, component);    
}

var VideoComponentViewer = function (component) {
    var element = $(component.getUISelector());
    var autoplay = component.getProperty(AUTOPLAY).value;
    element.attr('data-autoplay', autoplay);
    var src = Helpers.convertToUniversalUrl(component.getProperty(SRC).value);

    if (!UI.getSetting("ispreview")) {
        $(element).children('iframe').attr('src', src);

        if (component.isSelectable) {
            element.bind('click', function (event) {
                if (ViewerFactory.editorCanBeShown(element, event)) {
                    event.stopPropagation();
                    $(this).highlightSelectedElement(component, true);
                }
            });
        }
        $(element).find('a').bind('click', function (e) {
            e.preventDefault();
        });
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    }

    if (!UI.getSetting("ispreview") || UI.getSetting("isThumbnailPreview")) {
        ViewerFactory.replacementImageInsteadVideo(element.selector, src);
    }

    ViewerFactory.addLayeringController(this, component);
}


var SoundComponentViewer = function (component) {
    var element = $(component.getUISelector());

    if (!UI.getDevice().isDesktop()) {
        element.removeClass("std-sound-mobile").addClass("std-sound-mobile");
    }

    if (component.getProperty(HEIGHT) == null) {
        var heightJPlayer = $(component.getUISelector() + JPLAYER_CONTAINER_SUFFIX).outerHeight();
        component.setProperty(HEIGHT, heightJPlayer + 'px');
        element.css(HEIGHT, heightJPlayer);
    }

    var autoplay = component.getProperty(AUTOPLAY).value;
    element.attr('data-autoplay', autoplay);
    //var hide = component.getProperty(HIDE).value;
        
    if (!UI.getSetting("ispreview")) {
        if (component.isSelectable) {

            element.bind('click', function (event) {
                if (ViewerFactory.editorCanBeShown(element, event)) {
                    event.stopPropagation();
                    $(this).highlightSelectedElement(component, true);
                }
            });
        }

        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    }
    else {
        var src = Helpers.convertToUniversalUrl(component.getProperty(SRC).value);
        var title = component.getProperty(TITLE).value;

        if (document.readyState == "complete") {
            ViewerFactory.setJPlayerForComponent(component, src, title);
        } else {
            $(window).load(function () {
                ViewerFactory.setJPlayerForComponent(component, src, title);
            });
        }
    }

    ViewerFactory.addLayeringController(this, component);
}


var ContactUsComponentViewer = function (component) {
    if (UI.getSetting("ispreview")) UI.autocompleteAddress.addScript();
    var element = $(component.getUISelector());
    var captchaId = 'captcha_' + component.id;

    var useCaptcha = component.getProperty(USE_CAPTCHA).value.toBoolean();
    if (!UI.getSetting("ispreview")) {
        $('#' + captchaId).addClass('inactively');
        if (component.isSelectable) {
            element.bind('click', function (event) {
                if (ViewerFactory.editorCanBeShown(element, event)) {
                    event.stopPropagation();
                    $(this).highlightSelectedElement(component, true);
                }
            });
        }
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    } else {
        $(element.selector).on('change keydown', 'input, select, textarea', function () {
            $(this).removeClass('validation-error');
        });

        $(element.selector).find("input").attr("readonly", false);
        $(element.selector).find("textarea").attr("readonly", false);
        $(element.selector).find("textarea").css({ 'resize': 'none' });
        $(element.selector).find("select").removeAttr("disabled");

        $(element.selector).find("input[name='SBMT']").bind("click", function () {
            var validationErrors = ViewerFactory.staticFormValidation(component);
            if (!validationErrors.length) {
                sendMail(element.selector);
            } else {
                var validationMessage = _.uniq(validationErrors.map(function (item) { return item.message; })).join(', ') + '!';
                $(element.selector).find(validationErrors.map(function(item){return item.selector }).join(',')).addClass('validation-error');
                Application.showOkDialog('Error', validationMessage);
            }
        });


        function sendMail(elementSelector) {

            var type = component.getProperty(TYPE).value;
            var dataArray = new Array();
            var customerEmail = "";

            if (type == "Request a quote") {
                var TITLE = $(elementSelector).find('h3').html();

                var NA = "Name: " + $(elementSelector).find("input[name='NA']")[0].value;
                var DP = "Day Phone: " + $(elementSelector).find("input[name='DP']")[0].value;
                var EP = "Evening Phone: " + $(elementSelector).find("input[name='EP']")[0].value;
                var EM = "Email: " + $(elementSelector).find("input[name='EM']")[0].value;
                var CM = "Comments: " + $(elementSelector).find("textarea[name='CM']")[0].value;

                dataArray.push(TITLE, NA, DP, EP, EM, CM);
                customerEmail = EM;
            }
            else {
                var TITLE = $(elementSelector).find('h3').html();

                var NA = "Name: " + $(elementSelector).find("input[name='NA']")[0].value;
                var PH = "Phone: " + $(elementSelector).find("input[name='PH']")[0].value;
                var EM = "Email: " + $(elementSelector).find("input[name='EM']")[0].value;

                var BTTC = "Best Time To Call: " + $(elementSelector).find("select[name='BTTC']")[0].value;
                var IA = "Inspection Address: " + $(elementSelector).find("input[name='IA']")[0].value;
                var CSZ = "City, ST, ZIP: " + $(elementSelector).find("input[name='CSZ']")[0].value;

                var PRT = "Property Type: " + $(elementSelector).find("select[name='PRT']")[0].value;
                var SF = "Square Footage: " + $(elementSelector).find("input[name='SF']")[0].value;
                var YB = "Year Built: " + $(elementSelector).find("input[name='YB']")[0].value;

                var FT = "Foundation Type: " + $(elementSelector).find("select[name='FT']")[0].value;
                var PT = "Pool Type: " + $(elementSelector).find("select[name='PT']")[0].value;
                var CM = "Comments: " + $(elementSelector).find("textarea[name='CM']")[0].value;

                dataArray.push(TITLE, NA, PH, EM, BTTC, IA, CSZ, PRT, SF, YB, FT, PT, CM);
                customerEmail = EM;
            }
            
            ViewerFactory.sendComponentDataToMail(component, dataArray, customerEmail);
        }
    }
    var fs = component.getProperty(FONT_SIZE).value;
    $(element).css('font-size', parseInt(fs) + 'px');

    if (useCaptcha) {
        ViewerFactory.initCaptcha(component, captchaId);
    }

    ViewerFactory.addLayeringController(this, component);
}

ViewerFactory.initCaptcha = function(component, captchaId) {
    var captchaOptions = {
        'sitekey': RECAPTCHA_SITE_KEY,
        'callback': captchaCallback,
        'expired-callback': captchaCallback,
        'error-callback': captchaCallback
    };

    if (!UI.getDevice().isDesktop()) {
        captchaOptions.size = "compact";
    }

    grecaptcha.ready(function () {
        grecaptcha.render(captchaId, captchaOptions);
    });

    function captchaCallback(valid) {
        $('#' + captchaId + ' > div').removeClass("validation-error");
        component.valid = !!valid;
    }
}

var PanelComponentViewer = function (component)
{	

    var element = $(component.getUISelector());

    ViewerFactory.initComponentPosition(component, element);

    if (!UI.getSetting("ispreview")) {
        if (component.isSelectable) {
            element.bind('click',
                function(event) {
                    if (ViewerFactory.editorCanBeShown(element, event)) {
                        event.stopPropagation();
                        $(this).highlightSelectedElement(component, true);
                    }

                });
        }
        if (component.isDraggable && !component.getProperty(IS_PINED).value.toBoolean()) {
            dragDrop.initElement(component.id);
        }
    }

    component.stretcher();

    ViewerFactory.addLayeringController(this, component);
}

var FormComponentViewer = function (component) {
    var element = $(component.getUISelector());
    if (!UI.getSetting("ispreview")) {
        if (component.isSelectable) {
            element.bind('click',
                function(event) {
                    if (ViewerFactory.editorCanBeShown(element, event)) {
                        event.stopPropagation();
                        $(this).highlightSelectedElement(component, true);
                    }
                });
        }
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    } else {
        element.on('change keydown', 'input, select, textarea', function () {
                $(this).removeClass('validation-error');
            });
    }

    ViewerFactory.addLayeringController(this, component);
}

var AnchorComponentViewer = function (component) {
    var element = $(component.getUISelector());
    var name = component.getProperty("name").value;

    if (!UI.getSetting("ispreview")) {
        if (component.isSelectable) {
            element.bind('click', function (event) {
                if (ViewerFactory.editorCanBeShown(element, event)) {
                    event.stopPropagation();
                    $(this).highlightSelectedElement(component, true);
                }
            });
        }
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    }
    else {
        element.attr('id', name.substring(1));
    }
}

var FrameComponentViewer = function (component) {

    var element = $(component.getUISelector());

    if (!UI.getSetting("ispreview")) {
        element.empty();

        var src = component.getProperty(MODE_VALUE).value;
        if (src != "") {
            if (src.indexOf('http://') !== -1) {
                element.append('<svg viewBox="0 0 100 100"><text fill="#fff" font-size="20" x="50%" y="50%" dy="0.3em" text-anchor="middle">Iframe</text></svg>');
            } else {
                element.append('<div class="std-frame-overlay"></div>');
                element.append('<iframe src="' + src + '" sandbox="allow-forms allow-scripts allow-same-origin"></iframe>');
                element.css('background', 'none');
            }
        }

        if (component.isSelectable) {
            element.bind('click', function (event) {
                if (ViewerFactory.editorCanBeShown(element, event)) {
                    event.stopPropagation();
                    $(this).highlightSelectedElement(component, true);
                }
            });
        }
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    } else {
        var src = component.getProperty(MODE_VALUE).value;
        if (src != "") {
            element.css('background', 'none');
        }
        element.children().attr("src", src);
    }

    if (UI.getSetting("isThumbnailPreview")) {
        ViewerFactory.replacementImagesInsteadEveryVideoInContainer(element.selector);
    }

    ViewerFactory.addLayeringController(this, component);
}

var PdfComponentViewer = function (component) {

    var element = $(component.getUISelector());

    if (!UI.getSetting("ispreview")) {
        if (component.isSelectable) {
            element.bind('click', function (event) {
                if (ViewerFactory.editorCanBeShown(element, event)) {
                    event.stopPropagation();
                    $(this).highlightSelectedElement(component, true);
                }
            });
        }
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    } else {
        var pdfSrc = component.getProperty('mode-value').value;
        var pdfName = component.getProperty('name').value;
        element.on('click', function () {
            Helpers.showPdfInsideModal(pdfSrc, pdfName);
        });
        element.addClass('std-pdf-preview');
    }


    ViewerFactory.addLayeringController(this, component);
}

var MortgageCalculatorComponentViewer = function (component) {

    var element = $(component.getUISelector());
    if (!UI.getSetting("ispreview")) {
        if (component.isSelectable) {
            element.bind('click', function (event) {
                if (ViewerFactory.editorCanBeShown(element, event)) {
                    event.stopPropagation();
                    $(this).highlightSelectedElement(component, true);
                }
            });
        }
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    }
    else {
        $(element.selector).find("input[name='LA'], input[name='YR'], input[name='IR'], input[name='AT'], input[name='AI']").attr("readonly", false);

        $(element.selector).find("input[name='CALC']").bind(
            "click", function () {
                sum(element.selector);
            });

        $(element.selector).find("input[name='LA'], input[name='YR'], input[name='IR'], input[name='AT'], input[name='AI']").bind(
            "change", function () {
                sum(element.selector);
            });

        $(element.selector).find("input[name='LA'], input[name='YR'], input[name='IR'], input[name='AT'], input[name='AI']").bind(
            "keypress", function (event) {
                return isNumberKey(event)
        });


        function isNumberKey(evt) {
            var charCode = (evt.which) ? evt.which : evt.keyCode;
            if (charCode != 46 && charCode > 31
              && (charCode < 48 || charCode > 57))
                return false;
            return true;
        }

        function floor(number) {
            return Math.floor(number * Math.pow(10, 2)) / Math.pow(10, 2);
        }

        function sum(elementSelector) {

            var IR = $(elementSelector).find("input[name='IR']")[0];
            var YR = $(elementSelector).find("input[name='YR']")[0];
            var PI = $(elementSelector).find("input[name='PI']")[0];
            var MT = $(elementSelector).find("input[name='MT']")[0];
            var MI = $(elementSelector).find("input[name='MI']")[0];
            var MP = $(elementSelector).find("input[name='MP']")[0];
            var LA = $(elementSelector).find("input[name='LA']")[0];
            var AT = $(elementSelector).find("input[name='AT']")[0];
            var AI = $(elementSelector).find("input[name='AI']")[0];

            var mi = IR.value / 1200;
            var base = 1;
            var mbase = 1 + mi;
            for (i = 0; i < YR.value * 12; i++) {
                base = base * mbase
            }
            PI.value = floor(LA.value * mi / (1 - (1 / base)))
            MT.value = floor(AT.value / 12)
            MI.value = floor(AI.value / 12)
            var dasum = LA.value * mi / (1 - (1 / base)) +
                  AT.value / 12 +
                  AI.value / 12;
            MP.value = floor(dasum);
        }
    }
    var fs = component.getProperty(FONT_SIZE).value;
    $(element).css('font-size', parseInt(fs) + 'px');

    ViewerFactory.addLayeringController(this, component);
}

var EvaluateHomeComponentViewer = function (component) {
    if (UI.getSetting("ispreview")) UI.autocompleteAddress.addScript();
    var element = $(component.getUISelector());
    
    var captchaId = 'captcha_' + component.id;

    var useCaptcha = component.getProperty(USE_CAPTCHA).value.toBoolean();
    if (!UI.getSetting("ispreview")) {
        $('#' + captchaId).addClass('inactively');
        if (component.isSelectable) {
            element.bind('click', function (event) {
                if (ViewerFactory.editorCanBeShown(element, event)) {
                    event.stopPropagation();;
                    $(this).highlightSelectedElement(component, true);
                }
            });
        }
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    } else {
        $(element.selector).on('change keydown', 'input, select, textarea', function () {
            $(this).removeClass('validation-error');
        });

        $(element.selector).find("input").attr("readonly", false);
        $(element.selector).find("input[name='SBMT']").bind("click", function () {
            var validationErrors = ViewerFactory.staticFormValidation(component);
            if (!validationErrors.length) {
                sendMail(element.selector);
            } else {
                var validationMessage = _.uniq(validationErrors.map(function (item) { return item.message; })).join(', ') + '!';
                $(element.selector).find(validationErrors.map(function (item) { return item.selector }).join(',')).addClass('validation-error');
                Application.showOkDialog('Error', validationMessage);
            }
        });

        function sendMail(elementSelector) {

            var dataArray = new Array();

            var TITLE = $(elementSelector).find('h3').html();

            var NA = "Name: " + $(elementSelector).find("input[name='NA']")[0].value;
            var EM = "Email: " + $(elementSelector).find("input[name='EM']")[0].value;
            var PN = "Phone Number: " + $(elementSelector).find("input[name='PN']")[0].value;

            var AD = "Address: " + $(elementSelector).find("input[name='AD']")[0].value;
            var CT = "City: " + $(elementSelector).find("input[name='CT']")[0].value;
            var ST = "State: " + $(elementSelector).find("input[name='ST']")[0].value;

            var ZC = "Zip-Code: " + $(elementSelector).find("input[name='ZC']")[0].value;
            var BD = "Beds: " + $(elementSelector).find("input[name='BD']")[0].value;
            var BTH = "Baths: " + $(elementSelector).find("input[name='BTH']")[0].value;

            var SF = "Square Feet: " + $(elementSelector).find("input[name='SF']")[0].value;
            var LS = "Lot Size: " + $(elementSelector).find("input[name='LS']")[0].value;
            var YB = "Year Built: " + $(elementSelector).find("input[name='YB']")[0].value;

            dataArray.push(TITLE, NA, EM, PN, AD, CT, ST, ZC, BD, BTH, SF, LS, YB);

            ViewerFactory.sendComponentDataToMail(component, dataArray, EM);
        }
    }
    var fs = component.getProperty(FONT_SIZE).value;
    $(element).css('font-size', parseInt(fs) + 'px');

    if (useCaptcha) {
        ViewerFactory.initCaptcha(component, captchaId);
    }

    ViewerFactory.addLayeringController(this, component);
}

ViewerFactory.staticFormValidation = function(component) {
    var errors = [];
    var fields = [];
    var element = $(component.getUISelector());
    switch(component.proto.name) {
        case CONTACT_US:
            fields = ContextFactory.getContactUsFields(component);
            break;
        case EVALUATE_HOME:
            fields = ContextFactory.getEvaluateHomeFields(component);
            break;
    }
    fields.forEach(function(field) {
        if (field.required) {
            var el = $(element.selector).find(field.selector);
            if (el.length) {
                if (!el.val()) {
                    errors.push({
                        selector: field.selector,
                        message: "Please fill required fields",
                        type: 'email'
                    });
                } else if (field.selector === "input[name='EM']") {
                    if (!ViewerFactory.validateEmail(el.val())) {
                        errors.push({
                            selector: field.selector,
                            message: "Invalid email",
                            type: 'email'
                        });
                    }
                }
            }
        }
    });
    var useCaptcha = component.getProperty(USE_CAPTCHA);
    if (useCaptcha != null && useCaptcha.value.toBoolean()) {
        if (!component.valid) {
            errors.push({
                selector: '#captcha_' + component.id + ' > div',
                message: "Captcha required",
                type: 'captcha'
            });
        }
    }
    return errors;
}

var ImageComponentViewer = function (component) {
    var element = $(component.getUISelector());

    ViewerFactory.initComponentPosition(component, element);

    if (!UI.getSetting("ispreview")) {
        if (component.isSelectable) {
            element.bind('click', function (event) {
                if (ViewerFactory.editorCanBeShown(element, event)) {
                    event.stopPropagation();
                    $(this).highlightSelectedElement(component, true);
                }
            });
        }
        if (component.isDraggable && !component.getProperty(IS_PINED).value.toBoolean()) {
            dragDrop.initElement(component.id);
        }
    } else {
        $(element.selector).on('mouseenter', function () {
            var src = component.getProperty(IMAGE_ON_HOVER).value;
            if (src != "") {
                var isShowOptimizedHover = component.getProperty(SHOW_OPTIMIZED_HOVER).value.toBoolean();
                src = ContextFactory.prepareImgSrc(src, isShowOptimizedHover);
                $(this).children()[0].src = src;
            }
        });

        $(element.selector).on('mousedown', function () {
            var src = component.getProperty(IMAGE_ON_PRESSED).value;
            if (src != "") {
                var isShowOptimizedPressed = component.getProperty(SHOW_OPTIMIZED_PRESSED).value.toBoolean();
                src = ContextFactory.prepareImgSrc(src, isShowOptimizedPressed);
                $(this).children()[0].src = src;
            }
        });

        $(element.selector).on('mouseup', function () {
            var src = component.getProperty(IMAGE_ON_HOVER).value;
            if (src != "") {
                var isShowOptimizedHover = component.getProperty(SHOW_OPTIMIZED_HOVER).value.toBoolean();
                src = ContextFactory.prepareImgSrc(src, isShowOptimizedHover);
                $(this).children()[0].src = src;
            }
            else {
                var normalStateSrc = component.getProperty(SRC).value;
                var isShowOptimized = component.getProperty(SHOW_OPTIMIZED).value.toBoolean();
                normalStateSrc = ContextFactory.prepareImgSrc(normalStateSrc, isShowOptimized);
                $(this).children()[0].src = normalStateSrc;
            }
        });

        $(element.selector).on('mouseleave', function () {
            var src = component.getProperty(SRC).value;
            var isShowOptimized = component.getProperty(SHOW_OPTIMIZED).value.toBoolean();
            src = ContextFactory.prepareImgSrc(src, isShowOptimized);

            $(this).children()[0].src = src;
        });

        if (UI.getSetting("ispublished")) {
            ViewerFactory.bindEventGoToPage(component, $(element.selector));
        }
    }

    component.stretcher();

    ViewerFactory.addLayeringController(this, component);
}

var HtmlContainerComponentViewer = function (component) {
    var element = $(component.getUISelector());

    if (!UI.getSetting("ispreview")) {
        if (component.isSelectable) {
            element.bind('click', function (event) {
                if (ViewerFactory.editorCanBeShown(element, event)) {
                    event.stopPropagation();
                    $(this).highlightSelectedElement(component, true);
                }
            });
        }
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    }
    else if (!UI.getSetting("isThumbnailPreview"))  {
        element.find('iframe[src*="youtube.com"]').each(function () { 
            var el = $(this);            
            el.attr("src", Helpers.addQueryParam(el.attr("src"), "enablejsapi=1"));
        });

        element.find('iframe[src*="vimeo.com"]').each(function () {
            var el = $(this);
            el.attr("src", Helpers.addQueryParam(el.attr("src"), "api=1"));
        });
    }

    if (UI.getSetting("ispreview")) {
        element.find(".html-container-overlay").remove();
    }

    if (UI.getSetting("ispreview") || component.isPreviewEnabled) {
        var src = component.getProperty(TEXT).value;
        var iframe = element.find('.html-container-content > iframe');
        var iframedoc = iframe[0].contentDocument || iframe[0].contentWindow.document;
        iframedoc.open();
        iframedoc.write(src);
        iframedoc.close();
    }

    if (UI.getSetting("isThumbnailPreview")) {
        ViewerFactory.replacementImagesInsteadEveryVideoInContainer(element.selector);
    }

    ViewerFactory.addLayeringController(this, component);
}

ViewerFactory.getFixedLocationPosition = function (component, isWrapper) {
    var offsetX = component.getProperty(OFFSET_X).value;
    var offsetY = component.getProperty(OFFSET_Y).value;
    var fixedLocation = component.getProperty(FIXED_LOCATION).value;
    var top = '';
    var deviceWidth = parseInt(UI.getDevice().getWidth());
    var advHeight = 0;
    var isStretched = StretcherFactory.getCurrentStretchStatus(component);

    if (!UI.getSetting("ispaid")) {
        try {
            var template = dealerAdvTemplate;
            advHeight = parseInt((deviceWidth * 90) / 960);
        }
        catch (ex) {
            console.log('no adv...' + ex);
        }
    }    
    var bodyOffset = $('.body').offset().top;
    var css = {};
    switch (fixedLocation) {
        case "center-top":
            if (!UI.getSetting("ispreview")) {
                top = (parseInt(offsetY) + bodyOffset) + 'px';
            } else {
                top = (parseInt(offsetY) + advHeight) + 'px';
            }
            css = {
                'top': top,
                'left': (parseInt(offsetX) * 2) + 'px',
                'margin': 'auto',
                'right': "0px"
            };
            if (isWrapper) {
                css.top = parseInt(css.top) - 1 + 'px';                
            }
            break;

        case "left-top":
            if (!UI.getSetting("ispreview")) {
                top = (parseInt(offsetY) + bodyOffset) + 'px';
            } else {
                top = (parseInt(offsetY) + advHeight) + 'px';
            }
            css = {
                'top': top,
                'left': parseInt(offsetX) + 'px'
            };
            if (isWrapper) {
                css.top = parseInt(css.top) - 1 + 'px';
                css.left = parseInt(css.left) - 1 + 'px';
            }
            break;

        case "right-top":
            if (!UI.getSetting("ispreview")) {
                top = (parseInt(offsetY) + bodyOffset) + 'px';
            } else {
                top = (parseInt(offsetY) + advHeight) + 'px';
            }
            css = {
                'top': top,
                'right': parseInt(offsetX) + 'px',
                'left': 'initial'
            };
            if (isWrapper) {
                css.top = parseInt(css.top) - 1 + 'px';
                css.right = parseInt(css.right) - 1 + 'px';
            }
            break;

        case "left-center":
            css = {
                'top': (parseInt(offsetY) * 2) + 'px',
                'left': parseInt(offsetX) + 'px',
                'margin': 'auto',
                'bottom': '0px'
            };
            if (isWrapper) {                
                css.left = parseInt(css.left) - 1 + 'px';
            }
            break;

        case "left-bottom":
            css = {
                'bottom': parseInt(offsetY) + 'px',
                'left': parseInt(offsetX) + 'px',
                'top': 'initial'
            };
            if (isWrapper) {
                css.bottom = parseInt(css.bottom) - 1 + 'px';
                css.left = parseInt(css.left) - 1 + 'px';
            }
            break;

        case "center-bottom":
            css = {
                'bottom': parseInt(offsetY) + 'px',
                'left': (parseInt(offsetX) * 2) + 'px',
                'top': 'initial',
                'margin': 'auto',
                'right': "0px"
            };
            if (isWrapper) {
                css.bottom = parseInt(css.bottom) - 1 + 'px';
                css.left = parseInt(css.left) - 1 + 'px';
            }
            break;

        case "right-bottom":
            css = {
                'bottom': parseInt(offsetY) + 'px',
                'left': 'initial',
                'top': 'initial',
                'right': parseInt(offsetX) + 'px'
            };
            if (isWrapper) {
                css.bottom = parseInt(css.bottom) - 1 + 'px';
                css.right = parseInt(css.right) - 1 + 'px';
            }
            break;

        case "right-center":
            css = {
                'top': (parseInt(offsetY) * 2) + 'px',
                'right': (parseInt(offsetX) + 10) + 'px',
                'left': 'initial',
                'bottom': '0px',
                'margin': 'auto'
            };
            if (isWrapper) {                
                css.right = parseInt(css.right) - 1 + 'px';
            }
            break;

        default:
            break;
    }

    //remove conflict styles if it is stretched
    if (isStretched) {
        delete css.right;
        delete css.left;
    }

    return css;
}

ViewerFactory.initComponentPosition = function (component, element) {    
    var isPined = component.getProperty(IS_PINED).value.toBoolean();
    var fixedLocation = component.getProperty(FIXED_LOCATION).value;

    if (isPined) {
        if (fixedLocation) {
            var el = $(component.getUISelector());
            var zIndex = parseInt(el.css('z-index'));            
            if (!zIndex || zIndex < 4) {
                el.css('z-index', 4);
            }
            $(el.detach()).appendTo(UI.getBody().getUISelector());            
        }
        
        $(element).css(ViewerFactory.getFixedLocationPosition(component));
    }
};



ViewerFactory.calculateButtonLineHeight = function (elementselector) {
    var element = $(elementselector);
    var elementBorder = parseInt(element.css('border-top-width'));
    var elementHeight = parseInt(element.css('height'));
    var elementLine = elementHeight - elementBorder * 2;
    element.css('line-height', elementLine + 'px');
};

var BloggingComponentViewer = function(component) {
    var element = $(component.getUISelector());
    if (!UI.getSetting("ispreview")) {
        if (component.isSelectable) {
            element.bind('click', function (event) {
                if (ViewerFactory.editorCanBeShown(element, event)) {
                    event.stopPropagation();
                    $(this).highlightSelectedElement(component, true);
                }
            });
        }
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    } else {
        if (UI.getSetting("ispublished")) {
            $(element.selector).on('click', '.std-blogging-page a', Helpers.generateEventGoToPage);
        }
    }
    
    ViewerFactory.addLayeringController(this, component);
}

var ButtonComponentViewer = function (component) {

    var element = $(component.getUISelector());

    ViewerFactory.initComponentPosition(component, element);

    $(element).css({ "text-overflow": "ellipsis", "overflow": "hidden" });
    $(element).hover(function() {
            if (component.getProperty(BACKGROUND_COLOR_HOVER) != null) {
                $(this).css('background-color', component.getProperty(BACKGROUND_COLOR_HOVER).value);
            }
            if (component.getProperty(BORDER_COLOR_HOVER) != null) {
                $(this).css('border-color', component.getProperty(BORDER_COLOR_HOVER).value);
            }
            if (component.getProperty(TEXT_COLOR_HOVER) != null) {
                $(this).css('color', component.getProperty(TEXT_COLOR_HOVER).value);
            }
        },
        function() {
            $(this).css('background-color', component.getProperty(BACKGROUND_COLOR).value);
            $(this).css('border-color', component.getProperty(BORDER_COLOR).value);
            $(this).css('color', component.getProperty(COLOR).value);
        });

    if (!UI.getSetting("ispreview") || UI.settings.isComponentEditor) {
        if (component.isSelectable) {
            element.bind('click', function (event) {
                if (ViewerFactory.editorCanBeShown(element, event)) {
                    event.stopPropagation();
                    $(this).highlightSelectedElement(component, true);
                }
            });
        }
        if (component.isDraggable && !component.getProperty(IS_PINED).value.toBoolean()) {
            dragDrop.initElement(component.id);
        }
    } else {
        if (UI.getSetting("ispublished")) {
            ViewerFactory.bindEventGoToPage(component, element);
        }
    }

    ViewerFactory.calculateButtonLineHeight(component.getUISelector());

    ViewerFactory.addLayeringController(this, component);
}

var MapComponentViewer = function(component) {
    var element = $(component.getUISelector());

    var lat = component.getProperty(LATITUDE).value;
    var lng = component.getProperty(LONGITUDE).value;
    var address = component.getProperty(TEXT).value;
    var title = component.getProperty(TITLE).value;

    var showMapType = component.getProperty(SHOW_MAP_TYPE).value;
    var showZoom = component.getProperty(SHOW_ZOOM).value;
    var mapInteractive = component.getProperty(MAP_INTERACTIVE).value;
    var showStreetView = component.getProperty(SHOW_STREET_VIEW).value;

    var src = '/Editor/GoogleMap?lat=' + lat + '&lng=' + lng + '&address=' + address + '&title=' + title + '&showMapType=' + showMapType + '&showZoom=' + showZoom + '&mapInteractive=' + mapInteractive + '&showStreetView=' + showStreetView;

    element.empty();
    element.append('<iframe class="map" src="' + src + '" frameborder="0" scrolling="no" marginheight="0" marginwidth="0"></iframe>');

    if (!UI.getSetting("ispreview")) {
        element.find('.map').addClass('not-active');

        if (component.isSelectable) {
            element.bind('click', function (event) {
                if (ViewerFactory.editorCanBeShown(element, event)) {
                    event.stopPropagation();
                    $(this).highlightSelectedElement(component, true);
                }
            });
        }
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    }

    ViewerFactory.addLayeringController(this, component);
}

/* sign-in */
var SigninComponentViewer = function (component) {
    var element = $(component.getUISelector());
    $(element).css({ "text-overflow": "ellipsis", "overflow": "hidden" });
    $(element).hover(function () {
        if (component.getProperty(BACKGROUND_COLOR_HOVER) != null) {
            $(this).css('background-color', component.getProperty(BACKGROUND_COLOR_HOVER).value);
        }
        if (component.getProperty(BORDER_COLOR_HOVER) != null) {
            $(this).css('border-color', component.getProperty(BORDER_COLOR_HOVER).value);
        }
        if (component.getProperty(TEXT_COLOR_HOVER) != null) {
            $(this).css('color', component.getProperty(TEXT_COLOR_HOVER).value);
        }
    }, function () {
        $(this).css('background-color', component.getProperty(BACKGROUND_COLOR).value);
        $(this).css('border-color', component.getProperty(BORDER_COLOR).value);
        $(this).css('color', component.getProperty(COLOR).value);
    });

    if (!UI.getSetting("ispreview")) {
        if (component.isSelectable) {
            element.bind('click',
                function(event) {
                    if (ViewerFactory.editorCanBeShown(element, event)) {
                        event.stopPropagation();
                        $(this).highlightSelectedElement(component, true);
                    }
                });
        }
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    } else {
        element.bind('click', function (e) {
            UI.componentService.addModalContentToForm(component, '#signin-login');
        });
    }

    ViewerFactory.calculateButtonLineHeight(component.getUISelector());

    ViewerFactory.addLayeringController(this, component);
}

var ParagraphComponentViewer = function (component) {
    var element = $(component.getUISelector());
    component.isckeditorworking = false;

    if (!UI.getSetting("ispreview")) {
        element.bind("dblclick",
            function () {
                var isHide = component.getProperty(HIDE_COMPONENT);

                if (UI.isckeditorworking != true && (isHide == null || !isHide.value.toBoolean())) {
                    component.isckeditorworking = true;
                    $(this).attr('contenteditable', 'true');
                    UI.isckeditorworking = true;
                    UI.callEditor(component);
                }
            });

        if (component.isSelectable) {
            element.bind('click', function (event) {
                if (ViewerFactory.editorCanBeShown(element, event)) {
                    event.stopPropagation();
                    $(this).highlightSelectedElement(component, !component.isckeditorworking);
                }
            });
        }
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    }
    
    ViewerFactory.addLayeringController(this, component);
}

var HeadertextComponentViewer = function (component) {
    var element = $(component.getUISelector());
    component.isckeditorworking = false;

    if (!UI.getSetting("ispreview")) {
        element.bind("dblclick",
            function (event) {
                var isHide = component.getProperty(HIDE_COMPONENT);

                if (UI.isckeditorworking != true && (isHide == null || !isHide.value.toBoolean())) {
                    component.isckeditorworking = true;
                    $(this).attr('contenteditable', 'true');
                    UI.isckeditorworking = true;
                    UI.callEditor(component);
                }
            });

        if (component.isSelectable) {
            element.bind('click', function (event) {
                if (ViewerFactory.editorCanBeShown(element, event)) {
                    event.stopPropagation();
                    $(this).highlightSelectedElement(component, !component.isckeditorworking);
                }
            });
        }
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    }
    

    ViewerFactory.addLayeringController(this, component);
}

ViewerFactory.calculateMenuLineHeight = function (component, selector)
{
    var predefined = component.getProperty(PREDEFINED).value;
    if (predefined == "simple") {
        var height = parseInt($(selector).css('height'));
        var borderWidth = parseInt($(selector).css('border-top-width'));
        $(selector).css('line-height', (height - borderWidth * 2) + 'px');
    }
}


ViewerFactory.calculateMarginForGallery = function (component, selector, onlySet) {
    onlySet = onlySet || false;
    var type = component.getProperty(TYPE).value;
    if (type != "with-captions") {
        return false;    
    }
    var columns = component.getProperty(COLUMNS).value;
    var galleryItems = $(selector).find('.gallery-item');

    var sizePx = component.getProperty(FONT_SIZE).value;
    var size = parseInt(sizePx);

    var row = 0;
    var margin = 0;
    var index;
    for (index = 0; index < galleryItems.length; index++) {
        margin = ViewerFactory.calcRowMargin(component, selector, parseInt(index / columns), onlySet)
        $(galleryItems[index]).css('margin-bottom', margin + 'px');
        if (!onlySet && component.children.length > 0) {
            component.children[index].setProperty('gallery-item-bottom-margin', margin + 'px');
        }
    } 
    
    return false;
}

ViewerFactory.calcRowMargin = function (component, selector, rowNumber, onlySet) {
    onlySet = onlySet || false;

    var infoList = $(selector).find('.info');
    var columns = component.getProperty(COLUMNS).value;

    var indexFrom = rowNumber * columns;
    var indexTo = (rowNumber + 1) * columns - 1;
    var index;
    var maxHeight = 0;

    for (index = indexFrom; index <= indexTo; index++) {

        if (onlySet)
        {
            var margin = 10;
            if (component.children.length > 0) {
                margin = component.children[index].getProperty(GALLERY_ITEM_BOTTOM_MARGIN).value;
            }
            return parseInt(margin);
        }

        var heightInfo = $(infoList[index]).outerHeight(true);
        var height = heightInfo;
        if (height > maxHeight) {
            maxHeight = height;
        }
    }
    return maxHeight + 10;
}

ViewerFactory.calculateHeightForGallery = function (component, selector, onlySet) {
    onlySet = onlySet || false;
    if (component.proto.name == GALLERY) {
        var type = component.getProperty(TYPE).value;
        var height = parseInt(component.getProperty(HEIGHT).value);
        var columns = component.getProperty(COLUMNS).value;

        var rows = 3;
        if (component.children.length > 0) {
            rows = Math.ceil(component.children.length / columns);
        }
        var margin = 10;
        var newHeight = (height - (rows * margin - margin)) / rows;
        if (newHeight < 10) {
            newHeight = 10;
        }
        $(selector).find('.gallery-item').css('height', newHeight + 'px');
        
        function calculateGalleryMinHeight() {

            var index = 0;
            var galleryHeight = 0;
            var items = $(selector).find('.gallery-item');
            for (index = 0; index < rows; index++) {
                var item = items[index * columns];
                if (item != undefined) {
                    var itemMargin = parseInt($(item).css('margin-bottom'));
                    var itemHeight = parseInt($(item).css('height'));
                    galleryHeight += itemMargin + itemHeight;
                }
            }
            component.setProperty('height', galleryHeight + 'px');
            $(selector).css('height', galleryHeight + 'px');
            var pageElem = $(selector).parent('.page');
            Resizer.recalculateHeaderFooterAndPageSize(pageElem);
        }

        if (type == "with-captions") {
            var marginSum = 0;
            var index;
            for (index = 0; index < rows; index++) {
                var rowMargin = ViewerFactory.calcRowMargin(component, selector, index, onlySet);
                marginSum += rowMargin;
            }

            var newHeight = (height - marginSum) / rows;
            if (newHeight < 10) {
                newHeight = 10;
                $(selector).find('.gallery-item').css('height', newHeight + 'px');
                calculateGalleryMinHeight();
            }
            else {
                $(selector).find('.gallery-item').css('height', newHeight + 'px');
            }
        }//end if with-caption
    }
    return false;
}

ViewerFactory.replacementImagesInsteadEveryVideoInContainer = function (elementselector) {
    $($(elementselector).find('iframe[src*="youtube.com"], iframe[src*="vimeo.com"]')).each(function (index, item) {
        ViewerFactory.replacementImageInsteadVideo('#' + $(item).parent()[0].id, item.src, item, index);
    });
};
ViewerFactory.replacementImageInsteadVideo = function (elementselector, src, item, index) {

    var el = $(elementselector);
    var index = index || 0;

    if (item === undefined) {
        var image = "<img " + "data-index=\"" + index + "\" class=\"video-thumbnail\" />";
        el.empty();
        el.append(image);
    }
    else {
        var image = "<img " + "data-index=\"" + index + "\" class=\"video-thumbnail\" style=\"width:" + item.width +  "px; height:" + item.height + "px;\" />";
        $(item).before(image);
        $(item).remove();
    }

    url = (src.lastIndexOf('/') === src.length - 1) ? src.substring(0, src.length - 1) : src;
    
    if (src != "") {
        if (/vimeo/.test(url)) {
            var lstIndex = url.lastIndexOf('/');
            if (lstIndex != -1 && lstIndex < url.length - 1) {
                var id = url.substring(lstIndex + 1);
                $.ajax({
                    type: 'GET',
                    url: 'https://vimeo.com/api/v2/video/' + id + '.json',
                    jsonp: 'callback',
                    dataType: 'jsonp',
                    success: function (data) {
                        var thumbnail_src = data[0].thumbnail_large;
                        el.find('img[data-index="' + index + '"]').attr('src', thumbnail_src);
                    }
                });
            }
        }
        else if (/youtu/.test(url)) {
            var lstIndex = url.lastIndexOf('/');
            if (lstIndex != -1 && lstIndex < url.length - 1) {
                var id = url.substring(lstIndex + 1);
                el.find('img[data-index="' + index +'"]').attr('src', '//img.youtube.com/vi/' + id + '/sddefault.jpg');
            }
        }
    } else {
        $(el).html("<span class='video-src'> Video source is not specified </span> ");
        $(el).addClass("video-src-parrent");
    }
}

var GalleryComponentViewer = function (component) {
    var element = $(component.getUISelector());

    function fancybox(element) {
        if (!UI.getSetting("isrenderpublish"))
        {
            $(element.selector).find(".fancybox").fancybox();
        }        
    }

    if (component.children.length == 0) {
        var index;
        for (index = 0; index < 9; index += 1) {
            $(element.selector).find('.gallery-items-list').append("<div class=\"gallery-item c3\"><div class=\"holder\"><a class=\"fancybox\" rel=\"gallery\" href=\"/Images/gallery_predefined.png\"><img src=\"/Images/gallery_predefined.png\"></a></div></div>");
        }
    }

    $(element.selector).find('.title').css('color', component.getProperty(IMAGE_SETTINGS_HEADERCOLOR).value);
    $(element.selector).find('.descr').css('color', component.getProperty(IMAGE_SETTINGS_COLOR).value);
    $(element.selector).find('.info').css('background-color', component.getProperty(IMAGE_SETTINGS_BGCOLOR).value);
    
    $(element.selector).css('font-size', parseInt(component.getProperty(FONT_SIZE).value) + 'px');

    if (!UI.getSetting("ispreview")) {        
        if (component.isSelectable) {
            element.bind('click', function (event) {
                if (ViewerFactory.editorCanBeShown(element, event)) {
                    event.stopPropagation();
                    $(this).highlightSelectedElement(component, true);
                }
            });
        }
        if (component.isDraggable) {
        	dragDrop.initElement(component.id);
        }        
    } else {
        if (UI.getSetting("ispublished")) {
            _.forEach(component.children, function (child) {
                ViewerFactory.bindEventGoToPage(child, $(child.getUISelector() + '_view').find('a'));
            });
        }
        fancybox(element);
    }
		
	var stretchingVal = component.getProperty(IMAGE_STRETCHING).value;
	var stretchingController = new StretchingController(['fill', 'crop'], stretchingVal, component);
	ko.observable($(element).find('.gallery-items-list')[0]).extend({ applyBindings: stretchingController });
    //here we get updates to dom automatically when editor changes component...
	
    ViewerFactory.calculateMarginForGallery(component, element.selector, true);
    ViewerFactory.calculateHeightForGallery(component, element.selector, true);

    component.stretcher();

	ViewerFactory.addLayeringController(this, component);
}

var SlideshowComponentViewer = function (component) {

    var element = $(component.getUISelector());

    function preview(element) {
        if (!UI.getSetting("isrenderpublish")) {
            $(element.selector).find(".fancybox").fancybox();
        }
    }

    ViewerFactory.tryUpdatePredefinedView(component);        

    $(element.selector).find('.title').css('color', component.getProperty(IMAGE_SETTINGS_HEADERCOLOR).value);
    $(element.selector).find('.descr').css('color', component.getProperty(IMAGE_SETTINGS_COLOR).value);
    if (component.getProperty(CAPTION).value.toBoolean()) {
        $(element.selector).find('.carousel-caption').css('background-color', component.getProperty(IMAGE_SETTINGS_BGCOLOR).value);
    }

    if (!UI.getSetting("ispreview")) {        
        if (component.isSelectable) {
            element.bind('click', function (event) {
                if (ViewerFactory.editorCanBeShown(element, event)) {
                    event.stopPropagation();
                    $(this).highlightSelectedElement(component, true);
                }
            });
        }
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    }
    else {
        preview(element);
        if (UI.getSetting("ispublished")) {
            _.forEach(component.children, function (child) {
                ViewerFactory.bindEventGoToPage(child, $(child.getUISelector() + '_view').find('a'));
            });
        }
    }

    var stretchingVal = component.getProperty(IMAGE_STRETCHING).value;
    var stretchingController = new StretchingController(['fill', 'crop'], stretchingVal, component);
    ko.observable($(element).find('.carousel-inner')[0]).extend({ applyBindings: stretchingController });

    component.stretcher();

    ViewerFactory.addLayeringController(this, component);
}

ViewerFactory.housePhotoTourPostRender = function (component) {
    var element = $(component.getUISelector());
    var height = component.getProperty(HEIGHT).value;

    //width = "960px"; //must always be 960px

    if (parseInt(height) < 480) {
        height = "480px";
    }

    var width = element[0].offsetWidth;
    var thumbnailElement = element.find('div[u="thumbnavigator"]')[0];
    var widthThumbnails = thumbnailElement != null ? thumbnailElement.offsetWidth : 240;
    var widthSlides = width - widthThumbnails;

    if (component.children.length == 0) {
        element.children().css('display', 'none');
        element.children('div.std-house-photo-tour-predefined').css('display', 'block');
    }
    else
    {
        element.children('div.std-house-photo-tour-predefined').css('display', 'none');
        var options = {
            $AutoPlay: false,
            $ArrowNavigatorOptions: {
                $Class: $JssorArrowNavigator$,
                $ChanceToShow: 1,
                $AutoCenter: 2,
                $Steps: 1
            },
            $SlideWidth: widthSlides,
            $ThumbnailNavigatorOptions: {
                $Class: $JssorThumbnailNavigator$,
                $ChanceToShow: 2,
                $ActionMode: 1,
                $Lanes: 2,
                $SpacingX: 14,
                $SpacingY: 12,
                $DisplayPieces: 6,
                $ParkingPosition: 0,
                $Orientation: 2
            }
        };
        //element.css('width', '960px');


        function ScaleSlider(width) {
            house_photo_tour.$ScaleWidth(width);

        }

        if (!UI.getSetting("isrenderpublish")) {
            var house_photo_tour = new $JssorSlider$(element[0].id, options);
            house_photo_tour.$ScaleWidth(width);
            element.css('height', height);
            ViewerFactory.housePhotoTourSetHeight(element.selector);

            element.find('.std-house-photo-tour').css('background-color', 'inherit');
            element.find('.std-house-photo-tour').css('font-size', 'inherit');

            element.find('.std-house-photo-tour').find('div[u="slides"]').slice(0, 2).css('width', widthSlides + 'px');
            element.children().css('transform', ' scale(1)');
            element.find('div[u="thumbnavigator"]').children().css('transform', 'scale(1)');
            element.find('.std-house-photo-tour').css('margin', '0');
        }
        else {
            element.css('height', height);
        }



    }
    $(element.selector).find('.title').css('color', component.getProperty(IMAGE_SETTINGS_HEADERCOLOR).value);
    $(element.selector).find('.hpt_descr').css('color', component.getProperty(IMAGE_SETTINGS_COLOR).value);

    $(element.selector).find('.house_photo_tour_div').css('background-color', component.getProperty(PHOTO_TOUR_TEXT_COLOR).value);
    $(element.selector).find('.house_photo_tour_editor_div').css('background-color', component.getProperty(PHOTO_TOUR_TEXT_COLOR).value);

    if (component.getProperty('location').value == "left") {
        $(element.selector).find('.house_photo_tour_div').css('right', "480px");
        $(element.selector).find('.house_photo_tour_editor_div').css('right', "480px");
    } else {
        $(element.selector).find('.house_photo_tour_div').css('right', "0px");
        $(element.selector).find('.house_photo_tour_editor_div').css('right', "0px");
    }

    if (component.getProperty(IMAGE_STRETCHING) != null) {
        var stretchClass = component.getProperty(IMAGE_STRETCHING).value;
        if (stretchClass == "crop") {
            var images = element.find('.house_photo_tour_img');
            images.removeClass('house_photo_tour_img');
            images.addClass('house_photo_tour_img_crop');
            element.find('.house_photo_tour_predefined_img').addClass('house_photo_tour_img_crop');
        }
    }
}

ViewerFactory.housePhotoTourRerender = function (component) {
    var template = TemplateFactory.templateFor(component, VIEWER_TEMPLATE).compiledTemplate;
    $(component.getUISelector()).html($(template).html());
    ViewerFactory.housePhotoTourPostRender(component);
}

ViewerFactory.housePhotoTourSetHeight = function (elementSelector) {
    var element = $(elementSelector);

    var scaledChild = $(elementSelector).children();
    var matrixRegex = /matrix\((-?\d*\.?\d+),\s*0,\s*0,\s*(-?\d*\.?\d+),\s*0,\s*0\)/;
    var scaleValue = scaledChild.css('transform').match(matrixRegex);
    scaleValue = parseFloat(scaleValue ? (scaleValue[1] ? scaleValue[1] : 1) : 1);
    scaledChild.css('height', 1 / scaleValue * element[0].offsetHeight);

    var contentChild = scaledChild.children();
    contentChild.css('height', '100%');

    var slides1 = contentChild.children(":not(span)");

    slides1.css('height', '100%');
    slides1.children().css('height', '100%');

    contentChild.children('span.jssora05l').css('top', element[0].offsetHeight / 2 / scaleValue - 20);
    contentChild.children('span.jssora05r').css('top', element[0].offsetHeight / 2 / scaleValue - 20);

    var thumbnavigator = contentChild.children('div.jssort02');
    thumbnavigator.css('height', '100%');
    thumbnavigator.children().css('transform', 'scale(1,' + 1 / scaleValue + ')');
}

var HousePhotoTourComponentViewer = function (component) {

    var element = $(component.getUISelector());

    var stretchingVal = component.getProperty(IMAGE_STRETCHING).value;
    
    var stretchingController = new StretchingController(['fill', 'crop'], stretchingVal, component);
    if (!UI.getSetting("ispreview")) {        
        if (component.isSelectable) {
            element.bind('click', function (event) {
                if (ViewerFactory.editorCanBeShown(element, event)) {
                    event.stopPropagation();
                    $(this).highlightSelectedElement(component, true);
                }
            });
        }
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
        ko.observable($(element).find('.std-house-photo-tour-predefined-items')[0]).extend({ applyBindings: stretchingController });//predefined
    }
    else {
        element.children('div.std-house-photo-tour-overlay').css('display', 'none');
        ko.observable($(element).find('div[u="slides"]')[1]).extend({ applyBindings: stretchingController });//item-list
        if (UI.getSetting("ispublished")) {
            _.forEach(component.children, function (child) {
                ViewerFactory.bindEventGoToPage(child, $(child.getUISelector() + '_view').find('a'));
            });
        }
    }

    component.stretcher();

    ViewerFactory.housePhotoTourPostRender(component);

    ViewerFactory.addLayeringController(this, component);
}

ViewerFactory.calculateHeightForList = function (component, selector) {
    var height = 0;
    var element = $(selector);
    var listItems = element.find('.item');
    var index;
    for (index = 0; index < listItems.length; index++) {
        height += parseInt($(listItems[index]).css('height'));
        if (index != listItems.length - 1) {
            height += parseInt($(listItems[index]).css('margin-bottom'));
        }
    }
    if (height < 25) {
        height = 300;
    }
    component.setProperty('height', height + 'px');
    $(selector).css('height', height + 'px');
    var pageElem = $(selector).parent('.page');
    Resizer.recalculateHeaderFooterAndPageSize(pageElem);
}

var ListComponentViewer = function (component) {
    var element = $(component.getUISelector());

    var stretchingVal = component.getProperty(IMAGE_STRETCHING).value;
    var stretchingController = new StretchingController(['fill', 'crop'], stretchingVal, component);
    ko.observable($(element).find('.list-stretch-binding-node')[0]).extend({ applyBindings: stretchingController });

    $(element).find('.title').css('color', component.getProperty(IMAGE_SETTINGS_HEADERCOLOR).value);
    $(element).find('.description').css('color', component.getProperty(IMAGE_SETTINGS_COLOR).value);
    $(element).find('.content').css('background-color', component.getProperty(IMAGE_SETTINGS_BGCOLOR).value);

    ViewerFactory.correctShowListComponent(component);
        
    $(element).find('.title, .description, .meta, .optional').css('font-size', component.getProperty(FONT_SIZE).value);
    ViewerFactory.calculateHeightForList(component, element);

    if (!UI.getSetting("ispreview")) {
        if (component.isSelectable) {
            element.bind('click', function (event) {
                if (ViewerFactory.editorCanBeShown(element, event)) {
                    event.stopPropagation();
                    $(this).highlightSelectedElement(component, true);
                }
            });
        }
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    } else {
        if (UI.getSetting("ispublished")) {
            _.forEach(component.children, function (child) {
                ViewerFactory.bindEventGoToPage(child, $(child.getUISelector() + '_view').find('a'));
            });
        }
    }

    if (UI.getSetting("ispreview") && !UI.getSetting("isrenderpublish")) {
        $(element.selector).find(".fancybox").fancybox();
    }
    ViewerFactory.addLayeringController(this, component);
    
}

ViewerFactory.editorCanBeShown = function (element, event) {
    element = element || null;

    if (element == null)
        return false;

    var id = element[0].id;

    //for fixed elements in group
    if (StretcherFactory.isPined(Grouping.getComponent(id))) {
        grouping.dropEditableGroupId();
        return true;
    }

    var hasAnyGroup = Grouping.hasAnyGroup(id);
    var isSelectedInGroup = Grouping.isSelected(id);
    var elementGroupId = Grouping.getGroupId(id)

    if (grouping.editableGroupId != null && grouping.editableGroupId === elementGroupId) {
        return true;
    } else {
        grouping.dropEditableGroupId();
    }

    if (dragDrop.groupDragging && isSelectedInGroup) {
        return false;
    }
        

    if (grouping.ctrlIsPressed == true || hasAnyGroup) {
        return false;
    }

    Grouping.dropItems();
    return true;
}

/*Form Components*/

var FormSubComponentViewer = function (component) {
    var element = $(component.getUISelector());
    if (!UI.getSetting("ispreview")) {
        if (component.isSelectable) {
            element.bind('click',
                function(event) {
                    if (ViewerFactory.editorCanBeShown(element, event)) {
                        event.stopPropagation();
                        $(this).highlightSelectedElement(component, true);
                    }
                });
        }
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    }

    ViewerFactory.addLayeringController(this, component);
}

var AutocompleteAddressComponentViewer = function (component) {
    new FormSubComponentViewer(component);
    if (UI.getSetting("ispreview")) UI.autocompleteAddress.addScript();
}

var RadiolistComponentViewer = function (component) {
    var element = $(component.getUISelector());
    if (!UI.getSetting("ispreview")) {
        if (component.isSelectable) {
            element.bind('click',
                function(event) {
                    if (ViewerFactory.editorCanBeShown(element, event)) {
                        event.stopPropagation();
                        $(this).highlightSelectedElement(component, true);
                    }
                });
        }
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    } else {
        if (element.find('input').length > 0) {
            element.find('input')[0].checked = true;
        }
    }

    ViewerFactory.addLayeringController(this, component);
}

var FormSubmitComponentViewer = function (component) {
    var element = $(component.getUISelector());
    if (!UI.getSetting("ispreview")) {
        if (component.isSelectable) {
            element.bind('click',
                function(event) {
                    if (ViewerFactory.editorCanBeShown(element, event)) {
                        event.stopPropagation();
                        $(this).highlightSelectedElement(component, true);
                    }
                });
        }
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    }
    else {
        element.bind('click', function (event) {
            var validationErrors = ViewerFactory.customFormValidation(component);
            if (!validationErrors.length) {
                var dataArray = ViewerFactory.getFormSubmitData(component);
                ViewerFactory.sendComponentDataToMail(component,
                    dataArray,
                    ViewerFactory.getFormCustomerEmail(component), null, component.parentComponent.id, function () { ViewerFactory.clearFormData(component); });
            } else {
                var validationMessage = _.uniq(validationErrors.map(function (item) { return item.message; })).join(', ') + '!';
                validationErrors.forEach(function(item) {
                    $('#' + item.id).addClass('validation-error');
                });
                Application.showOkDialog('Error', validationMessage);
            }
        });
    }

    $(element).css({ "text-overflow": "ellipsis", "overflow": "hidden" });
    ViewerFactory.calculateButtonLineHeight(component.getUISelector());

    $(element)
        .hover(function() {
                if (component.getProperty(BACKGROUND_COLOR_HOVER) != null) {
                    $(this).css('background-color', component.getProperty(BACKGROUND_COLOR_HOVER).value);
                }
                if (component.getProperty(BORDER_COLOR_HOVER) != null) {
                    $(this).css('border-color', component.getProperty(BORDER_COLOR_HOVER).value);
                }
                if (component.getProperty(TEXT_COLOR_HOVER) != null) {
                    $(this).css('color', component.getProperty(TEXT_COLOR_HOVER).value);
                }
            },
            function() {
                $(this).css('background-color', component.getProperty(BACKGROUND_COLOR).value);
                $(this).css('border-color', component.getProperty(BORDER_COLOR).value);
                $(this).css('color', component.getProperty(COLOR).value);
            });

    ViewerFactory.addLayeringController(this, component);
}

var FormCancelComponentViewer = function (component) {
    var element = $(component.getUISelector());
    if (!UI.getSetting("ispreview")) {
        if (component.isSelectable) {
            element.bind('click',
                function (event) {
                    if (ViewerFactory.editorCanBeShown(element, event)) {
                        event.stopPropagation();
                        $(this).highlightSelectedElement(component, true);
                    }
                });
        }
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    } else {
        element.bind('click', function (event) {
            ViewerFactory.clearFormData(component);
        });
    }
    $(element)
       .hover(function () {
           if (component.getProperty(BACKGROUND_COLOR_HOVER) != null) {
               $(this).css('background-color', component.getProperty(BACKGROUND_COLOR_HOVER).value);
           }
           if (component.getProperty(BORDER_COLOR_HOVER) != null) {
               $(this).css('border-color', component.getProperty(BORDER_COLOR_HOVER).value);
           }
           if (component.getProperty(TEXT_COLOR_HOVER) != null) {
               $(this).css('color', component.getProperty(TEXT_COLOR_HOVER).value);
           }
       },
           function () {
               $(this).css('background-color', component.getProperty(BACKGROUND_COLOR).value);
               $(this).css('border-color', component.getProperty(BORDER_COLOR).value);
               $(this).css('color', component.getProperty(COLOR).value);
           });
    $(element).css({ "text-overflow": "ellipsis", "overflow": "hidden" });
    ViewerFactory.calculateButtonLineHeight(component.getUISelector());

    ViewerFactory.addLayeringController(this, component);
}

var FormAttachmentComponentViewer = function (component) {
    var element = $(component.getUISelector());
    if (!UI.getSetting("ispreview")) {
        if (component.isSelectable) {
            element.bind('click',
                function (event) {
                    if (ViewerFactory.editorCanBeShown(element, event)) {
                        event.stopPropagation();
                        $(this).highlightSelectedElement(component, true);
                    }
                });
        }
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    }
    else {
        //todo: init uploader
        var prefix = "Attachment";
        $(component.getUISelector()).find('input').on('change', function (e) {
            if (e && e.target && e.target.files[0]) {
                var fd = new FormData;
                fd.append('Filedata', e.target.files[0]);
                $.ajax({
                    url: UI.getSetting("adminSiteDomain") + "/Editor/Upload" + prefix + "?templateId=" + UI.getTemplateProperty("templateId"),
                    data: fd,
                    processData: false,
                    contentType: false,
                    type: 'POST',
                    success: function (response) {                        
                        var wrapper = JSON.parse(response);
                        var errorMessage = Helpers.GetUploadingSingleFileError(wrapper);
                        if (errorMessage == "") {
                            var file = wrapper.files[0];
                            if (file != null) {
                                $(component.getUISelector()).find('span').text(file.nativeName);
                                $(component.getUISelector()).data('url', file.url);
                                $(component.getUISelector()).find('i').show();
                            }
                        } else {
                            Application.showOkDialog('Error', errorMessage);
                        };
                    },
                    error: function (data) {
                        console.log(data);
                    },
                    beforeSend: function() {
                        Application.addLocker();
                    },
                    complete: function () {
                        Application.removeLocker();
                    }
                });
            } else {
                $(component.getUISelector()).find('span').text(component.getProperty(TEXT).value);
                $(component.getUISelector()).find('i').hide();
                $(component.getUISelector()).data('url', null);
            }
        });
        $(component.getUISelector()).find('i').bind('click', function (e) {
            e.preventDefault();
            e.stopPropagation();
            $(component.getUISelector()).find('input').val('');
            $(component.getUISelector()).find('input').trigger('change');
        });
    }
    $(element)
        .hover(function () {
            if (component.getProperty(BACKGROUND_COLOR_HOVER) != null) {
                $(this).css('background-color', component.getProperty(BACKGROUND_COLOR_HOVER).value);
            }
            if (component.getProperty(BORDER_COLOR_HOVER) != null) {
                $(this).css('border-color', component.getProperty(BORDER_COLOR_HOVER).value);
            }
            if (component.getProperty(TEXT_COLOR_HOVER) != null) {
                $(this).css('color', component.getProperty(TEXT_COLOR_HOVER).value);
            }
        },
            function () {
                $(this).css('background-color', component.getProperty(BACKGROUND_COLOR).value);
                $(this).css('border-color', component.getProperty(BORDER_COLOR).value);
                $(this).css('color', component.getProperty(COLOR).value);
            });
    $(element).css({ "text-overflow": "ellipsis", "overflow": "hidden" });
    ViewerFactory.calculateButtonLineHeight(component.getUISelector());

    ViewerFactory.addLayeringController(this, component);
}

var FormCaptchaComponentViewer = function (component) {
    var element = $(component.getUISelector());
    var captchaId = 'captcha_' + component.id;
    if (!UI.getSetting("ispreview")) {
        $('#' + captchaId).addClass('inactively');
        if (component.isSelectable) {
            element.bind('click',
                function (event) {
                    if (ViewerFactory.editorCanBeShown(element, event)) {
                        event.stopPropagation();
                        $(this).highlightSelectedElement(component, true);
                    }
                });
        }
        if (component.isDraggable) {
            dragDrop.initElement(component.id);
        }
    }

    ViewerFactory.initCaptcha(component, captchaId);
}

ViewerFactory.calculateFormMinSize = function (component, element) {
    var minHeight = 100;
    var minWidth = 100;
    var elementBorder = parseInt(element.css('border-top-width')) * 2;
    element.children().each(function (index, item) {
        var bottomLine = item.offsetTop + item.offsetHeight + elementBorder;
        var rightLine = item.offsetLeft + item.offsetWidth + elementBorder;
        if (minHeight < bottomLine) {
            minHeight = bottomLine;
        }
        if (minWidth < rightLine) {
            minWidth = rightLine;
        }
    });
    if (element[0].offsetHeight <= minHeight) {
        element.css('min-height', minHeight + 'px');
        element.css('height', minHeight + 'px');
        component.setProperty('height', minHeight + 'px');
    }
    if (element[0].offsetWidth <= minWidth) {
        element.css('min-width', minWidth + 'px');
        element.css('width', minWidth + 'px');
        component.setProperty('width', minWidth + 'px');
    }
}

ViewerFactory.validateEmail = function validateEmail(email) {
    var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    return re.test(email);
}

ViewerFactory.validateRequiredField = function (component) {
    var element = $(component.getUISelector());
    switch (component.proto.name) {
        case TEXTBOX:
        case TEXTAREA:
        case SELECTLIST:
            return !!element.val();
        case RADIOLIST:
            return element.find("input").toArray().some(function (item) { return item.checked; });
        case ATTACHMENT:
            return !!element.data('url');
        case CHECKBOX:
            return element.find('input').is(':checked');
        default:
            return !!element.val();
    }
}

ViewerFactory.clearFormData = function (component) {
    function findForm(com) {
        if (com.proto.name === FORM) {
            return com;
        } else {
            return UI.siteComponentRepository.lookupData({ id: com.parentComponent.id });
        }
    }
    var formComponent = findForm(component);
    if (formComponent != null) {
        formComponent.children.forEach(function (item, index) {
            var id = item.id;
            var element = $('#' + id);

            //textbox
            if (element.hasClass('std-form-textbox')) {
                element.val('');
            }
            //textarea
            if (element.hasClass('std-form-textarea')) {
                element.val('');
            }
            //checkbox
            if (element.hasClass('std-form-checkbox')) {
                element.find('input').prop("checked", false);
            }
            //radio
            if (element.hasClass('std-form-radio-list')) {               
                element.find('input[name="' + id + '"]').prop('checked', false)
            }
            //dropdown
            if (element.hasClass('std-form-select-list')) {
                element.val('');
            }
            //attachment
            if (element.hasClass('std-form-attachment')) {
                element.find('input').val('');
                element.find('input').trigger('change');
            }
        });
    }
}

ViewerFactory.customFormValidation = function (component) {
    function findForm(com) {
        if (com.proto.name === FORM) {
            return com;
        } else {
            return UI.siteComponentRepository.lookupData({ id: com.parentComponent.id });
        }
    }
    var formComponent = findForm(component);
    var result = [];
    if (formComponent != null) {
        formComponent.children.forEach(function (item, index) {
            var element = $(item.getUISelector());
            var required = item.getProperty(REQUIRED_FIELD);
            if (item.proto.name === CAPTCHA) {
                if (!item.valid) {
                    result.push({
                        id: 'captcha_' + item.id + ' > div',
                        message: "Captcha required",
                        type: 'captcha'
                    });
                }
            } else {
                if (item.proto.name === TEXTBOX) {
                    var type = item.getProperty(TEXT_BOX_TYPE).value;
                    if (type == "Email") {
                        if (!ViewerFactory.validateEmail(element.val())) {
                            result.push({
                                id: item.id,
                                message: "Invalid email",
                                type: 'email'
                            });
                        }
                    }
                }

                if (required != null && required.value.toBoolean()) {
                    //check component
                    if (!ViewerFactory.validateRequiredField(item)) {
                        result.push({
                            id: item.id,
                            message: "Please fill required fields",
                            type: 'required'
                        });
                    }
                }
            }
        });
    }
    return result;
}

ViewerFactory.getFormCustomerEmail = function (submitComponent) {
    var formComponent = submitComponent.parentComponent;
    var result = '';
    if (formComponent != null) {

        formComponent.children.forEach(function (item, index) {
            var line = "";
            var id = item.id;
            var element = $('#' + id);
            if (element.hasClass('std-form-textbox')) {
                var component = UI.siteComponentRepository.lookupData({ id: id });
                if (component != null) {
                    var type = component.getProperty("text-box-type").value;
                    if (type == "Email") {
                        result = element.val();
                    }
                }
            }
        });
    }
    return result;
}

ViewerFactory.getFormSubmitData = function (submitComponent) {

    /*additional functions*/
    var getLabelValue = function (item) {
        var labelvalue = item.getProperty(SELECTEDLABEL).value;
        var labeltype = item.getProperty(SELECTEDLABELTYPE).value;
        if (labeltype == LABELTYPENAME) {
            return labelvalue + ": ";
        }

        if (labelvalue != "") {
            var value = "";
            UI.siteComponentRepository.lookupData({ id: item.parentComponent.id }).children.where({ displayName: LABEL }).forEach(function(label) {
                var captionToLabel = label.getProperty(CAPTION_COMPONENTS_TO_LABEL).value;

                if(captionToLabel == labelvalue)
                    value = label.getProperty(TEXT).value + ": ";
            });
            return value;
        }
        return "";
    };
    /*end additional functions*/
    var result = new Array();
    var formComponent = UI.siteComponentRepository.lookupData({ id: submitComponent.parentComponent.id });
    if (formComponent != null) {

        formComponent.children.sort(function (a, b) {
            var aTop =  parseInt(a.getProperty(TOP).value);
            var bTop =  parseInt(b.getProperty(TOP).value);
            var aLeft = parseInt(a.getProperty(LEFT).value);
            var bLeft = parseInt(b.getProperty(LEFT).value);
            return aTop == bTop ? aLeft - bLeft : aTop - bTop;
        });
        var attachments = new Array();
        formComponent.children.forEach(function (item, index) {
            var line = "";
            var id = item.id;
            var element = $('#' + id);

            //textbox
            if (element.hasClass('std-form-textbox')) {
                line = getLabelValue(item);
                line += element.val();
                result.push(line);
            }
            //textarea
            if (element.hasClass('std-form-textarea')) {
                line = getLabelValue(item);
                line += element.val();
                result.push(line);
            }
            //checkbox
            if (element.hasClass('std-form-checkbox')) {
                line = getLabelValue(item);
                line += (element.find('input').is(':checked') ? 'Yes' : 'No');
                result.push(line);
            }
            //radio
            if (element.hasClass('std-form-radio-list')) {
                line = getLabelValue(item);
                line += $('input[name="' + id + '"]:checked').val();
                result.push(line);
            }
            //dropdown
            if (element.hasClass('std-form-select-list')) {
                line = getLabelValue(item);
                line += element.val();
                result.push(line);
            }
            //attachment
            if (element.hasClass('std-form-attachment') && element.data('url')) {
                attachments.push(element.data('url'));                
            }

            var additionalSpaceProperty = item.getProperty(FORM_SPACE_AFTER_ITEM);

            if (additionalSpaceProperty != null && additionalSpaceProperty.value.toBoolean()) {
                result.push("");
            }
        });//end forEach
        if (attachments.length) {
            result.push(encodeURIComponent('<input type=\'hidden\' name=\'attachments\' value=\'' + attachments.join(';') + '\'/>'));
        }
    }
    return result;
}

/*End Form Components*/

ViewerFactory.tryUpdatePredefinedView = function (component) {
    switch (component.displayName) {
        case SLIDESHOW:
            if (component.children.length == 0) {
                $(component.getUISelector()).find('.carousel-inner').append(PREDEFINED_VIEW_SLIDESHOW);
            }
            break;
        default:
            break;
    }
}

ViewerFactory.bindEventGoToPage = function (component, element) {
    var mode = component.getProperty(MODE).value;
    var modeValue = component.getProperty(MODE_VALUE).value;
    var newTab = component.getProperty(OPEN_LINK_ON_BLANK).value.toBoolean();
    if (mode == 'page' && modeValue != 'None' && !newTab) {
        element.bind('click', Helpers.generateEventGoToPage);
    }
}

ViewerFactory.sendComponentDataToMail = function (component, dataArray, customerEmail, url, formid, callback) {
    url = url || (UI.getSetting("adminSiteDomain") + '/Account/SendDataToMail');
    var dataModel = {
        templateid: app.dataServiceContext.getTemplateData().templateId,
        componentid: component.id,
        data: dataArray,
        subject: component.getProperty(FORM_SUBJECT).value,
        sender: customerEmail,
        formid: formid
    };
    callback = callback ? callback : function () { };

    $.ajax({
        url: url,
        type: 'POST',
        data: dataModel,
        traditional: true,
        headers: null,
        dataType: 'text',
        error: function (result) {
            Application.showOkDialog('Sending', 'Error');
        },
        beforeSend: function (jqXHR) {
            Application.addLocker();
        },
        complete: function () {
            Application.removeLocker();
        },
        success: function (result, textStatus, jqXHR) {
            if (result === "") { // result return null;
                result = window.MESSAGE_WAS_SENT;
            }
            if (result.indexOf("[checkedMessage]") != -1) {
                ViewerFactory.createModalWindow(component, customerEmail);
                callback();
            } else {
                Application.showOkDialog('Sending', result);
            }
        }
    });
};
var StretcherFactory = function () { }

StretcherFactory.proxy = function () {
    var component = this;
    var element = $(component.getUISelector());
    var name = component.proto.name;
    return StretcherFactory.stretcherForComponent[name] ? StretcherFactory.stretcherForComponent[name](component) : StretcherFactory.stretcherForComponent.default(component);
}

StretcherFactory.stretcherForComponent = {};

StretcherFactory.stretcherForComponent[MENU] = function (component) { return new MenuComponentStretcher(component); },
StretcherFactory.stretcherForComponent[IMAGE] = function (component) { return new ImageComponentStretcher(component); },
StretcherFactory.stretcherForComponent[PANEL] = function (component) { return new PanelComponentStretcher(component); },
StretcherFactory.stretcherForComponent[GALLERY] = function (component) { return new GalleryComponentStretcher(component); },
StretcherFactory.stretcherForComponent[HOUSE_PHOTO_TOUR] = function (component) { return new HousePhotoTourComponentStretcher(component); },
StretcherFactory.stretcherForComponent[SLIDESHOW] = function (component) { return new SlideshowComponentStretcher(component); },
StretcherFactory.stretcherForComponent['default'] = function (component) { return new DefaultComponent(component); }

var MenuComponentStretcher = function (component) {
    StretcherFactory.stretchComponent(component, function () {
        UI.renderMenus();
    });
}

var ImageComponentStretcher = function (component) {
    StretcherFactory.stretchComponent(component, function () {
        StretcherFactory.stretchImage(component);
    });
}

var PanelComponentStretcher = function (component) {
    StretcherFactory.stretchComponent(component);
}

var GalleryComponentStretcher = function (component) {
    StretcherFactory.stretchComponent(component);
}

var HousePhotoTourComponentStretcher = function (component) {
    StretcherFactory.stretchComponent(component);
}

var SlideshowComponentStretcher = function (component) {
    StretcherFactory.stretchComponent(component);
}

var DefaultComponent = function (component) {
    StretcherFactory.stretchComponent(component);
}

StretcherFactory.stretchImage = function (component) {
    var img = $(component.getUISelector()).find('img');
    var stretching = component.getProperty(IMAGE_STRETCHING);
    if (stretching != null) {
        img.css('object-fit', stretching.value);
    }
}


StretcherFactory.afterStretchActions = function (component) {
    if (component.proto.name === MENU) {
        UI.renderMenus();
    }
    else if (component.proto.name === IMAGE) {
        StretcherFactory.stretchImage(component);
    }
}

StretcherFactory.getCurrentStretchStatus = function (component) {
    if (component == null) {
        return false;
    }
    var stretchToFullWidth = component.getProperty(STRETCH_TO_FULL_WIDTH) == null ? false : component.getProperty(STRETCH_TO_FULL_WIDTH).value.toBoolean();
    return stretchToFullWidth;

}

StretcherFactory.isPined = function (component) {
    if (component == null) {
        return false;
    }

    var isPined = component.getProperty(IS_PINED) == null ? false : component.getProperty(IS_PINED).value.toBoolean();
    return isPined;
}

StretcherFactory.toggleStretchToFullWidth = function (component) {
    var oldValue = StretcherFactory.getCurrentStretchStatus(component);    
    StretcherFactory.changeStretching(component, !oldValue);
    StretcherFactory.changePropertiesVisability(!oldValue);
}

StretcherFactory.changeStretching = function (component, toStretch) {
    var element = component.getUISelector();

    var struct = [
                            {
                                component: component,
                                property: STRETCH_TO_FULL_WIDTH,
                                newvalue: toStretch.toString(),
                                oldvalue: (!toStretch).toString()
                            }
    ];

    if (!toStretch) {
        struct.push({
            component: component,
            property: MARGINS_WIDTH,
            newvalue: '0px',
            oldvalue: ''
        });
    }

    var callback = function () {            
        UI.actionService.runActionForComponent(component, 'remove-component-from-form', true);
        UI.actionService.runActionForComponent(component, 'add-component-to-form', true);
        $(element).highlightSelectedElement(component, true);

        if (Helpers.isGalleryComponent(component)) {
            //call editor to refresh children components
            UI.callEditor(component);
        }
    };

    UI.undoManagerAddSimpleArr(struct, callback, callback, true);
    StretcherFactory.afterStretchActions(component);
}

StretcherFactory.enableMarginsToStretchedComponent = function (component, element, value) {
    var margin = value;
    var width = StretcherFactory.getComponentFullWidth(value);
    StretcherFactory.setElementValues(element, width, margin, margin);
    StretcherFactory.afterStretchActions(component);
    $(element).highlightSelectedElement(component, true);
}

StretcherFactory.getComponentFullWidth = function (margin) {
    //canvas.css('margin-left') doesn't work in phantomjs and firefox
    var canvas = $('.site-wrapper');
    var canvasFullWidth = canvas.offset().left * 2 + canvas.width() - 2 * parseInt(margin, 0);
    return canvasFullWidth + 'px';
}

StretcherFactory.getComponentLeft = function (component) {
    //canvas.css('margin-left') doesn't work in phantomjs and firefox
    var left = -parseFloat($('.site-wrapper').offset().left) + 'px';

    if (StretcherFactory.isPined(component))
        left = '0px';

    return left;
}

StretcherFactory.stretchComponent = function (component, callback) {
    console.log('STRETCH COMPONENT')
    var element = $(component.getUISelector());

    var stretchToFullWidth = StretcherFactory.getCurrentStretchStatus(component);

    if (stretchToFullWidth) {

        var margin = component.getProperty(MARGINS_WIDTH).value == null ? '0px' : component.getProperty(MARGINS_WIDTH).value;
        var left = StretcherFactory.getComponentLeft(component);
        var width = StretcherFactory.getComponentFullWidth(margin);
        StretcherFactory.setElementValues(element, width, margin, margin, left);
    }

    callback = callback || function () { };
    callback();

}

StretcherFactory.setElementValues = function (element, width, marginLeft, marginRight, left) {
    $(element).css({ 'width': width, 'margin-left': marginLeft, 'margin-right': marginRight });
    if (left != undefined) {
        $(element).css({ 'left': left });
    }
    if (parseInt(marginLeft) == 0) {
        $(element).offset({ left: 0 });
    }
}

StretcherFactory.switchToOriginalElementSizeToTheCenterOfWindow = function (component) {

    var element = component.getUISelector();
    var oldZIndex = $(element).css('z-index');
    var basicComponentsFiltered = UI.basicComponentRepository.getAll().where({ name: component.proto.name });

    if (basicComponentsFiltered.any()) {
        var basicComponent = basicComponentsFiltered.firstOrDefault();
        var width = basicComponent.getProperty(WIDTH).value;
        var marginLeft = basicComponent.getProperty('margin-left') == null ? '0px' : basicComponent.getProperty('margin-left').value;
        var marginRight = basicComponent.getProperty('margin-right') == null ? '0px' : basicComponent.getProperty('margin-right').value;
        StretcherFactory.setElementValues(element, width, marginLeft, marginRight);
    }
    element = $(element).detach();
    $(UI.getBody().getUISelector()).append(element);
    element.css('z-index', '1000');
    $(element).position({ of: $(window), at: "center center" });
    element.css('z-index', oldZIndex);

    $(element).highlightSelectedElement(component, true);

}

StretcherFactory.switchToOriginalElementSize = function (component) {


    var basicComponentsFiltered = UI.basicComponentRepository.getAll().where({ name: component.proto.name });

    var basicLeft = 0;
    var basicWidth = 0;

    if (basicComponentsFiltered.any()) {
        var basicComponent = basicComponentsFiltered.firstOrDefault();
        
        basicLeft = basicComponent.getProperty(LEFT) != null ? basicComponent.getProperty(LEFT).value : basicLeft;
        basicWidth = basicComponent.getProperty(WIDTH) != null ? basicComponent.getProperty(WIDTH).value : basicWidth;
    }

    var element = component.getUISelector();
    var left = component.getProperty(LEFT) != null ? component.getProperty(LEFT).value : basicLeft;
    var width = component.getProperty(WIDTH) != null ? component.getProperty(WIDTH).value : basicWidth;

    $(element).css({ 'left': left, 'width': width, 'margin-left': '0px', 'margin-right': '0px' });

    $(element).highlightSelectedElement(component, true);

}

StretcherFactory.updateComponentWrapper = function (component) {
    var stretchToFullWidth = StretcherFactory.getCurrentStretchStatus(component);
    var isPined = StretcherFactory.isPined(component);
    if (stretchToFullWidth) {
        var wrapElements = document.getElementsByClassName(SELECT_WRAPPER);
        var wrapper = wrapElements[0];
        var oldWidth = parseFloat($(wrapper).css('width'));
        var oldLeft = parseFloat($(wrapper).css('left'));
        $(wrapper).css({ 'border-left': 'none', 'border-right': 'none', 'width': oldWidth - 2 });

        if (isPined) {
            var margin = $(component.getUISelector()).css('margin');
            $(wrapper).css('margin', margin);
        }

        $(wrapper).find('.resizer').addClass('stretched');
        $(wrapper).find('.' + SELECT_WRAPPER_MENU).addClass('stretched');
    }
}

StretcherFactory.isPropertyCouldBeWritten = function (component, property) {
    var propertyShouldBeWritten = true;
    var stretchToFullWidth = StretcherFactory.getCurrentStretchStatus(component);
    if (stretchToFullWidth) {
        return !(property == LEFT || property == WIDTH);
    }
    return true;
}


StretcherFactory.stretchComponents = function () {
    var componentsElemets = document.getElementsByClassName('std-component');
    for (i = 0; i < componentsElemets.length; i++) {
        var component = UI.siteComponentRepository.lookupData({ id: $(componentsElemets[i]).getId() });
        if (component.getProperty('stretch-to-full-width') != null) {
            component.stretcher();
        }
    }
}

StretcherFactory.onStretchChange = function (component) {
    var stretchToFullWidth = StretcherFactory.getCurrentStretchStatus(component);
    StretcherFactory.changePropertiesVisability(stretchToFullWidth);
}

StretcherFactory.changePropertiesVisability = function (stretchToFullWidth) {
    if (stretchToFullWidth){
        $("#marginswidthdiv").show();
        $("#widthInput").parent().hide();
        $("#leftInput").parent().hide();
    }
    else {
        $("#marginswidthdiv").hide();
        $("#widthInput").parent().show();
        $("#leftInput").parent().show();
    }
}

StretcherFactory.checkOneOfSelectedElementsStretched = function () {
    var stretched = false;
    ko.utils.arrayForEach(grouping.selectedItems(), function (item) {
        var element = $(item);
        var component = UI.siteComponentRepository.lookupData({ id: $(element).getId() });
        var stretchToFullWidth = StretcherFactory.getCurrentStretchStatus(component);
        if (stretchToFullWidth) {
            stretched = true;
        }
    })
    return stretched;

}
;
var ContextFactory = function () { }

ContextFactory.contextFor = function (component, type) {
    var properties = UI.getDevice().getPropertiesList(component);
    if (type === EDITOR_CONTEXT) {
        switch (component.proto.name) {
            case MENU:
                return new MenuComponentEditorContext(component);
            case 'anchor':
                return new AnchorComponentEditorContext(component);
            case "div":
                return new PanelComponentEditorContext(component);
            case FORM:
                return new FormComponentEditorContext(component);
            case LABEL:
                return new LabelComponentEditorContext(component);
            case CAPTCHA:
                return new CaptchaComponentEditorContext(component);
            case TEXTBOX:
            case TEXTAREA:
                return new TextboxComponentEditorContext(component);
            case AUTOCOMPLETE_ADDRESS:
                return new AutocompleteAddressComponentEditorContext(component);
            case RADIOLIST:
                return new RadiolistComponentEditorContext(component);
            case SELECTLIST:
                return new SelectlistComponentEditorContext(component);
            case CHECKBOX:
                return new CheckboxComponentEditorContext(component);
            case SUBMIT:
                return new SubmitComponentEditorContext(component);
            case ATTACHMENT:
                return new AttachmentComponentEditorContext(component);
            case CANCEL:
                return new CancelComponentEditorContext(component);
            case BLOGGING:
                return new BloggingComponentEditorContext(component);
            case "frame":
                return new FrameComponentEditorContext(component);
            case "link":
                return new LinkComponentEditorContext(component);
            case IMAGE:
                return new ImageComponentEditorContext(component);
            case GALLERY:
                return new GalleryComponentEditorContext(component);
            case HOUSE_PHOTO_TOUR:
                return new HousePhotoTourComponentEditorContext(component);
            case CONTACT_US:
                return new ContactUsComponentEditorContext(component);
            case 'html-container':
                return new HtmlContainerComponentEditorContext(component);
            case SLIDESHOW:
                return new SlideshowComponentEditorContext(component);
            case VIDEO:
                return new VideoComponentEditorContext(component);
            case SOUND:
                return new SoundComponentEditorContext(component);
            case MORTGAGE_CALCULATOR:
                return new MortgageCalculatorComponentEditorContext(component);
            case EVALUATE_HOME:
                return new EvaluateHomeComponentEditorContext(component);
            case LIST:
                return new ListComponentEditorContext(component);
            case 'heading':
                return new HeadingComponentEditorContext(component);
            case 'button':
                return new ButtonComponentEditorContext(component);
            case MAP:
                return new MapComponentEditorContext(component);
            case 'paragraph':
                return new ParagraphComponentEditorContext(component);
            case 'headertext':
                return new HeadertextComponentEditorContext(component);
            case PDF:
                return new PdfComponentEditorContext(component);
            case SIGNIN:
                return new SignInComponentEditorContext(component);
            case STORE:
                return new StoreEditorContext(component);
            case STORE_PRODUCT:
                return new StoreProductEditorContext(component);

            case STORE_PRODUCT_IMAGES:
                return new StoreProductImagesEditorContext(component);
            case STORE_PRODUCT_TITLE:
                return new StoreProductTitleEditorContext(component);
            case STORE_PRODUCT_PRICE:
                return new StoreProductPriceEditorContext(component);
            case STORE_PRODUCT_SKU:
                return new StoreProductSkuEditorContext(component);
            case STORE_PRODUCT_DESCRIPTION:
                return new StoreProductDescriptionEditorContext(component);
            case STORE_PRODUCT_OPTIONS:
                return new StoreProductOptionsEditorContext(component);
            case STORE_PRODUCT_QUANTITY:
                return new StoreProductQuantityEditorContext(component);
            case STORE_PRODUCT_ADD_TO_CART:
                return new StoreProductAddToCartEditorContext(component);
            case STORE_PRODUCT_SOCIAL:
                return new StoreProductSocialEditorContext(component);

            case STORE_CART_LINK:
                return new StoreCartLinkEditorContext(component);
            case STORE_CATEGORIES:
                return new StoreCategoriesEditorContext(component);
            case STORE_CART:
                return new StoreCartEditorContext(component);
            case STORE_CART_CHECKOUT:
                return new StoreCartCheckoutEditorContext(component);
            case STORE_THANK_YOU:
                return new StoreThankYouEditorContext(component);
            case STORE_GALLERY:
                return new StoreGalleryEditorContext(component);
            case STORE_GALLERY_SHOW_MORE:
                return new StoreGalleryShowMoreEditorContext(component);
            case STORE_GALLERY_PRODUCT:
                return new StoreGalleryProductEditorContext(component);
            case STORE_GALLERY_PRODUCT_IMAGE:
                return new StoreGalleryProductImageEditorContext(component);
            case STORE_GALLERY_PRODUCT_LABEL:
                return new StoreGalleryProductLabelEditorContext(component);
            case STORE_GALLERY_PRODUCT_PRICE:
                return new StoreGalleryProductPriceEditorContext(component);
            case STORE_GALLERY_PRODUCT_TITLE:
                return new StoreGalleryProductTitleEditorContext(component);

            case STORE_GALLERY_PRODUCT_DESCRIPTION:
                return new StoreGalleryProductDescriptionEditorContext(component);
            default:
                return {};
        }
    } else if (type === VIEWER_CONTEXT) {
        switch (component.proto.name) {
            case MENU:
            return new MenuComponentViewerContext(component, properties);
            case 'anchor':
            return AnchorComponentViewerContext(component, properties);
            case "div":
            return new PanelComponentViewerContext(component, properties);
            case FORM:
            return new FormComponentViewerContext(component, properties);
            case LABEL:
                return new LabelComponentViewerContext(component, properties);
            case CAPTCHA:
                return new CaptchaComponentViewerContext(component, properties);
            case TEXTBOX:
            case TEXTAREA:
                return new TextboxComponentViewerContext(component, properties);
            case AUTOCOMPLETE_ADDRESS:
                return new AutocompleteAddressComponentViewerContext(component, properties);
            case RADIOLIST:
            return new RadiolistComponentViewerContext(component, properties);
            case SELECTLIST:
            return new SelectlistComponentViewerContext(component, properties);
            case CHECKBOX:
            return new CheckboxComponentViewerContext(component, properties);
            case SUBMIT:
                return new SubmitComponentViewerContext(component, properties);
            case ATTACHMENT:
                return new AttachmentComponentViewerContext(component, properties);
            case CANCEL:
                return new CancelComponentViewerContext(component, properties);
            case "frame":
            return new FrameComponentViewerContext(component, properties);
            case "link":
            return new LinkComponentViewerContext(component, properties);
            case IMAGE:
            return new ImageComponentViewerContext(component, properties);
            case GALLERY:
            return new GalleryComponentViewerContext(component, properties);
            case HOUSE_PHOTO_TOUR:
            return new HousePhotoTourComponentViewerContext(component, properties);
            case CONTACT_US:
            return new ContactUsComponentViewerContext(component, properties);
            case 'html-container':
            return new HtmlContainerComponentViewerContext(component, properties);
            case SLIDESHOW:
            return new SlideshowComponentViewerContext(component, properties);
            case VIDEO:
            return new VideoComponentViewerContext(component, properties);
            case SOUND:
            return new SoundComponentViewerContext(component, properties);
            case MORTGAGE_CALCULATOR:
            return new MortgageCalculatorComponentViewerContext(component, properties);
            case EVALUATE_HOME:
            return new EvaluateHomeComponentViewerContext(component, properties);
            case LIST:
            return new ListComponentViewerContext(component, properties);
            case 'heading':
            return new HeadingComponentViewerContext(component, properties);
            case 'button':
                return new ButtonComponentViewerContext(component, properties);
            case MAP:
                return new MapComponentViewerContext(component, properties);
            case 'paragraph':
            return new ParagraphComponentViewerContext(component, properties);
            case 'headertext':
            return new HeadertextComponentViewerContext(component, properties);
            case PDF:
                return new PdfComponentViewerContext(component, properties);
            case BLOGGING:
                return new BloggingComponentViewerContext(component, properties);
            case SIGNIN:
                return new SignInComponentViewerContext(component, properties);
            case STORE:
                return new StoreViewerContext(component, properties);
            case STORE_PRODUCT:
                return new StoreProductViewerContext(component, properties);
            case STORE_PRODUCT_PRICE:
                return new StoreProductPriceViewerContext(component, properties);
            case STORE_PRODUCT_SKU:
                return new StoreProductSkuViewerContext(component, properties);
            case STORE_PRODUCT_QUANTITY:
                return new StoreProductQuantityViewerContext(component, properties);
            case STORE_PRODUCT_ADD_TO_CART:
                return new StoreProductAddToCartViewerContext(component, properties);            
            case STORE_PRODUCT_SOCIAL:
                return new StoreProductSocialViewerContext(component, properties);
            case STORE_PRODUCT_TITLE:
                return new StoreProductTitleViewerContext(component, properties);
            case STORE_PRODUCT_DESCRIPTION:
                return new StoreProductDescriptionViewerContext(component, properties);
            case STORE_PRODUCT_IMAGES:
                return new StoreProductImagesViewerContext(component, properties);
            case STORE_PRODUCT_OPTIONS:
                return new StoreProductOptionsViewerContext(component, properties);
            case STORE_CART_LINK:
                return new StoreCartLinkViewerContext(component, properties);
            case STORE_CATEGORIES:
                return new StoreCategoriesViewerContext(component, properties);
            case STORE_CART:
                return new StoreCartViewerContext(component, properties);
            case STORE_CART_CHECKOUT:
                return new StoreCartCheckoutViewerContext(component, properties);
            case STORE_THANK_YOU:
                return new StoreThankYouViewerContext(component, properties);
            case STORE_GALLERY:
                return new StoreGalleryViewerContext(component, properties);
            case STORE_GALLERY_SHOW_MORE:
                return new StoreGalleryShowMoreViewerContext(component, properties);
            case STORE_GALLERY_PRODUCT_LABEL:
                return new StoreGalleryProductLabelViewerContext(component, properties);
            case STORE_GALLERY_PRODUCT_PRICE:
                return new StoreGalleryProductPriceViewerContext(component, properties);
            case STORE_GALLERY_PRODUCT_IMAGE:
                return new StoreGalleryProductImageViewerContext(component, properties);
            case STORE_GALLERY_PRODUCT:
                return new StoreGalleryProductViewerContext(component, properties);
            case MANAGE_STORE_PRODUCTS_ADD_CATEGORY:
                return new ManageStoreProductsCategoryViewerContext(component, properties);
            case MANAGE_STORE_PRODUCTS_ADDING_PRODUCTS_TO_CATEGORY:
                return new ManageStoreProductsAddingProductsToCategoryViewerContext(component, properties);
            case HEADER:
                return new HeaderViewerContext(component, properties);
            default:
                return {
                    isDockable: component.isDockable ? "dockable" : "",
                    id: component.id,
                    styles: properties.where({ group: 'style' })
                };
        }
    }
};

var HeaderViewerContext = function(component, properties) {
    var ctx = {
        isPined: false,
        isDockable: component.isDockable ? "dockable" : "",
        id: component.id,
        styles: properties.where({ group: 'style' })
    };
    if (UI.getSetting("ispreview") && UI.getDevice().isDesktop()) {
        ctx.isPined = component.getProperty(IS_PINED).value.toBoolean();
        ctx.styles.push(new Property({ name: 'width', value: UI.getDevice().getWidth() + 'px' }));
    }
    return ctx;
}

var StoreCategoriesViewerContext = function (component, properties) {
    return {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        text: 'Categories'
    };
}

var StoreCategoriesEditorContext = function (component) {
    var txtHoverColor = component.getProperty(TEXT_COLOR_HOVER).value;
    var color = component.getProperty(COLOR).value;

    var bgColor = component.getProperty(BACKGROUND_COLOR).value;
    var bgHoverColor = component.getProperty(BACKGROUND_COLOR_HOVER).value;

    var borderColor = component.getProperty(BORDER_COLOR).value;
    var borderColorHover = component.getProperty(BORDER_COLOR_HOVER).value;

    var ff = component.getProperty(FONT_FAMILY).value;
    var ta = component.getProperty(TEXT_ALIGN).value;

    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;
    var zindex = component.getProperty(Z_INDEX).value;
    var fontSize = component.getProperty(FONT_SIZE).value;

    var br = component.getProperty(BORDER_RADIUS).value;
    var bw = component.getProperty(BORDER_WIDTH).value;

    var textalignvalues = [
        { name: "Left", value: "left" },
        { name: "Right", value: "right" },
        { name: "Center", value: "center" },
        { name: "Justify", value: "justify" }
    ];
    var fontfamilyvalues = ContextFactory.getComponentFonts();

    var context = {
        width: width,
        height: height,
        left: left,
        top: top,
        zindex: zindex || 0,

        color: color,
        colorHover: txtHoverColor,
        bgColor: bgColor,
        bgHoverColor: bgHoverColor,
        borderColor: borderColor,
        borderColorHover: borderColorHover,

        ff: ff,
        ta: ta,
       
        fontSize: fontSize,
        borderRadius: br,
        borderWidth: bw,
        ffOptions: fontfamilyvalues,
        taOptions: textalignvalues
    };
       
    return context;
}

var StoreProductImagesEditorContext = function (component) {
    var imageStretching = component.getProperty(IMAGE_STRETCHING).value;
    var borderColor = component.getProperty(BORDER_COLOR).value;
    var borderWidth = component.getProperty(BORDER_WIDTH).value;
    var context = {
        scaling: imageStretching,
        borderColor: borderColor,
        borderWidth: borderWidth
    };
    return context;
}
var StoreProductTitleEditorContext = function (component) {
    var color = component.getProperty(COLOR).value;
    var ff = component.getProperty(FONT_FAMILY).value;
    var ta = component.getProperty(TEXT_ALIGN).value;
    var context = {
        color: color,
        ff: ff,
        ta: ta
    };
    return context;
}
var StoreProductPriceEditorContext = function (component) {
    var color = component.getProperty(COLOR).value;
    var secondaryColor = component.getProperty(SECONDARY_COLOR).value;
    var ff = component.getProperty(FONT_FAMILY).value;
    var ta = component.getProperty(TEXT_ALIGN).value;
    var context = {
        color: color,
        ff: ff,
        ta: ta,
        secondaryColor: secondaryColor
    };
    return context;
}
var StoreProductSkuEditorContext = function (component) {
    var color = component.getProperty(COLOR).value;
    var ff = component.getProperty(FONT_FAMILY).value;
    var ta = component.getProperty(TEXT_ALIGN).value;
    var context = {
        color: color,
        ff: ff,
        ta: ta
    };
    return context;
}
var StoreProductDescriptionEditorContext = function (component) {
    var color = component.getProperty(COLOR).value;
    var ff = component.getProperty(FONT_FAMILY).value;
    var ta = component.getProperty(TEXT_ALIGN).value;
    var context = {
        color: color,
        ff: ff,
        ta: ta
    };
    return context;
}
var StoreProductOptionsEditorContext = function (component) {
    var color = component.getProperty(COLOR).value;
    var ff = component.getProperty(FONT_FAMILY).value;
    var ta = component.getProperty(TEXT_ALIGN).value;
    var context = {
        color: color,
        ff: ff,
        ta: ta
    };
    return context;
}
var StoreProductQuantityEditorContext = function (component) {
    var color = component.getProperty(COLOR).value;
    var ff = component.getProperty(FONT_FAMILY).value;
    var ta = component.getProperty(TEXT_ALIGN).value;
    var context = {
        color: color,
        ff: ff,
        ta: ta
    };
    return context;
}
var StoreProductAddToCartEditorContext = function (component) {
    var color = component.getProperty(COLOR).value;
    var ff = component.getProperty(FONT_FAMILY).value;
    var ta = component.getProperty(TEXT_ALIGN).value;
    var text = component.getProperty(TEXT).value;
    var bgColor = component.getProperty(BACKGROUND_COLOR).value;
    var borderColor = component.getProperty(BORDER_COLOR).value;
    var borderRadius = component.getProperty(BORDER_RADIUS).value;
    var context = {
        text: text,
        color: color,
        ff: ff,
        ta: ta,
        bgcolor: bgColor,
        borderColor: borderColor,
        borderRadius: borderRadius
    };
    return context;
}
var StoreProductSocialEditorContext = function (component) {
    var context = {
        ta: component.getProperty(TEXT_ALIGN).value,
        shareToFacebook: component.getProperty(SHARE_TO_FACEBOOK).value.toBoolean(),
        shareToTwitter: component.getProperty(SHARE_TO_TWITTER).value.toBoolean(),
        shareToGplus: component.getProperty(SHARE_TO_GPLUS).value.toBoolean(),
        fbImage: STORE_SOCIAL_FACEBOOK_IMAGE,
        twitterImage: STORE_SOCIAL_TWITTER_IMAGE,
        gplusImage: STORE_SOCIAL_GPLUS_IMAGE
    };
    return context;
}

var StoreProductPriceViewerContext = function (component, properties) {
    var secondaryColor = component.getProperty(SECONDARY_COLOR).value;
    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        secondaryColor: secondaryColor
    };
    return context;
}

var StoreProductSkuViewerContext = function (component, properties) {
    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
    };
    return context;
}

var StoreProductQuantityViewerContext = function (component, properties) {
    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
    };
    return context;
}

var StoreProductAddToCartViewerContext = function (component, properties) {
    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        text: component.getProperty(TEXT).value
    };
    return context;
}

var StoreProductSocialViewerContext = function (component, properties) {
    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        shareToFacebook: component.getProperty(SHARE_TO_FACEBOOK).value.toBoolean(),
        shareToTwitter: component.getProperty(SHARE_TO_TWITTER).value.toBoolean(),
        shareToGplus: component.getProperty(SHARE_TO_GPLUS).value.toBoolean(),
        fbImage: STORE_SOCIAL_FACEBOOK_IMAGE,
        twitterImage: STORE_SOCIAL_TWITTER_IMAGE,
        gplusImage: STORE_SOCIAL_GPLUS_IMAGE
    };
    return context;
}

var StoreProductTitleViewerContext = function (component, properties) {
    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
    };
    return context;
}

var StoreProductDescriptionViewerContext = function (component, properties) {
    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' })
    };
    return context;
}

var StoreProductImagesViewerContext = function (component, properties) {
    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        isCrop: component.getProperty(IMAGE_STRETCHING).value === 'crop'
    };
    var viewModel = UI.viewModelRepository[STORE_PRODUCT];
    if (viewModel && viewModel.product() && !_.isEmpty(viewModel.product()) && viewModel.product().images().length) {
        context.images = viewModel.product().images();
    } else {
        context.images = [{ url: STORE_PRODUCT_DEFAULT_IMAGE }];
    }
    return context;
}

var StoreProductOptionsViewerContext = function (component, properties) {
    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
    };
    return context;
}


var StoreEditorContext = function(component) {
    return {
        paypalEmail: component.getProperty(PAYPAL_EMAIL).value
    };
}

var StoreViewerContext = function (component, properties) {
    return {
        id: component.id,
        styles: properties.where({ group: 'style' })
    };
}

var StoreProductEditorContext = function (component) {    
    var components = [];
    components.push(UI.basicComponentRepository.lookupData({name: STORE_PRODUCT_PRICE}));
    components.push(UI.basicComponentRepository.lookupData({name: STORE_PRODUCT_SKU}));
    components.push(UI.basicComponentRepository.lookupData({name: STORE_PRODUCT_QUANTITY}));
    components.push(UI.basicComponentRepository.lookupData({name: STORE_PRODUCT_ADD_TO_CART}));    
    components.push(UI.basicComponentRepository.lookupData({ name: STORE_PRODUCT_SOCIAL }));
    _.forEach(components,
        function(com) {
            com.checked = false;
        });
    _.forEach(component.children,
        function (child) {            
            var item = _.find(components, function (comp) { return comp.id === child.componentId });            
            if (item) {                
                item.checked = true;
            }
        });    

    var textalignvalues = [
    { name: "Left", value: "left" },
    { name: "Right", value: "right" },
    { name: "Center", value: "center" },
    { name: "Justify", value: "justify" }
    ];

    var imagestretchvalues = [
    { name: "Crop", value: "crop" },
    { name: "Fill", value: "fill" }
    ];

    var fontfamilyvalues = ContextFactory.getComponentFonts();

    var context = {
        layout: component.getProperty(LAYOUT).value,
        bgcolor: component.getProperty(BACKGROUND_COLOR).value,
        components: components,
        children: {},
        scalingOptions: imagestretchvalues,
        fontOptions: fontfamilyvalues,
        taOptions: textalignvalues
    };
    _.forEach(component.children,
        function(child) {
            context.children[child.proto.name] = ContextFactory.contextFor(child, EDITOR_CONTEXT);
        });
    return context;
}

var StoreProductViewerContext = function (component, properties) {    
    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        layout: component.getProperty(LAYOUT).value
    };
    return context;
}

var StoreCartLinkEditorContext = function (component) {
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;
    var zindex = component.getProperty(Z_INDEX).value;
    var fontSize = component.getProperty(FONT_SIZE).value;
    var color = component.getProperty(COLOR).value;
    var secondaryColor = component.getProperty(SECONDARY_COLOR).value;

    var context = {
        width: width,
        height: height,
        left: left,
        top: top,
        zindex: zindex || 0,
        fontSize: fontSize,
        color: color,
        secondaryColor: secondaryColor
    };
    return context;
}

var StoreCartLinkViewerContext = function (component, properties) {    
    return {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        href: Helpers.generateLinkToPage('cart'),
        secondaryColor: component.getProperty(SECONDARY_COLOR).value
    };
}

var StoreCartCheckoutEditorContext = function (component) {   
    var context = {        
        text: component.getProperty(TEXT).value,
        fontFamily: component.getProperty(FONT_FAMILY).value,
        color: component.getProperty(COLOR).value,
        bgColor: component.getProperty(BACKGROUND_COLOR).value,
        borderColor: component.getProperty(BORDER_COLOR).value,
        borderWidth: component.getProperty(BORDER_WIDTH).value,
        borderRadius: component.getProperty(BORDER_RADIUS).value
    };
    return context;
}

var StoreCartCheckoutViewerContext = function (component, properties) {
    return {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        text: component.getProperty(TEXT).value
    };
}

var StoreCartEditorContext = function (component) {
    var context = {
        fontOptions: ContextFactory.getComponentFonts(),
        fontFamily: component.getProperty(FONT_FAMILY).value,
        color: component.getProperty(COLOR).value,
        bgColor: component.getProperty(BACKGROUND_COLOR).value,
        dividerColor: component.getProperty(DIVIDER_COLOR).value,

        title: component.getProperty(TITLE).value,
        text: component.getProperty(TEXT).value,
        description: component.getProperty(DESCRIPTION).value,
        checkout: {}
    };
    var storeCartCheckoutBtn = _.find(component.children,
        function (child) {
            return child.proto.name === STORE_CART_CHECKOUT;
        });
    if (storeCartCheckoutBtn) {
        context.checkout = ContextFactory.contextFor(storeCartCheckoutBtn, EDITOR_CONTEXT);
    }
    return context;
}

var StoreCartViewerContext = function (component, properties) {
    return {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        title: component.getProperty(TITLE).value,
        text: component.getProperty(TEXT).value,
        description: component.getProperty(DESCRIPTION).value,
        dividerColor: component.getProperty(DIVIDER_COLOR).value
    };
}

var StoreThankYouEditorContext = function (component) {
    return {
        fontOptions: ContextFactory.getComponentFonts(),
        fontFamily: component.getProperty(FONT_FAMILY).value,
        color: component.getProperty(COLOR).value,
        bgColor: component.getProperty(BACKGROUND_COLOR).value,
        dividerColor: component.getProperty(DIVIDER_COLOR).value,
        title: component.getProperty(TITLE).value,
        text: component.getProperty(TEXT).value,
        description: component.getProperty(DESCRIPTION).value,
        textOrderNumber: component.getProperty(TEXT_ORDER_NUMBER).value,
        textTotalCost: component.getProperty(TEXT_TOTAL_COST).value,
        textShippingTo: component.getProperty(TEXT_SHIPPING_TO).value
    };
}

var StoreThankYouViewerContext = function (component, properties) {
    return {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        title: component.getProperty(TITLE).value,
        text: component.getProperty(TEXT).value,
        description: component.getProperty(DESCRIPTION).value,
        textOrderNumber: component.getProperty(TEXT_ORDER_NUMBER).value,
        textTotalCost: component.getProperty(TEXT_TOTAL_COST).value,
        textShippingTo: component.getProperty(TEXT_SHIPPING_TO).value,
        dividerColor: component.getProperty(DIVIDER_COLOR).value
    };
}

var StoreGalleryEditorContext = function (component) {
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;
    var zindex = component.getProperty(Z_INDEX).value;

    var components = [];   
    var storeGalleryProduct = _.find(component.children,
        function(child) {
            return child.proto.name === STORE_GALLERY_PRODUCT;
        });
    if (storeGalleryProduct) {
        components.push(UI.basicComponentRepository.lookupData({ name: STORE_GALLERY_PRODUCT_TITLE }));
        components.push(UI.basicComponentRepository.lookupData({ name: STORE_GALLERY_PRODUCT_PRICE }));
        components.push(UI.basicComponentRepository.lookupData({ name: STORE_GALLERY_PRODUCT_DESCRIPTION }));
        _.forEach(components,
            function (com) {
                com.checked = false;
            });
        _.forEach(storeGalleryProduct.children,
           function (child) {
               var item = _.find(components, function (comp) { return comp.id === child.componentId });
               if (item) {
                   item.checked = true;
               }
           });
    }    
    var textalignvalues = [
        { name: "Left", value: "left" },
        { name: "Right", value: "right" },
        { name: "Center", value: "center" },
        { name: "Justify", value: "justify" }
    ];
    var fontfamilyvalues = ContextFactory.getComponentFonts();


    var cols = component.getProperty(COLUMNS).value;
    var rows = component.getProperty(ROWS).value;
    var storeShowMore = _.find(component.children,
        function(child) {
            return child.proto.name === STORE_GALLERY_SHOW_MORE;
        });
    var context = {
        width: width,
        height: height,
        left: left,
        top: top,
        zindex: zindex || 0,
        components: components,
        columns: cols,
        rows: rows,
        fontOptions: fontfamilyvalues,
        taOptions: textalignvalues,
        bgColor: component.getProperty(BACKGROUND_COLOR).value,
        borderColor: component.getProperty(BORDER_COLOR).value,
        borderWidth: component.getProperty(BORDER_WIDTH).value,
        dividerColor: component.getProperty(DIVIDER_COLOR).value,
        dividerWidth: component.getProperty(DIVIDER_WIDTH).value,
        children: {}
    };
    if (storeShowMore) {
        context.showMore = ContextFactory.contextFor(storeShowMore, EDITOR_CONTEXT);
    }
    if (storeGalleryProduct) {
        context.product = ContextFactory.contextFor(storeGalleryProduct, EDITOR_CONTEXT),
        _.forEach(storeGalleryProduct.children,
            function (child) {
                context.children[child.proto.name] = ContextFactory.contextFor(child, EDITOR_CONTEXT);
                _.forEach(child.children,
                    function (com) {
                        context.children[com.proto.name] = ContextFactory.contextFor(com, EDITOR_CONTEXT);
                    });
            });
    }    
    return context;
}
var StoreGalleryShowMoreEditorContext = function(component) {
    var context = {
        text: component.getProperty(TEXT).value,
        bgcolor: component.getProperty(BACKGROUND_COLOR).value,
        bgcolorhover: component.getProperty(BACKGROUND_COLOR_HOVER).value,
        color: component.getProperty(COLOR).value,
        txtcolorhover: component.getProperty(TEXT_COLOR_HOVER).value,
        brcolor: component.getProperty(BORDER_COLOR).value,
        brcolorhover: component.getProperty(BORDER_COLOR_HOVER).value,
        borderWidth: component.getProperty(BORDER_WIDTH).value,
        borderRadius: component.getProperty(BORDER_RADIUS).value,
        ta: component.getProperty(TEXT_ALIGN).value,
        fontFamily: component.getProperty(FONT_FAMILY).value,
        fontSize: component.getProperty(FONT_SIZE).value
    };
    return context;
}
var StoreGalleryProductTitleEditorContext = function (component) {
    var context = {
        color: component.getProperty(COLOR).value,
        fontFamily: component.getProperty(FONT_FAMILY).value,
        fontSize: component.getProperty(FONT_SIZE).value
    };
    return context;
}
var StoreGalleryProductLabelEditorContext = function (component) {
    var context = {
        bgColor: component.getProperty(BACKGROUND_COLOR).value,
        color: component.getProperty(COLOR).value,
        fontFamily: component.getProperty(FONT_FAMILY).value,
        fontSize: component.getProperty(FONT_SIZE).value,
        position: component.getProperty(CAPTION_POSITION).value
    };
    return context;
}
var StoreGalleryProductPriceEditorContext = function (component) {
    var context = {
        color: component.getProperty(COLOR).value,
        outOfStockColor: component.getProperty(SECONDARY_COLOR).value,
        fontFamily: component.getProperty(FONT_FAMILY).value,
        fontSize: component.getProperty(FONT_SIZE).value
    };
    return context;
}
var StoreGalleryProductImageEditorContext = function (component) {
    var context = {
        ratio: component.getProperty(IMAGE_RATIO).value
    };
    return context;
}
var StoreGalleryProductDescriptionEditorContext = function (component) {
    var context = {
        color: component.getProperty(COLOR).value,
        fontFamily: component.getProperty(FONT_FAMILY).value,
        fontSize: component.getProperty(FONT_SIZE).value
    };
    return context;
}
var StoreGalleryProductEditorContext = function(component) {
    var context = {
        layout: component.getProperty(LAYOUT).value,
        ta: component.getProperty(TEXT_ALIGN).value,
        hoverStyle: component.getProperty(HOVER_STYLE).value
    };
    return context;
}

var StoreGalleryViewerContext = function (component, properties) {
    return {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        dividerColor: component.getProperty(DIVIDER_COLOR).value,
        dividerWidth: component.getProperty(DIVIDER_WIDTH).value,
        col: component.getProperty(COLUMNS).value,
        row: component.getProperty(ROWS).value
    };
}

var StoreGalleryShowMoreViewerContext = function (component, properties) {
    return {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        text: component.getProperty(TEXT).value
    };
}

var StoreGalleryProductViewerContext = function (component, properties) {   
    return {
        id: component.id,
        styles: properties.where({ group: 'style' }),               
        hoverStyle: component.getProperty(HOVER_STYLE).value
    };
}

var StoreGalleryProductLabelViewerContext = function(component, properties) {
    return {
        id: component.id,
        styles: properties.where({ group: 'style' }),        
        location: component.getProperty(CAPTION_POSITION).value
    };
}

var StoreGalleryProductPriceViewerContext = function (component, properties) {
    return {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        secondaryColor: component.getProperty(SECONDARY_COLOR).value
    };
}

var StoreGalleryProductImageViewerContext = function (component, properties) {	
    return {
        id: component.id,
        styles: properties.where({ group: 'style' }),        
        ratio: component.getProperty(IMAGE_RATIO).value
    };
}

var ManageStoreProductsCategoryViewerContext = function (component, properties) {
    var products = [];
    products.push(
        {
            src: 'http://127.0.0.1:10000/devstoreaccount1/resources-ade0e120-464a-4007-b31d-34b13bf5b2c2/de346cd6-bb6f-463c-8d8d-82780f7bd67d.jpg',
            title: 'Zalf bloom chair'
        },
         {
             src: 'http://127.0.0.1:10000/devstoreaccount1/resources-ade0e120-464a-4007-b31d-34b13bf5b2c2/a6e781c0-1784-4371-9625-98e9cdc8cf7c.jpg',
             title: 'Zalf set chair'
         });

    return {
        id: component.id,
        products: products
    };
}

var ManageStoreProductsAddingProductsToCategoryViewerContext = function (component, properties) {
    var products = [];
    products.push(
        {
            src: 'http://127.0.0.1:10000/devstoreaccount1/resources-ade0e120-464a-4007-b31d-34b13bf5b2c2/de346cd6-bb6f-463c-8d8d-82780f7bd67d.jpg',
            title: 'Zalf bloom chair'
        },
         {
             src: 'http://127.0.0.1:10000/devstoreaccount1/resources-ade0e120-464a-4007-b31d-34b13bf5b2c2/a6e781c0-1784-4371-9625-98e9cdc8cf7c.jpg',
             title: 'Zalf set chair'
         });

    return {
        id: component.id,
        products: products
    };
}

var MenuComponentEditorContext = function (component) {
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;
    var zindex = component.getProperty(Z_INDEX).value;

    var predefined = component.getProperty(PREDEFINED).value;

    var bgcolor = component.getProperty(BACKGROUND_COLOR).value;
    var bghovercolor = component.getProperty(BACKGROUND_COLOR_HOVER).value;

    var borderColor = component.getProperty(BORDER_COLOR).value;
    var borderColorHover = component.getProperty(BORDER_COLOR_HOVER).value;
    var br = component.getProperty(BORDER_RADIUS).value;
    var bw = component.getProperty(BORDER_WIDTH).value;

    var txthovercolor = component.getProperty(TEXT_COLOR_HOVER).value;
    var color = component.getProperty(COLOR).value;
    var font = component.getProperty(FONT_FAMILY).value;
    var fs = component.getProperty(FONT_SIZE).value;
    var stretchToFullWidth = component.getProperty(STRETCH_TO_FULL_WIDTH).value.toBoolean();
    var mw = component.getProperty(MARGINS_WIDTH).value;

    var offsetX = component.getProperty(OFFSET_X).value;
    var offsetY = component.getProperty(OFFSET_Y).value;
    var positionClass = component.getProperty(FIXED_LOCATION).value;
    var isPined = component.getProperty(IS_PINED).value.toBoolean();

    var menuPredefinedClasses = [
        { "name": "simple", "value": "simple" },
        { "name": "vertical", "value": "vertical" }
    ];
    var currentMenu = menuPredefinedClasses.where({ value: predefined }).firstOrDefault();
    currentMenu.selected = true;

    var menuPredefinedFonts = ContextFactory.getComponentFonts();

    var currentFont = menuPredefinedFonts.where({ value: font.toLowerCase() }).firstOrDefault();
    currentFont.selected = true;

    var context = {
        width: width,
        height: height,
        left: left,
        top: top,
        zindex: zindex || 0,
        fontSize: fs,
        borderRadius: br,
        borderWidth: bw,
        styleOptions: menuPredefinedClasses,
        fontOptions: menuPredefinedFonts,
        color: color,
        bgcolor: bgcolor,
        bgcolorhover: bghovercolor,
        txtcolorhover: txthovercolor,
        borderColorInput: borderColor,
        brcolorhover: borderColorHover,
        offsetX: offsetX,
        offsetY: offsetY,
        positionClass: positionClass,
        isPined: isPined,
        marginsWidth: mw,
        stretchToFullWidth: stretchToFullWidth
    }

    return context;
}

var MenuComponentViewerContext = function (component, properties) {

    var positionElement = component.getProperty(FIXED_LOCATION);
    var isPined = component.getProperty(IS_PINED);

	eventsystem.subscribe('/component/stretch/', function () {
	    component.stretcher();
	});
    var context = {
        styles: properties.where({ group: 'style' }),
        id: component.id,
        isPined: isPined.value.toBoolean(),
        positionClass: positionElement ? positionElement.value : 'default'
    };
    return context;
}

var AnchorComponentEditorContext = function (component) {
    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;
    var name = component.getProperty(NAME).value;
	var zindex = component.getProperty(Z_INDEX).value;

    var context = {
        left: left,
        top: top,
        name: name,
        zindex: zindex || 0
    };

    return context;
}

var AnchorComponentViewerContext = function (component, properties) {
    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        isDockable: component.isDockable ? "dockable" : ""
    };
    return context;
}

var MortgageCalculatorComponentEditorContext = function (component) {
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;
	var zindex = component.getProperty(Z_INDEX).value;       

    var brcolor = component.getProperty(BORDER_COLOR).value;
    var bw = component.getProperty(BORDER_WIDTH).value;
    var br = component.getProperty(BORDER_RADIUS).value;

    var color = component.getProperty(COLOR).value;
    var ff = component.getProperty(FONT_FAMILY).value;
    var fs = component.getProperty(FONT_SIZE).value;
    var title = component.getProperty(TITLE).value;

    var fontfamilyvalues = ContextFactory.getComponentFonts();
    fontfamilyvalues.forEach(function (item) {
        item.selected = item.value == ff;
    });
    
    var context = {
        color: color,
        width: width,

        height: height,
        left: left,
        top: top,

        borderWidth: bw,
        borderRadius: br,

        brcolor: brcolor,
        fontSize: fs,
        fontOptions: fontfamilyvalues,
        title: title,
        zindex: zindex || 0
    };
    return context;
}

var MortgageCalculatorComponentViewerContext = function (component, properties) {
    var title = component.getProperty(TITLE).value;

    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        title:title
    };
    return context;
}

var ContactUsComponentEditorContext = function (component) {
    var type = component.getProperty(TYPE).value;

    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;
	var zindex = component.getProperty(Z_INDEX).value;

    var title = component.getProperty(TITLE).value;
    var color = component.getProperty(COLOR).value;
    var ff = component.getProperty(FONT_FAMILY).value;
    var fs = component.getProperty(FONT_SIZE).value;

    var bgcolor = component.getProperty(BACKGROUND_COLOR).value;

    var brcolor = component.getProperty(BORDER_COLOR).value;
    var bw = component.getProperty(BORDER_WIDTH).value;
    var br = component.getProperty(BORDER_RADIUS).value;

    var mail = component.getProperty(MODE_VALUE).value;
    var formSubject = component.getProperty(FORM_SUBJECT).value;
    var useCaptcha = component.getProperty(USE_CAPTCHA).value.toBoolean();

    var fontfamilyvalues = ContextFactory.getComponentFonts();

    fontfamilyvalues.forEach(function (item) {
        item.selected = item.value === ff;
    });

    var contactUsTypes = [
    { "name": "Request a quote", "value": "Request a quote" },
    { "name": "Make an Appointment", "value": "Make an Appointment" }
    ];

    var currentUsType = contactUsTypes.where({ value: type });
    if (currentUsType.any()) {
        currentUsType = currentUsType.firstOrDefault();
        currentUsType.selected = true;
    }

    var successPageMasterLink = component.getProperty(SUCCESS_PAGE_MASTER_LINK).value;
    var successPageComponent = (successPageMasterLink && successPageMasterLink !== component.id)
        ? UI.siteComponentRepository.lookupData({ id: successPageMasterLink })
        : component;

    // property for success page
    var successPageMessage = successPageComponent.getProperty(SUCCESS_PAGE_MESSAGE).value;
    var successPageLinkRedirectButton = successPageComponent.getProperty(SUCCESS_PAGE_LINK_REDIRECT_BUTTON).value;
    var successPageContentColor = successPageComponent.getProperty(SUCCESS_PAGE_CONTENT_COLOR).value;
    var successPageTextColor = successPageComponent.getProperty(SUCCESS_PAGE_TEXT_COLOR).value;
    var successPageTextFontFamily = successPageComponent.getProperty(SUCCESS_PAGE_TEXT_FONT_FAMILY).value;
    var successPageTextFontSize = successPageComponent.getProperty(SUCCESS_PAGE_TEXT_FONT_SIZE).value;
    var successPageImageLogo = successPageComponent.getProperty(SUCCESS_PAGE_IMAGE_LOGO).value;
    var successPageHeaderColor = successPageComponent.getProperty(SUCCESS_PAGE_HEADER_COLOR).value;
    var successPageButtonColor = successPageComponent.getProperty(SUCCESS_PAGE_BUTTON_COLOR).value;
    var successPageHeaderText = successPageComponent.getProperty(SUCCESS_PAGE_HEADER_TEXT).value;
    var successPageButtonText = successPageComponent.getProperty(SUCCESS_PAGE_BUTTON_TEXT).value;

    if (successPageImageLogo === "") {
        successPageImageLogo = SUCCESS_LOGO_IMAGE;
    }

    var ffvalues = ContextFactory.getComponentFonts();

    ffvalues.forEach(function (item) {
        item.selected = item.value === successPageTextFontFamily;
    });

    var parentPages = ContextFactory.pages(successPageLinkRedirectButton);
    // property for success page

    var fields = ContextFactory.getContactUsFields(component);
    var mailHistory = component.getProperty(MAIL_HISTORY_PROPERTY).value.toBoolean();

    var context = {
        fields: _.filter(fields, function (item) { return !item.hideForEditor; }),
        color: color,
        width: width,

        height: height,
        left: left,
        top: top,
        useCaptcha: useCaptcha,
        borderWidth: bw,
        borderRadius: br,
        backgroundColor: bgcolor,
        mailHistory: mailHistory,

        brcolor: brcolor,
        fontSize: fs,
        fontOptions: fontfamilyvalues,
        email: mail,
        options: contactUsTypes,
        title: title,

        formSubject: formSubject,
        
        successPageMessage: successPageMessage,
        successPageParentPages: parentPages,
        successPageContentColor: successPageContentColor,
        successPageTextColor: successPageTextColor,
        successPageTextFontFamily: successPageTextFontFamily,
        successPageTextFontSize: successPageTextFontSize,
        successPageImageLogo: successPageImageLogo,
        successPageHeaderColor: successPageHeaderColor,
        successPageButtonColor: successPageButtonColor,
        successPageHeaderText: successPageHeaderText,
        successPageButtonText: successPageButtonText,
        successPageFFValues: ffvalues,
        zindex: zindex || 0,

        useSuccessPageAnyway: successPageMasterLink
    };
    return context;
}

ContextFactory.getContactUsFields = function (component) {
    var fields = [];
    var type = component.getProperty(TYPE).value;
    if (type == 'Request a quote') {
        fields = [
            {
                selector: "input[name='NA']",
                title: 'Name',
                required: false
            }, {
                selector: "input[name='DP']",
                title: 'Day Phone',
                required: false
            }, {
                selector: "input[name='EP']",
                title: 'Evening Phone',
                required: false
            }, {
                selector: "input[name='EM']",
                title: 'Email',
                required: false
            }, {
                selector: "textarea[name='CM']",
                title: 'Comments',
                required: false
            }
        ];
    } else {
        fields = [
            {
                selector: "input[name='NA']",
                title: 'Name',
                required: false
            }, {
                selector: "input[name='PH']",
                title: 'Phone',
                required: false
            }, {
                selector: "input[name='EM']",
                title: 'Email',
                required: false
            },
            {
                selector: "select[name='BTTC']",
                title: 'Best Time To Call',
                required: false,
                hideForEditor: true
            },
            {
                selector: "input[name='IA']",
                title: 'Inspection Address',
                required: false
            },
            {
                selector: "input[name='CSZ']",
                title: 'City, ST, ZIP',
                required: false
            },
            {
                selector: "select[name='PRT']",
                title: 'Property Type',
                required: false,
                hideForEditor: true
            },
            {
                selector: "input[name='SF']",
                title: 'Square Footage',
                required: false
            },
            {
                selector: "input[name='YB']",
                title: 'Year Built',
                required: false
            },
            {
                selector: "select[name='FT']",
                title: 'Foundation Type',
                required: false,
                hideForEditor: true
            },
            {
                selector: "select[name='PT']",
                title: 'Pool Type',
                required: false,
                hideForEditor: true
            },
            {
                selector: "textarea[name='CM']",
                title: 'Comments',
                required: false
            }
        ];
    }
    var requiredFields = component.getProperty(REQUIRED_FIELD).value.split(',');
    fields.forEach(function (field) {
        field.required = requiredFields.indexOf(field.selector) !== -1;
    });
    return fields;
}

var ContactUsComponentViewerContext = function (component, properties) {
    var title = component.getProperty(TITLE).value;
    var type = component.getProperty(TYPE).value;

    var requestQuote = false;
    var makeAppointment = false;

    switch (type) {
        case "Request a quote":
            requestQuote = true;
            break;
        case "Make an Appointment":
            makeAppointment = true;
            break;
    }

    var useCaptcha = component.getProperty(USE_CAPTCHA).value.toBoolean();

    var successPageMasterLink = component.getProperty(SUCCESS_PAGE_MASTER_LINK).value;
    var successPageComponent = (successPageMasterLink && successPageMasterLink !== component.id)
        ? UI.siteComponentRepository.lookupData({ id: successPageMasterLink })
        : component;

    // property for success page
    var successPageMessage = successPageComponent.getProperty(SUCCESS_PAGE_MESSAGE).value;
    var successPageLinkRedirectButton = successPageComponent.getProperty(SUCCESS_PAGE_LINK_REDIRECT_BUTTON).value;
    var successPageContentColor = successPageComponent.getProperty(SUCCESS_PAGE_CONTENT_COLOR).value;
    var successPageTextColor = successPageComponent.getProperty(SUCCESS_PAGE_TEXT_COLOR).value;
    var successPageTextFontFamily = successPageComponent.getProperty(SUCCESS_PAGE_TEXT_FONT_FAMILY).value;
    var successPageTextFontSize = successPageComponent.getProperty(SUCCESS_PAGE_TEXT_FONT_SIZE).value;
    var successPageImageLogo = successPageComponent.getProperty(SUCCESS_PAGE_IMAGE_LOGO).value;
    var successPageHeaderColor = successPageComponent.getProperty(SUCCESS_PAGE_HEADER_COLOR).value;
    var successPageButtonColor = successPageComponent.getProperty(SUCCESS_PAGE_BUTTON_COLOR).value;
    var successPageHeaderText = successPageComponent.getProperty(SUCCESS_PAGE_HEADER_TEXT).value;
    var successPageButtonText = successPageComponent.getProperty(SUCCESS_PAGE_BUTTON_TEXT).value;

    if (successPageImageLogo === "") {
        successPageImageLogo = SUCCESS_LOGO_IMAGE;
    }

    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        requestQuote: requestQuote,
        makeAppointment: makeAppointment,
        title: title,
        message: successPageMessage,
        link: successPageLinkRedirectButton,
        contentColor: successPageContentColor,
        textColor: successPageTextColor,
        ff: successPageTextFontFamily,
        fs: successPageTextFontSize,
        image: successPageImageLogo,
        headerColor: successPageHeaderColor,
        buttonColor: successPageButtonColor,
        headerText: successPageHeaderText,
        buttonText: successPageButtonText,
        hasCaptcha: useCaptcha
    };
    return context;
}

var EvaluateHomeComponentEditorContext = function (component) {
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
	var zindex = component.getProperty(Z_INDEX).value;

    var color = component.getProperty(COLOR).value;
    var brcolor = component.getProperty(BORDER_COLOR).value;
    var bgcolor = component.getProperty(BACKGROUND_COLOR).value;

    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;

    var ff = component.getProperty(FONT_FAMILY).value;

    var bw = component.getProperty(BORDER_WIDTH).value;
    var br = component.getProperty(BORDER_RADIUS).value;
    var fs = component.getProperty(FONT_SIZE).value;
    var mail = component.getProperty(MODE_VALUE).value;
    var title = component.getProperty(TITLE).value;
    var useCaptcha = component.getProperty(USE_CAPTCHA).value.toBoolean();

    var formSubject = component.getProperty(FORM_SUBJECT).value;

    var fontfamilyvalues = ContextFactory.getComponentFonts();


    fontfamilyvalues.forEach(function (item) {
        item.selected = item.value == ff;
    });
    
    var successPageMasterLink = component.getProperty(SUCCESS_PAGE_MASTER_LINK).value;
    var successPageComponent = (successPageMasterLink && successPageMasterLink !== component.id)
        ? UI.siteComponentRepository.lookupData({ id: successPageMasterLink })
        : component;

    // property for success page
    var successPageMessage = successPageComponent.getProperty(SUCCESS_PAGE_MESSAGE).value;
    var successPageLinkRedirectButton = successPageComponent.getProperty(SUCCESS_PAGE_LINK_REDIRECT_BUTTON).value;
    var successPageContentColor = successPageComponent.getProperty(SUCCESS_PAGE_CONTENT_COLOR).value;
    var successPageTextColor = successPageComponent.getProperty(SUCCESS_PAGE_TEXT_COLOR).value;
    var successPageTextFontFamily = successPageComponent.getProperty(SUCCESS_PAGE_TEXT_FONT_FAMILY).value;
    var successPageTextFontSize = successPageComponent.getProperty(SUCCESS_PAGE_TEXT_FONT_SIZE).value;
    var successPageImageLogo = successPageComponent.getProperty(SUCCESS_PAGE_IMAGE_LOGO).value;
    var successPageHeaderColor = successPageComponent.getProperty(SUCCESS_PAGE_HEADER_COLOR).value;
    var successPageButtonColor = successPageComponent.getProperty(SUCCESS_PAGE_BUTTON_COLOR).value;
    var successPageHeaderText = successPageComponent.getProperty(SUCCESS_PAGE_HEADER_TEXT).value;
    var successPageButtonText = successPageComponent.getProperty(SUCCESS_PAGE_BUTTON_TEXT).value;

    if (successPageImageLogo === "") {
        successPageImageLogo = SUCCESS_LOGO_IMAGE;
    }
    
    var ffvalues = ContextFactory.getComponentFonts();

    ffvalues.forEach(function (item) {
        item.selected = item.value === successPageTextFontFamily;
    });

    var parentPages = ContextFactory.pages(successPageLinkRedirectButton);
    // property for success page

    var fields = ContextFactory.getEvaluateHomeFields(component);
    var mailHistory = component.getProperty(MAIL_HISTORY_PROPERTY).value.toBoolean();

    var context = {
        fields: _.filter(fields, function(item) { return !item.hideForEditor; }),
        color: color,
        width: width,
        zindex: zindex || 0,

        height: height,
        left: left,
        top: top,

        mailHistory: mailHistory,

        borderWidth: bw,
        borderRadius: br,
        backgroundColor: bgcolor,
        useCaptcha: useCaptcha,
        brcolor: brcolor,
        fontSize: fs,
        fontOptions: fontfamilyvalues,
        email: mail,
        title: title,

        formSubject: formSubject,

        successPageMessage: successPageMessage,
        successPageParentPages: parentPages,
        successPageContentColor: successPageContentColor,
        successPageTextColor: successPageTextColor,
        successPageTextFontFamily: successPageTextFontFamily,
        successPageTextFontSize: successPageTextFontSize,
        successPageImageLogo: successPageImageLogo,
        successPageHeaderColor: successPageHeaderColor,
        successPageButtonColor: successPageButtonColor,
        successPageHeaderText: successPageHeaderText,
        successPageButtonText: successPageButtonText,
        successPageFFValues: ffvalues,

        useSuccessPageAnyway: successPageMasterLink
    };
    return context;
}

ContextFactory.getEvaluateHomeFields = function (component) {
    var fields = [
        {
            selector: "input[name='NA']",
            title: 'Name',
            required: false
        }, {
            selector: "input[name='EM']",
            title: 'Email',
            required: false
        }, {
            selector: "input[name='PN']",
            title: 'Phone Number',
            required: false
        }, {
            selector: "input[name='AD']",
            title: 'Address',
            required: false
        }, {
            selector: "input[name='CT']",
            title: 'City',
            required: false
        }, {
            selector: "input[name='ST']",
            title: 'State',
            required: false
        }, {
            selector: "input[name='ZC']",
            title: 'Zip-Code',
            required: false
        }, {
            selector: "input[name='BD']",
            title: 'Beds',
            required: false
        }, {
            selector: "input[name='BTH']",
            title: 'Baths',
            required: false
        }, {
            selector: "input[name='SF']",
            title: 'Square Feet',
            required: false
        }, {
            selector: "input[name='LS']",
            title: 'Lot Size',
            required: false
        }, {
            selector: "input[name='YB']",
            title: 'Year Built',
            required: false
        }
    ];
    var requiredFields = component.getProperty(REQUIRED_FIELD).value.split(',');
    fields.forEach(function (field) {
        field.required = requiredFields.indexOf(field.selector) !== -1;
    });
    return fields;
}

var EvaluateHomeComponentViewerContext = function (component, properties) {
    var title = component.getProperty(TITLE).value;
    var useCaptcha = component.getProperty(USE_CAPTCHA).value.toBoolean();

    var successPageMasterLink = component.getProperty(SUCCESS_PAGE_MASTER_LINK).value;
    var successPageComponent = (successPageMasterLink && successPageMasterLink !== component.id)
        ? UI.siteComponentRepository.lookupData({ id: successPageMasterLink })
        : component;

    // property for success page
    var successPageMessage = successPageComponent.getProperty(SUCCESS_PAGE_MESSAGE).value;
    var successPageLinkRedirectButton = successPageComponent.getProperty(SUCCESS_PAGE_LINK_REDIRECT_BUTTON).value;
    var successPageContentColor = successPageComponent.getProperty(SUCCESS_PAGE_CONTENT_COLOR).value;
    var successPageTextColor = successPageComponent.getProperty(SUCCESS_PAGE_TEXT_COLOR).value;
    var successPageTextFontFamily = successPageComponent.getProperty(SUCCESS_PAGE_TEXT_FONT_FAMILY).value;
    var successPageTextFontSize = successPageComponent.getProperty(SUCCESS_PAGE_TEXT_FONT_SIZE).value;
    var successPageImageLogo = successPageComponent.getProperty(SUCCESS_PAGE_IMAGE_LOGO).value;
    var successPageHeaderColor = successPageComponent.getProperty(SUCCESS_PAGE_HEADER_COLOR).value;
    var successPageButtonColor = successPageComponent.getProperty(SUCCESS_PAGE_BUTTON_COLOR).value;
    var successPageHeaderText = successPageComponent.getProperty(SUCCESS_PAGE_HEADER_TEXT).value;
    var successPageButtonText = successPageComponent.getProperty(SUCCESS_PAGE_BUTTON_TEXT).value;

    if (successPageImageLogo === "") {
        successPageImageLogo = SUCCESS_LOGO_IMAGE;
    }

    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        title: title,
        message: successPageMessage,
        link: successPageLinkRedirectButton,
        contentColor: successPageContentColor,
        textColor: successPageTextColor,
        ff: successPageTextFontFamily,
        fs: successPageTextFontSize,
        image: successPageImageLogo,
        headerColor: successPageHeaderColor,
        buttonColor: successPageButtonColor,
        headerText: successPageHeaderText,
        buttonText: successPageButtonText,
        hasCaptcha: useCaptcha
    };

    return context;
}

var HtmlContainerComponentEditorContext = function (component) {
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
	var zindex = component.getProperty(Z_INDEX).value;
    var bgcolor = component.getProperty(BACKGROUND_COLOR).value;
    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;
    var text = component.getProperty(TEXT).value;
    var bw = component.getProperty(BORDER_WIDTH).value;
    var bc = component.getProperty(BORDER_COLOR).value;

    var context = {
        bgcolor: bgcolor,
        zindex: zindex || 0,
        width: width,
        height: height,
        left: left,
        top: top,
        borderWidth: bw,
        brcolor: bc,
        text: text,
        isPreviewEnabled: component.isPreviewEnabled
    };
    return context;
}

var HtmlContainerComponentViewerContext = function (component, properties) {
    var text = component.getProperty(TEXT).value;
    var isPreview = false;

    if (UI.getSetting("ispreview") || !(new RegExp(REGEXP_CHECK_HTML_SCRIPT_AND_IFRAME_TAG).test(text))) {
        isPreview = true;
        component.isPreviewEnabled = true;
    }

    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        text: text,
        isPreview: isPreview || component.isPreviewEnabled
    };
    return context;
}

var VideoComponentViewerContext = function (component, properties) {
    var src = component.getProperty(SRC).value;
    var provider = component.getProperty(PROVIDER).value;    
    var loop = component.getProperty(LOOP).value.toBoolean();
    var rel = component.getProperty(REL).value.toBoolean();
    var autoplay = component.getProperty("autoplay").value.toBoolean();
    var params = [];
    var paramsString = "?";
    var youtube = false;
    var vimeo = false;
    switch (provider) {
        case "youtube":
            params.push("rel=" + (rel ? "0" : "1"));
            params.push("enablejsapi=" + 1);
            params.push("autoplay=0");//activate in MediaService.playVideo
            params.push("loop=" + (loop ? "1&playlist=" + src.substring(src.lastIndexOf('/') + 1) : "0"));
            paramsString += params.join('&');
            youtube = true;
            break;
        case "vimeo":
            vimeo = true;
            src = src.replace('http:', '');
            params.push("rel=" + (rel ? "0" : "1"));
            params.push("api=" + 1);
            params.push("autoplay=0");
            params.push("loop=" + (loop ? "1" : "0"));
            paramsString += params.join('&');
            break;
    }
    // change this to appropriate check for preview mode
    var viewer = UI.getSetting("ispreview");
    var context = {
        src: Helpers.convertToUniversalUrl(src),
        styles: properties.where({ group: 'style' }),
        id: component.id,
        viewer: viewer,
        youtube: youtube,
        vimeo: vimeo,
        paramsString: paramsString
    };
    return context;
}

var VideoComponentEditorContext = function (component) {
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
    var top = component.getProperty(TOP).value;
    var left = component.getProperty(LEFT).value;

    var zindex = component.getProperty(Z_INDEX).value;

    var rel = component.getProperty(REL).value.toBoolean();

    var src = component.getProperty(SRC).value;
    var provider = component.getProperty(PROVIDER).value;
    var autoplay = component.getProperty(AUTOPLAY).value.toBoolean();
    var loop = component.getProperty(LOOP).value.toBoolean();
    //var enablejsapi = component.getProperty(ENABLEJSAPI).value.toBoolean();
    //var api = component.getProperty(API).value.toBoolean();
    var providers = [
    { "name": "youtube", "value": "youtube" },
    { "name": "vimeo", "value": "vimeo" }
    ];
    var currentProvider = providers.where({ value: provider }).firstOrDefault();
    currentProvider.selected = true;
    var context = {
        width: width,
        height: height,
        top: top,
        left: left,
        src: Helpers.convertToUniversalUrl(src),
		zindex: zindex || 0,
        providerOptions: providers,
        autoplay: autoplay,
        loop: loop,
        rel: rel
    }
    return context;
}

var SoundComponentViewerContext = function (component, properties) {    
    var hide = component.getProperty(HIDE).value.toBoolean();
    var soundpause = component.getProperty(PAUSE).value;
    var title = component.getProperty(TITLE).value;
    var context = {
        hide: hide,
        viewer : UI.getSetting("ispreview"),
        styles: properties.where({ group: 'style' }),
        id: component.id,
        title: title,
        soundpause: soundpause
    };

    return context;
}

var SoundComponentEditorContext = function (component) {
    var top = component.getProperty(TOP).value;
    var left = component.getProperty(LEFT).value;
    var zindex = component.getProperty(Z_INDEX).value;
    var src = component.getProperty(SRC).value;
    var title = component.getProperty(TITLE).value;
    var autoplay = component.getProperty(AUTOPLAY).value.toBoolean();
    var hide = component.getProperty(HIDE).value.toBoolean();
    var soundpause = component.getProperty(PAUSE).value;
    var height = component.getProperty(HEIGHT).value;
    
    var context = {
        top: top,
        left: left,
        src: Helpers.convertToUniversalUrl(src),
        zindex: zindex || 0,
        autoplay: autoplay,
        hide: hide,
        soundpause: soundpause,
        title: title,
        height: height
    }
    return context;
}


var PanelComponentEditorContext = function (component) {
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
    var bgcolor = component.getProperty(BACKGROUND_COLOR).value;
    //var color = component.getProperty(COLOR).value
    var brcolor = component.getProperty(BORDER_COLOR).value;
    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;
	
    var zindex = component.getProperty(Z_INDEX).value;

    var offsetX = component.getProperty(OFFSET_X).value;
    var offsetY = component.getProperty(OFFSET_Y).value;
    var positionClass = component.getProperty(FIXED_LOCATION).value;
    var isPined = component.getProperty(IS_PINED).value.toBoolean();
    var stretchToFullWidth = component.getProperty(STRETCH_TO_FULL_WIDTH).value.toBoolean();

    //var ta = component.getProperty(TEXT_ALIGN).value
    var bw = component.getProperty(BORDER_WIDTH).value;
    var br = component.getProperty(BORDER_RADIUS).value;
    var mw = component.getProperty(MARGINS_WIDTH).value;
    //var fs = component.getProperty(FONT_SIZE).value
    //    var textalignvalues = mocktextalign;
    //    textalignvalues.forEach(function(item){
    //        item.taselected = item.value==ta;
    //    })
    var context = {
        bgcolor: bgcolor,
        width: width,
        height: height,
        left: left,
        top: top,
		zindex: zindex || 0,
        borderWidth: bw,
        borderRadius: br,
        brcolor: brcolor,
        offsetX: offsetX,
        offsetY: offsetY,
        positionClass: positionClass,
        isPined: isPined,
        marginsWidth: mw,
        stretchToFullWidth: stretchToFullWidth,
        noComponentEditor: !UI.settings.isComponentEditor
    };
    return context;
}

var PanelComponentViewerContext = function (component, properties) {

    eventsystem.subscribe('/component/stretch/', function () {
        component.stretcher();
    });

    var content = component.getProperty(CONTENT).value;
    var positionElement = component.getProperty(FIXED_LOCATION);
    var isPined = component.getProperty(IS_PINED);

    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        content: content,
        isDockable: component.isDockable ? "dockable" : "",
        isPined: isPined.value.toBoolean(),
        positionClass: positionElement ? positionElement.value : 'default'
    };
    return context;
}


var LinkComponentEditorContext = function (component) {
    return {};
}

var LinkComponentViewerContext = function (component, properties) {
    var text = component.getProperty(TEXT).value;

    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        text: text
    };
    return context;
}

var ImageComponentEditorContext = function (component) {
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
    var top = component.getProperty(TOP).value;
    var left = component.getProperty(LEFT).value;
    var alt = component.getProperty("alt").value;

	var zindex = component.getProperty(Z_INDEX).value;

    var src = component.getProperty(SRC).value;
    var isShowOptimized = component.getProperty(SHOW_OPTIMIZED).value.toBoolean();
    src = ContextFactory.prepareImgSrc(src, isShowOptimized);

    var srcHover = component.getProperty(IMAGE_ON_HOVER).value;
    var isShowOptimizedHover = component.getProperty(SHOW_OPTIMIZED_HOVER).value.toBoolean();
    srcHover = ContextFactory.prepareImgSrc(srcHover, isShowOptimizedHover);

    var srcPressed = component.getProperty(IMAGE_ON_PRESSED).value;
    var isShowOptimizedPressed = component.getProperty(SHOW_OPTIMIZED_PRESSED).value.toBoolean();
    srcPressed = ContextFactory.prepareImgSrc(srcPressed, isShowOptimizedPressed);

	var mw = component.getProperty(MARGINS_WIDTH).value;

    var predefined = component.getProperty(PREDEFINED).value;
    var imagePredefinedClasses = [
            { "name": "plain", "value": "plain" },
            { "name": "shadow", "value": "shadow" }
    ];
    var currentClass = imagePredefinedClasses.where({ value: predefined }).firstOrDefault();
    currentClass.selected = true;
    var linkContext = ContextFactory.linkManagementEditor(component);

    var offsetX = component.getProperty(OFFSET_X).value;
    var offsetY = component.getProperty(OFFSET_Y).value;
    var positionClass = component.getProperty(FIXED_LOCATION).value;
    var isPined = component.getProperty(IS_PINED).value.toBoolean();
    var stretchToFullWidth = component.getProperty(STRETCH_TO_FULL_WIDTH).value.toBoolean();
    var isPrimary = component.getProperty(IS_IMAGE_PRIMARY).value.toBoolean();

    var context = {
        isPrimary: isPrimary,
        width: width,
        height: height,
        top: top,
        left: left,
        zindex: zindex || 0,
        alt: alt,
        preview: src,
        styleOptions: imagePredefinedClasses,
        modeOptions: linkContext.linkModes,
        pageOptions: linkContext.pages,
        modeValue: linkContext.modeValue,
        anchors: linkContext.anchors,
        previewHover: srcHover,
        previewPressed: srcPressed,
        blankPage: linkContext.blankPage,
        offsetX: offsetX,
        offsetY: offsetY,
        positionClass: positionClass,
        isPined: isPined,
        marginsWidth: mw,
        stretchToFullWidth: stretchToFullWidth,
        isShowOptimized: isShowOptimized,
        isShowOptimizedHover: isShowOptimizedHover,
        isShowOptimizedPressed: isShowOptimizedPressed
    }
    return context;
}

var ImageComponentViewerContext = function (component, properties) {

    eventsystem.subscribe('/component/stretch/', function () {
        component.stretcher();
    });

    var linkContext = ContextFactory.linkManagementViewer(component);
           
    var src = component.getProperty(SRC).value;
    var isShowOptimized = component.getProperty(SHOW_OPTIMIZED).value.toBoolean();
    src = ContextFactory.prepareImgSrc(src, isShowOptimized);
    
    var alt = component.getProperty(ALT).value;
    var predefined = component.getProperty(PREDEFINED).value;
    var positionElement = component.getProperty(FIXED_LOCATION);
    var isPined = component.getProperty(IS_PINED);

    var context = {
        src: src,
        alt: alt,
        styles: properties.where({ group: 'style' }),
        id: component.id,
        predefined: predefined,
        href: linkContext.href,
        target: linkContext.target,
        isShowLink: linkContext.isShowLink,
        isPined: isPined.value.toBoolean(),
        positionClass: positionElement ? positionElement.value : 'default',
        isShowOptimized: isShowOptimized
    };
    return context;
}

function imageExists(image_url) {

    var http = new XMLHttpRequest();

    http.open('HEAD', image_url, false);
    http.send();

    return http.status != 404;

}

ContextFactory.prepareImgSrc = function (src, isShowOptimized) {
    var fileName = src.split('/').pop();
    var extension = fileName.split('.').pop();

    if (src === "") {
        src = NO_IMAGE_YET;
    }
    else if (isShowOptimized
            && src.indexOf(";base64") < 0 && extension !== "gif"
            && (src.indexOf(UI.getSetting("templateId")) > 0
                || (UI.getSetting("parentTemplateId")  != "" && src.indexOf(UI.getSetting("parentTemplateId"))) > 0)) {
        var newFileName = "optimized-" + fileName.replace(extension, "jpg");
        src = src.replace(fileName, newFileName);
    }
    
    //console.log("NewSrc:", src);
    return Helpers.convertToUniversalUrl(src);
};

var HeadingComponentEditorContext = function (component) {
    return {};
}

var HeadingComponentViewerContext = function (component, properties) {    
    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' })
    };
    return context;
}

var ButtonComponentEditorContext = function (component) {
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
    var bgcolor = component.getProperty(BACKGROUND_COLOR).value;
    var bghovercolor = component.getProperty(BACKGROUND_COLOR_HOVER).value;
    var brhovercolor = component.getProperty(BORDER_COLOR_HOVER).value;
    var color = component.getProperty(COLOR).value;

    var txthovercolor = component.getProperty(TEXT_COLOR_HOVER).value;

    var brcolor = component.getProperty(BORDER_COLOR).value;
    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;

	var zindex = component.getProperty(Z_INDEX).value;

    var ta = component.getProperty(TEXT_ALIGN).value;
    var ff = component.getProperty(FONT_FAMILY).value;
    var bw = component.getProperty(BORDER_WIDTH).value;
    var br = component.getProperty(BORDER_RADIUS).value;
    var fs = component.getProperty(FONT_SIZE).value;
    var text = component.getProperty(TEXT).value;

    var offsetX = component.getProperty(OFFSET_X).value;
    var offsetY = component.getProperty(OFFSET_Y).value;
    var positionClass = component.getProperty(FIXED_LOCATION).value;
    var isPined = component.getProperty(IS_PINED).value.toBoolean();

    var textalignvalues = [
    { name: "left", value: "left" },
    { name: "right", value: "right" },
    { name: "center", value: "center" },
    { name: "justify", value: "justify" }
    ];

    var fontfamilyvalues = ContextFactory.getComponentFonts();

    textalignvalues.forEach(function (item) {
        item.taselected = item.value == ta;
    });

    fontfamilyvalues.forEach(function (item) {
        item.selected = item.value == ff;
    });

    var linkContext = ContextFactory.linkManagementEditor(component);


    var context = {
        bgcolor: bgcolor,
        color: color,
        width: width,
        height: height,
        left: left,
        top: top,

		zindex: zindex || 0,

        borderWidth: bw,
        borderRadius: br,
        brcolor: brcolor,
        text: text,
        taOptions: textalignvalues,
        fontSize: fs,
        bgcolorhover: bghovercolor,
        brcolorhover: brhovercolor,
        fontOptions: fontfamilyvalues,
        txtcolorhover: txthovercolor,

        modeValue: linkContext.modeValue,
        modeOptions: linkContext.linkModes,
        pageOptions: linkContext.pages,
        blankPage: linkContext.blankPage,
        anchors: linkContext.anchors,

        offsetX: offsetX,
        offsetY: offsetY,
        positionClass: positionClass,
        isPined: isPined,
        noComponentEditor: !UI.settings.isComponentEditor
    };
    return context;
}
    
var ButtonComponentViewerContext = function (component, properties) {
    var text = component.getProperty(TEXT).value;

    var linkContext = ContextFactory.linkManagementViewer(component);

    var positionElement = component.getProperty(FIXED_LOCATION);
    var isPined = component.getProperty(IS_PINED);

    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        text: text,
        isShowLink: linkContext.isShowLink,
        href: linkContext.href,
        onclick: linkContext.target,
        isPined: isPined.value.toBoolean(),
        positionClass: positionElement ? positionElement.value : 'default'
    };
    return context;
}

var BloggingComponentEditorContext = function (component) {
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;
    var zindex = component.getProperty(Z_INDEX).value;

    var bgcolor = component.getProperty(BACKGROUND_COLOR).value;    
    var brcolor = component.getProperty(BORDER_COLOR).value;
    var color = component.getProperty(COLOR).value;
    
    var ff = component.getProperty(FONT_FAMILY).value;
    var bw = component.getProperty(BORDER_WIDTH).value;
    var br = component.getProperty(BORDER_RADIUS).value;
    var fs = component.getProperty(FONT_SIZE).value;
    var layout = component.getProperty(LAYOUT).value;
    
    var title = '';
    var servicePageId = component.getProperty(PARENT_PAGE).value;
    if (servicePageId) {
        var servicePage = UI.siteComponentRepository.lookupData({ id: servicePageId });
        if (servicePage) {
            title = servicePage.getProperty(TITLE).value;
        }
    }

    var fontfamilyvalues = ContextFactory.getComponentFonts();

    fontfamilyvalues.forEach(function (item) {
        item.selected = item.value === ff;
    });

    var context = {
        bgcolor: bgcolor,
        color: color,
        width: width,
        height: height,
        left: left,
        top: top,

        zindex: zindex || 0,
        title: title,
        borderWidth: bw,
        borderRadius: br,
        brcolor: brcolor,
        layout: layout,
        fontSize: fs,
        
        fontOptions: fontfamilyvalues
    };
    return context;
}
    
var BloggingComponentViewerContext = function (component, properties) {
    var layout = component.getProperty(LAYOUT).value;
    var pages = [];
    var servicePageId = component.getProperty(PARENT_PAGE).value;
    if (servicePageId) {
        var servicePage = UI.siteComponentRepository.lookupData({ id: servicePageId });
        if (servicePage) {
            pages = UI.siteComponentRepository.lookupDataByProperty({ 'parent-page': servicePageId })
                .where({ displayName: PAGE_COMPONENT }).map(function (item) {
                    var src = BLOGGING_EMPTY_IMAGE_URL;
                    //find image 
                    UI.siteComponentRepository.lookupDataSet({ displayName: IMAGE }, item).forEach(function (image) {
                        if (image.getProperty(IS_IMAGE_PRIMARY).value.toBoolean()) {
                            var isShowOptimized = image.getProperty(SHOW_OPTIMIZED).value.toBoolean();
                            src = ContextFactory.prepareImgSrc(image.getProperty(SRC).value, isShowOptimized);
                        }
                    });
                    return {
                        id: item.id,
                        imageSrc: src,
                        date: item.getProperty(META_DATE).value,
                        title: item.getProperty(TITLE).value,
                        href: Helpers.generateLinkToPage(item.getProperty(NAME).value, true),
                        description: item.getProperty(META_DESCRIPTION).value
                    }
                });
        }
    }

    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        layout: layout,
        pages: pages
    };
    return context;
}

var MapComponentEditorContext = function (component) {
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;
    var zindex = component.getProperty(Z_INDEX).value;

    var address = component.getProperty(TEXT).value;
    var label = component.getProperty(TITLE).value;

    var lat = component.getProperty(LATITUDE).value;
    var lng = component.getProperty(LONGITUDE).value;

    var showMapType = component.getProperty(SHOW_MAP_TYPE).value.toBoolean();
    var showZoom = component.getProperty(SHOW_ZOOM).value.toBoolean();
    var mapInteractive = component.getProperty(MAP_INTERACTIVE).value.toBoolean();
    var showStreetView = component.getProperty(SHOW_STREET_VIEW).value.toBoolean();


    var context = {
        width: width,
        height: height,
        left: left,
        top: top,
        zindex: zindex || 0,

        text: address,
        title: label,

        lat: lat,
        lng: lng,
        showMapType: showMapType,
        showZoom: showZoom,
        mapInteractive: mapInteractive,
        showStreetView: showStreetView
    };
    return context;
}
    
var MapComponentViewerContext = function (component, properties) {

    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' })
    };
    return context;
}

/* signin */
var SignInComponentEditorContext = function (component) {
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
    var bgcolor = component.getProperty(BACKGROUND_COLOR).value;
    var bghovercolor = component.getProperty(BACKGROUND_COLOR_HOVER).value;
    var brhovercolor = component.getProperty(BORDER_COLOR_HOVER).value;
    var color = component.getProperty(COLOR).value;

    var brcolor = component.getProperty(BORDER_COLOR).value;
    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;

    var zindex = component.getProperty(Z_INDEX).value;

    var ta = component.getProperty(TEXT_ALIGN).value;
    var ff = component.getProperty(FONT_FAMILY).value;
    var bw = component.getProperty(BORDER_WIDTH).value;
    var br = component.getProperty(BORDER_RADIUS).value;
    var fs = component.getProperty(FONT_SIZE).value;
    var text = component.getProperty(TEXT).value;

    var textalignvalues =
    [
        { name: "left", value: "left" },
        { name: "right", value: "right" },
        { name: "center", value: "center" },
        { name: "justify", value: "justify" }
    ];

    var fontfamilyvalues = ContextFactory.getComponentFonts();

    textalignvalues.forEach(function (item) {
        item.taselected = item.value === ta;
    });

    fontfamilyvalues.forEach(function (item) {
        item.selected = item.value === ff;
    });

    var context = {
        id: component.id,

        bgcolor: bgcolor,
        color: color,
        width: width,
        height: height,
        left: left,
        top: top,

        zindex: zindex || 0,

        borderWidth: bw,
        borderRadius: br,
        brcolor: brcolor,
        text: text,
        taOptions: textalignvalues,
        fontSize: fs,
        bgcolorhover: bghovercolor,
        brcolorhover: brhovercolor,
        fontOptions: fontfamilyvalues
    };

    return context;
};

var SignInComponentViewerContext = function (component, properties) {
    var text = component.getProperty(TEXT).value;

    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        text: text
    };
    return context;
};


var ParagraphComponentEditorContext = function (component) {
    var text = component.getProperty(TEXT).value;

    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;
    var zindex = component.getProperty(Z_INDEX).value;


    var bw = component.getProperty(BORDER_WIDTH).value;
    var br = component.getProperty(BORDER_RADIUS).value;
    var brcolor = component.getProperty(BORDER_COLOR).value;

    var bgcolor = component.getProperty(BACKGROUND_COLOR).value;

    var linkContext = ContextFactory.linkManagementEditor(component);
    
    var context = {
        text: text,
        width: width,
        height: height,
        left: left,
        top: top,
        zindex: zindex || 0,
        borderWidth: bw,
        borderRadius: br,
        brcolor: brcolor,
        bgcolor: bgcolor,

        modeValue: linkContext.modeValue,
        modeOptions: linkContext.linkModes,
        pageOptions: linkContext.pages,
        anchors: linkContext.anchors,
        blankPage: linkContext.blankPage,
        noComponentEditor: !UI.settings.isComponentEditor
    };
    return context;
}

var ParagraphComponentViewerContext = function (component, properties) {
    var text = component.getProperty(TEXT).value;
    
    var linkContext = ContextFactory.linkManagementViewer(component);
    
    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        text: text,
        isShowLink: linkContext.isShowLink,
        href: linkContext.href,
        target: linkContext.target
    };
    return context;
}


var HeadertextComponentEditorContext = function (component) {
    var text = component.getProperty(TEXT).value;
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;
    var zindex = component.getProperty(Z_INDEX).value;
    var bw = component.getProperty(BORDER_WIDTH).value;
    var br = component.getProperty(BORDER_RADIUS).value;
    var brcolor = component.getProperty(BORDER_COLOR).value;
    var bgcolor = component.getProperty(BACKGROUND_COLOR).value;

    var linkContext = ContextFactory.linkManagementEditor(component);

    var context = {
        text: text,

        width: width,
        height: height,
        left: left,
        top: top,
        zindex: zindex || 0,
        borderWidth: bw,
        borderRadius: br,
        brcolor: brcolor,
        bgcolor: bgcolor,
        
        modeValue: linkContext.modeValue,
        modeOptions: linkContext.linkModes,
        pageOptions: linkContext.pages,
        anchors: linkContext.anchors,
        blankPage: linkContext.blankPage,
        noComponentEditor: !UI.settings.isComponentEditor
    };
    return context;
}

var HeadertextComponentViewerContext = function (component, properties) {
    var text = component.getProperty(TEXT).value;

    var linkContext = ContextFactory.linkManagementViewer(component);
        
    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        text: text,
        isShowLink: linkContext.isShowLink,
        href: linkContext.href,
        target: linkContext.target
    };
    return context;
}

var GalleryComponentEditorContext = function (component) {
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
    var top = component.getProperty(TOP).value;
    var left = component.getProperty(LEFT).value;
   
	var zindex = component.getProperty(Z_INDEX).value;

    var type = component.getProperty(TYPE).value;
    var columnCount = component.getProperty(COLUMNS).value;
    var expand = component.getProperty(EXPAND).value.toBoolean();
    //var setlink = component.getProperty(SET_IMAGE_LINK).value.toBoolean();
    
    var color = component.getProperty(IMAGE_SETTINGS_COLOR).value;
    var headercolor = component.getProperty(IMAGE_SETTINGS_HEADERCOLOR).value;
    var bgcolor = component.getProperty(IMAGE_SETTINGS_BGCOLOR).value;
    var fs = component.getProperty(FONT_SIZE).value;

    var predefined = component.getProperty(PREDEFINED).value;
    var mw = component.getProperty(MARGINS_WIDTH).value;

    var imagestretching = component.getProperty(IMAGE_STRETCHING).value;
    var imagestretchvalues = [{ name: "crop", value: "crop" },{ name: "fill", value: "fill" }];



    imagestretchvalues.forEach(function (item) {
    	item.siselected = item.value === imagestretching;
    });

    var ff = component.getProperty(FONT_FAMILY).value;

    var fontfamilyvalues = ContextFactory.getComponentFonts();
    fontfamilyvalues.forEach(function (item) {
        item.selected = item.value == ff;
    });

    var imagePredefinedClasses = [
            { "name": "plain", "value": "plain" },
            { "name": "shadow", "value": "shadow" }
    ];

    var currentClass = imagePredefinedClasses.where({ value: predefined }).firstOrDefault();
    
    var galleryTypes = [
        { "name": "simple", "value": "simple" },
        { "name": "with captions", "value": "with-captions" }
    ];
    var currentGalleryType = galleryTypes.where({ value: type });
    if (currentGalleryType.any()) {
        currentGalleryType = currentGalleryType.firstOrDefault();
        currentGalleryType.selected = true;
    }

    var isShowOptimized = component.getProperty(SHOW_OPTIMIZED).value.toBoolean();

    var galleryItems = [];
    component.children.forEach(function (item) {

        galleryItems.push({
            id: item.id,
            src: ContextFactory.prepareImgSrc(item.getProperty(SRC).value, isShowOptimized),
            order: item.getProperty(ORDER).value.toInteger()
        });
    });

    var linkContext = ContextFactory.linkManagementEditor(component);
    var captionPosition = component.getProperty(CAPTION_POSITION).value;
    var captionOptions = ContextFactory.getSlideshowCaptionPositionList(captionPosition);

    currentClass.selected = true;

    var stretchToFullWidth = component.getProperty(STRETCH_TO_FULL_WIDTH).value.toBoolean();

    var context = {
        width: width,
        height: height,
        top: top,
        left: left,
		zindex: zindex || 0,
        galleryItems: galleryItems.orderBy(ORDER),
        options: galleryTypes,
        columnCount: columnCount,
        expand: expand,
        modeOptions: linkContext.linkModes,
        pageOptions: linkContext.pages,
        anchors: linkContext.anchors,
        //setlink: setlink,
        fontSize: fs,
        color: color,
        headercolor: headercolor,
        bgcolor: bgcolor,
        isOptions: imagestretchvalues,
        styleOptions: imagePredefinedClasses,
        fontOptions: fontfamilyvalues,
        cpOptions: captionOptions,
        marginsWidth: mw,
        stretchToFullWidth: stretchToFullWidth,
        isShowOptimized: isShowOptimized
    }
    return context;
}

var GalleryComponentViewerContext = function (component, properties) {

    eventsystem.subscribe('/component/stretch/', function () {
        component.stretcher();
    });

    var type = component.getProperty(TYPE).value;
    var columnCount = component.getProperty(COLUMNS).value;
    var expand = component.getProperty(EXPAND).value.toBoolean();
    var simpleWithCaptions = false;
    switch (type) {
        case "with-captions":
            simpleWithCaptions = true;
            break;
    }
    var galleryItems = [];

    var stretchingVal = component.getProperty(IMAGE_STRETCHING).value;
    var captionPosition = component.getProperty(CAPTION_POSITION).value;
    var infoBgColor = component.getProperty(IMAGE_SETTINGS_BGCOLOR).value;
    var titleColor = component.getProperty(IMAGE_SETTINGS_HEADERCOLOR).value;
    var descrColor = component.getProperty(IMAGE_SETTINGS_COLOR).value;
    var isShowOptimized = component.getProperty(SHOW_OPTIMIZED).value.toBoolean();
            
    component.children.forEach(function (item) {
        
        var linkContext = ContextFactory.linkManagementViewer(item);

        galleryItems.push({
            id: item.id,
            src: ContextFactory.prepareImgSrc(item.getProperty(SRC).value, isShowOptimized),
            title: item.getProperty(TITLE).value,
            description: item.getProperty(DESCRIPTION).value,
            order: item.getProperty(ORDER).value.toInteger(),
            columnCount: "c" + columnCount,
            simpleWithCaptions: simpleWithCaptions,

            expand: linkContext.href == '' ? expand : true,//TODO: expand fix for old logic
            href: linkContext.href,
            onclick: linkContext.target,
            isShowLink: linkContext.isShowLink,
            marginbottom: item.getProperty(GALLERY_ITEM_BOTTOM_MARGIN).value,
            predefined: component.getProperty(PREDEFINED).value,
            infoBgColor: infoBgColor,
            titleColor: titleColor,
            descrColor: descrColor,
            isCrop: stretchingVal === 'crop',
            captionPosition: captionPosition
        });
    });
       
    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        galleryItems: galleryItems.orderBy(ORDER),
        predefined: component.getProperty(PREDEFINED).value,
        isShowOptimized: isShowOptimized
    };
    return context;
}

var HousePhotoTourComponentEditorContext = function (component) {
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
    var top = component.getProperty(TOP).value;
    var left = component.getProperty(LEFT).value;

    var zindex = component.getProperty(Z_INDEX).value;

    var color = component.getProperty(IMAGE_SETTINGS_COLOR).value;
    var headercolor = component.getProperty(IMAGE_SETTINGS_HEADERCOLOR).value;
    var bgcolor = component.getProperty(BACKGROUND_COLOR).value;
    var fs = component.getProperty(FONT_SIZE).value;
    var ff = component.getProperty(FONT_FAMILY).value;

    var ta = component.getProperty(TEXT_ALIGN).value;
    var mw = component.getProperty(MARGINS_WIDTH).value;
    var expand = component.getProperty(EXPAND).value.toBoolean();

    var tavalues = [
        { name: "left", value: "left" },
        { name: "right", value: "right" },
        { name: "center", value: "center" },
        { name: "justify", value: "justify" }
    ];

    tavalues.forEach(function (item) {
        item.selected = item.value == ta;
    });

    var location = component.getProperty(LOCATION).value;

    var locationvalues = [
        { name: "left", value: "left" },
        { name: "right", value: "right" }
    ];

    locationvalues.forEach(function (item) {
        item.selected = item.value == location;
    });

    var isShowOptimized = component.getProperty(SHOW_OPTIMIZED).value.toBoolean();

    var galleryItems = [];
    component.children.forEach(function (item) {

        galleryItems.push({
            id: item.id,
            src: ContextFactory.prepareImgSrc(item.getProperty(SRC).value, isShowOptimized),
            order: item.getProperty(ORDER).value.toInteger()
        });
    });

    var imagestretching = component.getProperty(IMAGE_STRETCHING).value;
    var imagestretchvalues = [
    { name: "crop", value: "crop" },
    { name: "fill", value: "fill" }
    ];

    imagestretchvalues.forEach(function (item) {
        item.siselected = item.value === imagestretching;
    });

    var fontfamilyvalues = ContextFactory.getComponentFonts();
    fontfamilyvalues.forEach(function (item) {
        item.selected = item.value === ff;
    });

    var textbgcolor = component.getProperty(PHOTO_TOUR_TEXT_COLOR).value;

    var captionPosition = component.getProperty(CAPTION_POSITION).value;    
    var captionOptions = ContextFactory.getSlideshowCaptionPositionList(captionPosition);
    var stretchToFullWidth = component.getProperty(STRETCH_TO_FULL_WIDTH).value.toBoolean();
    var linkContext = ContextFactory.linkManagementEditor(component);

    var context = {
        width: width,
        height: height,
        top: top,
        left: left,
        zindex: zindex || 0,
        galleryItems: galleryItems.orderBy(ORDER),
        isOptions: imagestretchvalues,
        fontSize: fs,
        color: color,
        headercolor: headercolor,
        bgcolor: bgcolor,
        fontOptions: fontfamilyvalues,
        taoptions: tavalues,
        textbgcolor: textbgcolor,
        locationoptions: locationvalues,
        cpOptions: captionOptions,
        marginsWidth: mw,
        stretchToFullWidth: stretchToFullWidth,
        expand: expand,
        modeOptions: linkContext.linkModes,
        pageOptions: linkContext.pages,
        anchors: linkContext.anchors,
        stretchToFullWidth: stretchToFullWidth,
        isShowOptimized: isShowOptimized
    }
    return context;
}

var HousePhotoTourComponentViewerContext = function (component, properties) {

    eventsystem.subscribe('/component/stretch/', function () {
        component.stretcher();
    });

    var galleryItems = [];
    var stretchingVal = component.getProperty(IMAGE_STRETCHING).value;
    var captionPosition = component.getProperty(CAPTION_POSITION).value;
    var infoBgColor = component.getProperty(PHOTO_TOUR_TEXT_COLOR).value;
    var titleColor = component.getProperty(IMAGE_SETTINGS_HEADERCOLOR).value;
    var descrColor = component.getProperty(IMAGE_SETTINGS_COLOR).value;
    var expand = component.getProperty(EXPAND).value.toBoolean();
    var isShowOptimized = component.getProperty(SHOW_OPTIMIZED).value.toBoolean();

    component.children.forEach(function (item) {
        var linkContext = ContextFactory.linkManagementViewer(item);

        galleryItems.push({
            id: item.id,
            src: ContextFactory.prepareImgSrc(item.getProperty(SRC).value, isShowOptimized),
            title: item.getProperty(TITLE).value,
            description: item.getProperty(DESCRIPTION).value,
            order: item.getProperty(ORDER).value.toInteger(),
            infoBgColor: infoBgColor,
            titleColor: titleColor,
            descrColor: descrColor,
            isCrop: stretchingVal === 'crop',
            captionPosition: captionPosition,
            expand: linkContext.href == '' ? expand : true,//TODO: expand fix for old logic
            href: linkContext.href,
            onclick: linkContext.target,
            captionPosition: captionPosition,
            isShowOptimized: isShowOptimized
        });
    });
    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        galleryItems: galleryItems.orderBy(ORDER),
        expand: expand
    };
    return context;
}

var SlideshowComponentEditorContext = function (component) {
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
    var top = component.getProperty(TOP).value;
    var left = component.getProperty(LEFT).value;

	var zindex = component.getProperty(Z_INDEX).value;

    var pauseOnHover = component.getProperty(PAUSE).value.toBoolean();
    var nav = component.getProperty(NAV).value.toBoolean();
    var dot = component.getProperty(DOT).value.toBoolean();
    var caption = component.getProperty(CAPTION).value.toBoolean();
    var interval = component.getProperty(INTERVAL).value.toInteger();
    var imagestretching = component.getProperty(IMAGE_STRETCHING).value;
    var expand = component.getProperty(EXPAND).value.toBoolean();


    var color = component.getProperty(IMAGE_SETTINGS_COLOR).value;
    var headercolor = component.getProperty(IMAGE_SETTINGS_HEADERCOLOR).value;
    var bgcolor = component.getProperty(IMAGE_SETTINGS_BGCOLOR).value;
    var fs = component.getProperty(FONT_SIZE).value;
    var ff = component.getProperty(FONT_FAMILY).value;
    var mw = component.getProperty(MARGINS_WIDTH).value;

    var isShowOptimized = component.getProperty(SHOW_OPTIMIZED).value.toBoolean();

    var galleryItems = [];
    component.children.forEach(function (item) {
        galleryItems.push({
            id: item.id,
            src: ContextFactory.prepareImgSrc(item.getProperty(SRC).value, isShowOptimized),
            order: item.getProperty(ORDER).value.toInteger()
        });
    });

    var imagestretchvalues = [
    { name: "crop", value: "crop" },
    { name: "fill", value: "fill" }
    ];

    imagestretchvalues.forEach(function (item) {
        item.siselected = item.value == imagestretching;
    });

    var linkContext = ContextFactory.linkManagementEditor(component);

    var fontfamilyvalues = ContextFactory.getComponentFonts();
    fontfamilyvalues.forEach(function (item) {
        item.selected = item.value == ff;
    });

    var captionPosition = component.getProperty(CAPTION_POSITION).value;
    var captionOptions = ContextFactory.getSlideshowCaptionPositionList(captionPosition);
    var stretchToFullWidth = component.getProperty(STRETCH_TO_FULL_WIDTH).value.toBoolean();

    var context = {
        width: width,
        height: height,
        top: top,
        left: left,
		zindex: zindex || 0,
        galleryItems: galleryItems.orderBy(ORDER),
        interval: interval,
        pauseOnHover: pauseOnHover,
        nav: nav,
        dot: dot,
        caption: caption,
        expand: expand,
        isOptions: imagestretchvalues,
        modeOptions: linkContext.linkModes,
        pageOptions: linkContext.pages,
        anchors: linkContext.anchors,
        fontSize: fs,
        color: color,
        headercolor: headercolor,
        bgcolor: bgcolor,
        fontOptions: fontfamilyvalues,
        cpOptions: captionOptions,
        marginsWidth: mw,
        stretchToFullWidth: stretchToFullWidth,
        isShowOptimized: isShowOptimized
    }
    return context;
}

var SlideshowComponentViewerContext = function (component, properties) {

    eventsystem.subscribe('/component/stretch/', function () {
        component.stretcher();
    });

    var pauseOnHover = component.getProperty(PAUSE).value.toBoolean();
    var interval = component.getProperty(INTERVAL).value.toInteger();
    var nav = component.getProperty(NAV).value.toBoolean();
    var dot = component.getProperty(DOT).value.toBoolean();
    var caption = component.getProperty(CAPTION).value.toBoolean();
    var expand = component.getProperty(EXPAND).value.toBoolean();

    var galleryItems = [];

    var stretchingVal = component.getProperty(IMAGE_STRETCHING).value;
    var captionPosition = component.getProperty(CAPTION_POSITION).value;
    var infoBgColor = component.getProperty(IMAGE_SETTINGS_BGCOLOR).value;
    var titleColor = component.getProperty(IMAGE_SETTINGS_HEADERCOLOR).value;
    var descrColor = component.getProperty(IMAGE_SETTINGS_COLOR).value;
    var isShowOptimized = component.getProperty(SHOW_OPTIMIZED).value.toBoolean();
    component.children.forEach(function (item) {
        var linkContext = ContextFactory.linkManagementViewer(item);

        galleryItems.push({
            id: item.id,
            controlId: component.id,
            src: ContextFactory.prepareImgSrc(item.getProperty(SRC).value, isShowOptimized),
            title: item.getProperty(TITLE).value,
            description: item.getProperty(DESCRIPTION).value,
            order: item.getProperty(ORDER).value.toInteger(),
            caption: caption,
            expand: linkContext.href == '' ? expand : true,//TODO: expand fix for old logic
            href: linkContext.href,
            onclick: linkContext.target,
            isCrop: stretchingVal === 'crop',
            captionPosition: captionPosition,
            infoBgColor: infoBgColor,
            titleColor: titleColor,
            descrColor: descrColor
        });
    });
    galleryItems = galleryItems.orderBy(ORDER);
    var active = galleryItems.firstOrDefault();
    if (active != null) {
        active.active = true;
    }
    galleryItems.forEach(function (item, index) {
        item.index = index;
    });

    var captionPositionClass = "";
    var captionOptions = ContextFactory.getSlideshowCaptionPositionList(captionPosition);
    captionOptions.forEach(function (item) {
        if (item.selected) {
            captionPositionClass = item.class;
        }
    });

    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        galleryItems: galleryItems,
        nav: nav,
        dot: dot,
        expand: expand,
        captionPositionClass: captionPositionClass,
        interval: interval * 1000,
        pauseOnHover: pauseOnHover,
        isShowOptimized: isShowOptimized
    };
    return context;
}


var ListComponentEditorContext = function (component) {
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
	var zindex = component.getProperty(Z_INDEX).value;
    var top = component.getProperty(TOP).value;
    var left = component.getProperty(LEFT).value;
    var type = component.getProperty(TYPE).value;
    var expand = component.getProperty(EXPAND).value.toBoolean();
    var color = component.getProperty(IMAGE_SETTINGS_COLOR).value;
    var headercolor = component.getProperty(IMAGE_SETTINGS_HEADERCOLOR).value;
    var bgcolor = component.getProperty(IMAGE_SETTINGS_BGCOLOR).value;
    var fs = component.getProperty(FONT_SIZE).value;
    var optionalcolor = component.getProperty(IMAGE_SETTING_OPTIONALCOLOR).value;
    var ff = component.getProperty(FONT_FAMILY).value;
    
    var fontfamilyvalues = ContextFactory.getComponentFonts();
    fontfamilyvalues.forEach(function (item) {
        item.selected = item.value == ff;
    });

    var imagestretching = component.getProperty(IMAGE_STRETCHING).value;
    var imagestretchvalues = [{ name: "crop", value: "crop" }, { name: "fill", value: "fill" }];
    
    imagestretchvalues.forEach(function (item) {
        item.siselected = item.value == imagestretching;
    });

    var galleryTypes = [
        { "name": "simple", "value": "simple" },
        { "name": "vertical", "value": "vertical" },
        { "name": "only text", "value": "only text" },
        { "name": "lines", "value": "lines" },
        { "name": "general", "value": "general" },
        { "name": "thumbnails", "value": "thumbnails" }
    ];
    var currentGalleryType = galleryTypes.where({ value: type });
    if (currentGalleryType.any()) {
        currentGalleryType = currentGalleryType.firstOrDefault();
        currentGalleryType.selected = true;
    }

    var isShowOptimized = component.getProperty(SHOW_OPTIMIZED).value.toBoolean();

    var galleryItems = [];
    component.children.forEach(function (item) {
        galleryItems.push({
            id: item.id,
            src: ContextFactory.prepareImgSrc(item.getProperty(SRC).value, isShowOptimized),
            order: item.getProperty(ORDER).value.toInteger()
        });
    });

    var linkContext = ContextFactory.linkManagementEditor(component);

    var context = {
        width: width,
        height: height,
        top: top,
        left: left,
        galleryItems: galleryItems.orderBy(ORDER),
        options: galleryTypes,
        modeOptions: linkContext.linkModes,
        pageOptions: linkContext.pages,
        anchors: linkContext.anchors,
        expand: expand,
        color: color,
        headercolor: headercolor,
        bgcolor: bgcolor,
        fontSize: fs,
        zindex: zindex || 0,
        optionalcolor: optionalcolor,
        isOptions: imagestretchvalues,
        fontOptions: fontfamilyvalues,
        isShowOptimized: isShowOptimized
    }
    return context;
}

var ListComponentViewerContext = function (component, properties) {
    var color = component.getProperty(IMAGE_SETTINGS_COLOR).value;
    var headercolor = component.getProperty(IMAGE_SETTINGS_HEADERCOLOR).value;
    var bgcolor = component.getProperty(IMAGE_SETTINGS_BGCOLOR).value;
    var fs = component.getProperty(FONT_SIZE).value;
    var optionalcolor = component.getProperty(IMAGE_SETTING_OPTIONALCOLOR).value;

    var type = component.getProperty(TYPE).value;
    var expand = component.getProperty(EXPAND).value.toBoolean();
    var simple = false;
    var vertical = false;
    var onlytext = false;
    var lines = false;
    var general = false;
    var thumbnail = false;
    switch (type) {
        case "simple":
            simple = true;
            break;
        case "vertical":
            vertical = true;
            break;
        case "only text":
            onlytext = true;
            expand = false;
            break;
        case "lines":
            lines = true;
            expand = false;
            break;
        case "general":
            general = true;
            break;
        case "thumbnails":
            thumbnail = true;
            break;
    }

    var isShowOptimized = component.getProperty(SHOW_OPTIMIZED).value.toBoolean();

    var galleryItems = [];
    component.children.forEach(function (item) {

        var linkContext = ContextFactory.linkManagementViewer(item);
        var cursor = "pointer";

        var itemExpand = linkContext.href == '' ? expand : true;//TODO: expand fix for old logic
        var src = ContextFactory.prepareImgSrc(item.getProperty(SRC).value, isShowOptimized);

        if (src == BLANKIMAGE)
        {
            itemExpand = false;
        }

        if (itemExpand) {
            cursor = "pointer";
        }


        galleryItems.push({
            id: item.id,
            src: src,
            title: item.getProperty(TITLE).value,
            description: item.getProperty(DESCRIPTION).value,
            optional: item.getProperty(OPTIONAL).value,
            order: item.getProperty(ORDER).value.toInteger(),
            href: linkContext.href,
            onclick: linkContext.target,
            isShowLink: linkContext.isShowLink,
            cursor: cursor,
            expand: itemExpand
        });
    });
    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        galleryItems: galleryItems.orderBy(ORDER),
        vertical: vertical,
        simple: simple,
        onlytext: onlytext,
        lines: lines,
        general: general,
        thumbnail: thumbnail,
        color: color,
        headercolor: headercolor,
        bgcolor: bgcolor,
        fontSize: fs,
        optionalcolor: optionalcolor,
        isShowOptimized: isShowOptimized
    };
    return context;
}

var FrameComponentEditorContext = function (component) {
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;
    var zindex = component.getProperty(Z_INDEX).value;

    var src = component.getProperty(MODE_VALUE).value;

    var context = {
        width: width,
        height: height,
        left: left,
        top: top,
        zindex: zindex || 0,
        src: src
    };
    return context;
}

var FrameComponentViewerContext = function (component, properties) {
    var src = component.getProperty(MODE_VALUE).value;
    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        isDockable: component.isDockable ? "dockable" : "",
        src: src
    };
    return context;
}

var PdfComponentEditorContext = function (component) {
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;
    var zindex = component.getProperty(Z_INDEX).value;


    var src = component.getProperty(MODE_VALUE).value;

    var isShowOptimizedPlaceholder = component.getProperty(SHOW_OPTIMIZED_PLACEHOLDER).value.toBoolean();
    var image = component.getProperty(IMAGE_PROPERTY).value;

    var name = component.getProperty(NAME).value;

    if (src.length === 0) {
        name = PDF_NOT_UPLOADED;
    }

    var context = {
        width: width,
        height: height,
        left: left,
        top: top,
        zindex: zindex || 0,
        src: src,
        name: name,
        isShowOptimizedPlaceholder: isShowOptimizedPlaceholder,
    };
    return context;
}

var PdfComponentViewerContext = function (component, properties) {
    var src = component.getProperty(MODE_VALUE).value;

    var isShowOptimizedPlaceholder = component.getProperty(SHOW_OPTIMIZED_PLACEHOLDER).value.toBoolean();
    var image = component.getProperty(IMAGE_PROPERTY).value;

    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        isDockable: component.isDockable ? "dockable" : "",
        src: src,
        image: ContextFactory.prepareImgSrc(image, isShowOptimizedPlaceholder),
        isShowOptimizedPlaceholder: isShowOptimizedPlaceholder
    };
    return context;
}

/*Form Components*/
var FormComponentEditorContext = function (component) {
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;
    var zindex = component.getProperty(Z_INDEX).value;
    var bgcolor = component.getProperty(BACKGROUND_COLOR).value;
    var brcolor = component.getProperty(BORDER_COLOR).value;
    var bw = component.getProperty(BORDER_WIDTH).value;
    var br = component.getProperty(BORDER_RADIUS).value;
    var mailHistory = component.getProperty(MAIL_HISTORY_PROPERTY).value.toBoolean();

    var context = {
        width: width,
        height: height,
        left: left,
        top: top,
        zindex: zindex || 0,
        bgcolor: bgcolor,
        borderWidth: bw,
        borderRadius: br,
        brcolor: brcolor,
        mailHistory: mailHistory
    };
    return context;
}

var FormComponentViewerContext = function (component, properties) {    
    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),        
        isDockable: component.isDockable ? "dockable" : ""
    };
    return context;
}

var LabelComponentEditorContext = function (component) {
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;
    var zindex = component.getProperty(Z_INDEX).value;
    var text = component.getProperty(TEXT).value;

    var bgcolor = component.getProperty(BACKGROUND_COLOR).value;

    var ta = component.getProperty(TEXT_ALIGN).value;
    var ff = component.getProperty(FONT_FAMILY).value;
    var color = component.getProperty(COLOR).value;
    var fs = component.getProperty(FONT_SIZE).value;

    var textalignvalues = [
    { name: "left", value: "left" },
    { name: "right", value: "right" },
    { name: "center", value: "center" },
    { name: "justify", value: "justify" }
    ];

    textalignvalues.forEach(function (item) {
        item.taselected = item.value === ta;
    });

    var fontfamilyvalues = ContextFactory.getComponentFonts();

    fontfamilyvalues.forEach(function (item) {
        item.selected = item.value === ff;
    });

    var context = {
        width: width,
        height: height,
        left: left,
        top: top,
        zindex: zindex || 0,
        text: text,
        color: color,
        taOptions: textalignvalues,
        fontSize: fs,
        fontOptions: fontfamilyvalues,
        bgcolor: bgcolor
    };
    return context;
}

var CaptchaComponentEditorContext = function (component) {
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;
    var zindex = component.getProperty(Z_INDEX).value;

    var context = {
        width: width,
        height: height,
        left: left,
        top: top,
        zindex: zindex || 0
    };
    return context;
}

var LabelComponentViewerContext = function (component, properties) {
    var text = component.getProperty(TEXT).value;
    if (component.getProperty(CAPTION_COMPONENTS_TO_LABEL).value === "") {
        component.setProperty(CAPTION_COMPONENTS_TO_LABEL, component.id);
    }
    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        text: text,
        isDockable: component.isDockable ? "dockable" : ""
    };
    return context;
}

var CaptchaComponentViewerContext = function (component, properties) {
    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' })
    };
    return context;
}

var AutocompleteAddressComponentEditorContext = function (component) {
    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;

    var placeholder = component.getProperty(PLACEHOLDER).value;

    var bgcolor = component.getProperty(BACKGROUND_COLOR).value;
    var color = component.getProperty(COLOR).value;
    var brcolor = component.getProperty(BORDER_COLOR).value;

    var zindex = component.getProperty(Z_INDEX).value;

    var ff = component.getProperty(FONT_FAMILY).value;
    var fs = component.getProperty(FONT_SIZE).value;

    var bw = component.getProperty(BORDER_WIDTH).value;
    var br = component.getProperty(BORDER_RADIUS).value;

    var labelOptions = ContextFactory.getFormLabels(component);
    var labelTypes = ContextFactory.getFormLabelTypes(component);
    var labelValue = component.getProperty(SELECTEDLABEL).value;

    var fontfamilyvalues = ContextFactory.getComponentFonts();
    fontfamilyvalues.forEach(function (item) {
        item.selected = item.value === ff;
    });
    
    var formSpaceAfterItem = component.getProperty(FORM_SPACE_AFTER_ITEM).value.toBoolean();
    var isRequired = component.getProperty(REQUIRED_FIELD).value.toBoolean();

    var context = {
        bgcolor: bgcolor,
        color: color,
        width: width,
        height: height,
        left: left,
        top: top,

        zindex: zindex || 0,

        borderWidth: bw,
        borderRadius: br,
        brcolor: brcolor,
        placeholder: placeholder,

        fontSize: fs,
        fontOptions: fontfamilyvalues,
        labelOptions: labelOptions,
        labelTypes: labelTypes,
        labelValue: labelValue,
        formSpaceAfterItem: formSpaceAfterItem,
        isRequired: isRequired
    };
    return context;
}
var AutocompleteAddressComponentViewerContext = function (component, properties) {
    var placeholder = component.getProperty(PLACEHOLDER).value;
    var isdisabled = !UI.getSetting("ispreview") ? "readonly" : "";
    var isRequired = component.getProperty(REQUIRED_FIELD).value.toBoolean();

    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        placeholder: placeholder,
        isDockable: component.isDockable ? "dockable" : "",
        isdisabled: isdisabled,
        isRequired: isRequired
    };
    return context;
}

var TextboxComponentEditorContext = function (component) {
    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;

    var placeholder = component.getProperty(PLACEHOLDER).value;

    var bgcolor = component.getProperty(BACKGROUND_COLOR).value;
    var color = component.getProperty(COLOR).value;
    var brcolor = component.getProperty(BORDER_COLOR).value;

    var zindex = component.getProperty(Z_INDEX).value;

    var ff = component.getProperty(FONT_FAMILY).value;
    var fs = component.getProperty(FONT_SIZE).value;

    var bw = component.getProperty(BORDER_WIDTH).value;
    var br = component.getProperty(BORDER_RADIUS).value;

    var labelOptions = ContextFactory.getFormLabels(component);
    var labelTypes = ContextFactory.getFormLabelTypes(component);
    var labelValue = component.getProperty(SELECTEDLABEL).value;

    var fontfamilyvalues = ContextFactory.getComponentFonts();
    fontfamilyvalues.forEach(function (item) {
        item.selected = item.value === ff;
    });

    var textboxtype = component.proto.name === 'textbox' ? component.getProperty(TEXT_BOX_TYPE).value : '';
    var textboxtypevalues = [
        { name: "Text", value: "Text" },
        { name: "Email", value: "Email" }
    ];
    textboxtypevalues.forEach(function (item) {
        item.selected = item.value === textboxtype;
    });
    var formSpaceAfterItem = component.getProperty(FORM_SPACE_AFTER_ITEM).value.toBoolean();
    var isRequired = component.getProperty(REQUIRED_FIELD).value.toBoolean();

    var context = {
        bgcolor: bgcolor,
        color: color,
        width: width,
        height: height,
        left: left,
        top: top,

        zindex: zindex || 0,

        borderWidth: bw,
        borderRadius: br,
        brcolor: brcolor,
        placeholder: placeholder,

        fontSize: fs,
        fontOptions: fontfamilyvalues,
        labelOptions: labelOptions,
        labelTypes: labelTypes,
        labelValue: labelValue,
        typeOptions: textboxtypevalues,
        formSpaceAfterItem: formSpaceAfterItem,
        isRequired: isRequired
    };
    return context;
}
var TextboxComponentViewerContext = function (component, properties) {
    var placeholder = component.getProperty(PLACEHOLDER).value;
    var isdisabled = !UI.getSetting("ispreview") ? "readonly" : "";
    var isRequired = component.getProperty(REQUIRED_FIELD).value.toBoolean();

    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        placeholder: placeholder,
        isDockable: component.isDockable ? "dockable" : "",
        isdisabled: isdisabled,
        isRequired: isRequired
    };
    return context;
}

var RadiolistComponentEditorContext = function (component) {
    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;

    var bgcolor = component.getProperty(BACKGROUND_COLOR).value;
    var color = component.getProperty(COLOR).value;
    var brcolor = component.getProperty(BORDER_COLOR).value;

    var zindex = component.getProperty(Z_INDEX).value;

    var ff = component.getProperty(FONT_FAMILY).value;
    var fs = component.getProperty(FONT_SIZE).value;

    var bw = component.getProperty(BORDER_WIDTH).value;
    var br = component.getProperty(BORDER_RADIUS).value;

    var fontfamilyvalues = ContextFactory.getComponentFonts();
    fontfamilyvalues.forEach(function (item) {
        item.selected = item.value == ff;
    });

    var items = [];
    component.children.forEach(function (item) {
        items.push({
            id: item.id,
            text: item.getProperty(TEXT).value,
            listid: component.id
        });
    });

    var predefined = component.getProperty(PREDEFINED).value;
    var predefinedClasses = [
        { "name": "Horizontal", "value": "Horizontal" },
        { "name": "Vertical", "value": "Vertical" }
    ];
    var currentPredefinedClass = predefinedClasses.where({ value: predefined }).firstOrDefault();
    currentPredefinedClass.selected = true;
    var labelOptions = ContextFactory.getFormLabels(component);
    var labelTypes = ContextFactory.getFormLabelTypes(component);
    var labelValue = component.getProperty(SELECTEDLABEL).value;
    var formSpaceAfterItem = component.getProperty(FORM_SPACE_AFTER_ITEM).value.toBoolean();
    var isRequired = component.getProperty(REQUIRED_FIELD).value.toBoolean();

    var context = {
        bgcolor: bgcolor,
        color: color,
        width: width,
        height: height,
        left: left,
        top: top,

        zindex: zindex || 0,

        borderWidth: bw,
        borderRadius: br,
        brcolor: brcolor,

        fontSize: fs,
        fontOptions: fontfamilyvalues,
        items: items,
        styleOptions: predefinedClasses,
        labelOptions: labelOptions,
        labelTypes: labelTypes,
        labelValue: labelValue,
        formSpaceAfterItem: formSpaceAfterItem,
        isRequired: isRequired
    };
    return context;
}

var RadiolistComponentViewerContext = function (component, properties) {
    var items = [];
    component.children.forEach(function (item) {
        items.push({
            id: item.id,
            text: item.getProperty('text').value,
            listid: component.id
        });
    });

    var predefined = component.getProperty(PREDEFINED).value;
    var additionalClass = "";
    if (predefined === "Horizontal") {
        additionalClass = "radio-list-horizontal";
    }
    var isRequired = component.getProperty(REQUIRED_FIELD).value.toBoolean();
    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        isDockable: component.isDockable ? "dockable" : "",
        items: items,
        additionalClass: additionalClass,
        isRequired: isRequired
    };
    return context;
}

var SelectlistComponentEditorContext = function (component) {
    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;

    var bgcolor = component.getProperty(BACKGROUND_COLOR).value;
    var color = component.getProperty(COLOR).value;
    var brcolor = component.getProperty(BORDER_COLOR).value;

    var zindex = component.getProperty(Z_INDEX).value;

    var ff = component.getProperty(FONT_FAMILY).value;
    var fs = component.getProperty(FONT_SIZE).value;

    var bw = component.getProperty(BORDER_WIDTH).value;
    var br = component.getProperty(BORDER_RADIUS).value;

    var fontfamilyvalues = ContextFactory.getComponentFonts();
    fontfamilyvalues.forEach(function (item) {
        item.selected = item.value === ff;
    });

    var labelOptions = ContextFactory.getFormLabels(component);
    var labelTypes = ContextFactory.getFormLabelTypes(component);
    var labelValue = component.getProperty(SELECTEDLABEL).value;

    var items = [];
    component.children.forEach(function (item) {
        items.push({
            id: item.id,
            text: item.getProperty(TEXT).value
        });
    });

    var formSpaceAfterItem = component.getProperty(FORM_SPACE_AFTER_ITEM).value.toBoolean();
    var isRequired = component.getProperty(REQUIRED_FIELD).value.toBoolean();

    var context = {
        bgcolor: bgcolor,
        color: color,
        width: width,
        height: height,
        left: left,
        top: top,

        zindex: zindex || 0,

        borderWidth: bw,
        borderRadius: br,
        brcolor: brcolor,

        fontSize: fs,
        fontOptions: fontfamilyvalues,
        items: items,
        labelOptions: labelOptions,
        labelTypes: labelTypes,
        labelValue: labelValue,
        formSpaceAfterItem: formSpaceAfterItem,
        isRequired: isRequired
    };
    return context;
}

var SelectlistComponentViewerContext = function (component, properties) {
    var items = [];
    component.children.forEach(function (item) {
        items.push({
            text: item.getProperty(TEXT).value
        });
    });
    var isReadOnly = '';

    var isRequired = component.getProperty(REQUIRED_FIELD).value.toBoolean();

    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        isDockable: component.isDockable ? "dockable" : "",
        items: items,
        isReadOnly: isReadOnly,
        isRequired: isRequired
    };
    return context;
}

var CheckboxComponentEditorContext = function (component) {
    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;
    var zindex = component.getProperty(Z_INDEX).value;
    var labelOptions = ContextFactory.getFormLabels(component);
    var labelTypes = ContextFactory.getFormLabelTypes(component);
    var labelValue = component.getProperty(SELECTEDLABEL).value;
    var formSpaceAfterItem = component.getProperty(FORM_SPACE_AFTER_ITEM).value.toBoolean();
    var isRequired = component.getProperty(REQUIRED_FIELD).value.toBoolean();
    var context = {
        left: left,
        top: top,
        zindex: zindex || 0,
        labelOptions: labelOptions,
        labelTypes: labelTypes,
        labelValue: labelValue,
        isRequired: isRequired,
        formSpaceAfterItem: formSpaceAfterItem
    };
    return context;
}

var CheckboxComponentViewerContext = function (component, properties) {
    var isReadOnly = '';
    var isRequired = component.getProperty(REQUIRED_FIELD).value.toBoolean();
    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        isDockable: component.isDockable ? "dockable" : "",
        isReadOnly: isReadOnly,
        isRequired: isRequired
    };
    return context;
}

var SubmitComponentEditorContext = function (component) {
    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
    var zindex = component.getProperty(Z_INDEX).value;

    var color = component.getProperty(COLOR).value;
    var txthovercolor = component.getProperty(TEXT_COLOR_HOVER).value;
    var brcolor = component.getProperty(BORDER_COLOR).value;
    var brhovercolor = component.getProperty(BORDER_COLOR_HOVER).value;
    var bgcolor = component.getProperty(BACKGROUND_COLOR).value;
    var bghovercolor = component.getProperty(BACKGROUND_COLOR_HOVER).value;

    var text = component.getProperty(TEXT).value;
    var ta = component.getProperty(TEXT_ALIGN).value;
    var ff = component.getProperty(FONT_FAMILY).value;
    var fs = component.getProperty(FONT_SIZE).value;

    var bw = component.getProperty(BORDER_WIDTH).value;
    var br = component.getProperty(BORDER_RADIUS).value;

    var textalignvalues = [
    { name: "left", value: "left" },
    { name: "right", value: "right" },
    { name: "center", value: "center" },
    { name: "justify", value: "justify" }
    ];

    var fontfamilyvalues = ContextFactory.getComponentFonts();

    textalignvalues.forEach(function (item) {
        item.taselected = item.value === ta;
    });

    fontfamilyvalues.forEach(function (item) {
        item.selected = item.value === ff;
    });

    var mail = component.getProperty(MODE_VALUE).value;

    var successPageMasterLink = component.getProperty(SUCCESS_PAGE_MASTER_LINK).value;
    var successPageComponent = (successPageMasterLink && successPageMasterLink !== component.id)
        ? UI.siteComponentRepository.lookupData({ id: successPageMasterLink })
        : component;

    // property for success page
    var successPageMessage = successPageComponent.getProperty(SUCCESS_PAGE_MESSAGE).value;
    var successPageLinkRedirectButton = successPageComponent.getProperty(SUCCESS_PAGE_LINK_REDIRECT_BUTTON).value;
    var successPageContentColor = successPageComponent.getProperty(SUCCESS_PAGE_CONTENT_COLOR).value;
    var successPageTextColor = successPageComponent.getProperty(SUCCESS_PAGE_TEXT_COLOR).value;
    var successPageTextFontFamily = successPageComponent.getProperty(SUCCESS_PAGE_TEXT_FONT_FAMILY).value;
    var successPageTextFontSize = successPageComponent.getProperty(SUCCESS_PAGE_TEXT_FONT_SIZE).value;
    var successPageImageLogo = successPageComponent.getProperty(SUCCESS_PAGE_IMAGE_LOGO).value;
    var successPageHeaderColor = successPageComponent.getProperty(SUCCESS_PAGE_HEADER_COLOR).value;
    var successPageButtonColor = successPageComponent.getProperty(SUCCESS_PAGE_BUTTON_COLOR).value;
    var successPageHeaderText = successPageComponent.getProperty(SUCCESS_PAGE_HEADER_TEXT).value;
    var successPageButtonText = successPageComponent.getProperty(SUCCESS_PAGE_BUTTON_TEXT).value;

    if (successPageImageLogo === "") {
        successPageImageLogo = SUCCESS_LOGO_IMAGE;
    }

    var parentPages = ContextFactory.pages(successPageLinkRedirectButton);
    var ffvalues = ContextFactory.getComponentFonts();
    ffvalues.forEach(function (item) {
        item.selected = item.value == successPageTextFontFamily;
    });

    var formSubject = component.getProperty(FORM_SUBJECT).value;

    var context = {
        width: width,
        height: height,
        left: left,
        top: top,
        zindex: zindex || 0,
        color: color,
        txtcolorhover: txthovercolor,
        brcolor: brcolor,
        brcolorhover: brhovercolor,
        bgcolor: bgcolor,
        bgcolorhover: bghovercolor,
        text: text,
        taOptions: textalignvalues,
        fontSize: fs,
        fontOptions: fontfamilyvalues,
        borderWidth: bw,
        borderRadius: br,
        email: mail,
        formSubject: formSubject,

        successPageMessage: successPageMessage,
        successPageParentPages: parentPages,
        successPageContentColor: successPageContentColor,
        successPageTextColor: successPageTextColor,
        successPageTextFontFamily: successPageTextFontFamily,
        successPageTextFontSize: successPageTextFontSize,
        successPageImageLogo: successPageImageLogo,
        successPageHeaderColor: successPageHeaderColor,
        successPageButtonColor: successPageButtonColor,
        successPageHeaderText: successPageHeaderText,
        successPageButtonText: successPageButtonText,
        successPageFFValues: ffvalues,

        useSuccessPageAnyway: successPageMasterLink
    };
    return context;
}

var SubmitComponentViewerContext = function (component, properties) {
    var text = component.getProperty(TEXT).value;
    
    var successPageMasterLink = component.getProperty(SUCCESS_PAGE_MASTER_LINK).value;
    var successPageComponent = (successPageMasterLink && successPageMasterLink !== component.id)
        ? UI.siteComponentRepository.lookupData({ id: successPageMasterLink })
        : component;

    // property for success page
    var successPageMessage = successPageComponent.getProperty(SUCCESS_PAGE_MESSAGE).value;
    var successPageLinkRedirectButton = successPageComponent.getProperty(SUCCESS_PAGE_LINK_REDIRECT_BUTTON).value;
    var successPageContentColor = successPageComponent.getProperty(SUCCESS_PAGE_CONTENT_COLOR).value;
    var successPageTextColor = successPageComponent.getProperty(SUCCESS_PAGE_TEXT_COLOR).value;
    var successPageTextFontFamily = successPageComponent.getProperty(SUCCESS_PAGE_TEXT_FONT_FAMILY).value;
    var successPageTextFontSize = successPageComponent.getProperty(SUCCESS_PAGE_TEXT_FONT_SIZE).value;
    var successPageImageLogo = successPageComponent.getProperty(SUCCESS_PAGE_IMAGE_LOGO).value;
    var successPageHeaderColor = successPageComponent.getProperty(SUCCESS_PAGE_HEADER_COLOR).value;
    var successPageButtonColor = successPageComponent.getProperty(SUCCESS_PAGE_BUTTON_COLOR).value;
    var successPageHeaderText = successPageComponent.getProperty(SUCCESS_PAGE_HEADER_TEXT).value;
    var successPageButtonText = successPageComponent.getProperty(SUCCESS_PAGE_BUTTON_TEXT).value;

    if (successPageImageLogo === "") {
        successPageImageLogo = SUCCESS_LOGO_IMAGE;
    }

    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        text: text,

        message: successPageMessage,
        link: successPageLinkRedirectButton,
        contentColor: successPageContentColor,
        textColor: successPageTextColor,
        ff: successPageTextFontFamily,
        fs: successPageTextFontSize,
        image: successPageImageLogo,
        headerColor: successPageHeaderColor,
        buttonColor: successPageButtonColor,
        headerText: successPageHeaderText,
        buttonText: successPageButtonText,
    };
    return context;
}

var AttachmentComponentEditorContext = function (component) {
    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
    var zindex = component.getProperty(Z_INDEX).value;

    var color = component.getProperty(COLOR).value;
    var txthovercolor = component.getProperty(TEXT_COLOR_HOVER).value;
    var brcolor = component.getProperty(BORDER_COLOR).value;
    var brhovercolor = component.getProperty(BORDER_COLOR_HOVER).value;
    var bgcolor = component.getProperty(BACKGROUND_COLOR).value;
    var bghovercolor = component.getProperty(BACKGROUND_COLOR_HOVER).value;

    var text = component.getProperty(TEXT).value;
    var ta = component.getProperty(TEXT_ALIGN).value;
    var ff = component.getProperty(FONT_FAMILY).value;
    var fs = component.getProperty(FONT_SIZE).value;

    var bw = component.getProperty(BORDER_WIDTH).value;
    var br = component.getProperty(BORDER_RADIUS).value;

    var textalignvalues = [
    { name: "left", value: "left" },
    { name: "right", value: "right" },
    { name: "center", value: "center" },
    { name: "justify", value: "justify" }
    ];

    var fontfamilyvalues = ContextFactory.getComponentFonts();

    textalignvalues.forEach(function (item) {
        item.taselected = item.value === ta;
    });

    fontfamilyvalues.forEach(function (item) {
        item.selected = item.value === ff;
    });

    var isRequired = component.getProperty(REQUIRED_FIELD).value.toBoolean();

    var context = {
        width: width,
        height: height,
        left: left,
        top: top,
        zindex: zindex || 0,
        color: color,
        txtcolorhover: txthovercolor,
        brcolor: brcolor,
        brcolorhover: brhovercolor,
        bgcolor: bgcolor,
        bgcolorhover: bghovercolor,
        text: text,
        taOptions: textalignvalues,
        fontSize: fs,
        fontOptions: fontfamilyvalues,
        borderWidth: bw,
        borderRadius: br,
        isRequired: isRequired
    };
    return context;
}

var AttachmentComponentViewerContext = function (component, properties) {
    var text = component.getProperty(TEXT).value;
    var isRequired = component.getProperty(REQUIRED_FIELD).value.toBoolean();

    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        text: text,
        isRequired: isRequired,
        accept: '.doc,.docx,.pdf,.txt,.jpg,.jpeg,.png'
    };
    return context;
}

var CancelComponentEditorContext = function (component) {
    var left = component.getProperty(LEFT).value;
    var top = component.getProperty(TOP).value;
    var width = component.getProperty(WIDTH).value;
    var height = component.getProperty(HEIGHT).value;
    var zindex = component.getProperty(Z_INDEX).value;

    var color = component.getProperty(COLOR).value;
    var txthovercolor = component.getProperty(TEXT_COLOR_HOVER).value;
    var brcolor = component.getProperty(BORDER_COLOR).value;
    var brhovercolor = component.getProperty(BORDER_COLOR_HOVER).value;
    var bgcolor = component.getProperty(BACKGROUND_COLOR).value;
    var bghovercolor = component.getProperty(BACKGROUND_COLOR_HOVER).value;

    var text = component.getProperty(TEXT).value;
    var ta = component.getProperty(TEXT_ALIGN).value;
    var ff = component.getProperty(FONT_FAMILY).value;
    var fs = component.getProperty(FONT_SIZE).value;

    var bw = component.getProperty(BORDER_WIDTH).value;
    var br = component.getProperty(BORDER_RADIUS).value;

    var textalignvalues = [
    { name: "left", value: "left" },
    { name: "right", value: "right" },
    { name: "center", value: "center" },
    { name: "justify", value: "justify" }
    ];

    var fontfamilyvalues = ContextFactory.getComponentFonts();

    textalignvalues.forEach(function (item) {
        item.taselected = item.value === ta;
    });

    fontfamilyvalues.forEach(function (item) {
        item.selected = item.value === ff;
    });

    var context = {
        width: width,
        height: height,
        left: left,
        top: top,
        zindex: zindex || 0,
        color: color,
        txtcolorhover: txthovercolor,
        brcolor: brcolor,
        brcolorhover: brhovercolor,
        bgcolor: bgcolor,
        bgcolorhover: bghovercolor,
        text: text,
        taOptions: textalignvalues,
        fontSize: fs,
        fontOptions: fontfamilyvalues,
        borderWidth: bw,
        borderRadius: br
    };
    return context;
}

var CancelComponentViewerContext = function (component, properties) {
    var text = component.getProperty(TEXT).value;

    var context = {
        id: component.id,
        styles: properties.where({ group: 'style' }),
        text: text
    };
    return context;
}
/*End Form Components*/

/* helpers */

ContextFactory.getComponentFonts = function () {
    var fontfamilyvalues = [
    { "name": "Open Sans", "value": "open sans" },
    { "name": "Open Sans Semibold", "value": "open sans semibold" },
    { "name": "Open Sans Bold", "value": "open sans bold" },
    { "name": "Open Sans Italic", "value": "open sans italic" },
    { "name": "Open Sans Semibold Italic", "value": "open sans semibold italic" },

    { "name": "Alegreya", "value": "alegreya" },
    { "name": "Andada", "value": "andada" },
    { "name": "Andale", "value": "'andale mono', andalemono, monospace" },
    { "name": "Arial", "value": "arial,helvetica,sans-serif" },
    { "name": "Arvo", "value": "arvo" },

    { "name": "Bitter", "value": "bitter" },
    { "name": "Brush Script MT", "value": "'brush script mt', cursive" },
    { "name": "Cambria", "value": "cambria, georgia, serif" },
    { "name": "Comic Sans MS", "value": "comic sans ms" },
    { "name": "Courier New", "value": "courier new" },

    { "name": "Droid Serif", "value": "droid serif" },
    { "name": "Kaushan Script", "value": "kaushan script" },
    { "name": "Lobster", "value": "lobster" },
    { "name": "Lobster Two", "value": "lobster two" },
    { "name": "Lucida Sans Unicode", "value": "lucida sans unicode,lucida grande,sans-serif" },

    { "name": "Palatino", "value": "palatino, 'palatino linotype', 'palatino lt std', 'book antiqua', georgia, serif" },
    { "name": "Raleway", "value": "raleway" },
    { "name": "Roboto", "value": "roboto,sans-serif" },
    { "name": "Tahoma", "value": "tahoma,geneva,sans-serif" },
    { "name": "Times New Roman", "value": "times new roman" }, 
    { "name": "Verdana", "value": "verdana,geneva,sans-serif" }
    ];
    return fontfamilyvalues;
}

ContextFactory.getComponentFontsInline = function () {
    var result = "";
    ContextFactory.getComponentFonts().forEach(function (entry) {
        result += entry.name + '/' + entry.value + ';';
    });
    return result;
}

ContextFactory.getSlideshowCaptionPositionList = function (selected) {
    var result = [
          { "name": "Bottom", "value": "bottom", "class":"slideshow-caption-bottom" },
          { "name": "Top", "value": "top", "class":"slideshow-caption-top" }
    ];

    if (selected !== undefined) {
        result.forEach(function (item) {
            item.selected = item.value == selected;
        });
    }

    return result;
}

ContextFactory.pages = function(link) {
    var parentPages = UI.pager.pages;
    var pages = [];

    for (var j = 0; j < parentPages.length; j++) {
        if ((link === "" && parentPages[j].isHome) || parentPages[j].id == link) {
            pages.push({ id: parentPages[j].id, title: parentPages[j].title, selected: true });
        } else {
            pages.push({ id: parentPages[j].id, title: parentPages[j].title });
        }
    }

    return pages;
};

ContextFactory.getFormLabels = function (component) {
    var result = new Array();
    var defaultItem = {
        id: "",
        name: "None",
        selected:false
    };
    result.push(defaultItem);

    if (component.parentComponent == null) {
        console.log("ContextFactory.getFormLabels -> parent component == null, componentid:" + component.id);
        return result;
    }
    var parentId = component.parentComponent.id;
    var selectedLabel = component.getProperty(SELECTEDLABEL).value;
    UI.siteComponentRepository.lookupData({ id: parentId }).children.where({ displayName: LABEL }).forEach(function (item) {
        var caption = item.getProperty(CAPTION_COMPONENTS_TO_LABEL).value;
        var arrayItem = {
            id: caption,
            name: item.getProperty("text").value,
            selected: caption === selectedLabel
        };
        result.push(arrayItem);
    });
    return result;
}

ContextFactory.getFormLabelTypes = function (component) {
    var type = component.getProperty(SELECTEDLABELTYPE).value;   
    var result = [
       { "name": "Label", "value": "Label" },
       { "name": LABELTYPENAME, "value": LABELTYPENAME }
    ];
    var currentType = result.where({ value: type }).firstOrDefault();
    currentType.selected = true;
    return result;
}

ContextFactory.linkManagementViewer = function (component) { 
    var mode = component.getProperty(MODE).value;
    var modeValue = component.getProperty(MODE_VALUE).value;
    var href = "";
    var linkTarget = "";
    var isShowLink = false;
    var blankPage = component.getProperty(OPEN_LINK_ON_BLANK).value.toBoolean();

    if (blankPage) {
        linkTarget = "target='_blank'";
    }

    if (UI.getSetting("ispreview")) {
        switch (mode) {
            case "anchor":
                if (isDefinedAndNotEmpty(modeValue)) {
                    href = "href='" + modeValue + "'";
                    isShowLink = true;
                }
                break;
            case "link":
                if (isDefinedAndNotEmpty(modeValue))
                {
                    if (!/http/.test(modeValue)) {
                        modeValue = '//' + modeValue;
                    }
                    href = "href='" + modeValue + "'";
                    isShowLink = true;
                }                
                break;
            case "page":
                if (isDefinedAndNotEmpty(modeValue) && modeValue != "None") {
                    var page = UI.siteComponentRepository.lookupData({ id: modeValue });
                    if (page != null) {
                        var prop = page.getProperty("name");
                        href = Helpers.generateLinkToPage(prop.value);
                        isShowLink = true;
                    }
                }
                break;
            case "call":
                if (isDefinedAndNotEmpty(modeValue)) {
                    href = "href='tel:" + modeValue + "'";
                    isShowLink = true;
                }
                break;
        }
        
        return {
            target: linkTarget,
            href: href,
            isShowLink: isShowLink
        }
    }
    else 
    {
        return {
            target: "",
            href: "",
            isShowLink: false
        };
    }
}

ContextFactory.linkManagementEditor = function (component) {
    var modeProperty = component.getProperty(MODE);
    var modeValueProperty = component.getProperty(MODE_VALUE);
    var mode = modeProperty ? modeProperty.value : '';    
    var modeValue = modeValueProperty ? modeValueProperty.value : '';

    if (modeValue == undefined) {
        modeValue = "None";
    }

    var linkModes = [
        { "name": "none", "value": "none", selected:false },
        { "name": "link", "value": "link", selected: false },
        { "name": "page", "value": "page", selected: false },
        { "name": "anchor", "value": "anchor", selected: false },
        { "name": "call", "value": "call", selected: false }
    ];

    var currentMode = linkModes.where({ value: mode }).firstOrDefault();
    var pages = [{ title: "None", id: "None" }];
    pages.addRange(_.map(_.cloneDeep(UI.pager.pages.where({isService: false})), function (page) {
        page.selected = false;
        return page;
    }));
    var currentPage = pages.where({ id: modeValue }).firstOrDefault();
    if (currentPage != null) {
        currentPage.selected = true;
    }
    if (currentMode != null) {
        currentMode.selected = true;
    }

    var blankPageProperty = component.getProperty(OPEN_LINK_ON_BLANK);
    var blankPage = blankPageProperty ? blankPageProperty.value.toBoolean() : false;
    
    var pageid = UI.pager.getCurrentPageId();
    var curpage = UI.siteComponentRepository.lookupData({ id: pageid })
    var header = UI.siteComponentRepository.lookupData({ displayName: "header" })
    var footer = UI.siteComponentRepository.lookupData({ displayName: "footer" })

    var anchorObjs = UI.siteComponentRepository.lookupDataSet({ displayName: "anchor" }, curpage);
    var anchorResult = [];
    var anchors = [];
    if (anchorObjs != null) {
        anchors = _.map(anchorObjs, function (el) {
            return { name: el.getProperty(NAME).value, selected: (modeValue == el.getProperty(NAME).value) };
        });
    }
    anchorResult = anchors.concat(anchorResult);

    var anchorObjs = UI.siteComponentRepository.lookupDataSet({ displayName: "anchor" }, header);
    if (anchorObjs != null) {
        anchors = _.map(anchorObjs, function (el) {
            return { name: el.getProperty(NAME).value, selected: (modeValue == el.getProperty(NAME).value) };
        });
    }
    anchorResult = anchorResult.concat(anchors);

    var anchorObjs = UI.siteComponentRepository.lookupDataSet({ displayName: "anchor" }, footer);
    if (anchorObjs != null) {
        anchors = _.map(anchorObjs, function (el) {
            return { name: el.getProperty(NAME).value, selected: (modeValue == el.getProperty(NAME).value) };
        });
    }
    anchorResult = anchorResult.concat(anchors);
        
    return {
        modeValue: modeValue,
        linkModes: linkModes,
        pages: pages,
        blankPage: blankPage,
        anchors: [{ name: "None", selected: false }].concat(anchorResult)
    }
}
;
var TransformFactory = function () { };

TransformFactory.staticHeightComponent = function (component) {
    switch (component.proto.name) {
        case SOUND:
            return 110;
        case ANCHOR:
            return 10;
        default:
            return 10;
    }
}

var TransformComponent = function (component, pTComp) {
    var self = this;
    self.init = function (component, pTComp) {
        self.component = component;
        self.children = [];
        self.isTransform = false;
        self.transformCalls = 0;
        self.parent = pTComp;
    }

    self.isLastTransformCall = function () {
        return self.transformCalls >= self.children.length;
    }

    self.isComponentInside = function(tComp) {        
        return Helpers.isComponentInsideComponent(tComp.component, self.component);
    }

    self.setParent = function(parent) {
        self.parent = parent;
    }

    self.addChild = function (tComp) {
        tComp.setParent(self);
        self.children.push(tComp);
    }

    self.isHaveComponentChildren = function() {
        return self.component.children.length;
    }
    
    self.getHeightChildren = function() {
        var heightSum = 0;
        var marginValue = 10;
        _.forEach(self.children,
            function (item) {
                var control = item.component;                
                var heightC = item.getHeightChildren();
                var height = control.getProperty(HEIGHT);
                height = parseInt(height ? height.value : TransformFactory.staticHeightComponent(control));
                if (heightC > height) {
                    control.setProperty(HEIGHT, heightC + 'px');
                    heightSum += marginValue + heightC;
                } else {
                    heightSum += marginValue + height;
                }
            });
        return heightSum;
    }

    self.checkHeight = function () {        
        var height = self.getHeightChildren();
        if (parseInt(self.component.getProperty(HEIGHT).value) < height) {
            self.component.setProperty(HEIGHT, height + 'px');
        }
    }

    if (component) {
        self.init(component, pTComp);
    }
}

//wraping all components
TransformFactory.wrapComponent = function (component) {
    var tComp = new TransformComponent(component);

    if (tComp.isHaveComponentChildren()) {
        //run for children
        _.forEach(component.children,
                function (child) {
                    tComp.addChild(TransformFactory.wrapComponent(child));
                });
    }
    return tComp;
}

//prepare and run
TransformFactory.transformMobileOrder = function (component, deviceId) {
    deviceId = deviceId ? deviceId : UI.getDevice().getId();
    var transformComponent = TransformFactory.wrapComponent(component);
    TransformFactory.runTransformMobileOrder(transformComponent, deviceId);
}

//start with the last element of tree
TransformFactory.runTransformMobileOrder = function (tComp, deviceId) {
    deviceId = deviceId ? deviceId : UI.getDevice().getId();    
    if (tComp.children.length) {
        //run for children
        _.forEach(tComp.children,
                function (child) {                    
                    TransformFactory.runTransformMobileOrder(child, deviceId);
                });
    } else {
        //if not start transform
        if (tComp.parent && !tComp.parent.isTransform) {
            TransformFactory.transformMobileOrderSorting(tComp.parent, deviceId);
        }
    }
}

//sort algorithm
TransformFactory.sortAndSet = function (array, startAt, deviceId) {
    deviceId = deviceId ? deviceId : UI.getDevice().getId();
    startAt = startAt ? startAt : 0;
    var nextTop = startAt;
    var marginValue = 10;
    _.forEach(array,
        function (item) {
            var control = item.component;
            var top = nextTop + marginValue;
            //calculate before sort
            if (control.proto.name === PANEL) {
                item.checkHeight();
            }
            var height = control.getProperty(HEIGHT, deviceId);
            nextTop = top + parseInt(height ? height.value : TransformFactory.staticHeightComponent(control));
            control.setProperty(TOP, top + 'px', false, deviceId);
            control.setProperty(LEFT, marginValue + 'px', false, deviceId);
        });
    return nextTop - startAt + marginValue;
}

TransformFactory.sortByList = function (array) {
    var productItemsOrderList = {};
    productItemsOrderList[STORE_PRODUCT_TITLE] = 1;
    productItemsOrderList[STORE_PRODUCT_SKU] = 2;
    productItemsOrderList[STORE_PRODUCT_PRICE] = 3;
    productItemsOrderList[STORE_PRODUCT_IMAGES] = 4;
    productItemsOrderList[STORE_PRODUCT_DESCRIPTION] = 5;
    productItemsOrderList[STORE_PRODUCT_OPTIONS] = 6;
    productItemsOrderList[STORE_PRODUCT_QUANTITY] = 7;
    productItemsOrderList[STORE_PRODUCT_ADD_TO_CART] = 8;
    productItemsOrderList[STORE_PRODUCT_SOCIAL] = 9;
    
    return _.sortBy(array,
        function (item) {
            return productItemsOrderList[item.component.proto.name];
        });
}

TransformFactory.sortByTop = function(array) {
    return _.sortBy(array,
                function (item) {
                    var component = item.component;
                    var top = component.getProperty(TOP, UI.getDefaultDeviceId());
                    if (top != null) {
                        return parseInt(top.value);
                    } else {
                        throw new Error('Does not have TOP property');
                    }
                });
}

//transform different containers
TransformFactory.transformMobileOrderSorting = function (pTComp, deviceId) {
    deviceId = deviceId ? deviceId : UI.getDevice().getId();    
    if (pTComp.component.proto.name === PAGE_COMPONENT ||
        pTComp.component.proto.name === HEADER ||
        pTComp.component.proto.name === FOOTER ||
        pTComp.component.proto.name === FORM || 
        pTComp.component.proto.name === STORE_PRODUCT) {
        var sort = pTComp.component.proto.name === STORE_PRODUCT ? TransformFactory.sortByList(pTComp.children) : TransformFactory.sortByTop(pTComp.children);
        if (pTComp.component.proto.name !== PAGE_COMPONENT &&
            pTComp.component.proto.name !== HEADER &&
            pTComp.component.proto.name !== FOOTER) {
            //default algorithm                        
            var height = TransformFactory.sortAndSet(sort, pTComp.component.proto.name === STORE_PRODUCT ? 25 : 0);
            if (!(pTComp.component.proto.name === STORE_PRODUCT && parseInt(pTComp.component.getProperty(HEIGHT).value) > height)) {
                pTComp.component.setProperty(HEIGHT, height + 'px');
            }
            pTComp.isTransform = true;
            TransformFactory.transformMobileOrderSorting(pTComp.parent, deviceId);
        } else {
            //increase the number of calls to the children were already transformed
            pTComp.transformCalls++;
            //check if it last call
            if (pTComp.isLastTransformCall()) {
                //for container with div
                sort = _.sortBy(sort,
                    function(item) {
                        return item.component.proto.name !== PANEL;
                    });

                //check component inside
                var checkInside = function(comp, sort) {
                    for (var i = 0; i < sort.length - 1; i++) {
                        var item1 = sort[i];
                        if (item1 && item1.component.proto.name === PANEL) {
                            for (var j = i + 1; j < sort.length; j++) {
                                var item2 = sort[j];
                                if (item2 && item1.isComponentInside(item2)) {
                                    item1.addChild(item2);
                                    sort[j] = null;
                                }
                            }
                            //run for children
                            checkInside(item1, item1.children);
                        }
                    }
                    //remove null
                    comp.children = _.compact(sort);
                }
                checkInside(pTComp, sort);

                //run sort
                var sortContainer = function(tComp) {
                    if (tComp.children.length) {
                        if (!tComp.isTransform &&
                        (
                            tComp.component.proto.name === PAGE_COMPONENT ||
                                tComp.component.proto.name === HEADER ||
                                tComp.component.proto.name === FOOTER ||
                                tComp.component.proto.name === PANEL
                        )) {

                            var array = TransformFactory.sortByTop(tComp.children);
                            var top = tComp.component.getProperty(TOP);
                            if (tComp.component.proto.name === FOOTER) {
                                top = null;
                            }
                            var height = TransformFactory.sortAndSet(array, parseInt(top != null ? top.value : 0));
                            if (parseInt(tComp.component.getProperty(HEIGHT).value) < height) {
                                tComp.component.setProperty(HEIGHT, height + 'px');
                                if (tComp.component.proto.name === HEADER) {
                                    var main = UI.getMain();
                                    main.setProperty(TOP, height + 'px');
                                }
                            }
                            tComp.isTransform = true;
                        }
                        _.forEach(tComp.children,
                            function(child) {
                                if (child.children.length) {
                                    sortContainer(child);
                                }
                            });
                    }
                }
                sortContainer(pTComp);
            }
        }
    } else if (Helpers.isGalleryComponent(pTComp.component) || pTComp.component.proto.name === STORE_GALLERY || pTComp.component.proto.name === STORE_CART) {
        pTComp.isTransform = true;
        TransformFactory.transformMobileOrderSorting(pTComp.parent, deviceId);
    }
}


TransformFactory.getLastChildByTop = function (component, ignoreCurrent, deviceId) {
    deviceId = deviceId ? deviceId : UI.getDevice().getId();
    return _.last(_.sortBy(component.children,
            function (control) {
                var top;
                if (ignoreCurrent) {
                    if (control.id !== ignoreCurrent) {
                        top = control.getProperty(TOP, deviceId);
                        if (top != null) {
                            return parseInt(top.value);
                        } else {
                            throw new Error('Does not have TOP property');
                        }
                    } else {
                        return -1;
                    }
                } else {
                    top = control.getProperty(TOP, deviceId);
                    if (top != null) {
                        return parseInt(top.value);
                    } else {
                        throw new Error('Does not have TOP property');
                    }
                }
            }));
}

TransformFactory.checkMinSizeContainer = function (component, deviceId) {
    deviceId = deviceId ? deviceId : UI.getDevice().getId();
    var last = TransformFactory.getLastChildByTop(component, false, deviceId);
    if (last != null) {
        var marginValue = 10;
        var lastTop = parseInt(last.getProperty(TOP, deviceId).value);

        var lastHeightProperty = last.getProperty(HEIGHT, deviceId);
        var lastHeightValue = parseInt(lastHeightProperty ? lastHeightProperty.value : TransformFactory.staticHeightComponent(last));

        var componentHeightProperty = component.getProperty(HEIGHT, deviceId);
        var componentHeightValue = parseInt(componentHeightProperty ? componentHeightProperty.value : TransformFactory.staticHeightComponent(component));
        if (componentHeightValue < lastTop + lastHeightValue) {
            component.setProperty(HEIGHT, lastTop + lastHeightValue + marginValue + 'px', false, deviceId);
            if (component.proto.name === HEADER) {
                var main = UI.getMain();
                main.setProperty(TOP, lastTop + lastHeightValue + marginValue + 'px', false, deviceId);
            } else if (component.proto.name === FORM) {
                //change parent height if big form
                var posibleHeight = parseInt(component.getProperty(TOP).value) + lastTop + lastHeightValue + marginValue*2;
                if (posibleHeight > parseInt(component.parentComponent.getProperty(HEIGHT, deviceId).value)) {
                    component.parentComponent.setProperty(HEIGHT, posibleHeight + 'px', false, deviceId);
                }
            }
        }
    }
}

TransformFactory.setNewPosition = function (component) {
    if (component.proto.name !== 'page' &&
            component.proto.name !== 'header' &&
            component.proto.name !== 'footer' &&
            component.proto.name !== 'main' &&
            component.proto.name !== 'body') {
        _.forEach(UI.getTemplateProperty('devices'),
            function (deviceId) {
                if (deviceId !== UI.getDefaultDeviceId()) {
                    var device = UI.getDevice(deviceId);
                    if (!device.isHaveComponentProperties()) {
                        Device.setDeviceRequest(device);
                    }
                    TransformFactory.runTransform(component, deviceId);
                    var marginValue = 10;
                    var parent = component.parentComponent;
                    if (parent != null) {
                        var last = TransformFactory.getLastChildByTop(parent, component.id, deviceId);
                        var lastTop = 0;
                        var lastHeight = 0;
                        if (last != null && parent.children.length > 1) {
                            lastTop = parseInt(last.getProperty(TOP, deviceId).value);
                            var height = last.getProperty(HEIGHT, deviceId);
                            lastHeight = parseInt(height ? height.value : TransformFactory.staticHeightComponent(last));
                        }
                        component.setProperty(TOP, lastTop + lastHeight + marginValue + 'px', false, deviceId);
                        component.setProperty(LEFT, marginValue + 'px', false, deviceId);
                        TransformFactory.checkMinSizeContainer(parent, deviceId);
                    }
                }
            });
    }
}

TransformFactory.refreshStoreProductContainer = function(parent, component) {
    _.forEach(UI.getTemplateProperty('devices'),
        function (deviceId) {
            if (deviceId !== UI.getDefaultDeviceId()) {
                var device = UI.getDevice(deviceId);
                if (!device.isHaveComponentProperties()) {
                    Device.setDeviceRequest(device);
                }
                if (component) {
                    TransformFactory.runTransform(component, deviceId);
                }

                var children = _.map(parent.children, function(item) { return new TransformComponent(item, parent); });
                TransformFactory.sortAndSet(TransformFactory.sortByList(children), 25, deviceId);
            }
        });
}

TransformFactory.runTransform = function (component, deviceId, isFirstRun) {
    deviceId = deviceId ? deviceId : UI.getDevice().getId();    

    if (TransformFactory.rules[deviceId]) {
        if (component.children && component.children.length) {
            _.forEach(component.children,
                function (child) {
                    TransformFactory.runTransform(child);
                });
        }
        if (TransformFactory.rules[deviceId][component.proto.name]) {
            _.forEach(_.keys(TransformFactory.rules[deviceId][component.proto.name]),
                function (property) {
                    TransformFactory.rules[deviceId][component.proto.name][property](component, property, deviceId);
                });
        }
        if (!UI.hasDeviceIdInTemplateData(deviceId)) {
            if (component.proto.name === 'body') {
                //sort elements for first run
                TransformFactory.transformMobileOrder(component, deviceId);
                UI.addDeviceIdToTemplateData(deviceId);                   
            }
        }
        if (isFirstRun) {
            //sort elements for first run
            TransformFactory.transformMobileOrder(component, deviceId);
        }
    }
}

TransformFactory.rules = {
    '6de56cf1-91c5-4cb8-bc6c-f4cfb4904750': {
        body: {
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            'only-desktop': function (component, property, deviceId) {
                component.removeProperty(property, deviceId);
            }
        },
        header: {
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            }
        },
        footer: {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            }
        },
        main: {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            }
        },
        page: {
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            }
        },
        menu: {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '300px');
            },
            'font-size': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'border-width': function (component, property, deviceId) {
                var borderw = component.getProperty(BORDER_WIDTH, UI.getDefaultDeviceId()).value;
                component.addPropertyIfNotExists(property, deviceId, borderw);
            },
            'border-color': function (component, property, deviceId) {
                var borderc = component.getProperty(BORDER_COLOR, UI.getDefaultDeviceId()).value;
                component.addPropertyIfNotExists(property, deviceId, borderc);
            }
        },
        button: {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '300px');
            }
        },
        'house-photo-tour': {
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '300px');
            },
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            'caption-position': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, 'top');
            }
        },
        'house-photo-tour-item': {
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            }
        },

        'sound': {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            }
        },

        'evaluate-home': {
            'height': function (component, property, deviceId) {
                var height = 850;
                var useCaptcha = component.getProperty(USE_CAPTCHA, UI.getDefaultDeviceId()).value.toBoolean();
                component.addPropertyIfNotExists(property, deviceId, (useCaptcha ? height + 150 : height ) + 'px' );
            },
            'width': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '300px');
            },
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            }
        },
        'contact-us': {
            height: function (component, property, deviceId) {
                var type = component.getProperty(TYPE, UI.getDefaultDeviceId()).value;
                var height;
                var useCaptcha = component.getProperty(USE_CAPTCHA, UI.getDefaultDeviceId()).value.toBoolean();
                if (type === "Request a quote") {
                    height = 450;
                    component.addPropertyIfNotExists(property, deviceId, (useCaptcha ? height + 150 : height) + 'px');
                } else if (type === "Make an Appointment") {
                    height = 830;
                    component.addPropertyIfNotExists(property, deviceId, (useCaptcha ? height + 150 : height) + 'px');
                }
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '300px');
            },
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            }
        },

        'gallery': {
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '300px');
            },
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            'caption-position': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, 'bottom');
            },
            'image-stretching': function (component, property, deviceId) {
                var oldProperty = component.getProperty(IMAGE_STRETCHING).value;
                component.addPropertyIfNotExists(property, deviceId, oldProperty);
            }
        },
        'gallery-item': {
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            }
        },

        'div': {
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '300px');
            },
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            }
        },
        'img': {
            height: function (component, property, deviceId) {
                var width = parseInt(component.getProperty(WIDTH, UI.getDefaultDeviceId()).value);
                var height = parseInt(component.getProperty(HEIGHT, UI.getDefaultDeviceId()).value);
                if (width > 300) {
                    component.addPropertyIfNotExists(property, deviceId, parseInt((300 * height) / width) + 'px');
                } else {
                    component.addPropertyIfNotExists(property, deviceId, height + 'px');
                }
            },
            width: function (component, property, deviceId) {
                var width = parseInt(component.getProperty(WIDTH, UI.getDefaultDeviceId()).value);
                if (width > 300) {
                    component.addPropertyIfNotExists(property, deviceId, '300px');
                } else {
                    component.addPropertyIfNotExists(property, deviceId, width + 'px');
                }
            },
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            }
        },
        'anchor': {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            }
        },
        'slideshow': {
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '300px');
            },
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            'caption-position': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, 'bottom');
            }
        },
        'mortgage-calculator': {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '600px');
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '300px');
            }
        },
        'link': {
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            }
        },
        'html-container': {
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '300px');
            },
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            'border-width': function (component, property, deviceId) {
                var borderw = component.getProperty(BORDER_WIDTH, UI.getDefaultDeviceId()).value;
                component.addPropertyIfNotExists(property, deviceId, borderw);
            },
            'border-color': function (component, property, deviceId) {
                var borderc = component.getProperty(BORDER_COLOR, UI.getDefaultDeviceId()).value;
                component.addPropertyIfNotExists(property, deviceId, borderc);
            }
        },
        'frame': {
            height: function (component, property, deviceId) {
                var width = parseInt(component.getProperty(WIDTH, UI.getDefaultDeviceId()).value);
                var height = parseInt(component.getProperty(HEIGHT, UI.getDefaultDeviceId()).value);
                if (width > 300) {
                    component.addPropertyIfNotExists(property, deviceId, parseInt((300 * height) / width) + 'px');
                } else {
                    component.addPropertyIfNotExists(property, deviceId, height + 'px');
                }
            },
            width: function (component, property, deviceId) {
                var width = parseInt(component.getProperty(WIDTH, UI.getDefaultDeviceId()).value);
                if (width > 300) {
                    component.addPropertyIfNotExists(property, deviceId, '300px');
                } else {
                    component.addPropertyIfNotExists(property, deviceId, width + 'px');
                }
            },
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            }
        },
        'heading': {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '300px');
            }
        },
        'map': {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                var heightOld = parseInt(component.getProperty(HEIGHT, UI.getDefaultDeviceId()).value);
                var widthOld = parseInt(component.getProperty(WIDTH, UI.getDefaultDeviceId()).value);
                var height = parseInt(heightOld * 300 / widthOld);
                component.addPropertyIfNotExists(property, deviceId, height + 'px');
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '300px');
            }
        },
        'headertext': {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                var heightOld = parseInt(component.getProperty(HEIGHT, UI.getDefaultDeviceId()).value);
                var widthOld = parseInt(component.getProperty(WIDTH, UI.getDefaultDeviceId()).value);
                var height = parseInt((widthOld / 300) * heightOld);
                component.addPropertyIfNotExists(property, deviceId, height + 'px');
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '300px');
            },
            'text': function (component, property, deviceId) {
                var oldText = component.getProperty(TEXT, UI.getDefaultDeviceId()).value;
                component.addPropertyIfNotExists(property, deviceId, oldText);
            },
            'border-width': function (component, property, deviceId) {               
                component.addPropertyIfNotExists(property, deviceId, '0px');
            },
            'border-color': function (component, property, deviceId) {
                var borderc = component.getProperty(BORDER_COLOR, UI.getDefaultDeviceId()).value;
                component.addPropertyIfNotExists(property, deviceId, borderc);
            }
        },
        'paragraph': {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                var heightOld = parseInt(component.getProperty(HEIGHT, UI.getDefaultDeviceId()).value);
                var widthOld = parseInt(component.getProperty(WIDTH, UI.getDefaultDeviceId()).value);
                var height = parseInt((widthOld / 300) * heightOld);
                component.addPropertyIfNotExists(property, deviceId, height + 'px');
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '300px');
            },
            'text': function (component, property, deviceId) {
                var oldText = component.getProperty(TEXT, UI.getDefaultDeviceId()).value;
                component.addPropertyIfNotExists(property, deviceId, oldText);
            },
            'border-width': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '0px');
            },
            'border-color': function (component, property, deviceId) {
                var borderc = component.getProperty(BORDER_COLOR, UI.getDefaultDeviceId()).value;
                component.addPropertyIfNotExists(property, deviceId, borderc);
            }
        },

        'list': {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                var width = parseInt(component.getProperty(WIDTH, UI.getDefaultDeviceId()).value);
                var height = parseInt(component.getProperty(HEIGHT, UI.getDefaultDeviceId()).value);
                if (width > 300) {
                    component.addPropertyIfNotExists(property, deviceId, parseInt((width * height) / 300) + 'px');
                } else {
                    component.addPropertyIfNotExists(property, deviceId, height + 'px');
                }
            },
            width: function (component, property, deviceId) {
                var width = parseInt(component.getProperty(WIDTH, UI.getDefaultDeviceId()).value);
                if (width > 300) {
                    component.addPropertyIfNotExists(property, deviceId, '300px');
                } else {
                    component.addPropertyIfNotExists(property, deviceId, width + 'px');
                }
            },
            'font-size': function (component, property, deviceId) {                
                component.addPropertyIfNotExists(property, deviceId);
            }
        },
        'list-item': {
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            }
        },
        'pdf': {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                var width = parseInt(component.getProperty(WIDTH, UI.getDefaultDeviceId()).value);
                var height = parseInt(component.getProperty(HEIGHT, UI.getDefaultDeviceId()).value);
                if (width > 300) {
                    component.addPropertyIfNotExists(property, deviceId, parseInt((300 * height) / width) + 'px');
                } else {
                    component.addPropertyIfNotExists(property, deviceId, height + 'px');
                }
            },
            width: function (component, property, deviceId) {
                var width = parseInt(component.getProperty(WIDTH, UI.getDefaultDeviceId()).value);
                if (width > 300) {
                    component.addPropertyIfNotExists(property, deviceId, '300px');
                } else {
                    component.addPropertyIfNotExists(property, deviceId, width + 'px');
                }
            }
        },
        'video': {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                var width = parseInt(component.getProperty(WIDTH, UI.getDefaultDeviceId()).value);
                var height = parseInt(component.getProperty(HEIGHT, UI.getDefaultDeviceId()).value);
                if (width > 300) {
                    component.addPropertyIfNotExists(property, deviceId, parseInt((300 * height) / width) + 'px');
                } else {
                    component.addPropertyIfNotExists(property, deviceId, height + 'px');
                }
            },
            width: function (component, property, deviceId) {
                var width = parseInt(component.getProperty(WIDTH, UI.getDefaultDeviceId()).value);
                if (width > 300) {
                    component.addPropertyIfNotExists(property, deviceId, '300px');
                } else {
                    component.addPropertyIfNotExists(property, deviceId, width + 'px');
                }
            }
        },
        'form': {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '300px');
            },
            'border-width': function (component, property, deviceId) {
                var borderw = component.getProperty(BORDER_WIDTH, UI.getDefaultDeviceId()).value;
                component.addPropertyIfNotExists(property, deviceId, borderw);
            },
            'border-color': function (component, property, deviceId) {
                var borderc = component.getProperty(BORDER_COLOR, UI.getDefaultDeviceId()).value;
                component.addPropertyIfNotExists(property, deviceId, borderc);
            }
        },
        '_checkbox': {
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            }
        },
        '_submit': {
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '280px');
            },
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            'font-size': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            }
        },
        '_attachment': {
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '280px');
            },
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            'font-size': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            }
        },
        '_cancel': {
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '280px');
            },
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            'font-size': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            }
        },
        '_editor-item': {},
        '_radio-list': {
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '280px');
            },
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            }
        },
        '_textarea': {
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '280px');
            },
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            }
        },
        '_label': {
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '280px');
            },
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            }
        },
        '_captcha': {
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '136px');
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '156px');
            },
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            }
        },
        '_textbox': {
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '280px');
            },
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            }
        },
        '_select-list': {
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '280px');
            },
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            }
        },
        'signin': {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '300px');
            }
        },

        'store': {},
        'store-cart': {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '300px');
            }
        },
        'store-cart-link': {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            }
        },
        'store-thank-you': {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '680px');
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '300px');
            }
        },
        'manage-store-products': {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, undefined, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '300px');
            }
        },
		'store-product': {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '1200px');
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '300px');
            }
        },
        'store-product-add-to-cart': {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '280px');
            }
        },
        'store-product-description': {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '280px');
            }
        },
        'store-product-images': {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '280px');
            }
        },
        'store-product-options': {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '280px');
            }
        },
        'store-product-price': {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '280px');
            }
        },
        'store-product-quantity': {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '280px');
            }
        },
        'store-product-sku': {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '280px');
            }
        },
        'store-product-social': {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '280px');
            }
        },
        'store-product-title': {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '280px');
            }
        },
        'store-gallery': {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {
                var rows = parseInt(component.getProperty(ROWS, UI.getDefaultDeviceId()).value);
                var cols = parseInt(component.getProperty(COLUMNS, UI.getDefaultDeviceId()).value);
                var height = (parseInt(component.getProperty(HEIGHT, UI.getDefaultDeviceId()).value) - 70 - 43) / rows;
                var width = parseInt(component.getProperty(WIDTH, UI.getDefaultDeviceId()).value) / cols;
                var newHeight = Math.ceil((300 * height) / width) * 2 + 70 + 43;                
                component.addPropertyIfNotExists(property, deviceId, newHeight + 'px');
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '300px');
            },
            columns: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '1');
            },
            rows: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '2');
            }
        },
        'store-categories' : {
            'top': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function (component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function (component, property, deviceId) {                
                component.addPropertyIfNotExists(property, deviceId);
            },
            width: function (component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '300px');
            }
        },
        'blogging': {
            'top': function(component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'left': function(component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId);
            },
            'z-index': function(component, property, deviceId) {
                var zIndex = parseInt(component.getProperty(Z_INDEX, UI.getDefaultDeviceId()).value);
                component.addPropertyIfNotExists(property, deviceId, zIndex);
            },
            height: function(component, property, deviceId) {
                var height = parseInt(component.getProperty(HEIGHT, UI.getDefaultDeviceId()).value);
                var width = parseInt(component.getProperty(WIDTH, UI.getDefaultDeviceId()).value);
                var newHeight = parseInt((300 * height) / width);
                component.addPropertyIfNotExists(property, deviceId, newHeight + 'px');
            },
            width: function(component, property, deviceId) {
                component.addPropertyIfNotExists(property, deviceId, '300px');
            }
        }
    }
};
;
var ProxyService = function () { }


//public const string Registration = "registrations";
//public const string Login = "login";
//public const string Logout = "logout";
//public const string ConfirmRegistration = "confirm";
//public const string Orders = "order";
//public const string Sites = "site";
//public const string Stores = "store";
//public const string Users = "user";
//public const string Categories = "category";
//public const string Products = "product";
//public const string Password = "password";
//public const string CheckToken = "checktoken";


ProxyService.send = function (data, type, method, filter, callback, sync) {
    data = defined(data) ? data : {};
    method = defined(method) ? method : 'GET';
    filter = defined(filter) ? filter : {};
    callback = defined(callback) ? callback : function () { };
    sync = defined(sync) ? !!sync : false;
    var ignoreErrorMessage = false;

    var options = {
        method: method,
        async: !sync,
        dataType: "json",
        crossDomain: true,
        success: function (response) {
            Helpers.consoleLog(method, options.url, response);
            if (response) {
                if (response.token && UI.authService) {
                    UI.authService.setToken(response.token);
                }
                callback(response.data || {});
            }
        },
        error: function (response) {
            Helpers.consoleLog(method, options.url, response);
            if (!ignoreErrorMessage) {
                ProxyService.errorHandler(response);
            }
        },
        complete: function () {
            Application.removeLocker();
        }
    };

    var APIURL = UI.getSetting('webApiDomain') + '/';
    var url = APIURL;
    switch (type) {
        case 'sign-in':
            url += 'login';
            if (data.key) {
                ignoreErrorMessage = true;
            }
            break;
        case 'sign-up':
            url += 'registrations';
            break;
        case 'sign-out':
            url += 'logout';
            break;
        case 'password':
            url += 'password';
            break;
        case 'service':
            url += 'service/';
            if (method === 'GET') {
                if (!_.isEmpty(filter)) {
                    //get list 
                    url += '?';
                    _.forEach(_.keys(filter),
                        function (key) {
                            url += key + '=' + filter[key] + '&';
                        });
                } else {
                    url += data.id;
                }
            }
            break;
        case 'check-token':
            url += 'checktoken';
            if (data.token) {
                options.headers = { "Authorization": data.token };
                ignoreErrorMessage = true;
            }
            break;
        case 'category':
            url += 'category/';
            if (method === 'GET') {
                if (!_.isEmpty(filter)) {
                    //get list 
                    url += '?';
                    _.forEach(_.keys(filter),
                        function (key) {
                            url += key + '=' + filter[key] + '&';
                        });
                } else {
                    url += data.id;
                }
            } else if (method === 'DELETE') {
                if (!_.isEmpty(filter)) {
                    //delete list 
                    url += '?';
                    _.forEach(_.keys(filter),
                        function (key) {
                            url += key + '=' + filter[key] + '&';
                        });
                } else {
                    url += data.id;
                }
            }
            break;
        case 'user':
            url += 'user/';
            if (method === 'GET') {
                if (!_.isEmpty(filter)) {
                    //get list 
                    url += '?';
                    _.forEach(_.keys(filter),
                        function (key) {
                            url += key + '=' + filter[key] + '&';
                        });
                } else {
                    url += data.id;
                    if (data.token) {
                        options.headers = { "Authorization": data.token };
                        ignoreErrorMessage = true;
                    }
                }
            } else if (method === 'DELETE') {
                if (!_.isEmpty(filter)) {
                    //delete list 
                    url += '?';
                    _.forEach(_.keys(filter),
                        function (key) {
                            url += key + '=' + filter[key] + '&';
                        });
                } else {
                    url += data.id;
                }
            }
            break;
        case 'formmail':
            url = '/Account/GetControlFormMail/';
            if (method === 'GET') {
                if (!_.isEmpty(filter)) {
                    //get list 
                    url += '?';
                    _.forEach(_.keys(filter),
                        function (key) {
                            url += key + '=' + filter[key] + '&';
                        });
                } else {
                    url += data.id;
                }
            } else if (method === 'DELETE') {
                if (!_.isEmpty(filter)) {
                    //delete list 
                    url += '?';
                    _.forEach(_.keys(filter),
                        function (key) {
                            url += key + '=' + filter[key] + '&';
                        });
                } else {
                    url += data.id;
                }
            }
            break;
        case 'product':
            url += 'product/';
            if (method === 'GET') {
                if (!_.isEmpty(filter)) {
                    url += '?';
                    _.forEach(_.keys(filter),
                        function (key) {
                            url += key + '=' + filter[key] + '&';
                        });
                } else {
                    url += data.id;
                }
            }
            else if (method === 'DELETE') {
                if (!_.isEmpty(filter)) {
                    //delete list 
                    url += '?';
                    _.forEach(_.keys(filter),
                        function (key) {
                            url += key + '=' + filter[key] + '&';
                        });
                } else {
                    url += data.id;
                }
            }
            break;
        case 'order':
            url += 'order/';
            if (method === 'GET') {
                if (!_.isEmpty(filter)) {
                    //get list 
                    url += '?';
                    _.forEach(_.keys(filter),
                        function (key) {
                            url += key + '=' + filter[key] + '&';
                        });
                } else {
                    url += data.id;
                }
            } else if (method === 'DELETE') {
                if (!_.isEmpty(filter)) {
                    //delete list 
                    url += '?';
                    _.forEach(_.keys(filter),
                        function (key) {
                            url += key + '=' + filter[key] + '&';
                        });
                } else {
                    url += data.id;
                }
            }
            break;
        case 'enum':
            url += 'enum/';
            if (method === 'GET') {
                url += data.name;
            }
            break;
        case 'taxrate':
            url += 'taxrate/';
            if (method === 'GET') {
                if (!_.isEmpty(filter)) {
                    url += '?';
                    _.forEach(_.keys(filter),
                        function (key) {
                            url += key + '=' + filter[key] + '&';
                        });
                } else {
                    url += data.id;
                }
            }
            else if (method === 'DELETE') {
                if (!_.isEmpty(filter)) {
                    //delete list 
                    url += '?';
                    _.forEach(_.keys(filter),
                        function (key) {
                            url += key + '=' + filter[key] + '&';
                        });
                } else {
                    url += data.id;
                }
            }
            break;
        case 'shippingrate':
            url += 'shippingrate/';
            if (method === 'GET') {
                if (!_.isEmpty(filter)) {
                    url += '?';
                    _.forEach(_.keys(filter),
                        function (key) {
                            url += key + '=' + filter[key] + '&';
                        });
                } else {
                    url += data.id;
                }
            }
            else if (method === 'DELETE') {
                if (!_.isEmpty(filter)) {
                    //delete list 
                    url += '?';
                    _.forEach(_.keys(filter),
                        function (key) {
                            url += key + '=' + filter[key] + '&';
                        });
                } else {
                    url += data.id;
                }
            }
            break;
        case 'sendemail':
            url += 'service/sendemail';
            break;
        case 'resetstore':
            url += 'service/resetstore';
            break;
        case 'storesettings':
            url += 'storesettings';
            if (method === 'GET') {
                url += '/0';
            }
            break;
    }

    var addFake = function () {
        var fakeParam = '';
        if (!UI.getSetting('ispreview')) {
            if (url.indexOf('?') !== -1) {
                if (url.slice(-1) !== '&') {
                    fakeParam += '&';
                }
                fakeParam += 'fake=1';
            } else {
                fakeParam += '?fake=1';
            }
        }
        return fakeParam;
    }

    //if type not defined
    if (url === APIURL) return;

    if (method !== 'GET') {
        options.data = '{ "data": ' + JSON.stringify(data) + '}';
    } else {
        if (data.fake) {
            url += addFake();
        }
    }

    options.url = url;
    Application.addLocker();
    UI.ajaxSetup();
    //console.log('headers', $.ajaxSettings.headers);
    $.ajax(options);    
}

ProxyService.errorHandler = function (response) {
    var self = this;
    self.handleStatus = function (response) {
        switch (response.status) {
            case 0:
                return 'Connection error!';
            default:
                return response.statusText;
        }
    }

    Application.showOkDialog('Error', response.responseJSON ? response.responseJSON.message : self.handleStatus(response));
};
var ActionFactory = function () { }

ActionFactory.actionFor = function (component, type) {
    if (type === ACTION_SIGN_OUT) {
        switch (component.name) {
            case SIGNIN:
                return new SignInButtonSignOutAction(component);
            default:
                return null;
        }
    } else if (type === ACTION_SIGN_IN) {
        switch (component.name) {
            case SIGNIN:
                return new SignInButtonSignInAction(component);
            default:
                return null;
        }
    } else if (type === ACTION_ADD_TO_FORM) {
        return new AddComponentToFormAction(component);
    } else if (type === ACTION_REMOVE_FROM_FORM) {
        return new RemoveComponentFromFormAction(component);
    } else if (type === ACTION_EDITOR_OPEN) {
        switch (component.name) {
        case STORE_GALLERY:
            return new EditorOpenStoreGalleryAction(component);
        default:
            return null;
        }
    } else if (type === ACTION_EDITOR_CLOSED) {
        switch (component.name) {
        case STORE_GALLERY:
            return new EditorClosedStoreGalleryAction(component);
        default:
            return null;
        }        
    }
};

var SignInButtonSignOutAction = function (component) {
    var action = function (component) {       
        UI.actionService.runActionForComponent(component, ACTION_ADD_TO_FORM);
        $('#sign-out-block-wrapper').html('');
    };
    return action;
};

var SignInButtonSignInAction = function (component) {
    var action = function (component) {        
        //remove component from form
        UI.actionService.runActionForComponent(component, ACTION_REMOVE_FROM_FORM);

        Helpers.createUserBar({
            profile: function () {
                //view profile event
                UI.componentService.addModalContentToForm(component, '#signin-profile');
            },
            signout: function () {
                //sign out event
                UI.authService.logout();
            }
        });        
    };
    return action;
};

var AddComponentToFormAction = function () {
    var action = function (component, data) {
        //check parent form
        var isHide, parentElement = null;
        if (component.parentComponent == null || component.parentComponent.proto.name !== FORM) {
            isHide = component.getProperty(HIDE_COMPONENT);
            if (isHide == null || !isHide.value.toBoolean() || UI.getSetting("showHidden")) {                
                if (component.proto.name !== SIGNIN || !UI.authService || !UI.authService.isAuth()) {                    
                    parentElement = UI.componentService.addComponentToForm(component, data);
                    component.viewer();
                    if (component.proto.name === MENU) {
                        UI.renderMenus();
                    }
                }
            }
        } else {
            isHide = component.parentComponent.getProperty(HIDE_COMPONENT);
            if (isHide == null || !isHide.value.toBoolean() || UI.getSetting("showHidden")) {
                parentElement = UI.componentService.addComponentToForm(component, data);
                component.viewer();
            }
        }
        return parentElement;
    };
    return action;
}

var RemoveComponentFromFormAction = function () {
    var action = function (component) {
        $(component.getUISelector()).remove();
    };
    return action;
}

var EditorOpenStoreGalleryAction = function () {
    var action = function (component) {
        var element = $(component.getUISelector());
        var page = element.parent('.page');
        var lastElement = Resizer.calculateContainerBottomLine(page[0], 0);
        //calc difference 
        element.data('lastElementDiff', page.height() - lastElement);
    };
    return action;
}

var EditorClosedStoreGalleryAction = function () {
    var action = function (component) {
        var element = $(component.getUISelector());
        var page = element.parent('.page');
        var lastElementHeight = Resizer.calculateContainerBottomLine(page[0], 0);
        var diff = parseInt(element.data('lastElementDiff'));
        element.data('lastElementDiff', null);
        if (diff !== NaN && page.height() - lastElementHeight !== diff) {
            var pageComponent = UI.siteComponentRepository.lookupData({ id: page.getId() });
            var height = lastElementHeight + diff;
            UI.undoManagerAddSimple(pageComponent,
                HEIGHT,
                height + 'px',
                function (val) {
                    page.height(val);
                    Resizer.recalculateHeaderFooterAndPageSize(page[0]);
                }, true);
        }
    };
    return action;
};
var AuthService = function () {
    var self = this;
    self.token = ko.observable();
    self.isAuth = ko.observable();
    self.user = ko.observable({});
    
    //subscribe to change token
    self.token.subscribe(function (newValue) {        
        if (!newValue) {
            self.user({});
            delete $.ajaxSettings.headers["Authorization"];
        } else {
            $.ajaxSetup({
                headers: { "Authorization": newValue }
            });
        }
        self.isAuth(!!newValue);        
    });

    //todo: check first run
    //bool value auth
    self.isAuth.subscribe(function (newValue) {
        if (newValue) {
            //sign-in
            UI.actionService.runAction(ACTION_SIGN_IN);
        } else {
            //sign-out
            UI.actionService.runAction(ACTION_SIGN_OUT);
        }
    });
        
    //check token and connection
    self.isAuthentificate = function (init) {
        var data = { id: Guid.empty() };
        var run = true;
        if (init) {            
            var key = Helpers.getQueryParamValue('key');
            var resetKey = Helpers.getQueryParamValue('resetKey');
            if (key || resetKey) {
                //try login
                run = false;
                self.login({ key: key || resetKey }, function() {
                    if (resetKey) {
                        //show change password
                        UI.componentService.addModalContentToForm(null, '#signin-change-password', { resetKey: resetKey});
                    }
                }, true);
            } else {
                if (LocalStorageService.GetItem('user_token')) {
                    data.token = LocalStorageService.GetItem('user_token');
                } else {
                    run = false;
                }
            }
        }
        if (run) {
            ProxyService.send(data,
                'user',
                'GET',
                {},
                function(data) {
                    if (data && data[0]) {
                        self.user(new User(data[0]));
                        self.setToken(LocalStorageService.GetItem('user_token'));
                    }
                },
                init);
        }
    }

    //set token and write to localStorage
    self.setToken = function (data) {                
        self.token(data);
        if (data) {
            LocalStorageService.SetItem('user_token', data);
        } else {
            LocalStorageService.RemoveItem('user_token');
        }
    }

    self.getToken = function() {
        return self.token();
    }
    
    //sign in
    self.login = function (data, callback, sync) {
        sync = defined(sync) ? sync : false;
        ProxyService.send(data, 'sign-in', "POST",
            {}, function () {
                ProxyService.send({ id: Guid.empty() }, 'user', "GET", {}, function (data) {
                    if (data && data[0]) {
                        self.user(new User(data[0]));
                        callback();
                    }
                });
            }, sync);
    }

    //sign out
    self.logout = function () {
        ProxyService.send({}, 'sign-out', 'DELETE', {}, function () {            
            self.setToken('');
        });
    };

    //sign up
    self.signup = function (data, callback) {
        ProxyService.send(data, 'sign-up', 'POST', {}, function () {
            callback();
        });
    };
    
    //reset password
    self.resetPassword = function (data) {
        ProxyService.send(data, 'password', 'POST', {}, function () {
            $('#component-modal .modal .modal-body').html("Link to reset your pass was sent to your email!");
        });
    }
    
    //chenge password
    self.changePassword = function (data, callback) {
        ProxyService.send(data, 'password', 'PUT', {}, function () {
            $('#component-modal .modal').modal('hide');
            callback();
        });
    }    
};;
var ViewerEventsFactory = function () { }

ViewerEventsFactory.attachEvents = function (component, type, data) {
    switch (type) {
        case '#signin-login':
            return new LoginFormEvents(component);
        case '#signin-signup':
            return new SignUpFormEvents(component);
        case '#signin-reset-password':
            return new ResetPasswordFormEvents(component);
        case '#signin-change-password':
            return new ChangePasswordFormEvents(component, data);
        case '#signin-profile':
            return new ProfileFormEvents(component);
        case '#signin-manage-users':
            return new ManageUsersFormEvents(component);
        case MANAGE_MAILS_COMPONENT:
            return new ManageMailsFormEvents(component, data);
        case '#manage-store-products':
            return new ManageStoreProductsFormEvents(component);
        case '#signin-manage-users-current':
            return new ManageUsersCurrentFormEvents(component);
        case MANAGE_MAILS_CURRENT_COMPONENT:
            return new ManageMailsCurrentFormEvents(component, data);
        case '#signin-secure-page':
            return new SecurePageEvents(component);
        case '#signin-send-email':
            return new SendEmailEvents(component);
        default:
            return null;
    }
}

var LoginFormEvents = function (component) {
    //bind forgot password link
    $('#forgot-password').click(function () {
        if (UI.getSetting('ispublished')) {
            UI.componentService.addModalContentToForm(component, '#signin-reset-password');
        } else {
            Application.showOkDialog('Error', 'Available only on published site!');
        }
    });

    //bind sign-up link
    $('#sign-up').click(function () {
        if (UI.getSetting('ispublished')) {
            UI.componentService.addModalContentToForm(component, '#signin-signup');
        } else {
            Application.showOkDialog('Error', 'Available only on published site!');
        }
    });

    //submit login form
    $('#login-form.form').submit(function (e) {
        e.preventDefault();

        var email = $('#email').val();
        var password = $('#password').val();

        UI.authService.login({ userName: email, password: password }, function () {
            $('#component-modal .modal').modal('hide');
        });
    });
}

var SignUpFormEvents = function (component) {
    //bind sign-in link
    $('#login').click(function () {
        UI.componentService.addModalContentToForm(component, '#signin-login');
    });

    var email = document.getElementById("email"),
        password = document.getElementById("password"),
        confirmPassword = document.getElementById("password-confirm");

    function validatePassword() {
        if (password.value !== confirmPassword.value) {
            confirmPassword.setCustomValidity("Passwords Don't Match");
        } else {
            confirmPassword.setCustomValidity('');
        }
    }

    password.onchange = validatePassword;
    confirmPassword.onkeyup = validatePassword;


    //submit sing up form
    $('#signup-form.form').submit(function (e) {
        e.preventDefault();
        UI.authService.signup({ userName: email.value, password: password.value },
            function () {
                UI.componentService.addModalContentToForm(component, '#signin-signup-success');
            });
    });
}

var ResetPasswordFormEvents = function (component) {
    //bind sign-in link
    $('#login').click(function () {
        UI.componentService.addModalContentToForm(component, '#signin-login');
    });

    //submit reset password
    $('#reset-password-form.form').submit(function (e) {
        e.preventDefault();
        var email = $('#email').val();

        UI.authService.resetPassword({ userName: email });
    });
}

var ChangePasswordFormEvents = function (component, data) {
    var password = document.getElementById("password"),
        newPassword = document.getElementById("new-password"),
        confirmPassword = document.getElementById("password-confirm");

    if (data && data.resetKey) {
        password.parentNode.remove();
    }

    function validatePassword() {
        if (newPassword.value !== confirmPassword.value) {
            confirmPassword.setCustomValidity("Passwords Don't Match");
        } else {
            confirmPassword.setCustomValidity('');
        }
    }

    newPassword.onchange = validatePassword;
    confirmPassword.onkeyup = validatePassword;    

    //event handler
    $('#profile').on('click', function () {
        UI.componentService.addModalContentToForm(component, '#signin-profile');
    });

    //submit reset password
    $('#change-password.form').submit(function (e) {
        e.preventDefault();
        var changePasswordObj = { newPassword: newPassword.value };
        if (data && data.resetKey) {
            changePasswordObj.resetKey = data.resetKey;
        } else {
            changePasswordObj.oldPassword = password.value;
        }
        UI.authService.changePassword(changePasswordObj, function () {
            UI.componentService.addModalContentToForm(component, '#signin-profile');
        });
    });
}

var ProfileFormEvents = function (component) {
    var model = UI.getViewModel('#signin-profile', { component: component, user: UI.authService.user });
    ko.applyBindings(model, $('#user-profile')[0]);
}

var ManageUsersFormEvents = function (component) {
    //get view model
    var model = UI.getViewModel('#signin-manage-users');

    //save currentPage
    $('#component-modal .modal').on('hide.bs.modal', function () {
        if ($(".grid-mvc").gridmvc().currentPage) {
            model.currentPage = $(".grid-mvc").gridmvc().currentPage;
        }        
    });

    var gridInit = {
        updateGridAction: '/Account/CommerceUsersListGrid'
    }
    if (model.currentPage) {
        gridInit.currentPage = model.currentPage;
    }    
    
    $.ajax({
        url: gridInit.updateGridAction,
        dataType: "json",
        success: function (data) {
            //paste 
            $('#grid').html(data.Html);

            //add grid to pageGrids
            $(".grid-mvc").gridmvc();
            
            //array selected users (ids)
            var selectedUsers = [];            
            
            //bind bulk options
            $('#send-emails').on('click', function () {
                if (selectedUsers.length) {
                    //bulk send email
                    model.sendEmailTo = selectedUsers;
                    UI.componentService.addModalContentToForm(component, '#signin-send-email');
                }
            });

            //bind bulk options 
            $('#delete-users').on('click', function () {
                if (selectedUsers.length) {
                    //bulk delete users
                    ProxyService.send({}, 'user', 'DELETE', {params: selectedUsers.join(',')}, function () {                        
                        //update grid
                        Helpers.gridLoadPage();
                    });
                }
            });

            //init grid
            GridMvcAjax.demo.init(gridInit, 'commerceUsersList', function () {
                //callback when grid loaded

                //clear select users when grid loaded
                selectedUsers = [];

                //inject element select all checkbox                 
                $('th.select-user').replaceWith(function () {
                    var input = $('<input type="checkbox"/>');
                    input.on('change', function (e) {
                        var users = $("td[data-name='UserId'");
                        if (e.target.checked) {
                            //selectAll event
                            users.each(function () {
                                var userid = $(this).text();
                                $("input[data-userid='" + userid + "'").prop('checked', true);
                                selectedUsers.push(userid);
                            });
                        } else {
                            //deselectAll event
                            users.each(function () {
                                var userid = $(this).text();
                                $("input[data-userid='" + userid + "'").prop('checked', false);
                                selectedUsers = [];
                            });
                        }
                    });
                    var label = $('<label class="checkbox-inline"></label>');
                    label.append(input);
                    return $('<th class="select-user text-center"></th>').append(label);
                });                

                //select user event
                pageGrids.commerceUsersList.onRowSelect(function (e) {
                    var id = e.row.UserId;
                    var index = selectedUsers.indexOf(id);

                    if (index === -1) {
                        selectedUsers.push(id);
                        $("input[data-userid='" + id + "'").prop('checked', true);
                    } else {
                        selectedUsers.splice(index, 1);
                        $("input[data-userid='" + id + "'").prop('checked', false);
                    }
                });

                //edit user event
                $('.actions-item.edit-user').on('click', function (e) {
                    e.stopPropagation();
                    Helpers.consoleLog(e.currentTarget.dataset.userid);
                    model.currentUser = e.currentTarget.dataset.userid;
                    UI.componentService.addModalContentToForm(component, '#signin-manage-users-current');
                });

                //send email event
                $('.actions-item.send-user-email').on('click', function (e) {
                    e.stopPropagation();
                    model.sendEmailTo = [e.currentTarget.dataset.userid];
                    UI.componentService.addModalContentToForm(component, '#signin-send-email');
                });

                //delete user event
                $('.actions-item.delete-user').on('click', function (e) {
                    e.stopPropagation();
                    ProxyService.send({ id: e.currentTarget.dataset.userid }, 'user', 'DELETE', {}, function () {
                        //update grid
                        Helpers.gridLoadPage();
                    });
                });                
            });

        }
    });
}

var ManageMailsFormEvents = function (component, data) {
    //get view model
    var model = UI.getViewModel(MANAGE_MAILS_COMPONENT, data, data.controlId);

    //save currentPage
    $('#component-modal .modal').on('hide.bs.modal', function () {
        if ($(".grid-mvc").gridmvc().currentPage) {
            model.currentPage = $(".grid-mvc").gridmvc().currentPage;
        }        
    });

    var gridInit = {
        updateGridAction: MANAGE_MAILS_GRID_URL
    }
    if (model.currentPage) {
        gridInit.currentPage = model.currentPage;
    }
    var componentData = data;
    $.ajax({
        url: gridInit.updateGridAction,
        data: { controlId: data.controlId },
        dataType: "json",
        success: function (data) {
            //paste 
            $('#grid').html(data.Html);

            //add grid to pageGrids
            $(".grid-mvc").gridmvc();

            //init grid
            GridMvcAjax.demo.init(gridInit, 'formMailsList', function () {
                //callback when grid loaded
                
                pageGrids.formMailsList.onRowSelect(function (e) {
                    var id = e.row.ControlFormMailId;
                    Helpers.consoleLog(id);
                    model.currentItem = id;
                    UI.componentService.addModalContentToForm(null, MANAGE_MAILS_CURRENT_COMPONENT, componentData);
                });
            });

            if (pageGrids && pageGrids.formMailsList) {
                pageGrids.formMailsList.onRowSelect(function (e) {
                    var id = e.row.ControlFormMailId;
                    Helpers.consoleLog(id);
                    model.currentItem = id;
                    UI.componentService.addModalContentToForm(null, MANAGE_MAILS_CURRENT_COMPONENT, componentData);
                });
            }

        }
    });
}

var ManageStoreProductsFormEvents = function (component) {

    var model = UI.getViewModel('#manage-store-products', { component: component });
    ko.applyBindings(model, $('#manage-store-products')[0]);


    $("#productsTab").click();
    $("#productsTab").addClass('active');
    $("#productsTab").siblings('.tablinks').removeClass('active');

    $('#component-modal').off('click');

    $('#component-modal').on('click', '.edit-product', function (e) {
        e.stopPropagation();
        var id = $(this).attr('data-productid');
        Product.view(id);
    });

    $('#component-modal').on('click', '.duplicate-product', function (e) {
        e.stopPropagation();
        var id = $(this).attr('data-productid');
        Product.duplicate(id, function () {
            PopoverHelper.hidePopovers();
            Product.handleGridRows(false);
            Helpers.gridLoadPage();
        });
    });

    $('#component-modal').on('click', '.delete-product', function (e) {
        e.stopPropagation();
        var id = $(this).attr('data-productid');
        Product.delete(id, function () {
            PopoverHelper.hidePopovers();
            Product.handleGridRows(false);
            Helpers.gridLoadPage();
        });
    });

    $('#component-modal').on('click', '.edit-category', function (e) {
        e.stopPropagation();
        var id = $(this).attr('data-categoryid');
        Category.view(id);
    });

    $('#component-modal').on('click', '.delete-category', function (e) {
        e.stopPropagation();
        var id = $(this).attr('data-categoryid');
        Category.delete(id, function () {
            Helpers.gridLoadPage();
        });
    });

    $('#component-modal').on('click', '.edit-order', function (e) {
        e.stopPropagation();
        var id = $(this).attr('data-orderid');
        Order.view(id);
    });


    $('#component-modal').on('click', '#add-product', function (e) {
        e.stopPropagation();
        var type = '#manage-store-products-product';
        var modalComponent = UI.basicComponentRepository.getAll().where({ name: type })[0];
        var compiledTemplate = TemplateFactory.templateFor({ proto: modalComponent }, VIEWER_TEMPLATE, component).compiledTemplate;
        $('#manage-store-products-tab-content').html("");
        ko.cleanNode($('#manage-store-products-tab-content')[0]);
        $('#manage-store-products-tab-content').html(compiledTemplate).promise().done(function (element) {   
            setTimeout(function (element, com) {
                ko.cleanNode($('#manage-store-products-tab-content')[0]);
                var model = UI.getViewModel('#manage-store-products-product');
                ko.applyBindings(model, element[0]);
            }, 0, element, modalComponent);
        });
    });

    $('#component-modal').on('click', '#add-category', function (e) {
        e.stopPropagation();
        var model = UI.getViewModel('#manage-store-products-category', null);
        model.addCategory(null);
    });

    $('#component-modal').bind('click', function (e) {
        //remove all batch popovers
        PopoverHelper.hidePopovers(BATCH_OPERATIONS_POPOVER_CUSTOM);
    });

    $('#manage-store-products').on('hidden.bs.modal', function () {
        $('#component-modal').unbind();
    })
}

var ManageUsersCurrentFormEvents = function (component) {
    var parentModal = UI.getViewModel('#signin-manage-users');
    if (parentModal.currentUser != null) {
        var userid = parentModal.currentUser;
        var model = UI.getViewModel('#signin-manage-users-current', userid);
        ko.applyBindings(model, $('#manage-users-current')[0]);
    } else {
        //error
        Helpers.consoleLog('error - no current user');
    }
    $('#component-modal .modal').on('hide.bs.modal', function () {
        UI.componentService.addModalContentToForm(component, '#signin-manage-users');
    });
}

var ManageMailsCurrentFormEvents = function (component, data) {
    $('#component-modal .modal').on('hide.bs.modal', function () {
        UI.componentService.addModalContentToForm(null, MANAGE_MAILS_COMPONENT, data);
    });
    var parentModal = UI.getViewModel(MANAGE_MAILS_COMPONENT, data, data.controlId);
    if (parentModal.currentItem != null) {
        var id = parentModal.currentItem;
        var model = UI.getViewModel(MANAGE_MAILS_CURRENT_COMPONENT, id);
        ko.applyBindings(model, $('#manage-mails-current')[0]);
    } else {
        //error
        Helpers.consoleLog('error - no current mail');
        $('#component-modal .modal').modal('hide');
    }
}

var SecurePageEvents = function (component) {
    $('#component-modal .modal').on('hidden.bs.modal', function () {
        UI.pager.goToHomePage();
    });

    $('#login').on('click', function () {
        UI.pager.goToHomePage();
        UI.componentService.addModalContentToForm(component, '#signin-login');
    });
}

var SendEmailEvents = function (component) {
    if (UI.viewModelRepository['#signin-manage-users']) {
        var sendEmailTo = UI.viewModelRepository['#signin-manage-users'].sendEmailTo;
        if (sendEmailTo) {
            var callback = function (users) {
                if (users) {
                    var emails = [];
                    _.forEach(users,
                        function(user) {
                            emails.push(user.userName);
                        });

                    if (emails.length > 1) {
                        $('#email').text(emails.join(', '));
                    } else {
                        $('#email').text(emails[0]);
                    }
                    $('#send-email .form').submit(function(e) {
                        e.preventDefault();
                        ProxyService.send(
                            {
                                data: sendEmailTo.join(','),
                                title: $('#email-title').val(),
                                body: $('#email-text').val()
                            },
                            'sendemail',
                            'POST',
                            {},
                            function() {
                                UI.componentService.addModalContentToForm(component, '#signin-manage-users');
                                Application.showOkDialog("Success", "The Email has been sent successfully!");
                            });
                    });
                    $('#component-modal .modal').on('hide.bs.modal',
                        function() {
                            UI.componentService.addModalContentToForm(component, '#signin-manage-users');
                        });
                } else {
                    UI.componentService.addModalContentToForm(component, '#signin-manage-users');
                    Application.showOkDialog("Error", "Can't get users!");
                }
            }          
            ProxyService.send({}, 'user', 'GET', { params: sendEmailTo.join(','), requesttype: 'items'}, callback);
        }        
    } else {
        //error
        Helpers.consoleLog('error - no viewmodel');
    }      
}

var ViewModelFactory = function () { }

ViewModelFactory.getModel = function (type, data) {
    switch (type) {
        case '#signin-login':
            return null;
        case '#signin-signup':
            return null;
        case '#signin-reset-password':
            return null;
        case '#signin-change-password':
            return null;
        case '#signin-profile':
            return new UserProfileViewModel(data);
        case '#signin-manage-users':
            return new ManageUsersViewModel();
        case '#signin-manage-users-current':
            return new ManageUsersCurrentViewModel(data);
        case MANAGE_MAILS_COMPONENT:
            return new ManageMailsViewModel();
        case MANAGE_MAILS_CURRENT_COMPONENT:
            return new ManageMailsCurrentViewModel(data);
        case '#signin-user-fields':
            return new UserFieldsViewModel();
        case 'device-management': 
            return new DeviceManagementModel(data);
        case 'hidden-elements':
            return new HiddenElementsModel(data);
        case '#manage-store-products':
            return new ManageStoreProductsViewModel(data);
        case '#manage-store-products-product':
            return new ManageStoreProductsProductViewModel(data);
        case '#manage-store-products-category':
            return new ManageStoreProductsCategoryViewModel(data);
        case '#manage-store-products-order':
            return new ManageStoreProductsOrderViewModel(data);
        case '#manage-store-products-tab-products':
            return new ManageStoreProductsTabProductsViewModel();
        case '#manage-store-products-tab-categories':
            return new ManageStoreProductsTabCategoriesViewModel();
        case '#manage-store-products-tab-orders':
            return new ManageStoreProductsTabOrdersViewModel();
        case '#manage-store-products-tab-payments':
            return new ManageStoreProductsTabPaymentsViewModel();
        case '#manage-store-products-tab-store-settings':
            return new ManageStoreProductsTabStoreSettingsViewModel();
        case '#manage-store-products-tab-shipping-and-tax':
            return new ManageStoreProductsTabShippingAndTaxViewModel(data);
        case '#manage-store-products-add-shipping-rate':
            return new ManageStoreProductsAddShippingRateViewModel(data);
        case '#manage-store-products-add-shipping-location':
            return new ManageStoreProductsAddShippingLocationViewModel();
        case '#manage-store-products-add-tax-location':
            return new ManageStoreProductsAddTaxLocationViewModel();
        case '#manage-store-products-adding-products-to-category':
            return new ManageStoreProductsAddingProductsToCategoryViewModel(data);
        case '#manage-store-products-adding-category-to-products':
            return new ManageStoreProductsAddingCategoryToProductsViewModel(data);
        case '#manage-store-products-adding-discount-to-products':
            return new ManageStoreProductsAddingDiscountToProductsViewModel(data);
        case '#manage-store-products-change-products-visability':
            return new ManageStoreProductsChangeProductsVisabilityViewModel(data);
        case 'right-navigation-panel':
            return new RightNavigationPanelModel(data);
        case 'history-management':
            return new HistoryManagementModel(data);
        case STORE_PRODUCT:
            return new StoreProductViewModel(data);
        case STORE_GALLERY:
            return new StoreGalleryViewModel(data);
        case STORE_CART:
            return new StoreCartViewModel(data);
        case STORE_THANK_YOU:
            return new StoreThankYouViewModel(data);
        case STORE_CART_LINK:
            return new StoreCartLinkViewModel(data);
        case STORE_CATEGORIES:
            return new StoreCategoriesViewModel(data);
        case 'header-settings':
            return new HeaderSettingsViewModel(data);
        default:
            return null;
    }
}

var ManageUsersViewModel = function () {
    var self = this;    
    self.currentUser = null;
    self.currentPage = null;
    self.sendEmailTo = null;
    self.init = function () { }
    self.init();
    return self;
}

var ManageMailsViewModel = function () {
    var self = this;    
    self.currentItem = null;
    self.currentPage = null;
    self.init = function () { }
    self.init();
    return self;
}

var ManageStoreProductsViewModel = function (data) {

    var self = this;
    self.currentProduct = null;
    self.currentPage = null;
    self.component = data.component;


    self.openTab = function (tabName, pageName, gridName, tabData, e) {
        $("#" + tabName).addClass('active');
        $("#" + tabName).siblings('.tabcontent').removeClass('active');

        $(e.currentTarget).addClass('active');
        $(e.currentTarget).siblings('.tablinks').removeClass('active');

        $("#" + tabName).css('display', 'block');
        $("#" + tabName).siblings('.tabcontent').css('display', 'none');

        var modalComponent = UI.basicComponentRepository.getAll().where({ name: '#manage-store-products-tab-' + tabName })[0];
        var compiledTemplate = TemplateFactory.templateFor({ proto: modalComponent }, VIEWER_TEMPLATE, tabData.component).compiledTemplate;
        $('#manage-store-products-tab-content').html("");
        $('#manage-store-products-tab-content').html(compiledTemplate);

        var model = UI.getViewModel('#manage-store-products-tab-' + tabName);
        ko.cleanNode($('#manage-store-products-tab-content')[0]);
        ko.applyBindings(model, $('#manage-store-products-tab-content')[0]);

        self.currentTab = tabName;
        if (gridName != '') {
            self.renderGrid(gridName, model);
        }
    };

    self.renderGrid = function (gridName, tabModel) {

        //get view model
        var model = UI.getViewModel('#manage-store-products');

        //save currentPage
        $('#component-modal .modal').on('hide.bs.modal', function () {
            if ($(".grid-mvc").gridmvc().currentPage) {
                model.currentPage = $(".grid-mvc").gridmvc().currentPage;
            }
        });

        var gridInit = {
            updateGridAction: '/Account/Commerce' + gridName + 'ListGrid'
        }

        if (self.currentPage) {
            gridInit.currentPage = self.currentPage;
        }
        if (gridName.toLowerCase() == 'products') {
            tabModel.selectedProducts([]);
        }
        $.ajax({
            url: gridInit.updateGridAction,
            dataType: "json",
            success: function (data) {
                //paste 
                $('#grid').html(data.Html);

                //add grid to pageGrids
                var grid = $(".grid-mvc").gridmvc();
               
                //init grid
                var listName = 'commerce' + gridName + 'List';
                GridMvcAjax.demo.init(gridInit, listName, function () {
                    //callback when grid loaded
                    $('#grid').find('table').attr('id', gridName.toLowerCase() + 'Grid');
                    pageGrids[listName].onRowSelect(function (e) {
                        Helpers.consoleLog('e.row = ', e.row);
                    });
                    var selectSelector = '.select-product input[type="checkbox"]';
                    var isElementAlreadyHasEvent = function () {
                        var eventsList = $._data($('#component-modal')[0], 'events').click;
                        var result = false;
                        $.each(eventsList, function (index, eventHandler) {
                            if (eventHandler.selector == selectSelector) {
                                result = true;
                            }
                        });
                        return result;
                    }
                    if (gridName.toLowerCase() == 'products' && !isElementAlreadyHasEvent()) {
                        //tabModel.selectedProducts([]);
                        $('#component-modal').on('click', selectSelector, function (e) {
                            e.stopPropagation();
                            var id = $(this).attr('data-productid');
                            var checked = $(this).prop('checked');
                            var selectedProducts = tabModel.selectedProducts();
                            if (checked) {
                                selectedProducts.push(id);
                            }
                            else {
                                var index = selectedProducts.indexOf(id);
                                if (index > -1) {
                                    selectedProducts.splice(index, 1);
                                }
                            }
                            tabModel.selectedProducts(selectedProducts);
                        });
                    }
                    

                    
                });

            }
        });
    }

    self.init = function () { }
    self.init();
    return self;
}

var ManageUsersCurrentViewModel = function (data) {
    var self = this;
    self.user = ko.observable();
    self.fields = ko.observableArray([]);

    var uploadAvatar = function () {
        var prefix = "Picture";
        upclick({
            type: UPCLICK_TYPE_PICTURE,
            element: "upload-avatar",
            action: "/Editor/Upload" + prefix + "?templateId=" + UI.getTemplateProperty("templateId"),
            accept: ".gif, .jpg, .png, .jpeg, .bmp",
            multiple: false,
            onstart: function () {
                Application.addLocker();
            },
            oncomplete: function (response) {
                Application.removeLocker();
                var file = Helpers.ProcessUploadSingleFileCompleted(response, $('#upclick-editor-error'), $('#upload-avatar'));
                if (file != null) {
                    self.user().avatar(file.url);
                }
            }
        });

        $('.upclick-container').bind('click', function (e) {
            e.stopPropagation();
        });
        
    }

    var birthdayMask = function () {
        $('#birthday-user-input').mask("99/99/9999", { placeholder: "mm/dd/yyyy" });
    }

    self.init = function (userId) {        
        self.fields(_.filter(UI.getCustomUserFields(), function (field) { return field.active() }));
        self.user(null);

        var callback = function (data) {
            if (data && data[0]) {
                self.user(new User(data[0]));
                uploadAvatar();
                birthdayMask();
            }
        };       
        
        //get user
        ProxyService.send({id: userId}, 'user', 'GET', {}, callback);
    }

    self.saveProfile = function () {        
        self.user().save(function() {
            $('#component-modal > .modal').modal('hide');
        });
    }

    self.init(data);
    return self;
}

var ManageMailsCurrentViewModel = function (data) {
    var self = this;
    self.mail = ko.observable(null);
    
    self.init = function (mailId) {        
        self.mail(null);

        var callback = function (data) {
            if (data && data[0]) {
                self.mail(new FormMail(data[0]));
            }
        };       
        
        ProxyService.send({ id: mailId }, 'formmail', 'GET', {}, callback);
    }

    self.init(data);
    return self;
}

var ManageStoreProductsProductViewModel = function (data) {
    var self = this;
    self.product = ko.observable({});
    self.options = ko.observableArray([]);
    self.weightUnitOptions = ko.observableArray([]);    

    var uploadImage = function () {

        var prefix = "Picture";
        upclick({
            type: UPCLICK_TYPE_PICTURE,
            element: "image-upload-button",
            action: "/Editor/Upload" + prefix + "?templateId=" + UI.getTemplateProperty("templateId"),
            accept: ".gif, .jpg, .png, .jpeg, .bmp",
            multiple: true,
            onstart: function () {
                Application.addLocker();
            },
            oncomplete: function (response) {
                Application.removeLocker();
                var files = Helpers.ProcessUploadMultipleFilesCompleted(response, $('#upclick-editor-error'), $('#image-upload-button'));
                if (files != null) {
                    files.forEach(function (file) {
                        self.addImage(Guid.empty(), file.url);
                    });                    
                }
            }
        });

        $('.upclick-container').bind('click', function (e) {
            e.stopPropagation();
        });
    }

    self.imageChangeOrder = function(data, value) {
        if (self.product()) {
            var change = function (ind1, ind2) {
                var images = self.product().images();
                var image = self.product().images()[ind1];
                images[ind1] = images[ind2];
                images[ind2] = image;
                self.product().images(images);
            }
            var index = self.product().images.indexOf(data);
            if (value > 0) {
                if (index !== self.product().images().length-1) {
                    change(index, index + 1);
                }
            } else {
                if (index !== 0) {
                    change(index, index - 1);
                }
            }
        }
    }

    self.imageRemove = function(data) {
        if (self.product()) {
            self.product().images.remove(data);
        }
    }

    self.addImage = function (id, src) {
        if (self.product()) {
            self.product().images.push({
                id: id,
                url: src
            });
        }
    }

    self.changeWeightUnit = function () {
        self.weightUnit(this);        
    }

    self.addOptionValue = function() {
        this.items.push({
            id: '',
            value: '',
            diff: 0.00
        });
    };  

    self.addOption = function () {
        self.options.push({
            id: '',
            title: '',
            items: ko.observableArray([])
        });
    }

    self.deleteOption = function () {
        self.options.remove(this);
    }

    self.backToProducts = function () {
        $("#productsTab").click();
    }

    self.changeCategories = function () {

        var oldCategoriesList = self.product().categories;
        var newCategoriesList = $('#categories-list').tagsinput('items');

        _.forEach(oldCategoriesList, function (item) {
            item.isActive = false;
        });

        _.forEach(newCategoriesList, function (item) {
            item.isActive = true;
            var categoriesById = oldCategoriesList.filter(function (category) { return category.id === item.id });

            if (categoriesById.length === 1) {
                categoriesById[0].isActive = true;
            } else {
                oldCategoriesList.push(item);
            }
        });
    }

    self.saveProduct = function () {
        self.product().options = ko.toJS(self.options);
        self.product().save(function() {
            self.backToProducts();
        });
    }

    self.initCategories = function () {        
        ProxyService.send({}, 'category', 'GET', { count: 100, requesttype: "items" }, function (categories) {
            var bh = new Bloodhound({
                datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'),
                queryTokenizer: Bloodhound.tokenizers.whitespace,
                local: categories
            });

            bh.initialize();

            $('#categories-list').tagsinput({
                itemValue: 'id',
                itemText: 'name',
                typeaheadjs: {
                    name: 'bh',
                    displayKey: 'name',
                    source: bh.ttAdapter()
                }
            });

            _.forEach(self.product().categories, function (item) {
                $('#categories-list').tagsinput('add', item);
            });
        });
    }

    self.toggleDiscoutType = function() {
        if (self.discountType() === 2) {
            self.discountType(1);
        } else {
            self.discountType(2);
        }
    }


    self.ckEditorHandling = function () {
        CKEDITOR.on('instanceReady', function (ev) {
            var ckeditor = CKEDITOR.instances.ckEditorDescription;
            ckeditor.on('change', function (e) {
                self.product().description = e.editor.getData();
            });
        });
    }

    self.initWeightUnitOptions = function () {
        var weightUnit = UI.store.getWeightUnit();
        return _.filter(UI.store.weightUnitOptions, function(item) { return item.groupId === weightUnit.groupId });
    }

    self.init = function (data) {
        uploadImage();
        self.weightUnitOptions(self.initWeightUnitOptions());
        self.product(new Product(data));
        self.weightUnit = ko.observable(self.product().weightUnit);
        self.ckEditorText = ko.observable(self.product().description);
        self.ckEditorHandling();
        self.inStock = ko.observable(!self.product().outOfStock);
        self.price = ko.observable(self.product().price);
        self.discount = ko.observable(self.product().discount);
        self.discountType = ko.observable(self.product().discountType);
        self.isDiscount = ko.observable(self.product().isDiscount);
        self.discountPrice = ko.observable(Product.calculateDiscountPrice(self.discountType(), self.price(), self.discount()));

        self.inStock.subscribe(function (newValue) {
            self.product().stockStatus = newValue ? 2 : 3;
            self.product().outOfStock = !newValue;
        });

        self.weightUnit.subscribe(function(newValue) {
            self.product().weightUnit = newValue;
            self.product().weightUnitTypeId = newValue.id;
        });

        self.price.subscribe(function (newValue) {
            if (newValue || newValue === 0) {
                self.product().price = newValue;
                //change discountPrice
                if (self.isDiscount()) {
                    self.discountPrice.codeUpdate(
                        Product.calculateDiscountPrice(self.discountType(), newValue, self.discount()));
                } else {
                    self.discountPrice.codeUpdate(newValue);
                }
            }
        });
        self.discount.subscribe(function (newValue) {
            if (newValue || newValue === 0) {
                self.product().discount = newValue;
                //change discountPrice
                if (self.isDiscount()) {
                    var discountPrice = Product.calculateDiscountPrice(self.discountType(), self.price(), newValue);
                    if (discountPrice > self.price()) {                                                
                        self.discount(0);
                        self.discountPrice.codeUpdate(self.price());
                    }
                    else {
                        self.discountPrice.codeUpdate(discountPrice);
                    }
                }
            }
        });
        self.discountType.subscribe(function (newValue) {
            var isCodeUpdate = newValue === 'codeUpdate';
            if (newValue && !isCodeUpdate) {
                self.product().discountType = newValue;
                self.discount(0);
            }
        });
        self.discountPrice.subscribe(function (newValue) {
            var isCodeUpdate = newValue === 'codeUpdate';
            if ((newValue || newValue === 0) && !isCodeUpdate) {                
                self.product().discountType = 2;
                self.discountType.codeUpdate(2);
                var discount = self.price() - newValue;
                self.discount(parseFloat(discount.toFixed(2)));
            }
        });

        self.isDiscount.subscribe(function (newValue) {            
            self.product().isDiscount = newValue ? newValue.toBoolean() : false;

            if (self.discountType() === 2) {
                self.discount(0);
            } else {
                self.discountType(2);
            }
        });
               


        self.initOptions();
        self.newCategory = ko.observable();

        self.initCategories();        
    }

    

    self.initOptions = function () {        
        self.options([]);
        _.forEach(self.product().options,
            function (option) {
                self.options.push({
                    id: option.id,
                    title: option.title,
                    items: ko.observableArray(option.items)
                });
            });
    }




    self.init(data);

    return self;
}

var ManageStoreProductsOrderViewModel = function (data) {
    var self = this;
    
    self.back = function () {
        $("#ordersTab").click();
    }

    self.deleteOrder = function () {
        var id = this.order().id;
        Application.showYesNoDialog('Are you sure you want to delete this order?', 'Delete order', function () {
            Order.delete(id, self.back());
        });
    }

    self.init = function (data) {
        self.order = ko.observable(new Order(data));      
        
        self.statusOptions = ko.observableArray(self.order().getAllStatuses());
        var status = _.find(self.statusOptions(),
            function(item) {
                return self.order().statusId === item.id;
            });
        if (status == null) {
            var notDefinedItem = { desc: "Not Defined", name: "notdef", id: self.order().statusId };
            self.status = ko.observable(notDefinedItem);
            self.statusOptions.push(notDefinedItem);
        } else {
            self.status = ko.observable(status);
        }
        
        self.status.subscribe(function(newvalue) {
            if (newvalue) {
                self.order().statusId = newvalue.id;
                self.order().updateOrderStatus(function() {});
            }
        });
        
    }
    self.init(data);

    return self;
}

var ManageStoreProductsTabPaymentsViewModel = function () {
    var self = this;   
    self.paymentAccounts = ko.observableArray([{
        icon: 'fa-paypal',
        type: 'paypal',
        label: 'PayPal',
        createAccountLink: 'https://www.paypal.com/us/webapps/mpp/merchant',
        disabled: ko.observable(true),
        connectMode: ko.observable(false),
        value: ko.observable(''),
        newvalue: ko.observable('')
    }]);

    self.init = function() {
        _.forEach(self.paymentAccounts(),
            function (account) {
                var value = UI.store.getPaymentAccount(account.type);
                account.value(value);
                account.newvalue(value);
                account.disabled(true);
                account.connectMode(false);
            });
    }

    self.save = function (e) {        
        var value = this.newvalue().trim();
        UI.store.setPaymentAccount(this.type, value);
        this.value(value);
        this.disabled(true);
    }

    self.cancel = function () {        
        this.disabled(true);
        var value = this.value().trim();        
        this.value(value);
        this.newvalue(value);
    }

    self.edit = function () {
        this.disabled(false);
    }

    self.connectAccount = function() {
        this.connectMode(true);
    }

    self.connectExisting = function () {
        this.value(' ');
        this.disabled(false);
        this.connectMode(false);
    }

    self.disconnectAccount = function() {
        UI.store.setPaymentAccount(this.type, '');
        this.value('');
        this.newvalue('');
    }

    self.init();

    return self;
}

var ManageStoreProductsTabCategoriesViewModel = function () {
    var self = this;
    self.allProductsQuantity = ko.observable(0);

    self.init = function () {
        ProxyService.send({ id: 'countproduct' }, 'service', 'GET', {}, function (response) {
            self.allProductsQuantity(response[0].count);
        });
    }
    self.init();

    return self;
}

var ManageStoreProductsTabProductsViewModel = function () {
    var self = this;

    //self.selectProducts = ko.observable(false);
    //self.selectedProducts = ko.observableArray([]);

    var displayBatchOpationsPopover = function (popoverCaller, popoverModelName, popoverElementName) {
        event.stopPropagation();
        PopoverHelper.hidePopovers();
        var modalComponent = UI.basicComponentRepository.getAll().where({ name: popoverModelName })[0];
        var compiledTemplate = TemplateFactory.templateFor({ proto: modalComponent }, VIEWER_TEMPLATE).compiledTemplate;
        PopoverHelper.bind(popoverCaller, compiledTemplate, BATCH_OPERATIONS_POPOVER_CUSTOM);
        $(".popover-title").remove();
        $('.popover .arrow').remove();
        
        $("body > div.batch-operations-popover-custom").css({ 'z-index': 1300, 'min-width': '400px' });

        var popoverTop = popoverCaller.offset().top + 25;// + $("body > div.batch-operations-popover-custom").height();
        var popoverLeft = popoverCaller.offset().left + parseFloat(popoverCaller.css('width')) - $("body > div.batch-operations-popover-custom").width();
        $("body > div.batch-operations-popover-custom").css({ 'top': popoverTop, 'left': popoverLeft, 'box-shadow': '0 4px 8px 8px rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)' });

        var model = UI.getViewModel(popoverModelName, self.selectedProducts());
        ko.cleanNode($(popoverElementName)[0]);
        ko.applyBindings(model, $(popoverElementName)[0]);
    }

    self.addCategoryToProducts = function () {
        displayBatchOpationsPopover($('#add-category-to-produts'), MANAGE_STORE_PRODUCTS_ADDING_CATEGORIES_TO_PRODUCTS, '#adding-category-to-products');
    }

    self.addDiscountToProducts = function () {
        displayBatchOpationsPopover($('#add-discount-to-products'), MANAGE_STORE_PRODUCTS_ADDING_DISCOUNT_TO_PRODUCTS, '#adding-discount-to-products');
    }

    self.changeProductsVisability = function () {
        displayBatchOpationsPopover($('#change-products-visability'), MANAGE_STORE_PRODUCTS_CHANGE_PRODUCTS_VISABILITY, '#changing-products-visability');
    }

    self.deleteProducts = function () {
        var processingData = [];
        self.selectedProducts().forEach(function (productId, index, array) {
            processingData.push({
                id: productId,
                isActive: false,
                processingFields: "isActive"
            });
        });
        var callback = function () {
            Product.handleGridRows(false);
            Helpers.gridLoadPage();
        }
        Product.batchUpdate(processingData, callback);
    }

    self.init = function () {
        self.selectProducts = ko.observable(false);
        self.selectProducts.subscribe(function (newValue) {
            Product.handleGridRows(newValue);
        });
        self.selectedProducts = ko.observableArray([]);
    }
    self.init();

    return self;
}

var ManageStoreProductsTabOrdersViewModel = function () {
    var self = this;

    self.displayingOrdersOptions = ko.observableArray(['All orders']);
    self.displayingOrders = ko.observable('All orders');
    self.selectAllOrders = ko.observable(false);
    self.searchField = ko.observable('');
    self.init = function () {
    }
    self.init();

    return self;
}

var ManageStoreProductsTabStoreSettingsViewModel = function () {
    var self = this;

    self.currency = ko.observable();
    self.weightUnit = ko.observable();
    self.returnPolicy = ko.observable();
    self.shippingPolicy = ko.observable();
    self.currencyOptions = ko.observableArray(UI.store.currencyOptions);
    self.weightUnitOptions = ko.observableArray(_.filter(UI.store.weightUnitOptions, function(item) { return item.isBase}));    

    self.saveStoreSettings = function () {
        if (!self.currency() || !self.weightUnit()) {
            Application.showOkDialog('Error', 'Please select currency and weight unit!');
        } else {
            UI.store.saveSettings({
                currencyId: self.currency() ? self.currency().id : '',
                weightUnitTypeId: self.weightUnit() ? self.weightUnit().id : '',
                returnPolicyUrl: self.returnPolicy(),
                shippingPolicyUrl: self.shippingPolicy()
            }, function () {
                self.backToProducts();
            });
        }
    }

    var uploadPdf = function (selector, value) {
        upclick({
            type: UPCLICK_TYPE_PDF,
            element: selector,
            action: "/Editor/UploadPdf?templateId=" + UI.getTemplateProperty("templateId"),
            accept: ".pdf",
            multiple: false,
            onstart: function () {
                Application.addLocker();
            },
            oncomplete: function (response) {
                Application.removeLocker();
                var file = Helpers.ProcessUploadSingleFileCompleted(response, $('#upclick-editor-error'), $('#' + selector));
                if (file != null) {
                    value(file.url);
                }
            }
        });

        $('.upclick-container').bind('click', function (e) {
            e.stopPropagation();
        });
    }

    self.backToProducts = function () {
        $("#productsTab").click();
    }

    self.init = function () {
        var settings = UI.store.getSettings();
        self.currency(UI.store.getCurrency());
        self.weightUnit(UI.store.getWeightUnit());
        self.returnPolicy(settings.returnPolicyUrl);
        self.shippingPolicy(settings.shippingPolicyUrl);
        uploadPdf('upload-return-policy', self.returnPolicy);
        uploadPdf('upload-shipping-policy', self.shippingPolicy);
    }

    self.init();

    return self;
}

var ManageStoreProductsAddingCategoryToProductsViewModel = function (data) {
    var self = this;

    var getChosenCategories = function () {
        var chosenCategories = [];
        _.forEach(self.chosenCategories(), function (category) {
            chosenCategories.push({
                id: category.id
            });
        });
        return chosenCategories;
    }


    self.expandChildren = function (data, event) {

        var parentCategory = $(event.target).closest('.parent-category');
        var childrenCategories = parentCategory.next();
        childrenCategories.slideToggle(500, function () {
            var title = parentCategory.find('.parent-category-title');
            if (childrenCategories.is(":visible")) {
                if (!title.hasClass('expanded')) {
                    title.addClass(' expanded');
                }
            }
            else {
                if (title.hasClass('expanded')) {
                    title.removeClass(' expanded');
                }
            }
        });

    };

    self.apply = function () {
        var chosenCategories = getChosenCategories();
        var processingData = [];
        self.products().forEach(function (productId, index, array) {
            processingData.push({
                id: productId,
                categories: chosenCategories,
                processingFields: "categories"
            });
        });
        var callback = function () {
            PopoverHelper.hidePopovers();
            Product.handleGridRows(false);   
        }
        Product.batchUpdate(processingData, callback);
    }

    self.init = function (data) {
        self.products = ko.observableArray(data);
        self.categories = ko.observableArray([]);
        self.chosenCategories = ko.observableArray([]);
        var filter = { count: 100, requesttype: "items", "grid-filter": "ParentId__10__null" };
        ProxyService.send({}, 'category', 'GET', filter, function (objectsDTO) {
            var categories = objectsDTO;
            self.categories(categories);
        });
    }
    self.init(data);

    return self;
}

var ManageStoreProductsAddingDiscountToProductsViewModel = function (data) {
    var self = this;    

    self.apply = function () {
        var processingData = [];
        self.products().forEach(function (productId, index, array) {
            processingData.push({
                id: productId,
                isDiscount: true,
                discount: parseFloat(self.discount()),
                discountType: self.discountType(),
                processingFields: "discount, isDiscount, discountType"
            });
        });
        var callback = function () {
            PopoverHelper.hidePopovers();
            Product.handleGridRows(false);
            Helpers.gridLoadPage();
        }
        Product.batchUpdate(processingData, callback);
    }

    self.toggleDiscoutType = function () {
        if (self.discountType() === 2) {
            self.discountType(1);
        } else {
            self.discountType(2);
        }
    }

    self.init = function (data) {
        self.discount = ko.observable(0);
        self.discountType = ko.observable(2);
        self.products = ko.observableArray([]);
        self.currency = UI.store.getCurrency().symbol;
        self.products = ko.observableArray(data);

        self.discountType.subscribe(function (newValue) {
            self.discount(0);
        });
    }
    self.init(data);

    return self;
}

var ManageStoreProductsChangeProductsVisabilityViewModel = function (data) {
    var self = this;

    self.changeVisability = function (isVisible) {
        isVisible = defined(isVisible) ? true : false;
        var processingData = [];
        self.products().forEach(function (productId) {
            processingData.push({
                id: productId,
                isVisible: isVisible,
                processingFields: "isVisible"
            });
        });
        var callback = function () {
            PopoverHelper.hidePopovers();
            Product.handleGridRows(false);
            Helpers.gridLoadPage();
        }
        Product.batchUpdate(processingData, callback);
    }
    
    self.init = function (data) {
        self.products = ko.observableArray(data);
    }
    self.init(data);

    return self;
}

var ManageStoreProductsAddingProductsToCategoryViewModel = function (data) {
    var self = this;

    var checkUsedByCategoryProducts = function (allProducts, selectProductModel) {
        var categoryProducts = selectProductModel.category().products();
        var checkedObservableArray = [];
        for (var i = 0; i < allProducts.length; i++) {
            for (var j = 0; j < categoryProducts.length; j++) {
                if (categoryProducts[j].id == allProducts[i].id) {
                    allProducts[i].addedToCategory = true;
                    checkedObservableArray.push(allProducts[i]);

                }
            }
        }
        selectProductModel.products(allProducts);
        selectProductModel.addedToCategory(checkedObservableArray);
        return checkedObservableArray;
    }

    self.searchField = ko.observable('');
    self.searchField.subscribe(function (searchField) {
        self.category().getAllProducts(self.searchField(), self, checkUsedByCategoryProducts);
    }, self);
    self.filteredProducts = function () {
        //self.category().getAllProducts(self.searchField(), self, checkUsedByCategoryProducts);
        //return ko.utils.arrayFilter(self.products(), function (product) {
        //    if (self.searchField() == undefined || self.searchField() == '')
        //        return true;
        //    else
        //        return (product.title.toLowerCase().indexOf(self.searchField()) > -1);
        //});
    };

    self.addProductToCategory = function (products) {
        self.category().products(products);
    }

    self.deleteSubcategory = function () {

    }

    self.init = function (data) {
        self.category = ko.observable(data);
        self.products = ko.observableArray([]);
        self.addedToCategory = ko.observableArray([]);
        self.addedToCategory.subscribe(function (products) {
            self.addProductToCategory(products);
        }, self);
        self.category().getAllProducts('', self, checkUsedByCategoryProducts);
    }
    self.init(data);

    return self;
}

var ManageStoreProductsCategoryViewModel = function (data) {
    var self = this;

    self.deleteCategoryFromProduct = function (product) {
        self.category().products.remove(product);
    }
    self.deleteSubcategory = function (category) {
        self.category().categories.remove(category);
        Category.delete(category.id);
    }

    self.addCategory = function (parentCategory) {
        var category = new Category({
            id: '',
            tempId: Guid.new(),
            name: '',
            parentCategory: parentCategory,
            hasSubcategories: false,
            products: [],
            categories: []
        });
        Category.view(category);
    }


    var mapProductToPlainObject = function (product) {
        var result = {
            id: product.id,
            name: product.title,
            blobs: []
        };

        result.blobs = [{
            main: true,
            url: encodeURIComponent(product.src)
        }];

        return result;
    }

    var mapCategoryToPlainObject = function (category) {
        var plainCategory = {
            id: category.id,
            tempId: category.tempId,
            name: category.name,
            parentCategory: ko.utils.unwrapObservable(category.parentCategory),
            hasSubCategories: ko.utils.unwrapObservable(category.hasSubcategories),
            products: [],
            children: []
        };

        var products = ko.utils.unwrapObservable(category.products);
        var categories = ko.utils.unwrapObservable(category.categories);

        _.forEach(products, function (product) {
            var plainProduct = mapProductToPlainObject(product);
            plainCategory.products.push(plainProduct);
        });

        _.forEach(categories, function (category) {
            var plainSubCategory = mapCategoryToPlainObject(category);
            plainCategory.children.push(plainSubCategory);
        });

       
        return plainCategory;
    }

    self.addSubcategory = function () {
        self.addCategory(self.category());
    }

    self.backToParentCategory = function () {
        var parentCategory = self.category().parentCategory();
        Category.view(mapCategoryToPlainObject(parentCategory));
    }

    self.viewCategory = function (category) {
        var categoryObject = mapCategoryToPlainObject(category);
        Category.view(categoryObject);
    }


    var handleSubcategoryInParentCategories = function (parentCategory, subcategory, operationType) {
        for (var i = 0; i < parentCategory.categories().length; i++) {
            if (parentCategory.categories()[i].id === subcategory.id) {
                if (operationType == 'delete') {
                    parentCategory.categories().splice(i, 1);
                }
                else if (operationType == 'merge') {
                    parentCategory.categories()[i] = subcategory;
                }
                return true;
            }
        }
    }

    self.deleteCategory = function () {
        if (self.category().parentCategory() == null) {
            Category.delete(self.category().id);
            self.backToCategories();
        }
        else {
            var parentCategory = self.category().parentCategory();
            handleSubcategoryInParentCategories(parentCategory, self.category(), 'delete');

            Category.delete(self.category().id);
            self.backToParentCategory();
        }
    };


    self.saveCategory = function () {
        if (self.category().parentCategory() == null) {
            self.category().save(function() {
                self.backToCategories();
            });
        }
        else {
            var parentCategory = self.category().parentCategory();
            var subcategoryWasHandled = handleSubcategoryInParentCategories(parentCategory, self.category(), 'merge');

            self.category().save(function () {
                self.backToParentCategory();
            });
      
        }

    }

    self.backToCategories = function () {
        $("#categoriesTab").click();
    }

    var displayBatchOpationsPopover = function (popoverCallerName, popoverModelName, popoverElementName) {
        event.stopPropagation();
        var modalComponent = UI.basicComponentRepository.getAll().where({ name: popoverModelName })[0];
        var compiledTemplate = TemplateFactory.templateFor({ proto: modalComponent }, VIEWER_TEMPLATE).compiledTemplate;
        PopoverHelper.bind($(popoverCallerName), compiledTemplate, BATCH_OPERATIONS_POPOVER_CUSTOM);
        $(".popover-title").remove();
        $('.popover .arrow').remove();

        $("body > div.batch-operations-popover-custom").css({ 'z-index': 1300, 'min-width': '400px' });

        var popoverTop = $(popoverCallerName).offset().top - $("body > div.batch-operations-popover-custom").height();
        var popoverLeft = $(popoverCallerName).offset().left + parseFloat($(popoverCallerName).css('width')) - $("body > div.batch-operations-popover-custom").width();
        $("body > div.batch-operations-popover-custom").css({ 'top': popoverTop, 'left': popoverLeft, 'box-shadow': '0 4px 8px 8px rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)' });

        var model = UI.getViewModel(popoverModelName, self.category());
        ko.cleanNode($(popoverElementName)[0]);
        ko.applyBindings(model, $(popoverElementName)[0]);
    }
    self.displayAddingProductsToCategoryPopover = function () {
        if (Helpers.isExistsByClassName(BATCH_OPERATIONS_POPOVER_CUSTOM)) {
            PopoverHelper.hidePopovers(BATCH_OPERATIONS_POPOVER_CUSTOM);
        }
        else {
            displayBatchOpationsPopover('#add-products-to-collection', MANAGE_STORE_PRODUCTS_ADDING_PRODUCTS_TO_CATEGORY, '#adding-products-to-category');
        }        
    }


    self.init = function (data) {
        self.category = ko.observable(new Category(data));

        self.category().parentCategory = ko.observable(self.category().parentCategory);
        self.category().products = ko.observableArray(self.category().products);
        self.category().categories = ko.observableArray(self.category().categories);
        self.category().hasSubcategories = ko.observable(self.category().hasSubcategories);
        self.category().hasSubcategories.subscribe(function () {
            //this.category().products([]);
            //this.category().categories([]);
        }, self);
    }
    self.init(data);

    return self;
}

var UserFieldsViewModel = function () {
    var self = this;
    self.fields = ko.observableArray(UI.getCustomUserFields());
    self.action = function() {};
    self.actionText = ko.observable();
    self.isEditMode = ko.observable();
    self.isEditMode.subscribe(function(newValue) {
        if (newValue) {
            self.actionText("Save");
            self.action = self.actionSave;
        } else {
            self.actionText("Edit");
            self.action = self.actionEdit;
        }
    });

    self.actionEdit = function() {
        self.isEditMode(true);
    }

    self.actionSave = function () {
        UI.setCustomUserFields(ko.toJSON(self.fields()));
        self.isEditMode(false);
    }

    self.init = function() {
        self.fields(UI.getCustomUserFields());
        self.isEditMode(false);
    }

    
    self.init();
    return self;
}

var UserProfileViewModel = function (data) {
    var self = this;
    self.user = ko.observable();
    self.fields = ko.observableArray();    

    var uploadAvatar = function () {
        var prefix = "Picture";
        upclick({
            type: UPCLICK_TYPE_PICTURE,
            element: "upload-avatar",
            action: "/Editor/Upload" + prefix + "?templateId=" + UI.getTemplateProperty("templateId"),
            accept: ".gif, .jpg, .png, .jpeg, .bmp",
            multiple: false,
            onstart: function () {
                Application.addLocker();
            },
            oncomplete: function (response) {
                Application.removeLocker();
                var file = Helpers.ProcessUploadSingleFileCompleted(response, $('#upclick-editor-error'), $('#image-upload-button'));
                if (file != null) {
                    self.user().avatar(file.url);
                }
            }
        });

        $('.upclick-container').bind('click', function (e) {
            e.stopPropagation();
        });        
    }

    var birthdayMask = function () {
        $('#birthday-user-input').mask("99/99/9999", { placeholder: "mm/dd/yyyy" });
    }

    self.init = function (data) {
        self.fields(_.filter(UI.getCustomUserFields(), function (field) { return field.active() }));
        self.user(data.user());        
        setTimeout(function () {
            uploadAvatar();
            birthdayMask();
        }, 0);
    };    

    self.saveProfile = function () {
        self.user().save(function() {
            $('#component-modal > .modal').modal('hide');
        });
    }

    self.goToChangePassword = function () {
        UI.componentService.addModalContentToForm(data.component, '#signin-change-password');
    }

    self.init(data);

    return self;
}

var RightNavigationPanelModel = function (data) {
    var self = this;
    var ignoreSelect = [STORE_PRODUCT, STORE_CART, STORE_THANK_YOU];
    var onlyOneRule = [SIGNIN, STORE_CATEGORIES, STORE_CART_LINK, STORE_GALLERY];
    self.pages = ko.observableArray([]);
    self.sortedPages = ko.computed(function() {
        return self.pages().sort(function (a, b) {
            if (a.displayName === HEADER || a.displayName === FOOTER) {
                return -1;
            }
            if (a.title < b.title) {
                return -1;
            }
            if (a.title > b.title) {
                return 1;
            }
            return 0;
        });
    });
    self.selectedTab = ko.observable();
    self.selectedTab.subscribe(function(newValue) {
        if (newValue === 'search') {
            self.initPages();
        }
    });
    self.isPanelShown = ko.observable(true);
    self.init = function (data) {
        for (var i = 1; i <= 3; i++){
            Aligning.setElementsAccess("aligning-item-" + i);
        }
        Grouping.setElementsAccess();
        ClipboardViewModel.setElementsAccess('layer-item', clipBoard.selectedItem() || grouping.showGroupingOptions());
        ClipboardViewModel.setElementsAccess('add-or-delete-item', (clipBoard.selectedItem() || grouping.showGroupingOptions()) && clipBoard.isDesktopView())
        ClipboardViewModel.setElementsAccess('paste-item', clipBoard.itemForClipboard() && clipBoard.isDesktopView());
        ClipboardViewModel.setElementsVisability();
        self.checkPanelCollapse();
        self.selectedTab('toolbox');
    }

    self.initPages = function () {
        self.pages([]);
        self.pages(_.clone(UI.pager.pages) || []);
        self.pages.push(UI.getFooter());
        self.pages.push(UI.getHeader());
        self.components(UI.newComponents || []);
        self.selectedPage(UI.pager.getPage(UI.pager.getCurrentPageId()));
        self.selectedComponent(null);
    }

    self.selectedPage = ko.observable();
    self.selectedPage.subscribe(function(newValue) {
        self.initControls();
    });

    self.controls = ko.observableArray([]);
    self.sortedControls = ko.computed(function () {
        return self.controls().sort(function (a, b) {
            if (a.displayName < b.displayName) {
                return -1;
            }
            if (a.displayName > b.displayName) {
                return 1;
            }
            return 0;
        });
    });
    self.components = ko.observableArray([]);
    self.sortedComponents = ko.computed(function () {
        return self.components().sort(function (a, b) {
            if (a.DisplayName < b.DisplayName) {
                return -1;
            }
            if (a.DisplayName > b.DisplayName) {
                return 1;
            }
            return 0;
        });
    });
    self.selectedComponent = ko.observable();
    self.selectedComponent.subscribe(function(newValue) {
        self.initControls();
    });

    var filterControls = function (controls, filter) {
        var key = Object.keys(filter);
        var value = filter[key];
        var result = [];
        controls.forEach(function(node) {
            if (node[key] === value) {
                result.push(node);
            }
        });
        return result;
    }

    self.initControls = function () {
        self.controls([]);
        var filter = {};
        var page = self.selectedPage();
        var component = self.selectedComponent();
        if (component) {
            filter['displayName'] = component.Name;
        }
        //search
        if (page) {
            var pageComponent = page.getComponent ? page.getComponent() : page;
            self.controls(filterControls(pageComponent.children, filter));
        } else {
            var result = [];
            self.pages().forEach(function (p, index, arr) {
                var pageComponent = p.getComponent ? p.getComponent() : p;
                result.addRange(pageComponent.children);
            });
            self.controls(filterControls(result, filter));
        }
    }
    function scrollAndSelect(component) {
        try {
            $([document.documentElement, document.body]).animate({
                scrollTop: $(component.getUISelector()).offset().top - 100
            }, 2000);
            $(component.getUISelector()).highlightSelectedElement(component, true);
        } catch (e) {
            self.controls.remove(component);
        }
    }

    //todo: scroll
    self.selectControl = function (component) {
        if (component.parentComponent.proto.name === HEADER ||
            component.parentComponent.proto.name === FOOTER ||
            (component.parentComponent.proto.name === PAGE &&
                component.parentComponent.id === UI.pager.getCurrentPageId())) {
            scrollAndSelect(component);
        } else {
            UI.pager.goToPage(component.parentComponent.id);
            scrollAndSelect(component);
        }
    }


    self.checkPanelCollapse = function () {
        var storageValue = LocalStorageService.GetItem('right-panel-collapse');
        self.isPanelShown((storageValue && storageValue.toBoolean()) || !storageValue);
    }

    self.changeVisiblePanel = function () {       
        self.isPanelShown(!self.isPanelShown());
        LocalStorageService.SetItem('right-panel-collapse', self.isPanelShown());
    }
    grouping.selectedItems.subscribe(function (newValue) {
        for (var i = 1; i <= 3; i++) {
            Aligning.setElementsAccess("aligning-item-" + i);
        }
        ClipboardViewModel.setElementsAccess('layer-bring-front', ClipboardViewModel.isLayeringEnabled());
        ClipboardViewModel.setElementsAccess('layer-bring-back', ClipboardViewModel.isLayeringEnabled(true));
    })
    grouping.showGroupingOptions.subscribe(function (newValue) {
        Grouping.setElementsAccess();
        ClipboardViewModel.setElementsAccess('layer-item', clipBoard.selectedItem() || grouping.showGroupingOptions());
        ClipboardViewModel.setElementsAccess('add-or-delete-item',
            (clipBoard.selectedItem() || grouping.showGroupingOptions()) && clipBoard.isDesktopView());
        ClipboardViewModel.setElementsAccess('paste-item', clipBoard.itemForClipboard() && clipBoard.isDesktopView());
    });
    clipBoard.selectedItem.subscribe(function (newValue) {
        if (clipBoard.selectedItem() && ignoreSelect.indexOf(clipBoard.selectedItem().proto.name) !== -1) return;
        ClipboardViewModel.setElementsAccess('layer-item', clipBoard.selectedItem() || grouping.showGroupingOptions());
        ClipboardViewModel.setElementsAccess('layer-bring-front', ClipboardViewModel.isLayeringEnabled());
        ClipboardViewModel.setElementsAccess('layer-bring-back', ClipboardViewModel.isLayeringEnabled(true));
        if (clipBoard.selectedItem()) {
            if (onlyOneRule.indexOf(clipBoard.selectedItem().proto.name) === -1) {
                ClipboardViewModel.setElementsAccess('add-or-delete-item', clipBoard.isDesktopView());
            } else {
                ClipboardViewModel.setElementsAccess('add-or-delete-item', clipBoard.isDesktopView(), 'only-delete');
            }
        } else {
            ClipboardViewModel.setElementsAccess('add-or-delete-item',
                grouping.showGroupingOptions() && clipBoard.isDesktopView());
        }
        Aligning.setElementsAccess("aligning-item-1");
    });
    clipBoard.isDesktopView.subscribe(function (newValue) {
        ClipboardViewModel.setElementsAccess('add-or-delete-item',
            (clipBoard.selectedItem() || grouping.showGroupingOptions()) && clipBoard.isDesktopView());
        ClipboardViewModel.setElementsAccess('paste-item', clipBoard.itemForClipboard() && clipBoard.isDesktopView());
    });
    clipBoard.itemForClipboard.subscribe(function (newValue) {
        ClipboardViewModel.setElementsAccess('paste-item', clipBoard.itemForClipboard() && clipBoard.isDesktopView());
        ClipboardViewModel.setElementsVisability();
    });

    self.init(data);
    return self;
}
var DeviceManagementModel = function (data) {
    var self = this;
    self.selectedDevice = ko.observable();
    self.devices = ko.observableArray();
    self.onlyDesktop = ko.observable();
    self.isInitModel = false;

    self.init = function (data) {
        self.isInitModel = true;
        self.selectedDevice(data.device);
        self.devices(data.devices);
        self.onlyDesktop(UI.getBody().getProperty(ONLY_DESKTOP).value.toBoolean());
    }

    self.resetDevice = function () {
        Application.showRemoveConfirmationDialog({ text: 'Are you sure you want to reset your entire mobile website?', style: 'text-upper text-danger-light' }, 'Reset Mobile Layout', [
            {
                css: 'simple',
                text: 'Reset Current Page',
                callback: function () {
                    self.selectedDevice().resetPage();
                    Application.removeModal();
                }
            },
            {
                css: 'danger',
                text: 'Reset Entire Website',
                callback: function () {
                    self.selectedDevice().reset();
                    Application.removeModal();
                }
            }
        ], 'You will lose any individual page edits you have made!<br>You might want to just reset an individual page instead.', '*This action can\'t be Undone!');    
    }

    self.optionsText = function (item) {
        return item.getDisplayName();
    }

    self.toggleIsPublish = function () {
        var oldValue = UI.getBody().getProperty(ONLY_DESKTOP).value.toBoolean();
        UI.undoManagerAddSimple(UI.getBody(), ONLY_DESKTOP, (!oldValue).toString(), function() {
            self.onlyDesktop(UI.getBody().getProperty(ONLY_DESKTOP).value.toBoolean());
        }, true);        
    }

    self.selectDesktop = function () {
        var device = self.devices().where({ type: DEVICE_DESKTOP_TYPE }).firstOrDefault();
        if (device && self.selectedDevice().getId() !== device.getId()) {
            self.selectedDevice(device);
        }
    }

    self.selectMobile = function () {
        var device = self.devices().where({ type: DEVICE_MOBILE_TYPE }).firstOrDefault();
        if (device && self.selectedDevice().getId() !== device.getId()) {
            self.selectedDevice(device);
        }
    }

    self.selectedDevice.subscribe(function (newValue) {
        if (newValue) {
            if (!self.isInitModel) {
                Application.addLocker();

                setTimeout(function () {
                        var isFirstRun = false;
                    if (!UI.hasDeviceIdInTemplateData(newValue.getId())) {
                        isFirstRun = true;
                    }
                    UI.setDevice(newValue);
                    Application.removeLocker();
                    if (isFirstRun) {
                        Application.showEmptyDialog('Hint', '<p class="text-center" style="font-size: 1.4em;">To add more elements, move them from header/footer to page, manage design, pages and site settings please change view to "Desktop" again.</p> <div class="text-center"><div class="service-button" data-dismiss="modal">Got It</div></div>');
                    }
                },
                    500);
            } else {
                self.isInitModel = false;
            }
        }
    });

    self.init(data);

    return self;
}
var HistoryManagementModel = function (data) {
    var self = this;
    self.versions = ko.observableArray([]);
    self.currentTemplateVersionId = ko.observable();

    self.init = function (data) {
        self.currentTemplateVersionId(window.lastEditorTemplateVersionId);
        self.versions([]);
        Application.GetHistoryManagement(window.templateId,
            function(arr) {
                if (arr) {
                    self.versions(arr.map(function (item) {
                        item.formatedDate = moment(item.date).format("DD.MM.YYYY hh:mm A");
                        return item;
                    }));
                }
            });
    }

    self.selectVersion = function(version) {
        Application.SetTemplateVersionActive(version.templateVersionId,
            function(data) {
                if (data) {
                    window.lastEditorTemplateVersionId = version.templateVersionId;
                    self.currentTemplateVersionId(window.lastEditorTemplateVersionId);
                    UI.unsavedChanges = false;
                    Application.addLocker();
                    window.location.reload();
                }
            });
    }

    self.init(data);

    return self;
}

var HeaderSettingsViewModel = function (component) {
    var self = this;

    self.isPined = ko.observable(false);

    self.isPined.subscribe(function (newValue) {
        if (newValue || newValue === false) {
            UI.undoManagerAddSimple(component, IS_PINED, newValue, function () { }, true);            
        }
    });

    self.init = function (component) {
        self.isPined(component.getProperty(IS_PINED).value.toBoolean());
    }

    self.togglePined = function () {
        self.isPined(!self.isPined());
    }

    self.init(component);
    return self;
}

var HiddenElementsModel = function (hidden) {
    var self = this;

    self.elements = ko.observableArray();
    self.showHiddens = ko.observable(false);

    self.showHiddens.subscribe(function(newValue) {
        if (newValue || newValue === false) {
            UI.settings["showHidden"] = newValue;            
            if (newValue) {
                _.forEach(self.elements(),
                    function(item) {
                        UI.actionService.runActionForComponent(item, ACTION_ADD_TO_FORM, true);             
                    });
            } else {
                _.forEach(self.elements(),
                    function (item) {
                        UI.actionService.runActionForComponent(item, ACTION_REMOVE_FROM_FORM, true);
                    });
            }
        }
    });

    self.init = function(hidden) {
        self.elements(hidden);
        self.showHiddens(UI.getSetting("showHidden"));
    }

    self.toggleShowHiddens = function() {
        self.showHiddens(!self.showHiddens());
    }

    self.showElement = function (component) {
        UI.undoManagerAddSimpleArr([
            { component: component, property: HIDE_COMPONENT, newvalue: 'false', oldvalue: 'true' }
        ], function () {
            self.elements.remove(component);
            if (UI.getSetting("showHidden")) {
                UI.actionService.runActionForComponent(component, ACTION_REMOVE_FROM_FORM, true);
            }
            UI.actionService.runActionForComponent(component, ACTION_ADD_TO_FORM, true);            
        }, function () {
            UI.actionService.runActionForComponent(component, ACTION_REMOVE_FROM_FORM, true);
            if (UI.getSetting("showHidden")) {
                UI.actionService.runActionForComponent(component, ACTION_ADD_TO_FORM, true);
            }
        }, true);
    }

    self.init(hidden);
    return self;
}

var StoreProductViewModel = function(component) {
    var self = this;
    self.component = component;
    self.productId = ko.observable();
    self.galleryPage = ko.observable();
    self.product = ko.observable({});   
    self.imagesIsCrop = false;
    self.price = ko.observable();
    self.discountPrice = ko.observable(0);
    self.price.subscribe(function(newValue) {
        if (newValue || newValue === 0) {
            if (self.product() && self.product().isDiscount) {
                self.discountPrice(Product.calculateDiscountPrice(self.product().discountType, newValue, self.product().discount));
            }
        }
    });
    self.options = ko.observableArray();
    self.quantity = ko.observable();
    self.isPreview = UI.getSetting('ispreview');

    var productIdSubscriber = function (value) {
        if (value) {
            UI.store.getProduct(value,
                function (data) {
                    if (data) {
                        self.initProduct(data);

                        //set page title
                        var page = UI.pager.getPage(UI.pager.getCurrentPageId());
                        if (page.name === 'product') {
                            page.setMeta(self.product().title);
                        }
                    } else {
                        UI.pager.goToHomePage();
                    }
                }, UI.getSetting('ispreview') ? false: true);
        } else {
            UI.pager.goToHomePage();
        }
    };

    self.init = function (component) {        
        self.component = component;
        self.galleryPage(UI.store.getPageWithGallery());
        
        var productId = Guid.empty();
        if (UI.getSetting('ispreview')) {
            productId = Helpers.getStoreParamFromURL('productId');
        }
        if (self.productId() !== productId) {            
            self.productId(productId);
        }
    }

    self.pageGalleryName = function () {
        var galleryPage = self.galleryPage();
        return galleryPage ? galleryPage.title : 'Store';
    };

    self.pageGalleryLink = function () {
        var galleryPage = self.galleryPage();
        return UI.getSetting('ispreview') ? (galleryPage ? Helpers.generateLinkToPage(galleryPage.name, true) : Helpers.generateLinkToPage(UI.pager.getPage(UI.pager.getHomePageId()).name, true)) : null;
    }

    self.productId.subscribe(productIdSubscriber);

    self.initProduct = function (data) {
        self.product(new Product(data));        
        self.price(self.product().price);
        self.quantity(1);

        self.options([]);
        _.forEach(self.product().options,
            function (option) {                
                var value = ko.observable();
                value.subscribe(function (val) {
                    if (val) {
                        self.price(self.price() + (val.diff * -1));
                    }
                }, this, 'beforeChange');
                value.subscribe(function(val) {
                    if (val) {
                        self.price(self.price() + val.diff);
                    }
                });
                self.options.push({
                    id: option.id,
                    title: option.title,
                    value: value,
                    items: ko.observableArray(option.items)
                });
            });
        self.shiftForOptions(self.options().length);

        self.refreshChild(STORE_PRODUCT_IMAGES);     
    }

    self.refreshChild = function(name) {
        var child = _.find(self.component.children,
            function (child) {
                return child.proto.name === name;
            });
        if (child) {
            UI.actionService.runActionForComponent(child, ACTION_REMOVE_FROM_FORM);
            UI.actionService.runActionForComponent(child, ACTION_ADD_TO_FORM);
        }
    }

    self.shiftForOptions = function (optionsLength) {
        var optionsComponent = _.find(self.component.children,
            function (child) {
                return child.proto.name === STORE_PRODUCT_OPTIONS;
            });
        if (optionsComponent) {
            var containerMaxHeight = parseInt($(optionsComponent.getUISelector()).find('div').css('max-height'));
            var height = parseInt(optionsComponent.getProperty(HEIGHT).value);
            var shift = (containerMaxHeight > height * optionsLength ? height * optionsLength : containerMaxHeight) - height;
            if (shift) {
                _.forEach(self.component.children,
                    function(child) {
                        if (child.proto.name === STORE_PRODUCT_ADD_TO_CART ||
                            child.proto.name === STORE_PRODUCT_SOCIAL ||
                            child.proto.name === STORE_PRODUCT_QUANTITY) {
                            var element = $(child.getUISelector());
                            var top = parseInt(child.getProperty(TOP).value);
                            element.css(TOP, top + shift + 'px');
                        }
                    });                
            }

        }
    }

    self.socialShare = function (type) {
        if (UI.getSetting("ispreview")) {
            if (UI.getSetting("ispublished")) {
                switch (type) {
                case 'tw':
                    window.open(
                        'https://twitter.com/share?url=' + location.href,
                        '_blank'
                    );
                    break;
                case 'fb':
                    window.open(
                        'https://www.facebook.com/sharer.php?u=' + location.href,
                        '_blank'
                    );
                    break;
                case 'gplus':
                    window.open(
                        'https://plus.google.com/share?url=' + location.href,
                        '_blank'
                    );
                    break;
                }
            } else {
                Application.showOkDialog('Error', 'Available only on published site!');
            }
        }
    };

    self.addToCart = function () {
        if (UI.getSetting('ispreview') && !self.product().outOfStock) {
            var isSelectAllOptions = true;
            var productStorageKey = self.productId() + '|';
            var options = [];
            _.forEach(_.sortBy(self.options(), 'title'),
                function(option) {
                    if (option.value() == null) {
                        isSelectAllOptions = false;
                    } else {
                        productStorageKey += option.id + '=' + option.value().id + ';';
                        options.push({
                            id: option.id,
                            title: option.title,
                            items: [option.value()]
                        });
                    }
                });
            //todo: add validation
            if (isSelectAllOptions) {
                if (self.quantity() < 1) {
                    Application.showOkDialog("Error", "Quantity of product must be greather then or equal 1");
                    self.quantity(1);
                }
                else {
                    UI.store.addToCart(productStorageKey, self.product().toDTO(options), self.quantity());
                    Application.showOkDialog('Success', 'Your product added to cart!');
                    //refresh count
                    var viewModel = UI.viewModelRepository[STORE_CART_LINK];
                    if (viewModel) {
                        viewModel.refresh();
                    }
                }
                
            } else {
                Application.showOkDialog('Error', 'Please select all options!');
            }
        }
    };

    self.optionText = function (item) {
        return item.value + ' (' + (item.diff < 0 ? '-' + self.product().currency + item.diff * -1 : '+' + self.product().currency + item.diff) + ')';
    }

    self.init(component);
    return self;
}

var StoreGalleryViewModel = function(component) {
    var self = this;
    self.component = {};
    self.products = ko.observableArray([]);
    self.sortOptions = [
        {
            title: 'Price (low to high)',
            column: 'CalcPrice',
            direction: 0
        },
        {
            title: 'Price (high to low)',
            column: 'CalcPrice',
            direction: 1
        },
        {
            title: 'Name A-Z',
            column: 'Name',
            direction: 0
        },
        {
            title: 'Name Z-A',
            column: 'Name',
            direction: 1
        }
    ];
    self.isPreview = UI.getSetting('ispreview');
    self.selectedSorting = ko.observable();
    self.selectedSorting.subscribe(function (newValue) {
        var isCodeUpdate = newValue === 'codeUpdate';
        if (!defined(newValue)) {
            delete self.filter['grid-column'];
            delete self.filter['grid-dir'];
        }
        if (!isCodeUpdate) {
            self.loading(false);
            self.initProducts(function () { }, self.page() * self.getInitialCount());
        } 
    });
    self.page = ko.observable(1);
    self.columns = 0;
    self.rows = 0;
    self.filter = {};
    self.isShowMoreButton = ko.observable(false);
    self.loading = ko.observable(false);

    self.init = function (component) {
        if (component.id !== self.component.id) {
            self.component = component;
            self.filter = {};
            self.selectedSorting.codeUpdate(undefined);
            self.reload();
        } else if (parseInt(self.component.getProperty(COLUMNS).value) !== self.columns ||
            parseInt(self.component.getProperty(ROWS).value) !== self.rows) {
            self.reload(function() {
                EditorFactory.recalculateStoreGallerySize(component);
            });
        } 
    }

    self.setFilter = function(filter) {
        self.filter = filter;
        self.reload();
    }

    self.reload = function (callback) {
        callback = defined(callback) ? callback : function() {};
        self.loading(false);
        self.initProducts(callback);
    }

    self.getInitialCount = function () {
        return self.columns * self.rows;
    }    

    self.showMore = function () {
        if (UI.getSetting('ispreview') && !self.loading()) {
            self.loading(true);
            var count = self.getInitialCount();
            var filter = defined(self.filter) ? self.filter : {};
            filter.count = count;
            filter.page = self.page() + 1;
            filter['grid-filter'] = 'IsVisible__1__true';
            if (self.selectedSorting()) {
                filter['grid-column'] = self.selectedSorting().column;
                filter['grid-dir'] = self.selectedSorting().direction;
            }
            UI.store.getProducts(filter,
                function (data) {
                    self.loading(false);                    
                    if (data) {
                        var element = $(self.component.getUISelector());
                        var height = element.outerHeight(true);
                        var page = element.parent('.page');

                        //find elements under bottom
                        var shiftElements = Helpers.findElementsUnder(element, page);

                        //add data
                        self.page(self.page() + 1);
                        _.forEach(data,
                            function(product) {
                                self.products.push(new Product(product));
                            });
                        self.isShowMoreButton(data.length === count);
                        
                        var diff = element.outerHeight(true) - height;
                        _.forEach(shiftElements,
                            function(child) {
                                var ctop = parseInt(child.css(TOP));
                                child.css(TOP, ctop + diff + 'px');
                            });
                        Resizer.recalculateHeaderFooterAndPageSize(page);
                        UI.pager.setPageHeight(page.getId());
                    } else {
                        self.isShowMoreButton(false);
                    }                  
                });
        }
    };    

    self.initProducts = function (callback, count) {
        callback = defined(callback) ? callback : function () { };
        var element = $(self.component.getUISelector());
        var filter = defined(self.filter) ? self.filter : {};
        self.products([]);
        self.isShowMoreButton(false);
        self.columns = parseInt(self.component.getProperty(COLUMNS).value);
        self.rows = parseInt(self.component.getProperty(ROWS).value);        
        if (count) {
            filter.page = 1;
        } else {
            self.page(1);
            count = self.getInitialCount();
            filter.page = self.page();
        }
        filter.count = count;
        filter['grid-filter'] = 'IsVisible__1__true';
        if (self.selectedSorting()) {
            filter['grid-column'] = self.selectedSorting().column;
            filter['grid-dir'] = self.selectedSorting().direction;
        }
        element.css('height', self.component.getProperty(HEIGHT).value);
        UI.store.getProducts(filter,
            function (data) {
                if (data) {
                    _.forEach(data,
                        function(product) {
                            self.products.push(new Product(product));
                        });
                    callback();
                    if (data.length === count) {
                        self.isShowMoreButton(true);
                        element.css('height', 'auto');
                    } else if (self.page() !== filter.page && self.page() > 1) {
                        element.css('height', 'auto');
                    }
                }
            }, UI.getSetting('ispreview') ? false : true);
    }
    
    self.init(component);

    return self;
}

var StoreCartViewModel = function(component) {
    var self = this;
    self.component = component;
    self.products = ko.observableArray();
    self.subTotal = ko.observable();

    self.init = function (component) {
        self.component = component;
        self.products([]);
        self.subTotal('');
        var total = 0;
        var currency = '';
        _.forEach(UI.store.getCart(),
            function (item) {
                var quantity = ko.observable(item.quantity);
                quantity.subscribeChanged(function (newValue, oldValue) {
                    if (newValue <= 0) {
                        //delete product
                        self.products.remove(function(prod) {
                            return prod.product.cartKey === item.product.cartKey;
                        });
                        UI.store.removeFromCart(item.product.cartKey, 0, true);
                        //refresh count
                        var viewModel = UI.viewModelRepository[STORE_CART_LINK];
                        if (viewModel) {
                            viewModel.refresh();
                        }
                    } else {
                        var diff = 0;
                        if (oldValue) {
                            diff = newValue - oldValue;
                        }
                        UI.store.addToCart(item.product.cartKey, item.product, diff);
                    }
                    var total = 0;
                    var currency = '';
                    ko.utils.arrayForEach(self.products(),
                        function (item) {
                            currency = item.product.currency;
                            total += (item.product.isDiscount ? item.product.getFullDiscountPrice() : item.product.getFullPrice()) * item.quantity();
                        });
                    self.subTotal(currency + total.toFixed(2));
                });
                total += (item.product.isDiscount ? item.product.getFullDiscountPrice() : item.product.getFullPrice()) * item.quantity;
                currency = item.product.currency;
                self.products.push({
                    product: item.product,
                    quantity: quantity
                });
            });        
        self.subTotal(currency + total.toFixed(2));
    }

    self.checkoutClick = function () {
        if (UI.getSetting('ispreview')) {
            UI.store.checkout(function() {
                self.products([]);
                self.subTotal('');
                //refresh count
                var viewModel = UI.viewModelRepository[STORE_CART_LINK];
                if (viewModel) {
                    viewModel.refresh();
                }
            });
        }
    }

    self.back =  function() {
        if (UI.getSetting('ispreview')) {
            if (history.length > 1) {
                window.history.back();
            } else {
                UI.pager.goToHomePage();
            }
        }
    }

    self.init(component);
    return self;
}

var StoreThankYouViewModel = function (component) {
    var self = this;
    self.component = component;    
    self.order = ko.observable({});
    self.orderId = ko.observable(null);    

    var orderNumberSubscriber = function (value) {        
        if (value) {
            UI.store.getOrder(value,
                function (data) {
                    if (data) {
                        self.initOrder(data);
                    } else {
                        UI.pager.goToHomePage();
                    }
                });
        } else {
            UI.pager.goToHomePage();
        }
    };

    self.init = function (component) {
        self.component = component;        
        var orderNumber = Helpers.getStoreParamFromURL('orderId');
        if (self.orderId() === orderNumber) {
            orderNumberSubscriber(orderNumber);
        } else {
            self.orderId(orderNumber);
        }
    }

    self.initOrder = function(data) {
        self.order(new Order(data));
    }

    self.orderId.subscribe(orderNumberSubscriber);

    self.init(component);
    return self;
}

var StoreCartLinkViewModel = function(component) {
    var self = this;
    self.component = component;
    self.count = ko.observable();

    self.init = function(component) {
        self.component = component;
        self.refresh();
    };

    self.refresh = function() {
        self.count(UI.store.cartCount());
    }

    self.init(component);
    return self;
}

var ManageStoreProductsTabShippingAndTaxViewModel = function(data) {
    var self = this;
    self.currency = '';
    self.weightUnit = '';
    self.shippingLocations = ko.observableArray([]);
    self.taxLocations = ko.observableArray([]);
    self.showAddShippingLocation = ko.observable(false);
    self.newShippingLocations = ko.observableArray([]);

    self.init = function (data) {
        self.currency = UI.store.getCurrency().symbol;
        self.weightUnit = UI.store.getWeightUnit().symbol;
        self.showAddShippingLocation(false);
        if (!data) {
            self.initData();
        }
    }

    self.sortShippingLocations = function () {
       self.shippingLocations.sort(function (a, b) {
            if (parseInt(a.groupId) < parseInt(b.groupId))
                return -1;
            if (parseInt(a.groupId) > parseInt(b.groupId))
                return 1;
            return 0;
        });
    }
    self.sortTaxLocations = function () {
        self.taxLocations.sort(function (a, b) {
            if (parseInt(a.groupId) < parseInt(b.groupId))
            return -1;
            if (parseInt(a.groupId) > parseInt(b.groupId))
                return 1;
            return 0;
        });
    }

    self.getStateNamesAsString = function (stateList) {
        return !stateList || (stateList && stateList.length == 0) ? '' : stateList.map(function (state) { return state.state.desc; }).join(", ");
    }
    self.addSavedShippingLocations = function (groupId, rates, country, states) {

        var rateList = [];
        _.forEach(rates, function (rate) {

            rateList.push({
                id: rate.id,
                type: rate.typeId,
                countryId: rate.countryId,
                name: rate.name,
                countStart: parseFloat(rate.startRange),
                countEnd: parseFloat(rate.endRange),
                price: parseFloat(rate.price)
            });
        });
        var existingItemIndex = _.findIndex(self.shippingLocations(),
            function (shippingLocation) {
                return shippingLocation.groupId == groupId;
            });
        var currentLocation = {
            groupId: groupId,
            states: states,
            statesAsString: self.getStateNamesAsString(states),
            country: country,
            rates: ko.observableArray(rateList)
        };
        if (existingItemIndex == -1) {
            self.shippingLocations.push(currentLocation);
        }
        else {
            self.shippingLocations.replace(self.shippingLocations()[existingItemIndex], currentLocation);
        }
        self.sortShippingLocations();
    }

    self.addSavedTaxLocations = function (groupId, rates, country, states) {
        _.forEach(rates, function (rateItem) {
            self.taxLocations.push({
                groupId: groupId,
                country: country || {},
                states: states,
                statesAsString: self.getStateNamesAsString(states),
                taxValue: parseFloat(rateItem.rate)
            });
        });
        self.sortTaxLocations();
    }
    self.handleFilteredRates = function (groupId, rateType, filteredRates, country, stateList) {
        if (rateType === 'shippingrate') {
            self.addSavedShippingLocations(groupId, filteredRates, country, stateList);
        }
        else if (rateType === 'taxrate') {
            self.addSavedTaxLocations(groupId, filteredRates, country, stateList);
        }
    }

    self.handleRates = function (countries, states, rates, rateType) {
        if (rates) {
            var groupIds = rates.map(function (rate) { return rate.groupId; });
            groupIds = _.filter(groupIds, function (value, index, self) {
                return self.indexOf(value) === index;
            });
            _.forEach(groupIds,
                function (groupId) {

                    var filteredRatesByGroupId = _.filter(rates, function (rate) {
                        return rate.groupId == groupId;
                    });
                    var countryId = filteredRatesByGroupId[0].countryId;
                    var country = _.find(countries, function (item) { return item.id === countryId; });

                    if (countryId === 0) {

                        var stateIds = filteredRatesByGroupId.map(function (rate) { return rate.stateId; });
                        stateIds = _.filter(stateIds, function (value, index, self) {
                            return self.indexOf(value) === index;
                        });
                        stateIds.sort();
                        var stateList = [];
                        if (stateIds && stateIds.length > 0) {
                            _.forEach(stateIds, function (stateId) {
                                if (stateId != undefined && stateId != '') {
                                    var state = _.find(states, function (item) { return item.shortName === stateId; });
                                    stateList.push({ state: state });
                                }
                            });
                        }
                        var filter = { requesttype: 'items', page: 1, count: 999 };
                        if (stateIds && stateIds.length > 0) {
                            filter['grid-filter'] = 'State__1__' + stateIds[0];
                        }
                        else {
                            filter['grid-filter'] = 'CountryId__1__0';
                        }
                        ProxyService.send({}, rateType, 'GET', filter, function (filteredRatesByCountry) {
                            self.handleFilteredRates(groupId, rateType, filteredRatesByCountry, country, stateList);
                        });

                    }
                    else {
                        self.handleFilteredRates(groupId, rateType, filteredRatesByGroupId, country, stateList);
                    }


                });
        }
    }

    self.initData = function () {

        var countries = UI.store.getCountries();
        var states = UI.store.getStates();
        
        self.shippingLocations([]);

        //add new temp data
        var newLocations = ko.toJS(self.newShippingLocations());
        _.forEach(newLocations,
            function (newLocation) {
                if (newLocation) {
                    var existingItemIndex = _.findIndex(self.shippingLocations(),
                        function (item) {
                            return item.groupId == newLocation.groupId;
                        });
                    if (existingItemIndex == -1) {
                        self.shippingLocations.push({
                            groupId: newLocation.groupId,
                            states: newLocation.states,
                            statesAsString: self.getStateNamesAsString(newLocation.states),
                            country: newLocation.country,
                            rates: ko.observableArray([])
                        });
                        self.sortShippingLocations();
                    }
                }
            });
        
        UI.store.getShippingRates(function (data) {
            self.handleRates(countries, states, data, 'shippingrate');
            self.sortShippingLocations();
        });

        UI.store.getTaxRates(function (data) {
            self.taxLocations([]);
            self.handleRates(countries, states, data, 'taxrate');
            
        });
    }

    self.addShippingLocation = function () {
        var newShippingLocations = self.updateNewShippingLocations();
        var newStates = self.updateNewStates();
        viewEntity('#manage-store-products-add-shipping-location', { newShippingLocations: newShippingLocations, newStates: newStates });
    }

    self.addNewShippingLocations = function (location) {
        var groupLocation = {
            groupId: location.groupId,
            states: location.states,
            country: location.country
        };
        self.newShippingLocations.push(groupLocation);
    }

    self.updateNewShippingLocations = function () {
        var newShippingLocations = [];
        _.forEach(self.newShippingLocations(), function (location) {
            newShippingLocations.push(location.country.id);
        });
        return newShippingLocations;
    }
    self.updateNewStates = function () {
        var newStates = [];
        _.forEach(self.newShippingLocations(), function (location) {
            _.forEach(location.states, function (state) {
                newStates.push(state.state.shortName);
            });
        });
        return newStates;
    }

    self.removeShippingLocation = function () {
        self.removeLocation(this.groupId, 'shippingrate');
    }

    self.removeTaxLocation = function () {
        self.removeLocation(this.groupId, 'taxrate');
    }

    self.removeLocation = function (groupId, rateType) {
        var filter = { requesttype: 'items', page: 1, count: 999, 'grid-filter': 'GroupId__1__' + groupId };
        ProxyService.send({}, rateType, 'GET', filter, function (rates) {
            if (rates) {
                rates = _.map(rates, function (item) { return { id: item.id, isActive: false } });
                ProxyService.send({ data: rates, isArrayBodyRequest: true },
                    rateType,
                    'PUT',
                    {},
                    function () {
                        if (rateType == 'taxrate') {
                            self.taxLocations(_.filter(self.taxLocations(),
                            function (item) { return item.groupId !== groupId }));
                        }
                        else if (rateType == 'shippingrate') {
                              self.newShippingLocations(_.filter(self.newShippingLocations(),
                            function (item) { return item.groupId !== groupId }));
                        }
                        self.init();
                    });
            }
            else {
                if (rateType == 'shippingrate') {
                    self.newShippingLocations(_.filter(self.newShippingLocations(),
                           function (item) { return item.groupId !== groupId }));
                    self.init();
                }
            }
        });
    }

    self.removeShippingRate = function(parent, rate) {
        var callback = function () {
            self.init();
        }
        ProxyService.send({ id: rate.id }, 'shippingrate', 'DELETE', {}, callback);
    }

    self.addRate = function() {
        viewEntity('#manage-store-products-add-shipping-rate', { groupId: this.groupId, country: this.country, states: this.states });
    }

    self.addTaxLocation = function () {
        viewEntity('#manage-store-products-add-tax-location', { groupId: this.groupId, country: this.country, states: this.states });
    }

    self.init(data);

    return self;
}

var ManageStoreProductsAddShippingRateViewModel = function (data) {
    var self = this;
    self.currency = '';
    self.weightUnit = '';
    self.name = ko.observable();
    self.price = ko.observable();
    self.countStart = ko.observable();
    self.countEnd = ko.observable();
    self.type = ko.observable();

    self.init = function (data) {
        self.groupId = data.groupId;
        self.country = data.country;
        self.states = data.states;
        self.currency = UI.store.getCurrency().symbol;
        self.weightUnit = UI.store.getWeightUnit().symbol;
        self.name('');
        self.price(0);
        self.countStart(0);
        self.countEnd(0);
        self.type(1);
    }

    self.save = function () {
        var rates = [];
        _.forEach(self.states, function (state) {
            var rate = {
                countryId: self.country.id,
                stateId: state.state.shortName,
                groupId: self.groupId,
                name: self.name(),
                price: self.price(),
                startRange: self.countStart(),
                endRange: self.countEnd(),
                typeId: self.type()
            }
            rates.push(rate);
        });
        if (self.country.id !== 0) {
            var rate = {
                countryId: self.country.id,
                stateId: '',
                groupId: self.groupId,
                name: self.name(),
                price: self.price(),
                startRange: self.countStart(),
                endRange: self.countEnd(),
                typeId: self.type()
            }
            rates.push(rate);
        }
        
        ProxyService.send({ data: rates, isArrayBodyRequest: true }, 'shippingrate', 'POST', {}, function (data) {
            self.back();
        });

    }

    self.toggleType = function () {
        if (self.type() === 1) {
            self.type(2);
        } else {
            self.type(1);
        }
    }

    self.back = function () {
        $("#shippingAndTaxTab").click();
    }

    self.init(data);
    return self;
}

var ManageStoreProductsAddShippingLocationViewModel = function (data) {
    var self = this;
    self.selectedCountry = ko.observable('');
    self.previousGroupId = -1;
    self.newShippingLocations = [];
    self.selectedCountry = ko.observable();
    self.selectedStates = ko.observableArray([]);
    self.newStates = [];


    self.init = function (data) {
        self.selectedCountry('');
        if (data && data.newShippingLocations) {
            self.newShippingLocations = data.newShippingLocations;
        }
        if (data && data.newStates) {
            self.newStates = data.newStates;
        }
        self.initCountries();
    }

    

    self.initCountries = function () {
        self.countries = ko.observableArray([]);
        self.states = ko.observableArray([]);
        self.availableCountries = UI.store.getCountries();
        self.availableStates = UI.store.getStates();
        ko.bindingHandlers.optionsShippingBind = {
            preprocess: function (value, key, addBinding) {
                addBinding('optionsAfterRender', 'function(option, item) { ko.bindingHandlers.optionsShippingBind.applyBindings(option, item, ' + value + ') }');
            },
            applyBindings: function (option, item, bindings) {
                if (item !== undefined) {
                    option.setAttribute('data-bind', bindings);
                    ko.applyBindings(ko.contextFor(option).createChildContext(item), option);
                }
            }
        };
        UI.store.getShippingRates(function (data) {
            if (data) {
                var savedCountryIds = data.map(function (element) { return element.countryId; });
                var savedStateIds = data.map(function (element) { return element.stateId; });
                var groupIds = data.map(function (element) { return element.groupId; });
                self.previousGroupId = self.previousGroupId > -1 ? self.previousGroupId : (groupIds.length > 0 ? Math.max.apply(-1, groupIds) : -1);
                _.forEach(self.availableCountries, function (country) {
                    country.enabled = ko.observable(country.id === 0 || ($.inArray(country.id, savedCountryIds) == -1 && $.inArray(country.id, self.newShippingLocations) == -1));
                });
                self.countries(self.availableCountries);
                _.forEach(self.availableStates, function (state) {
                    state.enabled = ko.observable($.inArray(state.shortName, savedStateIds) == -1 && $.inArray(state.shortName, self.newStates) == -1);
                });
                self.states(self.availableStates);
            }
        });
    }

    self.save = function () {
        var shipingLocation = {};
        var states = [];
        var selectedCountry = self.selectedCountry();
        var selectedStates = self.selectedStates();
        self.newShippingLocations.push(selectedCountry);
        self.newStates = self.newStates.concat(selectedStates);
        self.previousGroupId++;
       
        if (selectedCountry > 0 || selectedCountry == 0 && selectedStates.length > 0) {
            country = _.find(self.countries(), function (item) {
                return item.id === selectedCountry;
            });
            country.groupId = self.previousGroupId;
            if (country.id == 0 && selectedStates) {
                _.forEach(_.filter(self.states(),
                        function (item) {
                            return selectedStates.indexOf(item.shortName) !== -1;
                        }),
                    function (state) {
                        states.push({
                            state: state
                        });
                    });
            }
            shipingLocation = {
                groupId: self.previousGroupId,
                country: country,
                states: states
            }
            var viewModel = UI.getViewModel("#manage-store-products-tab-shipping-and-tax", {});
            if (viewModel) {
                viewModel.addNewShippingLocations(shipingLocation);
                self.back();
            }
        }
    }

    self.back = function () {
        $("#shippingAndTaxTab").click();
    }

    self.init(data);
    return self;
}

var ManageStoreProductsAddTaxLocationViewModel = function (data) {
    var self = this;
    self.selectedCountry = ko.observable('');
    self.previousGroupId = -1;
    self.selectedCountry = ko.observable();
    self.selectedStates = ko.observableArray([]);
    self.taxValue = ko.observable();

    self.init = function (data) {
        self.selectedCountry('');
        self.taxValue(0.0);
        self.initCountries();
    }

    self.initCountries = function () {
        self.countries = ko.observableArray([]);
        self.states = ko.observableArray([]);
        self.availableCountries = UI.store.getCountries();
        self.availableStates = UI.store.getStates();
        ko.bindingHandlers.optionsTaxBind = {
            preprocess: function (value, key, addBinding) {
                addBinding('optionsAfterRender', 'function(option, item) { ko.bindingHandlers.optionsTaxBind.applyBindings(option, item, ' + value + ') }');
            },
            applyBindings: function (option, item, bindings) {
                if (item !== undefined) {
                    option.setAttribute('data-bind', bindings);
                    ko.applyBindings(ko.contextFor(option).createChildContext(item), option);
                }
            }
        };
        UI.store.getTaxRates(function (data) {
            if (data) {
                var savedCountryIds = data.map(function (element) { return element.countryId; });
                var savedStateIds = data.map(function (element) { return element.stateId; });
                var groupIds = data.map(function (element) { return element.groupId; });
                self.previousGroupId = self.previousGroupId > -1 ? self.previousGroupId : (groupIds.length > 0 ? Math.max.apply(-1, groupIds) : -1);
                _.forEach(self.availableCountries, function (country) {
                    country.enabled = ko.observable(country.id === 0 || ($.inArray(country.id, savedCountryIds) == -1));
                });
                self.countries(self.availableCountries);
                _.forEach(self.availableStates, function (state) {
                    state.enabled = ko.observable($.inArray(state.shortName, savedStateIds) == -1);
                });
                self.states(self.availableStates);
            }
        });
    }

    self.save = function () {
        var rates = [];
        var selectedCountry = self.selectedCountry();
        var selectedStates = self.selectedStates();
        self.previousGroupId++;

        if (selectedCountry > 0 || selectedCountry == 0 && selectedStates.length > 0) {
            country = _.find(self.countries(), function (item) {
                return item.id === selectedCountry;
            });
            country.groupId = self.previousGroupId;
            if (country.id == 0 && selectedStates) {
                _.forEach(_.filter(self.states(),
                        function (item) {
                            return selectedStates.indexOf(item.shortName) !== -1;
                        }),
                    function (state) {
                        rates.push({
                            groupId: self.previousGroupId,
                            countryId: country.id,
                            stateId: state.shortName,
                            rate: parseFloat(self.taxValue())
                        });
                    });
            }
            else {
                rates.push({
                    groupId: self.previousGroupId,
                    countryId: country.id,
                    stateId: '',
                    rate: parseFloat(self.taxValue())
                });
            }

            ProxyService.send({
                data: rates,
                isArrayBodyRequest: true
            }, 'taxrate', 'POST', {}, function (data) {    
                self.back();
            });
        }
    }

    self.back = function () {
        $("#shippingAndTaxTab").click();
    }

    self.init(data);
    return self;
}

var StoreCategoriesViewModel = function (component) {
    var self = this;
    self.component = component;    
    self.text = ko.observable();
    self.categories = ko.observableArray([]);

    self.init = function (component) {
        self.component = component;
        self.categories([new Category({name: 'All', id: 0})]);
        self.text(self.component.getProperty(TEXT).value);
        self.selectedCategoryId = ko.observable(0);
        self.selectedCategoryId.subscribe(function (newValue) {
            _.forEach(UI.store.getGalleries(),
                function (galleryComponent) {
                    var viewModel = UI.viewModelRepository['store-gallery' + galleryComponent.id];
                    if (viewModel) {
                        if (newValue) {
                            viewModel.setFilter({ CategoryId: newValue });
                        } else {
                            viewModel.setFilter({});
                        }
                        
                    }
                });
        });
        UI.store.getCategories({ 'grid-filter' :'ParentId__10__null'},
            function(data) {
                if (data) {
                    _.forEach(data,
                        function(category) {
                            self.categories.push(new Category(category));
                        });
                }
            });
    };

    self.changeFilter = function () {
        if (self.selectedCategoryId() === this.id || this.id === 0) {
            self.clearFilter();
        } else {
            self.text(this.name);
            self.selectedCategoryId(this.id);
        }
    }

    self.clearFilter = function () {
        self.text(self.component.getProperty(TEXT).value);
        self.selectedCategoryId(0);
    }

    self.init(component);
    return self;
};
/// <reference path="jquery-2.1.1.intellisense.js" />
/// <reference path="bootstrap.js" />
/// <reference path="knockout-3.0.0.debug.js" />
/// <reference path="knockout.punches.js" />
/// <reference path="~/Content/Editor/js/popover-helper.js" />
/// <reference path="~/Content/Editor/js/design.js" />
/// <reference path="~/Content/Editor/js/helpers.js" />
/// <reference path="~/Content/Editor/js/templateCache/templateCache.js" />
/// <reference path="~/Content/Editor/js/editor.js" />
/// <reference path="~/Content/Editor/js/template.js" />
/// <reference path="~/Content/Editor/js/definitions.js" />
/// <reference path="~/Content/Editor/js/clipBoard/clipBoard.js" />
/// <reference path="~/Content/Editor/js/resizer.js" />
/// <reference path="~/Content/Editor/js/docking.js" />

var UI = function () { }
var autoSaveTimer = null;
UI.newComponents = [];

	UI.init = function () {
		UI.undoManager = new UndoManager();
		UI.componentcreatedispose = eventsystem.subscribe('/component/create/', UI.componentCreatedHandler);
	}

	//configuring ui for proper work
	UI.syncGoogleAnalitycs = function (curstep, code) {
	    if (!defined(UI.gogleAnalitycsStore)) UI.gogleAnalitycsStore = { curstep: 1, code: null }
	    if (defined(curstep)) {
	        if (code == null) code = UI.gogleAnalitycsStore.code;
	        UI.gogleAnalitycsStore = { curstep: curstep, code: code }
	    }
        else 
	        return UI.gogleAnalitycsStore;
	}


    //configuring ui for proper work
	UI.configure = function (config) {
	    UI.config = config;
	    if (!UI.getSetting("ispublished")) {
	        $(UI.getConfigurationValue(HTML)).bind('click', function (event) {
	           
	            if ($(event.target).hasClass("fa-clone")) {
	                return true;
	            } else if ($(event.target).hasClass("fa-clipboard")) {
	                return true;
	            }
	            UI.removeEditor();
	        });
	    }
	        
		PointerEventsPolyfill.initialize({});
	}

	UI.initDevices = function (devices) {	    
	    UI.devices = [];        
	    _.forEach(devices, function (device) {
	        var newDevice = new Device(device);
	        UI.devices.push(newDevice);	        

	        if (!UI.getSetting("ispreview") && newDevice.isDefault) {                
                //editor
	            UI.setDevice(newDevice, true);
	            //if devices not exist in model
	            if (!UI.getTemplateProperty('devices').length) {
	                UI.addDeviceIdToTemplateData(newDevice.getId());
	            }
	        } else if (UI.getSetting("ispreview")) {
	            if (UI.getSetting("isThumbnailPreview")) {
	                if (newDevice.isDefault) {
                        UI.setDevice(newDevice, true);
	                }	                
	            }
	            else if (UI.getSetting("ispublished")) {
                    //published
	                //check width
	                if (newDevice.isWidthInRange(UI.getSetting("width"))) {
	                    UI.setDevice(newDevice, true);
	                }
	            } else {
                    //viewer
	                if (Helpers.getQueryParamValue('deviceId')) {
	                    if (Helpers.getQueryParamValue('deviceId') == newDevice.getId() && UI.hasDeviceIdInTemplateData(newDevice.getId())) {
	                        //device in query
	                        UI.setDevice(newDevice, true);
	                    }  
	                } else if (newDevice.isDefault) {
	                    //default device in viewer
	                    UI.setDevice(newDevice, true);
	                }
	            }
	        }
	    });
        //if device not set but default device available
	    if (!UI.getDevice() && UI.getDefaultDeviceId()) {
	        if (UI.getSetting("ispreview") && !UI.getSetting("ispublished") && Helpers.getQueryParamValue('deviceId')) {
                //remove query param and reload if deviceId in query not valid
	            Helpers.changeQueryWithoutReload(Helpers.removeQueryParam('deviceId'));	            
	        }
	        UI.setDevice(UI.getDevice(UI.getDefaultDeviceId()), true);
	    }
	}

	UI.setDevice = function (device, initial) {
	    if (!UI.settings) { UI.settings = {} };
	    if (UI.getDevice() && UI.getDevice().getId() === device.getId()) return;
	    if (!device.isHaveComponentProperties()) {
	        Device.setDeviceRequest(device);
	    }
	    
	    if (UI.settings.current_device) {
	        $('.site-wrapper').removeClass(UI.settings.current_device.getType());
	    }
	    UI.settings.current_device = device;        

	    if (device.isDesktop()) {            
	        $('#design-management').removeClass('disabled').attr('data-original-title', 'Design Management').bind('click', function (event) {
	            UI.displayDesignPopover(event);
	        });
	        $('#page-management').removeClass('disabled').attr('data-original-title', 'Page Management').bind('click', function (event) {
	            UI.displayPagePopover(event);
	        });
	        $('#history-management').removeClass('disabled').attr('data-original-title', 'History').bind('click', function (event) {
	            UI.displayHistoryPopover(event);
	        });
	        $('#site-settings').removeClass('disabled').attr('data-original-title', 'Site Settings').bind('click', function (event) {
	            UI.displaySitePopover(event);
	        });
	        $('#store').removeClass('disabled').attr('data-original-title', 'My Store').bind('click', function (event) {
	            UI.displayStorePopover(event);
	        });

	        $('.position-add-new-com').show();
	    } else {	        
	        $('#design-management').addClass('disabled').attr('data-original-title', 'Available only on Desktop view').unbind('click');
	        $('#page-management').addClass('disabled').attr('data-original-title', 'Available only on Desktop view').unbind('click');
	        $('#site-settings').addClass('disabled').attr('data-original-title', 'Available only on Desktop view').unbind('click');
	        $('#history-management').addClass('disabled').attr('data-original-title', 'Available only on Desktop view').unbind('click');
	        $('#store').addClass('disabled').attr('data-original-title', 'Available only on Desktop view').unbind('click');

	        $('.position-add-new-com').hide();
	    }
	    $('.site-wrapper').css('width', device.getWidth() + 'px').addClass(device.getType());

	    if (!initial) {
	        EditorApplication.reBuild();
	    }
        
	    if (UI.getSetting("ispreview")) {
            //bind viewport
	        Helpers.bindViewportToResize(UI.getSetting("width"), device.getWidth());
	        $('body').addClass(device.getType());
	    }

	    if (!UI.getSetting("ispreview")) {
            clipBoard.isDesktopView(device.isDesktop());
        }
	}

	UI.getDevice = function (id) {
        if (!id) {
	    return UI.settings.current_device;
        } else {
            return _.find(UI.devices, function(item) {
                return item.id === id;
            });
        }
	}

    UI.getDefaultDeviceId = function() {
        return UI.devices.where({ isDefault: true }).firstOrDefault().getId();
    }

    UI.logs = [];

    UI.addLog = function (action, model) {
        UI.logs.push(new LogItem({
            action: action,
            controlId: model.id,
            componentId: model.componentId,
            parentId: model.parentComponent.id
        }));
    }

    UI.removeLog = function (action, model) {
        _.remove(UI.logs,
            function(log) {
                return log.action === action && model.id === log.controlId;
            });
    }

    UI.exportLogs = function () {
        UI.getBody().setProperty(LOGS_PROPERTY, JSON.stringify(UI.logs.map(function(item) { return item.toString(); })));
    }

    UI.resetLogs = function() {
        UI.getBody().setProperty(LOGS_PROPERTY, '');
        UI.logs = [];
    }

//injecting repos for ui usage
	UI.injectRepositories = function (siteComponentRepository, basicComponentRepository) {
		UI.siteComponentRepository = siteComponentRepository;
		UI.basicComponentRepository = basicComponentRepository;
		UI.viewModelRepository = {};
	}

	UI.setViewModel = function (type, model) {
	    UI.viewModelRepository[type] = model;
	}

	UI.getViewModel = function (type, data, prefix, init) {
        prefix = defined(prefix)? prefix : '' ;
        init = defined(init) ? init : true;
        var viewModel = UI.viewModelRepository[type + prefix];
        if (viewModel) {
            if (init) {
                viewModel.init(data);
            }
	        return viewModel;
	    } else {
	        viewModel = ViewModelFactory.getModel(type, data);
	        UI.setViewModel(type + prefix, viewModel);
	        return viewModel;
	    }
	}

	UI.injectServices = function (componentService) {
	    UI.componentService = componentService;
	}

	UI.getTooltips = function () {
	    $.ajax({
	        url: "/Editor/GetTooltips",
	        type: "GET",
	        success: function (data) {
	            UI.tooltips = new Tooltips(data);
	        }
	    });
	}

	UI.injectPostLoadInit = function (postLoadInit) {
	    UI.postLoadInit = postLoadInit;
	}

	UI.injectVideo = function (video) {
	    UI.MediaService = video;
	}

	UI.injectRulerGuides = function (rulerGuides) {
	    UI.RulerGuides = rulerGuides;
	}

	UI.injectJsonCompareService = function (compareService) {
	    UI.jsonCompareService = compareService;
	}

	UI.injectSwitchManager = function (switcher) {
	    UI.SwitchManager = switcher;
	}

	UI.injectActionService = function (actionService) {
	    UI.actionService = actionService;
	}

	UI.injectAuthService = function (authService) {
	    UI.authService = authService;
	    Application.addLocker();
	    UI.authService.isAuthentificate(true);
	    Application.removeLocker();
	}

	
	UI.getConfigurationValue = function (name) {
		if (defined(UI.config[name])) {
			return UI.config[name];
		}
		return String.empty;
	}

	UI.setSettings = function (settings) {
		if (settings !== null) {
		    UI.settings = $.extend({}, {
                isadmin: false,
		        templateId: "",
		        ispreview: false,
		        adminSiteDomain: "",
		        isThumbnailPreview: false,
		        ispublished: false,
		        isrender: false,
		        isrenderpublish: false,
		        deviceType: SWITCHER_DESKTOP,
		        actionType: 0,
		        width: 960,
		        onlyDesktop: false,
		        showHidden: false,
                showFirstSavePopup: true
		    }, settings);
		}	    
	}

	UI.ajaxSetup = function () {
	    var templateId = UI.getTemplateProperty("templateId");
	    var headers = {};
	    if (!$.ajaxSettings.headers || !$.ajaxSettings.headers["TemplateId"] || $.ajaxSettings.headers["TemplateId"] !== templateId) {
	        headers["TemplateId"] = templateId;	        
	    }
	    var authorizationToken = UI.getSetting("webApiToken");
	    if (!UI.getSetting("ispreview") && authorizationToken && (!$.ajaxSettings.headers || !$.ajaxSettings.headers["Authorization"] || $.ajaxSettings.headers["Authorization"] !== templateId)) {
	        headers["Authorization"] = authorizationToken;
	    }
	    $.ajaxSetup({ headers: headers });
	}

	UI.getSetting = function (name) {
		try {
			if (defined(UI.settings[name]))
			{
				return UI.settings[name];
			}
		}
		catch (e) {
			console.log('UI.getSetting fail:' + name);	
		}
		//finally {		
		//	var def = {
		//		templateId: "",
		//		ispreview: false
		//	};
		//	return def[name] || String.empty;
		//}        
	}

	UI.undoManagerUndo = function () {
	    if ($('.design-popover-custom').is(':visible')) {
	        paletteCount--;
	        if (paletteCount < 1) {
	           $('#palete-undo').addClass("not-active");
	            UI.undoManager.undo();
	            
	        } else {
	            UI.undoManager.undo();
	        }
	        
	    } else {
	        UI.removeEditor();
	        UI.undoManager.undo();
	    }
		
	}

	UI.undoManagerRedo = function () {
		UI.removeEditor();
		UI.undoManager.redo();
	}

	UI.undoManagerClear = function () {
		UI.undoManager.clear();
	}

	UI.undoManagerAdd = function (struct) {
		UI.undoManager.add(struct);
	}

	UI.undoManagerGetLatestProperty = function () {
		return UI.undoManager.getLatestProperty();
	}

	UI.undoManagerGetLatestComponentId = function () {
		return UI.undoManager.getLatestComponentId();
	}

	UI.undoManagerAddSpecific = function (struct, action) {
		UI.undoManager.addSpecificActionToEnd(struct, action);
	}

	UI.undoManagerAddSimple = function (component, property, newvalue, callback, isRun) {
		return UI.undoManagerAddSimpleArr([{component: component, property: property, newvalue: _.clone(newvalue), oldvalue: '' }], callback, callback, isRun);
	}

	UI.undoManagerGetLatestCommand = function (struct) {
	    return UI.undoManager.getLatestCommand();
	}

/*
           var struct = {
		        actionObj: this,
		        action: action,
		        oldvalue: oldPaletteObj,
		        newvalue: newPaletteObj
		    };

*/


	UI.undoManagerAddNewItemToUpdatedStructSimpleArr = function (undoRedoItem, structNew, isRun) {
	    Helpers.consoleLog('structNew ', structNew);
	    Helpers.consoleLog('undoRedoItem ', undoRedoItem);
	    var undoredo = function (values, action) {
	        if (action == 'undo')
	            values.forEach(function (struct) {
	                if (struct.action) {//test if action  = execute 
	                    struct.action(struct.oldvalue);
	                }
	                else {//change componnnts properties
	                    struct.component.setProperty(struct.property, struct.oldvalue);
	                    undoRedoItem.callbackundo(struct.component, struct.property, struct.oldvalue);
	                };
	            });
	        else
	            values.forEach(function (struct) {
	                if (struct.action) {
	                    struct.action( struct.newvalue);
	                }
	                else {
	                    struct.component.setProperty(struct.property, struct.newvalue);
	                    undoRedoItem.callbackredo(struct.component, struct.property, struct.newvalue);
	                };
	            });
	    }
        //if structs is not exist - set empty array
	    if (!undoRedoItem.structs && undoRedoItem.callbackredo && undoRedoItem.callbackundo) {
	        undoRedoItem.structs = [];
	    }
        //check structs
	    if (undoRedoItem.structs) {
	        undoRedoItem.structs.push(structNew);

	        undoRedoItem.undo = function () {
	            undoredo(undoRedoItem.structs, 'undo')
	        };

	        undoRedoItem.redo = function () {
	            undoredo(undoRedoItem.structs, 'redo');
	        };
	    }

	    if (isRun) {
	        if (structNew.action) {
	            structNew.action(structNew.newvalue);
	        }
	        else {
	            structNew.component.setProperty(structNew.property, structNew.newvalue);
	            //set css
	            $(structNew.component.getUISelector()).css(structNew.property, structNew.newvalue);
	            //if don't have callbackredo
	            if (undoRedoItem.callbackredo) {
	                undoRedoItem.callbackredo(structNew.component, structNew.property, structNew.newvalue);
	            }
	        };
	    }    

	}

	UI.undoManagerAddUpdatedStructSimpleArr = function (callbackredo, callbackundo) {
	    callbackredo = callbackredo || function () { };
	    callbackundo = callbackundo || function () { };

	    var st = {
	        undo: null,
	        redo: null,
	        structs: [],
	        callbackredo: callbackredo,
	        callbackundo: callbackundo
	    };

	    UI.undoManagerAdd(st);
	}

	UI.undoManagerAddSimpleArr = function (structs, callbackredo, callbackundo, isRun) {
    
		callbackredo = callbackredo || function () { };
		callbackundo = callbackundo || function () { };

		structs.forEach(function (struct) {
		    if (struct.oldvalue == '') {
		        struct.oldvalue = _.clone(struct.component.getProperty(struct.property).value);
		    }
		});    

		var undoredo = function (values, action) {
			values.forEach(function (struct, index) {
			    
				if (action == 'undo') {
					struct.component.setProperty(struct.property, struct.oldvalue);
					callbackundo(struct.oldvalue, struct.component, struct.property, index, values.length);
				} else {
					struct.component.setProperty(struct.property, struct.newvalue);
					callbackredo(struct.newvalue, struct.component, struct.property, index, values.length);
				}
			});

		}

		var st = {
			undo: function () {
				undoredo(structs, 'undo');
			},
			redo: function () {
				undoredo(structs, 'redo');
			}
		};

		UI.undoManagerAdd(st);

		if (isRun)
		{
			st.redo();
		}

		return st;
	}

	UI.undoManagerAddRemoving = function (id, expression, callback) {

		var componentid = _.clone(id);
		var elementExpr = _.clone(expression);

		var redo = function () {
			var removed = UI.siteComponentRepository.remove({ id: componentid });
			callback();
			return removed;
		}

		var removed = redo();

		var undo = function () {
			var parent = UI.siteComponentRepository.lookupData({ id: removed.parentComponent.id })
			UI.siteComponentRepository.appendTo(removed, parent);
			callback();
		}


		UI.undoManagerAdd(
			{
				undo: function () {
					undo();
				},
				redo: function () {
					redo();
				}
			});
	}

	UI.undoManagerAddAddElements = function (items, callback) {
		var redo = function () {
			items.forEach(function (item) {
				var parent = UI.siteComponentRepository.lookupData({ id: item.parentComponent.id })
				UI.siteComponentRepository.appendTo(item, parent);
			});

			callback();
		}

		var undo = function () {

			items.forEach(function (item) {
				UI.siteComponentRepository.remove({ id: item.id });
			});

			callback();
		}


		UI.undoManagerAdd(
			{
				undo: function () {
					undo();
				},
				redo: function () {
					redo();
				}
			});
	}

	//setting template data
	UI.setTemplateData = function (templateData) {
		UI.templateData = templateData;
	}
	//setting template property
	UI.setTemplateProperty = function (property, value) {
		if (defined(UI.templateData)) {
			UI.templateData[property] = value;
		}
	}
	//getting template property
	UI.getTemplateProperty = function (property) {
		if (defined(UI.templateData) && UI.templateData.hasOwnProperty(property)) {
			return UI.templateData[property];
		};
		return null;
	}
	//setting template properties in batch
	UI.setTemplateProperties = function (properties) {
		if (defined(UI.templateData)) {
			var keys = Object.keys(properties);
			if (keys.length > 0) {
				keys.forEach(function (item, index) {
					UI.setTemplateProperty(item, properties[item]);
				});
			}
		}
	}
	//readding controls set to template
	UI.addControlsToTemplateData = function (controls) {
		if (defined(UI.templateData)) {
			UI.templateData.controls = controls;
		}
	}
	//returing repo controls as json
	UI.templateToJson = function () {
		if (defined(UI.templateData)) {
			UI.addControlsToTemplateData(UI.siteComponentRepository.resolveDependecies());

		   return JSON.stringify(UI.templateData);
		}
		return JSON.stringify({});
	}

	UI.hasDeviceIdInTemplateData = function (deviceId) {
	    return _.indexOf(UI.getTemplateProperty('devices'), deviceId) !== -1;
	}

    UI.addDeviceIdToTemplateData = function(deviceId) {
        var devices = UI.getTemplateProperty('devices');
        devices.push(deviceId);
        UI.setTemplateProperty('devices', devices);
    }

    UI.removeDeviceIdFromTemplateData = function(deviceId) {
        var devices = UI.getTemplateProperty('devices');        
        _.remove(devices, function (item) { return item == deviceId });
        UI.setTemplateProperty('devices', devices);
    };

//setting pager
	UI.setPager = function (pager) {
		if (defined(pager)) {
			UI.pager = pager;
		} else {
			UI.pager = null;
		}
	}

	UI.setAnchor = function (anchor) {
	    if (defined(anchor)) {
	        UI.anchor = anchor;
	    } else {
	        UI.anchor = null;
	    }
	}

	UI.setSiteSettings = function (siteSettings) {
	    if (defined(siteSettings)) {
	        UI.siteSettings = siteSettings;
	    } else {
	        UI.siteSettings = null;
	    }
	}
	//setting designer
	UI.setDesigner = function (designer) {
		if (defined(designer)) {
			UI.designer = designer;
		} else {
			UI.designer = null;
		}
	}

	UI.setPaletteId = function () {
	    var templateid = UI.settings.templateId;
	    $.getJSON("/palettes/template/" + templateid).done(function (palette) {
	        try {
	            UI.currentPaletteId = palette.Id;
	        }
	        catch (ex) {
	            console.log('Error on load default palette id');
	        }
	    });
	}
	//menu rendering
	UI.renderMenus = function() {
		MenuHelper.renderMenus();
	}
	//removing all editors and associated wrappers:dock, popovers, colorpicker etc
	UI.removeEditor = function () {
	    if ($('#locker').length == 1 || $('#component-modal > .modal').length || UI.isckeditorworking) {
			return false;
		}
		$(UI.getConfigurationValue(EDITOR)).find('input').blur();
		$(UI.getConfigurationValue(EDITOR)).remove();
		$(UI.getConfigurationValue(SELECT_WRAPPER)).remove();
		dragDrop.releaseElement();
		$('*').data('selected', false);
		//$('*').removeClass('drag');
		PopoverHelper.hidePopovers();
		ColorPickerHelper.hide();
		$('.upclick-container').remove();
		$('#gallery-editor').remove();
		$('#gallery-pre-editor').remove();

		var componentNamesWithPreEditor = ["list", "contact-us"];
		var nameIndex;
		for (nameIndex = 0; nameIndex < componentNamesWithPreEditor.length; nameIndex++) {
			var componentsWithPreEditor = UI.siteComponentRepository.lookupDataSet({ displayName: componentNamesWithPreEditor[nameIndex] });
			var index;
			for (index = 0; index < componentsWithPreEditor.length; index++) {
				var componentWithPreEditor = componentsWithPreEditor[index];
				if (componentWithPreEditor.isNotApproved == true) {
					UI.siteComponentRepository.remove({ id: componentWithPreEditor.id });
				}
			}
		}
	}
	//if inline ckeditor is currently active
	function hasActiveCKEditor() {
		for (name in CKEDITOR.instances) {
			var displayProp = $('#cke_' + name).css('display');
			if (displayProp != "block") {
				return true;
			}
		}
		return false;
	}
	UI.removeDockContainer = function () {
		$(UI.getConfigurationValue(DOCK_WRAPPER)).remove();
	}

	UI.lastKeyDownEvent = null;

	//processing keydown handling(e.g. removal)
	UI.handleKeyDown = function (siteComponentRepository) {
	    var handleElementDelete = function () {
	        var selectedElementWrapper = $(UI.getConfigurationValue(SELECT_WRAPPER));
	        if (selectedElementWrapper.length > 0) {
	            var selectedElement = $('#' + selectedElementWrapper.data('for'));
	            var id = selectedElement.getId();
	            var selectedComponent = siteComponentRepository.lookupData({ id: id });

	            if (selectedComponent == null ||
	                $('#' + selectedComponent.id).attr('contenteditable') == "true" ||
	                !Helpers.allowDelete(selectedComponent)) {
	                return;
	            }

	            var redo = function () {
	                var removedModel = siteComponentRepository.remove({ id: selectedComponent.id });
	                UI.addLog('remove', removedModel);
	                UI.removeEditor();
	                return { model: removedModel};
	            };
	            var removed = redo();
	            var undo = function () {
                    //return model with actions
	                function returnRemoveModel(removedModel) {                        
	                    var specialComponentId = "88cbc4c2-bb93-45e9-a318-57218c7c0171";
	                    if (removedModel.componentId === specialComponentId) {
	                        UI.renderMenus();
	                    }

	                    if (removedModel.isDockable && removedModel.children.length > 0) {
	                        removedModel.children.forEach(returnRemoveModel);
	                    }
	                    UI.removeLog('remove', removedModel);
	                }//end function returnRemoveModel

	                var parent = siteComponentRepository.lookupData({ id: removed.model.parentComponent.id });
	                siteComponentRepository.appendTo(removed.model, parent);
	                UI.actionService.addToActionData(removed.model, true);
	                UI.actionService.runActionForComponent(removed.model, ACTION_ADD_TO_FORM, true);

	                returnRemoveModel(removed.model);
	            }

	            UI.undoManagerAdd({ undo: function () { undo() }, redo: function () { redo() } });
	        }//end if (selectedElementWrapper.length > 0)

	        if (Grouping.isActive()) {
	            Grouping.deleteGroupSelectedComponents();
	        }//end if (Grouping.isActive())

	    }

	    var toggleElementHide = function() {
	        var selectedElementWrapper = $(UI.getConfigurationValue(SELECT_WRAPPER));
	        if (selectedElementWrapper.length > 0) {
	            var selectedElement = $('#' + selectedElementWrapper.data('for'));
	            var id = selectedElement.getId();
	            var selectedComponent = siteComponentRepository.lookupData({ id: id });

	            if (selectedComponent == null ||
					$('#' + selectedComponent.id).attr('contenteditable') == "true" || selectedElement.hasClass('std-form-subcomponent') || !Helpers.allowHide(selectedComponent)) return;

	            var isHide = selectedComponent.getProperty(HIDE_COMPONENT);
	            var oldValue = isHide != null ? isHide.value.toBoolean() : false;

	            UI.undoManagerAddSimpleArr([
                    { component: selectedComponent, property: HIDE_COMPONENT, newvalue: (!oldValue).toString(), oldvalue: oldValue.toString() }
	            ], function () {
	                if (oldValue) {
                        if (UI.getSetting('showHidden')) {
                            UI.actionService.runActionForComponent(selectedComponent, ACTION_REMOVE_FROM_FORM, true);
                            UI.removeEditor();
                        }
	                    UI.actionService.runActionForComponent(selectedComponent, ACTION_ADD_TO_FORM, true);
	                } else {	                    
	                    UI.actionService.runActionForComponent(selectedComponent, ACTION_REMOVE_FROM_FORM, true);
	                    UI.removeEditor();
	                    if (UI.getSetting('showHidden')) {
	                        UI.actionService.runActionForComponent(selectedComponent, ACTION_ADD_TO_FORM, true);
	                    }
	                }
	            }, function () {
	                if (!oldValue) {
	                    if (UI.getSetting('showHidden')) {
	                        UI.actionService.runActionForComponent(selectedComponent, ACTION_REMOVE_FROM_FORM, true);
	                        UI.removeEditor();
	                    }
	                    UI.actionService.runActionForComponent(selectedComponent, ACTION_ADD_TO_FORM, true);
	                } else {
	                    UI.actionService.runActionForComponent(selectedComponent, ACTION_REMOVE_FROM_FORM, true);
	                    UI.removeEditor();
	                    if (UI.getSetting('showHidden')) {
	                        UI.actionService.runActionForComponent(selectedComponent, ACTION_ADD_TO_FORM, true);
	                    }
	                }
	            }, true);

	        }//end if (selectedElementWrapper.length > 0)
	        if (Grouping.isActive()) {
	            Grouping.showOrHideGroupSelectedComponents(true);
	        }//end if (Grouping.isActive())
	    }

	    $(UI.getConfigurationValue(HTML)).bind('keydown', function (e) {

	        UI.disableBrowserHotKeys(e);

	        if (UI.lastKeyDownEvent && UI.lastKeyDownEvent.keyCode === e.keyCode && e.keyCode !== 0) {
	            return;
	        }

	        UI.lastKeyDownEvent = e;

	        switch (e.keyCode) {
	            case 8:
			    case 46: //delete
			        {
			            if (!
                              (
                                $(e.target).hasClass("std-input") || $(e.target).hasClass("std-textarea")
                              )
                              &&
                              $(e.target).closest("#editor")!=null
                            )
			            {
			                if (UI.getDevice().isDesktop()) {
			                    handleElementDelete();
			                } else {
			                    toggleElementHide();
			                }
			                clipBoard.selectedItem(null);
			                clipBoard.itemForClipboard(null);
			            };

			            if (UI.getDevice().isDesktop()) {
			                Resizer.recalculateSizeFooterContainer($('.footer')[0]);
			            }

			            UI.lastKeyDownEvent = null;
			            break;
			        }
			    case 67: //c (copy)
			        {
			            if ((e.ctrlKey || e.metaKey) && !Helpers.hasInputClass(e.target))
			            {
			                ClipboardViewModel.distributionClipboard(COPY);
			            }
			            break;
			        }
			    case 86: //v (paste)
			        {
			            if ((e.ctrlKey || e.metaKey) && !Helpers.hasInputClass(e.target)) {
			                ClipboardViewModel.distributionClipboard(PASTE);
			            }
			            break;
			        }
			    case 68: //d (duplicate)
			        {
			            if ((e.ctrlKey || e.metaKey) && !Helpers.hasInputClass(e.target)) {
			                e.stopPropagation();
			                e.preventDefault();
			                ClipboardViewModel.distributionClipboard(DUPLICATE);
			            }
			            break;
			        }
			    case 90: //z (undo)
			        {
			            if ((e.ctrlKey || e.metaKey) && !Helpers.hasInputClass(e.target)) {
			                UI.undoManagerUndo();
			            }
			            break;
			        }
			    case 89: //y (redo)
			        {
			            if ((e.ctrlKey || e.metaKey) && !Helpers.hasInputClass(e.target)) {
			                UI.undoManagerRedo();
			            }
			            break;
			        }
			    case 0:
			        e.preventDefault();
			        toggleElementHide();
			        clipBoard.selectedItem(null);
			        clipBoard.itemForClipboard(null);

			        break;
				
			}
		});
	}

	//processing keyup handling
	UI.handleKeyUp = function () {
	    $(UI.getConfigurationValue(HTML)).bind('keyup', function (e) {
	        UI.lastKeyDownEvent = null;
	    });

	}

	UI.disableBrowserHotKeys = function (e) {
	    switch (e.keyCode) {
	        case 8: //backspace
	            {
	                if ($(e.target).is('body')) {
	                    e.stopPropagation();
	                    e.preventDefault();
	                }
	                break;
	            }
	        case 46: //delete 
	            {
	                if ($(e.target).is('body')) {
	                    e.stopPropagation();
	                    e.preventDefault();
	                }
	                break;
	            }
	        case 68: //ctrl + d
	            {
	                if (e.ctrlKey || e.metaKey) {
	                    e.stopPropagation();
	                    e.preventDefault();
	                }
	                break;
	            }
	        case 89: //y (redo)
	            {
	                if (e.metaKey) {
	                    e.stopPropagation();
	                    e.preventDefault();
	                }
	                break;
	            }
	        case 90: //y (undo)
	            {
	                if (e.metaKey) {
	                    e.stopPropagation();
	                    e.preventDefault();
	                }
	                break;
	            }
	    }
	}

	//calling editor window for specific component
	UI.callEditor = function (component) {		
	    var isHide = component.getProperty(HIDE_COMPONENT);
	    if (!(UI.getSetting("showHidden") && isHide != null && isHide.value.toBoolean())) {

	        for (name in CKEDITOR.instances) {
	            if (CKEDITOR.instances.hasOwnProperty(name)) {
	                var element = CKEDITOR.document.getById(name);
	                if (element && element.$.isContentEditable && element.$.contentEditable == "true") {
	                    var focusManager = $(element.$).data("focusManagerInstance");
	                    if (focusManager != null) {
	                        focusManager.blur();
	                    }
	                }
	            }
	        }

	        UI.removeEditor();
	        var template;
	        var selectedElement = $(component.getUISelector());

	        if (!Helpers.isSpecificComponent(component)) {

	            template = TemplateFactory.templateFor(component, EDITOR_TEMPLATE).compiledTemplate;
	            PopoverHelper.bind(selectedElement, template, "simple-editor-popover", 'body',
	                {
	                    shown: function() {
	                        UI.actionService.runActionForComponent(component, ACTION_EDITOR_OPEN);
	                    },
	                    hidden: function() {
	                        UI.actionService.runActionForComponent(component, ACTION_EDITOR_CLOSED);
	                    }
	                });	           	           

	            ko.observable($(UI.getConfigurationValue(EDITOR))).extend({ applyBindingsToDescendants: clipBoard });

	        } else {
	            if (Helpers.isGalleryComponent(component) && UI.getDevice().isDesktop()) {
	                template = TemplateFactory.templateFor(component, EDITOR_TEMPLATE).compiledTemplate;
	                $('body').append(template);
	                $('#gallery-editor')
	                    .bind('click',
	                        function(event) {
	                            event.stopPropagation();
	                        });

	                $('#gallery-editor')
	                    .position({
	                        of: $(window),
	                        at: "center center"
	                    });

	                if (parseInt($('#gallery-editor').css('top')) < 0) {
	                    $('#gallery-editor').css('top', '0px');
	                }
	            }
	        }
			component.editor();
			if (!UI.settings.isComponentEditor) UI.getAllTemplatesForComponent(component);

	        ko.observable($(UI.getConfigurationValue(EDITOR))).extend({ applyBindingsToDescendants: clipBoard });

	        $(UI.getConfigurationValue(EDITOR) + " .accordion .ui-accordion-content").hide();
	        $(UI.getConfigurationValue(EDITOR) + " .accordion .ui-accordion-content.ui-accordion-content-active").show();

	        $('.popover').draggable();
	    }
	}
	//calling editor window for specific component
	UI.callPreEditor = function (component) {
	    var isHide = component.getProperty(HIDE_COMPONENT);
	    if (!(UI.getSetting("showHidden") && isHide != null && isHide.value.toBoolean())) {
	        UI.removeEditor();
	        var selectedElement = $(component.getUISelector());

	        component.isNotApproved = true;

	        var template = TemplateFactory.templateFor(component, EDITOR_TEMPLATE).compiledTemplate;
	        PopoverHelper.bind(selectedElement, template, "pre-editor-popover");

	        $(UI.getConfigurationValue(EDITOR))
	            .bind('click',
	                function(event) {
	                    event.stopPropagation();
	                });

	        $('#gallery-editor').hide();
	        $('#editor').hide();
	        $('#gallery-pre-editor').show();
	        $('#pre-editor').show();
	        $('#gallery-pre-editor .close-button')
	            .bind('click',
	                function() {
	                    UI.removeEditor();
	                });
	        var editor = component.editor();
	        $('.popover .arrow').remove();

	        $('.pre-editor-popover')
	            .position({
	                of: $(window),
	                at: "center center"
	            });

	        if (parseInt($('.pre-editor-popover').css('top')) < 0) {
	            $('.pre-editor-popover').css('top', '0px');
	        }
	    }
	}
	//displaying popover for component
	UI.displayComponentPopover = function(e) {
	    e.stopPropagation();
	    UI.removeEditor();
	    PopoverHelper.bind($('.component-menu-button'), $('#component-menu').html(), "add-elements-popover-custom");
	    $('.popover .tab-li').first().addClass('active');
	    $('.popover .tab-pane').removeClass('active');
	    $('.popover .tab-pane').first().addClass('active');
	};
	//displaying popover for page
	UI.displayPagePopover = function(e) {
	    e.stopPropagation();
	    UI.removeEditor();
	    PopoverHelper.bind($('#page-management'), Helpers.loadServiceTemplate("page-management-template"), "page-popover-custom");
	    Pager.renderPager(UI.pager);
	    $(".popover-title").hide();
	    $("body > div.page-popover-custom").css({ "top": "118px", "position": "fixed" });
	    $('.popover').draggable();
	};
	//displaying popover for site
	UI.displaySitePopover = function (e) {
	    e.stopPropagation();
	    UI.removeEditor();
	    PopoverHelper.bind($('#site-settings'), Helpers.loadServiceTemplate("site-settings-template"), "site-popover-custom");
	    $(".popover-title").hide();
	    $("body > div.site-popover-custom").css({ "top": "163px", "position": "fixed" });
	    UI.siteSettings.showSettings();
	    $('.popover').draggable();
	}

	//displaying popover for navigation panel
	UI.displayNavigationPanelPopover = function () {
	    var offset = 10;
	    var verticalRulerWidth = parseInt($('.vRule').width()) + parseInt($('.vRule').css('border-right-width'));
	    PopoverHelper.bind($('#right-navigation-panel'), Helpers.loadServiceTemplate("right-navigation-panel-template"), "right-navigation-panel-popover-custom");
	    $(".popover-title").hide();
	    $(".arrow").hide();
	    var left = parseInt($(window).width()) - 155 - offset - verticalRulerWidth;
	    $("body > div.right-navigation-panel-popover-custom").css({ "min-width": "155px", "width": "155px", "top": "85px", "left": left, "position": "fixed", "margin": "0px", "z-index": 1100 });
	    $('.popover').draggable();

	    var model = UI.getViewModel('right-navigation-panel');
	    ko.applyBindings(model, $(".right-navigation-panel-popover-custom")[0]);
	}
	UI.displayPlacingOnCanvasTopPopover = function (e) {
	    e.stopPropagation();
	    if (!$(e.currentTarget).children().hasClass('disabled')) {
	        PopoverHelper.bind($('#placing-on-canvas-top'), Helpers.loadServiceTemplate("placing-on-canvas-top-template"), "placing-on-canvas-top-custom");
	        $(".popover-title").hide();
	        $(".arrow").hide();
	        $("body > div.placing-on-canvas-top-custom").css({ "min-width": "135px", "width": "135px", "max-height": "42px", "max-height": "42px", "border-radius": "5px" });
	    }
	}
	UI.displayPlacingOnCanvasBottomPopover = function (e) {
	    e.stopPropagation();
	    if (!$(e.currentTarget).children().hasClass('disabled')) {
	        PopoverHelper.bind($('#placing-on-canvas-bottom'), Helpers.loadServiceTemplate("placing-on-canvas-bottom-template"), "placing-on-canvas-bottom-custom");
	        $(".popover-title").hide();
	        $(".arrow").hide();
	        $("body > div.placing-on-canvas-bottom-custom").css({ "min-width": "90px", "width": "90px", "max-height": "42px", "max-height": "42px", "border-radius": "5px"});
	    }
	}

	//displaying popover for designer
	UI.displayDesignPopover = function(e) {
	    e.stopPropagation();

	    UI.removeEditor();
	    PopoverHelper.bind($('#design-management'),
	        TemplateCache.get("design-management-template"), DESIGN_POPOVER_CUSTOM);
	    $(".popover-title").hide();

	    $("body > div.design-popover-custom").css({ "top": "73px", "position": "fixed" });

	    Designer.showBackgroundMenu();
	    $('.popover').draggable();
	    ko.applyBindings({}, $('#collapseThree')[0]);
	    //get view model
	    var model = UI.getViewModel('header-settings', UI.getHeader());
	    ko.applyBindings(model, $('#collapseFour')[0]);
	};

	//displaying popover for device
	UI.displayDevicePopover = function (e) {
	    e.stopPropagation();

	    UI.removeEditor();
	    PopoverHelper.bind($("#device-management"),
	        Helpers.loadServiceTemplate("device-management-template"), 'device-popover-custom');

	    $("body > div.device-popover-custom").css({ "top": "208px", "position": "fixed" });
	    $(".popover-title").hide();

	    //get view model
	    var model = UI.getViewModel('device-management', { device: UI.getDevice(), devices: UI.devices });
	    ko.applyBindings(model, $('#device-management-template')[0]);
	    $('.popover').draggable();
	};

    UI.displayHiddenPopover = function(e) {
        e.stopPropagation();

        UI.removeEditor();
        PopoverHelper.bind($("#hidden-elements"),
	        Helpers.loadServiceTemplate("hidden-elements-template"), 'hidden-popover-custom');

        $("body > div.hidden-popover-custom").css({ "top": "253px", "position": "fixed" });
        $(".popover-title").hide();
        
        //get view model
        var model = UI.getViewModel('hidden-elements', UI.siteComponentRepository.lookupDataByProperty({ 'hide-component': 'true' }));        
        ko.applyBindings(model, $('#hidden-elements-template')[0]);
        $('.popover').draggable();
    }

    UI.displayHistoryPopover = function (e) {
        e.stopPropagation();

        UI.removeEditor();
        PopoverHelper.bind($("#history-management"),
	        Helpers.loadServiceTemplate("history-management-template"), 'history-management-popover-custom');

        $("body > div.history-management-popover-custom").css({ "top": "298px", "position": "fixed" });
        $(".popover-title").hide();
        
        //get view model
        var model = UI.getViewModel('history-management');
        ko.applyBindings(model, $('#history-management-template')[0]);
        $('.popover').draggable();
    }

	UI.displayAdditionaloptions = function (e, color) {
	    e.stopPropagation();

	    UI.removeEditor();
	    PopoverHelper.bind($('.additionals-components-image'), TemplateCache.get("additional-options-template"), ADDITIONAL_COMPONENTS);

	    $(".popover-title").hide();

	    var components = $("." + ADDITIONAL_COMPONENTS);

	    components.find(".arrow").addClass("additional-components-arrow");

	    components.css("left", $(components).position().left + 45);

	    components.css("color", color);

	    $(".additional-components-editor-content").css("background-color", $("." + MAIN_MENU).css("background-color"));

	    $(".menu-icons").tooltip({
	        placement: 'bottom'
	    });
	};

	UI.displayStorePopover = function (e) {
	    if (e) e.stopPropagation();

	    var store = UI.siteComponentRepository.lookupData({ displayName: STORE });
	    UI.removeEditor();
	    var template = TemplateFactory.templateFor(store, EDITOR_TEMPLATE).compiledTemplate;
	    var selectedElement = $('#store');
	    PopoverHelper.bind(selectedElement, template, "simple-editor-popover");
        store.editor();

	    //ko.observable($(UI.getConfigurationValue(EDITOR))).extend({ applyBindingsToDescendants: clipBoard });
        $('#manage-store-btn').on('click', function () {
            var storeRendered = UI.componentService.addModalContentToForm(store, '#manage-store-products');
        });

	    $(".accordion")
            .accordion({
                heightStyle: "content"
            });

	    $('.popover').draggable();

	}

	//blankTemplateId - templateId created before creating website
UI.saveComponent = function (name) {
	UI.generateThumbnail(actionType, function (dataUrl) {
		var thumbnail = dataUrl.split(';base64,')[1];
		var json = JSON.parse(UI.templateToJson());
		UI.ajaxJsonRequest('/Editor/SaveComponent', {
			data: JSON.stringify(json),
			actionType: actionType,
			parentComponentId: '',
			thumbnail: thumbnail,
			displayName: name
		}, UI.updateControlFromComponent)
	})
	
}

UI.saveSection = function (name, categoryId) {
	UI.generateThumbnail(actionType, function (dataUrl) {
		var thumbnail = dataUrl.split(';base64,')[1];
		var json = JSON.parse(UI.templateToJson());
		UI.ajaxJsonRequest('/Editor/SaveSection', {
			data: JSON.stringify(json),
			actionType: actionType,
			categoryId: categoryId,
			thumbnail: thumbnail,
			displayName: name
		}, UI.updateControlFromComponent)
	})

}
UI.selectSection = function () {
	Application.getModal('/Editor/SelectSectionTemplate');
}

UI.addSectionToForm = function (sectionId) {
	var currentPageId = UI.pager.getCurrentPageId();
	var currentPage = UI.siteComponentRepository.lookupData({ id: currentPageId });
	var pageElement = $(currentPage.getUISelector());
	var pageHeight = parseInt(currentPage.getProperty(HEIGHT).value);
	var section = UI.basicComponentRepository.lookupData({ id: sectionId });
	var sectionHeight = parseInt(section.getProperty(HEIGHT).value);
	var sectionData = {
		topPosition: null,
		height: null,
		bottomLine: null
	}

	var undoState = {
		pageHeight: pageHeight + 'px',
		controls: [],
		sectionChildren: []
	}

	//change page height
	var newPageHeight = pageHeight + sectionHeight + 'px';
	currentPage.setProperty(HEIGHT, newPageHeight);
	pageElement.css('height', newPageHeight);

	//move components down
	currentPage.children.forEach(component => {
		var componentTopValue = parseInt(component.getProperty(TOP).value);
		var componentNewTopPosition = componentTopValue + sectionHeight + 'px';
		component.setProperty(TOP, componentNewTopPosition);
		var element = $(component.getUISelector());
		element.css('top', componentNewTopPosition);	
		undoState.controls.push({
			uiSelector: element,
			topValue: componentTopValue + 'px'
		})
	})

	Grouping.dropItems();

	//add sections components
	section.children.forEach(child => {
		if (child.isActive) {
			var childTopValue = parseInt(child.getProperty(TOP).value);
			var childHeightValue = parseInt(child.getProperty(HEIGHT).value);
			var childLeftValue = child.getProperty(LEFT).value;
			var sectionComponent = new Component().createNew(child, true, currentPage);
			UI.componentService.addComponentToForm(sectionComponent, true);
			UI.siteComponentRepository.appendTo(sectionComponent, currentPage);
			var element = $(sectionComponent.getUISelector());
			element.css('top', childTopValue);
			element.css('left', childLeftValue);
			sectionComponent.viewer();
			if (sectionData.topPosition == null) {
				sectionData.topPosition = childTopValue;
				sectionData.bottomLine = sectionData.topPosition + childHeightValue;
			} else {
				if (sectionData.topPosition > childTopValue) sectionData.topPosition = childTopValue;
				var childBottomLine = childTopValue + childHeightValue;
				if (childBottomLine > sectionData.bottomLine) sectionData.bottomLine = childBottomLine;
				sectionData.height = sectionData.bottomLine - sectionData.topPosition;
			}

			//select all sectionChildren (with grouping)
			grouping.section = sectionData;
			Grouping.addItem(document.getElementById(sectionComponent.id))

			undoState.sectionChildren.push(element.getId());
		}
	});

	var undo = function () {
		//delete section
		var idSectionSelected = grouping.selectedItems().length;
		if (idSectionSelected) {
			clipBoard.deleteComponent();
		} else {
			undoState.sectionChildren.forEach(id => {
				UI.siteComponentRepository.remove({ id: id });
			})	
		}	

		//move site components to their previous position
		undoState.controls.forEach(control => {
			var component = UI.siteComponentRepository.lookupData({ id: control.uiSelector.getId() });
			component.setProperty(TOP, control.topValue);
			control.uiSelector.css('top', control.topValue);
		})

		//set previous page height
		currentPage.setProperty(HEIGHT, undoState.pageHeight);
		pageElement.css('height', undoState.pageHeight);
		var header = UI.siteComponentRepository.lookupData({ displayName: 'header' });
		var footer = UI.siteComponentRepository.lookupData({ displayName: 'footer' });
		var headerHeight = header.getProperty(HEIGHT).value;
		var footerTop = parseFloat(headerHeight) + parseFloat(undoState.pageHeight) + 'px';
		footer.setProperty(TOP, undoState.pageHeight);
		$(footer.getUISelector()).css('top', footerTop);
	}
	var redo = function () {
		UI.addSectionToForm(sectionId)
	}

	UI.undoManagerAdd({
		undo: function () {
			undo();
		},
		redo: function () {
			redo();
		}
	});
}

UI.updateControlFromComponent = function (data) {
	var component = data.component;
	var control = UI.siteComponentRepository.lookupData({ id: data.control.id || data.control.controlId });
	control.componentId = component.id || component.componentId;
	control.name = component.id || component.componentId;
}
	UI.saveorpublish = function (actionType, editorAction, websiteurl, name, categories, callback, blankTemplateId, parentTemplateId, websiteurls) {
	    if ($("#save-site-data").hasClass('disabled')) {
	        return false;
	    }
	    
	    if (actionType != 1 && actionType != 4) {
	        UI.DisableSaveAndPublishButtons(true);
	    }

	    UI.exportLogs();

	    UI.setTemplateProperty("webSiteUrls", websiteurls);
	    UI.setTemplateProperty("webSiteUrlsExtra", websiteurls);

	    UI.setTemplateProperty("name", name);
	    UI.setTemplateProperty("categories", categories);

	    var json = JSON.parse(UI.templateToJson());

	    UI.jsonCompareService.compare(json.controls[0]);

	    IndexedDBService.setLocalTemplateParam({
	        "last-saved-model": json,
	        "auto-saved-model": null,
	        "auto-saved-time": null,
	        "save-result": false,
	        "last-saved-version": null
	    });

	    //console.log(UI.getBody().getProperty(LOGS_PROPERTY).value);
	    if (actionType == 1 || actionType == 4) {
	        if (UI.getSetting('showFirstSavePopup') && actionType !== ACTION_TYPE_CREATE_WEB_SITE && actionType !== ACTION_TYPE_CREATE_TEMPLATE && !(editorAction === 2 && $('#site-form').length)) {
	            UI.settings.showFirstSavePopup = false;
	            if (editorAction === 1) {
	                Application.showOkDialog('Hint', 'Saving is in a background process. Publishing will need to wait a while depending upon your website size. To Re-Save please wait for the "Success" indicator at the top right of your screen.');
	            } else if (editorAction === 2) {
	                Application.showOkDialog('Hint', 'Publishing is in a background process.  To Re-Publish please wait a for the "Success" indicator at the top right of your screen, time out depends upon your site size.');
	            }
	        }
	        UI.ajaxJsonRequest('/Editor/SaveOrPublish', { data: JSON.stringify(json), actionType: actionType, editorAction: editorAction, blankTemplateId: blankTemplateId, parentTemplateId: parentTemplateId, thumbnail: null }, null);
	    }
	    else{
	      UI.generateThumbnail(actionType,
	        function(base64) {
	            var thumbnail = base64 != null ? base64.split(',')[1] : null;

	            if (UI.getSetting('showFirstSavePopup') && actionType !== ACTION_TYPE_CREATE_WEB_SITE && actionType !== ACTION_TYPE_CREATE_TEMPLATE && !(editorAction === 2 && $('#site-form').length)) {
	                UI.settings.showFirstSavePopup = false;
	                if (editorAction === 1) {
	                    Application.showOkDialog('Hint', 'Saving is in a background process. Publishing will need to wait a while depending upon your website size. To Re-Save please wait for the "Success" indicator at the top right of your screen.');
	                } else if (editorAction === 2) {
	                    Application.showOkDialog('Hint', 'Publishing is in a background process.  To Re-Publish please wait a for the "Success" indicator at the top right of your screen, time out depends upon your site size.');
	                }
	            }
				var ajaxJsonRequestUrl = '/Editor/SaveOrPublish';
				if (actionType === ACTION_TYPE_CREATE_COMPONENT_TEMPLATE || actionType === ACTION_TYPE_EDIT_COMPONENT_TEMPLATE) ajaxJsonRequestUrl = '/Editor/SaveComponentTemplate';
				UI.ajaxJsonRequest(ajaxJsonRequestUrl,
	                {
	                    data: JSON.stringify(json),
	                    actionType: actionType,
	                    editorAction: editorAction,
	                    blankTemplateId: blankTemplateId,
	                    parentTemplateId: parentTemplateId,
	                    thumbnail: thumbnail
	                },
	                function(wrapper) {
	                    UI.DisableSaveAndPublishButtons(false);
	                    json.templateVersionId = wrapper.templateVersion.templateVersionId;
	                    IndexedDBService.setLocalTemplateParam({
	                        "save-result": true,
	                        "last-saved-version": wrapper.templateVersion.templateVersionId,
	                        "last-saved-model": json
	                    });
	                    window.lastEditorTemplateVersionId = wrapper.templateVersion.templateVersionId;
	                    UI.resetLogs();

	                Helpers.showToastMessage('success', 'Success!', 'Your site has been ' + ((editorAction === 1) ? 'Saved' : 'Published'));
	                },
	                function(error) {
	                    UI.DisableSaveAndPublishButtons(false);
	                    console.log(error);
	                    Helpers.showToastMessage('error', 'Unable to Save', 'An error occured while ' + ((editorAction === 1) ? 'saving' : 'publishing') + '. Please try again later.');

	                    });
	            });
	    }
	}

    UI.DisableSaveAndPublishButtons= function(disable) {
        if (disable) {
            Application.addLocker();
            Application.disableNewLockers = true;
            $("#save-site-data").addClass("disabled");
            $("#publish-site-data").addClass("disabled");
        } else {
            Application.disableNewLockers = false;
            Application.removeLocker();
            $("#save-site-data").removeClass("disabled");
            $("#publish-site-data").removeClass("disabled");
        }
    }

UI.generateThumbnail = function (actionType, callback) {
	var finish = function (data) {
		Application.removeLocker();
		callback(data);
	}
	var encoderOptions = 0.1;
	if (actionType === 1 || actionType === 2) {

			var element = document.querySelector("body");
			var options = {
				onclone: function () {
					UI.setDevice(device);
					UI.pager.goToPage(pageId);
				},
				useCORS: true,
				logging: true,
				y: 52,
				windowWidth: 1920,
				windowHeight: 1080,
				width: 1920,
				height: 1080
			};
		} else if (actionType == 9 || actionType == 10) {
			var element = document.querySelector(".std-component");
			var options = {
				useCORS: true,
				logging: true,
			};
			encoderOptions = 0.8;
	} else if (actionType == 11 || actionType == 12) {
		var element = document.querySelector(".page");
		var options = {
			useCORS: true,
			logging: true,
		};
	}

		var isActionType = [1, 2, 9, 10, 11, 12].includes(actionType);

		if (isActionType) {
	        //prepare interface 
			if (!UI.settings.isComponentEditor && !UI.settings.isSectionEditor) {
				var device = UI.getDevice();
				var pageId = UI.pager.getCurrentPageId();
				UI.setDevice(UI.getDevice(UI.getDefaultDeviceId()));
				UI.pager.goToPage(UI.pager.getHomePageId());
			}

	        setTimeout(function () {
	                try
	                {
	                    Application.removeLocker();
	                    Application.addLocker();
						html2canvas(element, options)
							.then(function (canvas) {
	                            try {
									var thumb = canvas.toDataURL('image/jpeg', encoderOptions);
	                                finish(thumb);
	                            } catch (e) {
	                                console.log(e);
	                                finish(null);
	                            }
	                        },
	                        function(e) {
	                            console.log(e);
	                            finish(null);
	                        }).catch(function(e) {
	                            console.log(e);
	                            finish(null);
	                        });
	                }
	                catch(e){
	                    Application.removeLocker();
	                    finish(null);
	                }
	            },
	            1000);
	    } else {
	        finish(null);
	    }
}

	UI.ajaxJsonRequest = function (url, data, callback, errorCallback) {
		callback = callback || function () { };

		var oldActionType = data.actionType;

		$.ajax({
			type: "POST",
			url: url,
			data: data,
			dataType: "json",
			beforeSend: function (xhr) {
			    xhr.setRequestHeader("Content-Encoding", "gzip");
			},
			success: function (response) {
				var wrapper = JSON.parse(response);				
                
			    if (wrapper.errorCode == 0 && wrapper.templateVersion != null) {
			        var x = wrapper.templateVersion;

			        if (oldActionType == ACTION_TYPE_CREATE_WEB_SITE)
			        {
			            Helpers.changeUrl("Editor", "/Editor?templateId=" + x.templateId + "&" + "actionType=EditWebSite");
			        }
			        if (oldActionType == ACTION_TYPE_CREATE_TEMPLATE) {
			            Helpers.changeUrl("Editor", "/Editor?templateId=" + x.templateId + "&" + "actionType=EditTemplate");
					}
                    			        
			        UI.setTemplateProperty("templateVersionId", x.templateVersionId);
			        UI.setTemplateProperty("templateId", x.templateId);
			        UI.setTemplateProperty("templateTypeId", x.templateTypeId);
			        UI.setTemplateProperty("actionType", x.actionType);
			        UI.setTemplateProperty("categories", x.categories);
			        var oldData = JSON.parse(data.data);
			        if (actionType) {
			            actionType = x.actionType;
			        }
			        UI.jsonCompareService.reInit(oldData.controls);

			        if (typeof (callback) == "function") {
			            callback(wrapper);
			        }
				} else if (wrapper.errorCode == 0 && wrapper.component != null) {
					if (oldActionType == ACTION_TYPE_CREATE_COMPONENT_TEMPLATE) {
						appSettings.componentId = wrapper.component.componentId;
						Helpers.changeUrl("Editor", "/Editor/EditComponent?componentId=" + appSettings.componentId + "&" + "actionType=EditComponentTemplate");
						appSettings.actionType = ACTION_TYPE_EDIT_COMPONENT_TEMPLATE;
						callback(wrapper);
						Application.removeModal();
					}

					if (oldActionType == ACTION_TYPE_CREATE_SECTION_TEMPLATE) {
						appSettings.componentId = wrapper.component.componentId;
						Helpers.changeUrl("Editor", "/Editor/EditSection?componentId=" + appSettings.componentId + "&" + "actionType=EditSectionTemplate");
						appSettings.actionType = ACTION_TYPE_EDIT_SECTION_TEMPLATE;
						callback(wrapper);
						Application.removeModal();
					}
				} else {
			        Helpers.showModalDialog("Error", wrapper.errorMessage, TEMPLATE_MASTER_URL);
			    }
			},
			error: function (error) {
			    if (typeof (errorCallback) == "function") {
			        errorCallback(error);
			    } else if (typeof (callback) == "function") {
			        callback();
			    }
			}
		});
	}

	UI.initializeViewerPaletteController = function (viewerPaletteController) {
		if (UI.settings.hasOwnProperty("ispreview")) {
			if (UI.settings.ispreview) {
				viewerPaletteController();
			}
		}
	};

//  	 eventsystem.subscribe('/component/create/', UI.componentCreatedHandler); 	
	UI.componentCreatedHandler = function (component) {
	    if (null == component) return;

	    TransformFactory.setNewPosition(component);

			var el$ = $(component.getUISelector());
			el$.show();

			$('.popover').fadeOut('fast');

			var element = el$.detach();
			$('.wrapper .body').append(element);

	        var zIndex = Helpers.getMaxZIndex(component);
	        el$.css('z-index', zIndex);
	        component.setProperty(Z_INDEX, zIndex);

			el$.position({
				of: $(window),
				at: "center center"
			}).addClass('fadeInUp').addClass('animated');

			el$.one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend',
				function (e) {
					$('.popover').fadeIn('fast');
			
					el$.removeClass('fadeInUp').removeClass('animated');

					var top = el$[0].offsetTop;
					var height = el$[0].offsetHeight;

					var mainTop = $('.main')[0].offsetTop;
					var mainHeight = $('.main')[0].offsetHeight;

					var element = el$.detach();
					$('#' + UI.pager.getCurrentPageId()).append(element);

					if (top + (height / 2) < mainTop) {
					    Dock.unconditionalElementDocking("#" + component.id, $('.header'), true);
					    if (top + height < mainTop) {
					        var value = top;
					        if (value < 0) {
					            value = 0;
					        }
					        el$.css('top', value);
					    }

					} else 	if (top + height / 2 > mainTop + mainHeight) {
					    Dock.unconditionalElementDocking("#" + component.id, $('.footer'), false);

					    var value = top - (mainTop + mainHeight);
					    if (value > 0) {
					        el$.css('top', value);
					    }

					} else {
                        var value = top - mainTop;
                    if (value < 0) {
                            value = 0;
                        }
                        if (value + height > mainHeight) {
                            value -= (value + height - mainHeight);
                        }
                        el$.css('top', value);
					}
					Resizer.recalculateHeaderFooterAndPageSize($('#' + UI.pager.getCurrentPageId())[0]);					
					var newTop = el$.css('top');
					var left = el$.css('left');
					component.setProperty(LEFT, left);
					component.setProperty(TOP, newTop);
				    UI.callEditor(component);
				    el$.highlightSelectedElement(component, true);
				});
    }

	UI.ComponentMethods = {
	    "div": {
	        "editor": {},
	        "viewer": {},
	        "contextviewer": {},
	        "contexteditor": {}
	    }

	}

UI.getAllTemplatesForComponent = function (component) {		
	$(".component-templates-list").children().remove();
	var html = '';
	var templateableComponent = UI.basicComponentRepository.getAll().filter(x => x.name == (component.componentName || component.displayName) && x.componentType == componentType.templateable);
	var parentId = templateableComponent[0].id;
	UI.templatesForComponent = UI.templateData.components.where({ parentId: parentId });			

	if (UI.templatesForComponent.length) {
		UI.templatesForComponent.forEach(template => {
			html += '<div class="component-templates-item" onclick="app.setPropertiesForComponentFromTemplate(' +
				"'" +
				component.id +
				"', '" +
				template.componentId +
				"'" +
				')">' +
				'<div class="component-templates-thumb">' +
				'<img src="' + template.thumbnail + '" />' +
				'</div>' +
				'</div>';			
		});
		$(".component-templates-list").append(html);
	} else {
		html = '<div>No templates</div>';
		$(".component-templates-list").append(html);
	}
}
UI.getComponents = function (templateId) {
	    if (templateId != "") {
	        $.ajax({
	            url: "/Editor/GetNewComponents",
	            data: { templateId: templateId },
	            success: function (response) {
	                if (typeof response != "undefined" && response != null) {
	                    UI.removeEditor();
						$(".component-list").children().remove();
						if (!UI.settings.isSectionEditor) {
							//var addSectionButton = '<div class="item" onclick="UI.selectSection()"><div class="holder"><i class="fa fa-plus"></i></div><div class="text">Section</div></div>';
							//$(".component-list").append(addSectionButton);
						}
	                    if (response.components.length > 0) {
	                        UI.newComponents = response.components;
	                        response.components.forEach(function (item) {
	                            var code;
	                            if (item.Name !== STORE) {
	                                code = '<div class="item" onclick="app.addNewComponent(' +
	                                    "'" +
	                                    item.Name +
	                                    "'" +
	                                    ')">' +
	                                    '<div class="holder">' +
	                                    '<i class="' +
	                                    item.Description +
	                                    '"></i>' +
	                                    '</div>' +
	                                    '<div class="text">' +
	                                    item.DisplayName +
	                                    '</div>' +
	                                    '</div>';
	                            } else {
	                                code = '<div class="item" onclick="UI.addStore()">' +
	                                    '<div class="holder">' +
	                                    '<i class="' +
	                                    item.Description +
	                                    '"></i>' +
	                                    '</div>' +
	                                    '<div class="text">' +
	                                    item.DisplayName +
	                                    '</div>' +
	                                    '</div>';
	                            }
	                            $(".component-list").append(code);
	                        });
	                    } else {
	                         $(".component-list").append('<p class="m5005">Components are not available now!</p>');
	                    }
	                }
	            }
	        });
	    }
	}

	UI.getBody = function () {
	    return UI.siteComponentRepository.lookupData({ displayName: "body" });
	}

	UI.getMain = function () {
	    return UI.siteComponentRepository.lookupData({ displayName: MAIN_COMPONENT });
	}

    UI.getHeader = function() {
        return UI.siteComponentRepository.lookupData({ displayName: HEADER });
    }

    UI.getFooter = function () {
        return UI.siteComponentRepository.lookupData({ displayName: FOOTER });
    }
	
	UI.configureGoogleAnalytics = function() {
	    var val = $("#textarea_googleanalytics_script").val();
	    UI.getBody().setProperty(GOOGLE_ANALYTICS_SCRIPT, val, true);
	    UI.getBody().setProperty(GOOGLE_ANALYTICS, "1", true);
	    H.actionOnOffButtonValueSet($(".btn-toggle-googleanalytics"), "1");
	    //$("#textarea_googleanalytics_script").val("");
	    $(".edit-googleanalytics-container").hide();
	}

	UI.addStore = function () { 
	    if (UI.siteComponentRepository.checkExistingRule(STORE)) {
	        //getting component
	        var basicComponent = UI.basicComponentRepository.lookupData({ name: STORE });

	        if (basicComponent) {
	            //new control creation
	            var component = new Component().createNew(basicComponent, true);

	            UI.siteComponentRepository.appendTo(component, UI.getBody());
	            UI.store = new Store(component);

	            //check page address for old logic
	            var reservedStoreePages = ['product', 'cart', 'thank-you'];
	            _.forEach(UI.pager.pages,
	                function(page) {
                        if (reservedStoreePages.indexOf(page.name) !== -1) {
                            page.setName(page.name + page.id);                           
                        }
	                });

	            //create product page
	            var storePage = new Component().createNew(UI.basicComponentRepository.lookupData({ name: PAGE_COMPONENT }), true);
	            storePage.setProperty(TITLE, 'Product Page', true);
	            storePage.setProperty(ISSERVICE, 'true', true);
	            storePage.setProperty(NAME, 'product', true);
	            UI.siteComponentRepository.appendTo(storePage, UI.getMain());
	            var pagerStorePage = new Page({
	                id: storePage.id,
	                title: 'Product Page',
	                name: 'product'
	            });
	            UI.pager.pages.push(pagerStorePage);

	            //create product element
	            var product = new Component().createNew(UI.basicComponentRepository.lookupData({ name: STORE_PRODUCT }), true);
	            UI.siteComponentRepository.appendTo(product, storePage);
	            storePage.setProperty(HEIGHT, product.getProperty(HEIGHT).value);
	            TransformFactory.setNewPosition(product);

	            var productItems = [STORE_PRODUCT_TITLE, STORE_PRODUCT_DESCRIPTION, STORE_PRODUCT_IMAGES, STORE_PRODUCT_OPTIONS, STORE_PRODUCT_PRICE, STORE_PRODUCT_ADD_TO_CART, STORE_PRODUCT_QUANTITY, STORE_PRODUCT_SOCIAL, STORE_PRODUCT_SKU];
	            _.forEach(productItems,
	                function (name) {
	                    var item = new Component().createNew(UI.basicComponentRepository.lookupData({ name: name }), true);
	                    UI.siteComponentRepository.appendTo(item, product);
	                    TransformFactory.setNewPosition(item);
	                });

	            UI.actionService.runActionForComponent(storePage, ACTION_ADD_TO_FORM, true);
	            $(storePage.getUISelector()).hide();

	            //create cart page
	            var cartPage = new Component().createNew(UI.basicComponentRepository.lookupData({ name: PAGE_COMPONENT }), true);
	            cartPage.setProperty(TITLE, 'Cart Page', true);
	            cartPage.setProperty(ISSERVICE, 'true', true);
	            cartPage.setProperty(NAME, 'cart', true);
	            UI.siteComponentRepository.appendTo(cartPage, UI.getMain());
	            var pagerCartPage = new Page({
	                id: cartPage.id,
	                title: 'Cart Page',
	                name: 'cart'
	            });
	            UI.pager.pages.push(pagerCartPage);

	            //create cart element
	            var cart = new Component().createNew(UI.basicComponentRepository.lookupData({ name: STORE_CART }), true);
	            UI.siteComponentRepository.appendTo(cart, cartPage);
	            cartPage.setProperty(HEIGHT, cart.getProperty(HEIGHT).value);
	            TransformFactory.setNewPosition(cart);
	            var cartCheckout = new Component().createNew(UI.basicComponentRepository.lookupData({ name: STORE_CART_CHECKOUT }), true);
	            UI.siteComponentRepository.appendTo(cartCheckout, cart);   

	            UI.actionService.runActionForComponent(cartPage, ACTION_ADD_TO_FORM, true);
	            $(cartPage.getUISelector()).hide();

	            //create thank-you page
	            var thankYouPage = new Component().createNew(UI.basicComponentRepository.lookupData({ name: PAGE_COMPONENT }), true);
	            thankYouPage.setProperty(TITLE, 'Thank You Page', true);
	            thankYouPage.setProperty(ISSERVICE, 'true', true);
	            thankYouPage.setProperty(NAME, 'thank-you', true);
	            UI.siteComponentRepository.appendTo(thankYouPage, UI.getMain());
	            var pagerThankYouPage = new Page({
	                id: thankYouPage.id,
	                title: 'Thank You Page',
	                name: 'thank-you'
	            });
	            UI.pager.pages.push(pagerThankYouPage);

	            // create thank-you component
	            var thankYouComponent = new Component().createNew(UI.basicComponentRepository.lookupData({ name: STORE_THANK_YOU }), true);
	            UI.siteComponentRepository.appendTo(thankYouComponent, thankYouPage);
	            thankYouPage.setProperty(HEIGHT, thankYouComponent.getProperty(HEIGHT).value);
	            TransformFactory.setNewPosition(thankYouComponent);

                UI.actionService.runActionForComponent(thankYouPage, ACTION_ADD_TO_FORM, true);
	            $(thankYouPage.getUISelector()).hide();

	            Pager.renderTopMenuSelect();

	            var redo = function () {
	                $('#store').show();
	                UI.siteComponentRepository.appendTo(component, UI.getBody());
	                UI.store = new Store(component);
	                UI.siteComponentRepository.appendTo(storePage, UI.getMain());
	                UI.siteComponentRepository.appendTo(cartPage, UI.getMain());
	                UI.siteComponentRepository.appendTo(thankYouPage, UI.getMain());
	                UI.pager.pages.push(pagerStorePage);
	                UI.pager.pages.push(pagerCartPage);
	                UI.pager.pages.push(pagerThankYouPage);

	                UI.actionService.addToActionData(component, true);
	                UI.actionService.addToActionData(storePage, true);
	                UI.actionService.addToActionData(cartPage, true);
	                UI.actionService.addToActionData(thankYouPage, true);
	                
	                UI.actionService.runActionForComponent(storePage, ACTION_ADD_TO_FORM, true);
	                $(storePage.getUISelector()).hide();
	                UI.actionService.runActionForComponent(cartPage, ACTION_ADD_TO_FORM, true);
	                $(cartPage.getUISelector()).hide();
	                UI.actionService.runActionForComponent(thankYouPage, ACTION_ADD_TO_FORM, true);
	                $(thankYouPage.getUISelector()).hide();
	                Pager.renderTopMenuSelect();
	            }
	            var undo = function () {
	                $('#store').hide();
	                UI.siteComponentRepository.remove({ id: component.id });
	                UI.store = null;
	                if (storePage.id === UI.pager.getCurrentPageId()) {
	                    UI.pager.goToHomePage();
	                }
	                if (cartPage.id === UI.pager.getCurrentPageId()) {
	                    UI.pager.goToHomePage();
	                }
	                if (thankYouPage.id === UI.pager.getCurrentPageId()) {
	                    UI.pager.goToHomePage();
	                }
	                _.remove(UI.pager.pages, function (item) { return pagerStorePage.id === item.id; });
	                _.remove(UI.pager.pages, function (item) { return pagerCartPage.id === item.id; });
	                _.remove(UI.pager.pages, function (item) { return pagerThankYouPage.id === item.id; });
	                UI.siteComponentRepository.remove({ id: storePage.id });
	                UI.siteComponentRepository.remove({ id: cartPage.id });
	                UI.siteComponentRepository.remove({ id: thankYouPage.id });
	                Pager.renderTopMenuSelect();
	            }
	            
	            UI.undoManagerAdd({
                    undo: function () {
                        undo();
                    },
                    redo: function () {
                        redo();
                    }
                });

	            $('#store').show();
	            UI.displayStorePopover();
	        }
	    }
	}

	UI.showRemoveStoreConfirmationModal = function (isReset) {
	    isReset = defined(isReset) ? isReset : false;
	    if (isReset) {	        
	        Application.showRemoveConfirmationDialog('Are you sure you want to reset your store data?', 'Reset Store Data', [
                {
                    css: 'danger',
                    text: 'Reset',
                    callback: function () {
                        if (UI.store) {
                            UI.store.reset(function() { Application.removeModal(); });
                        }
                    }
                }
            ], 'This action will completely delete your store data without ability to restore it: products, categories, orders etc.', '*This action can\'t be Undo!');
        } else {
            Application.showRemoveConfirmationDialog('Are you sure you want to remove your store?', 'Remove Store', [
                {
                    css: 'simple',
                    text: 'Remove',
                    callback: function () {
                        UI.removeStore();
                        Application.removeModal();
                    }
                }
            ], 'This action will remove your store from the website and save all related data for future usage: products, categories, orders etc.');
        }
	}

	UI.removeStore = function () {	    
        if (UI.store) {
            //remove store
            var component = UI.store.component;
            UI.store = null;
            UI.siteComponentRepository.remove({ id: component.id });

            //remove service pages
            var pagerStorePage = _.remove(UI.pager.pages, function (item) { return 'product' === item.name; })[0];
            var pagerCartPage = _.remove(UI.pager.pages, function (item) { return 'cart' === item.name; })[0];
            var pagerThankYouPage = _.remove(UI.pager.pages, function (item) { return 'thank-you' === item.name; })[0];
            if (pagerStorePage.id === UI.pager.getCurrentPageId()) {
                UI.pager.goToHomePage();
            }
            if (pagerCartPage.id === UI.pager.getCurrentPageId()) {
                UI.pager.goToHomePage();
            }
            if (pagerThankYouPage.id === UI.pager.getCurrentPageId()) {
                UI.pager.goToHomePage();
            }
            var storePage = UI.siteComponentRepository.remove({ id: pagerStorePage.id });
            var cartPage = UI.siteComponentRepository.remove({ id: pagerCartPage.id });
            var thankYouPage = UI.siteComponentRepository.remove({ id: pagerThankYouPage.id });

            //remove all store-gallery
            var galleries = UI.siteComponentRepository.lookupDataSet({ displayName: STORE_GALLERY });
            _.forEach(galleries,
                function (item) {
                    UI.siteComponentRepository.remove({ id: item.id });
                });

            //remove all store-cart-link
            var cartLinks = UI.siteComponentRepository.lookupDataSet({ displayName: STORE_CART_LINK });
            _.forEach(cartLinks,
                function(item) {
                    UI.siteComponentRepository.remove({ id: item.id });
                });

            //remove all store-categories
            var categories = UI.siteComponentRepository.lookupDataSet({ displayName: STORE_CATEGORIES });
            _.forEach(categories,
                function (item) {
                    UI.siteComponentRepository.remove({ id: item.id });
                });

            $('#store').hide();
            UI.removeEditor();
            Pager.renderTopMenuSelect();


            var redo = function () {
                $('#store').hide();
                UI.store = null;
                UI.siteComponentRepository.remove({ id: component.id });
                if (storePage.id === UI.pager.getCurrentPageId()) {
                    UI.pager.goToHomePage();
                }
                if (cartPage.id === UI.pager.getCurrentPageId()) {
                    UI.pager.goToHomePage();
                }
                if (thankYouPage.id === UI.pager.getCurrentPageId()) {
                    UI.pager.goToHomePage();
                }
                _.remove(UI.pager.pages, function (item) { return pagerStorePage.id === item.id; });
                _.remove(UI.pager.pages, function (item) { return pagerCartPage.id === item.id; });
                _.remove(UI.pager.pages, function (item) { return pagerThankYouPage.id === item.id; });
                UI.siteComponentRepository.remove({ id: storePage.id });
                UI.siteComponentRepository.remove({ id: cartPage.id });
                UI.siteComponentRepository.remove({ id: thankYouPage.id });
                _.forEach(cartLinks,
                    function (item) {
                        UI.siteComponentRepository.remove({ id: item.id });
                    });
                _.forEach(galleries,
                    function (item) {
                        UI.siteComponentRepository.remove({ id: item.id });
                    });
                _.forEach(categories,
                    function (item) {
                        UI.siteComponentRepository.remove({ id: item.id });
                    });
                Pager.renderTopMenuSelect();
            }
            var undo = function () {
                $('#store').show();
                UI.siteComponentRepository.appendTo(component, UI.getBody());
                UI.store = new Store(component);
                UI.siteComponentRepository.appendTo(storePage, UI.getMain());
                UI.siteComponentRepository.appendTo(cartPage, UI.getMain());
                UI.siteComponentRepository.appendTo(thankYouPage, UI.getMain());
                UI.pager.pages.push(pagerStorePage);
                UI.pager.pages.push(pagerCartPage);
                UI.pager.pages.push(pagerThankYouPage);

                UI.actionService.addToActionData(component, true);
                UI.actionService.addToActionData(storePage, true);
                UI.actionService.addToActionData(cartPage, true);
                UI.actionService.addToActionData(thankYouPage, true);

                _.forEach(cartLinks,
                    function (item) {
                        var parent = UI.siteComponentRepository.lookupData({ id: item.parentComponent.id });
                        UI.siteComponentRepository.appendTo(item, parent);
                        UI.actionService.addToActionData(item, true);
                        UI.actionService.runActionForComponent(item, ACTION_ADD_TO_FORM, true);
                    });
                _.forEach(galleries,
                    function (item) {
                        var parent = UI.siteComponentRepository.lookupData({ id: item.parentComponent.id });
                        UI.siteComponentRepository.appendTo(item, parent);
                        UI.actionService.addToActionData(item, true);
                        UI.actionService.runActionForComponent(item, ACTION_ADD_TO_FORM, true);
                    });

                _.forEach(categories,
                    function (item) {
                        var parent = UI.siteComponentRepository.lookupData({ id: item.parentComponent.id });
                        UI.siteComponentRepository.appendTo(item, parent);
                        UI.actionService.addToActionData(item, true);
                        UI.actionService.runActionForComponent(item, ACTION_ADD_TO_FORM, true);
                    });

                UI.actionService.runActionForComponent(storePage, ACTION_ADD_TO_FORM, true);
                $(storePage.getUISelector()).hide();
                UI.actionService.runActionForComponent(cartPage, ACTION_ADD_TO_FORM, true);
                $(cartPage.getUISelector()).hide();
                UI.actionService.runActionForComponent(thankYouPage, ACTION_ADD_TO_FORM, true);
                $(thankYouPage.getUISelector()).hide();

                Pager.renderTopMenuSelect();
            }

            UI.undoManagerAdd({
                undo: function () {
                    undo();
                },
                redo: function () {
                    redo();
                }
            });
        }
    }

    UI.getSignInComponent = function () {
        return UI.siteComponentRepository.lookupData({ displayName: SIGNIN });
    }

    UI.getCustomUserFields = function () {
        var component = UI.getSignInComponent();
        if (component) {
            var fields = [];
            _.forEach(JSON.parse(component.getProperty(CUSTOM_USER_FIELDS).value), function (field) {
                fields.push(new CustomUserField(field));
            });
            return fields;
        } else {
            return null;
        }
    }

    UI.setCustomUserFields = function (data) {
        var component = UI.getSignInComponent();
        if (component) {
            component.setProperty(CUSTOM_USER_FIELDS, data);
        }
    }

UI.getSiteColors = function () {
    var colors = UI.getBody().getProperty(SITE_COLORS).value;
    return colors ? colors.split(';') : [];
}

UI.addSiteColor = function (value) {
    var colors = UI.getSiteColors();
    if (colors.length < 100) {
        colors.push(value);
        colors = _.uniq(colors);
        UI.getBody().setProperty(SITE_COLORS, colors.join(';'));
    }
}

UI.initAutoSave = function() {
    if (autoSaveTimer) {
        clearInterval(autoSaveTimer);
    }

    var autoSaveInterval = UI.getBody().getProperty(AUTO_SAVE_PERIOD).value * 60000 ; 
    console.log(autoSaveInterval);

    autoSaveTimer = setInterval(UI.AutoSaveLocalTemplate, autoSaveInterval);
}

UI.AutoSaveLocalTemplate = function () {
    var json = JSON.parse(UI.templateToJson());
    var d = new Date();
    IndexedDBService.setLocalTemplateParam({
        "auto-saved-model": json,
        "auto-saved-time": d.toLocaleString()
    });
    console.log('autosave', d.toLocaleString());
};
var BasicComponent = function (root, parentComponent) {
    //basic component class construction
    var self = this;
    parentComponent = defined(parentComponent) ? parentComponent : null;
    root.properties = defined(root.properties) ? root.properties : [];
    root.children = defined(root.children) ? root.children : [];
    self.id = root.componentId;
    self.index = "";
    self.template = root.templateData || "";
    self.editorTemplate = root.editorTemplateData || "";
    self.slaveTemplate = root.slaveTemplateData || "";
    self.slaveEditorTemplate = root.slaveEditorTemplateData || "";
    self.name = root.name;
    self.properties = [];
    self.children = [];
    self.parentComponent = parentComponent;
    self.complexComponent = root.isComplex;
    self.componentType = root.componentType;
    self.thumbnail = defined(root.thumbnail) ? root.thumbnail : "";
    self.parentId = defined(root.parentId) ? root.parentId : null;
    self.isDraggable = defined(root.isDraggable) ? root.isDraggable : true;
    self.isDockable = defined(root.isDockable) ? root.isDockable : false;
    self.isSelectable = defined(root.isSelectable) ? root.isSelectable : true;
    self.isActive = defined(root.isActive) ? root.isActive : false;
    self.displayName = defined(root.displayName) ? root.displayName : root.name;
    self.description = defined(root.description) ? root.description : '';
    self.events = null;
    //add actions
    self.actions = {};
    self.actions[ACTION_SIGN_IN] = ActionFactory.actionFor(self, ACTION_SIGN_IN);
    self.actions[ACTION_SIGN_OUT] = ActionFactory.actionFor(self, ACTION_SIGN_OUT);
    self.actions[ACTION_ADD_TO_FORM] = ActionFactory.actionFor(self, ACTION_ADD_TO_FORM);
    self.actions[ACTION_REMOVE_FROM_FORM] = ActionFactory.actionFor(self, ACTION_REMOVE_FROM_FORM);
    self.actions[ACTION_EDITOR_OPEN] = ActionFactory.actionFor(self, ACTION_EDITOR_OPEN);
    self.actions[ACTION_EDITOR_CLOSED] = ActionFactory.actionFor(self, ACTION_EDITOR_CLOSED);
    if (defined(root.children)) {
        root.children.forEach(function (item) {
            var child = new BasicComponent(item, self);
            self.children.push(child);
        });
    }
    root.properties.forEach(function (item) {
        var property = new Property(item);
        self.properties.push(property);
    });
    self.getProperty = function (name, deviceId) {
        deviceId = deviceId ? deviceId : UI.getDefaultDeviceId();
        var properties = self.properties.where({ name: name }).where({ deviceId: deviceId });
        var property = properties ? properties.firstOrDefault() : null;
        return property;
    }
}

var Component = function () {
    var self = this;

    self.createNew = function (basicComponent, generateNewId, parentComponent) {
        //new component or existing component class instantiation 
        UI.unsavedChanges = true;
        var self = this;
        parentComponent = defined(parentComponent) ? parentComponent : null;
        self.id = generateNewId ? (Guid.new()) : basicComponent.controlId;
        self.name = generateNewId ? basicComponent.id : basicComponent.componentId;
        self.componentName = basicComponent.name;
        self.children = [];
        self.complexComponent = defined(basicComponent.complexComponent) ? basicComponent.complexComponent : false;
        self.isDraggable = defined(basicComponent.isDraggable) ? basicComponent.isDraggable : true;
        self.isDockable = defined(basicComponent.isDockable) ? basicComponent.isDockable : false;
        self.isSelectable = defined(basicComponent.isSelectable) ? basicComponent.isSelectable : true;
        self.isNew = generateNewId;
        self.parentComponent = parentComponent;
        self.parentId = defined(basicComponent.parentId) ? basicComponent.parentId : null;
        self.events = self.componentEvents(basicComponent.componentName != undefined ? basicComponent.componentName : basicComponent.name);
        self.proto = basicComponent;
        self.viewer = ViewerFactory.proxy;
        self.stretcher = StretcherFactory.proxy;
        self.editor = EditorFactory.proxy;
        UI.actionService.addToActionData(self);      
        if (defined(basicComponent.children)) {
            if (basicComponent.componentId === "9acd3bc9-f075-4f50-9cb8-496b718377c8") {
                //fixes old panel logic                
                var top = basicComponent.properties.where({ name: TOP }).firstOrDefault();
                var left = basicComponent.properties.where({ name: LEFT }).firstOrDefault();
                var zindex = basicComponent.properties.where({ name: Z_INDEX }).firstOrDefault();
                _.forEach(basicComponent.children, function (item) {
                    //create child component
                    var child = new Component().createNew(item, generateNewId, self.parentComponent);
                    self.parentComponent.children.push(child);
                    if (top != null) {
                        var childTop = child.properties.where({ name: TOP }).firstOrDefault();
                        if (childTop == null) {
                            //if property not exist in control create new
                            childTop = new Property(_.defaults({ controlId: child.id }, top));
                            child.properties.push(childTop);
                            //set default value
                            childTop.value = '0';
                        }
                        //set new value = old value + block value
                        childTop.value = parseInt(childTop.value) + parseInt(top.value) + 'px';                        
                    }
                    if (left != null) {
                        var childLeft = child.properties.where({ name: LEFT }).firstOrDefault();
                        if (childLeft == null) {
                            //if property not exist in control create new
                            childLeft = new Property(_.defaults({ controlId: child.id }, left));
                            child.properties.push(childLeft);
                            //set default value
                            childLeft.value = '0';
                        }
                        //set new value = old value + block value
                        childLeft.value = parseInt(childLeft.value) + parseInt(left.value) + 'px';                        
                    }
                    if (zindex != null) {
                        var childZindex = child.properties.where({ name: Z_INDEX }).firstOrDefault();
                        if (childZindex == null) {
                            //if property not exist in control create new
                            childZindex = new Property(_.defaults({ controlId: child.id }, zindex));
                            child.properties.push(childZindex);
                            //set default value
                            childZindex.value = '0';
                        }
                        //set new value = old value + block value
                        childZindex.value = parseInt(childZindex.value) + parseInt(zindex.value) + 1;                        
                    }                    
                });
                //fixes old panel logic
            } else {
                basicComponent.children.forEach(function (item) {
                    var child = new Component().createNew(item, generateNewId, self);
                    self.children.push(child);
                });
            }            
        }
        if (self.proto != null) {
            self.componentId = defined(self.proto.id) ? self.proto.id : basicComponent.componentId;
            self.complexComponent = defined(self.proto.complexComponent) ? self.proto.complexComponent : self.complexComponent;
            self.isDraggable = defined(self.proto.isDraggable) ? self.proto.isDraggable : self.isDraggable;
            self.isDockable = defined(self.proto.isDockable) ? self.proto.isDockable : self.isDockable;
            self.isSelectable = defined(self.proto.isSelectable) ? self.proto.isSelectable : self.isSelectable;
            self.displayName = defined(self.proto.name) ? self.proto.name : basicComponent.componentName;
        }
        self.properties = Component.cloneProperties(basicComponent.properties, self);
        return self;
    }

    self.componentEvents = function (name) {
        switch (name) {
            case "menu":
                return { onComponentProcessed: UI.renderMenus };
            default:
                return undefined;
        }
    }

    self.getUISelector = function () {
        //getting component id for jquery usage
        return '#' + this.id;
    }

    self.createFromExisting = function (existingComponent) {
        //process existing classes        
        self.id = existingComponent.id;
        self.name = existingComponent.name;
        self.children = [];
        self.parentComponent = existingComponent.parentComponent;
        self.proto = UI.basicComponentRepository.lookupData({ id: existingComponent.name });
        self.viewer = ViewerFactory.proxy;
        self.editor = EditorFactory.proxy;
        self.stretcher = StretcherFactory.proxy;
        UI.actionService.addToActionData(self);        
        self.isNew = existingComponent.isNew;
        self.componentId = self.proto.id;
        self.complexComponent = self.proto.complexComponent;
        self.isDraggable = self.proto.isDraggable;
        self.isDockable = self.proto.isDockable;
        self.isSelectable = self.proto.isSelectable;
        self.displayName = self.proto.name;
        self.events = existingComponent.events;

        self.properties = Component.cloneProperties(existingComponent.properties, self);
        if (defined(existingComponent.children)) {
            existingComponent.children.forEach(function (item) {
                var child = new Component().createFromExisting(item, UI.basicComponentRepository);
                if (child.displayName == 'header' || child.displayName == 'footer') {
                    self.children.unshift(child);
                }
                else {
                    self.children.push(child);
                }
                child.parentComponent = self;
            });
        }
        return self;
    }

    self.removeProperty = function (name, deviceId) {
        deviceId = deviceId ? deviceId : UI.getDevice().getId();
        var removed = _.remove(self.properties,
            function(property) {
                return property.deviceId == deviceId && property.name == name;
            });
        return removed;
    }

    self.resetProperty = function(name, deviceId) {
        deviceId = deviceId ? deviceId : UI.getDevice().getId();
        var device = UI.getDevice(deviceId);

        if (device.inheritComponentProperty(self, name)) {
            deviceId = UI.getDefaultDeviceId();
        }

        var properties = self.properties.where({ name: name }).where({ deviceId: deviceId }),
            property = properties ? properties.firstOrDefault() : null;

        if (property != null) {
            var defaultProperty = UI.basicComponentRepository.lookupData({ name: self.displayName }).getProperty(name);
            if (defaultProperty != null) {
                self.setProperty(name, defaultProperty.value, true, deviceId);
            }
        }        
    }

    self.getProperty = function (name, deviceId) {
        deviceId = deviceId ? deviceId : UI.getDevice().getId();
        var device = UI.getDevice(deviceId);

        if (device.inheritComponentProperty(self, name)) {
            deviceId = UI.getDefaultDeviceId();
        }

        var properties = self.properties.where({ name: name }).where({ deviceId: deviceId }),
            property = properties ? properties.firstOrDefault() : null;

        if (property == null && device.isComponentHasProperty(self, name)) {            
            var defaultProperty = UI.basicComponentRepository.lookupData({ name: self.displayName }).getProperty(name);
            if (defaultProperty != null) {
                property = self.addProperty(name,
                   defaultProperty.value,
                   defaultProperty.type,
                   defaultProperty.group,
                   defaultProperty.propertyId,
                   deviceId);
            }
        }
        return property;
    }

    self.getPropertyInt = function (name) {
        var property = self.getProperty(name);
        return property != null ? parseInt(property.value.replace(/[A-Za-z$-]/g, "")) : 0;
    }

    self.setProperty = function (name, value, createIfNotExists, deviceId) {
        if (!StretcherFactory.isPropertyCouldBeWritten(self, name)) {
            return;
        }
        UI.unsavedChanges = true;
        deviceId = deviceId ? deviceId : UI.getDevice().getId();
        createIfNotExists = createIfNotExists || false;
        var property = self.getProperty(name, deviceId);
        if (property != null) {
            property.value = value;
        } else {
            if (createIfNotExists) {
                self.addProperty(name,
                    value);
            }
        }
    }

    self.addPropertyIfNotExists = function (name, deviceId, value) {
        deviceId = deviceId ? deviceId : UI.getDevice().getId();
        var device = UI.getDevice(deviceId);

        if (device.inheritComponentProperty(self, name)) {
            deviceId = UI.getDefaultDeviceId();
        }

        var properties = self.properties.where({ name: name }).where({ deviceId: deviceId }),
            property = properties ? properties.firstOrDefault() : null;

        if (property == null && device.isComponentHasProperty(self, name)) {
            var defaultProperty = UI.basicComponentRepository.lookupData({ name: self.displayName }).getProperty(name);
            if (defaultProperty != null) {
                value = defined(value) ? value : defaultProperty.value;
                self.addProperty(name,
                   value,
                   defaultProperty.type,
                   defaultProperty.group,
                   defaultProperty.propertyId,
                   deviceId);
            }
        }
    }

    self.addProperty = function (name, value, type, group, propertyId, deviceId) {
        if (!defined(propertyId)) {
            console.log('[Classes.js -> function addProperty] PropertyId not defined. Property Name: ' + name);
        }
        //check if exist
        var property = self.properties
            .where({ name: name })
            .where({ deviceId: deviceId })
            .firstOrDefault();
        if (property == null) {
            type = defined(type) ? type : "common";
            group = defined(group) ? group : "common";
            value = defined(value) ? value : "";
            propertyId = defined(propertyId) ? propertyId : "";
            deviceId = defined(deviceId) ? deviceId : UI.getDevice().getId();
            property = new Property({
                name: name,
                value: value,
                type: type,
                group: group,
                propertyId: propertyId,
                deviceId: deviceId,
                componentId: self.proto.id,
                controlId: self.id
            });
            self.properties.push(property);            
        }
        return property;
    }
    return self;
}



//breaking component property to basic component property reference
Component.cloneProperties = function (properties, comp) {
    var clonedProperties = [];
    if (defined(properties)) {
        properties.forEach(function (item) {
            var property = new Property(item, comp);
            clonedProperties.push(property);
        });
    }
    return clonedProperties;
}

var Property = function (root, comp) {
    var self = this;
    self.name = root.name;
    self.value = defined(root.value) ? root.value : root.defaultValue;
    self.group = root.group;
    self.propertyId = defined(root.propertyId) ? root.propertyId : "";
    self.componentId = defined(root.componentId) ? root.componentId : "";
    self.controlId = defined(root.controlId) ? root.controlId : "";
    self.type = root.type;
    self.deviceId = root.deviceId || UI.getDefaultDeviceId();
    if (defined(comp)) {
        try {
            var props = {
                "meta-title": "bf88797a-ab62-4b91-bada-bd2d81c6b60e",
                'meta-description': '0e7555e1-7147-4a30-8991-e30eb93c6217',
                'meta-keywords': 'eadff7bf-0b15-4a55-9e5e-2793946a7a1f',
                'title': "BB0C3CBD-B973-4C30-8F96-BC8BE47967CE",
                "name": "14cbb701-790c-4003-947b-d07379851d1d",
                "home": "0e12b99c-e2de-47ad-86ed-78cb4712da8a",
                "googleanalytics-script": "131BAC41-0F4E-4977-B1EC-6F7796254BA8",
                "sitemapxml": "12CCC4D6-A97F-4F80-B8AB-5E071D369384",
                "searchengine": "0E4A7BB3-911E-4F32-A7A3-DCAEDD68EBEE",
                "googleanalytics": "DF243CDF-6880-44F5-B96F-D9EBCAF5B67A",

                "isprotected": "54227F57-AF4A-47CB-AD24-CBB88ABDEAA7",
                "protectedemail": "F9F53835-D319-4D5B-9545-FCA2F9FF9688",
                "protectedword": "ADCED20D-FE79-4D46-9D7F-F134E0A7CEF5",
                "google-verification-code": "B0811B3F-BBB0-44B4-B370-DFF85023ABC5",
                "bing-verification-code": "074F2EC5-662B-437D-B0C9-08D23EC9C368",

                "meta-facebook-title": "43769BF9-BB22-4189-A1D3-8B051E6066F4",
                "meta-facebook-description": "626E1D9D-6546-4B84-A362-7397498F617B",
                "meta-facebook-image": "94447ED1-5D9C-4689-B58A-9F7BD8D669A6",
                "auto-save-period": "1028DF1F-DFDF-4CBA-A51B-037661DD2D36",

                "header-content": "a95eefb7-616c-7e15-396a-126afd6a3344"
                
            }

            if (defined(props[self.name])) {
                self.propertyId = props[self.name];
            }

            if (!defined(self.propertyId) || self.propertyId == "") {
                var defaultProperty = UI.basicComponentRepository.getAll().where({ id: comp.componentId }).firstOrDefault().getProperty(root.name);
                self.propertyId = defaultProperty.propertyId;
            }
            if (!defined(self.componentId) || self.componentId == "") {
                self.componentId = comp.componentId;
            }
            if (!defined(self.componentId) || self.controlId == "") {
                self.controlId = comp.id;
            }
        }
        catch (e) {
            console.log("Error new Property");
        }
    }
}

var Tooltips = function (data) {
    var self = this;
    self.data = {};
    _.forEach(_.keys(data),
        function (key) {
            self.data[key] = ko.observable(data[key]);
        });

    self.getAll = function () {
        return self.data;
    }

    self.getValueByKey = function (key) {
        return self.data[key] ? self.data[key](): null;
    }

    self.setValueByKey = function (key, value) {
        self.data[key](value);
        $.ajax({
            url: "/Editor/SetTooltip",
            type: "POST",
            data: { data: JSON.stringify({ key: key, value: value }) }
        });
    }

    self.viewModel = {
        value: ko.observable(),
        save: function (data, e) {
            e.stopPropagation();
            self.setValueByKey(data.key, data.value());
            self.viewModel.element.popover('destroy');
        },
        cancel: function (data, e) {
            e.stopPropagation();
            self.viewModel.element.popover('destroy');
        }
    }

    self.getViewModel = function (options) {
        self.viewModel.key = options.key;
        self.viewModel.name = options.name;
        var value = self.getValueByKey(options.key);
        if (value !== null) {
            self.viewModel.value(value);
            self.viewModel.element = options.element;
            return self.viewModel;
        } else {
            return null;
        }
    }
}

var User = function (data) {
    var self = this;
    self.id = data.id || '';
    self.email = data.userName || '';
    self.avatar = ko.observable(data.avatar ? decodeURIComponent(data.avatar) : '');

    self.customFields = {};

    var getFormattedDate = function (date) {
        var year = date.getFullYear();

        var month = (1 + date.getMonth()).toString();
        month = month.length > 1 ? month : '0' + month;

        var day = date.getDate().toString();
        day = day.length > 1 ? day : '0' + day;

        return month + '/' + day + '/' + year;
    }
    //init custom user fields
    _.forEach(UI.getCustomUserFields(), function (field) {
        if (field.name === 'birthday' && data[field.name]) {
            var date = new Date(data[field.name]);
            self.customFields[field.name] = ko.observable(getFormattedDate(date));
        } else {
            self.customFields[field.name] = ko.observable(data[field.name] || '');
        }
    });    

    //computed field
    self.fullName = ko.computed(function () {
        if (_.keys(self.customFields).length && self.customFields.firstName() && self.customFields.lastName()) {
            return self.customFields.firstName() + " " + self.customFields.lastName();
        } else {
            return self.email;
        }
    }, self);

    self.getAvatar = function() {
        return self.avatar() ? self.avatar() : SIGNIN_USER_DEFAULT_IMAGE;
    }

    self.toDTO = function() {
        var user = {
            id: self.id,
            userName: self.email,
            avatar: encodeURIComponent(self.avatar())
        }
        _.forEach(_.keys(self.customFields),
            function(fieldKey) {
                user[fieldKey] = self.customFields[fieldKey]();
            });
        return user;
    }

    self.save = function (callback) {
        callback = defined(callback) ? callback : function() {};
        ProxyService.send(self.toDTO(), 'user', 'PUT', {}, callback);
    };

    return self;
}

var CustomUserField = function (data) {
    var self = this;
    self.name = data.name;
    self.displayName = data.displayName;
    self.active = ko.observable(data.active);
    self.required = ko.observable(data.required);
    return self;
}

var Device = function (data) {
    var self = this;

    self.init = function (data) {
        self.type = data.type;
        self.width = data.width;        
        self.displayName = data.displayName;
        self.minWidth = data.minWidth;
        self.maxWidth = data.maxWidth;
        self.componentsProperties = data.componentsProperties;
        self.name = data.name;
        self.id = data.id;
        self.isDefault = data.isDefault || false;
    }

    self.isWidthInRange = function(width) {
        return width >= self.minWidth && width <= self.maxWidth;
    }

    self.isHaveComponentProperties = function () {
        return !!self.componentsProperties;
    }

    self.isDesktop = function () {
        return self.type === DEVICE_DESKTOP_TYPE;
    }

    self.getName = function () {
        return self.name;
    }

    self.getDisplayName = function () {
        return self.displayName;
    }

    self.getId = function () {
        return self.id;
    }

    self.getType = function () {
        return self.type;
    }

    self.getWidth = function () {
        return self.width;
    }

    self.getPropertiesList = function (component) {
        var rules = self.componentsProperties[component.proto.id];
        var properties = [];
        _.forEach(component.properties, function (prop) {
            var name = prop.name;
            var deviceId = prop.deviceId;
            if ((rules[name] === true && deviceId === UI.getDefaultDeviceId()) || (rules[name] === false && deviceId === self.id)) {
                properties.push(prop);
            }
        });
        return properties;
    }

    self.isComponentHasProperty = function (component, property) {
        return _.has(self.componentsProperties[component.proto.id], property);
    }

    self.inheritComponentProperty = function (component, property) {
        return self.componentsProperties[component.proto.id][property];
    }

    self.setComponentsProperties = function (componentsProperties) {
        self.componentsProperties = componentsProperties;
    }

    self.getComponentsProperties = function () {
        return self.componentsProperties;
    }

    self.reset = function () {
        var removedProperties = [];

        var redo = function () {
            Application.addLocker();

            setTimeout(function () {
                UI.removeDeviceIdFromTemplateData(self.getId());
                removedProperties = Device.removeDeviceProperties(UI.siteComponentRepository.getAll().firstOrDefault(), self.getId());
                UI.setDevice(self);
                Application.removeLocker();
            }, 500);            
        }
        var undo = function () {
            Application.addLocker();
            setTimeout(function () {
                _.forEach(removedProperties,
                function (item) {
                    _.forEach(item.property, function(property) {
                        item.component.setProperty(
                            property.name,
                            property.value,
                            false,
                            property.deviceId);
                    });
                });                
                UI.setDevice(self);
                Application.removeLocker();
            }, 500);            
        }
        UI.undoManagerAdd(
			{
			    undo: function () {
			        undo();
			    },
			    redo: function () {
			        redo();
			    }
			});
        redo();
    }

    self.resetPage = function() {
        var removedProperties = [];
        var page = UI.siteComponentRepository.lookupData({ id: UI.pager.getCurrentPageId() });

        var redo = function () {
            Application.addLocker();           
            UI.actionService.runActionForComponent(page, ACTION_REMOVE_FROM_FORM, true);

            setTimeout(function () {              
                removedProperties = Device.removeDeviceProperties(page, self.getId());
                TransformFactory.runTransform(page, self.getId(), true);
                UI.actionService.runActionForComponent(page, ACTION_ADD_TO_FORM, true);
                Resizer.recalculateHeaderFooterAndPageSize($(page.getUISelector()));
                Application.removeLocker();
            }, 500);
        }
        var undo = function () {
            Application.addLocker();
            UI.actionService.runActionForComponent(page, ACTION_REMOVE_FROM_FORM, true);

            setTimeout(function () {
                _.forEach(removedProperties,
                    function (item) {
                        _.forEach(item.property, function (property) {
                            item.component.setProperty(
                                property.name,
                                property.value,
                                false,
                                property.deviceId);
                        });
                    });

                UI.actionService.runActionForComponent(page, ACTION_ADD_TO_FORM, true);
                Resizer.recalculateHeaderFooterAndPageSize($(page.getUISelector()));
                Application.removeLocker();
            }, 500);
        }
        UI.undoManagerAdd(
            {
                undo: function () {
                    undo();
                },
                redo: function () {
                    redo();
                }
            });
        redo();
    }

    self.init(data);
    return self;
}

Device.removeDeviceProperties = function (component, deviceId) {
    var device = UI.getDevice(deviceId);
    var removedProperties = [];
    if (device.isHaveComponentProperties()) {
        var componentsProperties = device.getComponentsProperties();
        _.forEach(_.keys(componentsProperties[component.proto.name]),
            function(property) {
                if (componentsProperties[component.proto.name][property] === false) {
                    //remove
                    var removed = component.removeProperty(property, device.getId());                    
                    removedProperties.push({ component: component, property: removed});
                }
            });
        _.forEach(component.children,
            function (child) {
                removedProperties.addRange(Device.removeDeviceProperties(child, device.getId()));
            });
    }
    return removedProperties;
}

Device.getDevices = function() {
    var result = [];
    var actinTypeValue = UI.settings.actionType.toNumber();

    $.ajax({
        url: "/Editor/GetDevices",
        type: "GET",
        data: { templateId: UI.getTemplateProperty('templateId'), actionType: actinTypeValue },
        async: false,
        success: function (data) {
            var wrapper = JSON.parse(data);
            result = wrapper.devices;
        }
    });
      
    return result;
}

Device.setDeviceRequest = function(device) {
    var templateid = UI.getTemplateProperty('templateId');
    if (UI.getSetting("actionType") == ACTION_TYPE_CREATE_WEB_SITE || UI.getSetting("actionType") == ACTION_TYPE_CREATE_TEMPLATE) {
        templateid = $("#sitecontent").data("templateid");
    }
    $.ajax({
        url: "/Editor/SetDevice",
        type: "GET",
        data: { templateId: templateid, deviceId: device.getId() },
        async: false,
        success: function (data) {
            var wrapper = JSON.parse(data);
            device.init(wrapper);
        }
    });       
}

var StoreEnum = function(data) {
    var self = this;
    self.id = 0;
    self.title = '';
    self.value = '';
    self.symbol = '';
    self.isBase = false;
    self.groupId = '';

    self.init = function(data) {
        if (data) {
            self.id = data.id;
            self.title = data.desc;
            self.value = data.name;
            self.symbol = data.shortName;
            self.isBase = data.isBase.toBoolean();
            self.groupId = data.groupId;
        }
    }

    self.init(data);
    return self;
};

var Store = function(component) {
    var self = this;
    self.component = component;

    var defaultSettings = {
        currencyId: 0,
        weightUnitTypeId: 2,
        shippingPolicyUrl: '',
        returnPolicyUrl: ''
    }

    self.getCurrencyOptions = function() {
        var options = [];
        ProxyService.send({ name: 'currency' }, 'enum', 'GET', {}, function (data) {
            if (data && data.length) {
                _.forEach(data, function(item) {
                    options.push(new StoreEnum(item));
                });
            }
        }, true);
        self.currencyOptions = options;
        return options;
    }
    self.getWeightUnitOptions = function () {
        var options = [];
        ProxyService.send({ name: 'weightunit' }, 'enum', 'GET', {}, function (data) {
            if (data && data.length) {
                _.forEach(data, function (item) {
                    options.push(new StoreEnum(item));
                });
            }
        }, true);
        self.weightUnitOptions = options;
        return options;
    }

    self.reset = function (callback) {
        callback = defined(callback) ? callback : function () { };
        ProxyService.send({}, 'resetstore', 'POST', {}, function () {
            _.forEach(UI.store.getGalleries(),
                function (galleryComponent) {                    
                    var viewModel = UI.viewModelRepository['store-gallery' + galleryComponent.id];
                    if (viewModel) {
                        viewModel.reload();
                    }
                });
            callback();
        });
    }

    self.getGalleries = function() {
        return UI.siteComponentRepository.lookupDataSet({ displayName: STORE_GALLERY });
    }

    self.getCurrency = function () {
        var currency = _.find(self.currencyOptions,
            function(item) {
                return self.settings && item.id === self.settings.currencyId;
            });
        return currency == null ? {} : currency;
    }

    self.getWeightUnit = function(id) {
        var weightUnit = _.find(self.weightUnitOptions,
            function (item) {
                if (defined(id)) {
                    return item.id === id;
                } else {
                    return self.settings && item.id === self.settings.weightUnitTypeId;
                }
            });
        return weightUnit == null ? {} : weightUnit;
    }    

    self.getProduct = function (id, callback, fake) {
        var data = {id: id};
        if (defined(fake) && !!fake) {
            data.fake = !!fake;
        }
        ProxyService.send(data, 'product', 'GET', {}, function (data) {
            if (data && data[0]) {
                callback(data[0]);
            } else {
                callback(null);
            }
        });
    }

    self.getOrder = function (id, callback) {
        ProxyService.send({ id: id }, 'order', 'GET', {}, function (data) {
            if (data && data[0]) {
                callback(data[0]);
            } else {
                callback(null);
            }
        });
    }

    self.updateOrder = function (data, callback) {
        callback = defined(callback) ? callback : function() {};
        ProxyService.send(data, 'order', 'PUT', {}, function () {
            callback();
        });
    }

    self.getOrderStatuses = function () {
        var statuses = [];
        ProxyService.send({ name: 'orderstatus' }, 'enum', 'GET', {}, function (data) {
            if (data) {
                statuses = data;
            }
        }, true);
        return statuses;
    }

    self.getCountries = function () {
        var countries = [];
        ProxyService.send({ name: 'country' }, 'enum', 'GET', {}, function (data) {
            if (data) {
                countries = data;
            }
        }, true);
        return countries;
    }

    self.getStates = function () {
        var states = [];
        ProxyService.send({ name: 'usstates' }, 'enum', 'GET', {}, function (data) {
            if (data) {
                states = data;
            }
        }, true);
        return states;
    }

    self.getSettings = function () {        
        ProxyService.send({}, 'storesettings', 'GET', {}, function (data) {
            if (data && data[0]) {
                self.settings = $.extend(defaultSettings, data[0]);
            }
        }, true);
        return self.settings;
    }

    self.saveSettings = function (data, callback) {
        callback = defined(callback) ? callback : function() {};
        ProxyService.send(data, 'storesettings', 'PUT', {}, function (data) {
            if (data && data[0]) {
                self.settings = $.extend(defaultSettings, data[0]);
            }
            callback();
        });
    }

    self.getProducts = function (filter, callback, fake) {
        var data = {};
        if (defined(fake) && !!fake) {
            data.fake = !!fake;
        }
        filter = defined(filter) ? filter : {};
        filter.requesttype = 'items';
        ProxyService.send(data, 'product', 'GET', filter, function (data) {            
            callback(data);
        });
    }

    self.getCategories = function (filter, callback) {
        filter = defined(filter) ? filter : {};
        filter.requesttype = 'items';
        ProxyService.send({}, 'category', 'GET', filter, function (data) {
            callback(data);
        });
    }

    self.getTaxRates = function (callback) {        
        ProxyService.send({}, 'taxrate', 'GET', { requesttype: 'items', page: 1, count: 999 }, function (data) {
            callback(data);
        });
    }

    self.getShippingRates = function (callback) {
        ProxyService.send({}, 'shippingrate', 'GET', { requesttype: 'items', page: 1, count: 999 }, function (data) {
            callback(data);
        });
    }

    self.checkout = function (callback) {
        if (UI.getSetting('ispublished')) {
            var paymentAccount = self.getPaymentAccount('paypal');
            if (paymentAccount) {
                var products = [];
                _.forEach(self.getCart(),
                    function(item) {
                        products.push({
                            id: item.product.id,
                            count: item.quantity,
                            attributes: Product.optionsToDto(item.product.options)
                        });
                    });
                if (products.length) {
                    console.log(products);
                    var order = new Order({ products: products });
                    console.log({ products: order.toDTO().products });
                    ProxyService.send({ products: order.toDTO().products },
                        'order',
                        'POST',
                        {},
                        function(data) {
                            if (data && data[0]) {
                                self.clearCart();
                                var checkoutUrl = UI.getSetting('paymentSiteDomain') +
                                    '/Store/CheckoutShippingAddress' +
                                    '?orderId=' +
                                    data[0].id +
                                    '&templateId=' +
                                    UI.getTemplateProperty("templateId") +
                                    '&returnUrl=' +
                                    origin +
                                    '/thank-you/' +
                                    data[0].number;
                                console.log(checkoutUrl);

                                callback();
                                var newWin = window.open(checkoutUrl);

                                if (!newWin || newWin.closed || typeof newWin.closed == 'undefined') {
                                    //POPUP BLOCKED
                                    location.href = checkoutUrl;
                                }
                            }
                        });
                }
            } else {
                Application.showOkDialog('Error', 'Payment Account is not set!');
            }
        } else {
            Application.showOkDialog('Error', 'Available only on published site!');
        }
    }

    self.getPaymentAccount = function(type) {
        switch (type) {
            case 'paypal':
                return self.component.getProperty(PAYPAL_EMAIL).value;
            default:
                return '';
        }
    }

    self.setPaymentAccount = function (type, value) {
        var property;
        switch (type) {
            case 'paypal':
                property = PAYPAL_EMAIL;
                break;
            default:
                return;                
        }
        UI.undoManagerAddSimple(self.component, property, value, function() {}, true);
    }

    self.getCart = function() {
        var storage = LocalStorageService.GetItem(UI.getTemplateProperty("templateId"));
        if (storage != null) {
            if (storage.cart != null) {
                var cart = [];                
                _.forEach(_.keys(storage.cart), function (key) {                    
                    if (key) {
                        var product = new Product(storage.cart[key].product);
                        product.cartKey = key;
                        cart.push({
                            product: product,
                            quantity: storage.cart[key].count
                        });
                    }
                });
                return cart;
            } else {
                return [];
            }
        } else {
            return [];
        }
    }

    self.cartCount = function() {
        return self.getCart().length;
    }

    self.getPageWithGallery = function() {
        var gallery = self.getGalleries().firstOrDefault();
        if (gallery) {
            return UI.pager.getPage(gallery.parentComponent.id);
        } else {
            return null;
        }
    }

    self.addToCart = function (key, product, count) {
        if(typeof count == 'string'){
          count = parseInt(count);
        }
        var storage = LocalStorageService.GetItem(UI.getTemplateProperty("templateId"));
        if (storage == null) {
            storage = {cart: {} };
        } else {
            if (storage.cart == null) {
                storage.cart = {};
            }
        }
        if (storage.cart[key] == null) {
            storage.cart[key] = {
                count: count,
                product: product
            }
        } else {
            if (storage.cart[key].count) {
                storage.cart[key].count = parseInt(storage.cart[key].count) + count;
                if (!storage.cart[key].product) {
                    storage.cart[key].product = product;
                }
            } else {
                storage.cart[key] = {
                    count: count,
                    product: product
                }
            }
        }        
        LocalStorageService.SetItem(UI.getTemplateProperty("templateId"), storage);
    }

    self.removeFromCart = function (key, count, removeAll) {
        var storage = LocalStorageService.GetItem(UI.getTemplateProperty("templateId"));
        if (storage != null) {
            if (storage.cart != null) {
                if (storage.cart[key] != null) {
                    if (storage.cart[key].count && !removeAll) {
                        storage.cart[key].count = parseInt(storage.cart[key].count) - count;
                        if (storage.cart[key] <= 0) {
                            delete storage.cart[key];
                        }
                    } else {
                        delete storage.cart[key];
                    }
                    LocalStorageService.SetItem(UI.getTemplateProperty("templateId"), storage);
                }                
            }
        }
    }

    self.clearCart = function() {
        localStorage.removeItem(UI.getTemplateProperty("templateId"));
    }

    self.init = function() {
        self.getCurrencyOptions();
        self.getWeightUnitOptions();
        self.getSettings();
    }

    self.init();
    return self;
}

var Product = function (data) {
    var self = this;
    self.currency = UI.store.getCurrency().symbol;
    self.weightUnit = UI.store.getWeightUnit();    

    self.id = '';
    self.friendlyUrl = '';
    self.dateCreated = '';
    self.title = '';
    self.sku = '';
    self.label = '';
    self.description = '';
    self.manufacturer = '';
    self.visability = true;
    self.barCode = '';
    self.weight = 0;
    self.weightUnitTypeId = 0;
    self.outOfStock = false;
    self.stockStatus = 2;
    self.images = ko.observableArray([]);
    self.price = 0;
    self.discount = 0;
    self.isDiscount = false;
    self.discountType = 2;
    self.quantity = 0;
    self.calcDiscount = 0;
    self.calcPrice = 0;
    self.options = [];
    self.cartKey = '';
    self.categories = [];
    self.іsFake = false;
    self.metaKeywords = '';
    self.metaDescription = '';

    self.mainImageUrl = ko.computed(function () {
        if (self.images() && self.images().length) {
            return self.images()[0].url;
        } else {
            return STORE_PRODUCT_DEFAULT_IMAGE;
        }
    }, self);

    self.alternateImageUrl = ko.computed(function () {
        if (self.images() && self.images().length && self.images().length > 1) {
            return self.images()[1].url;
        } else {
            return self.mainImageUrl();
        }
    }, self);

    self.discountPrice = function () {
        if (self.isDiscount) {
            return Product.calculateDiscountPrice(self.discountType, self.price, self.discount);
        } else {
            return 0;
        }
    }
    
    self.init = function (data) {
        self.currency = UI.store.getCurrency().symbol;        
        if (data) {
            self.id = data.id;
            self.friendlyUrl = data.friendlyUrl;
            self.dateCreated = data.dateCreated;
            self.title = data.name;
            self.sku = data.sku;
            self.label = data.label;
            self.description = data.description || '';
            self.manufacturer = data.manufacturer;
            self.barCode = data.barCode;
            self.weight = data.weight;
            self.visability = data.isVisible;
            self.stockStatus = data.stockStatus;
            self.outOfStock = data.stockStatus ? data.stockStatus !== 2 : false;
            self.images([]);
            _.forEach(data.blobs,
                function(item) {
                    self.images.push({
                        id: item.id,
                        url: decodeURIComponent(item.url)
                    });
                });
            self.price = parseFloat(data.price, 0.00);
            self.quantity = parseInt(data.qty);

            self.discount = parseFloat(data.discount);
            self.isDiscount = data.isDiscount ? data.isDiscount.toBoolean() : false;
            self.discountType = data.discountType;

            self.calcDiscount = parseFloat(data.calcDiscount);
            self.calcPrice = parseFloat(data.calcPrice);
            _.forEach(data.categories,
                function(item) {
                    self.categories.push({
                        id: item.id,
                        name: item.name
                    });
                });

            self.options = Product.initOptions(data.attributes);
            self.isFake = data.isFake || false;
            self.metaKeywords = data.metaKeywords;
            self.metaDescription = data.metaDescription;
            self.weightUnitTypeId = data.weightUnitTypeId;
            self.weightUnit = UI.store.getWeightUnit(data.weightUnitTypeId);
        } else {
            self.weightUnit = UI.store.getWeightUnit();
        }

    }

    self.toDTO = function (options) {
        options = defined(options) ? options : self.options;        
        var objectDTO = {
            name: self.title,
            friendlyUrl: self.friendlyUrl,
            manufacturer: self.manufacturer,
            sku: self.sku,
            label: self.label,
            barCode: self.barCode,
            weight: self.weight,
            isVisible: self.visability,
            blobs: [],
            qty: self.quantity,
            price: self.price,
            stockStatus: self.stockStatus,
            description: self.description,
            attributes: Product.optionsToDto(options),
            discount: self.discount,
            isDiscount: self.isDiscount,
            discountType: self.discountType,
            weightUnitTypeId: self.weightUnitTypeId,
            categories: self.categories,
            metaKeywords: self.metaKeywords,
            metaDescription: self.metaDescription
        }
        _.forEach(self.images(),
            function(image) {
                objectDTO.blobs.push({
                    id: image.id,
                    url: encodeURIComponent(image.url)
                });
            });
        if (self.id) {
            objectDTO.id = self.id;
        }
        return objectDTO;
    }

    self.getFullPrice = function (options) {
        options = defined(options) ? options : self.options;
        var price = self.price;
        _.forEach(options,
            function(option) {
                _.forEach(option.items, function (item) {
                    price += item.diff;
                });
            });
        return price;
    }

    self.getFullDiscountPrice = function (options) {
        options = defined(options) ? options : self.options;        
        return Product.calculateDiscountPrice(self.discountType, self.getFullPrice(options), self.discount);
    }

    self.generateLink = function () {
        return UI.getSetting('ispreview') ? Helpers.generateLinkToPage('product', true, { key: 'productId', value: self.friendlyUrl || self.id }) : '';
    }

    self.save = function (callback) {
        callback = defined(callback) ? callback : function() {};
        saveEntityData('product', self.toDTO(), callback);
    }

    self.init(data);
    return self;
}

Product.calculateDiscountPrice = function (discountType, price, discount) {
    var discountPrice = discountType === 2
        ? (price - discount)
        : (price * ((100 - discount) / 100));
    return parseFloat(discountPrice.toFixed(2));
}

Product.initOptions = function (attributes) {
    var options = [];
    if (attributes !== null) {
        _.forEach(attributes,
            function(option) {
                var items = [];
                _.forEach(option.values,
                    function(item) {
                        items.push({
                            id: item.id,
                            diff: parseFloat(item.diffPrice),
                            value: item.value
                        });
                    });
                options.push({
                    id: option.id,
                    title: option.name,
                    items: items
                });
            });
    }
    return options;
}

Product.optionsToDto = function (options) {
    var attributes = [];
    options = (typeof options == 'function' ? options() : options);
    _.forEach(options,
        function (option) {
            var items = [];
            var values = (typeof option.values == 'function' ? option.items() : option.items);
            _.forEach(values, function (item) {
                var valueObject = {                    
                    diffPrice: parseFloat(item.diff, 0.00),
                    value: item.value
                };
                if (item.id) {
                    valueObject.id = item.id;
                }
                items.push(valueObject);
            });
            var optionObject = {                
                name: option.title,
                values: items
            };
            if (option.id) {
                optionObject.id = option.id;
            }
            attributes.push(optionObject);
        });

    return attributes;
}


var Category = function (data) {

    var self = this;
    self.id = '';
    self.tempId = '';
    self.dateCreated = '';
    self.name = '';
    self.hasSubcategories = false;
    self.parentCategory = '';
    self.products = [];
    self.categories = [];

    self.init = function (data) {
        if (data) {
            self.id = data.id;
            self.tempId = Guid.new();
            self.dateCreated = data.dateCreated;
            self.name = data.name;
            self.hasSubcategories = data.hasSubCategories;
            self.parentCategory = data.parentCategory;

            _.forEach(data.children, function (category) {
                category.parentCategory = self;
            });

            self.categories = self.getSubcategoriesArrayFromObjectsDTO(data.children);
            self.products = self.getProductsArrayFromObjectsDTO(data.products);
        }
    }

    self.hasChildren = function() {
        return self.categories.length;
    }

    self.toDTO = function () {
        return toDTO(self);
    }

    var toDTO = function (category) {
        var parent = ko.utils.unwrapObservable(category.parentCategory);
        var objectDTO = {
            name: category.name,
            hasSubcategories: false,
            parentId: parent ? parent.id : '',
            products: [],
            children: []
        };

        objectDTO.hasSubcategories = ko.utils.unwrapObservable(category.hasSubcategories);

        if (category.id && category.id != '') {
            objectDTO.id = category.id;
        }
        var categories = typeof category.categories == 'function' ? category.categories() : category.categories;

        var products = typeof category.products == 'function' ? category.products() : category.products;

        _.forEach(products, function (product, keyProducts) {
            objectDTO.products.push({
                id: product.id
            });
        });

        categories.forEach(function (category, keyCategories) {
            objectDTO.children.push(toDTO(category));
        });

        return objectDTO;
    }

    self.getAllProducts = function (search, model, callback) {

        console.log('getting all products');
        var filter = { requesttype: 'items', count: 100 };
        if(search && search != ''){
            filter['grid-filter'] = 'Name__2__' + search;
        }
        ProxyService.send({}, 'product', 'GET', filter, function (objectsDTO) {
            if (objectsDTO) {
                var products = self.getProductsArrayFromObjectsDTO(objectsDTO);
                console.log('all products ', products);
                callback(products, model);
            }
        });
        
    }

    self.getProductsArrayFromObjectsDTO = function (objectsDTO) {
        var products = [];

        _.forEach(objectsDTO, function (objectDTO) {
            var mainBlobUrl = objectDTO.blobs.filter(function (x) { return x.main == true })[0];
            products.push({
                id: objectDTO.id,
                src: mainBlobUrl == null ? '' : decodeURIComponent(mainBlobUrl.url),
                title: objectDTO.name
            });
        });
        return products;
    }

    self.getSubcategoriesArrayFromObjectsDTO = function (objectsDTO) {
        var categories = [];

        _.forEach(objectsDTO, function (objectDTO) {
            var category = new Category(objectDTO);
            categories.push(category);
        });

        return categories;
    }

    self.save = function (callback) {
        callback = defined(callback) ? callback : function () { };

        var categoryCallback = function (response) {
            if (response && response.length) {
                if (self.parentCategory() != null && self.parentCategory().id == "" && response[0].children && response[0].children.length) {
                    self.parentCategory().id = response[0].id;
                    self.id = response[0].children[0].id;
                }
                else {
                    self.id = response[0].id;
                }
            }

            callback(response);
        }

        var dtoToSave = self.toDTO();

        if (self.parentCategory() != null) {
            self.parentCategory().categories().push(self);

            if (self.parentCategory().id == "") {
                dtoToSave = self.parentCategory().toDTO();
            }
        }

        saveEntityData('category', dtoToSave, categoryCallback);
    }

    self.init(data);

    return self;
}

var Order = function(data) {
    var self = this;
    self.id = '';
    self.number = '';
    self.statusId = '';
    self.products = [];
    self.shippingAddress = '';
    self.isPaid = '';
    self.isShipped = '';
    self.paymentStatus = '';
    self.paymentMethod = 'PayPal';
    self.history = [];
    self.taxCost = 0;
    self.shippingCost = 0;
    self.subTotal = 0;
    self.total = 0;
    self.user = null;
    self.userProfile = null;
    self.currency = '';

    self.init = function (data) {
        if (data) {
            self.id = data.id;
            self.number = data.number;
            self.statusId = data.statusId;
            self.products = [];
            self.shippingAddress = data.shippingAddress;
            _.forEach(data.products,
                function (item) {
                    var product = item.product ? new Product(item.product) : null;
                    if (product) self.currency = product.currency;
                    self.products.push({
                        id: item.id,
                        product: product,
                        count: item.count,
                        calcPrice: item.calcPrice ? parseFloat(item.calcPrice) : 0,
                        options: Product.initOptions(item.attributes)
                    });
                });
            self.userProfile = data.userProfile;
            if (data.userProfile) {
                self.user = new User(data.userProfile);
            }
            self.isPaid = data.isPaid;
            self.isShipped = data.isShipped;
            self.paymentStatus = data.paymentStatus;
            self.history = [];
            _.forEach(data.history,
                function (item) {
                    self.history.push({
                        date: new Date(item.date),
                        status: item.status
                    });
                });
            self.taxCost = data.taxCost || 0;
            self.shippingCost = data.shippingCost || 0;
            self.subTotal = data.subTotal || 0;
            self.total = data.amount || 0;
        }        
    }   

    self.toDTO = function () {
        var products = [];
        _.forEach(self.products,
            function(item) {
                products.push({
                    id: item.id,
                    attributes: Product.optionsToDto(item.options),
                    count: item.count
                    //product: item.product ? item.product.toDTO() : null
                });
            });
        var objectDTO = {
            number: self.number,
            statusId: self.statusId,            
            shippingAddress: self.shippingAddress,
            userProfile: self.userProfile,
            products: products,
            isPaid: self.isPaid,
            isShipped: self.isShipped,
            paymentStatus: self.paymentStatus,
            history: self.history
        }
        if (self.id) {
            objectDTO.id = self.id;
        }
        return objectDTO;
    }

    self.getAllStatuses = function () {
        return UI.store.getOrderStatuses();
    }

    self.updateOrderStatus = function (callback) {
        UI.store.updateOrder({ id: self.id, statusId: self.statusId }, callback);        
    }

    self.init(data);
    return self;
}


Product.view = function (data) {
    viewEntity('product', data);
}

Product.update = function (data, updatedProperties) {
    updateEntityData('product', data, updatedProperties);
}

Product.batchUpdate = function (data, callback) {
    batchUpdateEntities('product', data, callback);
}

Product.handleGridRows = function (select) {
    handleGridRows('product', select);
}

var handleGridRows = function (entityName, select) {
    var gridRows = $(".grid-mvc").find("tbody").find("tr");
    gridRows.each(function (e) {
        var selectEntity = $(this).find('.select-' + entityName +' input[type="checkbox"]');
        var checked = selectEntity.prop('checked');
        var handleClick = (select && !checked) || (!select && checked)
        if (handleClick) {
            selectEntity.click();
        }
    });
}

Product.duplicate = function (data, callback) {
    callback = defined(callback) ? callback : function() {};
    duplicateEntity('product', data, callback);
}

Product.delete = function (data, callback) {
    callback = defined(callback) ? callback : function () { };
    deleteEntity('product', data, callback);
}

Category.view = function (data) {
    viewEntity('category', data);
}

Order.view = function (data) {
    viewEntity('order', data);
}

Category.delete = function (data, callback) {
    callback = defined(callback) ? callback : function () { };
    deleteEntity('category', data, callback);
}

Order.delete = function (data, callback) {
    callback = defined(callback) ? callback : function () { };
    deleteEntity('order', data, callback);
}
var batchUpdateEntities = function (entityName, data, callback) {
    callback = defined(callback) ? callback : function () { };
    ProxyService.send({ data: data, isArrayBodyRequest: true }, entityName, 'PUT', {}, callback);
}

var getEntityData = function (entityName, id, callback) {
    callback = defined(callback) ? callback : function () { };
    ProxyService.send({ id: id }, entityName, 'GET', {}, function (entities) {
        if (entities && entities.length > 0) {
            var entity = entities[0];
            callback(entity, entityName);
        }
    });
}

var updateEntityData = function (entityName, data, updatedProperties) {

    var callback = function (data, entityName) {
        var entityObject = getEntityObject(entityName, data);

        if (entityObject) {
            var objectDTO = entityObject.toDTO();

            if (typeof updatedProperties == 'object') {
                $.each(updatedProperties, function (key, value) {
                    if (objectDTO.hasOwnProperty(key)) {
                        objectDTO[key] = updatedProperties[key];
                    }
                });
                saveEntityData(entityName, objectDTO);
            }
        }
    }

    if (typeof data == 'object') {
        callback(data, entityName);
    }
    else {
        getEntityData(entityName, data, callback);
    }
}

var saveEntityData = function (entityName, objectDTO, callback) {
    callback = defined(callback) ? callback : function () { };
    var operationType = objectDTO.id == null ? 'POST' : 'PUT';
    ProxyService.send(objectDTO, entityName, operationType, {}, function (objectsDTO) {        
		callback(objectsDTO);
    });
}

var duplicateEntityData = function (entityName, objectDTO, callback) {
    callback = defined(callback) ? callback : function() {};    
    saveEntityData(entityName, {
        id: objectDTO.id,
        webApiAction: "duplicate"
    }, callback);
}

var deleteEntityData = function (entityName, id, callback) {
    callback = defined(callback) ? callback : function() {};
    ProxyService.send({ id: id }, entityName, 'DELETE', {}, callback);
}

var viewEntity = function (entityName, data) {
    event.stopPropagation();

    var callback = function (entity, entityName) {

        var modelName = getEntityModelName(entityName);
        var modalComponent = UI.basicComponentRepository.getAll().where({ name: modelName })[0];
        var compiledTemplate = TemplateFactory.templateFor({ proto: modalComponent }, VIEWER_TEMPLATE).compiledTemplate;
        $('#manage-store-products-tab-content').html("");

        $('#manage-store-products-tab-content').html(compiledTemplate).promise().done(function (element) {
            setTimeout(function (modelName, entity) {
                ko.cleanNode($('#manage-store-products-tab-content')[0]);
                var model = UI.getViewModel(modelName, entity);
                ko.applyBindings(model, $('#manage-store-products-tab-content')[0]);
            }, 0, modelName, entity);
        });
    }

    if (typeof data == 'object') {
        callback(data, entityName);
    }
    else {
        getEntityData(entityName, data, callback);
    }

}

var duplicateEntity = function (entityName, data, externalCallback) {
    event.stopPropagation();
    externalCallback = defined(externalCallback) ? externalCallback : function () { };

    var callback = function (entity, name) {
        var entityObject = getEntityObject(name, entity);

        if (entityObject) {
            var objectDTO = entityObject.toDTO();
            duplicateEntityData(entityName, objectDTO, externalCallback);
        }
    }

    if (typeof data == 'object') {
        callback(data, name);
    }
    else {
        getEntityData(entityName, data, callback);
    }

}

var deleteEntity = function (entityName, data, callback) {
    event.stopPropagation();
    callback = defined(callback) ? callback : function() {};
    var id = typeof data == 'object' ? data.id : data;
    if (id) {
        deleteEntityData(entityName, id, callback);
    }
}

var getEntityObject = function (entityName, data) {
    switch (entityName) {
        case "product":
            return new Product(data);
        case "category":
            return new Category(data);
        case "order":
            return new Order(data);
        default:
            return data;
    }
}

var getEntityModelName = function (entityName) {
    switch (entityName) {
        case "product":
            return '#manage-store-products-product';
        case "category":
            return '#manage-store-products-category';
        case "order":
            return '#manage-store-products-order';
        default:
            return entityName;
    }
}
var FormMail = function (data) {
    var self = this;
    self.id = '';
    self.sender = '';
    self.date = '';
    self.message = '';
    self.subject = '';
    self.attachments = ko.observableArray([]);

    self.init = function (data) {
        if (data) {
            self.id = data.controlFormMailId || '';
            self.sender = data.sender || 'Anonymous';
            self.date = moment(data.date).format("DD.MM.YYYY hh:mm A");
            self.message = data.message || '';
            self.subject = data.subject || '';
            self.initAttachment(self.message);
        }
    }

    self.initAttachment = function (message) {
        try {
            var reg = new RegExp("<input type='hidden' name='attachments' value='(.+)'/>");
            var found = message.match(reg);
            self.attachments(found[1].split(';'));
        } catch (e) {
            console.error(e);
        }
    }

    self.init(data);
    return self;
}

var LogItem = function(data) {
    var self = this;
    self.componentId = '';
    self.controlId = '';
    self.action = '';
    self.parentId = '';

    self.init = function (data) {
        if (data) {
            self.componentId = data.componentId;
            self.controlId = data.controlId;
            self.action = data.action;
            self.parentId = data.parentId;
        }
    }

    self.toString = function() {
        var componentName = UI.basicComponentRepository.lookupData({ id: self.componentId }).displayName;
        var parentComponent = UI.siteComponentRepository.lookupData({ id: self.parentId });        
        var parent = parentComponent.componentId === PAGE ? 'page "' + parentComponent.getProperty(TITLE).value + '"' : 'component "' + parentComponent.proto.displayName + '"';
        var action = '';
        switch(self.action) {
            case 'add':
                action = 'added to';
                break;
            case 'remove':
                action = 'removed from';
                break;
        }
        return 'Component "' + componentName + '" ' + action + ' ' + parent;
    }

    self.init(data);
    return self;
};
var templateDataTmp = null;
var DataService = function (app, onServiceInitialized) {    
		var self = this;
        var data = [];
        var controls = [];
        var templateData = null;
        var wrapper = null;
        var jsComponents = null;
        //getting components data
		self.getData = function(){
			return templateData.components;
		}
        //getting controls data
        self.getControls = function(){
            return templateData.controls;
        }
        self.getDeviceObjects = function() {
            return templateData.deviceObjects.devices;
        }
        //getting template data
        self.getTemplateData = function() {
            return {
                templateVersionId: templateData.templateVersionId,
                templateId: templateData.templateId,
                entryControlId: null,
                displayName: templateData.displayName,
                name: templateData.name,
                templateTypeId: templateData.templateTypeId,
                actionType: templateData.actionType,
                categories: templateData.categories,
                devices: templateData.devices || []
            };
        }
        //setting template data
        self.setTemplateData = function (templateVersionId, templateId, displayName, name, templateTypeId, categories, devices)
        {
            templateData.templateVersionId = templateVersionId;
            templateData.templateId = templateId;
            templateData.displayName = displayName;
            templateData.name = name;
            templateData.templateTypeId = templateTypeId;
            templateData.categories = categories;
            templateData.devices = devices || [];
        }

    //todo: add check if it up version
    var isLastVersionInLocalStorage = function(templateValues) {
        return window.lastEditorTemplateVersionId === templateValues["last-saved-version"] && templateValues["save-result"] && templateValues["last-saved-model"];
    }

    var loadComponentsFromBlobOrStorage = function (callback) {
        if (window.lastJsComponentArrayHash !== LocalStorageService.GetItem("last-js-component-array-hash") || !LocalStorageService.GetItem("last-components-cashe")) {
            var scriptComponents = document.createElement("script");
            scriptComponents.setAttribute("src", window.componentsUrl + "?hash=" + window.lastJsComponentArrayHash);
            scriptComponents.onerror = function (e) {
                console.log("Components were not loaded from blob.");
            };

            scriptComponents.onload = function (e) {
                if (window.componentsArray) {
                    LocalStorageService.SetItem("last-js-component-array-hash", window.lastJsComponentArrayHash);
                    LocalStorageService.SetItem("last-components-cashe", window.componentsArray);
                    templateData.components = window.componentsArray;
                }
                if (typeof (callback) == "function") {
                    callback();
                }
            }
            document.head.appendChild(scriptComponents);
        } else {
            templateData.components = LocalStorageService.GetItem("last-components-cashe");

            if (typeof (callback) == "function") {
                callback();
            }
        }
    }

    var loadDataFromLoadMethodOldLogic = function() {
        Application.addLocker();
        var result = undefined;
        $.ajax({
            type: "POST",
            url: "/Editor/Load",
            data: { templateId: UI.getSetting("templateId") },
            async: true,
            dataType: "json",
            beforeSend: function(xhr) {
                xhr.setRequestHeader("Content-Encoding", "gzip");
            },
            success: function(response, status, xhr) {
                //retrieving data async and applying callback
                wrapper = JSON.parse(response);
                if (wrapper.errorCode == 0 && wrapper.templateVersion != null) {
                    templateData = wrapper.templateVersion;

                    LocalStorageService.SetItem("last-js-component-array-hash", window.lastJsComponentArrayHash);
                    LocalStorageService.SetItem("last-components-cashe", templateData.components);

                    var token = xhr.getResponseHeader('Authorization');
                    if (token && UI.getSetting("templateId")) {
                        LocalStorageService.SetItem(UI.getSetting("templateId") + "_token", token);
                    }
                    onServiceInitialized(self, app);
                    Application.removeLocker();
                } else {
                    Application.removeLocker();
                    Helpers.showModalDialog("Error", wrapper.errorMessage, TEMPLATE_MASTER_URL);
                }

            }
        });
    }

    var loadModelFromLocalStorage = function (templateValues) {
        if (templateValues["last-saved-model"]) {
            templateData = templateValues["last-saved-model"];
            loadComponentsFromBlobOrStorage(function() {
                onServiceInitialized(self, app);
            });
        }
    }

    var loadModelFromAutosave = function(templateValues) {
        if (templateValues["auto-saved-model"]) {
            templateData = templateValues["auto-saved-model"];
            loadComponentsFromBlobOrStorage(function () {
                onServiceInitialized(self, app);
            });
        }
    }

    var loadModelFromLastSavedModel = function(templateValues) {
        if (templateValues["save-result"] === false) {
            templateData = templateValues["last-saved-model"];
            loadComponentsFromBlobOrStorage(function () {
                onServiceInitialized(self, app);
            });
        }
    }

    var loadModelFromBlob = function(templateValues, callback, fallback) {
        if (window.lastEditorTemplateVersionId !== templateValues["last-saved-version"]) {
            var scriptModel = document.createElement("script");
            scriptModel.setAttribute("src", window.lastTemplateVersionDataUrl);
            scriptModel.onerror = function(e) {
                console.log("Model was not loaded from blob.");
                if (typeof (fallback) == "function") {
                    fallback();
                }
            };

            scriptModel.onload = function(e) {
                templateData = window.templateModel || null;
                if (typeof (callback) == "function") {
                    callback();
                }
            }

            document.head.appendChild(scriptModel);
        } else {
            templateData = templateValues["last-saved-model"];

            if (typeof (callback) == "function") {
                callback();
            }
        }
    }    

    var loadData = function() {
        console.log("loadData");
        if (UI.settings.isComponentEditor) {
            loadDataFromLoadMethodOldLogic();
        } else if (UI.getSetting('ispublished') || window.templateModel) {
            templateData = window.templateModel;
            if (window.componentsArray && !templateData.components) {
                templateData.components = window.componentsArray;
            }
            onServiceInitialized(self, app);
        } else {
            IndexedDBService.getLocalTemplateParam(null,
                function(templateValues) {
                    //if save with error or site crashed
                    if (templateValues["auto-saved-model"] &&
                        confirm("The last time you worked on this site, the session crashed. " +
                            "Do you want to load the last saved local copy? Click 'OK' to download a local copy of the data or Cancel' to download data from the server.")
                    ) {
                        loadModelFromAutosave(templateValues);
                    } else if (templateValues["save-result"] === false &&
                        confirm("The last time you worked " +
                            "with this site, the data was not correctly stored on the server. Do you want to load the last saved local copy? " +
                            "Click 'OK' to download a local copy of the data or Cancel' to download data from the server.")
                    ) {
                        loadModelFromLastSavedModel(templateValues);
                    } else {
                        //version in local storage equal to server version
                        if (isLastVersionInLocalStorage(templateValues)) {
                            loadModelFromLocalStorage(templateValues);
                        } else {
                            //add script for loading model from blob
                            loadModelFromBlob(templateValues,
                                function() {
                                    if (templateData != null) {
                                        //add script for loading components from blob
                                        loadComponentsFromBlobOrStorage(function() {
                                            onServiceInitialized(self, app);
                                        });
                                    } else {
                                        //fallback load from server
                                        loadDataFromLoadMethodOldLogic();
                                    }
                                },
                                function() {
                                    //fallback load from server
                                    loadDataFromLoadMethodOldLogic();
                                });
                        }
                    }
                });
        }
    }


    try {
        loadData();
    } catch (ex) {
        loadDataFromLoadMethodOldLogic();
        console.log('no templateModel...' + ex);
    } 
}


//creating class tree for components and controls
var EntitiesConstructorService = function(){
    var self = this;
    self.constructBasicEntities = function (data) {
		    var components = [];
			data.forEach(function(item){			
			    var component = new BasicComponent(item);
                //load js for components
				components.push(component);
			});
			return components;
		}
        self.constructExistingEntities  = function(data, isNewId){
            isNewId = isNewId || false;
            var components = [];
            data.forEach(function(item){
                var component = new Component().createNew(item,isNewId);
                components.push(component);
            });
            return components;
        }
}

var ComponentService = function(componentRoot){
    var self = this;
    //adding component to form
    self.addComponentToForm = function(component, newComponent){
        newComponent = newComponent || false;

        var parentElement = null;
        var rootDomNode = null;
        var rootDomId = null;
        var domId = null;

        rootDomId = newComponent ? UI.pager.getCurrentPageId() : null;

        var compiledTemplate = TemplateFactory.templateFor(component,VIEWER_TEMPLATE).compiledTemplate;

        if (component.parentComponent == null) {
            if (rootDomId == null) {
                domId = componentRoot;
            } else {
                domId = "#" + rootDomId;
                parentElement = UI.siteComponentRepository.lookupData({ id: rootDomId });
            }
        } else {
            domId = component.parentComponent.getUISelector();
            if (component.proto.name === STORE_GALLERY_PRODUCT) {
                domId += '>.gallery-products';
            } else if (component.parentComponent.proto.name === STORE_GALLERY_PRODUCT) {
                domId += '>.content';
            } else if (component.proto.name === STORE_CART_CHECKOUT) {
                domId += '>.checkout-btn';
            }
            parentElement = component.parentElement;
        }
        
        $(domId).append(compiledTemplate);
        if (newComponent)
        {
            Resizer.checkMinSizeHeaderContainer($('.header')[0]);
            Resizer.checkMinSizePageContainer($('#' + UI.pager.getCurrentPageId())[0]);
        }        
        return parentElement;
    }

    // add modal content
    self.addModalContentToForm = function (component, type, data) {        
        var modalComponent = UI.basicComponentRepository.getAll().where({ name: type })[0];
        var compiledTemplate = TemplateFactory.templateFor({ proto: modalComponent}, VIEWER_TEMPLATE, component).compiledTemplate;
        $('#component-modal').html('');
        $('.modal-backdrop.fade.in').length ? $('.modal-backdrop.fade.in')[0].remove(): '';
        $('#component-modal').html(compiledTemplate);
        var modal = $('#component-modal .modal');
        modal.modal('show');
        modal.on('shown.bs.modal',
            function() {
                //bind events
                ViewerEventsFactory.attachEvents(component, type, data);
            });
    };

    //recursive function to build controls tree and add them to dom
    self.buildExistingComponents = function (existingComponents, isAddToRepository) {
        var selectedPage = window.location.hash.substring(1).toLowerCase();

        if (!defined(isAddToRepository)) isAddToRepository = true;

        var renderTreeTimeout = function (tree, isAddComponentToForm)
        {
            setTimeout(function () {
                renderTree(tree, isAddComponentToForm);
            }, 10000);
        }

        var renderTree = function (tree, isAddComponentToForm) {
            if (!defined(isAddComponentToForm)) isAddComponentToForm = true;
            if (UI.getSetting("isrenderpublish")) isAddComponentToForm = true;
            if (defined(tree.children) && tree.children.any()) {
                tree.children.forEach(function (node) {
                    var isHide = node.getProperty('hide-component');
                    if (isHide == null || !isHide.value.toBoolean()) {
                        if (isAddComponentToForm || node.componentId === PAGE) {
                            //todo: move to action (not init UI.componentService)
                            self.addComponentToForm(node);
                            node.viewer();
                        }
                        if (UI.getSetting("ispreview")) {
                            if (node.componentId == PAGE) {
                                isAddComponentToForm = false;
                            }
                        }

                        if (!node.complexComponent) {
                            if (node.componentId !== PAGE) {
                                // if not page, continue recursion
                                renderTree(node, isAddComponentToForm);
                            } else {
                                var home = node.getProperty('home');
                                var currentNodeTitle = node.getProperty('name').value.toLowerCase();
                                // render home and selected page first
                                if (home != null && home.value == 'true' ||
                                    selectedPage === currentNodeTitle) {
                                    renderTree(node, isAddComponentToForm);
                                } else {
                                    renderTreeTimeout(node, isAddComponentToForm)
                                }
                            }
                        }
                    }
                });
            }
        }
        existingComponents.forEach(function(item) {
            var basicComponentsFiltered = UI.basicComponentRepository.getAll().where({ id: item.name });
            if (basicComponentsFiltered.any()) {
                var componentTree = new Component().createFromExisting(item);
                
                if (isAddToRepository) {
                    UI.siteComponentRepository.add(componentTree);
                }

                var isHide = componentTree.getProperty('hide-component');
                if (isHide == null || !isHide.value.toBoolean()) {
                    self.addComponentToForm(componentTree);
                    componentTree.viewer();
                    renderTree(componentTree);
                }                
            }
        });
        UI.unsavedChanges = false;
    }

    //recursive function to build controls tree and add them to dom
    self.correctComponentsBeforeCloned = function (components, rootElement) {
        if (components.any()) {
            components.forEach(function(item) {
                item.parentComponent = rootElement;
                item.id = Guid.new();
                if (defined(item.children) && item.children.any()) {
                    self.correctComponentsBeforeCloned(item.children, item);
                }
            });
        }
        return components;
    }

    //create empty template for Component Editor
    self.createTemplateForEditor = function () {
        var body = UI.basicComponentRepository.lookupData({ displayName: "body" });
        var main = UI.basicComponentRepository.lookupData({ displayName: "main" });
        var page = UI.basicComponentRepository.lookupData({ displayName: "page" });

        var bodyComponent = new Component().createNew(body, true);
        var mainComponent = new Component().createNew(main, true, bodyComponent);
        var pageComponent = new Component().createNew(page, true, mainComponent);

        mainComponent.setProperty('top', '0px');

        UI.siteComponentRepository.add(bodyComponent);
        UI.siteComponentRepository.appendTo(mainComponent, bodyComponent);
        UI.siteComponentRepository.appendTo(pageComponent, mainComponent);

        this.addComponentToForm(bodyComponent);
        this.addComponentToForm(mainComponent);
        this.addComponentToForm(pageComponent);
    }
}

var ActionService = function () {
    var self = this;

    //init data
    self.actionsData = {};

    self.addAction = function (type) {
        //add action with empty array
        self.actionsData[type] = [];
    };

    self.isHaveAction = function(component, type) {
        return component.proto && component.proto.actions && component.proto.actions[type];
    }

    //push component to action data
    self.addToActionData = function (component, isRunForAll) {
        //not add gallery items (it has not template)
        if (component.parentComponent == null || !Helpers.isGalleryComponent(component.parentComponent)) {
            _.forEach(_.keys(self.actionsData),
                function (key) {
                    if (self.isHaveAction(component, key)) {
                        self.actionsData[key].push(component);
                    }
                });
            if (isRunForAll) {
                _.forEach(component.children,
                    function (item) {
                        self.addToActionData(item, isRunForAll);
                    });
            }
        }
    };

    //remove component from action data
    self.removeFromActionData = function (component, isRunForAll) {
        _.forEach(_.keys(self.actionsData), function (key) {
            if (self.isHaveAction(component, key)) {
                self.actionsData[key] = _.without(self.actionsData[key], component);
            }            
        });
        if (isRunForAll) {
            _.forEach(component.children, function (item) {
                self.removeFromActionData(item, isRunForAll);
            });
        }
    }

    self.runActionForComponent = function(component, type, isRunForAll, data) {
        if (self.isHaveAction(component, type)) {
            component.proto.actions[type](component, data);            
        }
        if (isRunForAll) {
            _.forEach(component.children, function (item) {
                self.runActionForComponent(item, type, isRunForAll);
            });
        }
    }

    self.runAction = function (type) {
        //each data for type
        self.actionsData[type].forEach(function (item) {
            //run action
            self.runActionForComponent(item, type);
        });
    }
}
;

Array.prototype.firstOrDefault = function () {
    if (this.length > 0) {
        return this[0];
    } else {
        return null;
    }
}

Array.prototype.any = function () {
    return this.length > 0;
}
//getting index of element by some expression(e.g. for element removal)
Array.prototype.indexOfByProperty = function (expression) {
    var itemIndex = -1;
    var key = Object.keys(expression).firstOrDefault();
    var value = expression[key];
    this.forEach(function (item, index) {
        if (item.hasOwnProperty(key) && item[key] === value) {
            itemIndex = index;
        }
    });
    return itemIndex;
}
//adds a set of object to array
Array.prototype.addRange = function (range) {
    this.push.apply(this, range);
};

//array ordering by expression implementation
Array.prototype.orderBy = function (expression) {
    var comparison = function (obj1, obj2) {
        if (obj1.hasOwnProperty(expression) && obj2.hasOwnProperty(expression)) {
            if (obj1[expression] < obj2[expression]) {
                return -1;
            }
            if (obj1[expression] > obj2[expression]) {
                return 1;
            }
        }
        return 0;
    }
    return this.sort(comparison);
}
Array.prototype.orderByDescending = function (expression) {
    var comparison = function (obj1, obj2) {
        if (obj1.hasOwnProperty(expression) && obj2.hasOwnProperty(expression)) {
            if (obj1[expression] > obj2[expression]) {
                return -1;
            }
            if (obj1[expression] < obj2[expression]) {
                return 1;
            }
        }
        return 0;
    }
    return this.sort(comparison);
}
//getting array max value by expression(must me integer comparator)
Array.prototype.max = function (expression) {
    var max = null;
    this.forEach(function (item, index) {
        if (item.hasOwnProperty(expression)) {
            if (_.isNumber(_.parseInt(item[expression]))) {
                if (max != null) {
                    if (_.parseInt(item[expression]) > _.parseInt(max[expression])) {
                        max = item;
                    }
                } else {
                    max = item;
                }
            }
        }
    });
    return max;
}
//removing elements from array
Array.prototype.remove = function (from, to) {
    var rest = this.slice((to || from) + 1 || this.length);
    this.length = from < 0 ? this.length + from : from;
    return this.push.apply(this, rest);
};
//querying array for a set of element, that match passed expression
Array.prototype.where = function (expression) {
    var results = [];
    var key = Object.keys(expression).firstOrDefault();
    var value = expression[key];
    this.forEach(function (item) {
        if (item.hasOwnProperty(key) &&
            (item[key] === value || (typeof item[key] === "function" && item[key]() === value))) {
            results.push(item);
        }
    });
    return results;
}
Array.prototype.whereNot = function (expression) {
    var results = [];
    var key = Object.keys(expression).firstOrDefault();
    var value = expression[key];
    this.forEach(function (item) {
        if (item.hasOwnProperty(key) &&
            (item[key] !== value || (typeof item[key] === "function" && item[key]() !== value))) {
            results.push(item);
        }
    });
    return results;
}

String.prototype.capitalize = function () {
    return this.charAt(0).toUpperCase() + this.slice(1);
}
;
/// <reference path="repositories.js" />
var SiteComponentRepository = function(){
    var self = this;
    var siteComponents = [];
    self.add = function(component){
        siteComponents.push(component);
    }
    //append function
    self.appendTo = function(source,target){
        if (!defined(source.children)){
            target.children = [];
        }
        target.children.push(source);
        source.parentComponent = target;
        Helpers.bloggingPostCreate(source);
    }
    self.getAll = function(){
        return siteComponents;
    }
    self.getBody = function () {
        return self.lookupData({ displayName: "body" });;
    }
    self.addRange = function(range){
        siteComponents.addRange(range);
    }
    //remove function
    self.remove = function (expression, startPoint) {
        //todo: special algorithm for STORE
        var removed = null;
        startPoint = defined(startPoint)?startPoint:siteComponents;
        var key = Object.keys(expression);
        var value = expression[key];
        var lookupResult = self.lookupData(expression,startPoint);
        if (lookupResult!=null){
            if (lookupResult.parentComponent != null) {
                //getting element parent
                var parentComponent = self.lookupData({ id: lookupResult.parentComponent.id }, startPoint);
                //getting element index
                var itemIndex = parentComponent.children.indexOfByProperty({id:lookupResult.id});
                if (itemIndex!=-1){
                    removed = parentComponent.children[itemIndex];
                    parentComponent.children.remove(itemIndex);
                }
            }
        }
        if (removed) {
            Helpers.bloggingPostDelete(removed);
            UI.actionService.removeFromActionData(removed, true);
            UI.actionService.runActionForComponent(removed, ACTION_REMOVE_FROM_FORM, true);            
        }
        return removed;
    }
    //moving element in model
    self.move = function(fromExpression,toExpression){
        var fromKey = Object.keys(fromExpression);
        var toKey = Object.keys(toExpression);
        var fromValue = fromExpression[fromKey];
        var toValue = toExpression[toKey];
        var sourceElement = self.lookupData({id:fromValue});
        var targetElement = self.lookupData({id:toValue});
        self.remove({id:sourceElement.id});
        self.appendTo(sourceElement,targetElement);
    }
    //recursive function to search one result by expression
    self.lookupData= function(expression,startPoint){
        startPoint = defined(startPoint)?startPoint:siteComponents;
        var key = Object.keys(expression);
        var value = expression[key];
        var result = null;
        var found = false;
        var search = function(obj){
            if (!found){
                if (Array.isArray(obj)){
                    obj.forEach(function(node){
                        if (node[key]===value){
                            result = node;
                            found = true;
                        }
                        if (defined(node.children)){
                            search(node.children);
                        }
                    });
                }else{
                    if (defined(obj.children)){
                        search(obj.children);
                    }
                }
            }
        }
        search(startPoint);
        return result;
    }

    self.lookupDataByProperty = function (expression, startPoint) {        
        startPoint = defined(startPoint) ? startPoint : siteComponents;
        var key = Object.keys(expression).firstOrDefault();
        var value = expression[key];
        var result = [];
        var search = function (obj) {
            if (Array.isArray(obj)) {
                obj.forEach(function (node) {                    
                    var val = node.getProperty(key);
                    if (val != null && val.value == value) {
                        result.push(node);
                    }
                    if (defined(node.children)) {
                        search(node.children);
                    }
                });
            } else {
                if (defined(obj.children)) {
                    search(obj.children);
                }
            }
        }
        search(startPoint);
        return result;
    }

    self.lookupDataByPropertyName = function (property, startPoint) {
        startPoint = defined(startPoint) ? startPoint : siteComponents;
        var result = null;
        var found = false;
        var search = function (obj) {
            if (!found) {
                if (Array.isArray(obj)) {
                    obj.forEach(function(node) {
                        var val = node.getProperty(property);
                        if (val != null) {
                            result = node;
                            found = true;
                        }
                        if (defined(node.children)) {
                            search(node.children);
                        }
                    });
                } else {
                    if (defined(obj.children)) {
                        search(obj.children);
                    }
                }
            }
        }
        search(startPoint);
        return result;
    }

    self.lookupDataSetByPropertyName = function (property, startPoint) {
        startPoint = defined(startPoint) ? startPoint : siteComponents;
        var result = [];
        var search = function (obj) {
            if (Array.isArray(obj)) {
                obj.forEach(function (node) {
                    var val = node.getProperty(property);
                    if (val != null) {
                        result.push(node);
                    }
                    if (defined(node.children)) {
                        search(node.children);
                    }
                });
            } else {
                if (defined(obj.children)) {
                    search(obj.children);
                }
            }
        }
        search(startPoint);
        return result;
    }

    self.checkExistingRule = function (type, page) {
        var pageComponent = self.lookupData({ id: page });
        var isService = pageComponent && pageComponent.proto.name === PAGE_COMPONENT
            ? pageComponent.getProperty(ISSERVICE).value.toBoolean()
            : false;
        if (!isService) {
            switch (type) {
            case HEADERTEXT_SEO:
                return HeaderTextSeoFind(page);
            case SIGNIN:
            case STORE:
            case STORE_GALLERY:
            case STORE_CART_LINK:
            case STORE_CATEGORIES:
                return SimpleFind(type);
            case CAPTCHA:
                return FindInChildren(type, pageComponent);
            default:
                return true;
            }
            function HeaderTextSeoFind(page) {
                function checkChild(child) { return child.proto.name === HEADERTEXT_SEO };
                if (_.findIndex(self.lookupData({ displayName: 'header' }).children, checkChild) === -1 &&
                    _.findIndex(self.lookupData({ displayName: 'footer' }).children, checkChild) === -1) {
                    if (page) {
                        if (_.findIndex(self.lookupData({ id: page }).children, checkChild) === -1) {
                            return true;
                        } else {
                            Application.showEmptyDialog('Already Exist', "You already have a Header for this page. You can have only one Header per page.");
                            return false;
                        }
                    } else {
                        return true;
                    }
                } else {
                    Application.showEmptyDialog('Already Exist', "You already have a Header for this page. You can have only one Header per page.");
                    return false;
                }
            }            
            function SimpleFind(name) {
                if (self.lookupData({ displayName: name })) {
                    Application.showEmptyDialog('Already Exist', 'You can add only one component to the site.');
                    return false;
                } else {
                    return true;
                }
            }
            function FindInChildren(type, pageComponent) {
                var index = _.findIndex(pageComponent.children, function (child) { return child.proto.name === type });
                if (index === -1) {
                    return true;
                } else {
                    Application.showEmptyDialog('Already Exist', 'You can add only one captcha to the form.');
                    return false;
                }
            }
        } else {
            Application.showEmptyDialog('Not Allowed', 'You cann\'ot add component to the service page');
            return false;
        }
        
    }

    //recursive function to search data set by expression
    self.lookupDataSet = function (expression, startPoint) {
        startPoint = defined(startPoint) ? startPoint : siteComponents;
        var key = Object.keys(expression);
        var value = expression[key];
        var result = [];
        var search = function (obj) {
                if (Array.isArray(obj)) {
                    obj.forEach(function (node) {                        
                        if (node[key] === value) {
                            result.push(node);
                        }
                        if (defined(node.children)) {
                            search(node.children);
                        }
                    });
                } else {
                    if (defined(obj.children)) {
                        search(obj.children);
                    }
                }            
        }
        search(startPoint);
        return result;
    }

    //resolving circular dependecies before parsing model to json
    self.resolveDependecies = function(){
        var components = _.cloneDeep(siteComponents);
        var prepare = function(obj){
            if (Array.isArray(obj)) {
                obj.forEach(function (node) {
                    var parentId = node.parentComponent != null ? node.parentComponent.id : null;
                    node.setProperty("parentId",parentId);                    
                    if (defined(node.children)) {
                        prepare(node.children);
                    }
                });
            }else{
                if (defined(obj.children)) {
                    prepare(obj.children);
                }
            }
        }
        prepare(components);
        var resolve = function(obj){
            if (Array.isArray(obj)) {
                obj.forEach(function (node) {                    
                    node.parentComponent = undefined;
                    node.proto = undefined;
                    node.componentId = node.name;
                    node.name = undefined;
                    node.controlId = node.id;
                    node.id = undefined;
                    node.isDockable = undefined;
                    node.isSelectable = undefined;
                    node.isDraggable = undefined;
                    node.displayName = undefined;
                    node.complexComponent = undefined;
                    
                    if (defined(node.children)) {
                        resolve(node.children);
                    }
                });
            }else{
                if (defined(obj.children)) {
                    resolve(obj.children);
                }
            }
        }
        resolve(components);

        return components;
    }
    self.toJson = function(){
        return JSON.stringify(siteComponents,function(key,value){
            if (key=="parentComponent" || key=="proto") {
                return undefined;
            }
            return value;
        });
    }

    self.ajaxJsonRequest = function (url, data, callback) {
        callback = callback || function () { };
                
        $.ajax({
            type: "POST",
            url: url,
            data: { data: data },
            success: function (response) {
                alert("ok");

                if (typeof (callback) == "function") {
                    callback();
                }
            },
            error: function () {
                alert("error");

                if (typeof (callback) == "function") {
                    callback();
                }
            }
        });
    }


    self.save = function (callback) {

        
        self.ajaxJsonRequest('/Editor/Save', self.toJson(), callback);
    }

    self.publish = function (callback) {

        self.ajaxJsonRequest("/Editor/Publish", self.toJson(), callback);
    }

}
var BasicComponentRepository = function(){
    var self = this;
    var basicComponents = [];
    self.add = function(basicComponent){
        basicComponents.push(basicComponent);
    }
    self.getAll = function(){
        return basicComponents;
    }
    self.addRange = function(range){
        basicComponents.addRange(range);
    }
    self.lookupData = function(expression,startPoint){
        startPoint = defined(startPoint)?startPoint:basicComponents;
        var key = Object.keys(expression);
        var value = expression[key];
        var result = null;
        var found = false;
        var search = function(obj){
            if (!found){
            obj.forEach(function(node){
                if (node[key]===value){
                    result = node;
                    found = true;
                }
                if (defined(node.children)){
                    search(node.children);
                }
            });
            }
        }
        search(startPoint);
        return result;
    }
}
;
var EditorApplication = function (params) {
    //application is initialized with params, that may be used in server interaction
    var self = this;

    if (!params.ispreview) {
        //get isAdmin sync
        params.isadmin = Application.IsSystemAdministrator();
        if (window.actionType !== 1 && window.actionType !== 4) {
            $('#history-management').show();
        }
    }

    var applicationSettings = $.extend({}, self.defaultParams, params);    
    UI.setSettings(params);

    //configuring application ui with classes and ids of dom elements
    UI.configure(
        {
            "wrapper": ".wrapper",
            "component-bar": ".component-bar",
            "editor": '#editor',
            "select-wrapper": ".select-wrapper",
            "html": "html",
            "dock-wrapper": ".dock-wrapper"
        });

    if (!UI.getSetting("ispublished") || UI.settings.isComponentEditor) {
        //init UI
        UI.init();
        ClipboardViewModel.init();
        if (!UI.getSetting("ispreview")) {
            Grouping.init();
            if (!UI.settings.isComponentEditor && !UI.settings.isSectionEditor) ace.config.set("basePath", "/Content/Editor/js/libs/ace");

            window.onbeforeunload = function (evt) {
                IndexedDBService.setLocalTemplateParam({
                    "auto-saved-model": null,
                    "auto-saved-time": null
                });
                if (UI.unsavedChanges) {
                    var message = "Note: Any unsaved changes will be lost";
                    if (typeof evt == "undefined") {
                        evt = window.event;
                    }
                    if (evt) {
                        evt.returnValue = message;
                    }
                    return message;
                }
            }
        }
    }

    /*
	 *  subscribe / set own - for afterload notification
	 *  dataservice will publish on '/dataservice/callbackend' internally  
	 */
    self.onLoad = self.onLoad || function (dataService) {
        console.log('application loaded and initialized...' + dataService.toString());
        //todo: temp fix for clean localstorage
        try {
            var templateValuesRegExp = new RegExp(/template-values-(.+)/);
            var removeItems = [];
            for (var i = 0, len = localStorage.length; i < len; ++i) {
                if (templateValuesRegExp.test(localStorage.key(i))) {
                    removeItems.push(localStorage.key(i));
                }
            }
            removeItems.forEach(function(item) {
                localStorage.removeItem(item);
            });
        } catch (e) { }

        $(document).trigger("appstart", [self, UI, dataService]);
    }
    self.eventsystem = eventsystem.subscribe("/dataservice/onServiceInitialized", self.onLoad);
    window.onerror = function (msg, filepath, row, col) {
        console.log("Error: remove locker", msg, filepath + ':' + row + ':' + col);
        Application.removeLocker();
        return true;
    }
    self.dataServiceContext = new DataService(self, EditorApplication.init);
}


EditorApplication.init = function (dataService, app) {
    //init action service
    var actionService = new ActionService();
    actionService.addAction(ACTION_SIGN_IN);
    actionService.addAction(ACTION_SIGN_OUT);
    actionService.addAction(ACTION_ADD_TO_FORM);
    actionService.addAction(ACTION_REMOVE_FROM_FORM);
    UI.injectActionService(actionService);

    var componentService = new ComponentService(UI.getConfigurationValue(WRAPPER));
    var entitiesConstructorService = new EntitiesConstructorService();
    var siteComponentRepository = new SiteComponentRepository();
    var basicComponentRepository = new BasicComponentRepository();
    var injectPostLoadInit = new PostLoadInit();
    var video = new MediaService();

    var data = dataService.getData();
    var controls = dataService.getControls();

    if (!UI.getSetting("ispreview") && !UI.settings.isComponentEditor && !UI.settings.isSectionEditor) {
        var jsonCompare = new JsonCompService(controls);
        UI.injectJsonCompareService(jsonCompare);
    }
    //injecting created repositories for ui usage
    UI.injectRepositories(siteComponentRepository, basicComponentRepository);

    var templateData = dataService.getTemplateData()
    if (!templateData.components) templateData.components = data;
    UI.setTemplateData(templateData);
    UI.ajaxSetup();

    //get devices and set default
    if ((UI.getSetting("ispreview") || UI.getSetting("isThumbnailPreview")) && !UI.settings.isComponentEditor && !UI.settings.isSectionEditor) {
        //get devices and set default
        UI.initDevices(dataService.getDeviceObjects());
    } else {
        var devices = Device.getDevices()
        UI.initDevices(devices);
        var mobileDevice = UI.devices.where({ type: DEVICE_MOBILE_TYPE }).firstOrDefault();
        if (mobileDevice) {
            if (!mobileDevice.isHaveComponentProperties()) {
                Device.setDeviceRequest(mobileDevice);
            }
        }
    }

    UI.injectVideo(video);

    //creating components and adding them to repositories
    basicComponentRepository.addRange(entitiesConstructorService.constructBasicEntities(data));    

    //init store
    var store = siteComponentRepository.lookupData({ displayName: STORE });
    if (store) {
        $('#store').show();
        UI.store = new Store(store);
    }    
    if (UI.settings.isComponentEditor) {
        componentService.createTemplateForEditor();
        var basicComponent = UI.basicComponentRepository.lookupData({ id: UI.settings.componentId });
        var page = UI.siteComponentRepository.lookupData({ displayName: "page" });
        var component = new Component().createNew(basicComponent, true, page);
        var componentWidth = parseInt(component.getProperty('width').value);
        var componentHeight = parseInt(component.getProperty('height').value);
        var topPosition = document.querySelector(".page").offsetHeight / 2 - componentHeight / 2 + 'px';
        var leftPosition = document.querySelector(".page").offsetWidth / 2 - componentWidth / 2 + 'px';
        component.setProperty('top', topPosition);
        component.setProperty('left', leftPosition);
        UI.siteComponentRepository.appendTo(component, page);
        componentService.addComponentToForm(component);
        component.viewer();
        var element = $('#' + component.id);
        element.highlightSelectedElement(component, true);
    } else if (UI.settings.isSectionEditor) {
        componentService.createTemplateForEditor();
        var section = UI.basicComponentRepository.lookupData({ id: UI.settings.componentId });
        var page = UI.siteComponentRepository.lookupData({ displayName: "page" });
        if (UI.settings.actionType == ACTION_TYPE_EDIT_SECTION_TEMPLATE) UI.updateControlFromComponent({ control: page, component: section })
        if (section.children != null) {
            section.children.forEach(child => {
                if (child.isActive) {
                    var component = new Component().createNew(child, true, page);
                    UI.siteComponentRepository.appendTo(component, page);
                    componentService.addComponentToForm(component);
                    component.viewer();
                }                
            });
        }
    } else {
        //creating controls
        componentService.buildExistingComponents(
            entitiesConstructorService.constructExistingEntities(controls));

       

        if (!UI.getSetting("ispreview")) {
            var rulerGuides = new RulerGuides();
            UI.injectRulerGuides(rulerGuides);
        }
    } 

    //init autocompleteAddress
    if (UI.getSetting("ispreview")) {
        var autocompleteAddress = siteComponentRepository.lookupData({ displayName: AUTOCOMPLETE_ADDRESS });
        var evaluateHomeComponent = siteComponentRepository.lookupData({ displayName: EVALUATE_HOME });
        var contactUsComponent = siteComponentRepository.lookupData({ displayName: CONTACT_US });
        if (autocompleteAddress || evaluateHomeComponent || contactUsComponent) {
            UI.autocompleteAddress = new AutocompleteAddress();
        }
    }    
    
    if (UI.getSetting("actionType") == ACTION_TYPE_CREATE_WEB_SITE || UI.getSetting("actionType") == ACTION_TYPE_CREATE_TEMPLATE) {
        var bodyComponent = UI.getBody();        
        bodyComponent.setProperty(GOOGLE_ANALYTICS, "0", true);
        bodyComponent.setProperty(SEARCHENGINE, "1", true);
        bodyComponent.setProperty(SITEMAPXML, "1", true);
    }

    UI.injectServices(componentService);
    UI.injectPostLoadInit(injectPostLoadInit);

    if (!UI.getSetting("ispreview")) {
        UI.handleKeyDown(siteComponentRepository);
        UI.handleKeyUp();
    }

    if (UI.getSetting("ispreview")) {
        if (!UI.getSetting("ispaid")) {
            var service = new AdvertisementService();
        }
        //init auth service
        var authService = new AuthService();
        UI.injectAuthService(authService);
    }

    //creating pager on page controls basis
    if (UI.getSetting("ispublished")) {
        var pageKey = document.location.pathname.replace("\\", "").replace("/", "").replace(/ /g, '').split('/');
        UI.setPager(new Pager(pageKey.shift(), pageKey.shift()));
    }
    else {
        UI.setPager(new Pager());
        //UI.setAnchor(new Anchor());
    }

    UI.setSiteSettings(new SiteSettings());

    if (UI.getSetting("ispreview")) {
        //init only BG
        Designer.refreshBackground();
    } else {
        //get tooltips from database
        UI.getTooltips();
        //creating pager on page controls basis
        UI.setDesigner(new Designer());
    }

    //rendering menus on pager object basis
    UI.renderMenus();

    if (!UI.getSetting("ispreview")) {
        UI.setPaletteId();
    }    
    
    Pager.renderTopMenuSelect();
    app.setPropertiesForComponentFromTemplate = function (componentId, templateForComponentId) {
        var templateForComponent = UI.templatesForComponent.where({ componentId: templateForComponentId });
        var curComponent = siteComponentRepository.lookupData({ id: componentId });
        var element = $('#' + curComponent.id);
        var deviceId = UI.getDevice().getId();
        var templateProperties = templateForComponent[0].properties;
        var struct = templateProperties.map(property => {
            return {
                component: curComponent,
                property: property.name,
                newvalue: property.value,
                oldvalue: curComponent.getProperty(property.name, deviceId)?.value
            }
        });

        var undoredo = function (value, component, property) {
            var element = $('#' + component.id);
            element.css(property, value);
            component.viewer()
        };

        UI.undoManagerAddSimpleArr(struct, undoredo, undoredo);

        templateProperties.forEach(property => {
            if (property.name === 'top' || property.name === 'left') return;
            curComponent.setProperty(property.name, property.value, true, deviceId);
            element.css(property.name, property.value);
            element.highlightSelectedElement(curComponent, true);
            curComponent.viewer();
        });
        if (curComponent.displayName === HEADERTEXT || curComponent.displayName === PARAGRAPH) {
            var text = curComponent.getProperty(TEXT).value;
            $('#' + curComponent.id).html(text);
        }
    }
    app.addNewComponent = function (type, events, parent) {
        parent = defined(parent) ? parent : null;
        events = defined(events) ? events : {};
        if (siteComponentRepository.checkExistingRule(type, parent != null ? parent.id : null)) {
            //getting component
            var basicComponentsFiltered = basicComponentRepository.getAll().where({ name: type })
                .filter(component => component.componentType == 1 || component.componentType == 2);

            if (basicComponentsFiltered.any()) {

                var basicComponent = basicComponentsFiltered.firstOrDefault();

                //new control creation
                var component = new Component().createNew(basicComponent, true);

                if (defined(component.events)) {
                    events = component.events;
                } else {
                    component.events = events;
                }

                if (parent != null) {
                    //for custom form children components
                    var componentWidth = component.getProperty(WIDTH).value;
                    var componentHeight = component.getProperty(HEIGHT).value;
                    var freePosition = Helpers
                        .findFreePositionOnCustomForm(parent.id, parseInt(componentWidth), parseInt(componentHeight));
                    component.setProperty(TOP, freePosition.y + 'px');
                    component.setProperty(LEFT, freePosition.x + 'px');
                    var isParentNeedResize = parent.getPropertyInt('width') < component.getPropertyInt('width');
                    var newWidth = component.getPropertyInt('width') + 20 + 'px';
                    if (isParentNeedResize) Resizer.resizeComponent(parent, newWidth);
                }

                if (defined(events.onComponentCreated) && _.isFunction(events.onComponentCreated)) {
                    events.onComponentCreated(component);
                }
                //write master link to new component
                if (component.getProperty(SUCCESS_PAGE_MASTER_LINK) !== null) {
                    var itemWithSuccessPage = UI.siteComponentRepository.lookupDataByPropertyName(SUCCESS_PAGE_MASTER_LINK);
                    if (itemWithSuccessPage !== null) {                        
                        component.setProperty(SUCCESS_PAGE_MASTER_LINK,
                            itemWithSuccessPage.getProperty(SUCCESS_PAGE_MASTER_LINK).value);
                    }
                }

                //adding component to form
                var parentElement = componentService.addComponentToForm(component, true);
                //adding component to repositories
                //var parentElement = siteComponentRepository.lookupData({ id: UI.pager.getCurrentPageId() });
                if (parent != null) {
                    siteComponentRepository.appendTo(component, parent);
                } else {
                    siteComponentRepository.appendTo(component, parentElement);
                }
                component.viewer();
                if (defined(events.onComponentProcessed) && _.isFunction(events.onComponentProcessed)) {
                    events.onComponentProcessed(component);
                }

                var curComponent = siteComponentRepository.lookupData({ id: component.id });
                var curElement = $('#' + curComponent.id);

                if (parent != null) {
                    curElement.detach();
                    curElement.appendTo($(parent.getUISelector()));
                } else {
                    curElement.hide();
                }

                var newComponent, image, label, showMore;
                if (type === STORE_GALLERY) {
                    //add store gallery product
                    var basicComponentForGallery = UI.basicComponentRepository.lookupData({ name: STORE_GALLERY_PRODUCT });
                    if (basicComponentForGallery) {
                        newComponent = new Component().createNew(basicComponentForGallery, true);
                        UI.siteComponentRepository.appendTo(newComponent, curComponent);

                        basicComponentForGallery = UI.basicComponentRepository.lookupData({ name: STORE_GALLERY_PRODUCT_IMAGE });
                        if (basicComponentForGallery) {
                            image = new Component().createNew(basicComponentForGallery, true);
                            UI.siteComponentRepository.appendTo(image, newComponent);
                            basicComponentForGallery = UI.basicComponentRepository.lookupData({ name: STORE_GALLERY_PRODUCT_LABEL });
                            if (basicComponentForGallery) {
                                label = new Component().createNew(basicComponentForGallery, true);
                                UI.siteComponentRepository.appendTo(label, image);
                            }
                        }

                        var titleComponentForGallery = UI.basicComponentRepository.lookupData({ name: STORE_GALLERY_PRODUCT_TITLE });
                        var priceComponentForGallery = UI.basicComponentRepository.lookupData({ name: STORE_GALLERY_PRODUCT_PRICE });
                        var descComponentForGallery = UI.basicComponentRepository.lookupData({ name: STORE_GALLERY_PRODUCT_DESCRIPTION });

                        if (titleComponentForGallery) {
                            UI.siteComponentRepository.appendTo(new Component().createNew(titleComponentForGallery, true), newComponent);
                        }
                        if (priceComponentForGallery) {
                            UI.siteComponentRepository.appendTo(new Component().createNew(priceComponentForGallery, true), newComponent);
                        }
                        if (descComponentForGallery) {
                            UI.siteComponentRepository.appendTo(new Component().createNew(descComponentForGallery, true), newComponent);
                        }
                        UI.actionService.runActionForComponent(newComponent, ACTION_ADD_TO_FORM, true);
                    }
                    //add store show more
                    basicComponentForGallery = UI.basicComponentRepository.lookupData({ name: STORE_GALLERY_SHOW_MORE });
                    if (basicComponentForGallery) {
                        showMore = new Component().createNew(basicComponentForGallery, true);
                        UI.siteComponentRepository.appendTo(showMore, curComponent);
                        UI.actionService.runActionForComponent(showMore, ACTION_ADD_TO_FORM, true);
                    }
                }

                UI.addLog('add', curComponent);
                var redo = function() {
                    var parent = siteComponentRepository.lookupData({ id: curComponent.parentComponent.id });

                    var parentComponent = UI.pager.getCurrentPageId();

                    if (parent != null) {
                        if (curComponent.parentComponent.displayName == FORM) {
                            var componentWidth = component.getProperty(WIDTH).value;
                            var componentHeight = component.getProperty(HEIGHT).value;
                            var freePosition = Helpers
                                .findFreePositionOnCustomForm(parent.id,
                                    parseInt(componentWidth),
                                    parseInt(componentHeight));
                            component.setProperty(TOP, freePosition.y + 'px');
                            component.setProperty(LEFT, freePosition.x + 'px');
                        }

                        parentComponent = UI.siteComponentRepository.lookupData({ id: parent.id }).id;
                    }

                    siteComponentRepository.appendTo(curComponent,
                        UI.siteComponentRepository.lookupData({ id: parentComponent })); // add to model

                    if (type === STORE_GALLERY) {
                        UI.siteComponentRepository.appendTo(newComponent, curComponent);

                        UI.siteComponentRepository.appendTo(showMore, curComponent);

                        UI.siteComponentRepository.appendTo(image, newComponent);

                        UI.siteComponentRepository.appendTo(label, image);
                    }

                    TransformFactory.setNewPosition(curComponent);

                    UI.actionService.addToActionData(curComponent, true);
                    UI.actionService.runActionForComponent(curComponent, ACTION_ADD_TO_FORM, true);

                    if (defined(events.onComponentCreated) && _.isFunction(events.onComponentCreated)) {
                        events.onComponentCreated(curComponent);
                    }
                    UI.addLog('add', curComponent);
                }
                var undo = function () {
                    UI.removeLog('add', curComponent);
                    siteComponentRepository.remove({ id: curComponent.id });
                }

                //we are self subscribed on that, see handler Application.onLoad
                eventsystem.publish("/dataservice/onServiceInitialized", dataService);
                UI.undoManagerAdd(
                {
                    undo: function() {
                        undo();
                    },
                    redo: function() {
                        redo();
                    }
                });
                if (parent == null) {
                    var componentsWithPreEditor = [LIST, CONTACT_US];
                    if ($.inArray(type, componentsWithPreEditor) != -1) {
                        UI.callPreEditor(component);
                    } else {
                        if (type === STORE_GALLERY) {

                            
                        }
                        //publish component added 
                        eventsystem.publish('/component/create/', curComponent, curElement);
                    }
                } else {
                    TransformFactory.setNewPosition(curComponent);
                }
            } //if any basic component
        }
    }//end AddNewComponent

    $(".preview-advertisement").css("display", "block");
    UI.addNewComponent = app.addNewComponent;
    UI.initializeViewerPaletteController(viewerPaletteController);

    if (!UI.settings.isComponentEditor && !UI.settings.isSectionEditor) {
        var footer = $(".footer")[0];
        $('#wrapper-footer').css("top", (footer.offsetTop + footer.offsetHeight + 10) + "px");
    }

    //we are self subscribed on that, see handler Application.onLoad
    eventsystem.publish("/dataservice/onServiceInitialized", dataService);
    if (!UI.getSetting("ispreview") && UI.RulerGuides != undefined) {
        UI.RulerGuides.init();
    }
    UI.postLoadInit.execute();
    if (!UI.getSetting("ispreview")) {
        if (!UI.settings.isComponentEditor) UI.displayNavigationPanelPopover();
    }
    Application.removeLocker();
    if (!UI.getSetting("ispreview")) {
        if (!UI.settings.isComponentEditor) UI.initAutoSave();
    }
};

//rebuild method
EditorApplication.reBuild = function () {
    if (!UI.getSetting("ispreview")) {
        //hide ruler
        Helpers.hideRuler();

        //clean form
        UI.actionService.runAction(ACTION_REMOVE_FROM_FORM);

        Helpers.cleanSelected();

        TransformFactory.runTransform(UI.siteComponentRepository.getAll()[0]);
        //run action
        UI.actionService.runAction(ACTION_ADD_TO_FORM);

        if (!UI.getSetting("ispreview")) {
            UI.handleKeyDown(UI.siteComponentRepository);
            UI.handleKeyUp();
        }

        if (UI.getSetting("ispreview")) {
            if (!UI.getSetting("ispaid")) {
                var service = new AdvertisementService();
            }
        }

        //go to page
        UI.pager.goToCurrentPage();

        //rendering menus on pager object basis
        UI.renderMenus();

        //init palette controller
        UI.initializeViewerPaletteController(viewerPaletteController);

        var footer = $(".footer")[0];
        $('#wrapper-footer').css("top", (footer.offsetTop + footer.offsetHeight + 10) + "px");

        if (!UI.getSetting("ispreview") && UI.RulerGuides != undefined) {
            UI.RulerGuides.init();
        }
        UI.postLoadInit.execute();
    }
}
;
var AutocompleteAddress = function () {
    var self = this;
    var scriptSrc = 'https://maps.googleapis.com/maps/api/js?key=AIzaSyAGAZn8YvIs7cmgo_OpJAFwqyLMuACGMX4&libraries=places&callback=initAutocomplete';

    window.initAutocomplete = function() {
        const autocompleteAddressInForms = document.querySelectorAll(".std-form-autocomplete-address");
        const evaluateHomeComponents = document.querySelectorAll(".std-evaluate-home");
        const contactUsComponents = document.querySelectorAll(".std-contact-us");

        autocompleteAddressInForms.forEach(block => {
            const street1Field = block.querySelector("#street1");
            const street2Field = block.querySelector("#street2");
            const postalField = block.querySelector("#postcode");

            let autocomplete = new google.maps.places.Autocomplete(street1Field, {
                componentRestrictions: { country: ["us", "ca"] },
                fields: ["address_components", "geometry"],
                types: ["address"],
            });

            autocomplete.addListener("place_changed", function () {
                const place = autocomplete.getPlace();
                let address1 = "";
                let postcode = "";

                for (const component of place.address_components) {
                    const componentType = component.types[0];

                    switch (componentType) {
                        case "street_number": {
                            address1 = `${component.long_name} ${address1}`;
                            break;
                        }

                        case "route": {
                            address1 += component.short_name;
                            break;
                        }

                        case "postal_code": {
                            postcode = `${component.long_name}${postcode}`;
                            break;
                        }

                        case "postal_code_suffix": {
                            postcode = `${postcode}-${component.long_name}`;
                            break;
                        }
                        case "locality":
                            block.querySelector("#locality").value = component.long_name;
                            break;
                        case "administrative_area_level_1": {
                            block.querySelector("#state").value = component.long_name;
                            break;
                        }
                    }
                }

                street1Field.value = address1;
                postalField.value = postcode;
                street2Field.focus();
            });
        });

        evaluateHomeComponents.forEach(block => {
            const addressField = block.querySelector("input[name='AD']");
            const postalField = block.querySelector("input[name='ZC']");

            let autocomplete = new google.maps.places.Autocomplete(addressField, {
                componentRestrictions: { country: ["us", "ca"] },
                fields: ["address_components", "geometry"],
                types: ["address"],
            });

            autocomplete.addListener("place_changed", function () {
                const place = autocomplete.getPlace();
                let address1 = "";
                let postcode = "";

                for (const component of place.address_components) {
                    const componentType = component.types[0];

                    switch (componentType) {
                        case "street_number": {
                            address1 = `${component.long_name} ${address1}`;
                            break;
                        }

                        case "route": {
                            address1 += component.short_name;
                            break;
                        }

                        case "postal_code": {
                            postcode = `${component.long_name}${postcode}`;
                            break;
                        }

                        case "postal_code_suffix": {
                            postcode = `${postcode}-${component.long_name}`;
                            break;
                        }
                        case "locality":
                            block.querySelector("input[name='CT']").value = component.long_name;
                            break;
                        case "administrative_area_level_1": {
                            block.querySelector("input[name='ST']").value = component.long_name;
                            break;
                        }
                    }
                }

                addressField.value = address1;
                postalField.value = postcode;
            });
        })

        contactUsComponents.forEach(block => {
            const addressField = block.querySelector("input[name='IA']");
            const cityStateZipField = block.querySelector("input[name='CSZ']");

            let autocomplete = new google.maps.places.Autocomplete(addressField, {
                componentRestrictions: { country: ["us", "ca"] },
                fields: ["address_components", "geometry"],
                types: ["address"],
            });

            autocomplete.addListener("place_changed", function () {
                const place = autocomplete.getPlace();
                let address1 = "";
                let city = "";
                let state = "";
                let postcode = "";

                for (const component of place.address_components) {
                    const componentType = component.types[0];

                    switch (componentType) {
                        case "street_number": {
                            address1 = `${component.long_name} ${address1}`;
                            break;
                        }

                        case "route": {
                            address1 += component.short_name;
                            break;
                        }

                        case "postal_code": {
                            postcode = `${component.long_name}${postcode}`;
                            break;
                        }

                        case "postal_code_suffix": {
                            postcode = `${postcode}-${component.long_name}`;
                            break;
                        }
                        case "locality":
                            city = component.long_name;
                            break;
                        case "administrative_area_level_1": {
                            state = component.short_name;
                            break;
                        }
                    }
                }

                addressField.value = address1;
                cityStateZipField.value = `${city}, ${state}, ${postcode}`;
            });
        })
    };

    self.isScriptExist = function () {
        return document.querySelector(`script[src="${scriptSrc}"]`);
    }
    self.addScript = function () {
        if (!self.isScriptExist()) {
            const script = document.createElement('script');
            script.src = scriptSrc;
            script.defer = true;
            document.body.appendChild(script);
        }
    }
};
var Binding = function (items) {
    this.koDomElements = ko.observableArray(items || []);

    this.koAddElement = function (element) {
        if (this.koDomElements().indexOf(element) < 0) {
            this.koDomElements.push(element);
        }
    };

    this.koRemoveElement = function (element) {
        for (var i = this.koDomElements().length - 1; i >= 0; i--) {
            if (element == this.koDomElements()[i]) {
                this.koDomElements.remove(this.koDomElements()[i]);
                break;
            }
        }
    };

    this.koUnbindAll = function () {
        while (this.koDomElements().length > 0) {
            ko.cleanNode(this.koDomElements()[0]);
            this.koDomElements.remove(this.koDomElements()[0]);
        }
    };

    this.koUnbindOrphaned = function () {
        for (var i = this.koDomElements().length - 1; i >= 0; i--) {
            if (this.elementIsRemoved(this.koDomElements()[i])) {
                ko.cleanNode(this.koDomElements()[i]);
                this.koDomElements.remove(this.koDomElements()[i]);
            }
        }
    };

    this.elementIsRemoved = function (element) {
        while (true) {
            if (element == $('html')[0]) { //html has no parent
                return false;
            }
            if (element == null || element == undefined || element.parentElement == null || element.parentElement == undefined) {
                return true;
            }
            element = element.parentElement;
        }
    };
};

var BindingController = new Binding();;
/**
 * @license Rangy, a cross-browser JavaScript range and selection library
 * http://code.google.com/p/rangy/
 *
 * Copyright 2012, Tim Down
 * Licensed under the MIT license.
 * Version: 1.2.3
 * Build date: 26 February 2012
 */
window['rangy'] = (function() {


    var OBJECT = "object", FUNCTION = "function", UNDEFINED = "undefined";

    var domRangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed",
        "commonAncestorContainer", "START_TO_START", "START_TO_END", "END_TO_START", "END_TO_END"];

    var domRangeMethods = ["setStart", "setStartBefore", "setStartAfter", "setEnd", "setEndBefore",
        "setEndAfter", "collapse", "selectNode", "selectNodeContents", "compareBoundaryPoints", "deleteContents",
        "extractContents", "cloneContents", "insertNode", "surroundContents", "cloneRange", "toString", "detach"];

    var textRangeProperties = ["boundingHeight", "boundingLeft", "boundingTop", "boundingWidth", "htmlText", "text"];

    // Subset of TextRange's full set of methods that we're interested in
    var textRangeMethods = ["collapse", "compareEndPoints", "duplicate", "getBookmark", "moveToBookmark",
        "moveToElementText", "parentElement", "pasteHTML", "select", "setEndPoint", "getBoundingClientRect"];

    /*----------------------------------------------------------------------------------------------------------------*/

    // Trio of functions taken from Peter Michaux's article:
    // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting
    function isHostMethod(o, p) {
        var t = typeof o[p];
        return t == FUNCTION || (!!(t == OBJECT && o[p])) || t == "unknown";
    }

    function isHostObject(o, p) {
        return !!(typeof o[p] == OBJECT && o[p]);
    }

    function isHostProperty(o, p) {
        return typeof o[p] != UNDEFINED;
    }

    // Creates a convenience function to save verbose repeated calls to tests functions
    function createMultiplePropertyTest(testFunc) {
        return function(o, props) {
            var i = props.length;
            while (i--) {
                if (!testFunc(o, props[i])) {
                    return false;
                }
            }
            return true;
        };
    }

    // Next trio of functions are a convenience to save verbose repeated calls to previous two functions
    var areHostMethods = createMultiplePropertyTest(isHostMethod);
    var areHostObjects = createMultiplePropertyTest(isHostObject);
    var areHostProperties = createMultiplePropertyTest(isHostProperty);

    function isTextRange(range) {
        return range && areHostMethods(range, textRangeMethods) && areHostProperties(range, textRangeProperties);
    }

    var api = {
        version: "1.2.3",
        initialized: false,
        supported: true,

        util: {
            isHostMethod: isHostMethod,
            isHostObject: isHostObject,
            isHostProperty: isHostProperty,
            areHostMethods: areHostMethods,
            areHostObjects: areHostObjects,
            areHostProperties: areHostProperties,
            isTextRange: isTextRange
        },

        features: {},

        modules: {},
        config: {
            alertOnWarn: false,
            preferTextRange: false
        }
    };

    function fail(reason) {
        window.alert("Rangy not supported in your browser. Reason: " + reason);
        api.initialized = true;
        api.supported = false;
    }

    api.fail = fail;

    function warn(msg) {
        var warningMessage = "Rangy warning: " + msg;
        if (api.config.alertOnWarn) {
            window.alert(warningMessage);
        } else if (typeof window.console != UNDEFINED && typeof window.console.log != UNDEFINED) {
            window.console.log(warningMessage);
        }
    }

    api.warn = warn;

    if ({}.hasOwnProperty) {
        api.util.extend = function(o, props) {
            for (var i in props) {
                if (props.hasOwnProperty(i)) {
                    o[i] = props[i];
                }
            }
        };
    } else {
        fail("hasOwnProperty not supported");
    }

    var initListeners = [];
    var moduleInitializers = [];

    // Initialization
    function init() {
        if (api.initialized) {
            return;
        }
        var testRange;
        var implementsDomRange = false, implementsTextRange = false;

        // First, perform basic feature tests

        if (isHostMethod(document, "createRange")) {
            testRange = document.createRange();
            if (areHostMethods(testRange, domRangeMethods) && areHostProperties(testRange, domRangeProperties)) {
                implementsDomRange = true;
            }
            testRange.detach();
        }

        var body = isHostObject(document, "body") ? document.body : document.getElementsByTagName("body")[0];

        if (body && isHostMethod(body, "createTextRange")) {
            testRange = body.createTextRange();
            if (isTextRange(testRange)) {
                implementsTextRange = true;
            }
        }

        if (!implementsDomRange && !implementsTextRange) {
            fail("Neither Range nor TextRange are implemented");
        }

        api.initialized = true;
        api.features = {
            implementsDomRange: implementsDomRange,
            implementsTextRange: implementsTextRange
        };

        // Initialize modules and call init listeners
        var allListeners = moduleInitializers.concat(initListeners);
        for (var i = 0, len = allListeners.length; i < len; ++i) {
            try {
                allListeners[i](api);
            } catch (ex) {
                if (isHostObject(window, "console") && isHostMethod(window.console, "log")) {
                    window.console.log("Init listener threw an exception. Continuing.", ex);
                }

            }
        }
    }

    // Allow external scripts to initialize this library in case it's loaded after the document has loaded
    api.init = init;

    // Execute listener immediately if already initialized
    api.addInitListener = function(listener) {
        if (api.initialized) {
            listener(api);
        } else {
            initListeners.push(listener);
        }
    };

    var createMissingNativeApiListeners = [];

    api.addCreateMissingNativeApiListener = function(listener) {
        createMissingNativeApiListeners.push(listener);
    };

    function createMissingNativeApi(win) {
        win = win || window;
        init();

        // Notify listeners
        for (var i = 0, len = createMissingNativeApiListeners.length; i < len; ++i) {
            createMissingNativeApiListeners[i](win);
        }
    }

    api.createMissingNativeApi = createMissingNativeApi;

    /**
     * @constructor
     */
    function Module(name) {
        this.name = name;
        this.initialized = false;
        this.supported = false;
    }

    Module.prototype.fail = function(reason) {
        this.initialized = true;
        this.supported = false;

        throw new Error("Module '" + this.name + "' failed to load: " + reason);
    };

    Module.prototype.warn = function(msg) {
        api.warn("Module " + this.name + ": " + msg);
    };

    Module.prototype.createError = function(msg) {
        return new Error("Error in Rangy " + this.name + " module: " + msg);
    };

    api.createModule = function(name, initFunc) {
        var module = new Module(name);
        api.modules[name] = module;

        moduleInitializers.push(function(api) {
            initFunc(api, module);
            module.initialized = true;
            module.supported = true;
        });
    };

    api.requireModules = function(modules) {
        for (var i = 0, len = modules.length, module, moduleName; i < len; ++i) {
            moduleName = modules[i];
            module = api.modules[moduleName];
            if (!module || !(module instanceof Module)) {
                throw new Error("Module '" + moduleName + "' not found");
            }
            if (!module.supported) {
                throw new Error("Module '" + moduleName + "' not supported");
            }
        }
    };

    /*----------------------------------------------------------------------------------------------------------------*/

    // Wait for document to load before running tests

    var docReady = false;

    var loadHandler = function(e) {

        if (!docReady) {
            docReady = true;
            if (!api.initialized) {
                init();
            }
        }
    };

    // Test whether we have window and document objects that we will need
    if (typeof window == UNDEFINED) {
        fail("No window found");
        return;
    }
    if (typeof document == UNDEFINED) {
        fail("No document found");
        return;
    }

    if (isHostMethod(document, "addEventListener")) {
        document.addEventListener("DOMContentLoaded", loadHandler, false);
    }

    // Add a fallback in case the DOMContentLoaded event isn't supported
    if (isHostMethod(window, "addEventListener")) {
        window.addEventListener("load", loadHandler, false);
    } else if (isHostMethod(window, "attachEvent")) {
        window.attachEvent("onload", loadHandler);
    } else {
        fail("Window does not have required addEventListener or attachEvent method");
    }

    return api;
})();
rangy.createModule("DomUtil", function(api, module) {

    var UNDEF = "undefined";
    var util = api.util;

    // Perform feature tests
    if (!util.areHostMethods(document, ["createDocumentFragment", "createElement", "createTextNode"])) {
        module.fail("document missing a Node creation method");
    }

    if (!util.isHostMethod(document, "getElementsByTagName")) {
        module.fail("document missing getElementsByTagName method");
    }

    var el = document.createElement("div");
    if (!util.areHostMethods(el, ["insertBefore", "appendChild", "cloneNode"] ||
            !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]))) {
        module.fail("Incomplete Element implementation");
    }

    // innerHTML is required for Range's createContextualFragment method
    if (!util.isHostProperty(el, "innerHTML")) {
        module.fail("Element is missing innerHTML property");
    }

    var textNode = document.createTextNode("test");
    if (!util.areHostMethods(textNode, ["splitText", "deleteData", "insertData", "appendData", "cloneNode"] ||
            !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]) ||
            !util.areHostProperties(textNode, ["data"]))) {
        module.fail("Incomplete Text Node implementation");
    }

    /*----------------------------------------------------------------------------------------------------------------*/

    // Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been
    // able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that
    // contains just the document as a single element and the value searched for is the document.
    var arrayContains = /*Array.prototype.indexOf ?
        function(arr, val) {
            return arr.indexOf(val) > -1;
        }:*/

        function(arr, val) {
            var i = arr.length;
            while (i--) {
                if (arr[i] === val) {
                    return true;
                }
            }
            return false;
        };

    // Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI
    function isHtmlNamespace(node) {
        var ns;
        return typeof node.namespaceURI == UNDEF || ((ns = node.namespaceURI) === null || ns == "http://www.w3.org/1999/xhtml");
    }

    function parentElement(node) {
        var parent = node.parentNode;
        return (parent.nodeType == 1) ? parent : null;
    }

    function getNodeIndex(node) {
        var i = 0;
        while( (node = node.previousSibling) ) {
            i++;
        }
        return i;
    }

    function getNodeLength(node) {
        var childNodes;
        return isCharacterDataNode(node) ? node.length : ((childNodes = node.childNodes) ? childNodes.length : 0);
    }

    function getCommonAncestor(node1, node2) {
        var ancestors = [], n;
        for (n = node1; n; n = n.parentNode) {
            ancestors.push(n);
        }

        for (n = node2; n; n = n.parentNode) {
            if (arrayContains(ancestors, n)) {
                return n;
            }
        }

        return null;
    }

    function isAncestorOf(ancestor, descendant, selfIsAncestor) {
        var n = selfIsAncestor ? descendant : descendant.parentNode;
        while (n) {
            if (n === ancestor) {
                return true;
            } else {
                n = n.parentNode;
            }
        }
        return false;
    }

    function getClosestAncestorIn(node, ancestor, selfIsAncestor) {
        var p, n = selfIsAncestor ? node : node.parentNode;
        while (n) {
            p = n.parentNode;
            if (p === ancestor) {
                return n;
            }
            n = p;
        }
        return null;
    }

    function isCharacterDataNode(node) {
        var t = node.nodeType;
        return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment
    }

    function insertAfter(node, precedingNode) {
        var nextNode = precedingNode.nextSibling, parent = precedingNode.parentNode;
        if (nextNode) {
            parent.insertBefore(node, nextNode);
        } else {
            parent.appendChild(node);
        }
        return node;
    }

    // Note that we cannot use splitText() because it is bugridden in IE 9.
    function splitDataNode(node, index) {
        var newNode = node.cloneNode(false);
        newNode.deleteData(0, index);
        node.deleteData(index, node.length - index);
        insertAfter(newNode, node);
        return newNode;
    }

    function getDocument(node) {
        if (node.nodeType == 9) {
            return node;
        } else if (typeof node.ownerDocument != UNDEF) {
            return node.ownerDocument;
        } else if (typeof node.document != UNDEF) {
            return node.document;
        } else if (node.parentNode) {
            return getDocument(node.parentNode);
        } else {
            throw new Error("getDocument: no document found for node");
        }
    }

    function getWindow(node) {
        var doc = getDocument(node);
        if (typeof doc.defaultView != UNDEF) {
            return doc.defaultView;
        } else if (typeof doc.parentWindow != UNDEF) {
            return doc.parentWindow;
        } else {
            throw new Error("Cannot get a window object for node");
        }
    }

    function getIframeDocument(iframeEl) {
        if (typeof iframeEl.contentDocument != UNDEF) {
            return iframeEl.contentDocument;
        } else if (typeof iframeEl.contentWindow != UNDEF) {
            return iframeEl.contentWindow.document;
        } else {
            throw new Error("getIframeWindow: No Document object found for iframe element");
        }
    }

    function getIframeWindow(iframeEl) {
        if (typeof iframeEl.contentWindow != UNDEF) {
            return iframeEl.contentWindow;
        } else if (typeof iframeEl.contentDocument != UNDEF) {
            return iframeEl.contentDocument.defaultView;
        } else {
            throw new Error("getIframeWindow: No Window object found for iframe element");
        }
    }

    function getBody(doc) {
        return util.isHostObject(doc, "body") ? doc.body : doc.getElementsByTagName("body")[0];
    }

    function getRootContainer(node) {
        var parent;
        while ( (parent = node.parentNode) ) {
            node = parent;
        }
        return node;
    }

    function comparePoints(nodeA, offsetA, nodeB, offsetB) {
        // See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing
        var nodeC, root, childA, childB, n;
        if (nodeA == nodeB) {

            // Case 1: nodes are the same
            return offsetA === offsetB ? 0 : (offsetA < offsetB) ? -1 : 1;
        } else if ( (nodeC = getClosestAncestorIn(nodeB, nodeA, true)) ) {

            // Case 2: node C (container B or an ancestor) is a child node of A
            return offsetA <= getNodeIndex(nodeC) ? -1 : 1;
        } else if ( (nodeC = getClosestAncestorIn(nodeA, nodeB, true)) ) {

            // Case 3: node C (container A or an ancestor) is a child node of B
            return getNodeIndex(nodeC) < offsetB  ? -1 : 1;
        } else {

            // Case 4: containers are siblings or descendants of siblings
            root = getCommonAncestor(nodeA, nodeB);
            childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true);
            childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true);

            if (childA === childB) {
                // This shouldn't be possible

                throw new Error("comparePoints got to case 4 and childA and childB are the same!");
            } else {
                n = root.firstChild;
                while (n) {
                    if (n === childA) {
                        return -1;
                    } else if (n === childB) {
                        return 1;
                    }
                    n = n.nextSibling;
                }
                throw new Error("Should not be here!");
            }
        }
    }

    function fragmentFromNodeChildren(node) {
        var fragment = getDocument(node).createDocumentFragment(), child;
        while ( (child = node.firstChild) ) {
            fragment.appendChild(child);
        }
        return fragment;
    }

    function inspectNode(node) {
        if (!node) {
            return "[No node]";
        }
        if (isCharacterDataNode(node)) {
            return '"' + node.data + '"';
        } else if (node.nodeType == 1) {
            var idAttr = node.id ? ' id="' + node.id + '"' : "";
            return "<" + node.nodeName + idAttr + ">[" + node.childNodes.length + "]";
        } else {
            return node.nodeName;
        }
    }

    /**
     * @constructor
     */
    function NodeIterator(root) {
        this.root = root;
        this._next = root;
    }

    NodeIterator.prototype = {
        _current: null,

        hasNext: function() {
            return !!this._next;
        },

        next: function() {
            var n = this._current = this._next;
            var child, next;
            if (this._current) {
                child = n.firstChild;
                if (child) {
                    this._next = child;
                } else {
                    next = null;
                    while ((n !== this.root) && !(next = n.nextSibling)) {
                        n = n.parentNode;
                    }
                    this._next = next;
                }
            }
            return this._current;
        },

        detach: function() {
            this._current = this._next = this.root = null;
        }
    };

    function createIterator(root) {
        return new NodeIterator(root);
    }

    /**
     * @constructor
     */
    function DomPosition(node, offset) {
        this.node = node;
        this.offset = offset;
    }

    DomPosition.prototype = {
        equals: function(pos) {
            return this.node === pos.node & this.offset == pos.offset;
        },

        inspect: function() {
            return "[DomPosition(" + inspectNode(this.node) + ":" + this.offset + ")]";
        }
    };

    /**
     * @constructor
     */
    function DOMException(codeName) {
        this.code = this[codeName];
        this.codeName = codeName;
        this.message = "DOMException: " + this.codeName;
    }

    DOMException.prototype = {
        INDEX_SIZE_ERR: 1,
        HIERARCHY_REQUEST_ERR: 3,
        WRONG_DOCUMENT_ERR: 4,
        NO_MODIFICATION_ALLOWED_ERR: 7,
        NOT_FOUND_ERR: 8,
        NOT_SUPPORTED_ERR: 9,
        INVALID_STATE_ERR: 11
    };

    DOMException.prototype.toString = function() {
        return this.message;
    };

    api.dom = {
        arrayContains: arrayContains,
        isHtmlNamespace: isHtmlNamespace,
        parentElement: parentElement,
        getNodeIndex: getNodeIndex,
        getNodeLength: getNodeLength,
        getCommonAncestor: getCommonAncestor,
        isAncestorOf: isAncestorOf,
        getClosestAncestorIn: getClosestAncestorIn,
        isCharacterDataNode: isCharacterDataNode,
        insertAfter: insertAfter,
        splitDataNode: splitDataNode,
        getDocument: getDocument,
        getWindow: getWindow,
        getIframeWindow: getIframeWindow,
        getIframeDocument: getIframeDocument,
        getBody: getBody,
        getRootContainer: getRootContainer,
        comparePoints: comparePoints,
        inspectNode: inspectNode,
        fragmentFromNodeChildren: fragmentFromNodeChildren,
        createIterator: createIterator,
        DomPosition: DomPosition
    };

    api.DOMException = DOMException;
});rangy.createModule("DomRange", function(api, module) {
    api.requireModules( ["DomUtil"] );


    var dom = api.dom;
    var DomPosition = dom.DomPosition;
    var DOMException = api.DOMException;
    
    /*----------------------------------------------------------------------------------------------------------------*/

    // Utility functions

    function isNonTextPartiallySelected(node, range) {
        return (node.nodeType != 3) &&
               (dom.isAncestorOf(node, range.startContainer, true) || dom.isAncestorOf(node, range.endContainer, true));
    }

    function getRangeDocument(range) {
        return dom.getDocument(range.startContainer);
    }

    function dispatchEvent(range, type, args) {
        var listeners = range._listeners[type];
        if (listeners) {
            for (var i = 0, len = listeners.length; i < len; ++i) {
                listeners[i].call(range, {target: range, args: args});
            }
        }
    }

    function getBoundaryBeforeNode(node) {
        return new DomPosition(node.parentNode, dom.getNodeIndex(node));
    }

    function getBoundaryAfterNode(node) {
        return new DomPosition(node.parentNode, dom.getNodeIndex(node) + 1);
    }

    function insertNodeAtPosition(node, n, o) {
        var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node;
        if (dom.isCharacterDataNode(n)) {
            if (o == n.length) {
                dom.insertAfter(node, n);
            } else {
                n.parentNode.insertBefore(node, o == 0 ? n : dom.splitDataNode(n, o));
            }
        } else if (o >= n.childNodes.length) {
            n.appendChild(node);
        } else {
            n.insertBefore(node, n.childNodes[o]);
        }
        return firstNodeInserted;
    }

    function cloneSubtree(iterator) {
        var partiallySelected;
        for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) {
            partiallySelected = iterator.isPartiallySelectedSubtree();

            node = node.cloneNode(!partiallySelected);
            if (partiallySelected) {
                subIterator = iterator.getSubtreeIterator();
                node.appendChild(cloneSubtree(subIterator));
                subIterator.detach(true);
            }

            if (node.nodeType == 10) { // DocumentType
                throw new DOMException("HIERARCHY_REQUEST_ERR");
            }
            frag.appendChild(node);
        }
        return frag;
    }

    function iterateSubtree(rangeIterator, func, iteratorState) {
        var it, n;
        iteratorState = iteratorState || { stop: false };
        for (var node, subRangeIterator; node = rangeIterator.next(); ) {
            //log.debug("iterateSubtree, partially selected: " + rangeIterator.isPartiallySelectedSubtree(), nodeToString(node));
            if (rangeIterator.isPartiallySelectedSubtree()) {
                // The node is partially selected by the Range, so we can use a new RangeIterator on the portion of the
                // node selected by the Range.
                if (func(node) === false) {
                    iteratorState.stop = true;
                    return;
                } else {
                    subRangeIterator = rangeIterator.getSubtreeIterator();
                    iterateSubtree(subRangeIterator, func, iteratorState);
                    subRangeIterator.detach(true);
                    if (iteratorState.stop) {
                        return;
                    }
                }
            } else {
                // The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its
                // descendant
                it = dom.createIterator(node);
                while ( (n = it.next()) ) {
                    if (func(n) === false) {
                        iteratorState.stop = true;
                        return;
                    }
                }
            }
        }
    }

    function deleteSubtree(iterator) {
        var subIterator;
        while (iterator.next()) {
            if (iterator.isPartiallySelectedSubtree()) {
                subIterator = iterator.getSubtreeIterator();
                deleteSubtree(subIterator);
                subIterator.detach(true);
            } else {
                iterator.remove();
            }
        }
    }

    function extractSubtree(iterator) {

        for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) {


            if (iterator.isPartiallySelectedSubtree()) {
                node = node.cloneNode(false);
                subIterator = iterator.getSubtreeIterator();
                node.appendChild(extractSubtree(subIterator));
                subIterator.detach(true);
            } else {
                iterator.remove();
            }
            if (node.nodeType == 10) { // DocumentType
                throw new DOMException("HIERARCHY_REQUEST_ERR");
            }
            frag.appendChild(node);
        }
        return frag;
    }

    function getNodesInRange(range, nodeTypes, filter) {
        //log.info("getNodesInRange, " + nodeTypes.join(","));
        var filterNodeTypes = !!(nodeTypes && nodeTypes.length), regex;
        var filterExists = !!filter;
        if (filterNodeTypes) {
            regex = new RegExp("^(" + nodeTypes.join("|") + ")$");
        }

        var nodes = [];
        iterateSubtree(new RangeIterator(range, false), function(node) {
            if ((!filterNodeTypes || regex.test(node.nodeType)) && (!filterExists || filter(node))) {
                nodes.push(node);
            }
        });
        return nodes;
    }

    function inspect(range) {
        var name = (typeof range.getName == "undefined") ? "Range" : range.getName();
        return "[" + name + "(" + dom.inspectNode(range.startContainer) + ":" + range.startOffset + ", " +
                dom.inspectNode(range.endContainer) + ":" + range.endOffset + ")]";
    }

    /*----------------------------------------------------------------------------------------------------------------*/

    // RangeIterator code partially borrows from IERange by Tim Ryan (http://github.com/timcameronryan/IERange)

    /**
     * @constructor
     */
    function RangeIterator(range, clonePartiallySelectedTextNodes) {
        this.range = range;
        this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes;



        if (!range.collapsed) {
            this.sc = range.startContainer;
            this.so = range.startOffset;
            this.ec = range.endContainer;
            this.eo = range.endOffset;
            var root = range.commonAncestorContainer;

            if (this.sc === this.ec && dom.isCharacterDataNode(this.sc)) {
                this.isSingleCharacterDataNode = true;
                this._first = this._last = this._next = this.sc;
            } else {
                this._first = this._next = (this.sc === root && !dom.isCharacterDataNode(this.sc)) ?
                    this.sc.childNodes[this.so] : dom.getClosestAncestorIn(this.sc, root, true);
                this._last = (this.ec === root && !dom.isCharacterDataNode(this.ec)) ?
                    this.ec.childNodes[this.eo - 1] : dom.getClosestAncestorIn(this.ec, root, true);
            }

        }
    }

    RangeIterator.prototype = {
        _current: null,
        _next: null,
        _first: null,
        _last: null,
        isSingleCharacterDataNode: false,

        reset: function() {
            this._current = null;
            this._next = this._first;
        },

        hasNext: function() {
            return !!this._next;
        },

        next: function() {
            // Move to next node
            var current = this._current = this._next;
            if (current) {
                this._next = (current !== this._last) ? current.nextSibling : null;

                // Check for partially selected text nodes
                if (dom.isCharacterDataNode(current) && this.clonePartiallySelectedTextNodes) {
                    if (current === this.ec) {

                        (current = current.cloneNode(true)).deleteData(this.eo, current.length - this.eo);
                    }
                    if (this._current === this.sc) {

                        (current = current.cloneNode(true)).deleteData(0, this.so);
                    }
                }
            }

            return current;
        },

        remove: function() {
            var current = this._current, start, end;

            if (dom.isCharacterDataNode(current) && (current === this.sc || current === this.ec)) {
                start = (current === this.sc) ? this.so : 0;
                end = (current === this.ec) ? this.eo : current.length;
                if (start != end) {
                    current.deleteData(start, end - start);
                }
            } else {
                if (current.parentNode) {
                    current.parentNode.removeChild(current);
                } else {

                }
            }
        },

        // Checks if the current node is partially selected
        isPartiallySelectedSubtree: function() {
            var current = this._current;
            return isNonTextPartiallySelected(current, this.range);
        },

        getSubtreeIterator: function() {
            var subRange;
            if (this.isSingleCharacterDataNode) {
                subRange = this.range.cloneRange();
                subRange.collapse();
            } else {
                subRange = new Range(getRangeDocument(this.range));
                var current = this._current;
                var startContainer = current, startOffset = 0, endContainer = current, endOffset = dom.getNodeLength(current);

                if (dom.isAncestorOf(current, this.sc, true)) {
                    startContainer = this.sc;
                    startOffset = this.so;
                }
                if (dom.isAncestorOf(current, this.ec, true)) {
                    endContainer = this.ec;
                    endOffset = this.eo;
                }

                updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset);
            }
            return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes);
        },

        detach: function(detachRange) {
            if (detachRange) {
                this.range.detach();
            }
            this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null;
        }
    };

    /*----------------------------------------------------------------------------------------------------------------*/

    // Exceptions

    /**
     * @constructor
     */
    function RangeException(codeName) {
        this.code = this[codeName];
        this.codeName = codeName;
        this.message = "RangeException: " + this.codeName;
    }

    RangeException.prototype = {
        BAD_BOUNDARYPOINTS_ERR: 1,
        INVALID_NODE_TYPE_ERR: 2
    };

    RangeException.prototype.toString = function() {
        return this.message;
    };

    /*----------------------------------------------------------------------------------------------------------------*/

    /**
     * Currently iterates through all nodes in the range on creation until I think of a decent way to do it
     * TODO: Look into making this a proper iterator, not requiring preloading everything first
     * @constructor
     */
    function RangeNodeIterator(range, nodeTypes, filter) {
        this.nodes = getNodesInRange(range, nodeTypes, filter);
        this._next = this.nodes[0];
        this._position = 0;
    }

    RangeNodeIterator.prototype = {
        _current: null,

        hasNext: function() {
            return !!this._next;
        },

        next: function() {
            this._current = this._next;
            this._next = this.nodes[ ++this._position ];
            return this._current;
        },

        detach: function() {
            this._current = this._next = this.nodes = null;
        }
    };

    var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10];
    var rootContainerNodeTypes = [2, 9, 11];
    var readonlyNodeTypes = [5, 6, 10, 12];
    var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11];
    var surroundNodeTypes = [1, 3, 4, 5, 7, 8];

    function createAncestorFinder(nodeTypes) {
        return function(node, selfIsAncestor) {
            var t, n = selfIsAncestor ? node : node.parentNode;
            while (n) {
                t = n.nodeType;
                if (dom.arrayContains(nodeTypes, t)) {
                    return n;
                }
                n = n.parentNode;
            }
            return null;
        };
    }

    var getRootContainer = dom.getRootContainer;
    var getDocumentOrFragmentContainer = createAncestorFinder( [9, 11] );
    var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes);
    var getDocTypeNotationEntityAncestor = createAncestorFinder( [6, 10, 12] );

    function assertNoDocTypeNotationEntityAncestor(node, allowSelf) {
        if (getDocTypeNotationEntityAncestor(node, allowSelf)) {
            throw new RangeException("INVALID_NODE_TYPE_ERR");
        }
    }

    function assertNotDetached(range) {
        if (!range.startContainer) {
            throw new DOMException("INVALID_STATE_ERR");
        }
    }

    function assertValidNodeType(node, invalidTypes) {
        if (!dom.arrayContains(invalidTypes, node.nodeType)) {
            throw new RangeException("INVALID_NODE_TYPE_ERR");
        }
    }

    function assertValidOffset(node, offset) {
        if (offset < 0 || offset > (dom.isCharacterDataNode(node) ? node.length : node.childNodes.length)) {
            throw new DOMException("INDEX_SIZE_ERR");
        }
    }

    function assertSameDocumentOrFragment(node1, node2) {
        if (getDocumentOrFragmentContainer(node1, true) !== getDocumentOrFragmentContainer(node2, true)) {
            throw new DOMException("WRONG_DOCUMENT_ERR");
        }
    }

    function assertNodeNotReadOnly(node) {
        if (getReadonlyAncestor(node, true)) {
            throw new DOMException("NO_MODIFICATION_ALLOWED_ERR");
        }
    }

    function assertNode(node, codeName) {
        if (!node) {
            throw new DOMException(codeName);
        }
    }

    function isOrphan(node) {
        return !dom.arrayContains(rootContainerNodeTypes, node.nodeType) && !getDocumentOrFragmentContainer(node, true);
    }

    function isValidOffset(node, offset) {
        return offset <= (dom.isCharacterDataNode(node) ? node.length : node.childNodes.length);
    }

    function isRangeValid(range) {
        return (!!range.startContainer && !!range.endContainer
                && !isOrphan(range.startContainer)
                && !isOrphan(range.endContainer)
                && isValidOffset(range.startContainer, range.startOffset)
                && isValidOffset(range.endContainer, range.endOffset));
    }

    function assertRangeValid(range) {
        assertNotDetached(range);
        if (!isRangeValid(range)) {
            throw new Error("Range error: Range is no longer valid after DOM mutation (" + range.inspect() + ")");
        }
    }

    /*----------------------------------------------------------------------------------------------------------------*/

    // Test the browser's innerHTML support to decide how to implement createContextualFragment
    var styleEl = document.createElement("style");
    var htmlParsingConforms = false;
    try {
        styleEl.innerHTML = "<b>x</b>";
        htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Opera incorrectly creates an element node
    } catch (e) {
        // IE 6 and 7 throw
    }

    api.features.htmlParsingConforms = htmlParsingConforms;

    var createContextualFragment = htmlParsingConforms ?

        // Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See
        // discussion and base code for this implementation at issue 67.
        // Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface
        // Thanks to Aleks Williams.
        function(fragmentStr) {
            // "Let node the context object's start's node."
            var node = this.startContainer;
            var doc = dom.getDocument(node);

            // "If the context object's start's node is null, raise an INVALID_STATE_ERR
            // exception and abort these steps."
            if (!node) {
                throw new DOMException("INVALID_STATE_ERR");
            }

            // "Let element be as follows, depending on node's interface:"
            // Document, Document Fragment: null
            var el = null;

            // "Element: node"
            if (node.nodeType == 1) {
                el = node;

            // "Text, Comment: node's parentElement"
            } else if (dom.isCharacterDataNode(node)) {
                el = dom.parentElement(node);
            }

            // "If either element is null or element's ownerDocument is an HTML document
            // and element's local name is "html" and element's namespace is the HTML
            // namespace"
            if (el === null || (
                el.nodeName == "HTML"
                && dom.isHtmlNamespace(dom.getDocument(el).documentElement)
                && dom.isHtmlNamespace(el)
            )) {

            // "let element be a new Element with "body" as its local name and the HTML
            // namespace as its namespace.""
                el = doc.createElement("body");
            } else {
                el = el.cloneNode(false);
            }

            // "If the node's document is an HTML document: Invoke the HTML fragment parsing algorithm."
            // "If the node's document is an XML document: Invoke the XML fragment parsing algorithm."
            // "In either case, the algorithm must be invoked with fragment as the input
            // and element as the context element."
            el.innerHTML = fragmentStr;

            // "If this raises an exception, then abort these steps. Otherwise, let new
            // children be the nodes returned."

            // "Let fragment be a new DocumentFragment."
            // "Append all new children to fragment."
            // "Return fragment."
            return dom.fragmentFromNodeChildren(el);
        } :

        // In this case, innerHTML cannot be trusted, so fall back to a simpler, non-conformant implementation that
        // previous versions of Rangy used (with the exception of using a body element rather than a div)
        function(fragmentStr) {
            assertNotDetached(this);
            var doc = getRangeDocument(this);
            var el = doc.createElement("body");
            el.innerHTML = fragmentStr;

            return dom.fragmentFromNodeChildren(el);
        };

    /*----------------------------------------------------------------------------------------------------------------*/

    var rangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed",
        "commonAncestorContainer"];

    var s2s = 0, s2e = 1, e2e = 2, e2s = 3;
    var n_b = 0, n_a = 1, n_b_a = 2, n_i = 3;

    function RangePrototype() {}

    RangePrototype.prototype = {
        attachListener: function(type, listener) {
            this._listeners[type].push(listener);
        },

        compareBoundaryPoints: function(how, range) {
            assertRangeValid(this);
            assertSameDocumentOrFragment(this.startContainer, range.startContainer);

            var nodeA, offsetA, nodeB, offsetB;
            var prefixA = (how == e2s || how == s2s) ? "start" : "end";
            var prefixB = (how == s2e || how == s2s) ? "start" : "end";
            nodeA = this[prefixA + "Container"];
            offsetA = this[prefixA + "Offset"];
            nodeB = range[prefixB + "Container"];
            offsetB = range[prefixB + "Offset"];
            return dom.comparePoints(nodeA, offsetA, nodeB, offsetB);
        },

        insertNode: function(node) {
            assertRangeValid(this);
            assertValidNodeType(node, insertableNodeTypes);
            assertNodeNotReadOnly(this.startContainer);

            if (dom.isAncestorOf(node, this.startContainer, true)) {
                throw new DOMException("HIERARCHY_REQUEST_ERR");
            }

            // No check for whether the container of the start of the Range is of a type that does not allow
            // children of the type of node: the browser's DOM implementation should do this for us when we attempt
            // to add the node

            var firstNodeInserted = insertNodeAtPosition(node, this.startContainer, this.startOffset);
            this.setStartBefore(firstNodeInserted);
        },

        cloneContents: function() {
            assertRangeValid(this);

            var clone, frag;
            if (this.collapsed) {
                return getRangeDocument(this).createDocumentFragment();
            } else {
                if (this.startContainer === this.endContainer && dom.isCharacterDataNode(this.startContainer)) {
                    clone = this.startContainer.cloneNode(true);
                    clone.data = clone.data.slice(this.startOffset, this.endOffset);
                    frag = getRangeDocument(this).createDocumentFragment();
                    frag.appendChild(clone);
                    return frag;
                } else {
                    var iterator = new RangeIterator(this, true);
                    clone = cloneSubtree(iterator);
                    iterator.detach();
                }
                return clone;
            }
        },

        canSurroundContents: function() {
            assertRangeValid(this);
            assertNodeNotReadOnly(this.startContainer);
            assertNodeNotReadOnly(this.endContainer);

            // Check if the contents can be surrounded. Specifically, this means whether the range partially selects
            // no non-text nodes.
            var iterator = new RangeIterator(this, true);
            var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) ||
                    (iterator._last && isNonTextPartiallySelected(iterator._last, this)));
            iterator.detach();
            return !boundariesInvalid;
        },

        surroundContents: function(node) {
            assertValidNodeType(node, surroundNodeTypes);

            if (!this.canSurroundContents()) {
                throw new RangeException("BAD_BOUNDARYPOINTS_ERR");
            }

            // Extract the contents
            var content = this.extractContents();

            // Clear the children of the node
            if (node.hasChildNodes()) {
                while (node.lastChild) {
                    node.removeChild(node.lastChild);
                }
            }

            // Insert the new node and add the extracted contents
            insertNodeAtPosition(node, this.startContainer, this.startOffset);
            node.appendChild(content);

            this.selectNode(node);
        },

        cloneRange: function() {
            assertRangeValid(this);
            var range = new Range(getRangeDocument(this));
            var i = rangeProperties.length, prop;
            while (i--) {
                prop = rangeProperties[i];
                range[prop] = this[prop];
            }
            return range;
        },

        toString: function() {
            assertRangeValid(this);
            var sc = this.startContainer;
            if (sc === this.endContainer && dom.isCharacterDataNode(sc)) {
                return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : "";
            } else {
                var textBits = [], iterator = new RangeIterator(this, true);

                iterateSubtree(iterator, function(node) {
                    // Accept only text or CDATA nodes, not comments

                    if (node.nodeType == 3 || node.nodeType == 4) {
                        textBits.push(node.data);
                    }
                });
                iterator.detach();
                return textBits.join("");
            }
        },

        // The methods below are all non-standard. The following batch were introduced by Mozilla but have since
        // been removed from Mozilla.

        compareNode: function(node) {
            assertRangeValid(this);

            var parent = node.parentNode;
            var nodeIndex = dom.getNodeIndex(node);

            if (!parent) {
                throw new DOMException("NOT_FOUND_ERR");
            }

            var startComparison = this.comparePoint(parent, nodeIndex),
                endComparison = this.comparePoint(parent, nodeIndex + 1);

            if (startComparison < 0) { // Node starts before
                return (endComparison > 0) ? n_b_a : n_b;
            } else {
                return (endComparison > 0) ? n_a : n_i;
            }
        },

        comparePoint: function(node, offset) {
            assertRangeValid(this);
            assertNode(node, "HIERARCHY_REQUEST_ERR");
            assertSameDocumentOrFragment(node, this.startContainer);

            if (dom.comparePoints(node, offset, this.startContainer, this.startOffset) < 0) {
                return -1;
            } else if (dom.comparePoints(node, offset, this.endContainer, this.endOffset) > 0) {
                return 1;
            }
            return 0;
        },

        createContextualFragment: createContextualFragment,

        toHtml: function() {
            assertRangeValid(this);
            var container = getRangeDocument(this).createElement("div");
            container.appendChild(this.cloneContents());
            return container.innerHTML;
        },

        // touchingIsIntersecting determines whether this method considers a node that borders a range intersects
        // with it (as in WebKit) or not (as in Gecko pre-1.9, and the default)
        intersectsNode: function(node, touchingIsIntersecting) {
            assertRangeValid(this);
            assertNode(node, "NOT_FOUND_ERR");
            if (dom.getDocument(node) !== getRangeDocument(this)) {
                return false;
            }

            var parent = node.parentNode, offset = dom.getNodeIndex(node);
            assertNode(parent, "NOT_FOUND_ERR");

            var startComparison = dom.comparePoints(parent, offset, this.endContainer, this.endOffset),
                endComparison = dom.comparePoints(parent, offset + 1, this.startContainer, this.startOffset);

            return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
        },


        isPointInRange: function(node, offset) {
            assertRangeValid(this);
            assertNode(node, "HIERARCHY_REQUEST_ERR");
            assertSameDocumentOrFragment(node, this.startContainer);

            return (dom.comparePoints(node, offset, this.startContainer, this.startOffset) >= 0) &&
                   (dom.comparePoints(node, offset, this.endContainer, this.endOffset) <= 0);
        },

        // The methods below are non-standard and invented by me.

        // Sharing a boundary start-to-end or end-to-start does not count as intersection.
        intersectsRange: function(range, touchingIsIntersecting) {
            assertRangeValid(this);

            if (getRangeDocument(range) != getRangeDocument(this)) {
                throw new DOMException("WRONG_DOCUMENT_ERR");
            }

            var startComparison = dom.comparePoints(this.startContainer, this.startOffset, range.endContainer, range.endOffset),
                endComparison = dom.comparePoints(this.endContainer, this.endOffset, range.startContainer, range.startOffset);

            return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
        },

        intersection: function(range) {
            if (this.intersectsRange(range)) {
                var startComparison = dom.comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset),
                    endComparison = dom.comparePoints(this.endContainer, this.endOffset, range.endContainer, range.endOffset);

                var intersectionRange = this.cloneRange();

                if (startComparison == -1) {
                    intersectionRange.setStart(range.startContainer, range.startOffset);
                }
                if (endComparison == 1) {
                    intersectionRange.setEnd(range.endContainer, range.endOffset);
                }
                return intersectionRange;
            }
            return null;
        },

        union: function(range) {
            if (this.intersectsRange(range, true)) {
                var unionRange = this.cloneRange();
                if (dom.comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) {
                    unionRange.setStart(range.startContainer, range.startOffset);
                }
                if (dom.comparePoints(range.endContainer, range.endOffset, this.endContainer, this.endOffset) == 1) {
                    unionRange.setEnd(range.endContainer, range.endOffset);
                }
                return unionRange;
            } else {
                throw new RangeException("Ranges do not intersect");
            }
        },

        containsNode: function(node, allowPartial) {
            if (allowPartial) {
                return this.intersectsNode(node, false);
            } else {
                return this.compareNode(node) == n_i;
            }
        },

        containsNodeContents: function(node) {
            return this.comparePoint(node, 0) >= 0 && this.comparePoint(node, dom.getNodeLength(node)) <= 0;
        },

        containsRange: function(range) {
            return this.intersection(range).equals(range);
        },

        containsNodeText: function(node) {
            var nodeRange = this.cloneRange();
            nodeRange.selectNode(node);
            var textNodes = nodeRange.getNodes([3]);
            if (textNodes.length > 0) {
                nodeRange.setStart(textNodes[0], 0);
                var lastTextNode = textNodes.pop();
                nodeRange.setEnd(lastTextNode, lastTextNode.length);
                var contains = this.containsRange(nodeRange);
                nodeRange.detach();
                return contains;
            } else {
                return this.containsNodeContents(node);
            }
        },

        createNodeIterator: function(nodeTypes, filter) {
            assertRangeValid(this);
            return new RangeNodeIterator(this, nodeTypes, filter);
        },

        getNodes: function(nodeTypes, filter) {
            assertRangeValid(this);
            return getNodesInRange(this, nodeTypes, filter);
        },

        getDocument: function() {
            return getRangeDocument(this);
        },

        collapseBefore: function(node) {
            assertNotDetached(this);

            this.setEndBefore(node);
            this.collapse(false);
        },

        collapseAfter: function(node) {
            assertNotDetached(this);

            this.setStartAfter(node);
            this.collapse(true);
        },

        getName: function() {
            return "DomRange";
        },

        equals: function(range) {
            return Range.rangesEqual(this, range);
        },

        isValid: function() {
            return isRangeValid(this);
        },

        inspect: function() {
            return inspect(this);
        }
    };

    function copyComparisonConstantsToObject(obj) {
        obj.START_TO_START = s2s;
        obj.START_TO_END = s2e;
        obj.END_TO_END = e2e;
        obj.END_TO_START = e2s;

        obj.NODE_BEFORE = n_b;
        obj.NODE_AFTER = n_a;
        obj.NODE_BEFORE_AND_AFTER = n_b_a;
        obj.NODE_INSIDE = n_i;
    }

    function copyComparisonConstants(constructor) {
        copyComparisonConstantsToObject(constructor);
        copyComparisonConstantsToObject(constructor.prototype);
    }

    function createRangeContentRemover(remover, boundaryUpdater) {
        return function() {
            assertRangeValid(this);

            var sc = this.startContainer, so = this.startOffset, root = this.commonAncestorContainer;

            var iterator = new RangeIterator(this, true);

            // Work out where to position the range after content removal
            var node, boundary;
            if (sc !== root) {
                node = dom.getClosestAncestorIn(sc, root, true);
                boundary = getBoundaryAfterNode(node);
                sc = boundary.node;
                so = boundary.offset;
            }

            // Check none of the range is read-only
            iterateSubtree(iterator, assertNodeNotReadOnly);

            iterator.reset();

            // Remove the content
            var returnValue = remover(iterator);
            iterator.detach();

            // Move to the new position
            boundaryUpdater(this, sc, so, sc, so);

            return returnValue;
        };
    }

    function createPrototypeRange(constructor, boundaryUpdater, detacher) {
        function createBeforeAfterNodeSetter(isBefore, isStart) {
            return function(node) {
                assertNotDetached(this);
                assertValidNodeType(node, beforeAfterNodeTypes);
                assertValidNodeType(getRootContainer(node), rootContainerNodeTypes);

                var boundary = (isBefore ? getBoundaryBeforeNode : getBoundaryAfterNode)(node);
                (isStart ? setRangeStart : setRangeEnd)(this, boundary.node, boundary.offset);
            };
        }

        function setRangeStart(range, node, offset) {
            var ec = range.endContainer, eo = range.endOffset;
            if (node !== range.startContainer || offset !== range.startOffset) {
                // Check the root containers of the range and the new boundary, and also check whether the new boundary
                // is after the current end. In either case, collapse the range to the new position
                if (getRootContainer(node) != getRootContainer(ec) || dom.comparePoints(node, offset, ec, eo) == 1) {
                    ec = node;
                    eo = offset;
                }
                boundaryUpdater(range, node, offset, ec, eo);
            }
        }

        function setRangeEnd(range, node, offset) {
            var sc = range.startContainer, so = range.startOffset;
            if (node !== range.endContainer || offset !== range.endOffset) {
                // Check the root containers of the range and the new boundary, and also check whether the new boundary
                // is after the current end. In either case, collapse the range to the new position
                if (getRootContainer(node) != getRootContainer(sc) || dom.comparePoints(node, offset, sc, so) == -1) {
                    sc = node;
                    so = offset;
                }
                boundaryUpdater(range, sc, so, node, offset);
            }
        }

        function setRangeStartAndEnd(range, node, offset) {
            if (node !== range.startContainer || offset !== range.startOffset || node !== range.endContainer || offset !== range.endOffset) {
                boundaryUpdater(range, node, offset, node, offset);
            }
        }

        constructor.prototype = new RangePrototype();

        api.util.extend(constructor.prototype, {
            setStart: function(node, offset) {
                assertNotDetached(this);
                assertNoDocTypeNotationEntityAncestor(node, true);
                assertValidOffset(node, offset);

                setRangeStart(this, node, offset);
            },

            setEnd: function(node, offset) {
                assertNotDetached(this);
                assertNoDocTypeNotationEntityAncestor(node, true);
                assertValidOffset(node, offset);

                setRangeEnd(this, node, offset);
            },

            setStartBefore: createBeforeAfterNodeSetter(true, true),
            setStartAfter: createBeforeAfterNodeSetter(false, true),
            setEndBefore: createBeforeAfterNodeSetter(true, false),
            setEndAfter: createBeforeAfterNodeSetter(false, false),

            collapse: function(isStart) {
                assertRangeValid(this);
                if (isStart) {
                    boundaryUpdater(this, this.startContainer, this.startOffset, this.startContainer, this.startOffset);
                } else {
                    boundaryUpdater(this, this.endContainer, this.endOffset, this.endContainer, this.endOffset);
                }
            },

            selectNodeContents: function(node) {
                // This doesn't seem well specified: the spec talks only about selecting the node's contents, which
                // could be taken to mean only its children. However, browsers implement this the same as selectNode for
                // text nodes, so I shall do likewise
                assertNotDetached(this);
                assertNoDocTypeNotationEntityAncestor(node, true);

                boundaryUpdater(this, node, 0, node, dom.getNodeLength(node));
            },

            selectNode: function(node) {
                assertNotDetached(this);
                assertNoDocTypeNotationEntityAncestor(node, false);
                assertValidNodeType(node, beforeAfterNodeTypes);

                var start = getBoundaryBeforeNode(node), end = getBoundaryAfterNode(node);
                boundaryUpdater(this, start.node, start.offset, end.node, end.offset);
            },

            extractContents: createRangeContentRemover(extractSubtree, boundaryUpdater),

            deleteContents: createRangeContentRemover(deleteSubtree, boundaryUpdater),

            canSurroundContents: function() {
                assertRangeValid(this);
                assertNodeNotReadOnly(this.startContainer);
                assertNodeNotReadOnly(this.endContainer);

                // Check if the contents can be surrounded. Specifically, this means whether the range partially selects
                // no non-text nodes.
                var iterator = new RangeIterator(this, true);
                var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) ||
                        (iterator._last && isNonTextPartiallySelected(iterator._last, this)));
                iterator.detach();
                return !boundariesInvalid;
            },

            detach: function() {
                detacher(this);
            },

            splitBoundaries: function() {
                assertRangeValid(this);


                var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset;
                var startEndSame = (sc === ec);

                if (dom.isCharacterDataNode(ec) && eo > 0 && eo < ec.length) {
                    dom.splitDataNode(ec, eo);

                }

                if (dom.isCharacterDataNode(sc) && so > 0 && so < sc.length) {

                    sc = dom.splitDataNode(sc, so);
                    if (startEndSame) {
                        eo -= so;
                        ec = sc;
                    } else if (ec == sc.parentNode && eo >= dom.getNodeIndex(sc)) {
                        eo++;
                    }
                    so = 0;

                }
                boundaryUpdater(this, sc, so, ec, eo);
            },

            normalizeBoundaries: function() {
                assertRangeValid(this);

                var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset;

                var mergeForward = function(node) {
                    var sibling = node.nextSibling;
                    if (sibling && sibling.nodeType == node.nodeType) {
                        ec = node;
                        eo = node.length;
                        node.appendData(sibling.data);
                        sibling.parentNode.removeChild(sibling);
                    }
                };

                var mergeBackward = function(node) {
                    var sibling = node.previousSibling;
                    if (sibling && sibling.nodeType == node.nodeType) {
                        sc = node;
                        var nodeLength = node.length;
                        so = sibling.length;
                        node.insertData(0, sibling.data);
                        sibling.parentNode.removeChild(sibling);
                        if (sc == ec) {
                            eo += so;
                            ec = sc;
                        } else if (ec == node.parentNode) {
                            var nodeIndex = dom.getNodeIndex(node);
                            if (eo == nodeIndex) {
                                ec = node;
                                eo = nodeLength;
                            } else if (eo > nodeIndex) {
                                eo--;
                            }
                        }
                    }
                };

                var normalizeStart = true;

                if (dom.isCharacterDataNode(ec)) {
                    if (ec.length == eo) {
                        mergeForward(ec);
                    }
                } else {
                    if (eo > 0) {
                        var endNode = ec.childNodes[eo - 1];
                        if (endNode && dom.isCharacterDataNode(endNode)) {
                            mergeForward(endNode);
                        }
                    }
                    normalizeStart = !this.collapsed;
                }

                if (normalizeStart) {
                    if (dom.isCharacterDataNode(sc)) {
                        if (so == 0) {
                            mergeBackward(sc);
                        }
                    } else {
                        if (so < sc.childNodes.length) {
                            var startNode = sc.childNodes[so];
                            if (startNode && dom.isCharacterDataNode(startNode)) {
                                mergeBackward(startNode);
                            }
                        }
                    }
                } else {
                    sc = ec;
                    so = eo;
                }

                boundaryUpdater(this, sc, so, ec, eo);
            },

            collapseToPoint: function(node, offset) {
                assertNotDetached(this);

                assertNoDocTypeNotationEntityAncestor(node, true);
                assertValidOffset(node, offset);

                setRangeStartAndEnd(this, node, offset);
            }
        });

        copyComparisonConstants(constructor);
    }

    /*----------------------------------------------------------------------------------------------------------------*/

    // Updates commonAncestorContainer and collapsed after boundary change
    function updateCollapsedAndCommonAncestor(range) {
        range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset);
        range.commonAncestorContainer = range.collapsed ?
            range.startContainer : dom.getCommonAncestor(range.startContainer, range.endContainer);
    }

    function updateBoundaries(range, startContainer, startOffset, endContainer, endOffset) {
        var startMoved = (range.startContainer !== startContainer || range.startOffset !== startOffset);
        var endMoved = (range.endContainer !== endContainer || range.endOffset !== endOffset);

        range.startContainer = startContainer;
        range.startOffset = startOffset;
        range.endContainer = endContainer;
        range.endOffset = endOffset;

        updateCollapsedAndCommonAncestor(range);
        dispatchEvent(range, "boundarychange", {startMoved: startMoved, endMoved: endMoved});
    }

    function detach(range) {
        assertNotDetached(range);
        range.startContainer = range.startOffset = range.endContainer = range.endOffset = null;
        range.collapsed = range.commonAncestorContainer = null;
        dispatchEvent(range, "detach", null);
        range._listeners = null;
    }

    /**
     * @constructor
     */
    function Range(doc) {
        this.startContainer = doc;
        this.startOffset = 0;
        this.endContainer = doc;
        this.endOffset = 0;
        this._listeners = {
            boundarychange: [],
            detach: []
        };
        updateCollapsedAndCommonAncestor(this);
    }

    createPrototypeRange(Range, updateBoundaries, detach);

    api.rangePrototype = RangePrototype.prototype;

    Range.rangeProperties = rangeProperties;
    Range.RangeIterator = RangeIterator;
    Range.copyComparisonConstants = copyComparisonConstants;
    Range.createPrototypeRange = createPrototypeRange;
    Range.inspect = inspect;
    Range.getRangeDocument = getRangeDocument;
    Range.rangesEqual = function(r1, r2) {
        return r1.startContainer === r2.startContainer &&
               r1.startOffset === r2.startOffset &&
               r1.endContainer === r2.endContainer &&
               r1.endOffset === r2.endOffset;
    };

    api.DomRange = Range;
    api.RangeException = RangeException;
});rangy.createModule("WrappedRange", function(api, module) {
    api.requireModules( ["DomUtil", "DomRange"] );

    /**
     * @constructor
     */
    var WrappedRange;
    var dom = api.dom;
    var DomPosition = dom.DomPosition;
    var DomRange = api.DomRange;



    /*----------------------------------------------------------------------------------------------------------------*/

    /*
    This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement()
    method. For example, in the following (where pipes denote the selection boundaries):

    <ul id="ul"><li id="a">| a </li><li id="b"> b |</li></ul>

    var range = document.selection.createRange();
    alert(range.parentElement().id); // Should alert "ul" but alerts "b"

    This method returns the common ancestor node of the following:
    - the parentElement() of the textRange
    - the parentElement() of the textRange after calling collapse(true)
    - the parentElement() of the textRange after calling collapse(false)
     */
    function getTextRangeContainerElement(textRange) {
        var parentEl = textRange.parentElement();

        var range = textRange.duplicate();
        range.collapse(true);
        var startEl = range.parentElement();
        range = textRange.duplicate();
        range.collapse(false);
        var endEl = range.parentElement();
        var startEndContainer = (startEl == endEl) ? startEl : dom.getCommonAncestor(startEl, endEl);

        return startEndContainer == parentEl ? startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer);
    }

    function textRangeIsCollapsed(textRange) {
        return textRange.compareEndPoints("StartToEnd", textRange) == 0;
    }

    // Gets the boundary of a TextRange expressed as a node and an offset within that node. This function started out as
    // an improved version of code found in Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/) but has
    // grown, fixing problems with line breaks in preformatted text, adding workaround for IE TextRange bugs, handling
    // for inputs and images, plus optimizations.
    function getTextRangeBoundaryPosition(textRange, wholeRangeContainerElement, isStart, isCollapsed) {
        var workingRange = textRange.duplicate();

        workingRange.collapse(isStart);
        var containerElement = workingRange.parentElement();

        // Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so
        // check for that
        // TODO: Find out when. Workaround for wholeRangeContainerElement may break this
        if (!dom.isAncestorOf(wholeRangeContainerElement, containerElement, true)) {
            containerElement = wholeRangeContainerElement;

        }



        // Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and
        // similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx
        if (!containerElement.canHaveHTML) {
            return new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement));
        }

        var workingNode = dom.getDocument(containerElement).createElement("span");
        var comparison, workingComparisonType = isStart ? "StartToStart" : "StartToEnd";
        var previousNode, nextNode, boundaryPosition, boundaryNode;

        // Move the working range through the container's children, starting at the end and working backwards, until the
        // working range reaches or goes past the boundary we're interested in
        do {
            containerElement.insertBefore(workingNode, workingNode.previousSibling);
            workingRange.moveToElementText(workingNode);
        } while ( (comparison = workingRange.compareEndPoints(workingComparisonType, textRange)) > 0 &&
                workingNode.previousSibling);

        // We've now reached or gone past the boundary of the text range we're interested in
        // so have identified the node we want
        boundaryNode = workingNode.nextSibling;

        if (comparison == -1 && boundaryNode && dom.isCharacterDataNode(boundaryNode)) {
            // This is a character data node (text, comment, cdata). The working range is collapsed at the start of the
            // node containing the text range's boundary, so we move the end of the working range to the boundary point
            // and measure the length of its text to get the boundary's offset within the node.
            workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange);


            var offset;

            if (/[\r\n]/.test(boundaryNode.data)) {
                /*
                For the particular case of a boundary within a text node containing line breaks (within a <pre> element,
                for example), we need a slightly complicated approach to get the boundary's offset in IE. The facts:

                - Each line break is represented as \r in the text node's data/nodeValue properties
                - Each line break is represented as \r\n in the TextRange's 'text' property
                - The 'text' property of the TextRange does not contain trailing line breaks

                To get round the problem presented by the final fact above, we can use the fact that TextRange's
                moveStart() and moveEnd() methods return the actual number of characters moved, which is not necessarily
                the same as the number of characters it was instructed to move. The simplest approach is to use this to
                store the characters moved when moving both the start and end of the range to the start of the document
                body and subtracting the start offset from the end offset (the "move-negative-gazillion" method).
                However, this is extremely slow when the document is large and the range is near the end of it. Clearly
                doing the mirror image (i.e. moving the range boundaries to the end of the document) has the same
                problem.

                Another approach that works is to use moveStart() to move the start boundary of the range up to the end
                boundary one character at a time and incrementing a counter with the value returned by the moveStart()
                call. However, the check for whether the start boundary has reached the end boundary is expensive, so
                this method is slow (although unlike "move-negative-gazillion" is largely unaffected by the location of
                the range within the document).

                The method below is a hybrid of the two methods above. It uses the fact that a string containing the
                TextRange's 'text' property with each \r\n converted to a single \r character cannot be longer than the
                text of the TextRange, so the start of the range is moved that length initially and then a character at
                a time to make up for any trailing line breaks not contained in the 'text' property. This has good
                performance in most situations compared to the previous two methods.
                */
                var tempRange = workingRange.duplicate();
                var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length;

                offset = tempRange.moveStart("character", rangeLength);
                while ( (comparison = tempRange.compareEndPoints("StartToEnd", tempRange)) == -1) {
                    offset++;
                    tempRange.moveStart("character", 1);
                }
            } else {
                offset = workingRange.text.length;
            }
            boundaryPosition = new DomPosition(boundaryNode, offset);
        } else {


            // If the boundary immediately follows a character data node and this is the end boundary, we should favour
            // a position within that, and likewise for a start boundary preceding a character data node
            previousNode = (isCollapsed || !isStart) && workingNode.previousSibling;
            nextNode = (isCollapsed || isStart) && workingNode.nextSibling;



            if (nextNode && dom.isCharacterDataNode(nextNode)) {
                boundaryPosition = new DomPosition(nextNode, 0);
            } else if (previousNode && dom.isCharacterDataNode(previousNode)) {
                boundaryPosition = new DomPosition(previousNode, previousNode.length);
            } else {
                boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode));
            }
        }

        // Clean up
        workingNode.parentNode.removeChild(workingNode);

        return boundaryPosition;
    }

    // Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that node.
    // This function started out as an optimized version of code found in Tim Cameron Ryan's IERange
    // (http://code.google.com/p/ierange/)
    function createBoundaryTextRange(boundaryPosition, isStart) {
        var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset;
        var doc = dom.getDocument(boundaryPosition.node);
        var workingNode, childNodes, workingRange = doc.body.createTextRange();
        var nodeIsDataNode = dom.isCharacterDataNode(boundaryPosition.node);

        if (nodeIsDataNode) {
            boundaryNode = boundaryPosition.node;
            boundaryParent = boundaryNode.parentNode;
        } else {
            childNodes = boundaryPosition.node.childNodes;
            boundaryNode = (boundaryOffset < childNodes.length) ? childNodes[boundaryOffset] : null;
            boundaryParent = boundaryPosition.node;
        }

        // Position the range immediately before the node containing the boundary
        workingNode = doc.createElement("span");

        // Making the working element non-empty element persuades IE to consider the TextRange boundary to be within the
        // element rather than immediately before or after it, which is what we want
        workingNode.innerHTML = "&#feff;";

        // insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report
        // for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12
        if (boundaryNode) {
            boundaryParent.insertBefore(workingNode, boundaryNode);
        } else {
            boundaryParent.appendChild(workingNode);
        }

        workingRange.moveToElementText(workingNode);
        workingRange.collapse(!isStart);

        // Clean up
        boundaryParent.removeChild(workingNode);

        // Move the working range to the text offset, if required
        if (nodeIsDataNode) {
            workingRange[isStart ? "moveStart" : "moveEnd"]("character", boundaryOffset);
        }

        return workingRange;
    }

    /*----------------------------------------------------------------------------------------------------------------*/

    if (api.features.implementsDomRange && (!api.features.implementsTextRange || !api.config.preferTextRange)) {
        // This is a wrapper around the browser's native DOM Range. It has two aims:
        // - Provide workarounds for specific browser bugs
        // - provide convenient extensions, which are inherited from Rangy's DomRange

        (function() {
            var rangeProto;
            var rangeProperties = DomRange.rangeProperties;
            var canSetRangeStartAfterEnd;

            function updateRangeProperties(range) {
                var i = rangeProperties.length, prop;
                while (i--) {
                    prop = rangeProperties[i];
                    range[prop] = range.nativeRange[prop];
                }
            }

            function updateNativeRange(range, startContainer, startOffset, endContainer,endOffset) {
                var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset);
                var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset);

                // Always set both boundaries for the benefit of IE9 (see issue 35)
                if (startMoved || endMoved) {
                    range.setEnd(endContainer, endOffset);
                    range.setStart(startContainer, startOffset);
                }
            }

            function detach(range) {
                range.nativeRange.detach();
                range.detached = true;
                var i = rangeProperties.length, prop;
                while (i--) {
                    prop = rangeProperties[i];
                    range[prop] = null;
                }
            }

            var createBeforeAfterNodeSetter;

            WrappedRange = function(range) {
                if (!range) {
                    throw new Error("Range must be specified");
                }
                this.nativeRange = range;
                updateRangeProperties(this);
            };

            DomRange.createPrototypeRange(WrappedRange, updateNativeRange, detach);

            rangeProto = WrappedRange.prototype;

            rangeProto.selectNode = function(node) {
                this.nativeRange.selectNode(node);
                updateRangeProperties(this);
            };

            rangeProto.deleteContents = function() {
                this.nativeRange.deleteContents();
                updateRangeProperties(this);
            };

            rangeProto.extractContents = function() {
                var frag = this.nativeRange.extractContents();
                updateRangeProperties(this);
                return frag;
            };

            rangeProto.cloneContents = function() {
                return this.nativeRange.cloneContents();
            };

            // TODO: Until I can find a way to programmatically trigger the Firefox bug (apparently long-standing, still
            // present in 3.6.8) that throws "Index or size is negative or greater than the allowed amount" for
            // insertNode in some circumstances, all browsers will have to use the Rangy's own implementation of
            // insertNode, which works but is almost certainly slower than the native implementation.
/*
            rangeProto.insertNode = function(node) {
                this.nativeRange.insertNode(node);
                updateRangeProperties(this);
            };
*/

            rangeProto.surroundContents = function(node) {
                this.nativeRange.surroundContents(node);
                updateRangeProperties(this);
            };

            rangeProto.collapse = function(isStart) {
                this.nativeRange.collapse(isStart);
                updateRangeProperties(this);
            };

            rangeProto.cloneRange = function() {
                return new WrappedRange(this.nativeRange.cloneRange());
            };

            rangeProto.refresh = function() {
                updateRangeProperties(this);
            };

            rangeProto.toString = function() {
                return this.nativeRange.toString();
            };

            // Create test range and node for feature detection

            var testTextNode = document.createTextNode("test");
            dom.getBody(document).appendChild(testTextNode);
            var range = document.createRange();

            /*--------------------------------------------------------------------------------------------------------*/

            // Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and
            // correct for it

            range.setStart(testTextNode, 0);
            range.setEnd(testTextNode, 0);

            try {
                range.setStart(testTextNode, 1);
                canSetRangeStartAfterEnd = true;

                rangeProto.setStart = function(node, offset) {
                    this.nativeRange.setStart(node, offset);
                    updateRangeProperties(this);
                };

                rangeProto.setEnd = function(node, offset) {
                    this.nativeRange.setEnd(node, offset);
                    updateRangeProperties(this);
                };

                createBeforeAfterNodeSetter = function(name) {
                    return function(node) {
                        this.nativeRange[name](node);
                        updateRangeProperties(this);
                    };
                };

            } catch(ex) {


                canSetRangeStartAfterEnd = false;

                rangeProto.setStart = function(node, offset) {
                    try {
                        this.nativeRange.setStart(node, offset);
                    } catch (ex) {
                        this.nativeRange.setEnd(node, offset);
                        this.nativeRange.setStart(node, offset);
                    }
                    updateRangeProperties(this);
                };

                rangeProto.setEnd = function(node, offset) {
                    try {
                        this.nativeRange.setEnd(node, offset);
                    } catch (ex) {
                        this.nativeRange.setStart(node, offset);
                        this.nativeRange.setEnd(node, offset);
                    }
                    updateRangeProperties(this);
                };

                createBeforeAfterNodeSetter = function(name, oppositeName) {
                    return function(node) {
                        try {
                            this.nativeRange[name](node);
                        } catch (ex) {
                            this.nativeRange[oppositeName](node);
                            this.nativeRange[name](node);
                        }
                        updateRangeProperties(this);
                    };
                };
            }

            rangeProto.setStartBefore = createBeforeAfterNodeSetter("setStartBefore", "setEndBefore");
            rangeProto.setStartAfter = createBeforeAfterNodeSetter("setStartAfter", "setEndAfter");
            rangeProto.setEndBefore = createBeforeAfterNodeSetter("setEndBefore", "setStartBefore");
            rangeProto.setEndAfter = createBeforeAfterNodeSetter("setEndAfter", "setStartAfter");

            /*--------------------------------------------------------------------------------------------------------*/

            // Test for and correct Firefox 2 behaviour with selectNodeContents on text nodes: it collapses the range to
            // the 0th character of the text node
            range.selectNodeContents(testTextNode);
            if (range.startContainer == testTextNode && range.endContainer == testTextNode &&
                    range.startOffset == 0 && range.endOffset == testTextNode.length) {
                rangeProto.selectNodeContents = function(node) {
                    this.nativeRange.selectNodeContents(node);
                    updateRangeProperties(this);
                };
            } else {
                rangeProto.selectNodeContents = function(node) {
                    this.setStart(node, 0);
                    this.setEnd(node, DomRange.getEndOffset(node));
                };
            }

            /*--------------------------------------------------------------------------------------------------------*/

            // Test for WebKit bug that has the beahviour of compareBoundaryPoints round the wrong way for constants
            // START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738

            range.selectNodeContents(testTextNode);
            range.setEnd(testTextNode, 3);

            var range2 = document.createRange();
            range2.selectNodeContents(testTextNode);
            range2.setEnd(testTextNode, 4);
            range2.setStart(testTextNode, 2);

            if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 &
                    range.compareBoundaryPoints(range.END_TO_START, range2) == 1) {
                // This is the wrong way round, so correct for it


                rangeProto.compareBoundaryPoints = function(type, range) {
                    range = range.nativeRange || range;
                    if (type == range.START_TO_END) {
                        type = range.END_TO_START;
                    } else if (type == range.END_TO_START) {
                        type = range.START_TO_END;
                    }
                    return this.nativeRange.compareBoundaryPoints(type, range);
                };
            } else {
                rangeProto.compareBoundaryPoints = function(type, range) {
                    return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range);
                };
            }

            /*--------------------------------------------------------------------------------------------------------*/

            // Test for existence of createContextualFragment and delegate to it if it exists
            if (api.util.isHostMethod(range, "createContextualFragment")) {
                rangeProto.createContextualFragment = function(fragmentStr) {
                    return this.nativeRange.createContextualFragment(fragmentStr);
                };
            }

            /*--------------------------------------------------------------------------------------------------------*/

            // Clean up
            dom.getBody(document).removeChild(testTextNode);
            range.detach();
            range2.detach();
        })();

        api.createNativeRange = function(doc) {
            doc = doc || document;
            return doc.createRange();
        };
    } else if (api.features.implementsTextRange) {
        // This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a
        // prototype

        WrappedRange = function(textRange) {
            this.textRange = textRange;
            this.refresh();
        };

        WrappedRange.prototype = new DomRange(document);

        WrappedRange.prototype.refresh = function() {
            var start, end;

            // TextRange's parentElement() method cannot be trusted. getTextRangeContainerElement() works around that.
            var rangeContainerElement = getTextRangeContainerElement(this.textRange);

            if (textRangeIsCollapsed(this.textRange)) {
                end = start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, true);
            } else {

                start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, false);
                end = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, false, false);
            }

            this.setStart(start.node, start.offset);
            this.setEnd(end.node, end.offset);
        };

        DomRange.copyComparisonConstants(WrappedRange);

        // Add WrappedRange as the Range property of the global object to allow expression like Range.END_TO_END to work
        var globalObj = (function() { return this; })();
        if (typeof globalObj.Range == "undefined") {
            globalObj.Range = WrappedRange;
        }

        api.createNativeRange = function(doc) {
            doc = doc || document;
            return doc.body.createTextRange();
        };
    }

    if (api.features.implementsTextRange) {
        WrappedRange.rangeToTextRange = function(range) {
            if (range.collapsed) {
                var tr = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);



                return tr;

                //return createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
            } else {
                var startRange = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
                var endRange = createBoundaryTextRange(new DomPosition(range.endContainer, range.endOffset), false);
                var textRange = dom.getDocument(range.startContainer).body.createTextRange();
                textRange.setEndPoint("StartToStart", startRange);
                textRange.setEndPoint("EndToEnd", endRange);
                return textRange;
            }
        };
    }

    WrappedRange.prototype.getName = function() {
        return "WrappedRange";
    };

    api.WrappedRange = WrappedRange;

    api.createRange = function(doc) {
        doc = doc || document;
        return new WrappedRange(api.createNativeRange(doc));
    };

    api.createRangyRange = function(doc) {
        doc = doc || document;
        return new DomRange(doc);
    };

    api.createIframeRange = function(iframeEl) {
        return api.createRange(dom.getIframeDocument(iframeEl));
    };

    api.createIframeRangyRange = function(iframeEl) {
        return api.createRangyRange(dom.getIframeDocument(iframeEl));
    };

    api.addCreateMissingNativeApiListener(function(win) {
        var doc = win.document;
        if (typeof doc.createRange == "undefined") {
            doc.createRange = function() {
                return api.createRange(this);
            };
        }
        doc = win = null;
    });
});rangy.createModule("WrappedSelection", function(api, module) {
    // This will create a selection object wrapper that follows the Selection object found in the WHATWG draft DOM Range
    // spec (http://html5.org/specs/dom-range.html)

    api.requireModules( ["DomUtil", "DomRange", "WrappedRange"] );

    api.config.checkSelectionRanges = true;

    var BOOLEAN = "boolean",
        windowPropertyName = "_rangySelection",
        dom = api.dom,
        util = api.util,
        DomRange = api.DomRange,
        WrappedRange = api.WrappedRange,
        DOMException = api.DOMException,
        DomPosition = dom.DomPosition,
        getSelection,
        selectionIsCollapsed,
        CONTROL = "Control";



    function getWinSelection(winParam) {
        return (winParam || window).getSelection();
    }

    function getDocSelection(winParam) {
        return (winParam || window).document.selection;
    }

    // Test for the Range/TextRange and Selection features required
    // Test for ability to retrieve selection
    var implementsWinGetSelection = api.util.isHostMethod(window, "getSelection"),
        implementsDocSelection = api.util.isHostObject(document, "selection");

    var useDocumentSelection = implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange);

    if (useDocumentSelection) {
        getSelection = getDocSelection;
        api.isSelectionValid = function(winParam) {
            var doc = (winParam || window).document, nativeSel = doc.selection;

            // Check whether the selection TextRange is actually contained within the correct document
            return (nativeSel.type != "None" || dom.getDocument(nativeSel.createRange().parentElement()) == doc);
        };
    } else if (implementsWinGetSelection) {
        getSelection = getWinSelection;
        api.isSelectionValid = function() {
            return true;
        };
    } else {
        module.fail("Neither document.selection or window.getSelection() detected.");
    }

    api.getNativeSelection = getSelection;

    var testSelection = getSelection();
    var testRange = api.createNativeRange(document);
    var body = dom.getBody(document);

    // Obtaining a range from a selection
    var selectionHasAnchorAndFocus = util.areHostObjects(testSelection, ["anchorNode", "focusNode"] &&
                                     util.areHostProperties(testSelection, ["anchorOffset", "focusOffset"]));
    api.features.selectionHasAnchorAndFocus = selectionHasAnchorAndFocus;

    // Test for existence of native selection extend() method
    var selectionHasExtend = util.isHostMethod(testSelection, "extend");
    api.features.selectionHasExtend = selectionHasExtend;

    // Test if rangeCount exists
    var selectionHasRangeCount = (typeof testSelection.rangeCount == "number");
    api.features.selectionHasRangeCount = selectionHasRangeCount;

    var selectionSupportsMultipleRanges = false;
    var collapsedNonEditableSelectionsSupported = true;

    if (util.areHostMethods(testSelection, ["addRange", "getRangeAt", "removeAllRanges"]) &&
            typeof testSelection.rangeCount == "number" && api.features.implementsDomRange) {

        (function() {
            var iframe = document.createElement("iframe");
            iframe.frameBorder = 0;
            iframe.style.position = "absolute";
            iframe.style.left = "-10000px";
            body.appendChild(iframe);

            var iframeDoc = dom.getIframeDocument(iframe);
            iframeDoc.open();
            iframeDoc.write("<html><head></head><body>12</body></html>");
            iframeDoc.close();

            var sel = dom.getIframeWindow(iframe).getSelection();
            var docEl = iframeDoc.documentElement;
            var iframeBody = docEl.lastChild, textNode = iframeBody.firstChild;

            // Test whether the native selection will allow a collapsed selection within a non-editable element
            var r1 = iframeDoc.createRange();
            r1.setStart(textNode, 1);
            r1.collapse(true);
            sel.addRange(r1);
            collapsedNonEditableSelectionsSupported = (sel.rangeCount == 1);
            sel.removeAllRanges();

            // Test whether the native selection is capable of supporting multiple ranges
            var r2 = r1.cloneRange();
            r1.setStart(textNode, 0);
            r2.setEnd(textNode, 2);
            sel.addRange(r1);
            sel.addRange(r2);

            selectionSupportsMultipleRanges = (sel.rangeCount == 2);

            // Clean up
            r1.detach();
            r2.detach();

            body.removeChild(iframe);
        })();
    }

    api.features.selectionSupportsMultipleRanges = selectionSupportsMultipleRanges;
    api.features.collapsedNonEditableSelectionsSupported = collapsedNonEditableSelectionsSupported;

    // ControlRanges
    var implementsControlRange = false, testControlRange;

    if (body && util.isHostMethod(body, "createControlRange")) {
        testControlRange = body.createControlRange();
        if (util.areHostProperties(testControlRange, ["item", "add"])) {
            implementsControlRange = true;
        }
    }
    api.features.implementsControlRange = implementsControlRange;

    // Selection collapsedness
    if (selectionHasAnchorAndFocus) {
        selectionIsCollapsed = function(sel) {
            return sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset;
        };
    } else {
        selectionIsCollapsed = function(sel) {
            return sel.rangeCount ? sel.getRangeAt(sel.rangeCount - 1).collapsed : false;
        };
    }

    function updateAnchorAndFocusFromRange(sel, range, backwards) {
        var anchorPrefix = backwards ? "end" : "start", focusPrefix = backwards ? "start" : "end";
        sel.anchorNode = range[anchorPrefix + "Container"];
        sel.anchorOffset = range[anchorPrefix + "Offset"];
        sel.focusNode = range[focusPrefix + "Container"];
        sel.focusOffset = range[focusPrefix + "Offset"];
    }

    function updateAnchorAndFocusFromNativeSelection(sel) {
        var nativeSel = sel.nativeSelection;
        sel.anchorNode = nativeSel.anchorNode;
        sel.anchorOffset = nativeSel.anchorOffset;
        sel.focusNode = nativeSel.focusNode;
        sel.focusOffset = nativeSel.focusOffset;
    }

    function updateEmptySelection(sel) {
        sel.anchorNode = sel.focusNode = null;
        sel.anchorOffset = sel.focusOffset = 0;
        sel.rangeCount = 0;
        sel.isCollapsed = true;
        sel._ranges.length = 0;
    }

    function getNativeRange(range) {
        var nativeRange;
        if (range instanceof DomRange) {
            nativeRange = range._selectionNativeRange;
            if (!nativeRange) {
                nativeRange = api.createNativeRange(dom.getDocument(range.startContainer));
                nativeRange.setEnd(range.endContainer, range.endOffset);
                nativeRange.setStart(range.startContainer, range.startOffset);
                range._selectionNativeRange = nativeRange;
                range.attachListener("detach", function() {

                    this._selectionNativeRange = null;
                });
            }
        } else if (range instanceof WrappedRange) {
            nativeRange = range.nativeRange;
        } else if (api.features.implementsDomRange && (range instanceof dom.getWindow(range.startContainer).Range)) {
            nativeRange = range;
        }
        return nativeRange;
    }

    function rangeContainsSingleElement(rangeNodes) {
        if (!rangeNodes.length || rangeNodes[0].nodeType != 1) {
            return false;
        }
        for (var i = 1, len = rangeNodes.length; i < len; ++i) {
            if (!dom.isAncestorOf(rangeNodes[0], rangeNodes[i])) {
                return false;
            }
        }
        return true;
    }

    function getSingleElementFromRange(range) {
        var nodes = range.getNodes();
        if (!rangeContainsSingleElement(nodes)) {
            throw new Error("getSingleElementFromRange: range " + range.inspect() + " did not consist of a single element");
        }
        return nodes[0];
    }

    function isTextRange(range) {
        return !!range && typeof range.text != "undefined";
    }

    function updateFromTextRange(sel, range) {
        // Create a Range from the selected TextRange
        var wrappedRange = new WrappedRange(range);
        sel._ranges = [wrappedRange];

        updateAnchorAndFocusFromRange(sel, wrappedRange, false);
        sel.rangeCount = 1;
        sel.isCollapsed = wrappedRange.collapsed;
    }

    function updateControlSelection(sel) {
        // Update the wrapped selection based on what's now in the native selection
        sel._ranges.length = 0;
        if (sel.docSelection.type == "None") {
            updateEmptySelection(sel);
        } else {
            var controlRange = sel.docSelection.createRange();
            if (isTextRange(controlRange)) {
                // This case (where the selection type is "Control" and calling createRange() on the selection returns
                // a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected
                // ControlRange have been removed from the ControlRange and removed from the document.
                updateFromTextRange(sel, controlRange);
            } else {
                sel.rangeCount = controlRange.length;
                var range, doc = dom.getDocument(controlRange.item(0));
                for (var i = 0; i < sel.rangeCount; ++i) {
                    range = api.createRange(doc);
                    range.selectNode(controlRange.item(i));
                    sel._ranges.push(range);
                }
                sel.isCollapsed = sel.rangeCount == 1 && sel._ranges[0].collapsed;
                updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], false);
            }
        }
    }

    function addRangeToControlSelection(sel, range) {
        var controlRange = sel.docSelection.createRange();
        var rangeElement = getSingleElementFromRange(range);

        // Create a new ControlRange containing all the elements in the selected ControlRange plus the element
        // contained by the supplied range
        var doc = dom.getDocument(controlRange.item(0));
        var newControlRange = dom.getBody(doc).createControlRange();
        for (var i = 0, len = controlRange.length; i < len; ++i) {
            newControlRange.add(controlRange.item(i));
        }
        try {
            newControlRange.add(rangeElement);
        } catch (ex) {
            throw new Error("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)");
        }
        newControlRange.select();

        // Update the wrapped selection based on what's now in the native selection
        updateControlSelection(sel);
    }

    var getSelectionRangeAt;

    if (util.isHostMethod(testSelection,  "getRangeAt")) {
        getSelectionRangeAt = function(sel, index) {
            try {
                return sel.getRangeAt(index);
            } catch(ex) {
                return null;
            }
        };
    } else if (selectionHasAnchorAndFocus) {
        getSelectionRangeAt = function(sel) {
            var doc = dom.getDocument(sel.anchorNode);
            var range = api.createRange(doc);
            range.setStart(sel.anchorNode, sel.anchorOffset);
            range.setEnd(sel.focusNode, sel.focusOffset);

            // Handle the case when the selection was selected backwards (from the end to the start in the
            // document)
            if (range.collapsed !== this.isCollapsed) {
                range.setStart(sel.focusNode, sel.focusOffset);
                range.setEnd(sel.anchorNode, sel.anchorOffset);
            }

            return range;
        };
    }

    /**
     * @constructor
     */
    function WrappedSelection(selection, docSelection, win) {
        this.nativeSelection = selection;
        this.docSelection = docSelection;
        this._ranges = [];
        this.win = win;
        this.refresh();
    }

    api.getSelection = function(win) {
        win = win || window;
        var sel = win[windowPropertyName];
        var nativeSel = getSelection(win), docSel = implementsDocSelection ? getDocSelection(win) : null;
        if (sel) {
            sel.nativeSelection = nativeSel;
            sel.docSelection = docSel;
            sel.refresh(win);
        } else {
            sel = new WrappedSelection(nativeSel, docSel, win);
            win[windowPropertyName] = sel;
        }
        return sel;
    };

    api.getIframeSelection = function(iframeEl) {
        return api.getSelection(dom.getIframeWindow(iframeEl));
    };

    var selProto = WrappedSelection.prototype;

    function createControlSelection(sel, ranges) {
        // Ensure that the selection becomes of type "Control"
        var doc = dom.getDocument(ranges[0].startContainer);
        var controlRange = dom.getBody(doc).createControlRange();
        for (var i = 0, el; i < rangeCount; ++i) {
            el = getSingleElementFromRange(ranges[i]);
            try {
                controlRange.add(el);
            } catch (ex) {
                throw new Error("setRanges(): Element within the one of the specified Ranges could not be added to control selection (does it have layout?)");
            }
        }
        controlRange.select();

        // Update the wrapped selection based on what's now in the native selection
        updateControlSelection(sel);
    }

    // Selecting a range
    if (!useDocumentSelection && selectionHasAnchorAndFocus && util.areHostMethods(testSelection, ["removeAllRanges", "addRange"])) {
        selProto.removeAllRanges = function() {
            this.nativeSelection.removeAllRanges();
            updateEmptySelection(this);
        };

        var addRangeBackwards = function(sel, range) {
            var doc = DomRange.getRangeDocument(range);
            var endRange = api.createRange(doc);
            endRange.collapseToPoint(range.endContainer, range.endOffset);
            sel.nativeSelection.addRange(getNativeRange(endRange));
            sel.nativeSelection.extend(range.startContainer, range.startOffset);
            sel.refresh();
        };

        if (selectionHasRangeCount) {
            selProto.addRange = function(range, backwards) {
                if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
                    addRangeToControlSelection(this, range);
                } else {
                    if (backwards && selectionHasExtend) {
                        addRangeBackwards(this, range);
                    } else {
                        var previousRangeCount;
                        if (selectionSupportsMultipleRanges) {
                            previousRangeCount = this.rangeCount;
                        } else {
                            this.removeAllRanges();
                            previousRangeCount = 0;
                        }
                        this.nativeSelection.addRange(getNativeRange(range));

                        // Check whether adding the range was successful
                        this.rangeCount = this.nativeSelection.rangeCount;

                        if (this.rangeCount == previousRangeCount + 1) {
                            // The range was added successfully

                            // Check whether the range that we added to the selection is reflected in the last range extracted from
                            // the selection
                            if (api.config.checkSelectionRanges) {
                                var nativeRange = getSelectionRangeAt(this.nativeSelection, this.rangeCount - 1);
                                if (nativeRange && !DomRange.rangesEqual(nativeRange, range)) {
                                    // Happens in WebKit with, for example, a selection placed at the start of a text node
                                    range = new WrappedRange(nativeRange);
                                }
                            }
                            this._ranges[this.rangeCount - 1] = range;
                            updateAnchorAndFocusFromRange(this, range, selectionIsBackwards(this.nativeSelection));
                            this.isCollapsed = selectionIsCollapsed(this);
                        } else {
                            // The range was not added successfully. The simplest thing is to refresh
                            this.refresh();
                        }
                    }
                }
            };
        } else {
            selProto.addRange = function(range, backwards) {
                if (backwards && selectionHasExtend) {
                    addRangeBackwards(this, range);
                } else {
                    this.nativeSelection.addRange(getNativeRange(range));
                    this.refresh();
                }
            };
        }

        selProto.setRanges = function(ranges) {
            if (implementsControlRange && ranges.length > 1) {
                createControlSelection(this, ranges);
            } else {
                this.removeAllRanges();
                for (var i = 0, len = ranges.length; i < len; ++i) {
                    this.addRange(ranges[i]);
                }
            }
        };
    } else if (util.isHostMethod(testSelection, "empty") && util.isHostMethod(testRange, "select") &&
               implementsControlRange && useDocumentSelection) {

        selProto.removeAllRanges = function() {
            // Added try/catch as fix for issue #21
            try {
                this.docSelection.empty();

                // Check for empty() not working (issue #24)
                if (this.docSelection.type != "None") {
                    // Work around failure to empty a control selection by instead selecting a TextRange and then
                    // calling empty()
                    var doc;
                    if (this.anchorNode) {
                        doc = dom.getDocument(this.anchorNode);
                    } else if (this.docSelection.type == CONTROL) {
                        var controlRange = this.docSelection.createRange();
                        if (controlRange.length) {
                            doc = dom.getDocument(controlRange.item(0)).body.createTextRange();
                        }
                    }
                    if (doc) {
                        var textRange = doc.body.createTextRange();
                        textRange.select();
                        this.docSelection.empty();
                    }
                }
            } catch(ex) {}
            updateEmptySelection(this);
        };

        selProto.addRange = function(range) {
            if (this.docSelection.type == CONTROL) {
                addRangeToControlSelection(this, range);
            } else {
                WrappedRange.rangeToTextRange(range).select();
                this._ranges[0] = range;
                this.rangeCount = 1;
                this.isCollapsed = this._ranges[0].collapsed;
                updateAnchorAndFocusFromRange(this, range, false);
            }
        };

        selProto.setRanges = function(ranges) {
            this.removeAllRanges();
            var rangeCount = ranges.length;
            if (rangeCount > 1) {
                createControlSelection(this, ranges);
            } else if (rangeCount) {
                this.addRange(ranges[0]);
            }
        };
    } else {
        module.fail("No means of selecting a Range or TextRange was found");
        return false;
    }

    selProto.getRangeAt = function(index) {
        if (index < 0 || index >= this.rangeCount) {
            throw new DOMException("INDEX_SIZE_ERR");
        } else {
            return this._ranges[index];
        }
    };

    var refreshSelection;

    if (useDocumentSelection) {
        refreshSelection = function(sel) {
            var range;
            if (api.isSelectionValid(sel.win)) {
                range = sel.docSelection.createRange();
            } else {
                range = dom.getBody(sel.win.document).createTextRange();
                range.collapse(true);
            }


            if (sel.docSelection.type == CONTROL) {
                updateControlSelection(sel);
            } else if (isTextRange(range)) {
                updateFromTextRange(sel, range);
            } else {
                updateEmptySelection(sel);
            }
        };
    } else if (util.isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == "number") {
        refreshSelection = function(sel) {
            if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) {
                updateControlSelection(sel);
            } else {
                sel._ranges.length = sel.rangeCount = sel.nativeSelection.rangeCount;
                if (sel.rangeCount) {
                    for (var i = 0, len = sel.rangeCount; i < len; ++i) {
                        sel._ranges[i] = new api.WrappedRange(sel.nativeSelection.getRangeAt(i));
                    }
                    updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], selectionIsBackwards(sel.nativeSelection));
                    sel.isCollapsed = selectionIsCollapsed(sel);
                } else {
                    updateEmptySelection(sel);
                }
            }
        };
    } else if (selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && api.features.implementsDomRange) {
        refreshSelection = function(sel) {
            var range, nativeSel = sel.nativeSelection;
            if (nativeSel.anchorNode) {
                range = getSelectionRangeAt(nativeSel, 0);
                sel._ranges = [range];
                sel.rangeCount = 1;
                updateAnchorAndFocusFromNativeSelection(sel);
                sel.isCollapsed = selectionIsCollapsed(sel);
            } else {
                updateEmptySelection(sel);
            }
        };
    } else {
        module.fail("No means of obtaining a Range or TextRange from the user's selection was found");
        return false;
    }

    selProto.refresh = function(checkForChanges) {
        var oldRanges = checkForChanges ? this._ranges.slice(0) : null;
        refreshSelection(this);
        if (checkForChanges) {
            var i = oldRanges.length;
            if (i != this._ranges.length) {
                return false;
            }
            while (i--) {
                if (!DomRange.rangesEqual(oldRanges[i], this._ranges[i])) {
                    return false;
                }
            }
            return true;
        }
    };

    // Removal of a single range
    var removeRangeManually = function(sel, range) {
        var ranges = sel.getAllRanges(), removed = false;
        sel.removeAllRanges();
        for (var i = 0, len = ranges.length; i < len; ++i) {
            if (removed || range !== ranges[i]) {
                sel.addRange(ranges[i]);
            } else {
                // According to the draft WHATWG Range spec, the same range may be added to the selection multiple
                // times. removeRange should only remove the first instance, so the following ensures only the first
                // instance is removed
                removed = true;
            }
        }
        if (!sel.rangeCount) {
            updateEmptySelection(sel);
        }
    };

    if (implementsControlRange) {
        selProto.removeRange = function(range) {
            if (this.docSelection.type == CONTROL) {
                var controlRange = this.docSelection.createRange();
                var rangeElement = getSingleElementFromRange(range);

                // Create a new ControlRange containing all the elements in the selected ControlRange minus the
                // element contained by the supplied range
                var doc = dom.getDocument(controlRange.item(0));
                var newControlRange = dom.getBody(doc).createControlRange();
                var el, removed = false;
                for (var i = 0, len = controlRange.length; i < len; ++i) {
                    el = controlRange.item(i);
                    if (el !== rangeElement || removed) {
                        newControlRange.add(controlRange.item(i));
                    } else {
                        removed = true;
                    }
                }
                newControlRange.select();

                // Update the wrapped selection based on what's now in the native selection
                updateControlSelection(this);
            } else {
                removeRangeManually(this, range);
            }
        };
    } else {
        selProto.removeRange = function(range) {
            removeRangeManually(this, range);
        };
    }

    // Detecting if a selection is backwards
    var selectionIsBackwards;
    if (!useDocumentSelection && selectionHasAnchorAndFocus && api.features.implementsDomRange) {
        selectionIsBackwards = function(sel) {
            var backwards = false;
            if (sel.anchorNode) {
                backwards = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1);
            }
            return backwards;
        };

        selProto.isBackwards = function() {
            return selectionIsBackwards(this);
        };
    } else {
        selectionIsBackwards = selProto.isBackwards = function() {
            return false;
        };
    }

    // Selection text
    // This is conformant to the new WHATWG DOM Range draft spec but differs from WebKit and Mozilla's implementation
    selProto.toString = function() {

        var rangeTexts = [];
        for (var i = 0, len = this.rangeCount; i < len; ++i) {
            rangeTexts[i] = "" + this._ranges[i];
        }
        return rangeTexts.join("");
    };

    function assertNodeInSameDocument(sel, node) {
        if (sel.anchorNode && (dom.getDocument(sel.anchorNode) !== dom.getDocument(node))) {
            throw new DOMException("WRONG_DOCUMENT_ERR");
        }
    }

    // No current browsers conform fully to the HTML 5 draft spec for this method, so Rangy's own method is always used
    selProto.collapse = function(node, offset) {
        assertNodeInSameDocument(this, node);
        var range = api.createRange(dom.getDocument(node));
        range.collapseToPoint(node, offset);
        this.removeAllRanges();
        this.addRange(range);
        this.isCollapsed = true;
    };

    selProto.collapseToStart = function() {
        if (this.rangeCount) {
            var range = this._ranges[0];
            this.collapse(range.startContainer, range.startOffset);
        } else {
            throw new DOMException("INVALID_STATE_ERR");
        }
    };

    selProto.collapseToEnd = function() {
        if (this.rangeCount) {
            var range = this._ranges[this.rangeCount - 1];
            this.collapse(range.endContainer, range.endOffset);
        } else {
            throw new DOMException("INVALID_STATE_ERR");
        }
    };

    // The HTML 5 spec is very specific on how selectAllChildren should be implemented so the native implementation is
    // never used by Rangy.
    selProto.selectAllChildren = function(node) {
        assertNodeInSameDocument(this, node);
        var range = api.createRange(dom.getDocument(node));
        range.selectNodeContents(node);
        this.removeAllRanges();
        this.addRange(range);
    };

    selProto.deleteFromDocument = function() {
        // Sepcial behaviour required for Control selections
        if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
            var controlRange = this.docSelection.createRange();
            var element;
            while (controlRange.length) {
                element = controlRange.item(0);
                controlRange.remove(element);
                element.parentNode.removeChild(element);
            }
            this.refresh();
        } else if (this.rangeCount) {
            var ranges = this.getAllRanges();
            this.removeAllRanges();
            for (var i = 0, len = ranges.length; i < len; ++i) {
                ranges[i].deleteContents();
            }
            // The HTML5 spec says nothing about what the selection should contain after calling deleteContents on each
            // range. Firefox moves the selection to where the final selected range was, so we emulate that
            this.addRange(ranges[len - 1]);
        }
    };

    // The following are non-standard extensions
    selProto.getAllRanges = function() {
        return this._ranges.slice(0);
    };

    selProto.setSingleRange = function(range) {
        this.setRanges( [range] );
    };

    selProto.containsNode = function(node, allowPartial) {
        for (var i = 0, len = this._ranges.length; i < len; ++i) {
            if (this._ranges[i].containsNode(node, allowPartial)) {
                return true;
            }
        }
        return false;
    };

    selProto.toHtml = function() {
        var html = "";
        if (this.rangeCount) {
            var container = DomRange.getRangeDocument(this._ranges[0]).createElement("div");
            for (var i = 0, len = this._ranges.length; i < len; ++i) {
                container.appendChild(this._ranges[i].cloneContents());
            }
            html = container.innerHTML;
        }
        return html;
    };

    function inspect(sel) {
        var rangeInspects = [];
        var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset);
        var focus = new DomPosition(sel.focusNode, sel.focusOffset);
        var name = (typeof sel.getName == "function") ? sel.getName() : "Selection";

        if (typeof sel.rangeCount != "undefined") {
            for (var i = 0, len = sel.rangeCount; i < len; ++i) {
                rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i));
            }
        }
        return "[" + name + "(Ranges: " + rangeInspects.join(", ") +
                ")(anchor: " + anchor.inspect() + ", focus: " + focus.inspect() + "]";

    }

    selProto.getName = function() {
        return "WrappedSelection";
    };

    selProto.inspect = function() {
        return inspect(this);
    };

    selProto.detach = function() {
        this.win[windowPropertyName] = null;
        this.win = this.anchorNode = this.focusNode = null;
    };

    WrappedSelection.inspect = inspect;

    api.Selection = WrappedSelection;

    api.selectionPrototype = selProto;

    api.addCreateMissingNativeApiListener(function(win) {
        if (typeof win.getSelection == "undefined") {
            win.getSelection = function() {
                return api.getSelection(this);
            };
        }
        win = null;
    });
});
;
/*!
 * jQuery Browser Plugin 0.0.7
 * https://github.com/gabceb/jquery-browser-plugin
 *
 * Original jquery-browser code Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors
 * http://jquery.org/license
 *
 * Modifications Copyright 2015 Gabriel Cebrian
 * https://github.com/gabceb
 *
 * Released under the MIT license
 *
 * Date: 20-01-2015
 */!function (a) { "function" == typeof define && define.amd ? define(["jquery"], function (b) { a(b) }) : "object" == typeof module && "object" == typeof module.exports ? module.exports = a(require("jquery")) : a(window.jQuery) }(function (a) { "use strict"; function b(a) { void 0 === a && (a = window.navigator.userAgent), a = a.toLowerCase(); var b = /(edge)\/([\w.]+)/.exec(a) || /(opr)[\/]([\w.]+)/.exec(a) || /(chrome)[ \/]([\w.]+)/.exec(a) || /(version)(applewebkit)[ \/]([\w.]+).*(safari)[ \/]([\w.]+)/.exec(a) || /(webkit)[ \/]([\w.]+).*(version)[ \/]([\w.]+).*(safari)[ \/]([\w.]+)/.exec(a) || /(webkit)[ \/]([\w.]+)/.exec(a) || /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(a) || /(msie) ([\w.]+)/.exec(a) || a.indexOf("trident") >= 0 && /(rv)(?::| )([\w.]+)/.exec(a) || a.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(a) || [], c = /(ipad)/.exec(a) || /(ipod)/.exec(a) || /(iphone)/.exec(a) || /(kindle)/.exec(a) || /(silk)/.exec(a) || /(android)/.exec(a) || /(windows phone)/.exec(a) || /(win)/.exec(a) || /(mac)/.exec(a) || /(linux)/.exec(a) || /(cros)/.exec(a) || /(playbook)/.exec(a) || /(bb)/.exec(a) || /(blackberry)/.exec(a) || [], d = {}, e = { browser: b[5] || b[3] || b[1] || "", version: b[2] || b[4] || "0", versionNumber: b[4] || b[2] || "0", platform: c[0] || "" }; if (e.browser && (d[e.browser] = !0, d.version = e.version, d.versionNumber = parseInt(e.versionNumber, 10)), e.platform && (d[e.platform] = !0), (d.android || d.bb || d.blackberry || d.ipad || d.iphone || d.ipod || d.kindle || d.playbook || d.silk || d["windows phone"]) && (d.mobile = !0), (d.cros || d.mac || d.linux || d.win) && (d.desktop = !0), (d.chrome || d.opr || d.safari) && (d.webkit = !0), d.rv || d.edge) { var f = "msie"; e.browser = f, d[f] = !0 } if (d.safari && d.blackberry) { var g = "blackberry"; e.browser = g, d[g] = !0 } if (d.safari && d.playbook) { var h = "playbook"; e.browser = h, d[h] = !0 } if (d.bb) { var i = "blackberry"; e.browser = i, d[i] = !0 } if (d.opr) { var j = "opera"; e.browser = j, d[j] = !0 } if (d.safari && d.android) { var k = "android"; e.browser = k, d[k] = !0 } if (d.safari && d.kindle) { var l = "kindle"; e.browser = l, d[l] = !0 } if (d.safari && d.silk) { var m = "silk"; e.browser = m, d[m] = !0 } return d.name = e.browser, d.platform = e.platform, d } return window.jQBrowser = b(window.navigator.userAgent), window.jQBrowser.uaMatch = b, a && (a.browser = window.jQBrowser), window.jQBrowser });;
/*!
 * jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/2010
 * http://benalman.com/projects/jquery-bbq-plugin/
 * 
 * Copyright (c) 2010 "Cowboy" Ben Alman
 * Dual licensed under the MIT and GPL licenses.
 * http://benalman.com/about/license/
 */

// Script: jQuery BBQ: Back Button & Query Library
//
// *Version: 1.2.1, Last updated: 2/17/2010*
// 
// Project Home - http://benalman.com/projects/jquery-bbq-plugin/
// GitHub       - http://github.com/cowboy/jquery-bbq/
// Source       - http://github.com/cowboy/jquery-bbq/raw/master/jquery.ba-bbq.js
// (Minified)   - http://github.com/cowboy/jquery-bbq/raw/master/jquery.ba-bbq.min.js (4.0kb)
// 
// About: License
// 
// Copyright (c) 2010 "Cowboy" Ben Alman,
// Dual licensed under the MIT and GPL licenses.
// http://benalman.com/about/license/
// 
// About: Examples
// 
// These working examples, complete with fully commented code, illustrate a few
// ways in which this plugin can be used.
// 
// Basic AJAX     - http://benalman.com/code/projects/jquery-bbq/examples/fragment-basic/
// Advanced AJAX  - http://benalman.com/code/projects/jquery-bbq/examples/fragment-advanced/
// jQuery UI Tabs - http://benalman.com/code/projects/jquery-bbq/examples/fragment-jquery-ui-tabs/
// Deparam        - http://benalman.com/code/projects/jquery-bbq/examples/deparam/
// 
// About: Support and Testing
// 
// Information about what version or versions of jQuery this plugin has been
// tested with, what browsers it has been tested in, and where the unit tests
// reside (so you can test it yourself).
// 
// jQuery Versions - 1.3.2, 1.4.1, 1.4.2
// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.7, Safari 3-4,
//                   Chrome 4-5, Opera 9.6-10.1.
// Unit Tests      - http://benalman.com/code/projects/jquery-bbq/unit/
// 
// About: Release History
// 
// 1.2.1 - (2/17/2010) Actually fixed the stale window.location Safari bug from
//         <jQuery hashchange event> in BBQ, which was the main reason for the
//         previous release!
// 1.2   - (2/16/2010) Integrated <jQuery hashchange event> v1.2, which fixes a
//         Safari bug, the event can now be bound before DOM ready, and IE6/7
//         page should no longer scroll when the event is first bound. Also
//         added the <jQuery.param.fragment.noEscape> method, and reworked the
//         <hashchange event (BBQ)> internal "add" method to be compatible with
//         changes made to the jQuery 1.4.2 special events API.
// 1.1.1 - (1/22/2010) Integrated <jQuery hashchange event> v1.1, which fixes an
//         obscure IE8 EmulateIE7 meta tag compatibility mode bug.
// 1.1   - (1/9/2010) Broke out the jQuery BBQ event.special <hashchange event>
//         functionality into a separate plugin for users who want just the
//         basic event & back button support, without all the extra awesomeness
//         that BBQ provides. This plugin will be included as part of jQuery BBQ,
//         but also be available separately. See <jQuery hashchange event>
//         plugin for more information. Also added the <jQuery.bbq.removeState>
//         method and added additional <jQuery.deparam> examples.
// 1.0.3 - (12/2/2009) Fixed an issue in IE 6 where location.search and
//         location.hash would report incorrectly if the hash contained the ?
//         character. Also <jQuery.param.querystring> and <jQuery.param.fragment>
//         will no longer parse params out of a URL that doesn't contain ? or #,
//         respectively.
// 1.0.2 - (10/10/2009) Fixed an issue in IE 6/7 where the hidden IFRAME caused
//         a "This page contains both secure and nonsecure items." warning when
//         used on an https:// page.
// 1.0.1 - (10/7/2009) Fixed an issue in IE 8. Since both "IE7" and "IE8
//         Compatibility View" modes erroneously report that the browser
//         supports the native window.onhashchange event, a slightly more
//         robust test needed to be added.
// 1.0   - (10/2/2009) Initial release

(function($,window){
  '$:nomunge'; // Used by YUI compressor.
  
  // Some convenient shortcuts.
  var undefined,
    aps = Array.prototype.slice,
    decode = decodeURIComponent,
    
    // Method / object references.
    jq_param = $.param,
    jq_param_fragment,
    jq_deparam,
    jq_deparam_fragment,
    jq_bbq = $.bbq = $.bbq || {},
    jq_bbq_pushState,
    jq_bbq_getState,
    jq_elemUrlAttr,
    jq_event_special = $.event.special,
    
    // Reused strings.
    str_hashchange = 'hashchange',
    str_querystring = 'querystring',
    str_fragment = 'fragment',
    str_elemUrlAttr = 'elemUrlAttr',
    str_location = 'location',
    str_href = 'href',
    str_src = 'src',
    
    // Reused RegExp.
    re_trim_querystring = /^.*\?|#.*$/g,
    re_trim_fragment = /^.*\#/,
    re_no_escape,
    
    // Used by jQuery.elemUrlAttr.
    elemUrlAttr_cache = {};
  
  // A few commonly used bits, broken out to help reduce minified file size.
  
  function is_string( arg ) {
    return typeof arg === 'string';
  };
  
  // Why write the same function twice? Let's curry! Mmmm, curry..
  
  function curry( func ) {
    var args = aps.call( arguments, 1 );
    
    return function() {
      return func.apply( this, args.concat( aps.call( arguments ) ) );
    };
  };
  
  // Get location.hash (or what you'd expect location.hash to be) sans any
  // leading #. Thanks for making this necessary, Firefox!
  function get_fragment( url ) {
    return url.replace( /^[^#]*#?(.*)$/, '$1' );
  };
  
  // Get location.search (or what you'd expect location.search to be) sans any
  // leading #. Thanks for making this necessary, IE6!
  function get_querystring( url ) {
    return url.replace( /(?:^[^?#]*\?([^#]*).*$)?.*/, '$1' );
  };
  
  // Section: Param (to string)
  // 
  // Method: jQuery.param.querystring
  // 
  // Retrieve the query string from a URL or if no arguments are passed, the
  // current window.location.
  // 
  // Usage:
  // 
  // > jQuery.param.querystring( [ url ] );
  // 
  // Arguments:
  // 
  //  url - (String) A URL containing query string params to be parsed. If url
  //    is not passed, the current window.location is used.
  // 
  // Returns:
  // 
  //  (String) The parsed query string, with any leading "?" removed.
  //
  
  // Method: jQuery.param.querystring (build url)
  // 
  // Merge a URL, with or without pre-existing query string params, plus any
  // object, params string or URL containing query string params into a new URL.
  // 
  // Usage:
  // 
  // > jQuery.param.querystring( url, params [, merge_mode ] );
  // 
  // Arguments:
  // 
  //  url - (String) A valid URL for params to be merged into. This URL may
  //    contain a query string and/or fragment (hash).
  //  params - (String) A params string or URL containing query string params to
  //    be merged into url.
  //  params - (Object) A params object to be merged into url.
  //  merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not
  //    specified, and is as-follows:
  // 
  //    * 0: params in the params argument will override any query string
  //         params in url.
  //    * 1: any query string params in url will override params in the params
  //         argument.
  //    * 2: params argument will completely replace any query string in url.
  // 
  // Returns:
  // 
  //  (String) Either a params string with urlencoded data or a URL with a
  //    urlencoded query string in the format 'a=b&c=d&e=f'.
  
  // Method: jQuery.param.fragment
  // 
  // Retrieve the fragment (hash) from a URL or if no arguments are passed, the
  // current window.location.
  // 
  // Usage:
  // 
  // > jQuery.param.fragment( [ url ] );
  // 
  // Arguments:
  // 
  //  url - (String) A URL containing fragment (hash) params to be parsed. If
  //    url is not passed, the current window.location is used.
  // 
  // Returns:
  // 
  //  (String) The parsed fragment (hash) string, with any leading "#" removed.
  
  // Method: jQuery.param.fragment (build url)
  // 
  // Merge a URL, with or without pre-existing fragment (hash) params, plus any
  // object, params string or URL containing fragment (hash) params into a new
  // URL.
  // 
  // Usage:
  // 
  // > jQuery.param.fragment( url, params [, merge_mode ] );
  // 
  // Arguments:
  // 
  //  url - (String) A valid URL for params to be merged into. This URL may
  //    contain a query string and/or fragment (hash).
  //  params - (String) A params string or URL containing fragment (hash) params
  //    to be merged into url.
  //  params - (Object) A params object to be merged into url.
  //  merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not
  //    specified, and is as-follows:
  // 
  //    * 0: params in the params argument will override any fragment (hash)
  //         params in url.
  //    * 1: any fragment (hash) params in url will override params in the
  //         params argument.
  //    * 2: params argument will completely replace any query string in url.
  // 
  // Returns:
  // 
  //  (String) Either a params string with urlencoded data or a URL with a
  //    urlencoded fragment (hash) in the format 'a=b&c=d&e=f'.
  
  function jq_param_sub( is_fragment, get_func, url, params, merge_mode ) {
    var result,
      qs,
      matches,
      url_params,
      hash;
    
    if ( params !== undefined ) {
      // Build URL by merging params into url string.
      
      // matches[1] = url part that precedes params, not including trailing ?/#
      // matches[2] = params, not including leading ?/#
      // matches[3] = if in 'querystring' mode, hash including leading #, otherwise ''
      matches = url.match( is_fragment ? /^([^#]*)\#?(.*)$/ : /^([^#?]*)\??([^#]*)(#?.*)/ );
      
      // Get the hash if in 'querystring' mode, and it exists.
      hash = matches[3] || '';
      
      if ( merge_mode === 2 && is_string( params ) ) {
        // If merge_mode is 2 and params is a string, merge the fragment / query
        // string into the URL wholesale, without converting it into an object.
        qs = params.replace( is_fragment ? re_trim_fragment : re_trim_querystring, '' );
        
      } else {
        // Convert relevant params in url to object.
        url_params = jq_deparam( matches[2] );
        
        params = is_string( params )
          
          // Convert passed params string into object.
          ? jq_deparam[ is_fragment ? str_fragment : str_querystring ]( params )
          
          // Passed params object.
          : params;
        
        qs = merge_mode === 2 ? params                              // passed params replace url params
          : merge_mode === 1  ? $.extend( {}, params, url_params )  // url params override passed params
          : $.extend( {}, url_params, params );                     // passed params override url params
        
        // Convert params object to a string.
        qs = jq_param( qs );
        
        // Unescape characters specified via $.param.noEscape. Since only hash-
        // history users have requested this feature, it's only enabled for
        // fragment-related params strings.
        if ( is_fragment ) {
          qs = qs.replace( re_no_escape, decode );
        }
      }
      
      // Build URL from the base url, querystring and hash. In 'querystring'
      // mode, ? is only added if a query string exists. In 'fragment' mode, #
      // is always added.
      result = matches[1] + ( is_fragment ? '#' : qs || !matches[1] ? '?' : '' ) + qs + hash;
      
    } else {
      // If URL was passed in, parse params from URL string, otherwise parse
      // params from window.location.
      result = get_func( url !== undefined ? url : window[ str_location ][ str_href ] );
    }
    
    return result;
  };
  
  jq_param[ str_querystring ]                  = curry( jq_param_sub, 0, get_querystring );
  jq_param[ str_fragment ] = jq_param_fragment = curry( jq_param_sub, 1, get_fragment );
  
  // Method: jQuery.param.fragment.noEscape
  // 
  // Specify characters that will be left unescaped when fragments are created
  // or merged using <jQuery.param.fragment>, or when the fragment is modified
  // using <jQuery.bbq.pushState>. This option only applies to serialized data
  // object fragments, and not set-as-string fragments. Does not affect the
  // query string. Defaults to ",/" (comma, forward slash).
  // 
  // Note that this is considered a purely aesthetic option, and will help to
  // create URLs that "look pretty" in the address bar or bookmarks, without
  // affecting functionality in any way. That being said, be careful to not
  // unescape characters that are used as delimiters or serve a special
  // purpose, such as the "#?&=+" (octothorpe, question mark, ampersand,
  // equals, plus) characters.
  // 
  // Usage:
  // 
  // > jQuery.param.fragment.noEscape( [ chars ] );
  // 
  // Arguments:
  // 
  //  chars - (String) The characters to not escape in the fragment. If
  //    unspecified, defaults to empty string (escape all characters).
  // 
  // Returns:
  // 
  //  Nothing.
  
  jq_param_fragment.noEscape = function( chars ) {
    chars = chars || '';
    var arr = $.map( chars.split(''), encodeURIComponent );
    re_no_escape = new RegExp( arr.join('|'), 'g' );
  };
  
  // A sensible default. These are the characters people seem to complain about
  // "uglifying up the URL" the most.
  jq_param_fragment.noEscape( ',/' );
  
  // Section: Deparam (from string)
  // 
  // Method: jQuery.deparam
  // 
  // Deserialize a params string into an object, optionally coercing numbers,
  // booleans, null and undefined values; this method is the counterpart to the
  // internal jQuery.param method.
  // 
  // Usage:
  // 
  // > jQuery.deparam( params [, coerce ] );
  // 
  // Arguments:
  // 
  //  params - (String) A params string to be parsed.
  //  coerce - (Boolean) If true, coerces any numbers or true, false, null, and
  //    undefined to their actual value. Defaults to false if omitted.
  // 
  // Returns:
  // 
  //  (Object) An object representing the deserialized params string.
  
  $.deparam = jq_deparam = function( params, coerce ) {
    var obj = {},
      coerce_types = { 'true': !0, 'false': !1, 'null': null };
    
    // Iterate over all name=value pairs.
    $.each( params.replace( /\+/g, ' ' ).split( '&' ), function(j,v){
      var param = v.split( '=' ),
        key = decode( param[0] ),
        val,
        cur = obj,
        i = 0,
        
        // If key is more complex than 'foo', like 'a[]' or 'a[b][c]', split it
        // into its component parts.
        keys = key.split( '][' ),
        keys_last = keys.length - 1;
      
      // If the first keys part contains [ and the last ends with ], then []
      // are correctly balanced.
      if ( /\[/.test( keys[0] ) && /\]$/.test( keys[ keys_last ] ) ) {
        // Remove the trailing ] from the last keys part.
        keys[ keys_last ] = keys[ keys_last ].replace( /\]$/, '' );
        
        // Split first keys part into two parts on the [ and add them back onto
        // the beginning of the keys array.
        keys = keys.shift().split('[').concat( keys );
        
        keys_last = keys.length - 1;
      } else {
        // Basic 'foo' style key.
        keys_last = 0;
      }
      
      // Are we dealing with a name=value pair, or just a name?
      if ( param.length === 2 ) {
        val = decode( param[1] );
        
        // Coerce values.
        if ( coerce ) {
          val = val && !isNaN(val)            ? +val              // number
            : val === 'undefined'             ? undefined         // undefined
            : coerce_types[val] !== undefined ? coerce_types[val] // true, false, null
            : val;                                                // string
        }
        
        if ( keys_last ) {
          // Complex key, build deep object structure based on a few rules:
          // * The 'cur' pointer starts at the object top-level.
          // * [] = array push (n is set to array length), [n] = array if n is 
          //   numeric, otherwise object.
          // * If at the last keys part, set the value.
          // * For each keys part, if the current level is undefined create an
          //   object or array based on the type of the next keys part.
          // * Move the 'cur' pointer to the next level.
          // * Rinse & repeat.
          for ( ; i <= keys_last; i++ ) {
            key = keys[i] === '' ? cur.length : keys[i];
            cur = cur[key] = i < keys_last
              ? cur[key] || ( keys[i+1] && isNaN( keys[i+1] ) ? {} : [] )
              : val;
          }
          
        } else {
          // Simple key, even simpler rules, since only scalars and shallow
          // arrays are allowed.
          
          if ( $.isArray( obj[key] ) ) {
            // val is already an array, so push on the next value.
            obj[key].push( val );
            
          } else if ( obj[key] !== undefined ) {
            // val isn't an array, but since a second value has been specified,
            // convert val into an array.
            obj[key] = [ obj[key], val ];
            
          } else {
            // val is a scalar.
            obj[key] = val;
          }
        }
        
      } else if ( key ) {
        // No value was defined, so set something meaningful.
        obj[key] = coerce
          ? undefined
          : '';
      }
    });
    
    return obj;
  };
  
  // Method: jQuery.deparam.querystring
  // 
  // Parse the query string from a URL or the current window.location,
  // deserializing it into an object, optionally coercing numbers, booleans,
  // null and undefined values.
  // 
  // Usage:
  // 
  // > jQuery.deparam.querystring( [ url ] [, coerce ] );
  // 
  // Arguments:
  // 
  //  url - (String) An optional params string or URL containing query string
  //    params to be parsed. If url is omitted, the current window.location
  //    is used.
  //  coerce - (Boolean) If true, coerces any numbers or true, false, null, and
  //    undefined to their actual value. Defaults to false if omitted.
  // 
  // Returns:
  // 
  //  (Object) An object representing the deserialized params string.
  
  // Method: jQuery.deparam.fragment
  // 
  // Parse the fragment (hash) from a URL or the current window.location,
  // deserializing it into an object, optionally coercing numbers, booleans,
  // null and undefined values.
  // 
  // Usage:
  // 
  // > jQuery.deparam.fragment( [ url ] [, coerce ] );
  // 
  // Arguments:
  // 
  //  url - (String) An optional params string or URL containing fragment (hash)
  //    params to be parsed. If url is omitted, the current window.location
  //    is used.
  //  coerce - (Boolean) If true, coerces any numbers or true, false, null, and
  //    undefined to their actual value. Defaults to false if omitted.
  // 
  // Returns:
  // 
  //  (Object) An object representing the deserialized params string.
  
  function jq_deparam_sub( is_fragment, url_or_params, coerce ) {
    if ( url_or_params === undefined || typeof url_or_params === 'boolean' ) {
      // url_or_params not specified.
      coerce = url_or_params;
      url_or_params = jq_param[ is_fragment ? str_fragment : str_querystring ]();
    } else {
      url_or_params = is_string( url_or_params )
        ? url_or_params.replace( is_fragment ? re_trim_fragment : re_trim_querystring, '' )
        : url_or_params;
    }
    
    return jq_deparam( url_or_params, coerce );
  };
  
  jq_deparam[ str_querystring ]                    = curry( jq_deparam_sub, 0 );
  jq_deparam[ str_fragment ] = jq_deparam_fragment = curry( jq_deparam_sub, 1 );
  
  // Section: Element manipulation
  // 
  // Method: jQuery.elemUrlAttr
  // 
  // Get the internal "Default URL attribute per tag" list, or augment the list
  // with additional tag-attribute pairs, in case the defaults are insufficient.
  // 
  // In the <jQuery.fn.querystring> and <jQuery.fn.fragment> methods, this list
  // is used to determine which attribute contains the URL to be modified, if
  // an "attr" param is not specified.
  // 
  // Default Tag-Attribute List:
  // 
  //  a      - href
  //  base   - href
  //  iframe - src
  //  img    - src
  //  input  - src
  //  form   - action
  //  link   - href
  //  script - src
  // 
  // Usage:
  // 
  // > jQuery.elemUrlAttr( [ tag_attr ] );
  // 
  // Arguments:
  // 
  //  tag_attr - (Object) An object containing a list of tag names and their
  //    associated default attribute names in the format { tag: 'attr', ... } to
  //    be merged into the internal tag-attribute list.
  // 
  // Returns:
  // 
  //  (Object) An object containing all stored tag-attribute values.
  
  // Only define function and set defaults if function doesn't already exist, as
  // the urlInternal plugin will provide this method as well.
  $[ str_elemUrlAttr ] || ($[ str_elemUrlAttr ] = function( obj ) {
    return $.extend( elemUrlAttr_cache, obj );
  })({
    a: str_href,
    base: str_href,
    iframe: str_src,
    img: str_src,
    input: str_src,
    form: 'action',
    link: str_href,
    script: str_src
  });
  
  jq_elemUrlAttr = $[ str_elemUrlAttr ];
  
  // Method: jQuery.fn.querystring
  // 
  // Update URL attribute in one or more elements, merging the current URL (with
  // or without pre-existing query string params) plus any params object or
  // string into a new URL, which is then set into that attribute. Like
  // <jQuery.param.querystring (build url)>, but for all elements in a jQuery
  // collection.
  // 
  // Usage:
  // 
  // > jQuery('selector').querystring( [ attr, ] params [, merge_mode ] );
  // 
  // Arguments:
  // 
  //  attr - (String) Optional name of an attribute that will contain a URL to
  //    merge params or url into. See <jQuery.elemUrlAttr> for a list of default
  //    attributes.
  //  params - (Object) A params object to be merged into the URL attribute.
  //  params - (String) A URL containing query string params, or params string
  //    to be merged into the URL attribute.
  //  merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not
  //    specified, and is as-follows:
  //    
  //    * 0: params in the params argument will override any params in attr URL.
  //    * 1: any params in attr URL will override params in the params argument.
  //    * 2: params argument will completely replace any query string in attr
  //         URL.
  // 
  // Returns:
  // 
  //  (jQuery) The initial jQuery collection of elements, but with modified URL
  //  attribute values.
  
  // Method: jQuery.fn.fragment
  // 
  // Update URL attribute in one or more elements, merging the current URL (with
  // or without pre-existing fragment/hash params) plus any params object or
  // string into a new URL, which is then set into that attribute. Like
  // <jQuery.param.fragment (build url)>, but for all elements in a jQuery
  // collection.
  // 
  // Usage:
  // 
  // > jQuery('selector').fragment( [ attr, ] params [, merge_mode ] );
  // 
  // Arguments:
  // 
  //  attr - (String) Optional name of an attribute that will contain a URL to
  //    merge params into. See <jQuery.elemUrlAttr> for a list of default
  //    attributes.
  //  params - (Object) A params object to be merged into the URL attribute.
  //  params - (String) A URL containing fragment (hash) params, or params
  //    string to be merged into the URL attribute.
  //  merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not
  //    specified, and is as-follows:
  //    
  //    * 0: params in the params argument will override any params in attr URL.
  //    * 1: any params in attr URL will override params in the params argument.
  //    * 2: params argument will completely replace any fragment (hash) in attr
  //         URL.
  // 
  // Returns:
  // 
  //  (jQuery) The initial jQuery collection of elements, but with modified URL
  //  attribute values.
  
  function jq_fn_sub( mode, force_attr, params, merge_mode ) {
    if ( !is_string( params ) && typeof params !== 'object' ) {
      // force_attr not specified.
      merge_mode = params;
      params = force_attr;
      force_attr = undefined;
    }
    
    return this.each(function(){
      var that = $(this),
        
        // Get attribute specified, or default specified via $.elemUrlAttr.
        attr = force_attr || jq_elemUrlAttr()[ ( this.nodeName || '' ).toLowerCase() ] || '',
        
        // Get URL value.
        url = attr && that.attr( attr ) || '';
      
      // Update attribute with new URL.
      that.attr( attr, jq_param[ mode ]( url, params, merge_mode ) );
    });
    
  };
  
  $.fn[ str_querystring ] = curry( jq_fn_sub, str_querystring );
  $.fn[ str_fragment ]    = curry( jq_fn_sub, str_fragment );
  
  // Section: History, hashchange event
  // 
  // Method: jQuery.bbq.pushState
  // 
  // Adds a 'state' into the browser history at the current position, setting
  // location.hash and triggering any bound <hashchange event> callbacks
  // (provided the new state is different than the previous state).
  // 
  // If no arguments are passed, an empty state is created, which is just a
  // shortcut for jQuery.bbq.pushState( {}, 2 ).
  // 
  // Usage:
  // 
  // > jQuery.bbq.pushState( [ params [, merge_mode ] ] );
  // 
  // Arguments:
  // 
  //  params - (String) A serialized params string or a hash string beginning
  //    with # to merge into location.hash.
  //  params - (Object) A params object to merge into location.hash.
  //  merge_mode - (Number) Merge behavior defaults to 0 if merge_mode is not
  //    specified (unless a hash string beginning with # is specified, in which
  //    case merge behavior defaults to 2), and is as-follows:
  // 
  //    * 0: params in the params argument will override any params in the
  //         current state.
  //    * 1: any params in the current state will override params in the params
  //         argument.
  //    * 2: params argument will completely replace current state.
  // 
  // Returns:
  // 
  //  Nothing.
  // 
  // Additional Notes:
  // 
  //  * Setting an empty state may cause the browser to scroll.
  //  * Unlike the fragment and querystring methods, if a hash string beginning
  //    with # is specified as the params agrument, merge_mode defaults to 2.
  
  jq_bbq.pushState = jq_bbq_pushState = function( params, merge_mode ) {
    if ( is_string( params ) && /^#/.test( params ) && merge_mode === undefined ) {
      // Params string begins with # and merge_mode not specified, so completely
      // overwrite window.location.hash.
      merge_mode = 2;
    }
    
    var has_args = params !== undefined,
      // Merge params into window.location using $.param.fragment.
      url = jq_param_fragment( window[ str_location ][ str_href ],
        has_args ? params : {}, has_args ? merge_mode : 2 );
    
    // Set new window.location.href. If hash is empty, use just # to prevent
    // browser from reloading the page. Note that Safari 3 & Chrome barf on
    // location.hash = '#'.
    window[ str_location ][ str_href ] = url + ( /#/.test( url ) ? '' : '#' );
  };
  
  // Method: jQuery.bbq.getState
  // 
  // Retrieves the current 'state' from the browser history, parsing
  // location.hash for a specific key or returning an object containing the
  // entire state, optionally coercing numbers, booleans, null and undefined
  // values.
  // 
  // Usage:
  // 
  // > jQuery.bbq.getState( [ key ] [, coerce ] );
  // 
  // Arguments:
  // 
  //  key - (String) An optional state key for which to return a value.
  //  coerce - (Boolean) If true, coerces any numbers or true, false, null, and
  //    undefined to their actual value. Defaults to false.
  // 
  // Returns:
  // 
  //  (Anything) If key is passed, returns the value corresponding with that key
  //    in the location.hash 'state', or undefined. If not, an object
  //    representing the entire 'state' is returned.
  
  jq_bbq.getState = jq_bbq_getState = function( key, coerce ) {
    return key === undefined || typeof key === 'boolean'
      ? jq_deparam_fragment( key ) // 'key' really means 'coerce' here
      : jq_deparam_fragment( coerce )[ key ];
  };
  
  // Method: jQuery.bbq.removeState
  // 
  // Remove one or more keys from the current browser history 'state', creating
  // a new state, setting location.hash and triggering any bound
  // <hashchange event> callbacks (provided the new state is different than
  // the previous state).
  // 
  // If no arguments are passed, an empty state is created, which is just a
  // shortcut for jQuery.bbq.pushState( {}, 2 ).
  // 
  // Usage:
  // 
  // > jQuery.bbq.removeState( [ key [, key ... ] ] );
  // 
  // Arguments:
  // 
  //  key - (String) One or more key values to remove from the current state,
  //    passed as individual arguments.
  //  key - (Array) A single array argument that contains a list of key values
  //    to remove from the current state.
  // 
  // Returns:
  // 
  //  Nothing.
  // 
  // Additional Notes:
  // 
  //  * Setting an empty state may cause the browser to scroll.
  
  jq_bbq.removeState = function( arr ) {
    var state = {};
    
    // If one or more arguments is passed..
    if ( arr !== undefined ) {
      
      // Get the current state.
      state = jq_bbq_getState();
      
      // For each passed key, delete the corresponding property from the current
      // state.
      $.each( $.isArray( arr ) ? arr : arguments, function(i,v){
        delete state[ v ];
      });
    }
    
    // Set the state, completely overriding any existing state.
    jq_bbq_pushState( state, 2 );
  };
  
  // Event: hashchange event (BBQ)
  // 
  // Usage in jQuery 1.4 and newer:
  // 
  // In jQuery 1.4 and newer, the event object passed into any hashchange event
  // callback is augmented with a copy of the location.hash fragment at the time
  // the event was triggered as its event.fragment property. In addition, the
  // event.getState method operates on this property (instead of location.hash)
  // which allows this fragment-as-a-state to be referenced later, even after
  // window.location may have changed.
  // 
  // Note that event.fragment and event.getState are not defined according to
  // W3C (or any other) specification, but will still be available whether or
  // not the hashchange event exists natively in the browser, because of the
  // utility they provide.
  // 
  // The event.fragment property contains the output of <jQuery.param.fragment>
  // and the event.getState method is equivalent to the <jQuery.bbq.getState>
  // method.
  // 
  // > $(window).bind( 'hashchange', function( event ) {
  // >   var hash_str = event.fragment,
  // >     param_obj = event.getState(),
  // >     param_val = event.getState( 'param_name' ),
  // >     param_val_coerced = event.getState( 'param_name', true );
  // >   ...
  // > });
  // 
  // Usage in jQuery 1.3.2:
  // 
  // In jQuery 1.3.2, the event object cannot to be augmented as in jQuery 1.4+,
  // so the fragment state isn't bound to the event object and must instead be
  // parsed using the <jQuery.param.fragment> and <jQuery.bbq.getState> methods.
  // 
  // > $(window).bind( 'hashchange', function( event ) {
  // >   var hash_str = $.param.fragment(),
  // >     param_obj = $.bbq.getState(),
  // >     param_val = $.bbq.getState( 'param_name' ),
  // >     param_val_coerced = $.bbq.getState( 'param_name', true );
  // >   ...
  // > });
  // 
  // Additional Notes:
  // 
  // * Due to changes in the special events API, jQuery BBQ v1.2 or newer is
  //   required to enable the augmented event object in jQuery 1.4.2 and newer.
  // * See <jQuery hashchange event> for more detailed information.
  
  jq_event_special[ str_hashchange ] = $.extend( jq_event_special[ str_hashchange ], {
    
    // Augmenting the event object with the .fragment property and .getState
    // method requires jQuery 1.4 or newer. Note: with 1.3.2, everything will
    // work, but the event won't be augmented)
    add: function( handleObj ) {
      var old_handler;
      
      function new_handler(e) {
        // e.fragment is set to the value of location.hash (with any leading #
        // removed) at the time the event is triggered.
        var hash = e[ str_fragment ] = jq_param_fragment();
        
        // e.getState() works just like $.bbq.getState(), but uses the
        // e.fragment property stored on the event object.
        e.getState = function( key, coerce ) {
          return key === undefined || typeof key === 'boolean'
            ? jq_deparam( hash, key ) // 'key' really means 'coerce' here
            : jq_deparam( hash, coerce )[ key ];
        };
        
        old_handler.apply( this, arguments );
      };
      
      // This may seem a little complicated, but it normalizes the special event
      // .add method between jQuery 1.4/1.4.1 and 1.4.2+
      if ( $.isFunction( handleObj ) ) {
        // 1.4, 1.4.1
        old_handler = handleObj;
        return new_handler;
      } else {
        // 1.4.2+
        old_handler = handleObj.handler;
        handleObj.handler = new_handler;
      }
    }
    
  });
  
})(jQuery,this);

/*!
 * jQuery hashchange event - v1.2 - 2/11/2010
 * http://benalman.com/projects/jquery-hashchange-plugin/
 * 
 * Copyright (c) 2010 "Cowboy" Ben Alman
 * Dual licensed under the MIT and GPL licenses.
 * http://benalman.com/about/license/
 */

// Script: jQuery hashchange event
//
// *Version: 1.2, Last updated: 2/11/2010*
// 
// Project Home - http://benalman.com/projects/jquery-hashchange-plugin/
// GitHub       - http://github.com/cowboy/jquery-hashchange/
// Source       - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.js
// (Minified)   - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js (1.1kb)
// 
// About: License
// 
// Copyright (c) 2010 "Cowboy" Ben Alman,
// Dual licensed under the MIT and GPL licenses.
// http://benalman.com/about/license/
// 
// About: Examples
// 
// This working example, complete with fully commented code, illustrate one way
// in which this plugin can be used.
// 
// hashchange event - http://benalman.com/code/projects/jquery-hashchange/examples/hashchange/
// 
// About: Support and Testing
// 
// Information about what version or versions of jQuery this plugin has been
// tested with, what browsers it has been tested in, and where the unit tests
// reside (so you can test it yourself).
// 
// jQuery Versions - 1.3.2, 1.4.1, 1.4.2
// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.7, Safari 3-4, Chrome, Opera 9.6-10.1.
// Unit Tests      - http://benalman.com/code/projects/jquery-hashchange/unit/
// 
// About: Known issues
// 
// While this jQuery hashchange event implementation is quite stable and robust,
// there are a few unfortunate browser bugs surrounding expected hashchange
// event-based behaviors, independent of any JavaScript window.onhashchange
// abstraction. See the following examples for more information:
// 
// Chrome: Back Button - http://benalman.com/code/projects/jquery-hashchange/examples/bug-chrome-back-button/
// Firefox: Remote XMLHttpRequest - http://benalman.com/code/projects/jquery-hashchange/examples/bug-firefox-remote-xhr/
// WebKit: Back Button in an Iframe - http://benalman.com/code/projects/jquery-hashchange/examples/bug-webkit-hash-iframe/
// Safari: Back Button from a different domain - http://benalman.com/code/projects/jquery-hashchange/examples/bug-safari-back-from-diff-domain/
// 
// About: Release History
// 
// 1.2   - (2/11/2010) Fixed a bug where coming back to a page using this plugin
//         from a page on another domain would cause an error in Safari 4. Also,
//         IE6/7 Iframe is now inserted after the body (this actually works),
//         which prevents the page from scrolling when the event is first bound.
//         Event can also now be bound before DOM ready, but it won't be usable
//         before then in IE6/7.
// 1.1   - (1/21/2010) Incorporated document.documentMode test to fix IE8 bug
//         where browser version is incorrectly reported as 8.0, despite
//         inclusion of the X-UA-Compatible IE=EmulateIE7 meta tag.
// 1.0   - (1/9/2010) Initial Release. Broke out the jQuery BBQ event.special
//         window.onhashchange functionality into a separate plugin for users
//         who want just the basic event & back button support, without all the
//         extra awesomeness that BBQ provides. This plugin will be included as
//         part of jQuery BBQ, but also be available separately.

(function($,window,undefined){
  '$:nomunge'; // Used by YUI compressor.
  
  // Method / object references.
  var fake_onhashchange,
    jq_event_special = $.event.special,
    
    // Reused strings.
    str_location = 'location',
    str_hashchange = 'hashchange',
    str_href = 'href',
    
    // IE6/7 specifically need some special love when it comes to back-button
    // support, so let's do a little browser sniffing..
    browser = $.browser,
    mode = document.documentMode,
    is_old_ie = browser.msie && ( mode === undefined || mode < 8 ),
    
    // Does the browser support window.onhashchange? Test for IE version, since
    // IE8 incorrectly reports this when in "IE7" or "IE8 Compatibility View"!
    supports_onhashchange = 'on' + str_hashchange in window && !is_old_ie;
  
  // Get location.hash (or what you'd expect location.hash to be) sans any
  // leading #. Thanks for making this necessary, Firefox!
  function get_fragment( url ) {
    url = url || window[ str_location ][ str_href ];
    return url.replace( /^[^#]*#?(.*)$/, '$1' );
  };
  
  // Property: jQuery.hashchangeDelay
  // 
  // The numeric interval (in milliseconds) at which the <hashchange event>
  // polling loop executes. Defaults to 100.
  
  $[ str_hashchange + 'Delay' ] = 100;
  
  // Event: hashchange event
  // 
  // Fired when location.hash changes. In browsers that support it, the native
  // window.onhashchange event is used (IE8, FF3.6), otherwise a polling loop is
  // initialized, running every <jQuery.hashchangeDelay> milliseconds to see if
  // the hash has changed. In IE 6 and 7, a hidden Iframe is created to allow
  // the back button and hash-based history to work.
  // 
  // Usage:
  // 
  // > $(window).bind( 'hashchange', function(e) {
  // >   var hash = location.hash;
  // >   ...
  // > });
  // 
  // Additional Notes:
  // 
  // * The polling loop and Iframe are not created until at least one callback
  //   is actually bound to 'hashchange'.
  // * If you need the bound callback(s) to execute immediately, in cases where
  //   the page 'state' exists on page load (via bookmark or page refresh, for
  //   example) use $(window).trigger( 'hashchange' );
  // * The event can be bound before DOM ready, but since it won't be usable
  //   before then in IE6/7 (due to the necessary Iframe), recommended usage is
  //   to bind it inside a $(document).ready() callback.
  
  jq_event_special[ str_hashchange ] = $.extend( jq_event_special[ str_hashchange ], {
    
    // Called only when the first 'hashchange' event is bound to window.
    setup: function() {
      // If window.onhashchange is supported natively, there's nothing to do..
      if ( supports_onhashchange ) { return false; }
      
      // Otherwise, we need to create our own. And we don't want to call this
      // until the user binds to the event, just in case they never do, since it
      // will create a polling loop and possibly even a hidden Iframe.
      $( fake_onhashchange.start );
    },
    
    // Called only when the last 'hashchange' event is unbound from window.
    teardown: function() {
      // If window.onhashchange is supported natively, there's nothing to do..
      if ( supports_onhashchange ) { return false; }
      
      // Otherwise, we need to stop ours (if possible).
      $( fake_onhashchange.stop );
    }
    
  });
  
  // fake_onhashchange does all the work of triggering the window.onhashchange
  // event for browsers that don't natively support it, including creating a
  // polling loop to watch for hash changes and in IE 6/7 creating a hidden
  // Iframe to enable back and forward.
  fake_onhashchange = (function(){
    var self = {},
      timeout_id,
      iframe,
      set_history,
      get_history;
    
    // Initialize. In IE 6/7, creates a hidden Iframe for history handling.
    function init(){
      // Most browsers don't need special methods here..
      set_history = get_history = function(val){ return val; };
      
      // But IE6/7 do!
      if ( is_old_ie ) {
        
        // Create hidden Iframe after the end of the body to prevent initial
        // page load from scrolling unnecessarily.
        iframe = $('<iframe src="javascript:0"/>').hide().insertAfter( 'body' )[0].contentWindow;
        
        // Get history by looking at the hidden Iframe's location.hash.
        get_history = function() {
          return get_fragment( iframe.document[ str_location ][ str_href ] );
        };
        
        // Set a new history item by opening and then closing the Iframe
        // document, *then* setting its location.hash.
        set_history = function( hash, history_hash ) {
          if ( hash !== history_hash ) {
            var doc = iframe.document;
            doc.open().close();
            doc[ str_location ].hash = '#' + hash;
          }
        };
        
        // Set initial history.
        set_history( get_fragment() );
      }
    };
    
    // Start the polling loop.
    self.start = function() {
      // Polling loop is already running!
      if ( timeout_id ) { return; }
      
      // Remember the initial hash so it doesn't get triggered immediately.
      var last_hash = get_fragment();
      
      // Initialize if not yet initialized.
      set_history || init();
      
      // This polling loop checks every $.hashchangeDelay milliseconds to see if
      // location.hash has changed, and triggers the 'hashchange' event on
      // window when necessary.
      (function loopy(){
        var hash = get_fragment(),
          history_hash = get_history( last_hash );
        
        if ( hash !== last_hash ) {
          set_history( last_hash = hash, history_hash );
          
          $(window).trigger( str_hashchange );
          
        } else if ( history_hash !== last_hash ) {
          window[ str_location ][ str_href ] = window[ str_location ][ str_href ].replace( /#.*/, '' ) + '#' + history_hash;
        }
        
        timeout_id = setTimeout( loopy, $[ str_hashchange + 'Delay' ] );
      })();
    };
    
    // Stop the polling loop, but only if an IE6/7 Iframe wasn't created. In
    // that case, even if there are no longer any bound event handlers, the
    // polling loop is still necessary for back/next to work at all!
    self.stop = function() {
      if ( !iframe ) {
        timeout_id && clearTimeout( timeout_id );
        timeout_id = 0;
      }
    };
    
    return self;
  })();
  
})(jQuery,this);
;
$(function () {
    $('.action-close').live('click', function () { Application.removeModal(); });
    $('.modal').live('hidden.bs.modal', function () {
        Application.removeModal();

    });
});

var Application = function () {
};

Application.IsSystemAdministrator = function () {
    var result = false;
    $.ajax({
        url: "/Admin/IsSystemAdministrator",
        type: "GET",
        async: false,
        success: function (data) {
            result = data.IsSystemAdministrator;
        }
    });
    return result;
}

Application.GetHistoryManagement = function (templateId, callback) {
    $.ajax({
        url: "/Editor/HistoryManagement",
        data: {
            templateId: templateId
        },
        type: "GET",
        success: function (data) {
            callback(data);
        }
    });
}

Application.SetTemplateVersionActive = function (templateVersionId, callback) {
    $.ajax({
        url: "/Editor/SetTemplateVersionAsActive",
        type: "POST",
        data: {
            templateVersionId: templateVersionId
        },
        success: function (data) {
            callback(data);
        },
        error: function(err) {
            callback(null);
        }
    });
}

Application.getPartial = function (url, destinationContainer, timeoutOptions, data, callback, isAppend) {

    isAppend = isAppend || false;
    callback = callback || function () { };
    if (!isAppend) {
        destinationContainer.html('');
    }

    var timeout;
    var showloader;
    var timeoutOpts = timeoutOptions || null;
    callback = callback || function () { };
    if (timeoutOpts != null) {
        timeout = timeoutOpts.timeout;
        showloader = timeoutOpts.showloader;
    } else {
        timeout = 300;
        showloader = true;
    };

    setTimeout(function () {
        $.ajax({
            url: url,
            data: { data: JSON.stringify(data) },
            success: function (response) {
                
                if (isAppend) {
                    destinationContainer.append(response);
                }
                else {
                    destinationContainer.html(response);
                }

                if (typeof (callback) == "function") {
                    callback();
                }
            }
        });
    }, timeout);
};

Application.doGet = function (url, params) {
    document.location = url + '?' + params;
};

Application.doPost = function (url, params) {
    var $form = $("<form method='POST'>").attr("action", url);
    $.each(params, function (name, value) {
        $("<input type='hidden'>")
            .attr("name", name)
            .attr("value", value)
            .appendTo($form);
    });
    $form.appendTo("body");
    $form.submit();
};
    
Application.loginToDealerOrUser = function (idUser, urlUser, nameUser, loginByDealer, isSysAdmin) {
    if (isSysAdmin.toBoolean() == true) {
        login();
    } else {
        Application.showYesNoDialog('Are you sure you want to Login as "<b>' + nameUser + '</b>"', 'Login to account', function () {
            login();
        });
    }

    function login() {
        var url = "/Account/LoginToDealerOrUser/?rowKey=";
        var regexGuid = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
        $.ajax({
            url: "/Account/LoginAs",
            data: { idUser: idUser },
            success: function (response) {
                if (regexGuid.test(response)) {
                    if (loginByDealer) {
                        window.open(location.protocol + '//' + urlUser + url + response, "_blank");
                    } else {
                        location.href = url + response;
                    }
                } else {
                    console.log("Error: " + response);
                    Application.showOkDialog("Error", "Login to user impossible");
                }
            }
        });
        return;
    }
}

Application.getModal = function (url, data, callback, isAuthenticated) {
    Application.fadeOutListTable();
    data = data || "";
    isAuthenticated = (isAuthenticated || isAuthenticated == undefined || isAuthenticated == null) ? true : false;
    if (isAuthenticated) {
        $.ajax({
            url: "/Account/IsAutorize",
            success: function(response) {
                if (response) {
                    getModalInner(url, data, callback);
                } else {
                    document.location.reload();
                }
            }
        });
    } else {
        getModalInner(url, data, callback);
    }

    function getModalInner(responseUrl, responseData, responseCallback) {

        responseCallback = responseCallback || function () { };
        $.ajax({
            url: responseUrl,
            data: { data: JSON.stringify(responseData) },
            success: function (response) {
                $('body').append(response);
                $('.modal').modal('toggle');
                $('.action-close').bind('click', function () {
                    Application.removeModal();
                });
                responseCallback();
            }
        });
    }
};

Application.view = function (url, data, callback) {
    $.ajax({
        url: url,
        data: { data: JSON.stringify(data) },
        success: function (response) {
            callback(response);
        }
    });
}

Application.fadeOutListTable = function () {
    if ($("#listAllSites").is(":visible")) {
        $("#listAllSites").fadeOut("slow");
    }
}

Application.removeModal = function () {
    $('.modal, .modal-backdrop').remove();
    $('body').removeClass('modal-open');
};
jQuery.fn.live = function (types, data, fn) {
    jQuery(this.context).on(types, this.selector, data, fn);
    return this;
};
Application.callValidateForm = function (id) {
    jQuery.validator.unobtrusive.parse("#" + id);
};

Application.showYesNoDialog = function (message, title, onYesCallback) {
    onYesCallback = onYesCallback || function () { };
    var code =
        '<div class="modal fade" id="yes-no-dialog">' +
            '<div class="modal-dialog" style="width: 400px">' +
                '<div class="modal-content">' +
                    '<div class="modal-header">' +
                        '<h4 class="pop-up-title modal-title">' + title + '</h4>' +
                    '</div>' +
                    '<div class="modal-body">' +
                        '<div class="form-line pop-up-message" style="text-align: center;">' + message + '</div>' +
                        '<div class="dialog-content-actions text-center">' +
                            '<a class="std-button yes-button">Yes</a>' +
                            '<a class="std-button no-button">No</a>' +
                        '</div>' +
                    '</div>' +
                '</div>' +
            '</div>' +
        '</div>';

    var modal = $(code).appendTo('body').modal('show');

        $('.action-close').on('click', function (e) {
            e.preventDefault();
            e.stopPropagation();
            if ($('.modal').length > 1) {
                modal.remove();
                $('.modal-backdrop.fade.in').length ? $('.modal-backdrop.fade.in')[0].remove() : '';
            } else {
                Application.removeModal();
            }
        });

        $('#yes-no-dialog a.yes-button').on('click', function (e) {
            e.preventDefault();
            e.stopPropagation();
            if ($('.modal').length > 1) {
                modal.remove();
                $('.modal-backdrop.fade.in').length ? $('.modal-backdrop.fade.in')[0].remove() : '';
            } else {
                Application.removeModal();
            }

            onYesCallback();
        });

        $('#yes-no-dialog a.no-button').on('click', function (e) {
            e.preventDefault();
            e.stopPropagation();
            if ($('.modal').length > 1) {
                modal.remove();
                $('.modal-backdrop.fade.in').length ? $('.modal-backdrop.fade.in')[0].remove() : '';
            } else {
                Application.removeModal();
            }
        });
}

Application.showRemoveConfirmationDialog = function (message, title, confirmButtons, hint, warning) {
    confirmButtons = defined(confirmButtons) ? confirmButtons : [];
    var messageText, styleMessage = '';
    if (message && typeof message === 'object') {
        styleMessage = message.style;
        messageText = message.text;
    } else {
        messageText = message;
    }
    var code =
        '<div class="modal fade" id="confirmation-dialog">' +
            '<div class="modal-dialog">' +
                '<div class="modal-content">' +
                    '<div class="modal-header">' +
                        '<p class="pop-up-title modal-title">' + title + '</p>' +
                         '<a class="position-button close-button" data-dismiss="modal"><i class="fa fa-times"></i></a>' +
                    '</div>' +
                    '<div class="modal-body">' +
                        '<div class="form-line pop-up-message text-center ' + styleMessage + '">' + messageText + '</div>' +
                        (defined(hint) ? '<div class="form-line pop-up-message text-center"><small class="text-muted">' + hint + '</small></div>' : '') +
                        '<div class="dialog-content-actions text-center">' +
                            (defined(warning) ? '<div><small class="text-danger-light">' + warning + '</small></div>' : '') +
                            '<a class="service-button inverted" data-dismiss="modal"  style="margin: 10px;">Cancel</a>' +
                        '</div>' +
                    '</div>' +
                '</div>' +
            '</div>' +
        '</div>';


    var modal = $(code).appendTo('body');

    _.forEach(confirmButtons, function (button) {
        $('<a style="margin: 10px;width: initial;padding: 0 5px;" class="service-button ' + (defined(button.css) ? button.css : 'danger') + '">' + (defined(button.text) ? button.text : 'Remove') + '</a>' +
            (defined(button.tooltip) ? '<i class="fa fa-info-circle editable-tooltip" data-bind="editable_tooltip: { key: \'' + button.tooltip.key + '\', name: \'' + button.tooltip.name + '\', placement: \'top\'}"></i>' : ''))
            .appendTo(modal.find('.dialog-content-actions')).bind('click', defined(button.callback) ? button.callback : function () { });
    });

    modal.modal('show');
}

Application.showOkDialog = function (title, message) {
    var code =
        '<div class="above modal fade" style="z-index: 1300;" id="ok-dialog">' +
            '<div class="modal-dialog" style="width: 400px">' +
                '<div class="modal-content">' +
                    '<div class="modal-header">' +
                        '<h4 class="pop-up modal-title">' + title + '</h4>' +
                    '</div>' +
                    '<div class="modal-body">' +
                        '<div class="form-line pop-up-message text-center">' + message + '</div>' +
                        '<div class="text-center">' +
                            '<a class="std-button ok-button">Ok</a>' +
                        '</div>' +
                    '</div>' +
                '</div>' +
            '</div>' +
        '</div>';

    var modal = $(code).appendTo('body').modal('show');

    $('#ok-dialog a.ok-button').on('click', function (e) {
        e.preventDefault();
        e.stopPropagation();
        if ($('.modal').length > 1) {
            modal.remove();
            $('.modal-backdrop.fade.in').length ? $('.modal-backdrop.fade.in')[0].remove(): '';
        } else {
            Application.removeModal();
        }
    });
};

Application.showEmptyDialog = function (title, content, width, customClass) {
    customClass = customClass ? customClass : '';
    width = width ? 'style="width:' + width + 'px"' : '';
    var code =
        '<div class="modal fade" id="empty-dialog">' +
            '<div class="modal-dialog" ' + width + '>' +
                '<div class="modal-content">' +
                    '<div class="modal-header">' +
                        '<h4 class="pop-up modal-title">' + title + '</h4>' +
                        '<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">x</span></button>' +
                    '</div>' +
                    '<div class="modal-body ' + customClass + '">' +
                        content +
                    '</div>' +
                '</div>' +
            '</div>' +
        '</div>';

    $('body').append(code);
    $('.modal').modal('toggle');

    $('.action-close').on('click', function () {
        Application.removeModal();
    });
};

Application.confirmMessage = function(text) {
    var answer = window.confirm(text);

    if (answer == true) {
        return true;
    } else {
        return false;
    }
};

Application.showSuccessPage = function (message, link, contentColor, textColor, ff, fs, image, headerColor, buttonColor, headerText, buttonText) {        
    if (!UI.getDevice().isDesktop()) {
        fs = '14px';
    }
    var onClick = 'onclick="UI.pager.goToPage(\'' + link + '\')"';  
    if (!UI.getSetting('ispreview')) {
        onClick = '';
    }
    var code =
        '<div class="modal fade" id="ok-dialog" style="z-index: 100000;">' +
            '<div class="modal-dialog" style="width: 900px;">' +
                '<div class="modal-content" style="height: 600px; background-color: ' + contentColor + '">' +
                    '<div class="modal-header" style="background-color: ' + headerColor + ' !important">' +
                        '<h4 class="modal-title">' + headerText + '</h4>' +
                    '</div>' +
                    '<div class="modal-body" style="height: 440px;">' +
                        '<div class="dialog-content-image-logo">' +
                            '<span class="helper"></span>' +
                            '<img id="logoImage" class="dialog-image-logo" src="' + image + '" />' +
                        '</div>' +
                        '<div class="form-line dialog-content-message" style="font-size: ' + fs + '; font-family: ' + ff + '; color: ' + textColor + '">' +
                            '<div class="dialog-message">' + message + '</div>' +
                        '</div>' +
                        '<div class="dialog-content-actions">' +
                            '<a class="custom-button ok-button" style="background-color: ' + buttonColor + '" ' + onClick + '>' + buttonText + '</a>' +
                        '</div>' +
                    '</div>' +
                '</div>' +
            '</div>' +
        '</div>';

    $('body').append(code);
    $('.modal').modal('toggle');

    $('.action-close').on('click', function (e) {
        if (!UI.getSetting('ispreview')) {
            e.preventDefault();
            e.stopPropagation();
            $('#ok-dialog').modal('hide');
        } else {
            Application.removeModal();
        }
    });

    $('#ok-dialog a.ok-button').on('click', function (e) {
        if (!UI.getSetting('ispreview')) {
            e.preventDefault();
            e.stopPropagation();
            $('#ok-dialog').modal('hide');
        } else {
            Application.removeModal();
        }
    });
};

Application.addSpinner = function (spinnerClass, parentSelector) {
    var spinner = new Spinner({
        length: 0, // The length of each line
        width: 6, // The line thickness
        radius: 28, // The radius of the inner circle
        color: '#fff', // #rgb or #rrggbb or array of colors
        opacity: 0, // Opacity of the lines
        speed: 0.5, // Rounds per second
        trail: 60 // Afterglow percentage	        
    }).spin();
    var lockerTemplate = $('<div style="background-color: #000;background-color: rgba(0, 0, 0, 0.5);" class="' + spinnerClass + '"></div>');
    lockerTemplate.append(spinner.el);
    $(parentSelector).append(lockerTemplate);
};

Application.disableNewLockers = false;
Application.addLocker = function () {
    if (!Application.disableNewLockers) {
        var spinner = new Spinner({
            length: 0, // The length of each line
            width: 18, // The line thickness
            radius: 84, // The radius of the inner circle
            color: '#fff', // #rgb or #rrggbb or array of colors
            opacity: 0, // Opacity of the lines
            speed: 0.5, // Rounds per second
            trail: 60 // Afterglow percentage	        
        }).spin();
        var lockerTemplate = $('<div id="locker" data-html2canvas-ignore="true"></div>');
        lockerTemplate.append(spinner.el);
        $('body').append(lockerTemplate);
    }
};
Application.removeLocker = function () {
    $('#locker').remove();
    $('#locker').remove();
};

GridMvcAjax = {};
GridMvcAjax.demo = (function (my, $) {

    var constructorSpec = {
        updateGridAction: ''
    };

    my.init = function (options, gridtype, onGridLoadedCallback) {
        onGridLoadedCallback = onGridLoadedCallback || function () { };
        $(function () {
            constructorSpec = options;
            $.ajaxSetup({
                cache: false
            });

            var grid = null;

            if (gridtype == 'billingAll') {
                grid = pageGrids.billingAll;
            }
            if (gridtype == 'billingDealers') {
                grid = pageGrids.billingDealers;
            }
            else if (gridtype == 'userList') {
                grid = pageGrids.userslist;
            }
            else if (gridtype == 'paymentProfiles') {
                grid = pageGrids.paymentProfiles;
            }
            else if (gridtype == 'sites') {
                grid = pageGrids.sites;
            }
            else if (gridtype == 'sslPackages') {
                grid = pageGrids.sslPackages;
            }
            else if (gridtype == 'TemplateGrid') {
                grid = pageGrids.TemplateGrid;
            }

            else if (gridtype == 'SelectTemplatesGrid') {
                grid = pageGrids.SelectTemplatesGrid;
            }

            else if (gridtype == 'commerceUsersList') {
                grid = pageGrids.commerceUsersList;
            }

            else if (gridtype == 'logsGrid') {
                grid = pageGrids.logsGrid;
            }

            else if (gridtype == 'commerceProductsList') {
                grid = pageGrids.commerceProductsList;
            }
            else if (gridtype == 'adminLogsGrid') {
                grid = pageGrids.adminLogsGrid;
            }

            else if (gridtype == 'commerceCategoriesList') {
                grid = pageGrids.commerceCategoriesList;
            }

            else if (gridtype == 'commerceOrdersList') {
                grid = pageGrids.commerceOrdersList;
            }

            else if (gridtype == 'payments') {
                grid = pageGrids.payments;
            }

            else if (gridtype == 'shippingAnadTax') {
                grid = pageGrids.shippingAnadTax;
            }

            else if (gridtype == 'storeSettings') {
                grid = pageGrids.storeSettings;
            }

            else if (gridtype == 'ComponentsGrid') {
                grid = pageGrids.ComponentsGrid;
            }

            else if (gridtype == 'SectionsGrid') {
                grid = pageGrids.SectionsGrid;
            }

            else if (gridtype == 'SelectSectionsGrid') {
                grid = pageGrids.SelectSectionsGrid;
            }

            if (typeof grid != "undefined" && grid != null) {
                grid.ajaxify({
                    getPagedData: constructorSpec.updateGridAction,
                    getData: constructorSpec.updateGridAction
                });
                grid.onGridLoaded(function (result) {
                    onGridLoadedCallback(result);
                });

                if (gridtype == 'TemplateGrid') {
                    grid.lang.boolTrueLabel = "Hide";
                    grid.lang.boolFalseLabel = "Show";
                }

                if (gridtype == 'userList' || gridtype == 'sites' || gridtype == 'sslPackages') {
                    $(".grid-mvc").gridmvcstart(gridtype);
                }

                if (gridtype == 'commerceProductsList' || gridtype == 'commerceCategoriesList'
                    || gridtype == 'commerceUsersList'
                    || gridtype == 'commerceOrdersList' || gridtype == 'payments'
                    || gridtype == 'shippingAndTax' || gridtype == 'storeSettings') {
                    $(".grid-mvc").gridmvcstart(gridtype);
                }
               
                if (gridtype == 'commerceUsersList') {
                    var fullSearch = $.cookie(grid.loadPagedDataAction);
                    if (!String.isNullOrEmpty(fullSearch) && defined($.cookie)) {
                        grid.clearGridFilters();
                        grid.updateGrid(fullSearch, function () {
                            Application.removeLocker();
                            //go to page
                            grid.currentPage = constructorSpec.currentPage || 1;
                            //set filter and sort                         
                            var vars = fullSearch.substr(1).split('&');
                            for (var i = 0; i < vars.length; i++) {
                                var pair = vars[i].split('=');
                                if (decodeURIComponent(pair[0]) == 'grid-column') {
                                    grid.gridSort = 'grid-column=' + decodeURIComponent(pair[1]);
                                } else if (decodeURIComponent(pair[0]) == 'grid-dir') {
                                    grid.gridSort += '&grid-dir=' + decodeURIComponent(pair[1]);
                                } else if (decodeURIComponent(pair[0]) == 'grid-filter') {
                                    grid.gridColumnFilters = 'grid-filter=' + decodeURIComponent(pair[1]);
                                }
                            }
                            grid.loadPage();
                        });
                    } else {
                        grid.currentPage = constructorSpec.currentPage || 1;
                        grid.loadPage();
                    }
                }
            }
        });
    };

    return my;
}(GridMvcAjax.demo || {}, jQuery));


var LocalStorageService = function () { }

LocalStorageService.SetLocalTemplateParam = function (nameParam, value) {
    var templateValuesName = "template-values-" + window.templateId;
    try {
        var templateValues = JSON.parse(localStorage.getItem(templateValuesName)) || {};
        templateValues[nameParam] = value;
        if (value == null) {
            delete (templateValues[nameParam]);
        }
        localStorage.setItem(templateValuesName, JSON.stringify(templateValues));
    } catch (e) {
        console.log('Error write to localStorage');
        localStorage.removeItem(templateValuesName);
    }
}

LocalStorageService.SetItem = function (nameParam, value) {
    try {
        if (value !== null) {
            localStorage.setItem(nameParam, JSON.stringify(value));
        } else {
            localStorage.removeItem(nameParam);
        }
    } catch (e) {
        localStorage.removeItem(nameParam);
        console.log('Error write to localStorage', e);
    }
}

LocalStorageService.RemoveItem = function (nameParam) {
    LocalStorageService.SetItem(nameParam, null);
}

LocalStorageService.GetLocalTemplateParam = function (nameParam) {
    var templateValuesName = "template-values-" + window.templateId;
    var templateValues = JSON.parse(localStorage.getItem(templateValuesName)) || {};
    return templateValues[nameParam];
}

LocalStorageService.GetItem = function (nameParam) {
    try {
        var value = localStorage.getItem(nameParam);
        return JSON.parse(value);
    } catch (e) {
        return null;
    }
}

var IndexedDBService = function () { }

IndexedDBService.logerr = function (err) {
    console.log('IndexedDBService Error:', err);
}

IndexedDBService.connect = function (f) {
    var indexedDb = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
    // if your change schema => up version
    var request = indexedDb.open(INDEXEDDB_BASENAME, 1);
    request.onerror = IndexedDBService.logerr;
    request.onsuccess = function () {
        f(request.result);
    }
    request.onupgradeneeded = function (e) {
        var upgradeDb = e.currentTarget.result;
        if (!upgradeDb.objectStoreNames.contains(INDEXEDDB_STORE_TEMPLATE)) {
            upgradeDb.createObjectStore(INDEXEDDB_STORE_TEMPLATE, { keyPath: "id" });
        }
        // IndexedDBService.connect(f);
    }
}

IndexedDBService.getLocalTemplateParam = function (nameParam, f) {
    try {
        IndexedDBService.connect(function (db) {
            var store = db.transaction([INDEXEDDB_STORE_TEMPLATE], "readonly").objectStore(INDEXEDDB_STORE_TEMPLATE);
            var request = store.get(window.templateId || '');
            request.onerror = IndexedDBService.logerr;
            request.onsuccess = function (event) {
                var templateValues = event.target.result !== -1 && event.target.result || { id: window.templateId };
                if (nameParam) {
                    f(templateValues[nameParam]);
                } else {
                    f(templateValues);
                }
            };
        });
    } catch (e) {
        IndexedDBService.logerr(e);
    }
}

IndexedDBService.setLocalTemplateParam = function (obj) {
    try {
        IndexedDBService.connect(function (db) {
            var store = db.transaction([INDEXEDDB_STORE_TEMPLATE], "readwrite").objectStore(INDEXEDDB_STORE_TEMPLATE);
            var request = store.get(window.templateId || '');
            request.onerror = IndexedDBService.logerr;
            request.onsuccess = function (event) {
                var templateValues = event.target.result !== -1 && event.target.result || { id: window.templateId };
                Object.keys(obj).forEach(function (nameParam) {
                    if (obj[nameParam] == null) {
                        delete (templateValues[nameParam]);
                    } else {
                        templateValues[nameParam] = obj[nameParam];
                    }
                });
                var saveRequest = store.put(templateValues);
                saveRequest.onerror = IndexedDBService.logerr;
                saveRequest.onsuccess = function (event) { return event.target.result; };
            };
        });
    } catch (e) {
        IndexedDBService.logerr(e);
    }
}
;
/*!
 * Bootstrap v3.2.0 (http://getbootstrap.com)
 * Copyright 2011-2014 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 */

if (typeof jQuery === 'undefined') { throw new Error('Bootstrap\'s JavaScript requires jQuery') }

/* ========================================================================
 * Bootstrap: transition.js v3.2.0
 * http://getbootstrap.com/javascript/#transitions
 * ========================================================================
 * Copyright 2011-2014 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */


+function ($) {
  'use strict';

  // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
  // ============================================================

  function transitionEnd() {
    var el = document.createElement('bootstrap')

    var transEndEventNames = {
      WebkitTransition : 'webkitTransitionEnd',
      MozTransition    : 'transitionend',
      OTransition      : 'oTransitionEnd otransitionend',
      transition       : 'transitionend'
    }

    for (var name in transEndEventNames) {
      if (el.style[name] !== undefined) {
        return { end: transEndEventNames[name] }
      }
    }

    return false // explicit for ie8 (  ._.)
  }

  // http://blog.alexmaccaw.com/css-transitions
  $.fn.emulateTransitionEnd = function (duration) {
    var called = false
    var $el = this
    $(this).one('bsTransitionEnd', function () { called = true })
    var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
    setTimeout(callback, duration)
    return this
  }

  $(function () {
    $.support.transition = transitionEnd()

    if (!$.support.transition) return

    $.event.special.bsTransitionEnd = {
      bindType: $.support.transition.end,
      delegateType: $.support.transition.end,
      handle: function (e) {
        if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)
      }
    }
  })

}(jQuery);

/* ========================================================================
 * Bootstrap: alert.js v3.2.0
 * http://getbootstrap.com/javascript/#alerts
 * ========================================================================
 * Copyright 2011-2014 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */


+function ($) {
  'use strict';

  // ALERT CLASS DEFINITION
  // ======================

  var dismiss = '[data-dismiss="alert"]'
  var Alert   = function (el) {
    $(el).on('click', dismiss, this.close)
  }

  Alert.VERSION = '3.2.0'

  Alert.prototype.close = function (e) {
    var $this    = $(this)
    var selector = $this.attr('data-target')

    if (!selector) {
      selector = $this.attr('href')
      selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
    }

    var $parent = $(selector)

    if (e) e.preventDefault()

    if (!$parent.length) {
      $parent = $this.hasClass('alert') ? $this : $this.parent()
    }

    $parent.trigger(e = $.Event('close.bs.alert'))

    if (e.isDefaultPrevented()) return

    $parent.removeClass('in')

    function removeElement() {
      // detach from parent, fire event then clean up data
      $parent.detach().trigger('closed.bs.alert').remove()
    }

    $.support.transition && $parent.hasClass('fade') ?
      $parent
        .one('bsTransitionEnd', removeElement)
        .emulateTransitionEnd(150) :
      removeElement()
  }


  // ALERT PLUGIN DEFINITION
  // =======================

  function Plugin(option) {
    return this.each(function () {
      var $this = $(this)
      var data  = $this.data('bs.alert')

      if (!data) $this.data('bs.alert', (data = new Alert(this)))
      if (typeof option == 'string') data[option].call($this)
    })
  }

  var old = $.fn.alert

  $.fn.alert             = Plugin
  $.fn.alert.Constructor = Alert


  // ALERT NO CONFLICT
  // =================

  $.fn.alert.noConflict = function () {
    $.fn.alert = old
    return this
  }


  // ALERT DATA-API
  // ==============

  $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)

}(jQuery);

/* ========================================================================
 * Bootstrap: button.js v3.2.0
 * http://getbootstrap.com/javascript/#buttons
 * ========================================================================
 * Copyright 2011-2014 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */


+function ($) {
  'use strict';

  // BUTTON PUBLIC CLASS DEFINITION
  // ==============================

  var Button = function (element, options) {
    this.$element  = $(element)
    this.options   = $.extend({}, Button.DEFAULTS, options)
    this.isLoading = false
  }

  Button.VERSION  = '3.2.0'

  Button.DEFAULTS = {
    loadingText: 'loading...'
  }

  Button.prototype.setState = function (state) {
    var d    = 'disabled'
    var $el  = this.$element
    var val  = $el.is('input') ? 'val' : 'html'
    var data = $el.data()

    state = state + 'Text'

    if (data.resetText == null) $el.data('resetText', $el[val]())

    $el[val](data[state] == null ? this.options[state] : data[state])

    // push to event loop to allow forms to submit
    setTimeout($.proxy(function () {
      if (state == 'loadingText') {
        this.isLoading = true
        $el.addClass(d).attr(d, d)
      } else if (this.isLoading) {
        this.isLoading = false
        $el.removeClass(d).removeAttr(d)
      }
    }, this), 0)
  }

  Button.prototype.toggle = function () {
    var changed = true
    var $parent = this.$element.closest('[data-toggle="buttons"]')

    if ($parent.length) {
      var $input = this.$element.find('input')
      if ($input.prop('type') == 'radio') {
        if ($input.prop('checked') && this.$element.hasClass('active')) changed = false
        else $parent.find('.active').removeClass('active')
      }
      if (changed) $input.prop('checked', !this.$element.hasClass('active')).trigger('change')
    }

    if (changed) this.$element.toggleClass('active')
  }


  // BUTTON PLUGIN DEFINITION
  // ========================

  function Plugin(option) {
    return this.each(function () {
      var $this   = $(this)
      var data    = $this.data('bs.button')
      var options = typeof option == 'object' && option

      if (!data) $this.data('bs.button', (data = new Button(this, options)))

      if (option == 'toggle') data.toggle()
      else if (option) data.setState(option)
    })
  }

  var old = $.fn.button

  $.fn.button             = Plugin
  $.fn.button.Constructor = Button


  // BUTTON NO CONFLICT
  // ==================

  $.fn.button.noConflict = function () {
    $.fn.button = old
    return this
  }


  // BUTTON DATA-API
  // ===============

  $(document).on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) {
    var $btn = $(e.target)
    if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
    Plugin.call($btn, 'toggle')
    e.preventDefault()
  })

}(jQuery);

/* ========================================================================
 * Bootstrap: carousel.js v3.2.0
 * http://getbootstrap.com/javascript/#carousel
 * ========================================================================
 * Copyright 2011-2014 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */


+function ($) {
  'use strict';

  // CAROUSEL CLASS DEFINITION
  // =========================

  var Carousel = function (element, options) {
    this.$element    = $(element).on('keydown.bs.carousel', $.proxy(this.keydown, this))
    this.$indicators = this.$element.find('.carousel-indicators')
    this.options     = options
    this.paused      =
    this.sliding     =
    this.interval    =
    this.$active     =
    this.$items      = null

    this.options.pause == 'hover' && this.$element
      .on('mouseenter.bs.carousel', $.proxy(this.pause, this))
      .on('mouseleave.bs.carousel', $.proxy(this.cycle, this))
  }

  Carousel.VERSION  = '3.2.0'

  Carousel.DEFAULTS = {
    interval: 5000,
    pause: 'hover',
    wrap: true
  }

  Carousel.prototype.keydown = function (e) {
    switch (e.which) {
      case 37: this.prev(); break
      case 39: this.next(); break
      default: return
    }

    e.preventDefault()
  }

  Carousel.prototype.cycle = function (e) {
    e || (this.paused = false)

    this.interval && clearInterval(this.interval)

    this.options.interval
      && !this.paused
      && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))

    return this
  }

  Carousel.prototype.getItemIndex = function (item) {
    this.$items = item.parent().children('.item')
    return this.$items.index(item || this.$active)
  }

  Carousel.prototype.to = function (pos) {
    var that        = this
    var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active'))

    if (pos > (this.$items.length - 1) || pos < 0) return

    if (this.sliding)       return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid"
    if (activeIndex == pos) return this.pause().cycle()

    return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos]))
  }

  Carousel.prototype.pause = function (e) {
    e || (this.paused = true)

    if (this.$element.find('.next, .prev').length && $.support.transition) {
      this.$element.trigger($.support.transition.end)
      this.cycle(true)
    }

    this.interval = clearInterval(this.interval)

    return this
  }

  Carousel.prototype.next = function () {
    if (this.sliding) return
    return this.slide('next')
  }

  Carousel.prototype.prev = function () {
    if (this.sliding) return
    return this.slide('prev')
  }

  Carousel.prototype.slide = function (type, next) {
    var $active   = this.$element.find('.item.active')
    var $next     = next || $active[type]()
    var isCycling = this.interval
    var direction = type == 'next' ? 'left' : 'right'
    var fallback  = type == 'next' ? 'first' : 'last'
    var that      = this

    if (!$next.length) {
      if (!this.options.wrap) return
      $next = this.$element.find('.item')[fallback]()
    }

    if ($next.hasClass('active')) return (this.sliding = false)

    var relatedTarget = $next[0]
    var slideEvent = $.Event('slide.bs.carousel', {
      relatedTarget: relatedTarget,
      direction: direction
    })
    this.$element.trigger(slideEvent)
    if (slideEvent.isDefaultPrevented()) return

    this.sliding = true

    isCycling && this.pause()

    if (this.$indicators.length) {
      this.$indicators.find('.active').removeClass('active')
      var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)])
      $nextIndicator && $nextIndicator.addClass('active')
    }

    var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid"
    if ($.support.transition && this.$element.hasClass('slide')) {
      $next.addClass(type)
      $next[0].offsetWidth // force reflow
      $active.addClass(direction)
      $next.addClass(direction)
      $active
        .one('bsTransitionEnd', function () {
          $next.removeClass([type, direction].join(' ')).addClass('active')
          $active.removeClass(['active', direction].join(' '))
          that.sliding = false
          setTimeout(function () {
            that.$element.trigger(slidEvent)
          }, 0)
        })
        .emulateTransitionEnd($active.css('transition-duration').slice(0, -1) * 1000)
    } else {
      $active.removeClass('active')
      $next.addClass('active')
      this.sliding = false
      this.$element.trigger(slidEvent)
    }

    isCycling && this.cycle()

    return this
  }


  // CAROUSEL PLUGIN DEFINITION
  // ==========================

  function Plugin(option) {
    return this.each(function () {
      var $this   = $(this)
      var data    = $this.data('bs.carousel')
      var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)
      var action  = typeof option == 'string' ? option : options.slide

      if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))
      if (typeof option == 'number') data.to(option)
      else if (action) data[action]()
      else if (options.interval) data.pause().cycle()
    })
  }

  var old = $.fn.carousel

  $.fn.carousel             = Plugin
  $.fn.carousel.Constructor = Carousel


  // CAROUSEL NO CONFLICT
  // ====================

  $.fn.carousel.noConflict = function () {
    $.fn.carousel = old
    return this
  }


  // CAROUSEL DATA-API
  // =================

  $(document).on('click.bs.carousel.data-api', '[data-slide], [data-slide-to]', function (e) {
    var href
    var $this   = $(this)
    var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7
    if (!$target.hasClass('carousel')) return
    var options = $.extend({}, $target.data(), $this.data())
    var slideIndex = $this.attr('data-slide-to')
    if (slideIndex) options.interval = false

    Plugin.call($target, options)

    if (slideIndex) {
      $target.data('bs.carousel').to(slideIndex)
    }

    e.preventDefault()
  })

  $(window).on('load', function () {
    $('[data-ride="carousel"]').each(function () {
      var $carousel = $(this)
      Plugin.call($carousel, $carousel.data())
    })
  })

}(jQuery);

/* ========================================================================
 * Bootstrap: collapse.js v3.2.0
 * http://getbootstrap.com/javascript/#collapse
 * ========================================================================
 * Copyright 2011-2014 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */


+function ($) {
  'use strict';

  // COLLAPSE PUBLIC CLASS DEFINITION
  // ================================

  var Collapse = function (element, options) {
    this.$element      = $(element)
    this.options       = $.extend({}, Collapse.DEFAULTS, options)
    this.transitioning = null

    if (this.options.parent) this.$parent = $(this.options.parent)
    if (this.options.toggle) this.toggle()
  }

  Collapse.VERSION  = '3.2.0'

  Collapse.DEFAULTS = {
    toggle: true
  }

  Collapse.prototype.dimension = function () {
    var hasWidth = this.$element.hasClass('width')
    return hasWidth ? 'width' : 'height'
  }

  Collapse.prototype.show = function () {
    if (this.transitioning || this.$element.hasClass('in')) return

    var startEvent = $.Event('show.bs.collapse')
    this.$element.trigger(startEvent)
    if (startEvent.isDefaultPrevented()) return

    var actives = this.$parent && this.$parent.find('> .panel > .in')

    if (actives && actives.length) {
      var hasData = actives.data('bs.collapse')
      if (hasData && hasData.transitioning) return
      Plugin.call(actives, 'hide')
      hasData || actives.data('bs.collapse', null)
    }

    var dimension = this.dimension()

    this.$element
      .removeClass('collapse')
      .addClass('collapsing')[dimension](0)

    this.transitioning = 1

    var complete = function () {
      this.$element
        .removeClass('collapsing')
        .addClass('collapse in')[dimension]('')
      this.transitioning = 0
      this.$element
        .trigger('shown.bs.collapse')
    }

    if (!$.support.transition) return complete.call(this)

    var scrollSize = $.camelCase(['scroll', dimension].join('-'))

    this.$element
      .one('bsTransitionEnd', $.proxy(complete, this))
      .emulateTransitionEnd(350)[dimension](this.$element[0][scrollSize])
  }

  Collapse.prototype.hide = function () {
    if (this.transitioning || !this.$element.hasClass('in')) return

    var startEvent = $.Event('hide.bs.collapse')
    this.$element.trigger(startEvent)
    if (startEvent.isDefaultPrevented()) return

    var dimension = this.dimension()

    this.$element[dimension](this.$element[dimension]())[0].offsetHeight

    this.$element
      .addClass('collapsing')
      .removeClass('collapse')
      .removeClass('in')

    this.transitioning = 1

    var complete = function () {
      this.transitioning = 0
      this.$element
        .trigger('hidden.bs.collapse')
        .removeClass('collapsing')
        .addClass('collapse')
    }

    if (!$.support.transition) return complete.call(this)

    this.$element
      [dimension](0)
      .one('bsTransitionEnd', $.proxy(complete, this))
      .emulateTransitionEnd(350)
  }

  Collapse.prototype.toggle = function () {
    this[this.$element.hasClass('in') ? 'hide' : 'show']()
  }


  // COLLAPSE PLUGIN DEFINITION
  // ==========================

  function Plugin(option) {
    return this.each(function () {
      var $this   = $(this)
      var data    = $this.data('bs.collapse')
      var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)

      if (!data && options.toggle && option == 'show') option = !option
      if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
      if (typeof option == 'string') data[option]()
    })
  }

  var old = $.fn.collapse

  $.fn.collapse             = Plugin
  $.fn.collapse.Constructor = Collapse


  // COLLAPSE NO CONFLICT
  // ====================

  $.fn.collapse.noConflict = function () {
    $.fn.collapse = old
    return this
  }


  // COLLAPSE DATA-API
  // =================

  $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) {
    var href
    var $this   = $(this)
    var target  = $this.attr('data-target')
        || e.preventDefault()
        || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7
    var $target = $(target)
    var data    = $target.data('bs.collapse')
    var option  = data ? 'toggle' : $this.data()
    var parent  = $this.attr('data-parent')
    var $parent = parent && $(parent)

    if (!data || !data.transitioning) {
      if ($parent) $parent.find('[data-toggle="collapse"][data-parent="' + parent + '"]').not($this).addClass('collapsed')
      $this[$target.hasClass('in') ? 'addClass' : 'removeClass']('collapsed')
    }

    Plugin.call($target, option)
  })

}(jQuery);

/* ========================================================================
 * Bootstrap: dropdown.js v3.2.0
 * http://getbootstrap.com/javascript/#dropdowns
 * ========================================================================
 * Copyright 2011-2014 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */


+function ($) {
  'use strict';

  // DROPDOWN CLASS DEFINITION
  // =========================

  var backdrop = '.dropdown-backdrop'
  var toggle   = '[data-toggle="dropdown"]'
  var Dropdown = function (element) {
    $(element).on('click.bs.dropdown', this.toggle)
  }

  Dropdown.VERSION = '3.2.0'

  Dropdown.prototype.toggle = function (e) {
    var $this = $(this)

    if ($this.is('.disabled, :disabled')) return

    var $parent  = getParent($this)
    var isActive = $parent.hasClass('open')

    clearMenus()

    if (!isActive) {
      if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
        // if mobile we use a backdrop because click events don't delegate
        $('<div class="dropdown-backdrop"/>').insertAfter($(this)).on('click', clearMenus)
      }

      var relatedTarget = { relatedTarget: this }
      $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))

      if (e.isDefaultPrevented()) return

      $this.trigger('focus')

      $parent
        .toggleClass('open')
        .trigger('shown.bs.dropdown', relatedTarget)
    }

    return false
  }

  Dropdown.prototype.keydown = function (e) {
    if (!/(38|40|27)/.test(e.keyCode)) return

    var $this = $(this)

    e.preventDefault()
    e.stopPropagation()

    if ($this.is('.disabled, :disabled')) return

    var $parent  = getParent($this)
    var isActive = $parent.hasClass('open')

    if (!isActive || (isActive && e.keyCode == 27)) {
      if (e.which == 27) $parent.find(toggle).trigger('focus')
      return $this.trigger('click')
    }

    var desc = ' li:not(.divider):visible a'
    var $items = $parent.find('[role="menu"]' + desc + ', [role="listbox"]' + desc)

    if (!$items.length) return

    var index = $items.index($items.filter(':focus'))

    if (e.keyCode == 38 && index > 0)                 index--                        // up
    if (e.keyCode == 40 && index < $items.length - 1) index++                        // down
    if (!~index)                                      index = 0

    $items.eq(index).trigger('focus')
  }

  function clearMenus(e) {
    if (e && e.which === 3) return
    $(backdrop).remove()
    $(toggle).each(function () {
      var $parent = getParent($(this))
      var relatedTarget = { relatedTarget: this }
      if (!$parent.hasClass('open')) return
      $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
      if (e.isDefaultPrevented()) return
      $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget)
    })
  }

  function getParent($this) {
    var selector = $this.attr('data-target')

    if (!selector) {
      selector = $this.attr('href')
      selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
    }

    var $parent = selector && $(selector)

    return $parent && $parent.length ? $parent : $this.parent()
  }


  // DROPDOWN PLUGIN DEFINITION
  // ==========================

  function Plugin(option) {
    return this.each(function () {
      var $this = $(this)
      var data  = $this.data('bs.dropdown')

      if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))
      if (typeof option == 'string') data[option].call($this)
    })
  }

  var old = $.fn.dropdown

  $.fn.dropdown             = Plugin
  $.fn.dropdown.Constructor = Dropdown


  // DROPDOWN NO CONFLICT
  // ====================

  $.fn.dropdown.noConflict = function () {
    $.fn.dropdown = old
    return this
  }


  // APPLY TO STANDARD DROPDOWN ELEMENTS
  // ===================================

  $(document)
    .on('click.bs.dropdown.data-api', clearMenus)
    .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
    .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
    .on('keydown.bs.dropdown.data-api', toggle + ', [role="menu"], [role="listbox"]', Dropdown.prototype.keydown)

}(jQuery);

/* ========================================================================
 * Bootstrap: modal.js v3.2.0
 * http://getbootstrap.com/javascript/#modals
 * ========================================================================
 * Copyright 2011-2014 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */


+function ($) {
  'use strict';

  // MODAL CLASS DEFINITION
  // ======================

  var Modal = function (element, options) {
    this.options        = options
    this.$body          = $(document.body)
    this.$element       = $(element)
    this.$backdrop      =
    this.isShown        = null
    this.scrollbarWidth = 0

    if (this.options.remote) {
      this.$element
        .find('.modal-content')
        .load(this.options.remote, $.proxy(function () {
          this.$element.trigger('loaded.bs.modal')
        }, this))
    }
  }

  Modal.VERSION  = '3.2.0'

  Modal.DEFAULTS = {
    backdrop: true,
    keyboard: true,
    show: true
  }

  Modal.prototype.toggle = function (_relatedTarget) {
    return this.isShown ? this.hide() : this.show(_relatedTarget)
  }

  Modal.prototype.show = function (_relatedTarget) {
    var that = this
    var e    = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })

    this.$element.trigger(e)

    if (this.isShown || e.isDefaultPrevented()) return

    this.isShown = true

    this.checkScrollbar()
    this.$body.addClass('modal-open')

    this.setScrollbar()
    this.escape()

    this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))

    this.backdrop(function () {
      var transition = $.support.transition && that.$element.hasClass('fade')

      if (!that.$element.parent().length) {
        that.$element.appendTo(that.$body) // don't move modals dom position
      }

      that.$element
        .show()
        .scrollTop(0)

      if (transition) {
        that.$element[0].offsetWidth // force reflow
      }

      that.$element
        .addClass('in')
        .attr('aria-hidden', false)

      that.enforceFocus()

      var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })

      transition ?
        that.$element.find('.modal-dialog') // wait for modal to slide in
          .one('bsTransitionEnd', function () {
            that.$element.trigger('focus').trigger(e)
          })
          .emulateTransitionEnd(300) :
        that.$element.trigger('focus').trigger(e)
    })
  }

  Modal.prototype.hide = function (e) {
    if (e) e.preventDefault()

    e = $.Event('hide.bs.modal')

    this.$element.trigger(e)

    if (!this.isShown || e.isDefaultPrevented()) return

    this.isShown = false

    this.$body.removeClass('modal-open')

    this.resetScrollbar()
    this.escape()

    $(document).off('focusin.bs.modal')

    this.$element
      .removeClass('in')
      .attr('aria-hidden', true)
      .off('click.dismiss.bs.modal')

    $.support.transition && this.$element.hasClass('fade') ?
      this.$element
        .one('bsTransitionEnd', $.proxy(this.hideModal, this))
        .emulateTransitionEnd(300) :
      this.hideModal()
  }

  Modal.prototype.enforceFocus = function () {
    $(document)
      .off('focusin.bs.modal') // guard against infinite focus loop
      .on('focusin.bs.modal', $.proxy(function (e) {
        if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {
          this.$element.trigger('focus')
        }
      }, this))
  }

  Modal.prototype.escape = function () {
    if (this.isShown && this.options.keyboard) {
      this.$element.on('keyup.dismiss.bs.modal', $.proxy(function (e) {
        e.which == 27 && this.hide()
      }, this))
    } else if (!this.isShown) {
      this.$element.off('keyup.dismiss.bs.modal')
    }
  }

  Modal.prototype.hideModal = function () {
    var that = this
    this.$element.hide()
    this.backdrop(function () {
      that.$element.trigger('hidden.bs.modal')
    })
  }

  Modal.prototype.removeBackdrop = function () {
    this.$backdrop && this.$backdrop.remove()
    this.$backdrop = null
  }

  Modal.prototype.backdrop = function (callback) {
    var that = this
    var animate = this.$element.hasClass('fade') ? 'fade' : ''

    if (this.isShown && this.options.backdrop) {
      var doAnimate = $.support.transition && animate

      this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
        .appendTo(this.$body)

      this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) {
        if (e.target !== e.currentTarget) return
        this.options.backdrop == 'static'
          ? this.$element[0].focus.call(this.$element[0])
          : this.hide.call(this)
      }, this))

      if (doAnimate) this.$backdrop[0].offsetWidth // force reflow

      this.$backdrop.addClass('in')

      if (!callback) return

      doAnimate ?
        this.$backdrop
          .one('bsTransitionEnd', callback)
          .emulateTransitionEnd(150) :
        callback()

    } else if (!this.isShown && this.$backdrop) {
      this.$backdrop.removeClass('in')

      var callbackRemove = function () {
        that.removeBackdrop()
        callback && callback()
      }
      $.support.transition && this.$element.hasClass('fade') ?
        this.$backdrop
          .one('bsTransitionEnd', callbackRemove)
          .emulateTransitionEnd(150) :
        callbackRemove()

    } else if (callback) {
      callback()
    }
  }

  Modal.prototype.checkScrollbar = function () {
    if (document.body.clientWidth >= window.innerWidth) return
    this.scrollbarWidth = this.scrollbarWidth || this.measureScrollbar()
  }

  Modal.prototype.setScrollbar = function () {
    var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
    if (this.scrollbarWidth) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)
  }

  Modal.prototype.resetScrollbar = function () {
    this.$body.css('padding-right', '')
  }

  Modal.prototype.measureScrollbar = function () { // thx walsh
    var scrollDiv = document.createElement('div')
    scrollDiv.className = 'modal-scrollbar-measure'
    this.$body.append(scrollDiv)
    var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
    this.$body[0].removeChild(scrollDiv)
    return scrollbarWidth
  }


  // MODAL PLUGIN DEFINITION
  // =======================

  function Plugin(option, _relatedTarget) {
    return this.each(function () {
      var $this   = $(this)
      var data    = $this.data('bs.modal')
      var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)

      if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
      if (typeof option == 'string') data[option](_relatedTarget)
      else if (options.show) data.show(_relatedTarget)
    })
  }

  var old = $.fn.modal

  $.fn.modal             = Plugin
  $.fn.modal.Constructor = Modal


  // MODAL NO CONFLICT
  // =================

  $.fn.modal.noConflict = function () {
    $.fn.modal = old
    return this
  }


  // MODAL DATA-API
  // ==============

  $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
    var $this   = $(this)
    var href    = $this.attr('href')
    var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7
    var option  = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())

    if ($this.is('a')) e.preventDefault()

    $target.one('show.bs.modal', function (showEvent) {
      if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown
      $target.one('hidden.bs.modal', function () {
        $this.is(':visible') && $this.trigger('focus')
      })
    })
    Plugin.call($target, option, this)
  })

}(jQuery);



/* ========================================================================
 * Bootstrap: scrollspy.js v3.2.0
 * http://getbootstrap.com/javascript/#scrollspy
 * ========================================================================
 * Copyright 2011-2014 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */


+function ($) {
  'use strict';

  // SCROLLSPY CLASS DEFINITION
  // ==========================

  function ScrollSpy(element, options) {
    var process  = $.proxy(this.process, this)

    this.$body          = $('body')
    this.$scrollElement = $(element).is('body') ? $(window) : $(element)
    this.options        = $.extend({}, ScrollSpy.DEFAULTS, options)
    this.selector       = (this.options.target || '') + ' .nav li > a'
    this.offsets        = []
    this.targets        = []
    this.activeTarget   = null
    this.scrollHeight   = 0

    this.$scrollElement.on('scroll.bs.scrollspy', process)
    this.refresh()
    this.process()
  }

  ScrollSpy.VERSION  = '3.2.0'

  ScrollSpy.DEFAULTS = {
    offset: 10
  }

  ScrollSpy.prototype.getScrollHeight = function () {
    return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)
  }

  ScrollSpy.prototype.refresh = function () {
    var offsetMethod = 'offset'
    var offsetBase   = 0

    if (!$.isWindow(this.$scrollElement[0])) {
      offsetMethod = 'position'
      offsetBase   = this.$scrollElement.scrollTop()
    }

    this.offsets = []
    this.targets = []
    this.scrollHeight = this.getScrollHeight()

    var self     = this

    this.$body
      .find(this.selector)
      .map(function () {
        var $el   = $(this)
        var href  = $el.data('target') || $el.attr('href')
        var $href = /^#./.test(href) && $(href)

        return ($href
          && $href.length
          && $href.is(':visible')
          && [[$href[offsetMethod]().top + offsetBase, href]]) || null
      })
      .sort(function (a, b) { return a[0] - b[0] })
      .each(function () {
        self.offsets.push(this[0])
        self.targets.push(this[1])
      })
  }

  ScrollSpy.prototype.process = function () {
    var scrollTop    = this.$scrollElement.scrollTop() + this.options.offset
    var scrollHeight = this.getScrollHeight()
    var maxScroll    = this.options.offset + scrollHeight - this.$scrollElement.height()
    var offsets      = this.offsets
    var targets      = this.targets
    var activeTarget = this.activeTarget
    var i

    if (this.scrollHeight != scrollHeight) {
      this.refresh()
    }

    if (scrollTop >= maxScroll) {
      return activeTarget != (i = targets[targets.length - 1]) && this.activate(i)
    }

    if (activeTarget && scrollTop <= offsets[0]) {
      return activeTarget != (i = targets[0]) && this.activate(i)
    }

    for (i = offsets.length; i--;) {
      activeTarget != targets[i]
        && scrollTop >= offsets[i]
        && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
        && this.activate(targets[i])
    }
  }

  ScrollSpy.prototype.activate = function (target) {
    this.activeTarget = target

    $(this.selector)
      .parentsUntil(this.options.target, '.active')
      .removeClass('active')

    var selector = this.selector +
        '[data-target="' + target + '"],' +
        this.selector + '[href="' + target + '"]'

    var active = $(selector)
      .parents('li')
      .addClass('active')

    if (active.parent('.dropdown-menu').length) {
      active = active
        .closest('li.dropdown')
        .addClass('active')
    }

    active.trigger('activate.bs.scrollspy')
  }


  // SCROLLSPY PLUGIN DEFINITION
  // ===========================

  function Plugin(option) {
    return this.each(function () {
      var $this   = $(this)
      var data    = $this.data('bs.scrollspy')
      var options = typeof option == 'object' && option

      if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))
      if (typeof option == 'string') data[option]()
    })
  }

  var old = $.fn.scrollspy

  $.fn.scrollspy             = Plugin
  $.fn.scrollspy.Constructor = ScrollSpy


  // SCROLLSPY NO CONFLICT
  // =====================

  $.fn.scrollspy.noConflict = function () {
    $.fn.scrollspy = old
    return this
  }


  // SCROLLSPY DATA-API
  // ==================

  $(window).on('load.bs.scrollspy.data-api', function () {
    $('[data-spy="scroll"]').each(function () {
      var $spy = $(this)
      Plugin.call($spy, $spy.data())
    })
  })

}(jQuery);

/* ========================================================================
 * Bootstrap: tab.js v3.2.0
 * http://getbootstrap.com/javascript/#tabs
 * ========================================================================
 * Copyright 2011-2014 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */


+function ($) {
  'use strict';

  // TAB CLASS DEFINITION
  // ====================

  var Tab = function (element) {
    this.element = $(element)
  }

  Tab.VERSION = '3.2.0'

  Tab.prototype.show = function () {
    var $this    = this.element
    var $ul      = $this.closest('ul:not(.dropdown-menu)')
    var selector = $this.data('target')

    if (!selector) {
      selector = $this.attr('href')
      selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
    }

    if ($this.parent('li').hasClass('active')) return

    var previous = $ul.find('.active:last a')[0]
    var e        = $.Event('show.bs.tab', {
      relatedTarget: previous
    })

    $this.trigger(e)

    if (e.isDefaultPrevented()) return

    var $target = $(selector)

    this.activate($this.closest('li'), $ul)
    this.activate($target, $target.parent(), function () {
      $this.trigger({
        type: 'shown.bs.tab',
        relatedTarget: previous
      })
    })
  }

  Tab.prototype.activate = function (element, container, callback) {
    var $active    = container.find('> .active')
    var transition = callback
      && $.support.transition
      && $active.hasClass('fade')

    function next() {
      $active
        .removeClass('active')
        .find('> .dropdown-menu > .active')
        .removeClass('active')

      element.addClass('active')

      if (transition) {
        element[0].offsetWidth // reflow for transition
        element.addClass('in')
      } else {
        element.removeClass('fade')
      }

      if (element.parent('.dropdown-menu')) {
        element.closest('li.dropdown').addClass('active')
      }

      callback && callback()
    }

    transition ?
      $active
        .one('bsTransitionEnd', next)
        .emulateTransitionEnd(150) :
      next()

    $active.removeClass('in')
  }


  // TAB PLUGIN DEFINITION
  // =====================

  function Plugin(option) {
    return this.each(function () {
      var $this = $(this)
      var data  = $this.data('bs.tab')

      if (!data) $this.data('bs.tab', (data = new Tab(this)))
      if (typeof option == 'string') data[option]()
    })
  }

  var old = $.fn.tab

  $.fn.tab             = Plugin
  $.fn.tab.Constructor = Tab


  // TAB NO CONFLICT
  // ===============

  $.fn.tab.noConflict = function () {
    $.fn.tab = old
    return this
  }


  // TAB DATA-API
  // ============

  $(document).on('click.bs.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) {
    e.preventDefault()
    Plugin.call($(this), 'show')
  })

}(jQuery);

/* ========================================================================
 * Bootstrap: affix.js v3.2.0
 * http://getbootstrap.com/javascript/#affix
 * ========================================================================
 * Copyright 2011-2014 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */


+function ($) {
  'use strict';

  // AFFIX CLASS DEFINITION
  // ======================

  var Affix = function (element, options) {
    this.options = $.extend({}, Affix.DEFAULTS, options)

    this.$target = $(this.options.target)
      .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
      .on('click.bs.affix.data-api',  $.proxy(this.checkPositionWithEventLoop, this))

    this.$element     = $(element)
    this.affixed      =
    this.unpin        =
    this.pinnedOffset = null

    this.checkPosition()
  }

  Affix.VERSION  = '3.2.0'

  Affix.RESET    = 'affix affix-top affix-bottom'

  Affix.DEFAULTS = {
    offset: 0,
    target: window
  }

  Affix.prototype.getPinnedOffset = function () {
    if (this.pinnedOffset) return this.pinnedOffset
    this.$element.removeClass(Affix.RESET).addClass('affix')
    var scrollTop = this.$target.scrollTop()
    var position  = this.$element.offset()
    return (this.pinnedOffset = position.top - scrollTop)
  }

  Affix.prototype.checkPositionWithEventLoop = function () {
    setTimeout($.proxy(this.checkPosition, this), 1)
  }

  Affix.prototype.checkPosition = function () {
    if (!this.$element.is(':visible')) return

    var scrollHeight = $(document).height()
    var scrollTop    = this.$target.scrollTop()
    var position     = this.$element.offset()
    var offset       = this.options.offset
    var offsetTop    = offset.top
    var offsetBottom = offset.bottom

    if (typeof offset != 'object')         offsetBottom = offsetTop = offset
    if (typeof offsetTop == 'function')    offsetTop    = offset.top(this.$element)
    if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)

    var affix = this.unpin   != null && (scrollTop + this.unpin <= position.top) ? false :
                offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ? 'bottom' :
                offsetTop    != null && (scrollTop <= offsetTop) ? 'top' : false

    if (this.affixed === affix) return
    if (this.unpin != null) this.$element.css('top', '')

    var affixType = 'affix' + (affix ? '-' + affix : '')
    var e         = $.Event(affixType + '.bs.affix')

    this.$element.trigger(e)

    if (e.isDefaultPrevented()) return

    this.affixed = affix
    this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null

    this.$element
      .removeClass(Affix.RESET)
      .addClass(affixType)
      .trigger($.Event(affixType.replace('affix', 'affixed')))

    if (affix == 'bottom') {
      this.$element.offset({
        top: scrollHeight - this.$element.height() - offsetBottom
      })
    }
  }


  // AFFIX PLUGIN DEFINITION
  // =======================

  function Plugin(option) {
    return this.each(function () {
      var $this   = $(this)
      var data    = $this.data('bs.affix')
      var options = typeof option == 'object' && option

      if (!data) $this.data('bs.affix', (data = new Affix(this, options)))
      if (typeof option == 'string') data[option]()
    })
  }

  var old = $.fn.affix

  $.fn.affix             = Plugin
  $.fn.affix.Constructor = Affix


  // AFFIX NO CONFLICT
  // =================

  $.fn.affix.noConflict = function () {
    $.fn.affix = old
    return this
  }


  // AFFIX DATA-API
  // ==============

  $(window).on('load', function () {
    $('[data-spy="affix"]').each(function () {
      var $spy = $(this)
      var data = $spy.data()

      data.offset = data.offset || {}

      if (data.offsetBottom) data.offset.bottom = data.offsetBottom
      if (data.offsetTop)    data.offset.top    = data.offsetTop

      Plugin.call($spy, data)
    })
  })

}(jQuery);
;
(function ($) {
  "use strict";

  var defaultOptions = {
    tagClass: function(item) {
      return 'label label-info';
    },
    itemValue: function(item) {
      return item ? item.toString() : item;
    },
    itemText: function(item) {
      return this.itemValue(item);
    },
    freeInput: true,
    addOnBlur: true,
    maxTags: undefined,
    maxChars: undefined,
    confirmKeys: [13, 44],
    onTagExists: function(item, $tag) {
      $tag.hide().fadeIn();
    },
    trimValue: false,
    allowDuplicates: false
  };

  /**
   * Constructor function
   */
  function TagsInput(element, options) {
    this.itemsArray = [];

    this.$element = $(element);
    this.$element.hide();

    this.isSelect = (element.tagName === 'SELECT');
    this.multiple = (this.isSelect && element.hasAttribute('multiple'));
    this.objectItems = options && options.itemValue;
    this.placeholderText = element.hasAttribute('placeholder') ? this.$element.attr('placeholder') : '';
    this.inputSize = Math.max(1, this.placeholderText.length);

    this.$container = $('<div class="bootstrap-tagsinput"></div>');
    this.$input = $('<input type="text" placeholder="' + this.placeholderText + '"/>').appendTo(this.$container);

    this.$element.after(this.$container);

    var inputWidth = (this.inputSize < 3 ? 3 : this.inputSize) + "em";
    this.$input.get(0).style.cssText = "width: " + inputWidth + " !important;";
    this.build(options);
  }

  TagsInput.prototype = {
    constructor: TagsInput,

    /**
     * Adds the given item as a new tag. Pass true to dontPushVal to prevent
     * updating the elements val()
     */
    add: function(item, dontPushVal) {
      var self = this;

      if (self.options.maxTags && self.itemsArray.length >= self.options.maxTags)
        return;

      // Ignore falsey values, except false
      if (item !== false && !item)
        return;

      // Trim value
      if (typeof item === "string" && self.options.trimValue) {
        item = $.trim(item);
      }

      // Throw an error when trying to add an object while the itemValue option was not set
      if (typeof item === "object" && !self.objectItems)
        throw("Can't add objects when itemValue option is not set");

      // Ignore strings only containg whitespace
      if (item.toString().match(/^\s*$/))
        return;

      // If SELECT but not multiple, remove current tag
      if (self.isSelect && !self.multiple && self.itemsArray.length > 0)
        self.remove(self.itemsArray[0]);

      if (typeof item === "string" && this.$element[0].tagName === 'INPUT') {
        var items = item.split(',');
        if (items.length > 1) {
          for (var i = 0; i < items.length; i++) {
            this.add(items[i], true);
          }

          if (!dontPushVal)
            self.pushVal();
          return;
        }
      }

      var itemValue = self.options.itemValue(item),
          itemText = self.options.itemText(item),
          tagClass = self.options.tagClass(item);

      // Ignore items allready added
      var existing = $.grep(self.itemsArray, function(item) { return self.options.itemValue(item) === itemValue; } )[0];
      if (existing && !self.options.allowDuplicates) {
        // Invoke onTagExists
        if (self.options.onTagExists) {
          var $existingTag = $(".tag", self.$container).filter(function() { return $(this).data("item") === existing; });
          self.options.onTagExists(item, $existingTag);
        }
        return;
      }

      // if length greater than limit
      if (self.items().toString().length + item.length + 1 > self.options.maxInputLength)
        return;

      // raise beforeItemAdd arg
      var beforeItemAddEvent = $.Event('beforeItemAdd', { item: item, cancel: false });
      self.$element.trigger(beforeItemAddEvent);
      if (beforeItemAddEvent.cancel)
        return;

      // register item in internal array and map
      self.itemsArray.push(item);

      // add a tag element
      var $tag = $('<span class="tag ' + htmlEncode(tagClass) + '">' + htmlEncode(itemText) + '<span data-role="remove"></span></span>');
      $tag.data('item', item);
      self.findInputWrapper().before($tag);
      $tag.after(' ');

      // add <option /> if item represents a value not present in one of the <select />'s options
      if (self.isSelect && !$('option[value="' + encodeURIComponent(itemValue) + '"]',self.$element)[0]) {
        var $option = $('<option selected>' + htmlEncode(itemText) + '</option>');
        $option.data('item', item);
        $option.attr('value', itemValue);
        self.$element.append($option);
      }

      if (!dontPushVal)
        self.pushVal();

      // Add class when reached maxTags
      if (self.options.maxTags === self.itemsArray.length || self.items().toString().length === self.options.maxInputLength)
        self.$container.addClass('bootstrap-tagsinput-max');

      self.$element.trigger($.Event('itemAdded', { item: item }));
    },

    /**
     * Removes the given item. Pass true to dontPushVal to prevent updating the
     * elements val()
     */
    remove: function(item, dontPushVal) {
      var self = this;

      if (self.objectItems) {
        if (typeof item === "object")
          item = $.grep(self.itemsArray, function(other) { return self.options.itemValue(other) ==  self.options.itemValue(item); } );
        else
          item = $.grep(self.itemsArray, function(other) { return self.options.itemValue(other) ==  item; } );

        item = item[item.length-1];
      }

      if (item) {
        var beforeItemRemoveEvent = $.Event('beforeItemRemove', { item: item, cancel: false });
        self.$element.trigger(beforeItemRemoveEvent);
        if (beforeItemRemoveEvent.cancel)
          return;

        $('.tag', self.$container).filter(function() { return $(this).data('item') === item; }).remove();
        $('option', self.$element).filter(function() { return $(this).data('item') === item; }).remove();
        if($.inArray(item, self.itemsArray) !== -1)
          self.itemsArray.splice($.inArray(item, self.itemsArray), 1);
      }

      if (!dontPushVal)
        self.pushVal();

      // Remove class when reached maxTags
      if (self.options.maxTags > self.itemsArray.length)
        self.$container.removeClass('bootstrap-tagsinput-max');

      self.$element.trigger($.Event('itemRemoved',  { item: item }));
    },

    /**
     * Removes all items
     */
    removeAll: function() {
      var self = this;

      $('.tag', self.$container).remove();
      $('option', self.$element).remove();

      while(self.itemsArray.length > 0)
        self.itemsArray.pop();

      self.pushVal();
    },

    /**
     * Refreshes the tags so they match the text/value of their corresponding
     * item.
     */
    refresh: function() {
      var self = this;
      $('.tag', self.$container).each(function() {
        var $tag = $(this),
            item = $tag.data('item'),
            itemValue = self.options.itemValue(item),
            itemText = self.options.itemText(item),
            tagClass = self.options.tagClass(item);

          // Update tag's class and inner text
          $tag.attr('class', null);
          $tag.addClass('tag ' + htmlEncode(tagClass));
          $tag.contents().filter(function() {
            return this.nodeType == 3;
          })[0].nodeValue = htmlEncode(itemText);

          if (self.isSelect) {
            var option = $('option', self.$element).filter(function() { return $(this).data('item') === item; });
            option.attr('value', itemValue);
          }
      });
    },

    /**
     * Returns the items added as tags
     */
    items: function() {
      return this.itemsArray;
    },

    /**
     * Assembly value by retrieving the value of each item, and set it on the
     * element.
     */
    pushVal: function() {
      var self = this,
          val = $.map(self.items(), function(item) {
            return self.options.itemValue(item).toString();
          });

      self.$element.val(val, true).trigger('change');
    },

    /**
     * Initializes the tags input behaviour on the element
     */
    build: function(options) {
      var self = this;

      self.options = $.extend({}, defaultOptions, options);
      // When itemValue is set, freeInput should always be false
      if (self.objectItems)
        self.options.freeInput = false;

      makeOptionItemFunction(self.options, 'itemValue');
      makeOptionItemFunction(self.options, 'itemText');
      makeOptionFunction(self.options, 'tagClass');
      
      // Typeahead Bootstrap version 2.3.2
      if (self.options.typeahead) {
        var typeahead = self.options.typeahead || {};

        makeOptionFunction(typeahead, 'source');

        self.$input.typeahead($.extend({}, typeahead, {
          source: function (query, process) {
            function processItems(items) {
              var texts = [];

              for (var i = 0; i < items.length; i++) {
                var text = self.options.itemText(items[i]);
                map[text] = items[i];
                texts.push(text);
              }
              process(texts);
            }

            this.map = {};
            var map = this.map,
                data = typeahead.source(query);

            if ($.isFunction(data.success)) {
              // support for Angular callbacks
              data.success(processItems);
            } else if ($.isFunction(data.then)) {
              // support for Angular promises
              data.then(processItems);
            } else {
              // support for functions and jquery promises
              $.when(data)
               .then(processItems);
            }
          },
          updater: function (text) {
            self.add(this.map[text]);
          },
          matcher: function (text) {
            return (text.toLowerCase().indexOf(this.query.trim().toLowerCase()) !== -1);
          },
          sorter: function (texts) {
            return texts.sort();
          },
          highlighter: function (text) {
            var regex = new RegExp( '(' + this.query + ')', 'gi' );
            return text.replace( regex, "<strong>$1</strong>" );
          }
        }));
      }

      // typeahead.js
      if (self.options.typeaheadjs) {
          var typeaheadjs = self.options.typeaheadjs || {};
          
          self.$input.typeahead(null, typeaheadjs).on('typeahead:selected', $.proxy(function (obj, datum) {
            if (typeaheadjs.valueKey)
              self.add(datum[typeaheadjs.valueKey]);
            else
              self.add(datum);
            self.$input.typeahead('val', '');
          }, self));
      }

      self.$container.on('click', $.proxy(function(event) {
        if (! self.$element.attr('disabled')) {
          self.$input.removeAttr('disabled');
        }
        self.$input.focus();
      }, self));

        if (self.options.addOnBlur && self.options.freeInput) {
          self.$input.on('focusout', $.proxy(function(event) {
              // HACK: only process on focusout when no typeahead opened, to
              //       avoid adding the typeahead text as tag
              if ($('.typeahead, .twitter-typeahead', self.$container).length === 0) {
                self.add(self.$input.val());
                self.$input.val('');
              }
          }, self));
        }
        

      self.$container.on('keydown', 'input', $.proxy(function(event) {
        var $input = $(event.target),
            $inputWrapper = self.findInputWrapper();

        if (self.$element.attr('disabled')) {
          self.$input.attr('disabled', 'disabled');
          return;
        }

        switch (event.which) {
          // BACKSPACE
          case 8:
            if (doGetCaretPosition($input[0]) === 0) {
              var prev = $inputWrapper.prev();
              if (prev) {
                self.remove(prev.data('item'));
              }
            }
            break;

          // DELETE
          case 46:
            if (doGetCaretPosition($input[0]) === 0) {
              var next = $inputWrapper.next();
              if (next) {
                self.remove(next.data('item'));
              }
            }
            break;

          // LEFT ARROW
          case 37:
            // Try to move the input before the previous tag
            var $prevTag = $inputWrapper.prev();
            if ($input.val().length === 0 && $prevTag[0]) {
              $prevTag.before($inputWrapper);
              $input.focus();
            }
            break;
          // RIGHT ARROW
          case 39:
            // Try to move the input after the next tag
            var $nextTag = $inputWrapper.next();
            if ($input.val().length === 0 && $nextTag[0]) {
              $nextTag.after($inputWrapper);
              $input.focus();
            }
            break;
         default:
             // ignore
         }

        // Reset internal input's size
        var textLength = $input.val().length,
            wordSpace = Math.ceil(textLength / 5),
            size = textLength + wordSpace + 1;
        $input.attr('size', Math.max(this.inputSize, $input.val().length));
      }, self));

      self.$container.on('keypress', 'input', $.proxy(function(event) {
         var $input = $(event.target);

         if (self.$element.attr('disabled')) {
            self.$input.attr('disabled', 'disabled');
            return;
         }

         var text = $input.val(),
         maxLengthReached = self.options.maxChars && text.length >= self.options.maxChars;
         if (self.options.freeInput && (keyCombinationInList(event, self.options.confirmKeys) || maxLengthReached)) {
            self.add(maxLengthReached ? text.substr(0, self.options.maxChars) : text);
            $input.val('');
            event.preventDefault();
         }

         // Reset internal input's size
         var textLength = $input.val().length,
            wordSpace = Math.ceil(textLength / 5),
            size = textLength + wordSpace + 1;
         $input.attr('size', Math.max(this.inputSize, $input.val().length));
      }, self));

      // Remove icon clicked
      self.$container.on('click', '[data-role=remove]', $.proxy(function(event) {
        if (self.$element.attr('disabled')) {
          return;
        }
        self.remove($(event.target).closest('.tag').data('item'));
      }, self));

      // Only add existing value as tags when using strings as tags
      if (self.options.itemValue === defaultOptions.itemValue) {
        if (self.$element[0].tagName === 'INPUT') {
            self.add(self.$element.val());
        } else {
          $('option', self.$element).each(function() {
            self.add($(this).attr('value'), true);
          });
        }
      }
    },

    /**
     * Removes all tagsinput behaviour and unregsiter all event handlers
     */
    destroy: function() {
      var self = this;

      // Unbind events
      self.$container.off('keypress', 'input');
      self.$container.off('click', '[role=remove]');

      self.$container.remove();
      self.$element.removeData('tagsinput');
      self.$element.show();
    },

    /**
     * Sets focus on the tagsinput
     */
    focus: function() {
      this.$input.focus();
    },

    /**
     * Returns the internal input element
     */
    input: function() {
      return this.$input;
    },

    /**
     * Returns the element which is wrapped around the internal input. This
     * is normally the $container, but typeahead.js moves the $input element.
     */
    findInputWrapper: function() {
      var elt = this.$input[0],
          container = this.$container[0];
      while(elt && elt.parentNode !== container)
        elt = elt.parentNode;

      return $(elt);
    }
  };

  /**
   * Register JQuery plugin
   */
  $.fn.tagsinput = function(arg1, arg2) {
    var results = [];

    this.each(function() {
      var tagsinput = $(this).data('tagsinput');
      // Initialize a new tags input
      if (!tagsinput) {
          tagsinput = new TagsInput(this, arg1);
          $(this).data('tagsinput', tagsinput);
          results.push(tagsinput);

          if (this.tagName === 'SELECT') {
              $('option', $(this)).attr('selected', 'selected');
          }

          // Init tags from $(this).val()
          $(this).val($(this).val());
      } else if (!arg1 && !arg2) {
          // tagsinput already exists
          // no function, trying to init
          results.push(tagsinput);
      } else if(tagsinput[arg1] !== undefined) {
          // Invoke function on existing tags input
          var retVal = tagsinput[arg1](arg2);
          if (retVal !== undefined)
              results.push(retVal);
      }
    });

    if ( typeof arg1 == 'string') {
      // Return the results from the invoked function calls
      return results.length > 1 ? results : results[0];
    } else {
      return results;
    }
  };

  $.fn.tagsinput.Constructor = TagsInput;

  /**
   * Most options support both a string or number as well as a function as
   * option value. This function makes sure that the option with the given
   * key in the given options is wrapped in a function
   */
  function makeOptionItemFunction(options, key) {
    if (typeof options[key] !== 'function') {
      var propertyName = options[key];
      options[key] = function(item) { return item[propertyName]; };
    }
  }
  function makeOptionFunction(options, key) {
    if (typeof options[key] !== 'function') {
      var value = options[key];
      options[key] = function() { return value; };
    }
  }
  /**
   * HtmlEncodes the given value
   */
  var htmlEncodeContainer = $('<div />');
  function htmlEncode(value) {
    if (value) {
      return htmlEncodeContainer.text(value).html();
    } else {
      return '';
    }
  }

  /**
   * Returns the position of the caret in the given input field
   * http://flightschool.acylt.com/devnotes/caret-position-woes/
   */
  function doGetCaretPosition(oField) {
    var iCaretPos = 0;
    if (document.selection) {
      oField.focus ();
      var oSel = document.selection.createRange();
      oSel.moveStart ('character', -oField.value.length);
      iCaretPos = oSel.text.length;
    } else if (oField.selectionStart || oField.selectionStart == '0') {
      iCaretPos = oField.selectionStart;
    }
    return (iCaretPos);
  }

  /**
    * Returns boolean indicates whether user has pressed an expected key combination. 
    * @param object keyPressEvent: JavaScript event object, refer
    *     http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
    * @param object lookupList: expected key combinations, as in:
    *     [13, {which: 188, shiftKey: true}]
    */
  function keyCombinationInList(keyPressEvent, lookupList) {
      var found = false;
      $.each(lookupList, function (index, keyCombination) {
          if (typeof (keyCombination) === 'number' && keyPressEvent.which === keyCombination) {
              found = true;
              return false;
          }

          if (keyPressEvent.which === keyCombination.which) {
              var alt = !keyCombination.hasOwnProperty('altKey') || keyPressEvent.altKey === keyCombination.altKey,
                  shift = !keyCombination.hasOwnProperty('shiftKey') || keyPressEvent.shiftKey === keyCombination.shiftKey,
                  ctrl = !keyCombination.hasOwnProperty('ctrlKey') || keyPressEvent.ctrlKey === keyCombination.ctrlKey;
              if (alt && shift && ctrl) {
                  found = true;
                  return false;
              }
          }
      });

      return found;
  }

  /**
   * Initialize tagsinput behaviour on inputs and selects which have
   * data-role=tagsinput
   */
  $(function() {
    $("input[data-role=tagsinput], select[multiple][data-role=tagsinput]").tagsinput();
  });
})(window.jQuery);
;
!function(a){var b=new Array,c=new Array;a.fn.doAutosize=function(b){var c=a(this).data("minwidth"),d=a(this).data("maxwidth"),e="",f=a(this),g=a("#"+a(this).data("tester_id"));if(e!==(e=f.val())){var h=e.replace(/&/g,"&amp;").replace(/\s/g," ").replace(/</g,"&lt;").replace(/>/g,"&gt;");g.html(h);var i=g.width(),j=i+b.comfortZone>=c?i+b.comfortZone:c,k=f.width(),l=k>j&&j>=c||j>c&&d>j;l&&f.width(j)}},a.fn.resetAutosize=function(b){var c=a(this).data("minwidth")||b.minInputWidth||a(this).width(),d=a(this).data("maxwidth")||b.maxInputWidth||a(this).closest(".tagsinput").width()-b.inputPadding,e=a(this),f=a("<tester/>").css({position:"absolute",top:-9999,left:-9999,width:"auto",fontSize:e.css("fontSize"),fontFamily:e.css("fontFamily"),fontWeight:e.css("fontWeight"),letterSpacing:e.css("letterSpacing"),whiteSpace:"nowrap"}),g=a(this).attr("id")+"_autosize_tester";!a("#"+g).length>0&&(f.attr("id",g),f.appendTo("body")),e.data("minwidth",c),e.data("maxwidth",d),e.data("tester_id",g),e.css("width",c)},a.fn.addTag=function(d,e){return e=jQuery.extend({focus:!1,callback:!0},e),this.each(function(){var f=a(this).attr("id"),g=a(this).val().split(b[f]);if(""==g[0]&&(g=new Array),d=jQuery.trim(d),e.unique){var h=a(this).tagExist(d);1==h&&a("#"+f+"_tag").addClass("not_valid")}else var h=!1;if(""!=d&&1!=h){if(a("<span>").addClass("tag").append(a("<span>").text(d).append("&nbsp;&nbsp;"),a("<a>",{href:"#",title:"Removing tag",text:"x"}).click(function(){return a("#"+f).removeTag(escape(d))})).insertBefore("#"+f+"_addTag"),g.push(d),a("#"+f+"_tag").val(""),e.focus?a("#"+f+"_tag").focus():a("#"+f+"_tag").blur(),a.fn.tagsInput.updateTagsField(this,g),e.callback&&c[f]&&c[f].onAddTag){var i=c[f].onAddTag;i.call(this,d)}if(c[f]&&c[f].onChange){var j=g.length,i=c[f].onChange;i.call(this,a(this),g[j-1])}}}),!1},a.fn.removeTag=function(d){return d=unescape(d),this.each(function(){var e=a(this).attr("id"),f=a(this).val().split(b[e]);for(a("#"+e+"_tagsinput .tag").remove(),str="",i=0;i<f.length;i++)f[i]!=d&&(str=str+b[e]+f[i]);if(a.fn.tagsInput.importTags(this,str),c[e]&&c[e].onRemoveTag){var g=c[e].onRemoveTag;g.call(this,d)}}),!1},a.fn.tagExist=function(c){var d=a(this).attr("id"),e=a(this).val().split(b[d]);return jQuery.inArray(c,e)>=0},a.fn.importTags=function(b){var c=a(this).attr("id");a("#"+c+"_tagsinput .tag").remove(),a.fn.tagsInput.importTags(this,b)},a.fn.tagsInput=function(e){var f=jQuery.extend({interactive:!0,defaultText:"add a tag",minChars:0,width:"300px",height:"100px",autocomplete:{selectFirst:!1},hide:!0,delimiter:",",unique:!0,removeWithBackspace:!0,placeholderColor:"#666666",autosize:!0,comfortZone:20,inputPadding:12},e),g=0;return this.each(function(){if("undefined"==typeof a(this).attr("data-tagsinput-init")){a(this).attr("data-tagsinput-init",!0),f.hide&&a(this).hide();var e=a(this).attr("id");(!e||b[a(this).attr("id")])&&(e=a(this).attr("id","tags"+(new Date).getTime()+g++).attr("id"));var h=jQuery.extend({pid:e,real_input:"#"+e,holder:"#"+e+"_tagsinput",input_wrapper:"#"+e+"_addTag",fake_input:"#"+e+"_tag"},f);b[e]=h.delimiter,(f.onAddTag||f.onRemoveTag||f.onChange)&&(c[e]=new Array,c[e].onAddTag=f.onAddTag,c[e].onRemoveTag=f.onRemoveTag,c[e].onChange=f.onChange);var i='<div id="'+e+'_tagsinput" class="tagsinput"><div id="'+e+'_addTag">';if(f.interactive&&(i=i+'<input id="'+e+'_tag" value="" data-default="'+f.defaultText+'" />'),i+='</div><div class="tags_clear"></div></div>',a(i).insertAfter(this),a(h.holder).css("width",f.width),a(h.holder).css("min-height",f.height),a(h.holder).css("height",f.height),""!=a(h.real_input).val()&&a.fn.tagsInput.importTags(a(h.real_input),a(h.real_input).val()),f.interactive){if(a(h.fake_input).val(a(h.fake_input).attr("data-default")),a(h.fake_input).css("color",f.placeholderColor),a(h.fake_input).resetAutosize(f),a(h.holder).bind("click",h,function(b){a(b.data.fake_input).focus()}),a(h.fake_input).bind("focus",h,function(b){a(b.data.fake_input).val()==a(b.data.fake_input).attr("data-default")&&a(b.data.fake_input).val(""),a(b.data.fake_input).css("color","#000000")}),void 0!=f.autocomplete_url){autocomplete_options={source:f.autocomplete_url};for(attrname in f.autocomplete)autocomplete_options[attrname]=f.autocomplete[attrname];void 0!==jQuery.Autocompleter?(a(h.fake_input).autocomplete(f.autocomplete_url,f.autocomplete),a(h.fake_input).bind("result",h,function(b,c,d){c&&a("#"+e).addTag(c[0]+"",{focus:!0,unique:f.unique})})):void 0!==jQuery.ui.autocomplete&&(a(h.fake_input).autocomplete(autocomplete_options),a(h.fake_input).bind("autocompleteselect",h,function(b,c){return a(b.data.real_input).addTag(c.item.value,{focus:!0,unique:f.unique}),!1}))}else a(h.fake_input).bind("blur",h,function(b){var c=a(this).attr("data-default");return""!=a(b.data.fake_input).val()&&a(b.data.fake_input).val()!=c?b.data.minChars<=a(b.data.fake_input).val().length&&(!b.data.maxChars||b.data.maxChars>=a(b.data.fake_input).val().length)&&a(b.data.real_input).addTag(a(b.data.fake_input).val(),{focus:!0,unique:f.unique}):(a(b.data.fake_input).val(a(b.data.fake_input).attr("data-default")),a(b.data.fake_input).css("color",f.placeholderColor)),!1});a(h.fake_input).bind("keypress",h,function(b){return d(b)?(b.preventDefault(),b.data.minChars<=a(b.data.fake_input).val().length&&(!b.data.maxChars||b.data.maxChars>=a(b.data.fake_input).val().length)&&a(b.data.real_input).addTag(a(b.data.fake_input).val(),{focus:!0,unique:f.unique}),a(b.data.fake_input).resetAutosize(f),!1):void(b.data.autosize&&a(b.data.fake_input).doAutosize(f))}),h.removeWithBackspace&&a(h.fake_input).bind("keydown",function(b){if(8==b.keyCode&&""==a(this).val()){b.preventDefault();var c=a(this).closest(".tagsinput").find(".tag:last").text(),d=a(this).attr("id").replace(/_tag$/,"");c=c.replace(/[\s]+x$/,""),a("#"+d).removeTag(escape(c)),a(this).trigger("focus")}}),a(h.fake_input).blur(),h.unique&&a(h.fake_input).keydown(function(b){(8==b.keyCode||String.fromCharCode(b.which).match(/\w+|[áéíóúÁÉÍÓÚñÑ,/]+/))&&a(this).removeClass("not_valid")})}}}),this},a.fn.tagsInput.updateTagsField=function(c,d){var e=a(c).attr("id");a(c).val(d.join(b[e]))},a.fn.tagsInput.importTags=function(d,e){a(d).val("");var f=a(d).attr("id"),g=e.split(b[f]);for(i=0;i<g.length;i++)a(d).addTag(g[i],{focus:!1,callback:!1});if(c[f]&&c[f].onChange){var h=c[f].onChange;h.call(d,d,g[i])}};var d=function(b){var c=!1;return 13==b.which?!0:("string"==typeof b.data.delimiter?b.which==b.data.delimiter.charCodeAt(0)&&(c=!0):a.each(b.data.delimiter,function(a,d){b.which==d.charCodeAt(0)&&(c=!0)}),c)}}(jQuery);;
/*
    jQuery Masked Input Plugin
    Copyright (c) 2007 - 2015 Josh Bush (digitalbush.com)
    Licensed under the MIT license (http://digitalbush.com/projects/masked-input-plugin/#license)
    Version: 1.4.1
*/
!function(factory) {
    "function" == typeof define && define.amd ? define([ "jquery" ], factory) : factory("object" == typeof exports ? require("jquery") : jQuery);
}(function($) {
    var caretTimeoutId, ua = navigator.userAgent, iPhone = /iphone/i.test(ua), chrome = /chrome/i.test(ua), android = /android/i.test(ua);
    $.mask = {
        definitions: {
            "9": "[0-9]",
            a: "[A-Za-z]",
            "*": "[A-Za-z0-9]"
        },
        autoclear: !0,
        dataName: "rawMaskFn",
        placeholder: "_"
    }, $.fn.extend({
        caret: function(begin, end) {
            var range;
            if (0 !== this.length && !this.is(":hidden")) return "number" == typeof begin ? (end = "number" == typeof end ? end : begin, 
            this.each(function() {
                this.setSelectionRange ? this.setSelectionRange(begin, end) : this.createTextRange && (range = this.createTextRange(), 
                range.collapse(!0), range.moveEnd("character", end), range.moveStart("character", begin), 
                range.select());
            })) : (this[0].setSelectionRange ? (begin = this[0].selectionStart, end = this[0].selectionEnd) : document.selection && document.selection.createRange && (range = document.selection.createRange(), 
            begin = 0 - range.duplicate().moveStart("character", -1e5), end = begin + range.text.length), 
            {
                begin: begin,
                end: end
            });
        },
        unmask: function() {
            return this.trigger("unmask");
        },
        mask: function(mask, settings) {
            var input, defs, tests, partialPosition, firstNonMaskPos, lastRequiredNonMaskPos, len, oldVal;
            if (!mask && this.length > 0) {
                input = $(this[0]);
                var fn = input.data($.mask.dataName);
                return fn ? fn() : void 0;
            }
            return settings = $.extend({
                autoclear: $.mask.autoclear,
                placeholder: $.mask.placeholder,
                completed: null
            }, settings), defs = $.mask.definitions, tests = [], partialPosition = len = mask.length, 
            firstNonMaskPos = null, $.each(mask.split(""), function(i, c) {
                "?" == c ? (len--, partialPosition = i) : defs[c] ? (tests.push(new RegExp(defs[c])), 
                null === firstNonMaskPos && (firstNonMaskPos = tests.length - 1), partialPosition > i && (lastRequiredNonMaskPos = tests.length - 1)) : tests.push(null);
            }), this.trigger("unmask").each(function() {
                function tryFireCompleted() {
                    if (settings.completed) {
                        for (var i = firstNonMaskPos; lastRequiredNonMaskPos >= i; i++) if (tests[i] && buffer[i] === getPlaceholder(i)) return;
                        settings.completed.call(input);
                    }
                }
                function getPlaceholder(i) {
                    return settings.placeholder.charAt(i < settings.placeholder.length ? i : 0);
                }
                function seekNext(pos) {
                    for (;++pos < len && !tests[pos]; ) ;
                    return pos;
                }
                function seekPrev(pos) {
                    for (;--pos >= 0 && !tests[pos]; ) ;
                    return pos;
                }
                function shiftL(begin, end) {
                    var i, j;
                    if (!(0 > begin)) {
                        for (i = begin, j = seekNext(end); len > i; i++) if (tests[i]) {
                            if (!(len > j && tests[i].test(buffer[j]))) break;
                            buffer[i] = buffer[j], buffer[j] = getPlaceholder(j), j = seekNext(j);
                        }
                        writeBuffer(), input.caret(Math.max(firstNonMaskPos, begin));
                    }
                }
                function shiftR(pos) {
                    var i, c, j, t;
                    for (i = pos, c = getPlaceholder(pos); len > i; i++) if (tests[i]) {
                        if (j = seekNext(i), t = buffer[i], buffer[i] = c, !(len > j && tests[j].test(t))) break;
                        c = t;
                    }
                }
                function androidInputEvent() {
                    var curVal = input.val(), pos = input.caret();
                    if (oldVal && oldVal.length && oldVal.length > curVal.length) {
                        for (checkVal(!0); pos.begin > 0 && !tests[pos.begin - 1]; ) pos.begin--;
                        if (0 === pos.begin) for (;pos.begin < firstNonMaskPos && !tests[pos.begin]; ) pos.begin++;
                        input.caret(pos.begin, pos.begin);
                    } else {
                        for (checkVal(!0); pos.begin < len && !tests[pos.begin]; ) pos.begin++;
                        input.caret(pos.begin, pos.begin);
                    }
                    tryFireCompleted();
                }
                function blurEvent() {
                    checkVal(), input.val() != focusText && input.change();
                }
                function keydownEvent(e) {
                    if (!input.prop("readonly")) {
                        var pos, begin, end, k = e.which || e.keyCode;
                        oldVal = input.val(), 8 === k || 46 === k || iPhone && 127 === k ? (pos = input.caret(), 
                        begin = pos.begin, end = pos.end, end - begin === 0 && (begin = 46 !== k ? seekPrev(begin) : end = seekNext(begin - 1), 
                        end = 46 === k ? seekNext(end) : end), clearBuffer(begin, end), shiftL(begin, end - 1), 
                        e.preventDefault()) : 13 === k ? blurEvent.call(this, e) : 27 === k && (input.val(focusText), 
                        input.caret(0, checkVal()), e.preventDefault());
                    }
                }
                function keypressEvent(e) {
                    if (!input.prop("readonly")) {
                        var p, c, next, k = e.which || e.keyCode, pos = input.caret();
                        if (!(e.ctrlKey || e.altKey || e.metaKey || 32 > k) && k && 13 !== k) {
                            if (pos.end - pos.begin !== 0 && (clearBuffer(pos.begin, pos.end), shiftL(pos.begin, pos.end - 1)), 
                            p = seekNext(pos.begin - 1), len > p && (c = String.fromCharCode(k), tests[p].test(c))) {
                                if (shiftR(p), buffer[p] = c, writeBuffer(), next = seekNext(p), android) {
                                    var proxy = function() {
                                        $.proxy($.fn.caret, input, next)();
                                    };
                                    setTimeout(proxy, 0);
                                } else input.caret(next);
                                pos.begin <= lastRequiredNonMaskPos && tryFireCompleted();
                            }
                            e.preventDefault();
                        }
                    }
                }
                function clearBuffer(start, end) {
                    var i;
                    for (i = start; end > i && len > i; i++) tests[i] && (buffer[i] = getPlaceholder(i));
                }
                function writeBuffer() {
                    input.val(buffer.join(""));
                }
                function checkVal(allow) {
                    var i, c, pos, test = input.val(), lastMatch = -1;
                    for (i = 0, pos = 0; len > i; i++) if (tests[i]) {
                        for (buffer[i] = getPlaceholder(i); pos++ < test.length; ) if (c = test.charAt(pos - 1), 
                        tests[i].test(c)) {
                            buffer[i] = c, lastMatch = i;
                            break;
                        }
                        if (pos > test.length) {
                            clearBuffer(i + 1, len);
                            break;
                        }
                    } else buffer[i] === test.charAt(pos) && pos++, partialPosition > i && (lastMatch = i);
                    return allow ? writeBuffer() : partialPosition > lastMatch + 1 ? settings.autoclear || buffer.join("") === defaultBuffer ? (input.val() && input.val(""), 
                    clearBuffer(0, len)) : writeBuffer() : (writeBuffer(), input.val(input.val().substring(0, lastMatch + 1))), 
                    partialPosition ? i : firstNonMaskPos;
                }
                var input = $(this), buffer = $.map(mask.split(""), function(c, i) {
                    return "?" != c ? defs[c] ? getPlaceholder(i) : c : void 0;
                }), defaultBuffer = buffer.join(""), focusText = input.val();
                input.data($.mask.dataName, function() {
                    return $.map(buffer, function(c, i) {
                        return tests[i] && c != getPlaceholder(i) ? c : null;
                    }).join("");
                }), input.one("unmask", function() {
                    input.off(".mask").removeData($.mask.dataName);
                }).on("focus.mask", function() {
                    if (!input.prop("readonly")) {
                        clearTimeout(caretTimeoutId);
                        var pos;
                        focusText = input.val(), pos = checkVal(), caretTimeoutId = setTimeout(function() {
                            input.get(0) === document.activeElement && (writeBuffer(), pos == mask.replace("?", "").length ? input.caret(0, pos) : input.caret(pos));
                        }, 10);
                    }
                }).on("blur.mask", blurEvent).on("keydown.mask", keydownEvent).on("keypress.mask", keypressEvent).on("input.mask paste.mask", function() {
                    input.prop("readonly") || setTimeout(function() {
                        var pos = checkVal(!0);
                        input.caret(pos), tryFireCompleted();
                    }, 0);
                }), chrome && android && input.off("input.mask").on("input.mask", androidInputEvent), 
                checkVal();
            });
        }
    });
});;
/// <reference path="~/Scripts/knockout-3.0.0.debug.js"/>

///<summary> Extended info provided for incorrect knockout bindings </summary>
(function () {
	var existing = ko.bindingProvider.instance;	
	ko.bindingProvider.instance = {
		nodeHasBindings: existing.nodeHasBindings,
		getBindings: function (node, bindingContext) {
			var bindings;
			try {
				bindings = existing.getBindings(node, bindingContext);
			}
			catch (ex) {
				if (window.console && console.log) {
					console.log("binding error ", ex.message, node, bindingContext);
				}
			}

			return bindings;
		}
	};

})();

ko.subscribable.fn.subscribeChanged = function (callback) {
    var oldValue;
    this.subscribe(function (_oldValue) {
        oldValue = _oldValue;
    }, this, 'beforeChange');

    this.subscribe(function (newValue) {
        callback(newValue, oldValue);
    });
};

ko.observable.fn.codeUpdate = function (value) {
    this.notifySubscribers = function () {
        arguments[0] = "codeUpdate";
        ko.subscribable.fn.notifySubscribers.apply(this, arguments);
    };
    this(value);
    this.notifySubscribers = function () {
        ko.subscribable.fn.notifySubscribers.apply(this, arguments);
    };
};
;
ko.bindingHandlers.stopBinding = {
	init: function () {
		return { controlsDescendantBindings: true };
	}
};

ko.virtualElements.allowedBindings.stopBinding = true;;


	ko.bindingHandlers.withProperties = {
		init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
			// Make a modified binding context, with a extra properties, and apply it to descendant elements
			var innerBindingContext = bindingContext.extend(valueAccessor);
			ko.observable(element).extend({ applyBindingsToDescendants: innerBindingContext });

			// Also tell KO *not* to bind the descendants itself, otherwise they will be bound twice
			return { controlsDescendantBindings: true };
		}
	};


	ko.bindingHandlers.hidden = (function () {
	    function setVisibility(element, valueAccessor) {
	        var hidden = ko.unwrap(valueAccessor());
	        $(element).css('visibility', hidden ? 'hidden' : 'visible');
	    }
	    return { init: setVisibility, update: setVisibility };
	})();
;
//ckEditor inline binding handler
// https://gist.github.com/valerysntx/9e5219d3b48d9288ee12


function enableSelection(target) {
	//For IE This code will work
	if (typeof target.onselectstart != "undefined") {
	target.onselectstart = function () {
			return true;
		}
	}
		//For Firefox This code will work
	else if (typeof target.style.MozUserSelect != "undefined") {
		target.style.MozUserSelect = "text";
	}

		//All other  (ie: Opera) This code will work
	else {
		target.onmousedown = function () {
		 return true;
		}
		target.style.cursor = "default";
	}

	$(target).each(function (e) {
		$(this).css("user-select", "text !important");
		$(this).css("-moz-user-select", "text !important");
		$(this).css("-webkit-user-select", "text !important");
	});


}

function disableSelection(target) {
	//For IE This code will work
	if (typeof target.onselectstart != "undefined") {
		target.onselectstart = null;
	}
		//For Firefox This code will work
	else if (typeof target.style.MozUserSelect != "undefined") {
		target.style.MozUserSelect = "none";
	}

		//All other  (ie: Opera) This code will work
	else {
		target.onmousedown = function () {
			return false;
		}
		target.style.cursor = "default";
	}

	$(target).each(function (e) {
		$(this).css("user-select", "none !important");
		$(this).css("-moz-user-select", "none !important");
		$(this).css("-webkit-user-select", "none !important");
	});


}

ko.bindingHandlers.ckEditor = {
	initialized: ko.observable(false),

	initializeCKEditor: function (ckeditor) {
		ckeditor.config.toolbar_Full = [
			{ name: 'clipboard', items: ['Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo'] },
			{ name: 'editing', items: ['Find', 'Replace', '-', 'SelectAll'] },
            { name: 'links', items: ['Link', 'Unlink', 'Anchor'] },
			'/',
			{ name: 'basicstyles', items: ['Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript', '-', 'CopyFormatting', 'RemoveFormat'] },
			{
				name: 'paragraph', items: ['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', '/',
										   'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock']
			},
			'/',
			{ name: 'styles', items: ['Font', 'FontSize'] },
		    { name: 'lineheight', items: ['lineheight'] },
			{ name: 'colors', items: ['TextColor', 'BGColor'] }		   
		];

        //changed styles element if you need change format
	    //{ name: 'styles', items: ['Format', 'Font', 'FontSize'] },

		ckeditor.disableAutoInline = true;
		//Force pasted text to be allways plain 
		ckeditor.config.extraPlugins = 'pastetext,smallerselection,lineheight';	  
		ckeditor.config.forcePasteAsPlainText = true;
		ckeditor.config.pasteFromWordRemoveFontStyles = false;
		ckeditor.config.allowedContent = true;
		ckeditor.config.autoParagraph = true;
		ckeditor.config.fillEmptyBlocks = true;

		//ckeditor.config.font_style =
		//{
		//	element: 'p',
		//	styles: { 'font-family': '#(family)' },
		//	overrides: [{ element: 'font', attributes: { 'face': null } }]
		//};

		//ckeditor.config.fontSize_style = {
		//	element: 'p',
		//	styles: { 'font-size': '#(size)' },
		//	overrides: [{ element: 'font', attributes: { 'size': null } }]
		//};

		ckeditor.config.forceEnterMode = false;

		//ckeditor.config.format_div = {
		//	element: 'div',
		//	attributes: { "style": 'display:inline-block' }
		//};

		//ckeditor.config.format_p = {
		//	element: 'p',
		//	attributes: { "style": "-moz-user-select: text !important;user-select: text !important" }
		//};

		ckeditor.config.format_h1 = { element: "h1", name: "Heading 1", styles: { 'font-size': '32px' } };
		ckeditor.config.format_h2 = { element: "h2", name: "Heading 2", styles: { 'font-size': '24px' } };
		ckeditor.config.format_h3 = { element: "h3", name: "Heading 3", styles: { 'font-size': '18px' } };
		ckeditor.config.format_h4 = { element: "h4", name: "Heading 4", styles: { 'font-size': '16px' } };
		ckeditor.config.format_h5 = { element: "h5", name: "Heading 5", styles: { 'font-size': '13px' } };
		ckeditor.config.format_h6 = { element: "h6", name: "Heading 6", styles: { 'font-size': '10px' } };

		//	ckeditor.config.coreStyles_italic = { element: 'p', attributes: { 'class': 'Italic' } };
		//	ckeditor.config.coreStyles_bold = { element: 'p', attributes: { 'class': 'Bold' } };

		CKEDITOR.event.useCapture = true;

	},
	init: function (element, valueAccessor) {

		ko.bindingHandlers.ckEditor.initializeCKEditor(CKEDITOR);

		var modelValue = valueAccessor();
		var value = ko.utils.unwrapObservable(valueAccessor());
		var unparsedValue = value;
		var element$ = $(element);

		if (null == element$.attr('contenteditable')) return;
		if ("false" === element$.attr('contenteditable')) return;
		
	
		var id = element$.attr("id");
		var component = UI.siteComponentRepository.lookupDataSet({ id: id }).firstOrDefault();

		if (value != null) {
			// Set initial value and create the CKEditor
			if (component != null) {
				value = value !== component.getProperty(TEXT).value ? component.getProperty(TEXT).value : value;
				value = ko.utils.parseHtmlFragment(value);
				element$.highlightSelectedElement(component, true);

			}						
		    //fix for old paragraph or headertext which use format in ckeditor
			CKEDITOR.config.toolbar_Full[7].items.push('Format');
			if (component.proto.name == 'paragraph') {
			    CKEDITOR.config.format_tags = 'p;h2;h3;h4;h5;h6;pre;address';
			} else if (component.proto.name == 'headertext') {
			    CKEDITOR.config.format_tags = 'h1';
			}
			element$.html(value);
		}


		//clean all event handlers attached...
		(function cleanAttachedEventHandlers(node) {
				
			
			dragDrop.releaseElement(id);
			$(node).unbind().find('*').unbind();		
			node.onmouseup = node.onmousedown = node.onclick = null;

		})(element);

		//destroy all editors...
		(function destroyActiveEditorInstances(ckeditor) {
			for (name in ckeditor.instances) {
				if (ckeditor.instances.hasOwnProperty(name)) {
					ckeditor.instances[name].destroy();
				}
			}
		})(CKEDITOR);

		// FF rich-edit selection workaround ...
		enableSelection(element);

		// start new...
		var editor = CKEDITOR.inline(element, {
		    enterMode: CKEDITOR.ENTER_P, shiftEnterMode: CKEDITOR.ENTER_BR,
			allowedContent: true, toolbar: "Full", font_names: ContextFactory.getComponentFontsInline(), contentsCss: '../Content/font-opensans.css'
		});

		//set carret to the end, set paragraph rules...
		editor.on('instanceReady', function (ev) {

			
			// ReSharper disable once InconsistentNaming
			var focusManager = new CKEDITOR.focusManager(this);
			focusManager.add(CKEDITOR.document.getById(id), 1);

			element$.data("focusManagerInstance", focusManager);
		
		
			////control blur with focusManager
			element$.on('blur', function (e) {
				e.preventDefault();
			    //UI.isckeditorworking = false;
			//	//focusManager.blur();

			    //console.log('closing editor...' + id);
				
				return false;
			});
			
			//use css instead of tags for aligning in FF
			if (CKEDITOR.env.gecko) {
				document.execCommand("useCSS", false, false);
			}

			// Output paragraphs as <p>Text</p>.
			this.dataProcessor.writer.setRules('p', {
				indent: true,
				breakBeforeOpen: true,
				breakAfterOpen: false,
				breakBeforeClose: false,
				breakAfterClose: true
			});

			try {

				var range = ev.editor.createRange();
				range.moveToElementEditablePosition(ev.editor.editable(), true); // bar.^</p>
				var selection = ev.editor.getSelection();

				selection.selectRanges([range]);

			} catch (exception) {
				console.log(exception);
			}
			//// Always focus editor (not only when focusManger.hasFocus is false) (because of #10483).
			ev.editor.focus();
		});


		// bind to change events and link it to the observable and component property...
		editor.on('change', function (e) {
			var self = this;
			if (ko.isWriteableObservable(self)) {
				self(editor.getData());
			}
			// as you type - you get reactive updates on changes both in component
			// and in passed binding model value
			if (component != null) {
			    var inputData = editor.getData();
			    inputData = inputData.replace(/\u200B/g, ''); //removing ZERO WIDTH SPACE char
			    if (component.proto.name == PARAGRAPH_SEO) {
			        component.setProperty(TEXT, ko.utils.parseHtmlFragment(inputData)[0] ? ko.utils.parseHtmlFragment(inputData)[0].innerHTML : inputData);
			    } else {
			        component.setProperty(TEXT, inputData);
			    }
			}
		}, modelValue, element);

		editor.on('selectionChange', function (evt) {
			try{
				if (editor.readOnly) return;

				var sel = editor.getSelection();
				// Do it only when selection is not locked. (#8222)
				if (sel && !sel.isLocked) {
					var isDirty = editor.checkDirty();
					editor.fire('saveSnapshot', { contentOnly: 1 });

					editor.fire('updateSnapshot');
					!isDirty && editor.resetDirty();
				} 
			} catch (exception) {
				console.log(exception);
			}

		}, null, null, 1);

		//destroy editor, when lost focus, to be able drag again...
		editor.on('blur', function (e) {
			// prepare undoConfig for undomanager
			var currentValue;
			try {
				// Failed to execute 'extend' on 'Selection': 1 is larger than the given node's length. 
			    currentValue = editor.getData();
			    if (component.proto.name == PARAGRAPH_SEO) {
			        currentValue = ko.utils.parseHtmlFragment(currentValue)[0] ? ko.utils.parseHtmlFragment(currentValue)[0].innerHTML : currentValue;
			    }
			} catch (exception) {
				// cant get last value, so get from component property
				if (component != null) {
					currentValue = ko.utils.parseHtmlFragment(component.getProperty(TEXT));
				}
			}
			
			var undoConfig = {
			    lastValue: unparsedValue,
			    currentValue: currentValue || unparsedValue,
				updateCallback: function (val) {
				    if (component != null) {
				        component.setProperty(TEXT, val);
                        $(component.getUISelector()).html(val);
					}
				}
			};
			// pass config, that provides last value, the editor opened with...
			UI.undoManagerAdd({
				undo: function () {
					undoConfig.updateCallback(undoConfig.lastValue);
				},
				redo: function () {
					undoConfig.updateCallback(undoConfig.currentValue);
				}
			});

			//remove contenteditable for return dragmode...
			element$.removeAttr("contenteditable");
			//restore -moz-user-select to none...
			disableSelection(element);

			element$.data("focusManagerInstance", null);
			

			try {
				//destroy ckeditor instance...
				editor.destroy(false);
				element$.data('ckeditorInstance', null);
				component.isckeditorworking = false;
				UI.isckeditorworking = false;
				element$.highlightSelectedElement(component, true);
			} catch (ex) {
				//	console.log(ex);
			}

			CKEDITOR.event.useCapture = false;
		
			component.viewer();
			$('#gwf-popup').remove();
			return false; //stop bubbling... for lost focus...
		});

		/* Handle disposal if KO removes an editor 
         * through template binding */
		ko.utils.domNodeDisposal.addDisposeCallback(element,
			function () {
				editor.destroy(false);
			});
	}
}

ko.bindingHandlers.ckEditorSimple = {
    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
        var TOOLBAR_CONFIG = [
            { name: 'clipboard', items: ['Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo'] },
            { name: 'editing', items: ['Find', 'Replace', '-', 'SelectAll', '-', 'Scayt'] },
            { name: 'basicstyles', items: ['Bold', 'Italic', 'Underline', 'Strike', '-', 'RemoveFormat'] },
            { name: 'paragraph', items: ['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock'] },
            { name: 'styles', items: ['Styles', 'Format', 'Font', 'Fontsize'] },
            { name: 'colors', items: ['TextColor', 'BGColor'] },
            { name: 'tools', items: ['Maximize'] }
        ];
        var options = ko.utils.extend({
            toolbar: TOOLBAR_CONFIG,
            removePlugins: 'elementspath'
        }, allBindings.get('ckeditorOptions') || {
        });
        var modelValue = valueAccessor();

        var editor = CKEDITOR.replace(element, options);

        editor.on('change', function (e) {
            modelValue(editor.getData());
        });

        //handle disposal (if KO removes by the template binding)
        ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
            if (editor) {
                CKEDITOR.remove(editor);
            };
        });
    },
    update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
        var editor = new CKEDITOR.dom.element(element).getEditor();
        editor.setData(ko.unwrap(valueAccessor()), null, true);
    }
};;
/// <reference path="../classes.js" />
/// <reference path="../definitions.js" />
/// <reference path="../editor.js" />

///<param name="options" type="Array">ex. ['Fit','Crop'] select box options</param>
///<param name="selected" type="String">ex. 'Fit' - one of preselected options </param>
///<param name="component" type="Component"> expects Component instance with properties </param>

///<summary>
/// StretchingController auto-wires galleryviewer dom and gallery editor dom,
/// auto-updating ui, reflected to binded component property 'stretching'
///</summary>
var StretchingController = function(options, selected, component) {
	'use strict';

	options = options || ['fill', 'crop'];
	selected = selected || 'fill';

	var isBound = function(node) {
		    return !!ko.dataFor(node);
	    },

		self = this,

		callback = function() {
			this.forceDomUpdate();
		}.bind(this);

	// data-bind="css: stretchClass"
	this.stretchClass = ko.observable({ width: '100%', height: '100%', backgroundSize: 'cover' });

    // data-bind="css: fitClass"
	if (component.displayName == "list") 
		this.fitClass = ko.observable({ width: '100%', height: '100%', backgroundSize: 'contain', backgroundRepeat: 'no-repeat', backgroundPosition: 'center' });
	else 
		this.fitClass = ko.observable({ width: '100%', height: '100%', backgroundSize: 'contain' });


	// data-bind="options: stretchingOptions"
	this.stretchingOptions = ko.observableArray(options);
	// data-bind="value: stretchingValue"
	this.stretchingValue = ko.observable(selected);


		
	// this is data-bound value, with css class name reflect to select
	// data-bind="class: classToApply"
	this.classToApply = ko.computed(function () {

		//class to apply
		var cl = {
			"fill": "fitwidth",
			"crop": "stretch",   //filled to cover
			"": "fitwidth"		 //default
		};

		return (cl[self.stretchingValue()]);

	}, this);

	//get callback to be called with new values set (select list changed)
	this.stretchingValue.subscribe(function (newValue) {


		if (this.component !== null) {
			this.updateToComponent(self.component);
			
		}
	    

	}.bind(this));


	//DOM changes can be force applied by old-fashioned way 
	this.forceDomUpdate = function (selector) {

		if (!selector) {
			selector = ".gallery-list-items";
		}

		$(selector).find('img').each(function (a, e) {

			$('<div/>').css('background-image', 'url(' + $(this).attr('src') + ')')
					   .prependTo($(this).parent())
					   .data('bind', 'css: classToApply');

			$(this).data('bind', 'visible: { stretchClass == "stretch" }');
		});
		
		ko.observable($(selector)[0]).extend({ applyBindingsToDescendants: this });
		$(selector).find('.holder').removeClass('fitwidth', 'stretch').addClass(self.classToApply());
	}

	//sync vm to component
	this.updateToComponent = function (component) {

		try {

			if (component !== null) {
                // ReSharper disable once QualifiedExpressionMaybeNull
				component.setProperty('image-stretching', self.stretchingValue(), true) ;
				var property = component.getProperty('image-stretching') ;
				if (property != null) {
					property.componentId = component.name;
					property.controlId = component.id;
					property.propertyId = "74A60096-59B5-4749-8116-07ABAD4B4C95";
					property.group = "common";
					property.type = "common";
				}
			    if (component.getProperty(SHOW_OPTIMIZED) !== null) {
			        var isShowOptimized = component.getProperty(SHOW_OPTIMIZED).value.toBoolean();
			        component.children.forEach(function(item) {
			            $('#gallery-editor .gallery-items-list .gallery-item#' + item.id + ">div>div").css(
			                'background-image',
			                'url("' +
			                ContextFactory.prepareImgSrc(item.getProperty(SRC).value, isShowOptimized) +
			                '")');
			            $('#gallery-editor .gallery-items-list .gallery-item#' + item.id + " img").attr('src',
			                ContextFactory.prepareImgSrc(item.getProperty(SRC).value, isShowOptimized));
			        });
			    }
			}

		} finally {

			
		}

	}
	//sync component to vm 
	//it force adds component property and value
	this.updateFromComponent = function (component) {

		try {
			var value = component.getProperty('image-stretching');

			if (value !== null && value.value != null) {
				if (self.stretchingOptions().any(value.value)) {
					self.stretchingValue(value.value);
				}
			} else {
			   throw "no stretching property value found!";
			}
		
		} catch (e) {			
			self.updateToComponent(component);

		} finally {
			
		}
	}

	
	if (component !== null) {
		//updating component...
		this.component = component;
		this.updateFromComponent(component);
		this.updateToComponent(component);

	}


};

StretchingController.shareInstanceBetweenNodes = function (node1, node2) {
	'use strict';
		
	//apply template bindings glue
	var ctx = ko.dataFor(node1);
	//apply inherited $parent instance 
		if (ctx != ko.dataFor(node2)) {
		    ko.observable(node2).extend({ applyBindings: ctx });
		}
		return ctx;
}















;
/// <reference path="~/Scripts/knockout-3.0.0.debug.js" />
/// <reference path="~/Scripts/jquery-2.1.1.intellisense.js" />
/// <reference path="~/Content/Editor/js/classes.js" />
/// <reference path="~/Content/Editor/js/services.js" />
/// <reference path="~/Content/Editor/js/viewer.js" />
/// <reference path="~/Content/Editor/js/ui.js" />
/// <reference path="~/Content/Editor/js/definitions.js" />
/// <reference path="~/Content/Editor/js/grouping.js" />
/// <reference path="~/Content/Editor/js/layering/layeringController.js" />

var ClipboardViewModel = function (items) {

	//turn the components into plain objects
	this.items = ko.observableArray(items || []);

	//hold the currently selected item - reference to a component
	this.selectedItem = ko.observable(null);

	//make a copy of component - make a deep copy - not a ref.
	this.itemForClipboard = ko.observable(null);

    //device type
	this.isDesktopView = ko.observable(true);
};

ko.utils.extend(ClipboardViewModel.prototype,
{

    //select and item and make a light copy of it for storing in a clipboard   	
    selectComponent: function(component) {
        this.selectedItem(component);
    },

    copyComponent: function(obj, com) {
        clipBoard.items = ko.observableArray([]); // clear clipboard simple component
        var component = this.selectedItem() || com;

        if (component == null)
            return console.log("clipBoard.js: copyComponent - component is null");

        switch (obj) {
            case GROUP:
                grouping.clipboardGroup.push(component); // added to clipboard group

                break;
            case COMPONENT:
                clipBoard.items.push(ko.unwrap(component)); // added to clipboard

                break;
            default:
                console.log("Error: don't add " + obj + " to clipBoard");
                break;
        }

        if (grouping.clipboardGroup.length > 0 || clipBoard.items().length > 0) {
            clipBoard.itemForClipboard(component); // show button paste
            PopoverHelper.hidePopovers();
        }
    },

    //steps pasteComponent:
    //  1. get from clipBoard;
    //  2. set to new component parentComponent
    //  3. remove old guid
    //  4. set new left and top property for component
    //  5. set group property
    //  6. add new component to current page
    //  7. add new component to repository
    //  8. correct position new component(for components in FORM)
    //  9. bind events for new component
    //  10.bind undo redo
    //  11.append to page components which is inside of FORM component
    //  12.remove button "paste component"
    //  13.activate trigger for new component

    pasteComponent: function (component, groupId, parentComponent, isCallEditor, isDuplicatePage, isPasted) {
        if (!defined(isCallEditor)) {
            isCallEditor = true;
        }
        
	    var toPaste = component || clipBoard.items().pop() || clipBoard.itemForClipboard();
	    var pasted = ko.utils.unwrapObservable(toPaste);

	    var currentPageOrParentComponentId = UI.pager.getCurrentPageId();

	    // pasted - component from clipBoard
	    if (pasted != null) {
	        if (UI.siteComponentRepository.checkExistingRule(pasted.proto.name, currentPageOrParentComponentId)) {                
	            var componentTree = new Component().createFromExisting(pasted.proto.name === FORM ? _.defaults({children: []}, pasted) : pasted , true);

	            var shiftleft = parseInt(componentTree.getProperty('left').value, 10) + 5;
	            var shiftdown = parseInt(componentTree.getProperty('top').value, 10) + 5;

	            var prevPageHeigth = parseInt(pasted.parentComponent.getProperty('height').value);
	            var curPageheight = $('#' + currentPageOrParentComponentId + '').height();

	            //check if current page is smaller than previous
	            if (curPageheight < prevPageHeigth) {
	                shiftdown = curPageheight / 2;
	            }

	            var displayName = componentTree.parentComponent.displayName;

	            if (displayName == null) {
	                displayName = UI.siteComponentRepository.lookupData({ id: componentTree.parentComponent.id }).displayName;
	            }

	            // set to new component parentComponent
	            switch (displayName) {
	                case DISPLAY_PAGE: // page
	                    if (isDuplicatePage) {
	                        componentTree.parentComponent = UI.siteComponentRepository.lookupData({ id: parentComponent.id });
	                        currentPageOrParentComponentId = parentComponent.id;
	                        shiftleft = shiftleft - 5;
	                        shiftdown = shiftdown - 5;
	                    } else {
	                        componentTree.parentComponent = UI.siteComponentRepository.lookupData({ id: UI.pager.getCurrentPageId() });
	                    }

	                    break;
	                case FORM:
	                    if (parentComponent != null) {
	                        componentTree.parentComponent = parentComponent;
	                        currentPageOrParentComponentId = parentComponent.id;
	                        shiftleft = shiftleft - 5;
	                        shiftdown = shiftdown - 5;
	                    } else {
	                        // clear property CAPTION_COMPONENTS_TO_LABEL in label
	                        if (componentTree.displayName == LABEL && componentTree.getProperty(CAPTION_COMPONENTS_TO_LABEL) && componentTree.getProperty(CAPTION_COMPONENTS_TO_LABEL).value != "") {
	                            componentTree.setProperty(CAPTION_COMPONENTS_TO_LABEL, "");
	                        }

	                        // clear property SELECTEDLABEL in components such have caption to label
	                        if (componentTree.getProperty(SELECTEDLABEL) && componentTree.getProperty(SELECTEDLABEL).value != "") {
	                            componentTree.setProperty(SELECTEDLABEL, "");
	                        };

	                        currentPageOrParentComponentId = pasted.parentComponent.id;
	                    }

	                    break;
	                default:
	                    // set default page
	                    break;
	            }

	            //remove old guid
	            try {
	                Helpers.setNewGuid(componentTree);
	            } catch (e) {
	                clipBoard.itemForClipboard(null);
	                return window.onerror(e.message, "clipBoard.is -> pasteComponent: ", 141, 0);
	            }

	            // set new left and top property for component
	            componentTree.setProperty(LEFT, shiftleft + 'px');
	            componentTree.setProperty(TOP, shiftdown + 'px');

	            var isPined = componentTree.getProperty(IS_PINED);
                //set if fixed
	            if (isPined != null && isPined.value.toBoolean()) {	  
                    var shiftleftoffset = parseInt(componentTree.getProperty(OFFSET_X).value) + 5;
	                var shiftdownoffset = parseInt(componentTree.getProperty(OFFSET_Y).value) + 5;
	                componentTree.setProperty(OFFSET_X, shiftleftoffset + 'px');
	                componentTree.setProperty(OFFSET_Y, shiftdownoffset + 'px');
	            }

	            // set property group
	            if (groupId) {
	                componentTree.setProperty(GROUPID, groupId, true);
	            } else {
	                componentTree.setProperty(GROUPID, '', true);
	            }

	            // add new component to repository
	            UI.siteComponentRepository.appendTo(componentTree, UI.siteComponentRepository.lookupData({ id: currentPageOrParentComponentId }));

	            UI.actionService.runActionForComponent(componentTree, ACTION_ADD_TO_FORM);

	            TransformFactory.setNewPosition(componentTree);

	            // correct position new component(for components in FORM)
	            if (componentTree.parentComponent.displayName == FORM) {
	                ClipboardViewModel.attachToParentComponent($(componentTree.getUISelector()), currentPageOrParentComponentId);
	            }

	            // bind events for new component
	            ClipboardViewModel.bindEventsForComponent(componentTree);

	            // ....
	            if (paletteBindingProvider.subscribed()) {
	                console.log('wire palette...');
	                paletteBindingProvider.appliedComponents.push(componentTree);
	                ko.observable($(componentTree.getUISelector())[0]).extend({ applyBindings: paletteBindingProvider.colors });
	                $(componentTree.getUISelector()).trigger('click');
	            }

	            if (parentComponent == null) {
	                // bind undo redo
	                ClipboardViewModel.clipboardUndoRedoManager(componentTree);
	            }

	            // append to page components which is inside of FORM component
	            if (componentTree.displayName === FORM) {
	                componentTree.children = [];
	                _.forEach(pasted.children, function (item) {
	                    item.parentComponent.displayName = componentTree.displayName;
	                    clipBoard.pasteComponent(item, false, componentTree, false);
	                });
                    
	                if (componentTree.children.any()) {
	                    var children = UI.siteComponentRepository.lookupData({ id: componentTree.id }).children;
	                    _.forEach(children.where({ displayName: LABEL }), function (item) {
	                        var oldCaption = item.getProperty(CAPTION_COMPONENTS_TO_LABEL).value;
	                        var newCaption = Grouping.generateGuid();
	                        item.setProperty(CAPTION_COMPONENTS_TO_LABEL, newCaption);

	                        _.forEach(children.whereNot({ displayName: LABEL}), function (com) {
	                            var selectedLabel = com.getProperty(SELECTEDLABEL);
                                if (selectedLabel != null) {
                                    if (selectedLabel.value === oldCaption) {
                                        com.setProperty(SELECTEDLABEL, newCaption);
                                    }
                                }
	                        });
	                    });
	                }
	            }

	            if (isPasted) {
	                //scroll to new component
	                $(window).scrollTo($('#' + componentTree.id + ''), { over: -2 });
	            }
	            // remove button "paste component"
	            clipBoard.itemForClipboard(null);

	            // activate trigger for new component
	            if (grouping.clipboardGroup.length == 0 && isCallEditor) {
	                $($(componentTree.getUISelector())).highlightSelectedElement(componentTree, true);
	            } else {
	                clipBoard.selectedItem(null);
	            }

	            if (grouping.clipboardGroup.length > 0) {
	                grouping.queueToSelectAfterPaste.push(componentTree.getUISelector());
	            }

	            UI.renderMenus();

	            return true;  //return true for event bubbling to occur
	        } else {
	            clipBoard.itemForClipboard(null); //hide button
	        }
		}
	},

    duplicateComponent: function () {
        clipBoard.copyComponent(COMPONENT);
		clipBoard.pasteComponent();
		return true;

	},

    deleteComponent: function (model, event, isRun) {

        if (ClipboardViewModel.isActive() || Grouping.isActive() || isRun) {

            var e = $.Event('keydown');

            e.which = 46; // Character 'Del'
            e.keyCode = 46;

            $(UI.getConfigurationValue(HTML)).trigger(e);

            this.selectedItem(null);
            this.itemForClipboard(null);
            grouping.showGroupingOptions(false);

            Resizer.recalculateSizeFooterContainer($('.footer')[0]);

        }
        return true;
    },

    toggleShowHideComponent: function(model, event, isRun) {
        if (ClipboardViewModel.isActive() || Grouping.isActive() || isRun) {

            var e = $.Event('keydown');

            e.which = 0;
            e.keyCode = 0;

            $(UI.getConfigurationValue(HTML)).trigger(e);

            this.selectedItem(null);
            this.itemForClipboard(null);
            grouping.showGroupingOptions(false);

        }
        return true;
    }
});

ClipboardViewModel.bindEventsForComponent = function (component) {

    if (defined(component.events) && component.events != null) {
        if (defined(component.events.onComponentProcessed) && _.isFunction(component.events.onComponentProcessed)) {
            component.events.onComponentProcessed(component);
        }
        if (defined(component.events.onComponentCreated) && _.isFunction(component.events.onComponentCreated)) {
            component.events.onComponentCreated(component);
        }
    }
};

ClipboardViewModel.isActive = function() {
    if (UI.getSetting("ispreview")) {
        return false;
    }
    return clipBoard.selectedItem() !== null;
}

ClipboardViewModel.attachToParentComponent = function(component, parentComponentId) {
    component.detach();
    component.appendTo("#" + parentComponentId);
}

ClipboardViewModel.clipboardUndoRedoManager = function(component) {
    var pageIdOrParentId = UI.pager.getCurrentPageId();
    var id = component.id;
    UI.addLog('add', component);

    var redo = function () {
        if (component.parentComponent.displayName == FORM) {
            pageIdOrParentId = component.parentComponent.id;
        }

        UI.siteComponentRepository.appendTo(component, UI.siteComponentRepository.lookupData({ id: pageIdOrParentId }));

        UI.actionService.runActionForComponent(component, ACTION_ADD_TO_FORM, false, pageIdOrParentId);

        ClipboardViewModel.bindEventsForComponent(component);

        _.forEach(component.children, function (com) {

            UI.actionService.runActionForComponent(com, ACTION_ADD_TO_FORM, false, component.id);            

            ClipboardViewModel.bindEventsForComponent(com);

            ClipboardViewModel.attachToParentComponent($(com.getUISelector()), component.id);

            return;
        });

        if (component.parentComponent.displayName == FORM) {
            ClipboardViewModel.attachToParentComponent($(component.getUISelector()), component.parentComponent.id);
        }

        component.viewer();
        UI.addLog('add', component);
    }
    var undo = function () {
        UI.removeLog('add', UI.siteComponentRepository.lookupData({ id: id }));
        UI.siteComponentRepository.remove({ id: id });
    }

    UI.undoManagerAdd(
        {
            undo: function () {
                undo();
            },
            redo: function () {
                redo();
            }
        });
};

ClipboardViewModel.init = function () {
    $('.wrapper').on('click', function (e) {
        if (!UI.getSetting("ispreview")) {
            e.preventDefault();
            clipBoard.selectedItem(null);
        }
    });

    if (!UI.getSetting("ispreview") || UI.settings.isComponentEditor) {
        ko.applyBindings(clipBoard);
    }
}

ClipboardViewModel.distributionClipboard = function (process) {
    var clipboardGrouping = grouping.showGroupingOptions() || (grouping.clipboardGroup.length > 0) ? true : false;
    var clipboardSimpleComponents = clipBoard.selectedItem() || clipBoard.items()[0];
    var ignoreCopyList = [SIGNIN, STORE_CATEGORIES, STORE_CART_LINK, STORE_GALLERY];

    if (!UI.getDevice().isDesktop())
        return;

    switch (process) {
        case COPY:
            if (clipboardGrouping) {
                Grouping.copyGroup();
            } else if (clipboardSimpleComponents) {
                if (ignoreCopyList.indexOf(clipboardSimpleComponents.proto.name) === -1) {
                    clipBoard.copyComponent(COMPONENT);
                }
            }
            break;
        case DUPLICATE:
            if (clipboardGrouping) {
                Grouping.duplicateGroup();
            } else if (clipboardSimpleComponents) {
                clipBoard.duplicateComponent();
            }
            break;
        case PASTE:
            var isPasted = true;
            if (clipboardGrouping) {
                Grouping.pasteGroup(isPasted);
            } else if (clipboardSimpleComponents) {
                clipBoard.pasteComponent(null, false, null, true, false, isPasted);
            }
        default:
            break;
    }

    return;
}
ClipboardViewModel.getMinMaxZIndexValue = function (isMinValue) {
    var result = undefined;
    var currentArea = aligning.getCurrentArea();
    if (currentArea == undefined) {
        return result;
    }
    currentArea.children().each(function (element) {
        var zIndex = Number($(this).css("z-index"));
        result = (result == undefined ? zIndex : (isMinValue ? Math.min(result, zIndex) : Math.max(result, zIndex)));
    })
    return result;
}

ClipboardViewModel.getMaxMinZindexCount = function (zIndexMaxCount) {
    
    var currentArea = aligning.getCurrentArea();
    var result = 0;
    if (currentArea == undefined) {
        return result;
    }
    currentArea.children().each(function() {
        var zIndex = Number($(this).css("z-index"));
        if (zIndex === zIndexMaxCount) {
            result++;
        }
    });
    return result;
}

ClipboardViewModel.zIndex = function (process) {
    var simpleComponent = clipBoard.selectedItem();
    var group = Grouping.isActive();

    switch (process) {
        case "bringFrontAheadOfAll":
            var isEnabled = ClipboardViewModel.isLayeringEnabled();
            var maxZindex = ClipboardViewModel.getMinMaxZIndexValue(false);

            if (!isEnabled)
                break;
            
            if (simpleComponent) {
                new LayeringController(clipBoard.selectedItem()).setZIndex(maxZindex + 1);
            } else if (group) {
                var groupMaxZIndex = Grouping.getGroupZIndexValue();
                var difference = maxZindex + 1 - groupMaxZIndex;

                ko.utils.arrayForEach(grouping.selectedItems(), function (item) {
                    var com = Grouping.getComponent(item.id);
                    var zIndex = Number($(item).css("z-index")) + difference;
                    new LayeringController(com).setZIndex(zIndex);
                });
            }
            break;
        case "bringFront":
            var isEnabled = ClipboardViewModel.isLayeringEnabled();

            if (!isEnabled)
                break;

            if (simpleComponent) {
                new LayeringController(clipBoard.selectedItem()).bringFront();
            } else if (group) {
                ko.utils.arrayForEach(grouping.selectedItems(), function (item) {
                    var com = Grouping.getComponent(item.id);
                    new LayeringController(com).bringFront();
                });
            }
            break;
        case "sendBack":
            var isEnabled = ClipboardViewModel.isLayeringEnabled(true);

            if (!isEnabled)
                break;

            if (simpleComponent) {
                new LayeringController(clipBoard.selectedItem()).sendBack();
            } else if (group) {
                ko.utils.arrayForEach(grouping.selectedItems(), function (item) {
                    var com = Grouping.getComponent(item.id);
                    new LayeringController(com).sendBack();
                });
            }
            break;
        case "sendBackBehindAll":
            var isEnabled = ClipboardViewModel.isLayeringEnabled(true);
            var minZindex = ClipboardViewModel.getMinMaxZIndexValue(true);

            if (!isEnabled)
                break;

            if (simpleComponent) {
                new LayeringController(clipBoard.selectedItem()).setZIndex(minZindex - 1);
            } else if (group) {
                var groupMinZIndex = Grouping.getGroupZIndexValue(true);
                var difference = groupMinZIndex - (minZindex - 1);

                ko.utils.arrayForEach(grouping.selectedItems(), function (item) {
                    var com = Grouping.getComponent(item.id);
                    var zIndex = Number($(item).css("z-index")) - difference;
                    new LayeringController(com).setZIndex(zIndex);
                });
            }
            break;
        default:
            break;
    }

    ClipboardViewModel.setElementsAccess('layer-bring-front', ClipboardViewModel.isLayeringEnabled());
    ClipboardViewModel.setElementsAccess('layer-bring-back', ClipboardViewModel.isLayeringEnabled(true));    
};

ClipboardViewModel.isLayeringEnabled = function (bringToBack) {
    bringToBack = bringToBack || false;
    var simpleComponent = clipBoard.selectedItem();
    var group = Grouping.isActive();

    var extremumZindex = ClipboardViewModel.getMinMaxZIndexValue(bringToBack);
    var extremumZindexCount = ClipboardViewModel.getMaxMinZindexCount(extremumZindex);
    var currentElementsExtremumZindex = 0;
    var selectedComponentsExtremumZindexCount = 1;

    if (group) {
        currentElementsExtremumZindex = Grouping.getGroupZIndexValue(bringToBack);
        selectedComponentsExtremumZindexCount = Grouping.getGroupItemsCountByZindex(currentElementsExtremumZindex);
    }
    else
    if (simpleComponent) {
        currentElementsExtremumZindex = parseInt(simpleComponent.getProperty('z-index').value);
    }

    if (currentElementsExtremumZindex !== extremumZindex || extremumZindexCount > selectedComponentsExtremumZindexCount)
        return true;

    return false;
}

ClipboardViewModel.disabledEnabledLayreDown = function (selectorName, condition) {
    var element = document.querySelector(selectorName);
  
    if (condition) {
        element.classList.remove('disabled');
    }
    else {
        element.classList.add('disabled');
    }
    
}

ClipboardViewModel.setElementsAccess = function (selectorName, condition, excludeClass) {
    var elements = document.getElementsByClassName(selectorName);
    for (i = 0; i < elements.length; i++) {
        var element = elements[i];        
        if (condition && ((excludeClass && element.classList.value.indexOf(excludeClass) !== -1) || !excludeClass)) {
            element.classList.remove('disabled');
        }
        else {
            element.classList.add('disabled');
        }
    }
}

ClipboardViewModel.setElementsVisability = function () {

    var copiedItem = document.getElementById("copied-item");
    if(copiedItem != null){
        copiedItem.style.display = clipBoard.itemForClipboard() ? "none" : "block";
    }

    var pastedItem = document.getElementById("pasted-item");
    if(pastedItem != null){
        pastedItem.style.display = !clipBoard.itemForClipboard() ? "none" : "block";
    }
   
}

var clipBoard = new ClipboardViewModel([]);
;
﻿var EventsComponents = function () { }

EventsComponents.initEvents = function (component) {
    if (!UI.getSetting("ispreview")) {
        if (typeof component.eventComponent != "function") {
                component.eventComponent = function () { };
            }
    }
};

if (!window['YT']) { var YT = { loading: 0, loaded: 0 }; } if (!window['YTConfig']) { var YTConfig = { 'host': 'http://www.youtube.com' }; } if (!YT.loading) { YT.loading = 1; (function () { var l = []; YT.ready = function (f) { if (YT.loaded) { f(); } else { l.push(f); } }; window.onYTReady = function () { YT.loaded = 1; for (var i = 0; i < l.length; i++) { try { l[i](); } catch (e) { } } }; YT.setConfig = function (c) { for (var k in c) { if (c.hasOwnProperty(k)) { YTConfig[k] = c[k]; } } }; var a = document.createElement('script'); a.type = 'text/javascript'; a.id = 'www-widgetapi-script'; a.src = 'https:' + '//s.ytimg.com/yts/jsbin/www-widgetapi-vfliZmGBd/www-widgetapi.js'; a.async = true; var b = document.getElementsByTagName('script')[0]; b.parentNode.insertBefore(a, b); })(); };

if (!window['YT']) { var YT = { loading: 0, loaded: 0 }; } if (!window['YTConfig']) { var YTConfig = { 'host': 'http://www.youtube.com' }; } if (!YT.loading) { YT.loading = 1; (function () { var l = []; YT.ready = function (f) { if (YT.loaded) { f(); } else { l.push(f); } }; window.onYTReady = function () { YT.loaded = 1; for (var i = 0; i < l.length; i++) { try { l[i](); } catch (e) { } } }; YT.setConfig = function (c) { for (var k in c) { if (c.hasOwnProperty(k)) { YTConfig[k] = c[k]; } } }; var a = document.createElement('script'); a.type = 'text/javascript'; a.id = 'www-widgetapi-script'; a.src = 'https:' + '//s.ytimg.com/yts/jsbin/www-widgetapi-vfliZmGBd/www-widgetapi.js'; a.async = true; var b = document.getElementsByTagName('script')[0]; b.parentNode.insertBefore(a, b); })(); };
// Init style shamelessly stolen from jQuery http://jquery.com
var Froogaloop = (function(){
    // Define a local copy of Froogaloop
    function Froogaloop(iframe) {
        // The Froogaloop object is actually just the init constructor
        return new Froogaloop.fn.init(iframe);
    }

    var eventCallbacks = {},
        hasWindowEvent = false,
        isReady = false,
        slice = Array.prototype.slice,
        playerOrigin = '*';

    Froogaloop.fn = Froogaloop.prototype = {
        element: null,

        init: function(iframe) {
            if (typeof iframe === "string") {
                iframe = document.getElementById(iframe);
            }

            this.element = iframe;

            return this;
        },

        /*
         * Calls a function to act upon the player.
         *
         * @param {string} method The name of the Javascript API method to call. Eg: 'play'.
         * @param {Array|Function} valueOrCallback params Array of parameters to pass when calling an API method
         *                                or callback function when the method returns a value.
         */
        api: function(method, valueOrCallback) {
            if (!this.element || !method) {
                return false;
            }

            var self = this,
                element = self.element,
                target_id = element.id !== '' ? element.id : null,
                params = !isFunction(valueOrCallback) ? valueOrCallback : null,
                callback = isFunction(valueOrCallback) ? valueOrCallback : null;

            // Store the callback for get functions
            if (callback) {
                storeCallback(method, callback, target_id);
            }

            postMessage(method, params, element);
            return self;
        },

        /*
         * Registers an event listener and a callback function that gets called when the event fires.
         *
         * @param eventName (String): Name of the event to listen for.
         * @param callback (Function): Function that should be called when the event fires.
         */
        addEvent: function(eventName, callback) {
            if (!this.element) {
                return false;
            }

            var self = this,
                element = self.element,
                target_id = element.id !== '' ? element.id : null;


            storeCallback(eventName, callback, target_id);

            // The ready event is not registered via postMessage. It fires regardless.
            if (eventName != 'ready') {
                postMessage('addEventListener', eventName, element);
            }
            else if (eventName == 'ready' && isReady) {
                callback.call(null, target_id);
            }

            return self;
        },

        /*
         * Unregisters an event listener that gets called when the event fires.
         *
         * @param eventName (String): Name of the event to stop listening for.
         */
        removeEvent: function(eventName) {
            if (!this.element) {
                return false;
            }

            var self = this,
                element = self.element,
                target_id = element.id !== '' ? element.id : null,
                removed = removeCallback(eventName, target_id);

            // The ready event is not registered
            if (eventName != 'ready' && removed) {
                postMessage('removeEventListener', eventName, element);
            }
        }
    };

    /**
     * Handles posting a message to the parent window.
     *
     * @param method (String): name of the method to call inside the player. For api calls
     * this is the name of the api method (api_play or api_pause) while for events this method
     * is api_addEventListener.
     * @param params (Object or Array): List of parameters to submit to the method. Can be either
     * a single param or an array list of parameters.
     * @param target (HTMLElement): Target iframe to post the message to.
     */
    function postMessage(method, params, target) {
        if (!target.contentWindow.postMessage) {
            return false;
        }

        var data = JSON.stringify({
            method: method,
            value: params
        });

        target.contentWindow.postMessage(data, playerOrigin);
    }

    /**
     * Event that fires whenever the window receives a message from its parent
     * via window.postMessage.
     */
    function onMessageReceived(event) {
        var data, method;

        try {
            data = JSON.parse(event.data);
            method = data.event || data.method;
        }
        catch(e)  {
            //fail silently... like a ninja!
        }

        if (method == 'ready' && !isReady) {
            isReady = true;
        }

        // Handles messages from the vimeo player only
        if (!(/^https?:\/\/player.vimeo.com/).test(event.origin)) {
            return false;
        }

        if (playerOrigin === '*') {
            playerOrigin = event.origin;
        }

        var value = data.value,
            eventData = data.data,
            target_id = target_id === '' ? null : data.player_id,

            callback = getCallback(method, target_id),
            params = [];

        if (!callback) {
            return false;
        }

        if (value !== undefined) {
            params.push(value);
        }

        if (eventData) {
            params.push(eventData);
        }

        if (target_id) {
            params.push(target_id);
        }

        return params.length > 0 ? callback.apply(null, params) : callback.call();
    }


    /**
     * Stores submitted callbacks for each iframe being tracked and each
     * event for that iframe.
     *
     * @param eventName (String): Name of the event. Eg. api_onPlay
     * @param callback (Function): Function that should get executed when the
     * event is fired.
     * @param target_id (String) [Optional]: If handling more than one iframe then
     * it stores the different callbacks for different iframes based on the iframe's
     * id.
     */
    function storeCallback(eventName, callback, target_id) {
        if (target_id) {
            if (!eventCallbacks[target_id]) {
                eventCallbacks[target_id] = {};
            }
            eventCallbacks[target_id][eventName] = callback;
        }
        else {
            eventCallbacks[eventName] = callback;
        }
    }

    /**
     * Retrieves stored callbacks.
     */
    function getCallback(eventName, target_id) {
        if (target_id) {
            return eventCallbacks[target_id][eventName];
        }
        else {
            return eventCallbacks[eventName];
        }
    }

    function removeCallback(eventName, target_id) {
        if (target_id && eventCallbacks[target_id]) {
            if (!eventCallbacks[target_id][eventName]) {
                return false;
            }
            eventCallbacks[target_id][eventName] = null;
        }
        else {
            if (!eventCallbacks[eventName]) {
                return false;
            }
            eventCallbacks[eventName] = null;
        }

        return true;
    }

    function isFunction(obj) {
        return !!(obj && obj.constructor && obj.call && obj.apply);
    }

    function isArray(obj) {
        return toString.call(obj) === '[object Array]';
    }

    // Give the init function the Froogaloop prototype for later instantiation
    Froogaloop.fn.init.prototype = Froogaloop.fn;

    // Listens for the message event.
    // W3C
    if (window.addEventListener) {
        window.addEventListener('message', onMessageReceived, false);
    }
    // IE
    else {
        window.attachEvent('onmessage', onMessageReceived);
    }

    // Expose froogaloop to the global object
    return (window.Froogaloop = window.$f = Froogaloop);

})();;
/*
 * jPlayer Plugin for jQuery JavaScript Library
 * http://www.jplayer.org
 *
 * Copyright (c) 2009 - 2014 Happyworm Ltd
 * Licensed under the MIT license.
 * http://opensource.org/licenses/MIT
 *
 * Author: Mark J Panaghiston
 * Version: 2.9.2
 * Date: 14th December 2014
 */

/* Support for Zepto 1.0 compiled with optional data module.
 * For AMD or NODE/CommonJS support, you will need to manually switch the related 2 lines in the code below.
 * Search terms: "jQuery Switch" and "Zepto Switch"
 */

(function (root, factory) {
	if (typeof define === 'function' && define.amd) {
		// AMD. Register as an anonymous module.
		define(['jquery'], factory); // jQuery Switch
		// define(['zepto'], factory); // Zepto Switch
	} else if (typeof exports === 'object') {
		// Node/CommonJS
		factory(require('jquery')); // jQuery Switch
		//factory(require('zepto')); // Zepto Switch
	} else {
		// Browser globals
		if(root.jQuery) { // Use jQuery if available
			factory(root.jQuery);
		} else { // Otherwise, use Zepto
			factory(root.Zepto);
		}
	}
}(this, function ($, undefined) {

	// Adapted from jquery.ui.widget.js (1.8.7): $.widget.bridge - Tweaked $.data(this,XYZ) to $(this).data(XYZ) for Zepto
	$.fn.jPlayer = function( options ) {
		var name = "jPlayer";
		var isMethodCall = typeof options === "string",
			args = Array.prototype.slice.call( arguments, 1 ),
			returnValue = this;

		// allow multiple hashes to be passed on init
		options = !isMethodCall && args.length ?
			$.extend.apply( null, [ true, options ].concat(args) ) :
			options;

		// prevent calls to internal methods
		if ( isMethodCall && options.charAt( 0 ) === "_" ) {
			return returnValue;
		}

		if ( isMethodCall ) {
			this.each(function() {
				var instance = $(this).data( name ),
					methodValue = instance && $.isFunction( instance[options] ) ?
						instance[ options ].apply( instance, args ) :
						instance;
				if ( methodValue !== instance && methodValue !== undefined ) {
					returnValue = methodValue;
					return false;
				}
			});
		} else {
			this.each(function() {
				var instance = $(this).data( name );
				if ( instance ) {
					// instance.option( options || {} )._init(); // Orig jquery.ui.widget.js code: Not recommend for jPlayer. ie., Applying new options to an existing instance (via the jPlayer constructor) and performing the _init(). The _init() is what concerns me. It would leave a lot of event handlers acting on jPlayer instance and the interface.
					instance.option( options || {} ); // The new constructor only changes the options. Changing options only has basic support atm.
				} else {
					$(this).data( name, new $.jPlayer( options, this ) );
				}
			});
		}

		return returnValue;
	};

	$.jPlayer = function( options, element ) {
		// allow instantiation without initializing for simple inheritance
		if ( arguments.length ) {
			this.element = $(element);
			this.options = $.extend(true, {},
				this.options,
				options
			);
			var self = this;
			this.element.bind( "remove.jPlayer", function() {
				self.destroy();
			});
			this._init();
		}
	};
	// End of: (Adapted from jquery.ui.widget.js (1.8.7))

	// Zepto is missing one of the animation methods.
	if(typeof $.fn.stop !== 'function') {
		$.fn.stop = function() {};
	}

	// Emulated HTML5 methods and properties
	$.jPlayer.emulateMethods = "load play pause";
	$.jPlayer.emulateStatus = "src readyState networkState currentTime duration paused ended playbackRate";
	$.jPlayer.emulateOptions = "muted volume";

	// Reserved event names generated by jPlayer that are not part of the HTML5 Media element spec
	$.jPlayer.reservedEvent = "ready flashreset resize repeat error warning";

	// Events generated by jPlayer
	$.jPlayer.event = {};
	$.each(
		[
			'ready',
			'setmedia', // Fires when the media is set
			'flashreset', // Similar to the ready event if the Flash solution is set to display:none and then shown again or if it's reloaded for another reason by the browser. For example, using CSS position:fixed on Firefox for the full screen feature.
			'resize', // Occurs when the size changes through a full/restore screen operation or if the size/sizeFull options are changed.
			'repeat', // Occurs when the repeat status changes. Usually through clicks on the repeat button of the interface.
			'click', // Occurs when the user clicks on one of the following: poster image, html video, flash video.
			'error', // Event error code in event.jPlayer.error.type. See $.jPlayer.error
			'warning', // Event warning code in event.jPlayer.warning.type. See $.jPlayer.warning

			// Other events match HTML5 spec.
			'loadstart',
			'progress',
			'suspend',
			'abort',
			'emptied',
			'stalled',
			'play',
			'pause',
			'loadedmetadata',
			'loadeddata',
			'waiting',
			'playing',
			'canplay',
			'canplaythrough',
			'seeking',
			'seeked',
			'timeupdate',
			'ended',
			'ratechange',
			'durationchange',
			'volumechange'
		],
		function() {
			$.jPlayer.event[ this ] = 'jPlayer_' + this;
		}
	);

	$.jPlayer.htmlEvent = [ // These HTML events are bubbled through to the jPlayer event, without any internal action.
		"loadstart",
		// "progress", // jPlayer uses internally before bubbling.
		// "suspend", // jPlayer uses internally before bubbling.
		"abort",
		// "error", // jPlayer uses internally before bubbling.
		"emptied",
		"stalled",
		// "play", // jPlayer uses internally before bubbling.
		// "pause", // jPlayer uses internally before bubbling.
		"loadedmetadata",
		// "loadeddata", // jPlayer uses internally before bubbling.
		// "waiting", // jPlayer uses internally before bubbling.
		// "playing", // jPlayer uses internally before bubbling.
		"canplay",
		"canplaythrough"
		// "seeking", // jPlayer uses internally before bubbling.
		// "seeked", // jPlayer uses internally before bubbling.
		// "timeupdate", // jPlayer uses internally before bubbling.
		// "ended", // jPlayer uses internally before bubbling.
		// "ratechange" // jPlayer uses internally before bubbling.
		// "durationchange" // jPlayer uses internally before bubbling.
		// "volumechange" // jPlayer uses internally before bubbling.
	];

	$.jPlayer.pause = function() {
		$.jPlayer.prototype.destroyRemoved();
		$.each($.jPlayer.prototype.instances, function(i, element) {
			if(element.data("jPlayer").status.srcSet) { // Check that media is set otherwise would cause error event.
				element.jPlayer("pause");
			}
		});
	};

	// Default for jPlayer option.timeFormat
	$.jPlayer.timeFormat = {
		showHour: false,
		showMin: true,
		showSec: true,
		padHour: false,
		padMin: true,
		padSec: true,
		sepHour: ":",
		sepMin: ":",
		sepSec: ""
	};
	var ConvertTime = function() {
		this.init();
	};
	ConvertTime.prototype = {
		init: function() {
			this.options = {
				timeFormat: $.jPlayer.timeFormat
			};
		},
		time: function(s) { // function used on jPlayer.prototype._convertTime to enable per instance options.
			s = (s && typeof s === 'number') ? s : 0;

			var myTime = new Date(s * 1000),
				hour = myTime.getUTCHours(),
				min = this.options.timeFormat.showHour ? myTime.getUTCMinutes() : myTime.getUTCMinutes() + hour * 60,
				sec = this.options.timeFormat.showMin ? myTime.getUTCSeconds() : myTime.getUTCSeconds() + min * 60,
				strHour = (this.options.timeFormat.padHour && hour < 10) ? "0" + hour : hour,
				strMin = (this.options.timeFormat.padMin && min < 10) ? "0" + min : min,
				strSec = (this.options.timeFormat.padSec && sec < 10) ? "0" + sec : sec,
				strTime = "";

			strTime += this.options.timeFormat.showHour ? strHour + this.options.timeFormat.sepHour : "";
			strTime += this.options.timeFormat.showMin ? strMin + this.options.timeFormat.sepMin : "";
			strTime += this.options.timeFormat.showSec ? strSec + this.options.timeFormat.sepSec : "";

			return strTime;
		}
	};
	var myConvertTime = new ConvertTime();
	$.jPlayer.convertTime = function(s) {
		return myConvertTime.time(s);
	};

	// Adapting jQuery 1.4.4 code for jQuery.browser. Required since jQuery 1.3.2 does not detect Chrome as webkit.
	$.jPlayer.uaBrowser = function( userAgent ) {
		var ua = userAgent.toLowerCase();

		// Useragent RegExp
		var rwebkit = /(webkit)[ \/]([\w.]+)/;
		var ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/;
		var rmsie = /(msie) ([\w.]+)/;
		var rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/;

		var match = rwebkit.exec( ua ) ||
			ropera.exec( ua ) ||
			rmsie.exec( ua ) ||
			ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
			[];

		return { browser: match[1] || "", version: match[2] || "0" };
	};

	// Platform sniffer for detecting mobile devices
	$.jPlayer.uaPlatform = function( userAgent ) {
		var ua = userAgent.toLowerCase();

		// Useragent RegExp
		var rplatform = /(ipad|iphone|ipod|android|blackberry|playbook|windows ce|webos)/;
		var rtablet = /(ipad|playbook)/;
		var randroid = /(android)/;
		var rmobile = /(mobile)/;

		var platform = rplatform.exec( ua ) || [];
		var tablet = rtablet.exec( ua ) ||
			!rmobile.exec( ua ) && randroid.exec( ua ) ||
			[];

		if(platform[1]) {
			platform[1] = platform[1].replace(/\s/g, "_"); // Change whitespace to underscore. Enables dot notation.
		}

		return { platform: platform[1] || "", tablet: tablet[1] || "" };
	};

	$.jPlayer.browser = {
	};
	$.jPlayer.platform = {
	};

	var browserMatch = $.jPlayer.uaBrowser(navigator.userAgent);
	if ( browserMatch.browser ) {
		$.jPlayer.browser[ browserMatch.browser ] = true;
		$.jPlayer.browser.version = browserMatch.version;
	}
	var platformMatch = $.jPlayer.uaPlatform(navigator.userAgent);
	if ( platformMatch.platform ) {
		$.jPlayer.platform[ platformMatch.platform ] = true;
		$.jPlayer.platform.mobile = !platformMatch.tablet;
		$.jPlayer.platform.tablet = !!platformMatch.tablet;
	}

	// Internet Explorer (IE) Browser Document Mode Sniffer. Based on code at:
	// http://msdn.microsoft.com/en-us/library/cc288325%28v=vs.85%29.aspx#GetMode
	$.jPlayer.getDocMode = function() {
		var docMode;
		if ($.jPlayer.browser.msie) {
			if (document.documentMode) { // IE8 or later
				docMode = document.documentMode;
			} else { // IE 5-7
				docMode = 5; // Assume quirks mode unless proven otherwise
				if (document.compatMode) {
					if (document.compatMode === "CSS1Compat") {
						docMode = 7; // standards mode
					}
				}
			}
		}
		return docMode;
	};
	$.jPlayer.browser.documentMode = $.jPlayer.getDocMode();

	$.jPlayer.nativeFeatures = {
		init: function() {

			/* Fullscreen function naming influenced by W3C naming.
			 * No support for: Mozilla Proposal: https://wiki.mozilla.org/Gecko:FullScreenAPI
			 */

			var d = document,
				v = d.createElement('video'),
				spec = {
					// http://www.w3.org/TR/fullscreen/
					w3c: [
						'fullscreenEnabled',
						'fullscreenElement',
						'requestFullscreen',
						'exitFullscreen',
						'fullscreenchange',
						'fullscreenerror'
					],
					// https://developer.mozilla.org/en-US/docs/DOM/Using_fullscreen_mode
					moz: [
						'mozFullScreenEnabled',
						'mozFullScreenElement',
						'mozRequestFullScreen',
						'mozCancelFullScreen',
						'mozfullscreenchange',
						'mozfullscreenerror'
					],
					// http://developer.apple.com/library/safari/#documentation/WebKit/Reference/ElementClassRef/Element/Element.html
					// http://developer.apple.com/library/safari/#documentation/UserExperience/Reference/DocumentAdditionsReference/DocumentAdditions/DocumentAdditions.html
					webkit: [
						'',
						'webkitCurrentFullScreenElement',
						'webkitRequestFullScreen',
						'webkitCancelFullScreen',
						'webkitfullscreenchange',
						''
					],
					// http://developer.apple.com/library/safari/#documentation/AudioVideo/Reference/HTMLVideoElementClassReference/HTMLVideoElement/HTMLVideoElement.html
					// https://developer.apple.com/library/safari/samplecode/HTML5VideoEventFlow/Listings/events_js.html#//apple_ref/doc/uid/DTS40010085-events_js-DontLinkElementID_5
					// Events: 'webkitbeginfullscreen' and 'webkitendfullscreen'
					webkitVideo: [
						'webkitSupportsFullscreen',
						'webkitDisplayingFullscreen',
						'webkitEnterFullscreen',
						'webkitExitFullscreen',
						'',
						''
					],
					ms: [
						'',
						'msFullscreenElement',
						'msRequestFullscreen',
						'msExitFullscreen',
						'MSFullscreenChange',
						'MSFullscreenError'
					]
				},
				specOrder = [
					'w3c',
					'moz',
					'webkit',
					'webkitVideo',
					'ms'
				],
				fs, i, il;

			this.fullscreen = fs = {
				support: {
					w3c: !!d[spec.w3c[0]],
					moz: !!d[spec.moz[0]],
					webkit: typeof d[spec.webkit[3]] === 'function',
					webkitVideo: typeof v[spec.webkitVideo[2]] === 'function',
					ms: typeof v[spec.ms[2]] === 'function'
				},
				used: {}
			};

			// Store the name of the spec being used and as a handy boolean.
			for(i = 0, il = specOrder.length; i < il; i++) {
				var n = specOrder[i];
				if(fs.support[n]) {
					fs.spec = n;
					fs.used[n] = true;
					break;
				}
			}

			if(fs.spec) {
				var s = spec[fs.spec];
				fs.api = {
					fullscreenEnabled: true,
					fullscreenElement: function(elem) {
						elem = elem ? elem : d; // Video element required for webkitVideo
						return elem[s[1]];
					},
					requestFullscreen: function(elem) {
						return elem[s[2]](); // Chrome and Opera want parameter (Element.ALLOW_KEYBOARD_INPUT) but Safari fails if flag used.
					},
					exitFullscreen: function(elem) {
						elem = elem ? elem : d; // Video element required for webkitVideo
						return elem[s[3]]();
					}
				};
				fs.event = {
					fullscreenchange: s[4],
					fullscreenerror: s[5]
				};
			} else {
				fs.api = {
					fullscreenEnabled: false,
					fullscreenElement: function() {
						return null;
					},
					requestFullscreen: function() {},
					exitFullscreen: function() {}
				};
				fs.event = {};
			}
		}
	};
	$.jPlayer.nativeFeatures.init();

	// The keyboard control system.

	// The current jPlayer instance in focus.
	$.jPlayer.focus = null;

	// The list of element node names to ignore with key controls.
	$.jPlayer.keyIgnoreElementNames = "A INPUT TEXTAREA SELECT BUTTON";

	// The function that deals with key presses.
	var keyBindings = function(event) {
		var f = $.jPlayer.focus,
			ignoreKey;

		// A jPlayer instance must be in focus. ie., keyEnabled and the last one played.
		if(f) {
			// What generated the key press?
			$.each( $.jPlayer.keyIgnoreElementNames.split(/\s+/g), function(i, name) {
				// The strings should already be uppercase.
				if(event.target.nodeName.toUpperCase() === name.toUpperCase()) {
					ignoreKey = true;
					return false; // exit each.
				}
			});
			if(!ignoreKey) {
				// See if the key pressed matches any of the bindings.
				$.each(f.options.keyBindings, function(action, binding) {
					// The binding could be a null when the default has been disabled. ie., 1st clause in if()
					if(
						(binding && $.isFunction(binding.fn)) &&
						((typeof binding.key === 'number' && event.which === binding.key) ||
						(typeof binding.key === 'string' && event.key === binding.key))
					) {
						event.preventDefault(); // Key being used by jPlayer, so prevent default operation.
						binding.fn(f);
						return false; // exit each.
					}
				});
			}
		}
	};

	$.jPlayer.keys = function(en) {
		var event = "keydown.jPlayer";
		// Remove any binding, just in case enabled more than once.
		$(document.documentElement).unbind(event);
		if(en) {
			$(document.documentElement).bind(event, keyBindings);
		}
	};

	// Enable the global key control handler ready for any jPlayer instance with the keyEnabled option enabled.
	$.jPlayer.keys(true);

	$.jPlayer.prototype = {
		count: 0, // Static Variable: Change it via prototype.
		version: { // Static Object
			script: "2.9.2",
			needFlash: "2.9.0",
			flash: "unknown"
		},
		options: { // Instanced in $.jPlayer() constructor
			swfPath: "js", // Path to jquery.jplayer.swf. Can be relative, absolute or server root relative.
			solution: "html, flash", // Valid solutions: html, flash, aurora. Order defines priority. 1st is highest,
			supplied: "mp3", // Defines which formats jPlayer will try and support and the priority by the order. 1st is highest,
			auroraFormats: "wav", // List the aurora.js codecs being loaded externally. Its core supports "wav". Specify format in jPlayer context. EG., The aac.js codec gives the "m4a" format.
			preload: 'metadata',  // HTML5 Spec values: none, metadata, auto.
			volume: 0.8, // The volume. Number 0 to 1.
			muted: false,
			remainingDuration: false, // When true, the remaining time is shown in the duration GUI element.
			toggleDuration: false, // When true, clicks on the duration toggle between the duration and remaining display.
			captureDuration: true, // When true, clicks on the duration are captured and no longer propagate up the DOM.
			playbackRate: 1,
			defaultPlaybackRate: 1,
			minPlaybackRate: 0.5,
			maxPlaybackRate: 4,
			wmode: "opaque", // Valid wmode: window, transparent, opaque, direct, gpu. 
			backgroundColor: "#000000", // To define the jPlayer div and Flash background color.
			cssSelectorAncestor: "#jp_container_1",
			cssSelector: { // * denotes properties that should only be required when video media type required. _cssSelector() would require changes to enable splitting these into Audio and Video defaults.
				videoPlay: ".jp-video-play", // *
				play: ".jp-play",
				pause: ".jp-pause",
				stop: ".jp-stop",
				seekBar: ".jp-seek-bar",
				playBar: ".jp-play-bar",
				mute: ".jp-mute",
				unmute: ".jp-unmute",
				volumeBar: ".jp-volume-bar",
				volumeBarValue: ".jp-volume-bar-value",
				volumeMax: ".jp-volume-max",
				playbackRateBar: ".jp-playback-rate-bar",
				playbackRateBarValue: ".jp-playback-rate-bar-value",
				currentTime: ".jp-current-time",
				duration: ".jp-duration",
				title: ".jp-title",
				fullScreen: ".jp-full-screen", // *
				restoreScreen: ".jp-restore-screen", // *
				repeat: ".jp-repeat",
				repeatOff: ".jp-repeat-off",
				gui: ".jp-gui", // The interface used with autohide feature.
				noSolution: ".jp-no-solution" // For error feedback when jPlayer cannot find a solution.
			},
			stateClass: { // Classes added to the cssSelectorAncestor to indicate the state.
				playing: "jp-state-playing",
				seeking: "jp-state-seeking",
				muted: "jp-state-muted",
				looped: "jp-state-looped",
				fullScreen: "jp-state-full-screen",
				noVolume: "jp-state-no-volume"
			},
			useStateClassSkin: false, // A state class skin relies on the state classes to change the visual appearance. The single control toggles the effect, for example: play then pause, mute then unmute.
			autoBlur: true, // GUI control handlers will drop focus after clicks.
			smoothPlayBar: false, // Smooths the play bar transitions, which affects clicks and short media with big changes per second.
			fullScreen: false, // Native Full Screen
			fullWindow: false,
			autohide: {
				restored: false, // Controls the interface autohide feature.
				full: true, // Controls the interface autohide feature.
				fadeIn: 200, // Milliseconds. The period of the fadeIn anim.
				fadeOut: 600, // Milliseconds. The period of the fadeOut anim.
				hold: 1000 // Milliseconds. The period of the pause before autohide beings.
			},
			loop: false,
			repeat: function(event) { // The default jPlayer repeat event handler
				if(event.jPlayer.options.loop) {
					$(this).unbind(".jPlayerRepeat").bind($.jPlayer.event.ended + ".jPlayer.jPlayerRepeat", function() {
						$(this).jPlayer("play");
					});
				} else {
					$(this).unbind(".jPlayerRepeat");
				}
			},
			nativeVideoControls: {
				// Works well on standard browsers.
				// Phone and tablet browsers can have problems with the controls disappearing.
			},
			noFullWindow: {
				msie: /msie [0-6]\./,
				ipad: /ipad.*?os [0-4]\./,
				iphone: /iphone/,
				ipod: /ipod/,
				android_pad: /android [0-3]\.(?!.*?mobile)/,
				android_phone: /(?=.*android)(?!.*chrome)(?=.*mobile)/,
				blackberry: /blackberry/,
				windows_ce: /windows ce/,
				iemobile: /iemobile/,
				webos: /webos/
			},
			noVolume: {
				ipad: /ipad/,
				iphone: /iphone/,
				ipod: /ipod/,
				android_pad: /android(?!.*?mobile)/,
				android_phone: /android.*?mobile/,
				blackberry: /blackberry/,
				windows_ce: /windows ce/,
				iemobile: /iemobile/,
				webos: /webos/,
				playbook: /playbook/
			},
			timeFormat: {
				// Specific time format for this instance. The supported options are defined in $.jPlayer.timeFormat
				// For the undefined options we use the default from $.jPlayer.timeFormat
			},
			keyEnabled: false, // Enables keyboard controls.
			audioFullScreen: false, // Enables keyboard controls to enter full screen with audio media.
			keyBindings: { // The key control object, defining the key codes and the functions to execute.
				// The parameter, f = $.jPlayer.focus, will be checked truethy before attempting to call any of these functions.
				// Properties may be added to this object, in key/fn pairs, to enable other key controls. EG, for the playlist add-on.
				play: {
					key: 80, // p
					fn: function(f) {
						if(f.status.paused) {
							f.play();
						} else {
							f.pause();
						}
					}
				},
				fullScreen: {
					key: 70, // f
					fn: function(f) {
						if(f.status.video || f.options.audioFullScreen) {
							f._setOption("fullScreen", !f.options.fullScreen);
						}
					}
				},
				muted: {
					key: 77, // m
					fn: function(f) {
						f._muted(!f.options.muted);
					}
				},
				volumeUp: {
					key: 190, // .
					fn: function(f) {
						f.volume(f.options.volume + 0.1);
					}
				},
				volumeDown: {
					key: 188, // ,
					fn: function(f) {
						f.volume(f.options.volume - 0.1);
					}
				},
				loop: {
					key: 76, // l
					fn: function(f) {
						f._loop(!f.options.loop);
					}
				}
			},
			verticalVolume: false, // Calculate volume from the bottom of the volume bar. Default is from the left. Also volume affects either width or height.
			verticalPlaybackRate: false,
			globalVolume: false, // Set to make volume and muted changes affect all jPlayer instances with this option enabled
			idPrefix: "jp", // Prefix for the ids of html elements created by jPlayer. For flash, this must not include characters: . - + * / \
			noConflict: "jQuery",
			emulateHtml: false, // Emulates the HTML5 Media element on the jPlayer element.
			consoleAlerts: true, // Alerts are sent to the console.log() instead of alert().
			errorAlerts: false,
			warningAlerts: false
		},
		optionsAudio: {
			size: {
				width: "0px",
				height: "0px",
				cssClass: ""
			},
			sizeFull: {
				width: "0px",
				height: "0px",
				cssClass: ""
			}
		},
		optionsVideo: {
			size: {
				width: "480px",
				height: "270px",
				cssClass: "jp-video-270p"
			},
			sizeFull: {
				width: "100%",
				height: "100%",
				cssClass: "jp-video-full"
			}
		},
		instances: {}, // Static Object
		status: { // Instanced in _init()
			src: "",
			media: {},
			paused: true,
			format: {},
			formatType: "",
			waitForPlay: true, // Same as waitForLoad except in case where preloading.
			waitForLoad: true,
			srcSet: false,
			video: false, // True if playing a video
			seekPercent: 0,
			currentPercentRelative: 0,
			currentPercentAbsolute: 0,
			currentTime: 0,
			duration: 0,
			remaining: 0,
			videoWidth: 0, // Intrinsic width of the video in pixels.
			videoHeight: 0, // Intrinsic height of the video in pixels.
			readyState: 0,
			networkState: 0,
			playbackRate: 1, // Warning - Now both an option and a status property
			ended: 0

/*		Persistant status properties created dynamically at _init():
			width
			height
			cssClass
			nativeVideoControls
			noFullWindow
			noVolume
			playbackRateEnabled // Warning - Technically, we can have both Flash and HTML, so this might not be correct if the Flash is active. That is a niche case.
*/
		},

		internal: { // Instanced in _init()
			ready: false
			// instance: undefined
			// domNode: undefined
			// htmlDlyCmdId: undefined
			// autohideId: undefined
			// mouse: undefined
			// cmdsIgnored
		},
		solution: { // Static Object: Defines the solutions built in jPlayer.
			html: true,
			aurora: true,
			flash: true
		},
		// 'MPEG-4 support' : canPlayType('video/mp4; codecs="mp4v.20.8"')
		format: { // Static Object
			mp3: {
				codec: 'audio/mpeg',
				flashCanPlay: true,
				media: 'audio'
			},
			m4a: { // AAC / MP4
				codec: 'audio/mp4; codecs="mp4a.40.2"',
				flashCanPlay: true,
				media: 'audio'
			},
			m3u8a: { // AAC / MP4 / Apple HLS
				codec: 'application/vnd.apple.mpegurl; codecs="mp4a.40.2"',
				flashCanPlay: false,
				media: 'audio'
			},
			m3ua: { // M3U
				codec: 'audio/mpegurl',
				flashCanPlay: false,
				media: 'audio'
			},
			oga: { // OGG
				codec: 'audio/ogg; codecs="vorbis, opus"',
				flashCanPlay: false,
				media: 'audio'
			},
			flac: { // FLAC
				codec: 'audio/x-flac',
				flashCanPlay: false,
				media: 'audio'
			},
			wav: { // PCM
				codec: 'audio/wav; codecs="1"',
				flashCanPlay: false,
				media: 'audio'
			},
			webma: { // WEBM
				codec: 'audio/webm; codecs="vorbis"',
				flashCanPlay: false,
				media: 'audio'
			},
			fla: { // FLV / F4A
				codec: 'audio/x-flv',
				flashCanPlay: true,
				media: 'audio'
			},
			rtmpa: { // RTMP AUDIO
				codec: 'audio/rtmp; codecs="rtmp"',
				flashCanPlay: true,
				media: 'audio'
			},
			m4v: { // H.264 / MP4
				codec: 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"',
				flashCanPlay: true,
				media: 'video'
			},
			m3u8v: { // H.264 / AAC / MP4 / Apple HLS
				codec: 'application/vnd.apple.mpegurl; codecs="avc1.42E01E, mp4a.40.2"',
				flashCanPlay: false,
				media: 'video'
			},
			m3uv: { // M3U
				codec: 'audio/mpegurl',
				flashCanPlay: false,
				media: 'video'
			},
			ogv: { // OGG
				codec: 'video/ogg; codecs="theora, vorbis"',
				flashCanPlay: false,
				media: 'video'
			},
			webmv: { // WEBM
				codec: 'video/webm; codecs="vorbis, vp8"',
				flashCanPlay: false,
				media: 'video'
			},
			flv: { // FLV / F4V
				codec: 'video/x-flv',
				flashCanPlay: true,
				media: 'video'
			},
			rtmpv: { // RTMP VIDEO
				codec: 'video/rtmp; codecs="rtmp"',
				flashCanPlay: true,
				media: 'video'
			}
		},
		_init: function() {
			var self = this;
			
			this.element.empty();
			
			this.status = $.extend({}, this.status); // Copy static to unique instance.
			this.internal = $.extend({}, this.internal); // Copy static to unique instance.

			// Initialize the time format
			this.options.timeFormat = $.extend({}, $.jPlayer.timeFormat, this.options.timeFormat);

			// On iOS, assume commands will be ignored before user initiates them.
			this.internal.cmdsIgnored = $.jPlayer.platform.ipad || $.jPlayer.platform.iphone || $.jPlayer.platform.ipod;

			this.internal.domNode = this.element.get(0);

			// Add key bindings focus to 1st jPlayer instanced with key control enabled.
			if(this.options.keyEnabled && !$.jPlayer.focus) {
				$.jPlayer.focus = this;
			}

			// A fix for Android where older (2.3) and even some 4.x devices fail to work when changing the *audio* SRC and then playing immediately.
			this.androidFix = {
				setMedia: false, // True when media set
				play: false, // True when a progress event will instruct the media to play
				pause: false, // True when a progress event will instruct the media to pause at a time.
				time: NaN // The play(time) parameter
			};
			if($.jPlayer.platform.android) {
				this.options.preload = this.options.preload !== 'auto' ? 'metadata' : 'auto'; // Default to metadata, but allow auto.
			}

			this.formats = []; // Array based on supplied string option. Order defines priority.
			this.solutions = []; // Array based on solution string option. Order defines priority.
			this.require = {}; // Which media types are required: video, audio.
			
			this.htmlElement = {}; // DOM elements created by jPlayer
			this.html = {}; // In _init()'s this.desired code and setmedia(): Accessed via this[solution], where solution from this.solutions array.
			this.html.audio = {};
			this.html.video = {};
			this.aurora = {}; // In _init()'s this.desired code and setmedia(): Accessed via this[solution], where solution from this.solutions array.
			this.aurora.formats = [];
			this.aurora.properties = [];
			this.flash = {}; // In _init()'s this.desired code and setmedia(): Accessed via this[solution], where solution from this.solutions array.
			
			this.css = {};
			this.css.cs = {}; // Holds the css selector strings
			this.css.jq = {}; // Holds jQuery selectors. ie., $(css.cs.method)

			this.ancestorJq = []; // Holds jQuery selector of cssSelectorAncestor. Init would use $() instead of [], but it is only 1.4+

			this.options.volume = this._limitValue(this.options.volume, 0, 1); // Limit volume value's bounds.

			// Create the formats array, with prority based on the order of the supplied formats string
			$.each(this.options.supplied.toLowerCase().split(","), function(index1, value1) {
				var format = value1.replace(/^\s+|\s+$/g, ""); //trim
				if(self.format[format]) { // Check format is valid.
					var dupFound = false;
					$.each(self.formats, function(index2, value2) { // Check for duplicates
						if(format === value2) {
							dupFound = true;
							return false;
						}
					});
					if(!dupFound) {
						self.formats.push(format);
					}
				}
			});

			// Create the solutions array, with prority based on the order of the solution string
			$.each(this.options.solution.toLowerCase().split(","), function(index1, value1) {
				var solution = value1.replace(/^\s+|\s+$/g, ""); //trim
				if(self.solution[solution]) { // Check solution is valid.
					var dupFound = false;
					$.each(self.solutions, function(index2, value2) { // Check for duplicates
						if(solution === value2) {
							dupFound = true;
							return false;
						}
					});
					if(!dupFound) {
						self.solutions.push(solution);
					}
				}
			});
				
			// Create Aurora.js formats array
			$.each(this.options.auroraFormats.toLowerCase().split(","), function(index1, value1) {
				var format = value1.replace(/^\s+|\s+$/g, ""); //trim
				if(self.format[format]) { // Check format is valid.
					var dupFound = false;
					$.each(self.aurora.formats, function(index2, value2) { // Check for duplicates
						if(format === value2) {
							dupFound = true;
							return false;
						}
					});
					if(!dupFound) {
						self.aurora.formats.push(format);
					}
				}
			});

			this.internal.instance = "jp_" + this.count;
			this.instances[this.internal.instance] = this.element;

			// Check the jPlayer div has an id and create one if required. Important for Flash to know the unique id for comms.
			if(!this.element.attr("id")) {
				this.element.attr("id", this.options.idPrefix + "_jplayer_" + this.count);
			}

			this.internal.self = $.extend({}, {
				id: this.element.attr("id"),
				jq: this.element
			});
			this.internal.audio = $.extend({}, {
				id: this.options.idPrefix + "_audio_" + this.count,
				jq: undefined
			});
			this.internal.video = $.extend({}, {
				id: this.options.idPrefix + "_video_" + this.count,
				jq: undefined
			});
			this.internal.flash = $.extend({}, {
				id: this.options.idPrefix + "_flash_" + this.count,
				jq: undefined,
				swf: this.options.swfPath + (this.options.swfPath.toLowerCase().slice(-4) !== ".swf" ? (this.options.swfPath && this.options.swfPath.slice(-1) !== "/" ? "/" : "") + "jquery.jplayer.swf" : "")
			});
			this.internal.poster = $.extend({}, {
				id: this.options.idPrefix + "_poster_" + this.count,
				jq: undefined
			});

			// Register listeners defined in the constructor
			$.each($.jPlayer.event, function(eventName,eventType) {
				if(self.options[eventName] !== undefined) {
					self.element.bind(eventType + ".jPlayer", self.options[eventName]); // With .jPlayer namespace.
					self.options[eventName] = undefined; // Destroy the handler pointer copy on the options. Reason, events can be added/removed in other ways so this could be obsolete and misleading.
				}
			});

			// Determine if we require solutions for audio, video or both media types.
			this.require.audio = false;
			this.require.video = false;
			$.each(this.formats, function(priority, format) {
				self.require[self.format[format].media] = true;
			});

			// Now required types are known, finish the options default settings.
			if(this.require.video) {
				this.options = $.extend(true, {},
					this.optionsVideo,
					this.options
				);
			} else {
				this.options = $.extend(true, {},
					this.optionsAudio,
					this.options
				);
			}
			this._setSize(); // update status and jPlayer element size

			// Determine the status for Blocklisted options.
			this.status.nativeVideoControls = this._uaBlocklist(this.options.nativeVideoControls);
			this.status.noFullWindow = this._uaBlocklist(this.options.noFullWindow);
			this.status.noVolume = this._uaBlocklist(this.options.noVolume);

			// Create event handlers if native fullscreen is supported
			if($.jPlayer.nativeFeatures.fullscreen.api.fullscreenEnabled) {
				this._fullscreenAddEventListeners();
			}

			// The native controls are only for video and are disabled when audio is also used.
			this._restrictNativeVideoControls();

			// Create the poster image.
			this.htmlElement.poster = document.createElement('img');
			this.htmlElement.poster.id = this.internal.poster.id;
			this.htmlElement.poster.onload = function() { // Note that this did not work on Firefox 3.6: poster.addEventListener("onload", function() {}, false); Did not investigate x-browser.
				if(!self.status.video || self.status.waitForPlay) {
					self.internal.poster.jq.show();
				}
			};
			this.element.append(this.htmlElement.poster);
			this.internal.poster.jq = $("#" + this.internal.poster.id);
			this.internal.poster.jq.css({'width': this.status.width, 'height': this.status.height});
			this.internal.poster.jq.hide();
			this.internal.poster.jq.bind("click.jPlayer", function() {
				self._trigger($.jPlayer.event.click);
			});
			
			// Generate the required media elements
			this.html.audio.available = false;
			if(this.require.audio) { // If a supplied format is audio
				this.htmlElement.audio = document.createElement('audio');
				this.htmlElement.audio.id = this.internal.audio.id;
				this.html.audio.available = !!this.htmlElement.audio.canPlayType && this._testCanPlayType(this.htmlElement.audio); // Test is for IE9 on Win Server 2008.
			}
			this.html.video.available = false;
			if(this.require.video) { // If a supplied format is video
				this.htmlElement.video = document.createElement('video');
				this.htmlElement.video.id = this.internal.video.id;
				this.html.video.available = !!this.htmlElement.video.canPlayType && this._testCanPlayType(this.htmlElement.video); // Test is for IE9 on Win Server 2008.
			}

			this.flash.available = this._checkForFlash(10.1);

			this.html.canPlay = {};
			this.aurora.canPlay = {};
			this.flash.canPlay = {};
			$.each(this.formats, function(priority, format) {
				self.html.canPlay[format] = self.html[self.format[format].media].available && "" !== self.htmlElement[self.format[format].media].canPlayType(self.format[format].codec);
				self.aurora.canPlay[format] = ($.inArray(format, self.aurora.formats) > -1);
				self.flash.canPlay[format] = self.format[format].flashCanPlay && self.flash.available;
			});
			this.html.desired = false;
			this.aurora.desired = false;
			this.flash.desired = false;
			$.each(this.solutions, function(solutionPriority, solution) {
				if(solutionPriority === 0) {
					self[solution].desired = true;
				} else {
					var audioCanPlay = false;
					var videoCanPlay = false;
					$.each(self.formats, function(formatPriority, format) {
						if(self[self.solutions[0]].canPlay[format]) { // The other solution can play
							if(self.format[format].media === 'video') {
								videoCanPlay = true;
							} else {
								audioCanPlay = true;
							}
						}
					});
					self[solution].desired = (self.require.audio && !audioCanPlay) || (self.require.video && !videoCanPlay);
				}
			});
			// This is what jPlayer will support, based on solution and supplied.
			this.html.support = {};
			this.aurora.support = {};
			this.flash.support = {};
			$.each(this.formats, function(priority, format) {
				self.html.support[format] = self.html.canPlay[format] && self.html.desired;
				self.aurora.support[format] = self.aurora.canPlay[format] && self.aurora.desired;
				self.flash.support[format] = self.flash.canPlay[format] && self.flash.desired;
			});
			// If jPlayer is supporting any format in a solution, then the solution is used.
			this.html.used = false;
			this.aurora.used = false;
			this.flash.used = false;
			$.each(this.solutions, function(solutionPriority, solution) {
				$.each(self.formats, function(formatPriority, format) {
					if(self[solution].support[format]) {
						self[solution].used = true;
						return false;
					}
				});
			});

			// Init solution active state and the event gates to false.
			this._resetActive();
			this._resetGate();

			// Set up the css selectors for the control and feedback entities.
			this._cssSelectorAncestor(this.options.cssSelectorAncestor);
			
			// If neither html nor aurora nor flash are being used by this browser, then media playback is not possible. Trigger an error event.
			if(!(this.html.used || this.aurora.used || this.flash.used)) {
				this._error( {
					type: $.jPlayer.error.NO_SOLUTION, 
					context: "{solution:'" + this.options.solution + "', supplied:'" + this.options.supplied + "'}",
					message: $.jPlayer.errorMsg.NO_SOLUTION,
					hint: $.jPlayer.errorHint.NO_SOLUTION
				});
				if(this.css.jq.noSolution.length) {
					this.css.jq.noSolution.show();
				}
			} else {
				if(this.css.jq.noSolution.length) {
					this.css.jq.noSolution.hide();
				}
			}

			// Add the flash solution if it is being used.
			if(this.flash.used) {
				var htmlObj,
				flashVars = 'jQuery=' + encodeURI(this.options.noConflict) + '&id=' + encodeURI(this.internal.self.id) + '&vol=' + this.options.volume + '&muted=' + this.options.muted;

				// Code influenced by SWFObject 2.2: http://code.google.com/p/swfobject/
				// Non IE browsers have an initial Flash size of 1 by 1 otherwise the wmode affected the Flash ready event. 

				if($.jPlayer.browser.msie && (Number($.jPlayer.browser.version) < 9 || $.jPlayer.browser.documentMode < 9)) {
					var objStr = '<object id="' + this.internal.flash.id + '" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="0" height="0" tabindex="-1"></object>';

					var paramStr = [
						'<param name="movie" value="' + this.internal.flash.swf + '" />',
						'<param name="FlashVars" value="' + flashVars + '" />',
						'<param name="allowScriptAccess" value="always" />',
						'<param name="bgcolor" value="' + this.options.backgroundColor + '" />',
						'<param name="wmode" value="' + this.options.wmode + '" />'
					];

					htmlObj = document.createElement(objStr);
					for(var i=0; i < paramStr.length; i++) {
						htmlObj.appendChild(document.createElement(paramStr[i]));
					}
				} else {
					var createParam = function(el, n, v) {
						var p = document.createElement("param");
						p.setAttribute("name", n);	
						p.setAttribute("value", v);
						el.appendChild(p);
					};

					htmlObj = document.createElement("object");
					htmlObj.setAttribute("id", this.internal.flash.id);
					htmlObj.setAttribute("name", this.internal.flash.id);
					htmlObj.setAttribute("data", this.internal.flash.swf);
					htmlObj.setAttribute("type", "application/x-shockwave-flash");
					htmlObj.setAttribute("width", "1"); // Non-zero
					htmlObj.setAttribute("height", "1"); // Non-zero
					htmlObj.setAttribute("tabindex", "-1");
					createParam(htmlObj, "flashvars", flashVars);
					createParam(htmlObj, "allowscriptaccess", "always");
					createParam(htmlObj, "bgcolor", this.options.backgroundColor);
					createParam(htmlObj, "wmode", this.options.wmode);
				}

				this.element.append(htmlObj);
				this.internal.flash.jq = $(htmlObj);
			}

			// Setup playbackRate ability before using _addHtmlEventListeners()
			if(this.html.used && !this.flash.used) { // If only HTML
				// Using the audio element capabilities for playbackRate. ie., Assuming video element is the same.
				this.status.playbackRateEnabled = this._testPlaybackRate('audio');
			} else {
				this.status.playbackRateEnabled = false;
			}

			this._updatePlaybackRate();

			// Add the HTML solution if being used.
			if(this.html.used) {

				// The HTML Audio handlers
				if(this.html.audio.available) {
					this._addHtmlEventListeners(this.htmlElement.audio, this.html.audio);
					this.element.append(this.htmlElement.audio);
					this.internal.audio.jq = $("#" + this.internal.audio.id);
				}

				// The HTML Video handlers
				if(this.html.video.available) {
					this._addHtmlEventListeners(this.htmlElement.video, this.html.video);
					this.element.append(this.htmlElement.video);
					this.internal.video.jq = $("#" + this.internal.video.id);
					if(this.status.nativeVideoControls) {
						this.internal.video.jq.css({'width': this.status.width, 'height': this.status.height});
					} else {
						this.internal.video.jq.css({'width':'0px', 'height':'0px'}); // Using size 0x0 since a .hide() causes issues in iOS
					}
					this.internal.video.jq.bind("click.jPlayer", function() {
						self._trigger($.jPlayer.event.click);
					});
				}
			}
			
			// Add the Aurora.js solution if being used.
			if(this.aurora.used) {
				// Aurora.js player need to be created for each media, see setMedia function.
			}

			// Create the bridge that emulates the HTML Media element on the jPlayer DIV
			if( this.options.emulateHtml ) {
				this._emulateHtmlBridge();
			}

			if((this.html.used || this.aurora.used) && !this.flash.used) { // If only HTML, then emulate flash ready() call after 100ms.
				setTimeout( function() {
					self.internal.ready = true;
					self.version.flash = "n/a";
					self._trigger($.jPlayer.event.repeat); // Trigger the repeat event so its handler can initialize itself with the loop option.
					self._trigger($.jPlayer.event.ready);
				}, 100);
			}

			// Initialize the interface components with the options.
			this._updateNativeVideoControls();
			// The other controls are now setup in _cssSelectorAncestor()
			if(this.css.jq.videoPlay.length) {
				this.css.jq.videoPlay.hide();
			}

			$.jPlayer.prototype.count++; // Change static variable via prototype.
		},
		destroy: function() {
			// MJP: The background change remains. Would need to store the original to restore it correctly.
			// MJP: The jPlayer element's size change remains.

			// Clear the media to reset the GUI and stop any downloads. Streams on some browsers had persited. (Chrome)
			this.clearMedia();
			// Remove the size/sizeFull cssClass from the cssSelectorAncestor
			this._removeUiClass();
			// Remove the times from the GUI
			if(this.css.jq.currentTime.length) {
				this.css.jq.currentTime.text("");
			}
			if(this.css.jq.duration.length) {
				this.css.jq.duration.text("");
			}
			// Remove any bindings from the interface controls.
			$.each(this.css.jq, function(fn, jq) {
				// Check selector is valid before trying to execute method.
				if(jq.length) {
					jq.unbind(".jPlayer");
				}
			});
			// Remove the click handlers for $.jPlayer.event.click
			this.internal.poster.jq.unbind(".jPlayer");
			if(this.internal.video.jq) {
				this.internal.video.jq.unbind(".jPlayer");
			}
			// Remove the fullscreen event handlers
			this._fullscreenRemoveEventListeners();
			// Remove key bindings
			if(this === $.jPlayer.focus) {
				$.jPlayer.focus = null;
			}
			// Destroy the HTML bridge.
			if(this.options.emulateHtml) {
				this._destroyHtmlBridge();
			}
			this.element.removeData("jPlayer"); // Remove jPlayer data
			this.element.unbind(".jPlayer"); // Remove all event handlers created by the jPlayer constructor
			this.element.empty(); // Remove the inserted child elements
			
			delete this.instances[this.internal.instance]; // Clear the instance on the static instance object
		},
		destroyRemoved: function() { // Destroy any instances that have gone away.
			var self = this;
			$.each(this.instances, function(i, element) {
				if(self.element !== element) { // Do not destroy this instance.
					if(!element.data("jPlayer")) { // Check that element is a real jPlayer.
						element.jPlayer("destroy");
						delete self.instances[i];
					}
				}
			});
		},
		enable: function() { // Plan to implement
			// options.disabled = false
		},
		disable: function () { // Plan to implement
			// options.disabled = true
		},
		_testCanPlayType: function(elem) {
			// IE9 on Win Server 2008 did not implement canPlayType(), but it has the property.
			try {
				elem.canPlayType(this.format.mp3.codec); // The type is irrelevant.
				return true;
			} catch(err) {
				return false;
			}
		},
		_testPlaybackRate: function(type) {
			// type: String 'audio' or 'video'
			var el, rate = 0.5;
			type = typeof type === 'string' ? type : 'audio';
			el = document.createElement(type);
			// Wrapping in a try/catch, just in case older HTML5 browsers throw and error.
			try {
				if('playbackRate' in el) {
					el.playbackRate = rate;
					return el.playbackRate === rate;
				} else {
					return false;
				}
			} catch(err) {
				return false;
			}
		},
		_uaBlocklist: function(list) {
			// list : object with properties that are all regular expressions. Property names are irrelevant.
			// Returns true if the user agent is matched in list.
			var	ua = navigator.userAgent.toLowerCase(),
				block = false;

			$.each(list, function(p, re) {
				if(re && re.test(ua)) {
					block = true;
					return false; // exit $.each.
				}
			});
			return block;
		},
		_restrictNativeVideoControls: function() {
			// Fallback to noFullWindow when nativeVideoControls is true and audio media is being used. Affects when both media types are used.
			if(this.require.audio) {
				if(this.status.nativeVideoControls) {
					this.status.nativeVideoControls = false;
					this.status.noFullWindow = true;
				}
			}
		},
		_updateNativeVideoControls: function() {
			if(this.html.video.available && this.html.used) {
				// Turn the HTML Video controls on/off
				this.htmlElement.video.controls = this.status.nativeVideoControls;
				// Show/hide the jPlayer GUI.
				this._updateAutohide();
				// For when option changed. The poster image is not updated, as it is dealt with in setMedia(). Acceptable degradation since seriously doubt these options will change on the fly. Can again review later.
				if(this.status.nativeVideoControls && this.require.video) {
					this.internal.poster.jq.hide();
					this.internal.video.jq.css({'width': this.status.width, 'height': this.status.height});
				} else if(this.status.waitForPlay && this.status.video) {
					this.internal.poster.jq.show();
					this.internal.video.jq.css({'width': '0px', 'height': '0px'});
				}
			}
		},
		_addHtmlEventListeners: function(mediaElement, entity) {
			var self = this;
			mediaElement.preload = this.options.preload;
			mediaElement.muted = this.options.muted;
			mediaElement.volume = this.options.volume;

			if(this.status.playbackRateEnabled) {
				mediaElement.defaultPlaybackRate = this.options.defaultPlaybackRate;
				mediaElement.playbackRate = this.options.playbackRate;
			}

			// Create the event listeners
			// Only want the active entity to affect jPlayer and bubble events.
			// Using entity.gate so that object is referenced and gate property always current
			
			mediaElement.addEventListener("progress", function() {
				if(entity.gate) {
					if(self.internal.cmdsIgnored && this.readyState > 0) { // Detect iOS executed the command
						self.internal.cmdsIgnored = false;
					}
					self._getHtmlStatus(mediaElement);
					self._updateInterface();
					self._trigger($.jPlayer.event.progress);
				}
			}, false);
			mediaElement.addEventListener("loadeddata", function() {
				if(entity.gate) {
					self.androidFix.setMedia = false; // Disable the fix after the first progress event.
					if(self.androidFix.play) { // Play Android audio - performing the fix.
						self.androidFix.play = false;
						self.play(self.androidFix.time);
					}
					if(self.androidFix.pause) { // Pause Android audio at time - performing the fix.
						self.androidFix.pause = false;
						self.pause(self.androidFix.time);
					}
					self._trigger($.jPlayer.event.loadeddata);
				}
			}, false);
			mediaElement.addEventListener("timeupdate", function() {
				if(entity.gate) {
					self._getHtmlStatus(mediaElement);
					self._updateInterface();
					self._trigger($.jPlayer.event.timeupdate);
				}
			}, false);
			mediaElement.addEventListener("durationchange", function() {
				if(entity.gate) {
					self._getHtmlStatus(mediaElement);
					self._updateInterface();
					self._trigger($.jPlayer.event.durationchange);
				}
			}, false);
			mediaElement.addEventListener("play", function() {
				if(entity.gate) {
					self._updateButtons(true);
					self._html_checkWaitForPlay(); // So the native controls update this variable and puts the hidden interface in the correct state. Affects toggling native controls.
					self._trigger($.jPlayer.event.play);
				}
			}, false);
			mediaElement.addEventListener("playing", function() {
				if(entity.gate) {
					self._updateButtons(true);
					self._seeked();
					self._trigger($.jPlayer.event.playing);
				}
			}, false);
			mediaElement.addEventListener("pause", function() {
				if(entity.gate) {
					self._updateButtons(false);
					self._trigger($.jPlayer.event.pause);
				}
			}, false);
			mediaElement.addEventListener("waiting", function() {
				if(entity.gate) {
					self._seeking();
					self._trigger($.jPlayer.event.waiting);
				}
			}, false);
			mediaElement.addEventListener("seeking", function() {
				if(entity.gate) {
					self._seeking();
					self._trigger($.jPlayer.event.seeking);
				}
			}, false);
			mediaElement.addEventListener("seeked", function() {
				if(entity.gate) {
					self._seeked();
					self._trigger($.jPlayer.event.seeked);
				}
			}, false);
			mediaElement.addEventListener("volumechange", function() {
				if(entity.gate) {
					// Read the values back from the element as the Blackberry PlayBook shares the volume with the physical buttons master volume control.
					// However, when tested 6th July 2011, those buttons do not generate an event. The physical play/pause button does though.
					self.options.volume = mediaElement.volume;
					self.options.muted = mediaElement.muted;
					self._updateMute();
					self._updateVolume();
					self._trigger($.jPlayer.event.volumechange);
				}
			}, false);
			mediaElement.addEventListener("ratechange", function() {
				if(entity.gate) {
					self.options.defaultPlaybackRate = mediaElement.defaultPlaybackRate;
					self.options.playbackRate = mediaElement.playbackRate;
					self._updatePlaybackRate();
					self._trigger($.jPlayer.event.ratechange);
				}
			}, false);
			mediaElement.addEventListener("suspend", function() { // Seems to be the only way of capturing that the iOS4 browser did not actually play the media from the page code. ie., It needs a user gesture.
				if(entity.gate) {
					self._seeked();
					self._trigger($.jPlayer.event.suspend);
				}
			}, false);
			mediaElement.addEventListener("ended", function() {
				if(entity.gate) {
					// Order of the next few commands are important. Change the time and then pause.
					// Solves a bug in Firefox, where issuing pause 1st causes the media to play from the start. ie., The pause is ignored.
					if(!$.jPlayer.browser.webkit) { // Chrome crashes if you do this in conjunction with a setMedia command in an ended event handler. ie., The playlist demo.
						self.htmlElement.media.currentTime = 0; // Safari does not care about this command. ie., It works with or without this line. (Both Safari and Chrome are Webkit.)
					}
					self.htmlElement.media.pause(); // Pause otherwise a click on the progress bar will play from that point, when it shouldn't, since it stopped playback.
					self._updateButtons(false);
					self._getHtmlStatus(mediaElement, true); // With override true. Otherwise Chrome leaves progress at full.
					self._updateInterface();
					self._trigger($.jPlayer.event.ended);
				}
			}, false);
			mediaElement.addEventListener("error", function() {
				if(entity.gate) {
					self._updateButtons(false);
					self._seeked();
					if(self.status.srcSet) { // Deals with case of clearMedia() causing an error event.
						clearTimeout(self.internal.htmlDlyCmdId); // Clears any delayed commands used in the HTML solution.
						self.status.waitForLoad = true; // Allows the load operation to try again.
						self.status.waitForPlay = true; // Reset since a play was captured.
						if(self.status.video && !self.status.nativeVideoControls) {
							self.internal.video.jq.css({'width':'0px', 'height':'0px'});
						}
						if(self._validString(self.status.media.poster) && !self.status.nativeVideoControls) {
							self.internal.poster.jq.show();
						}
						if(self.css.jq.videoPlay.length) {
							self.css.jq.videoPlay.show();
						}
						self._error( {
							type: $.jPlayer.error.URL,
							context: self.status.src, // this.src shows absolute urls. Want context to show the url given.
							message: $.jPlayer.errorMsg.URL,
							hint: $.jPlayer.errorHint.URL
						});
					}
				}
			}, false);
			// Create all the other event listeners that bubble up to a jPlayer event from html, without being used by jPlayer.
			$.each($.jPlayer.htmlEvent, function(i, eventType) {
				mediaElement.addEventListener(this, function() {
					if(entity.gate) {
						self._trigger($.jPlayer.event[eventType]);
					}
				}, false);
			});
		},
		_addAuroraEventListeners : function(player, entity) {
			var self = this;
			//player.preload = this.options.preload;
			//player.muted = this.options.muted;
			player.volume = this.options.volume * 100;

			// Create the event listeners
			// Only want the active entity to affect jPlayer and bubble events.
			// Using entity.gate so that object is referenced and gate property always current
			
			player.on("progress", function() {
				if(entity.gate) {
					if(self.internal.cmdsIgnored && this.readyState > 0) { // Detect iOS executed the command
						self.internal.cmdsIgnored = false;
					}
					self._getAuroraStatus(player);
					self._updateInterface();
					self._trigger($.jPlayer.event.progress);
					// Progress with song duration, we estimate timeupdate need to be triggered too.
					if (player.duration > 0) {
						self._trigger($.jPlayer.event.timeupdate);
					}
				}
			}, false);
			player.on("ready", function() {
				if(entity.gate) {
					self._trigger($.jPlayer.event.loadeddata);
				}
			}, false);
			player.on("duration", function() {
				if(entity.gate) {
					self._getAuroraStatus(player);
					self._updateInterface();
					self._trigger($.jPlayer.event.durationchange);
				}
			}, false);
			player.on("end", function() {
				if(entity.gate) {
					// Order of the next few commands are important. Change the time and then pause.
					self._updateButtons(false);
					self._getAuroraStatus(player, true);
					self._updateInterface();
					self._trigger($.jPlayer.event.ended);
				}
			}, false);
			player.on("error", function() {
				if(entity.gate) {
					self._updateButtons(false);
					self._seeked();
					if(self.status.srcSet) { // Deals with case of clearMedia() causing an error event.
						self.status.waitForLoad = true; // Allows the load operation to try again.
						self.status.waitForPlay = true; // Reset since a play was captured.
						if(self.status.video && !self.status.nativeVideoControls) {
							self.internal.video.jq.css({'width':'0px', 'height':'0px'});
						}
						if(self._validString(self.status.media.poster) && !self.status.nativeVideoControls) {
							self.internal.poster.jq.show();
						}
						if(self.css.jq.videoPlay.length) {
							self.css.jq.videoPlay.show();
						}
						self._error( {
							type: $.jPlayer.error.URL,
							context: self.status.src, // this.src shows absolute urls. Want context to show the url given.
							message: $.jPlayer.errorMsg.URL,
							hint: $.jPlayer.errorHint.URL
						});
					}
				}
			}, false);
		},
		_getHtmlStatus: function(media, override) {
			var ct = 0, cpa = 0, sp = 0, cpr = 0;

			// Fixes the duration bug in iOS, where the durationchange event occurs when media.duration is not always correct.
			// Fixes the initial duration bug in BB OS7, where the media.duration is infinity and displays as NaN:NaN due to Date() using inifity.
			if(isFinite(media.duration)) {
				this.status.duration = media.duration;
			}

			ct = media.currentTime;
			cpa = (this.status.duration > 0) ? 100 * ct / this.status.duration : 0;
			if((typeof media.seekable === "object") && (media.seekable.length > 0)) {
				sp = (this.status.duration > 0) ? 100 * media.seekable.end(media.seekable.length-1) / this.status.duration : 100;
				cpr = (this.status.duration > 0) ? 100 * media.currentTime / media.seekable.end(media.seekable.length-1) : 0; // Duration conditional for iOS duration bug. ie., seekable.end is a NaN in that case.
			} else {
				sp = 100;
				cpr = cpa;
			}
			
			if(override) {
				ct = 0;
				cpr = 0;
				cpa = 0;
			}

			this.status.seekPercent = sp;
			this.status.currentPercentRelative = cpr;
			this.status.currentPercentAbsolute = cpa;
			this.status.currentTime = ct;

			this.status.remaining = this.status.duration - this.status.currentTime;

			this.status.videoWidth = media.videoWidth;
			this.status.videoHeight = media.videoHeight;

			this.status.readyState = media.readyState;
			this.status.networkState = media.networkState;
			this.status.playbackRate = media.playbackRate;
			this.status.ended = media.ended;
		},
		_getAuroraStatus: function(player, override) {
			var ct = 0, cpa = 0, sp = 0, cpr = 0;

			this.status.duration = player.duration / 1000;

			ct = player.currentTime / 1000;
			cpa = (this.status.duration > 0) ? 100 * ct / this.status.duration : 0;
			if(player.buffered > 0) {
				sp = (this.status.duration > 0) ? (player.buffered * this.status.duration) / this.status.duration : 100;
				cpr = (this.status.duration > 0) ? ct / (player.buffered * this.status.duration) : 0;
			} else {
				sp = 100;
				cpr = cpa;
			}
			
			if(override) {
				ct = 0;
				cpr = 0;
				cpa = 0;
			}

			this.status.seekPercent = sp;
			this.status.currentPercentRelative = cpr;
			this.status.currentPercentAbsolute = cpa;
			this.status.currentTime = ct;

			this.status.remaining = this.status.duration - this.status.currentTime;

			this.status.readyState = 4; // status.readyState;
			this.status.networkState = 0; // status.networkState;
			this.status.playbackRate = 1; // status.playbackRate;
			this.status.ended = false; // status.ended;
		},
		_resetStatus: function() {
			this.status = $.extend({}, this.status, $.jPlayer.prototype.status); // Maintains the status properties that persist through a reset.
		},
		_trigger: function(eventType, error, warning) { // eventType always valid as called using $.jPlayer.event.eventType
			var event = $.Event(eventType);
			event.jPlayer = {};
			event.jPlayer.version = $.extend({}, this.version);
			event.jPlayer.options = $.extend(true, {}, this.options); // Deep copy
			event.jPlayer.status = $.extend(true, {}, this.status); // Deep copy
			event.jPlayer.html = $.extend(true, {}, this.html); // Deep copy
			event.jPlayer.aurora = $.extend(true, {}, this.aurora); // Deep copy
			event.jPlayer.flash = $.extend(true, {}, this.flash); // Deep copy
			if(error) {
				event.jPlayer.error = $.extend({}, error);
			}
			if(warning) {
				event.jPlayer.warning = $.extend({}, warning);
			}
			this.element.trigger(event);
		},
		jPlayerFlashEvent: function(eventType, status) { // Called from Flash
			if(eventType === $.jPlayer.event.ready) {
				if(!this.internal.ready) {
					this.internal.ready = true;
					this.internal.flash.jq.css({'width':'0px', 'height':'0px'}); // Once Flash generates the ready event, minimise to zero as it is not affected by wmode anymore.

					this.version.flash = status.version;
					if(this.version.needFlash !== this.version.flash) {
						this._error( {
							type: $.jPlayer.error.VERSION,
							context: this.version.flash,
							message: $.jPlayer.errorMsg.VERSION + this.version.flash,
							hint: $.jPlayer.errorHint.VERSION
						});
					}
					this._trigger($.jPlayer.event.repeat); // Trigger the repeat event so its handler can initialize itself with the loop option.
					this._trigger(eventType);
				} else {
					// This condition occurs if the Flash is hidden and then shown again.
					// Firefox also reloads the Flash if the CSS position changes. position:fixed is used for full screen.

					// Only do this if the Flash is the solution being used at the moment. Affects Media players where both solution may be being used.
					if(this.flash.gate) {

						// Send the current status to the Flash now that it is ready (available) again.
						if(this.status.srcSet) {

							// Need to read original status before issuing the setMedia command.
							var	currentTime = this.status.currentTime,
								paused = this.status.paused; 

							this.setMedia(this.status.media);
							this.volumeWorker(this.options.volume);
							if(currentTime > 0) {
								if(paused) {
									this.pause(currentTime);
								} else {
									this.play(currentTime);
								}
							}
						}
						this._trigger($.jPlayer.event.flashreset);
					}
				}
			}
			if(this.flash.gate) {
				switch(eventType) {
					case $.jPlayer.event.progress:
						this._getFlashStatus(status);
						this._updateInterface();
						this._trigger(eventType);
						break;
					case $.jPlayer.event.timeupdate:
						this._getFlashStatus(status);
						this._updateInterface();
						this._trigger(eventType);
						break;
					case $.jPlayer.event.play:
						this._seeked();
						this._updateButtons(true);
						this._trigger(eventType);
						break;
					case $.jPlayer.event.pause:
						this._updateButtons(false);
						this._trigger(eventType);
						break;
					case $.jPlayer.event.ended:
						this._updateButtons(false);
						this._trigger(eventType);
						break;
					case $.jPlayer.event.click:
						this._trigger(eventType); // This could be dealt with by the default
						break;
					case $.jPlayer.event.error:
						this.status.waitForLoad = true; // Allows the load operation to try again.
						this.status.waitForPlay = true; // Reset since a play was captured.
						if(this.status.video) {
							this.internal.flash.jq.css({'width':'0px', 'height':'0px'});
						}
						if(this._validString(this.status.media.poster)) {
							this.internal.poster.jq.show();
						}
						if(this.css.jq.videoPlay.length && this.status.video) {
							this.css.jq.videoPlay.show();
						}
						if(this.status.video) { // Set up for another try. Execute before error event.
							this._flash_setVideo(this.status.media);
						} else {
							this._flash_setAudio(this.status.media);
						}
						this._updateButtons(false);
						this._error( {
							type: $.jPlayer.error.URL,
							context:status.src,
							message: $.jPlayer.errorMsg.URL,
							hint: $.jPlayer.errorHint.URL
						});
						break;
					case $.jPlayer.event.seeking:
						this._seeking();
						this._trigger(eventType);
						break;
					case $.jPlayer.event.seeked:
						this._seeked();
						this._trigger(eventType);
						break;
					case $.jPlayer.event.ready:
						// The ready event is handled outside the switch statement.
						// Captured here otherwise 2 ready events would be generated if the ready event handler used setMedia.
						break;
					default:
						this._trigger(eventType);
				}
			}
			return false;
		},
		_getFlashStatus: function(status) {
			this.status.seekPercent = status.seekPercent;
			this.status.currentPercentRelative = status.currentPercentRelative;
			this.status.currentPercentAbsolute = status.currentPercentAbsolute;
			this.status.currentTime = status.currentTime;
			this.status.duration = status.duration;
			this.status.remaining = status.duration - status.currentTime;

			this.status.videoWidth = status.videoWidth;
			this.status.videoHeight = status.videoHeight;

			// The Flash does not generate this information in this release
			this.status.readyState = 4; // status.readyState;
			this.status.networkState = 0; // status.networkState;
			this.status.playbackRate = 1; // status.playbackRate;
			this.status.ended = false; // status.ended;
		},
		_updateButtons: function(playing) {
			if(playing === undefined) {
				playing = !this.status.paused;
			} else {
				this.status.paused = !playing;
			}
			// Apply the state classes. (For the useStateClassSkin:true option)
			if(playing) {
				this.addStateClass('playing');
			} else {
				this.removeStateClass('playing');
			}
			if(!this.status.noFullWindow && this.options.fullWindow) {
				this.addStateClass('fullScreen');
			} else {
				this.removeStateClass('fullScreen');
			}
			if(this.options.loop) {
				this.addStateClass('looped');
			} else {
				this.removeStateClass('looped');
			}
			// Toggle the GUI element pairs. (For the useStateClassSkin:false option)
			if(this.css.jq.play.length && this.css.jq.pause.length) {
				if(playing) {
					this.css.jq.play.hide();
					this.css.jq.pause.show();
				} else {
					this.css.jq.play.show();
					this.css.jq.pause.hide();
				}
			}
			if(this.css.jq.restoreScreen.length && this.css.jq.fullScreen.length) {
				if(this.status.noFullWindow) {
					this.css.jq.fullScreen.hide();
					this.css.jq.restoreScreen.hide();
				} else if(this.options.fullWindow) {
					this.css.jq.fullScreen.hide();
					this.css.jq.restoreScreen.show();
				} else {
					this.css.jq.fullScreen.show();
					this.css.jq.restoreScreen.hide();
				}
			}
			if(this.css.jq.repeat.length && this.css.jq.repeatOff.length) {
				if(this.options.loop) {
					this.css.jq.repeat.hide();
					this.css.jq.repeatOff.show();
				} else {
					this.css.jq.repeat.show();
					this.css.jq.repeatOff.hide();
				}
			}
		},
		_updateInterface: function() {
			if(this.css.jq.seekBar.length) {
				this.css.jq.seekBar.width(this.status.seekPercent+"%");
			}
			if(this.css.jq.playBar.length) {
				if(this.options.smoothPlayBar) {
					this.css.jq.playBar.stop().animate({
						width: this.status.currentPercentAbsolute+"%"
					}, 250, "linear");
				} else {
					this.css.jq.playBar.width(this.status.currentPercentRelative+"%");
				}
			}
			var currentTimeText = '';
			if(this.css.jq.currentTime.length) {
				currentTimeText = this._convertTime(this.status.currentTime);
				if(currentTimeText !== this.css.jq.currentTime.text()) {
					this.css.jq.currentTime.text(this._convertTime(this.status.currentTime));
				}
			}
			var durationText = '',
				duration = this.status.duration,
				remaining = this.status.remaining;
			if(this.css.jq.duration.length) {
				if(typeof this.status.media.duration === 'string') {
					durationText = this.status.media.duration;
				} else {
					if(typeof this.status.media.duration === 'number') {
						duration = this.status.media.duration;
						remaining = duration - this.status.currentTime;
					}
					if(this.options.remainingDuration) {
						durationText = (remaining > 0 ? '-' : '') + this._convertTime(remaining);
					} else {
						durationText = this._convertTime(duration);
					}
				}
				if(durationText !== this.css.jq.duration.text()) {
					this.css.jq.duration.text(durationText);
				}
			}
		},
		_convertTime: ConvertTime.prototype.time,
		_seeking: function() {
			if(this.css.jq.seekBar.length) {
				this.css.jq.seekBar.addClass("jp-seeking-bg");
			}
			this.addStateClass('seeking');
		},
		_seeked: function() {
			if(this.css.jq.seekBar.length) {
				this.css.jq.seekBar.removeClass("jp-seeking-bg");
			}
			this.removeStateClass('seeking');
		},
		_resetGate: function() {
			this.html.audio.gate = false;
			this.html.video.gate = false;
			this.aurora.gate = false;
			this.flash.gate = false;
		},
		_resetActive: function() {
			this.html.active = false;
			this.aurora.active = false;
			this.flash.active = false;
		},
		_escapeHtml: function(s) {
			return s.split('&').join('&amp;').split('<').join('&lt;').split('>').join('&gt;').split('"').join('&quot;');
		},
		_qualifyURL: function(url) {
			var el = document.createElement('div');
			el.innerHTML= '<a href="' + this._escapeHtml(url) + '">x</a>';
			return el.firstChild.href;
		},
		_absoluteMediaUrls: function(media) {
			var self = this;
			$.each(media, function(type, url) {
				if(url && self.format[type] && url.substr(0, 5) !== "data:") {
					media[type] = self._qualifyURL(url);
				}
			});
			return media;
		},
		addStateClass: function(state) {
			if(this.ancestorJq.length) {
				this.ancestorJq.addClass(this.options.stateClass[state]);
			}
		},
		removeStateClass: function(state) {
			if(this.ancestorJq.length) {
				this.ancestorJq.removeClass(this.options.stateClass[state]);
			}
		},
		setMedia: function(media) {
		
			/*	media[format] = String: URL of format. Must contain all of the supplied option's video or audio formats.
			 *	media.poster = String: Video poster URL.
			 *	media.track = Array: Of objects defining the track element: kind, src, srclang, label, def.
			 *	media.stream = Boolean: * NOT IMPLEMENTED * Designating actual media streams. ie., "false/undefined" for files. Plan to refresh the flash every so often.
			 */

			var	self = this,
				supported = false,
				posterChanged = this.status.media.poster !== media.poster; // Compare before reset. Important for OSX Safari as this.htmlElement.poster.src is absolute, even if original poster URL was relative.

			this._resetMedia();
			this._resetGate();
			this._resetActive();

			// Clear the Android Fix.
			this.androidFix.setMedia = false;
			this.androidFix.play = false;
			this.androidFix.pause = false;

			// Convert all media URLs to absolute URLs.
			media = this._absoluteMediaUrls(media);

			$.each(this.formats, function(formatPriority, format) {
				var isVideo = self.format[format].media === 'video';
				$.each(self.solutions, function(solutionPriority, solution) {
					if(self[solution].support[format] && self._validString(media[format])) { // Format supported in solution and url given for format.
						var isHtml = solution === 'html';
						var isAurora = solution === 'aurora';

						if(isVideo) {
							if(isHtml) {
								self.html.video.gate = true;
								self._html_setVideo(media);
								self.html.active = true;
							} else {
								self.flash.gate = true;
								self._flash_setVideo(media);
								self.flash.active = true;
							}
							if(self.css.jq.videoPlay.length) {
								self.css.jq.videoPlay.show();
							}
							self.status.video = true;
						} else {
							if(isHtml) {
								self.html.audio.gate = true;
								self._html_setAudio(media);
								self.html.active = true;

								// Setup the Android Fix - Only for HTML audio.
								if($.jPlayer.platform.android) {
									self.androidFix.setMedia = true;
								}
							} else if(isAurora) {
								self.aurora.gate = true;
								self._aurora_setAudio(media);
								self.aurora.active = true;
							} else {
								self.flash.gate = true;
								self._flash_setAudio(media);
								self.flash.active = true;
							}
							if(self.css.jq.videoPlay.length) {
								self.css.jq.videoPlay.hide();
							}
							self.status.video = false;
						}
						
						supported = true;
						return false; // Exit $.each
					}
				});
				if(supported) {
					return false; // Exit $.each
				}
			});

			if(supported) {
				if(!(this.status.nativeVideoControls && this.html.video.gate)) {
					// Set poster IMG if native video controls are not being used
					// Note: With IE the IMG onload event occurs immediately when cached.
					// Note: Poster hidden by default in _resetMedia()
					if(this._validString(media.poster)) {
						if(posterChanged) { // Since some browsers do not generate img onload event.
							this.htmlElement.poster.src = media.poster;
						} else {
							this.internal.poster.jq.show();
						}
					}
				}
				if(typeof media.title === 'string') {
					if(this.css.jq.title.length) {
						this.css.jq.title.html(media.title);
					}
					if(this.htmlElement.audio) {
						this.htmlElement.audio.setAttribute('title', media.title);
					}
					if(this.htmlElement.video) {
						this.htmlElement.video.setAttribute('title', media.title);
					}
				}
				this.status.srcSet = true;
				this.status.media = $.extend({}, media);
				this._updateButtons(false);
				this._updateInterface();
				this._trigger($.jPlayer.event.setmedia);
			} else { // jPlayer cannot support any formats provided in this browser
				// Send an error event
				this._error( {
					type: $.jPlayer.error.NO_SUPPORT,
					context: "{supplied:'" + this.options.supplied + "'}",
					message: $.jPlayer.errorMsg.NO_SUPPORT,
					hint: $.jPlayer.errorHint.NO_SUPPORT
				});
			}
		},
		_resetMedia: function() {
			this._resetStatus();
			this._updateButtons(false);
			this._updateInterface();
			this._seeked();
			this.internal.poster.jq.hide();

			clearTimeout(this.internal.htmlDlyCmdId);

			if(this.html.active) {
				this._html_resetMedia();
			} else if(this.aurora.active) {
				this._aurora_resetMedia();
			} else if(this.flash.active) {
				this._flash_resetMedia();
			}
		},
		clearMedia: function() {
			this._resetMedia();

			if(this.html.active) {
				this._html_clearMedia();
			} else if(this.aurora.active) {
				this._aurora_clearMedia();
			} else if(this.flash.active) {
				this._flash_clearMedia();
			}

			this._resetGate();
			this._resetActive();
		},
		load: function() {
			if(this.status.srcSet) {
				if(this.html.active) {
					this._html_load();
				} else if(this.aurora.active) {
					this._aurora_load();
				} else if(this.flash.active) {
					this._flash_load();
				}
			} else {
				this._urlNotSetError("load");
			}
		},
		focus: function() {
			if(this.options.keyEnabled) {
				$.jPlayer.focus = this;
			}
		},
		play: function(time) {
			var guiAction = typeof time === "object"; // Flags GUI click events so we know this was not a direct command, but an action taken by the user on the GUI.
			if(guiAction && this.options.useStateClassSkin && !this.status.paused) {
				this.pause(time); // The time would be the click event, but passing it over so info is not lost.
			} else {
				time = (typeof time === "number") ? time : NaN; // Remove jQuery event from click handler
				if(this.status.srcSet) {
					this.focus();
					if(this.html.active) {
						this._html_play(time);
					} else if(this.aurora.active) {
						this._aurora_play(time);
					} else if(this.flash.active) {
						this._flash_play(time);
					}
				} else {
					this._urlNotSetError("play");
				}
			}
		},
		videoPlay: function() { // Handles clicks on the play button over the video poster
			this.play();
		},
		pause: function(time) {
			time = (typeof time === "number") ? time : NaN; // Remove jQuery event from click handler
			if(this.status.srcSet) {
				if(this.html.active) {
					this._html_pause(time);
				} else if(this.aurora.active) {
					this._aurora_pause(time);
				} else if(this.flash.active) {
					this._flash_pause(time);
				}
			} else {
				this._urlNotSetError("pause");
			}
		},
		tellOthers: function(command, conditions) {
			var self = this,
				hasConditions = typeof conditions === 'function',
				args = Array.prototype.slice.call(arguments); // Convert arguments to an Array.

			if(typeof command !== 'string') { // Ignore, since no command.
				return; // Return undefined to maintain chaining.
			}
			if(hasConditions) {
				args.splice(1, 1); // Remove the conditions from the arguments
			}

			$.jPlayer.prototype.destroyRemoved();
			$.each(this.instances, function() {
				// Remember that "this" is the instance's "element" in the $.each() loop.
				if(self.element !== this) { // Do not tell my instance.
					if(!hasConditions || conditions.call(this.data("jPlayer"), self)) {
						this.jPlayer.apply(this, args);
					}
				}
			});
		},
		pauseOthers: function(time) {
			this.tellOthers("pause", function() {
				// In the conditions function, the "this" context is the other instance's jPlayer object.
				return this.status.srcSet;
			}, time);
		},
		stop: function() {
			if(this.status.srcSet) {
				if(this.html.active) {
					this._html_pause(0);
				} else if(this.aurora.active) {
					this._aurora_pause(0);
				} else if(this.flash.active) {
					this._flash_pause(0);
				}
			} else {
				this._urlNotSetError("stop");
			}
		},
		playHead: function(p) {
			p = this._limitValue(p, 0, 100);
			if(this.status.srcSet) {
				if(this.html.active) {
					this._html_playHead(p);
				} else if(this.aurora.active) {
					this._aurora_playHead(p);
				} else if(this.flash.active) {
					this._flash_playHead(p);
				}
			} else {
				this._urlNotSetError("playHead");
			}
		},
		_muted: function(muted) {
			this.mutedWorker(muted);
			if(this.options.globalVolume) {
				this.tellOthers("mutedWorker", function() {
					// Check the other instance has global volume enabled.
					return this.options.globalVolume;
				}, muted);
			}
		},
		mutedWorker: function(muted) {
			this.options.muted = muted;
			if(this.html.used) {
				this._html_setProperty('muted', muted);
			}
			if(this.aurora.used) {
				this._aurora_mute(muted);
			}
			if(this.flash.used) {
				this._flash_mute(muted);
			}

			// The HTML solution generates this event from the media element itself.
			if(!this.html.video.gate && !this.html.audio.gate) {
				this._updateMute(muted);
				this._updateVolume(this.options.volume);
				this._trigger($.jPlayer.event.volumechange);
			}
		},
		mute: function(mute) { // mute is either: undefined (true), an event object (true) or a boolean (muted).
			var guiAction = typeof mute === "object"; // Flags GUI click events so we know this was not a direct command, but an action taken by the user on the GUI.
			if(guiAction && this.options.useStateClassSkin && this.options.muted) {
				this._muted(false);
			} else {
				mute = mute === undefined ? true : !!mute;
				this._muted(mute);
			}
		},
		unmute: function(unmute) { // unmute is either: undefined (true), an event object (true) or a boolean (!muted).
			unmute = unmute === undefined ? true : !!unmute;
			this._muted(!unmute);
		},
		_updateMute: function(mute) {
			if(mute === undefined) {
				mute = this.options.muted;
			}
			if(mute) {
				this.addStateClass('muted');
			} else {
				this.removeStateClass('muted');
			}
			if(this.css.jq.mute.length && this.css.jq.unmute.length) {
				if(this.status.noVolume) {
					this.css.jq.mute.hide();
					this.css.jq.unmute.hide();
				} else if(mute) {
					this.css.jq.mute.hide();
					this.css.jq.unmute.show();
				} else {
					this.css.jq.mute.show();
					this.css.jq.unmute.hide();
				}
			}
		},
		volume: function(v) {
			this.volumeWorker(v);
			if(this.options.globalVolume) {
				this.tellOthers("volumeWorker", function() {
					// Check the other instance has global volume enabled.
					return this.options.globalVolume;
				}, v);
			}
		},
		volumeWorker: function(v) {
			v = this._limitValue(v, 0, 1);
			this.options.volume = v;

			if(this.html.used) {
				this._html_setProperty('volume', v);
			}
			if(this.aurora.used) {
				this._aurora_volume(v);
			}
			if(this.flash.used) {
				this._flash_volume(v);
			}

			// The HTML solution generates this event from the media element itself.
			if(!this.html.video.gate && !this.html.audio.gate) {
				this._updateVolume(v);
				this._trigger($.jPlayer.event.volumechange);
			}
		},
		volumeBar: function(e) { // Handles clicks on the volumeBar
			if(this.css.jq.volumeBar.length) {
				// Using $(e.currentTarget) to enable multiple volume bars
				var $bar = $(e.currentTarget),
					offset = $bar.offset(),
					x = e.pageX - offset.left,
					w = $bar.width(),
					y = $bar.height() - e.pageY + offset.top,
					h = $bar.height();
				if(this.options.verticalVolume) {
					this.volume(y/h);
				} else {
					this.volume(x/w);
				}
			}
			if(this.options.muted) {
				this._muted(false);
			}
		},
		_updateVolume: function(v) {
			if(v === undefined) {
				v = this.options.volume;
			}
			v = this.options.muted ? 0 : v;

			if(this.status.noVolume) {
				this.addStateClass('noVolume');
				if(this.css.jq.volumeBar.length) {
					this.css.jq.volumeBar.hide();
				}
				if(this.css.jq.volumeBarValue.length) {
					this.css.jq.volumeBarValue.hide();
				}
				if(this.css.jq.volumeMax.length) {
					this.css.jq.volumeMax.hide();
				}
			} else {
				this.removeStateClass('noVolume');
				if(this.css.jq.volumeBar.length) {
					this.css.jq.volumeBar.show();
				}
				if(this.css.jq.volumeBarValue.length) {
					this.css.jq.volumeBarValue.show();
					this.css.jq.volumeBarValue[this.options.verticalVolume ? "height" : "width"]((v*100)+"%");
				}
				if(this.css.jq.volumeMax.length) {
					this.css.jq.volumeMax.show();
				}
			}
		},
		volumeMax: function() { // Handles clicks on the volume max
			this.volume(1);
			if(this.options.muted) {
				this._muted(false);
			}
		},
		_cssSelectorAncestor: function(ancestor) {
			var self = this;
			this.options.cssSelectorAncestor = ancestor;
			this._removeUiClass();
			this.ancestorJq = ancestor ? $(ancestor) : []; // Would use $() instead of [], but it is only 1.4+
			if(ancestor && this.ancestorJq.length !== 1) { // So empty strings do not generate the warning.
				this._warning( {
					type: $.jPlayer.warning.CSS_SELECTOR_COUNT,
					context: ancestor,
					message: $.jPlayer.warningMsg.CSS_SELECTOR_COUNT + this.ancestorJq.length + " found for cssSelectorAncestor.",
					hint: $.jPlayer.warningHint.CSS_SELECTOR_COUNT
				});
			}
			this._addUiClass();
			$.each(this.options.cssSelector, function(fn, cssSel) {
				self._cssSelector(fn, cssSel);
			});

			// Set the GUI to the current state.
			this._updateInterface();
			this._updateButtons();
			this._updateAutohide();
			this._updateVolume();
			this._updateMute();
		},
		_cssSelector: function(fn, cssSel) {
			var self = this;
			if(typeof cssSel === 'string') {
				if($.jPlayer.prototype.options.cssSelector[fn]) {
					if(this.css.jq[fn] && this.css.jq[fn].length) {
						this.css.jq[fn].unbind(".jPlayer");
					}
					this.options.cssSelector[fn] = cssSel;
					this.css.cs[fn] = this.options.cssSelectorAncestor + " " + cssSel;

					if(cssSel) { // Checks for empty string
						this.css.jq[fn] = $(this.css.cs[fn]);
					} else {
						this.css.jq[fn] = []; // To comply with the css.jq[fn].length check before its use. As of jQuery 1.4 could have used $() for an empty set. 
					}

					if(this.css.jq[fn].length && this[fn]) {
						var handler = function(e) {
							e.preventDefault();
							self[fn](e);
							if(self.options.autoBlur) {
								$(this).blur();
							} else {
								$(this).focus(); // Force focus for ARIA.
							}
						};
						this.css.jq[fn].bind("click.jPlayer", handler); // Using jPlayer namespace
					}

					if(cssSel && this.css.jq[fn].length !== 1) { // So empty strings do not generate the warning. ie., they just remove the old one.
						this._warning( {
							type: $.jPlayer.warning.CSS_SELECTOR_COUNT,
							context: this.css.cs[fn],
							message: $.jPlayer.warningMsg.CSS_SELECTOR_COUNT + this.css.jq[fn].length + " found for " + fn + " method.",
							hint: $.jPlayer.warningHint.CSS_SELECTOR_COUNT
						});
					}
				} else {
					this._warning( {
						type: $.jPlayer.warning.CSS_SELECTOR_METHOD,
						context: fn,
						message: $.jPlayer.warningMsg.CSS_SELECTOR_METHOD,
						hint: $.jPlayer.warningHint.CSS_SELECTOR_METHOD
					});
				}
			} else {
				this._warning( {
					type: $.jPlayer.warning.CSS_SELECTOR_STRING,
					context: cssSel,
					message: $.jPlayer.warningMsg.CSS_SELECTOR_STRING,
					hint: $.jPlayer.warningHint.CSS_SELECTOR_STRING
				});
			}
		},
		duration: function(e) {
			if(this.options.toggleDuration) {
				if(this.options.captureDuration) {
					e.stopPropagation();
				}
				this._setOption("remainingDuration", !this.options.remainingDuration);
			}
		},
		seekBar: function(e) { // Handles clicks on the seekBar
			if(this.css.jq.seekBar.length) {
				// Using $(e.currentTarget) to enable multiple seek bars
				var $bar = $(e.currentTarget),
					offset = $bar.offset(),
					x = e.pageX - offset.left,
					w = $bar.width(),
					p = 100 * x / w;
				this.playHead(p);
			}
		},
		playbackRate: function(pbr) {
			this._setOption("playbackRate", pbr);
		},
		playbackRateBar: function(e) { // Handles clicks on the playbackRateBar
			if(this.css.jq.playbackRateBar.length) {
				// Using $(e.currentTarget) to enable multiple playbackRate bars
				var $bar = $(e.currentTarget),
					offset = $bar.offset(),
					x = e.pageX - offset.left,
					w = $bar.width(),
					y = $bar.height() - e.pageY + offset.top,
					h = $bar.height(),
					ratio, pbr;
				if(this.options.verticalPlaybackRate) {
					ratio = y/h;
				} else {
					ratio = x/w;
				}
				pbr = ratio * (this.options.maxPlaybackRate - this.options.minPlaybackRate) + this.options.minPlaybackRate;
				this.playbackRate(pbr);
			}
		},
		_updatePlaybackRate: function() {
			var pbr = this.options.playbackRate,
				ratio = (pbr - this.options.minPlaybackRate) / (this.options.maxPlaybackRate - this.options.minPlaybackRate);
			if(this.status.playbackRateEnabled) {
				if(this.css.jq.playbackRateBar.length) {
					this.css.jq.playbackRateBar.show();
				}
				if(this.css.jq.playbackRateBarValue.length) {
					this.css.jq.playbackRateBarValue.show();
					this.css.jq.playbackRateBarValue[this.options.verticalPlaybackRate ? "height" : "width"]((ratio*100)+"%");
				}
			} else {
				if(this.css.jq.playbackRateBar.length) {
					this.css.jq.playbackRateBar.hide();
				}
				if(this.css.jq.playbackRateBarValue.length) {
					this.css.jq.playbackRateBarValue.hide();
				}
			}
		},
		repeat: function(event) { // Handle clicks on the repeat button
			var guiAction = typeof event === "object"; // Flags GUI click events so we know this was not a direct command, but an action taken by the user on the GUI.
			if(guiAction && this.options.useStateClassSkin && this.options.loop) {
				this._loop(false);
			} else {
				this._loop(true);
			}
		},
		repeatOff: function() { // Handle clicks on the repeatOff button
			this._loop(false);
		},
		_loop: function(loop) {
			if(this.options.loop !== loop) {
				this.options.loop = loop;
				this._updateButtons();
				this._trigger($.jPlayer.event.repeat);
			}
		},

		// Options code adapted from ui.widget.js (1.8.7).  Made changes so the key can use dot notation. To match previous getData solution in jPlayer 1.
		option: function(key, value) {
			var options = key;

			 // Enables use: options().  Returns a copy of options object
			if ( arguments.length === 0 ) {
				return $.extend( true, {}, this.options );
			}

			if(typeof key === "string") {
				var keys = key.split(".");

				 // Enables use: options("someOption")  Returns a copy of the option. Supports dot notation.
				if(value === undefined) {

					var opt = $.extend(true, {}, this.options);
					for(var i = 0; i < keys.length; i++) {
						if(opt[keys[i]] !== undefined) {
							opt = opt[keys[i]];
						} else {
							this._warning( {
								type: $.jPlayer.warning.OPTION_KEY,
								context: key,
								message: $.jPlayer.warningMsg.OPTION_KEY,
								hint: $.jPlayer.warningHint.OPTION_KEY
							});
							return undefined;
						}
					}
					return opt;
				}

				 // Enables use: options("someOptionObject", someObject}).  Creates: {someOptionObject:someObject}
				 // Enables use: options("someOption", someValue).  Creates: {someOption:someValue}
				 // Enables use: options("someOptionObject.someOption", someValue).  Creates: {someOptionObject:{someOption:someValue}}

				options = {};
				var opts = options;

				for(var j = 0; j < keys.length; j++) {
					if(j < keys.length - 1) {
						opts[keys[j]] = {};
						opts = opts[keys[j]];
					} else {
						opts[keys[j]] = value;
					}
				}
			}

			 // Otherwise enables use: options(optionObject).  Uses original object (the key)

			this._setOptions(options);

			return this;
		},
		_setOptions: function(options) {
			var self = this;
			$.each(options, function(key, value) { // This supports the 2 level depth that the options of jPlayer has. Would review if we ever need more depth.
				self._setOption(key, value);
			});

			return this;
		},
		_setOption: function(key, value) {
			var self = this;

			// The ability to set options is limited at this time.

			switch(key) {
				case "volume" :
					this.volume(value);
					break;
				case "muted" :
					this._muted(value);
					break;
				case "globalVolume" :
					this.options[key] = value;
					break;
				case "cssSelectorAncestor" :
					this._cssSelectorAncestor(value); // Set and refresh all associations for the new ancestor.
					break;
				case "cssSelector" :
					$.each(value, function(fn, cssSel) {
						self._cssSelector(fn, cssSel); // NB: The option is set inside this function, after further validity checks.
					});
					break;
				case "playbackRate" :
					this.options[key] = value = this._limitValue(value, this.options.minPlaybackRate, this.options.maxPlaybackRate);
					if(this.html.used) {
						this._html_setProperty('playbackRate', value);
					}
					this._updatePlaybackRate();
					break;
				case "defaultPlaybackRate" :
					this.options[key] = value = this._limitValue(value, this.options.minPlaybackRate, this.options.maxPlaybackRate);
					if(this.html.used) {
						this._html_setProperty('defaultPlaybackRate', value);
					}
					this._updatePlaybackRate();
					break;
				case "minPlaybackRate" :
					this.options[key] = value = this._limitValue(value, 0.1, this.options.maxPlaybackRate - 0.1);
					this._updatePlaybackRate();
					break;
				case "maxPlaybackRate" :
					this.options[key] = value = this._limitValue(value, this.options.minPlaybackRate + 0.1, 16);
					this._updatePlaybackRate();
					break;
				case "fullScreen" :
					if(this.options[key] !== value) { // if changed
						var wkv = $.jPlayer.nativeFeatures.fullscreen.used.webkitVideo;
						if(!wkv || wkv && !this.status.waitForPlay) {
							if(!wkv) { // No sensible way to unset option on these devices.
								this.options[key] = value;
							}
							if(value) {
								this._requestFullscreen();
							} else {
								this._exitFullscreen();
							}
							if(!wkv) {
								this._setOption("fullWindow", value);
							}
						}
					}
					break;
				case "fullWindow" :
					if(this.options[key] !== value) { // if changed
						this._removeUiClass();
						this.options[key] = value;
						this._refreshSize();
					}
					break;
				case "size" :
					if(!this.options.fullWindow && this.options[key].cssClass !== value.cssClass) {
						this._removeUiClass();
					}
					this.options[key] = $.extend({}, this.options[key], value); // store a merged copy of it, incase not all properties changed.
					this._refreshSize();
					break;
				case "sizeFull" :
					if(this.options.fullWindow && this.options[key].cssClass !== value.cssClass) {
						this._removeUiClass();
					}
					this.options[key] = $.extend({}, this.options[key], value); // store a merged copy of it, incase not all properties changed.
					this._refreshSize();
					break;
				case "autohide" :
					this.options[key] = $.extend({}, this.options[key], value); // store a merged copy of it, incase not all properties changed.
					this._updateAutohide();
					break;
				case "loop" :
					this._loop(value);
					break;
				case "remainingDuration" :
					this.options[key] = value;
					this._updateInterface();
					break;
				case "toggleDuration" :
					this.options[key] = value;
					break;
				case "nativeVideoControls" :
					this.options[key] = $.extend({}, this.options[key], value); // store a merged copy of it, incase not all properties changed.
					this.status.nativeVideoControls = this._uaBlocklist(this.options.nativeVideoControls);
					this._restrictNativeVideoControls();
					this._updateNativeVideoControls();
					break;
				case "noFullWindow" :
					this.options[key] = $.extend({}, this.options[key], value); // store a merged copy of it, incase not all properties changed.
					this.status.nativeVideoControls = this._uaBlocklist(this.options.nativeVideoControls); // Need to check again as noFullWindow can depend on this flag and the restrict() can override it.
					this.status.noFullWindow = this._uaBlocklist(this.options.noFullWindow);
					this._restrictNativeVideoControls();
					this._updateButtons();
					break;
				case "noVolume" :
					this.options[key] = $.extend({}, this.options[key], value); // store a merged copy of it, incase not all properties changed.
					this.status.noVolume = this._uaBlocklist(this.options.noVolume);
					this._updateVolume();
					this._updateMute();
					break;
				case "emulateHtml" :
					if(this.options[key] !== value) { // To avoid multiple event handlers being created, if true already.
						this.options[key] = value;
						if(value) {
							this._emulateHtmlBridge();
						} else {
							this._destroyHtmlBridge();
						}
					}
					break;
				case "timeFormat" :
					this.options[key] = $.extend({}, this.options[key], value); // store a merged copy of it, incase not all properties changed.
					break;
				case "keyEnabled" :
					this.options[key] = value;
					if(!value && this === $.jPlayer.focus) {
						$.jPlayer.focus = null;
					}
					break;
				case "keyBindings" :
					this.options[key] = $.extend(true, {}, this.options[key], value); // store a merged DEEP copy of it, incase not all properties changed.
					break;
				case "audioFullScreen" :
					this.options[key] = value;
					break;
				case "autoBlur" :
					this.options[key] = value;
					break;
			}

			return this;
		},
		// End of: (Options code adapted from ui.widget.js)

		_refreshSize: function() {
			this._setSize(); // update status and jPlayer element size
			this._addUiClass(); // update the ui class
			this._updateSize(); // update internal sizes
			this._updateButtons();
			this._updateAutohide();
			this._trigger($.jPlayer.event.resize);
		},
		_setSize: function() {
			// Determine the current size from the options
			if(this.options.fullWindow) {
				this.status.width = this.options.sizeFull.width;
				this.status.height = this.options.sizeFull.height;
				this.status.cssClass = this.options.sizeFull.cssClass;
			} else {
				this.status.width = this.options.size.width;
				this.status.height = this.options.size.height;
				this.status.cssClass = this.options.size.cssClass;
			}

			// Set the size of the jPlayer area.
			this.element.css({'width': this.status.width, 'height': this.status.height});
		},
		_addUiClass: function() {
			if(this.ancestorJq.length) {
				this.ancestorJq.addClass(this.status.cssClass);
			}
		},
		_removeUiClass: function() {
			if(this.ancestorJq.length) {
				this.ancestorJq.removeClass(this.status.cssClass);
			}
		},
		_updateSize: function() {
			// The poster uses show/hide so can simply resize it.
			this.internal.poster.jq.css({'width': this.status.width, 'height': this.status.height});

			// Video html or flash resized if necessary at this time, or if native video controls being used.
			if(!this.status.waitForPlay && this.html.active && this.status.video || this.html.video.available && this.html.used && this.status.nativeVideoControls) {
				this.internal.video.jq.css({'width': this.status.width, 'height': this.status.height});
			}
			else if(!this.status.waitForPlay && this.flash.active && this.status.video) {
				this.internal.flash.jq.css({'width': this.status.width, 'height': this.status.height});
			}
		},
		_updateAutohide: function() {
			var	self = this,
				event = "mousemove.jPlayer",
				namespace = ".jPlayerAutohide",
				eventType = event + namespace,
				handler = function(event) {
					var moved = false,
						deltaX, deltaY;
					if(typeof self.internal.mouse !== "undefined") {
						//get the change from last position to this position
						deltaX = self.internal.mouse.x - event.pageX;
						deltaY = self.internal.mouse.y - event.pageY;
						moved = (Math.floor(deltaX) > 0) || (Math.floor(deltaY)>0); 
					} else {
						moved = true;
					}
					// store current position for next method execution
					self.internal.mouse = {
							x : event.pageX,
							y : event.pageY
					};
					// if mouse has been actually moved, do the gui fadeIn/fadeOut
					if (moved) {
						self.css.jq.gui.fadeIn(self.options.autohide.fadeIn, function() {
							clearTimeout(self.internal.autohideId);
							self.internal.autohideId = setTimeout( function() {
								self.css.jq.gui.fadeOut(self.options.autohide.fadeOut);
							}, self.options.autohide.hold);
						});
					}
				};

			if(this.css.jq.gui.length) {

				// End animations first so that its callback is executed now.
				// Otherwise an in progress fadeIn animation still has the callback to fadeOut again.
				this.css.jq.gui.stop(true, true);

				// Removes the fadeOut operation from the fadeIn callback.
				clearTimeout(this.internal.autohideId);
				// undefine mouse
				delete this.internal.mouse;

				this.element.unbind(namespace);
				this.css.jq.gui.unbind(namespace);

				if(!this.status.nativeVideoControls) {
					if(this.options.fullWindow && this.options.autohide.full || !this.options.fullWindow && this.options.autohide.restored) {
						this.element.bind(eventType, handler);
						this.css.jq.gui.bind(eventType, handler);
						this.css.jq.gui.hide();
					} else {
						this.css.jq.gui.show();
					}
				} else {
					this.css.jq.gui.hide();
				}
			}
		},
		fullScreen: function(event) {
			var guiAction = typeof event === "object"; // Flags GUI click events so we know this was not a direct command, but an action taken by the user on the GUI.
			if(guiAction && this.options.useStateClassSkin && this.options.fullScreen) {
				this._setOption("fullScreen", false);
			} else {
				this._setOption("fullScreen", true);
			}
		},
		restoreScreen: function() {
			this._setOption("fullScreen", false);
		},
		_fullscreenAddEventListeners: function() {
			var self = this,
				fs = $.jPlayer.nativeFeatures.fullscreen;

			if(fs.api.fullscreenEnabled) {
				if(fs.event.fullscreenchange) {
					// Create the event handler function and store it for removal.
					if(typeof this.internal.fullscreenchangeHandler !== 'function') {
						this.internal.fullscreenchangeHandler = function() {
							self._fullscreenchange();
						};
					}
					document.addEventListener(fs.event.fullscreenchange, this.internal.fullscreenchangeHandler, false);
				}
				// No point creating handler for fullscreenerror.
				// Either logic avoids fullscreen occurring (w3c/moz), or their is no event on the browser (webkit).
			}
		},
		_fullscreenRemoveEventListeners: function() {
			var fs = $.jPlayer.nativeFeatures.fullscreen;
			if(this.internal.fullscreenchangeHandler) {
				document.removeEventListener(fs.event.fullscreenchange, this.internal.fullscreenchangeHandler, false);
			}
		},
		_fullscreenchange: function() {
			// If nothing is fullscreen, then we cannot be in fullscreen mode.
			if(this.options.fullScreen && !$.jPlayer.nativeFeatures.fullscreen.api.fullscreenElement()) {
				this._setOption("fullScreen", false);
			}
		},
		_requestFullscreen: function() {
			// Either the container or the jPlayer div
			var e = this.ancestorJq.length ? this.ancestorJq[0] : this.element[0],
				fs = $.jPlayer.nativeFeatures.fullscreen;

			// This method needs the video element. For iOS and Android.
			if(fs.used.webkitVideo) {
				e = this.htmlElement.video;
			}

			if(fs.api.fullscreenEnabled) {
				fs.api.requestFullscreen(e);
			}
		},
		_exitFullscreen: function() {

			var fs = $.jPlayer.nativeFeatures.fullscreen,
				e;

			// This method needs the video element. For iOS and Android.
			if(fs.used.webkitVideo) {
				e = this.htmlElement.video;
			}

			if(fs.api.fullscreenEnabled) {
				fs.api.exitFullscreen(e);
			}
		},
		_html_initMedia: function(media) {
			// Remove any existing track elements
			var $media = $(this.htmlElement.media).empty();

			// Create any track elements given with the media, as an Array of track Objects.
			$.each(media.track || [], function(i,v) {
				var track = document.createElement('track');
				track.setAttribute("kind", v.kind ? v.kind : "");
				track.setAttribute("src", v.src ? v.src : "");
				track.setAttribute("srclang", v.srclang ? v.srclang : "");
				track.setAttribute("label", v.label ? v.label : "");
				if(v.def) {
					track.setAttribute("default", v.def);
				}
				$media.append(track);
			});

			this.htmlElement.media.src = this.status.src;

			if(this.options.preload !== 'none') {
				this._html_load(); // See function for comments
			}
			this._trigger($.jPlayer.event.timeupdate); // The flash generates this event for its solution.
		},
		_html_setFormat: function(media) {
			var self = this;
			// Always finds a format due to checks in setMedia()
			$.each(this.formats, function(priority, format) {
				if(self.html.support[format] && media[format]) {
					self.status.src = media[format];
					self.status.format[format] = true;
					self.status.formatType = format;
					return false;
				}
			});
		},
		_html_setAudio: function(media) {
			this._html_setFormat(media);
			this.htmlElement.media = this.htmlElement.audio;
			this._html_initMedia(media);
		},
		_html_setVideo: function(media) {
			this._html_setFormat(media);
			if(this.status.nativeVideoControls) {
				this.htmlElement.video.poster = this._validString(media.poster) ? media.poster : "";
			}
			this.htmlElement.media = this.htmlElement.video;
			this._html_initMedia(media);
		},
		_html_resetMedia: function() {
			if(this.htmlElement.media) {
				if(this.htmlElement.media.id === this.internal.video.id && !this.status.nativeVideoControls) {
					this.internal.video.jq.css({'width':'0px', 'height':'0px'});
				}
				this.htmlElement.media.pause();
			}
		},
		_html_clearMedia: function() {
			if(this.htmlElement.media) {
				this.htmlElement.media.src = "about:blank";
				// The following load() is only required for Firefox 3.6 (PowerMacs).
				// Recent HTMl5 browsers only require the src change. Due to changes in W3C spec and load() effect.
				this.htmlElement.media.load(); // Stops an old, "in progress" download from continuing the download. Triggers the loadstart, error and emptied events, due to the empty src. Also an abort event if a download was in progress.
			}
		},
		_html_load: function() {
			// This function remains to allow the early HTML5 browsers to work, such as Firefox 3.6
			// A change in the W3C spec for the media.load() command means that this is no longer necessary.
			// This command should be removed and actually causes minor undesirable effects on some browsers. Such as loading the whole file and not only the metadata.
			if(this.status.waitForLoad) {
				this.status.waitForLoad = false;
				this.htmlElement.media.load();
			}
			clearTimeout(this.internal.htmlDlyCmdId);
		},
		_html_play: function(time) {
			var self = this,
				media = this.htmlElement.media;

			this.androidFix.pause = false; // Cancel the pause fix.

			this._html_load(); // Loads if required and clears any delayed commands.

			// Setup the Android Fix.
			if(this.androidFix.setMedia) {
				this.androidFix.play = true;
				this.androidFix.time = time;

			} else if(!isNaN(time)) {

				// Attempt to play it, since iOS has been ignoring commands
				if(this.internal.cmdsIgnored) {
					media.play();
				}

				try {
					// !media.seekable is for old HTML5 browsers, like Firefox 3.6.
					// Checking seekable.length is important for iOS6 to work with setMedia().play(time)
					if(!media.seekable || typeof media.seekable === "object" && media.seekable.length > 0) {
						media.currentTime = time;
						media.play();
					} else {
						throw 1;
					}
				} catch(err) {
					this.internal.htmlDlyCmdId = setTimeout(function() {
						self.play(time);
					}, 250);
					return; // Cancel execution and wait for the delayed command.
				}
			} else {
				media.play();
			}
			this._html_checkWaitForPlay();
		},
		_html_pause: function(time) {
			var self = this,
				media = this.htmlElement.media;

			this.androidFix.play = false; // Cancel the play fix.

			if(time > 0) { // We do not want the stop() command, which does pause(0), causing a load operation.
				this._html_load(); // Loads if required and clears any delayed commands.
			} else {
				clearTimeout(this.internal.htmlDlyCmdId);
			}

			// Order of these commands is important for Safari (Win) and IE9. Pause then change currentTime.
			media.pause();

			// Setup the Android Fix.
			if(this.androidFix.setMedia) {
				this.androidFix.pause = true;
				this.androidFix.time = time;

			} else if(!isNaN(time)) {
				try {
					if(!media.seekable || typeof media.seekable === "object" && media.seekable.length > 0) {
						media.currentTime = time;
					} else {
						throw 1;
					}
				} catch(err) {
					this.internal.htmlDlyCmdId = setTimeout(function() {
						self.pause(time);
					}, 250);
					return; // Cancel execution and wait for the delayed command.
				}
			}
			if(time > 0) { // Avoids a setMedia() followed by stop() or pause(0) hiding the video play button.
				this._html_checkWaitForPlay();
			}
		},
		_html_playHead: function(percent) {
			var self = this,
				media = this.htmlElement.media;

			this._html_load(); // Loads if required and clears any delayed commands.

			// This playHead() method needs a refactor to apply the android fix.

			try {
				if(typeof media.seekable === "object" && media.seekable.length > 0) {
					media.currentTime = percent * media.seekable.end(media.seekable.length-1) / 100;
				} else if(media.duration > 0 && !isNaN(media.duration)) {
					media.currentTime = percent * media.duration / 100;
				} else {
					throw "e";
				}
			} catch(err) {
				this.internal.htmlDlyCmdId = setTimeout(function() {
					self.playHead(percent);
				}, 250);
				return; // Cancel execution and wait for the delayed command.
			}
			if(!this.status.waitForLoad) {
				this._html_checkWaitForPlay();
			}
		},
		_html_checkWaitForPlay: function() {
			if(this.status.waitForPlay) {
				this.status.waitForPlay = false;
				if(this.css.jq.videoPlay.length) {
					this.css.jq.videoPlay.hide();
				}
				if(this.status.video) {
					this.internal.poster.jq.hide();
					this.internal.video.jq.css({'width': this.status.width, 'height': this.status.height});
				}
			}
		},
		_html_setProperty: function(property, value) {
			if(this.html.audio.available) {
				this.htmlElement.audio[property] = value;
			}
			if(this.html.video.available) {
				this.htmlElement.video[property] = value;
			}
		},
		_aurora_setAudio: function(media) {
			var self = this;            
			
			// Always finds a format due to checks in setMedia()
			$.each(this.formats, function(priority, format) {
				if(self.aurora.support[format] && media[format]) {
					self.status.src = media[format];
					self.status.format[format] = true;
					self.status.formatType = format;
			
					return false;
				}
			});
			
			this.aurora.player = new AV.Player.fromURL(this.status.src);
			this._addAuroraEventListeners(this.aurora.player, this.aurora);

			if(this.options.preload === 'auto') {
				this._aurora_load();
				this.status.waitForLoad = false;
			}
		},
		_aurora_resetMedia: function() {
			if (this.aurora.player) {
				this.aurora.player.stop();
			}
		},
		_aurora_clearMedia: function() {
			// Nothing to clear.
		},
		_aurora_load: function() {
			if(this.status.waitForLoad) {
				this.status.waitForLoad = false;
				this.aurora.player.preload();
			}
		},
		_aurora_play: function(time) {
			if (!this.status.waitForLoad) {
				if (!isNaN(time)) {
					this.aurora.player.seek(time);
				}
			}
			if (!this.aurora.player.playing) {
				this.aurora.player.play();
			}
			this.status.waitForLoad = false;
			this._aurora_checkWaitForPlay();
			
			// No event from the player, update UI now.
			this._updateButtons(true);
			this._trigger($.jPlayer.event.play);
		},
		_aurora_pause: function(time) {
			if (!isNaN(time)) {
				this.aurora.player.seek(time * 1000);
			}
			this.aurora.player.pause();
			
			if(time > 0) { // Avoids a setMedia() followed by stop() or pause(0) hiding the video play button.
				this._aurora_checkWaitForPlay();
			}
			
			// No event from the player, update UI now.
			this._updateButtons(false);
			this._trigger($.jPlayer.event.pause);
		},
		_aurora_playHead: function(percent) {
			if(this.aurora.player.duration > 0) {
				// The seek() sould be in milliseconds, but the only codec that works with seek (aac.js) uses seconds.
				this.aurora.player.seek(percent * this.aurora.player.duration / 100); // Using seconds
			}
				
			if(!this.status.waitForLoad) {
				this._aurora_checkWaitForPlay();
			}
		},
		_aurora_checkWaitForPlay: function() {
			if(this.status.waitForPlay) {
				this.status.waitForPlay = false;
			}
		},
		_aurora_volume: function(v) {
			this.aurora.player.volume = v * 100;
		},
		_aurora_mute: function(m) {
			if (m) {
				this.aurora.properties.lastvolume = this.aurora.player.volume;
				this.aurora.player.volume = 0;
			} else {
				this.aurora.player.volume = this.aurora.properties.lastvolume;
			}
			this.aurora.properties.muted = m;
		},
		_flash_setAudio: function(media) {
			var self = this;
			try {
				// Always finds a format due to checks in setMedia()
				$.each(this.formats, function(priority, format) {
					if(self.flash.support[format] && media[format]) {
						switch (format) {
							case "m4a" :
							case "fla" :
								self._getMovie().fl_setAudio_m4a(media[format]);
								break;
							case "mp3" :
								self._getMovie().fl_setAudio_mp3(media[format]);
								break;
							case "rtmpa":
								self._getMovie().fl_setAudio_rtmp(media[format]);
								break;
						}
						self.status.src = media[format];
						self.status.format[format] = true;
						self.status.formatType = format;
						return false;
					}
				});

				if(this.options.preload === 'auto') {
					this._flash_load();
					this.status.waitForLoad = false;
				}
			} catch(err) { this._flashError(err); }
		},
		_flash_setVideo: function(media) {
			var self = this;
			try {
				// Always finds a format due to checks in setMedia()
				$.each(this.formats, function(priority, format) {
					if(self.flash.support[format] && media[format]) {
						switch (format) {
							case "m4v" :
							case "flv" :
								self._getMovie().fl_setVideo_m4v(media[format]);
								break;
							case "rtmpv":
								self._getMovie().fl_setVideo_rtmp(media[format]);
								break;		
						}
						self.status.src = media[format];
						self.status.format[format] = true;
						self.status.formatType = format;
						return false;
					}
				});

				if(this.options.preload === 'auto') {
					this._flash_load();
					this.status.waitForLoad = false;
				}
			} catch(err) { this._flashError(err); }
		},
		_flash_resetMedia: function() {
			this.internal.flash.jq.css({'width':'0px', 'height':'0px'}); // Must do via CSS as setting attr() to zero causes a jQuery error in IE.
			this._flash_pause(NaN);
		},
		_flash_clearMedia: function() {
			try {
				this._getMovie().fl_clearMedia();
			} catch(err) { this._flashError(err); }
		},
		_flash_load: function() {
			try {
				this._getMovie().fl_load();
			} catch(err) { this._flashError(err); }
			this.status.waitForLoad = false;
		},
		_flash_play: function(time) {
			try {
				this._getMovie().fl_play(time);
			} catch(err) { this._flashError(err); }
			this.status.waitForLoad = false;
			this._flash_checkWaitForPlay();
		},
		_flash_pause: function(time) {
			try {
				this._getMovie().fl_pause(time);
			} catch(err) { this._flashError(err); }
			if(time > 0) { // Avoids a setMedia() followed by stop() or pause(0) hiding the video play button.
				this.status.waitForLoad = false;
				this._flash_checkWaitForPlay();
			}
		},
		_flash_playHead: function(p) {
			try {
				this._getMovie().fl_play_head(p);
			} catch(err) { this._flashError(err); }
			if(!this.status.waitForLoad) {
				this._flash_checkWaitForPlay();
			}
		},
		_flash_checkWaitForPlay: function() {
			if(this.status.waitForPlay) {
				this.status.waitForPlay = false;
				if(this.css.jq.videoPlay.length) {
					this.css.jq.videoPlay.hide();
				}
				if(this.status.video) {
					this.internal.poster.jq.hide();
					this.internal.flash.jq.css({'width': this.status.width, 'height': this.status.height});
				}
			}
		},
		_flash_volume: function(v) {
			try {
				this._getMovie().fl_volume(v);
			} catch(err) { this._flashError(err); }
		},
		_flash_mute: function(m) {
			try {
				this._getMovie().fl_mute(m);
			} catch(err) { this._flashError(err); }
		},
		_getMovie: function() {
			return document[this.internal.flash.id];
		},
		_getFlashPluginVersion: function() {

			// _getFlashPluginVersion() code influenced by:
			// - FlashReplace 1.01: http://code.google.com/p/flashreplace/
			// - SWFObject 2.2: http://code.google.com/p/swfobject/

			var version = 0,
				flash;
			if(window.ActiveXObject) {
				try {
					flash = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
					if (flash) { // flash will return null when ActiveX is disabled
						var v = flash.GetVariable("$version");
						if(v) {
							v = v.split(" ")[1].split(",");
							version = parseInt(v[0], 10) + "." + parseInt(v[1], 10);
						}
					}
				} catch(e) {}
			}
			else if(navigator.plugins && navigator.mimeTypes.length > 0) {
				flash = navigator.plugins["Shockwave Flash"];
				if(flash) {
					version = navigator.plugins["Shockwave Flash"].description.replace(/.*\s(\d+\.\d+).*/, "$1");
				}
			}
			return version * 1; // Converts to a number
		},
		_checkForFlash: function (version) {
			var flashOk = false;
			if(this._getFlashPluginVersion() >= version) {
				flashOk = true;
			}
			return flashOk;
		},
		_validString: function(url) {
			return (url && typeof url === "string"); // Empty strings return false
		},
		_limitValue: function(value, min, max) {
			return (value < min) ? min : ((value > max) ? max : value);
		},
		_urlNotSetError: function(context) {
			this._error( {
				type: $.jPlayer.error.URL_NOT_SET,
				context: context,
				message: $.jPlayer.errorMsg.URL_NOT_SET,
				hint: $.jPlayer.errorHint.URL_NOT_SET
			});
		},
		_flashError: function(error) {
			var errorType;
			if(!this.internal.ready) {
				errorType = "FLASH";
			} else {
				errorType = "FLASH_DISABLED";
			}
			this._error( {
				type: $.jPlayer.error[errorType],
				context: this.internal.flash.swf,
				message: $.jPlayer.errorMsg[errorType] + error.message,
				hint: $.jPlayer.errorHint[errorType]
			});
			// Allow the audio player to recover if display:none and then shown again, or with position:fixed on Firefox.
			// This really only affects audio in a media player, as an audio player could easily move the jPlayer element away from such issues.
			this.internal.flash.jq.css({'width':'1px', 'height':'1px'});
		},
		_error: function(error) {
			this._trigger($.jPlayer.event.error, error);
			if(this.options.errorAlerts) {
				this._alert("Error!" + (error.message ? "\n" + error.message : "") + (error.hint ? "\n" + error.hint : "") + "\nContext: " + error.context);
			}
		},
		_warning: function(warning) {
			this._trigger($.jPlayer.event.warning, undefined, warning);
			if(this.options.warningAlerts) {
				this._alert("Warning!" + (warning.message ? "\n" + warning.message : "") + (warning.hint ? "\n" + warning.hint : "") + "\nContext: " + warning.context);
			}
		},
		_alert: function(message) {
			var msg = "jPlayer " + this.version.script + " : id='" + this.internal.self.id +"' : " + message;
			if(!this.options.consoleAlerts) {
				alert(msg);
			} else if(window.console && window.console.log) {
				window.console.log(msg);
			}
		},
		_emulateHtmlBridge: function() {
			var self = this;

			// Emulate methods on jPlayer's DOM element.
			$.each( $.jPlayer.emulateMethods.split(/\s+/g), function(i, name) {
				self.internal.domNode[name] = function(arg) {
					self[name](arg);
				};

			});

			// Bubble jPlayer events to its DOM element.
			$.each($.jPlayer.event, function(eventName,eventType) {
				var nativeEvent = true;
				$.each( $.jPlayer.reservedEvent.split(/\s+/g), function(i, name) {
					if(name === eventName) {
						nativeEvent = false;
						return false;
					}
				});
				if(nativeEvent) {
					self.element.bind(eventType + ".jPlayer.jPlayerHtml", function() { // With .jPlayer & .jPlayerHtml namespaces.
						self._emulateHtmlUpdate();
						var domEvent = document.createEvent("Event");
						domEvent.initEvent(eventName, false, true);
						self.internal.domNode.dispatchEvent(domEvent);
					});
				}
				// The error event would require a special case
			});

			// IE9 has a readyState property on all elements. The document should have it, but all (except media) elements inherit it in IE9. This conflicts with Popcorn, which polls the readyState.
		},
		_emulateHtmlUpdate: function() {
			var self = this;

			$.each( $.jPlayer.emulateStatus.split(/\s+/g), function(i, name) {
				self.internal.domNode[name] = self.status[name];
			});
			$.each( $.jPlayer.emulateOptions.split(/\s+/g), function(i, name) {
				self.internal.domNode[name] = self.options[name];
			});
		},
		_destroyHtmlBridge: function() {
			var self = this;

			// Bridge event handlers are also removed by destroy() through .jPlayer namespace.
			this.element.unbind(".jPlayerHtml"); // Remove all event handlers created by the jPlayer bridge. So you can change the emulateHtml option.

			// Remove the methods and properties
			var emulated = $.jPlayer.emulateMethods + " " + $.jPlayer.emulateStatus + " " + $.jPlayer.emulateOptions;
			$.each( emulated.split(/\s+/g), function(i, name) {
				delete self.internal.domNode[name];
			});
		}
	};

	$.jPlayer.error = {
		FLASH: "e_flash",
		FLASH_DISABLED: "e_flash_disabled",
		NO_SOLUTION: "e_no_solution",
		NO_SUPPORT: "e_no_support",
		URL: "e_url",
		URL_NOT_SET: "e_url_not_set",
		VERSION: "e_version"
	};

	$.jPlayer.errorMsg = {
		FLASH: "jPlayer's Flash fallback is not configured correctly, or a command was issued before the jPlayer Ready event. Details: ", // Used in: _flashError()
		FLASH_DISABLED: "jPlayer's Flash fallback has been disabled by the browser due to the CSS rules you have used. Details: ", // Used in: _flashError()
		NO_SOLUTION: "No solution can be found by jPlayer in this browser. Neither HTML nor Flash can be used.", // Used in: _init()
		NO_SUPPORT: "It is not possible to play any media format provided in setMedia() on this browser using your current options.", // Used in: setMedia()
		URL: "Media URL could not be loaded.", // Used in: jPlayerFlashEvent() and _addHtmlEventListeners()
		URL_NOT_SET: "Attempt to issue media playback commands, while no media url is set.", // Used in: load(), play(), pause(), stop() and playHead()
		VERSION: "jPlayer " + $.jPlayer.prototype.version.script + " needs Jplayer.swf version " + $.jPlayer.prototype.version.needFlash + " but found " // Used in: jPlayerReady()
	};

	$.jPlayer.errorHint = {
		FLASH: "Check your swfPath option and that Jplayer.swf is there.",
		FLASH_DISABLED: "Check that you have not display:none; the jPlayer entity or any ancestor.",
		NO_SOLUTION: "Review the jPlayer options: support and supplied.",
		NO_SUPPORT: "Video or audio formats defined in the supplied option are missing.",
		URL: "Check media URL is valid.",
		URL_NOT_SET: "Use setMedia() to set the media URL.",
		VERSION: "Update jPlayer files."
	};

	$.jPlayer.warning = {
		CSS_SELECTOR_COUNT: "e_css_selector_count",
		CSS_SELECTOR_METHOD: "e_css_selector_method",
		CSS_SELECTOR_STRING: "e_css_selector_string",
		OPTION_KEY: "e_option_key"
	};

	$.jPlayer.warningMsg = {
		CSS_SELECTOR_COUNT: "The number of css selectors found did not equal one: ",
		CSS_SELECTOR_METHOD: "The methodName given in jPlayer('cssSelector') is not a valid jPlayer method.",
		CSS_SELECTOR_STRING: "The methodCssSelector given in jPlayer('cssSelector') is not a String or is empty.",
		OPTION_KEY: "The option requested in jPlayer('option') is undefined."
	};

	$.jPlayer.warningHint = {
		CSS_SELECTOR_COUNT: "Check your css selector and the ancestor.",
		CSS_SELECTOR_METHOD: "Check your method name.",
		CSS_SELECTOR_STRING: "Check your css selector is a string.",
		OPTION_KEY: "Check your option name."
	};
}));
;
///<reference path="~/Scripts/jquery-2.1.1.intellisense.js"/>

///<summary>
/// proper $(element).zIndex()
///</summary>
jQuery.fn.extend({

		zIndex: function (zIndex) {
			if (zIndex !== undefined) {
				return this.css("zIndex", zIndex);
			}

			if (this.length) {
				var elem = $(this[0]), position, value;
				while (elem.length && elem[0] !== document) {
					// Ignore z-index if position is set to a value where z-index is ignored by the browser
					// This makes behavior of this function consistent across browsers
					// WebKit always returns auto if the element is positioned
					position = elem.css("position");
					if (position === "absolute" || position === "relative" || position === "fixed") {
						// IE returns 0 when zIndex is not specified
						// other browsers return a string
						// we ignore the case of nested elements with an explicit value of 0
						// <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
						value = parseInt(elem.css("zIndex"), 10);
						if (!isNaN(value) && value !== 0) {
							return value;
						}
					}
					elem = elem.parent();
				}
			}

			return 0;
		}

	});;
/// <reference path="~/Scripts/jquery-2.1.1.js" />

///<summary> 
/// traverse all present components z-index 
/// min constraints by wrapper
///</summary>

jQuery.fn.extend({
	getMinZ: function () {
		return Math.min.apply(null, jQuery(this).map(function (e) {
			var z;
			return isNaN(z = parseInt(jQuery(this).css("z-index"), 10)) ? 0 : z;
		}).get());
	},
	getMaxZ: function () {
		return Math.max.apply(null, jQuery(this).map(function () {
			var z;
			return isNaN(z = parseInt(jQuery(this).css("z-index"), 10)) ? 0 : z;
		}).get());
	}
});

var LayeringConstraints = function(element){

		var constraints = {
			
			min: function(){
				return $(UI.getConfigurationValue(WRAPPER)).getMinZ();	
			},
			max: function(){
				return  $(UI.getConfigurationValue(WRAPPER), $(element)).getMaxZ();					
			}
		}

		this.min = constraints.min();
		this.max = constraints.max();

		return constraints;

	};
/// <reference path="~/Scripts/knockout-3.0.0.debug.js"/>
/// <reference path="jquery.zIndex.js"/>
/// <reference path="~/Content/Editor/js/layering/layeringConstraints.js" />


/// <summary> LayeringController wires component property z-index with UI </summary>
/// <param name="component"> Component instance with or without 'z-index' property set</param>

ko.extenders.stringNumeric = function (target, precision) {
	var result = ko.dependentObservable({
		read: function () {
			return target().toString();
		},
		write: target
	});

	result.raw = target;
	return result;
};

var LayeringController = function (component) {
	"use strict";

	var self = this;

	//data-bind="text: zIndex"
	this.zIndex = ko.observable(0).extend({stringNumeric: 1});	 

	//data-bind="style: layeringStyle"
	this.layeringStyle = ko.computed(function(value) {
		return { zIndex: this.zIndex() }
	}.bind(this));

	//get callback to be called with new values set (z-index changed)
	this.zIndex.subscribe(function (newValue) {		
		if (this.component !== null) {
			
				this.updateToComponent(self.component);
				this.updateElement();			
			
		}
	}.bind(this));


	this.bringFront = function () {
		var z = this.zIndex();
		this.zIndex(++z);
	}.bind(this);

	this.sendBack = function() {	
		var z = this.zIndex();
		this.zIndex(--z);
	}.bind(this);

	this.setZIndex = function (z) {
	    this.zIndex(z);
	}.bind(this);
	//sync vm to component
	this.updateToComponent = function (component)
		{			
		try {
			if (component !== null) {
				component.setProperty('z-index', self.zIndex(), true);			
				var property = component.getProperty('z-index');
				if (property != null) {
					property.componentId = component.name;					
					property.controlId = component.id;
					property.propertyId = "66ffaa9b-e079-4f6f-8495-a2b7db3ef7a2";
					property.group = "style";
					property.type = "common";									
				}


			}
		} finally {


		}
	}

	//sync component to vm 
	//it force adds component property and value
	this.updateFromComponent = function (component) {

		try {

			var value = component.getProperty('z-index');

			if (value !== null) {
			
					self.zIndex(parseInt(value.value,10));
			
			}

		} catch (e) {
			self.updateToComponent(component);

		} 
	}


	this.updateElement = function() {
		if (this.component !== null) {
			var element = $(this.component.getUISelector());
			if (element.length > 0) {
				element.zIndex(self.zIndex());

				console.log("element z-index:" + $(element).zIndex());

			}
		}
	};


	this.bindElement = function (element) {

		if ($(element).length > 0) {
			$(element).data('bind', 'style: layeringStyle');
			ko.observable($(element)[0]).extend({ applyBindings: self });
		}

	};

	if (component !== null) {
		//updating component...
		this.component = component;

		this.layeringConstraints = new LayeringConstraints(this.component.getUISelector());

		this.updateFromComponent(component);
		this.updateToComponent(component);

	}

};


















;
// Bind Twitter Tooltip

ko.bindingHandlers.tooltip = {
	update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
		var $element, options, tooltip;
		options = ko.utils.unwrapObservable(valueAccessor());
		$element = $(element);

		// If the title is an observable, make it auto-updating.
		if (ko.isObservable(options.title)) {
			var isToolTipVisible = false;

			$element.on('show.bs.tooltip', function () {
				isToolTipVisible = true;
			});
			$element.on('hide.bs.tooltip', function () {
				isToolTipVisible = false;
			});

			// "true" is the bootstrap default.
			var origAnimation = options.animation || true;
			options.title.subscribe(function () {
				if (isToolTipVisible) {
					$element.data('bs.tooltip').options.animation = false; // temporarily disable animation to avoid flickering of the tooltip
					$element.tooltip('fixTitle') // call this method to update the title
						.tooltip('show');
					$element.data('bs.tooltip').options.animation = origAnimation;
				}
			});
		}

		tooltip = $element.data('bs.tooltip');
		if (tooltip) {
			$.extend(tooltip.options, options);
		} else {
			$element.tooltip(options);
		}
	}
};

ko.bindingHandlers.editable_tooltip = {
    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
        var $element = $(element),
            options = ko.utils.unwrapObservable(valueAccessor());
        if (UI.tooltips) {
            if (UI.getSetting("isadmin")) {
                $element.bind("dblclick",
                    function (e) {
                        e.stopPropagation();

                        //hide other editable-tooltip
                        $(".editable-tooltip").popover("destroy");

                        //create popover
                        var template = Helpers.loadServiceTemplate("tooltip-management-template");
                        PopoverHelper.bind($element, template, options.key, '');

                        //get popover
                        var popover = $('.popover.' + options.key);

                        //fix top
                        var newTop = parseInt(popover.css('top')) - 18;
                        popover.css('top', newTop + 'px');

                        //fix z-index
                        popover.css('z-index', 99999);

                        //bind ko
                        var newViewModel = UI.tooltips.getViewModel({ key: options.key, name: options.name, element: $element });
                        if (newViewModel) {
                            ko.cleanNode(popover[0]);
                            ko.applyBindings(newViewModel, popover[0]);

                            popover.draggable();
                            popover.click(
                                function(e) {
                                    e.stopPropagation();
                                });
                        } else {
                            popover.popover("destroy");
                        }
                    });
            }
            $element.tooltip({ title: UI.tooltips.data[options.key], placement: options.placement });
        }
    }
};;
// knockout-sortable 0.9.2 | (c) 2014 Ryan Niemeyer |  http://www.opensource.org/licenses/mit-license
;(function(factory) {
    if (typeof define === "function" && define.amd) {
        // AMD anonymous module
        define(["knockout", "jquery", "jquery.ui.sortable"], factory);
    } else {
        // No module loader (plain <script> tag) - put directly in global namespace
        factory(window.ko, jQuery);
    }
})(function(ko, $) {
    var ITEMKEY = "ko_sortItem",
        INDEXKEY = "ko_sourceIndex",
        LISTKEY = "ko_sortList",
        PARENTKEY = "ko_parentList",
        DRAGKEY = "ko_dragItem",
        unwrap = ko.utils.unwrapObservable,
        dataGet = ko.utils.domData.get,
        dataSet = ko.utils.domData.set,
        version = $.ui && $.ui.version,
        //1.8.24 included a fix for how events were triggered in nested sortables. indexOf checks will fail if version starts with that value (0 vs. -1)
        hasNestedSortableFix = version && version.indexOf("1.6.") && version.indexOf("1.7.") && (version.indexOf("1.8.") || version === "1.8.24");

    //internal afterRender that adds meta-data to children
    var addMetaDataAfterRender = function(elements, data) {
        ko.utils.arrayForEach(elements, function(element) {
            if (element.nodeType === 1) {
                dataSet(element, ITEMKEY, data);
                dataSet(element, PARENTKEY, dataGet(element.parentNode, LISTKEY));
            }
        });
    };

    //prepare the proper options for the template binding
    var prepareTemplateOptions = function(valueAccessor, dataName) {
        var result = {},
            options = unwrap(valueAccessor()) || {},
            actualAfterRender;

        //build our options to pass to the template engine
        if (options.data) {
            result[dataName] = options.data;
            result.name = options.template;
        } else {
            result[dataName] = valueAccessor();
        }

        ko.utils.arrayForEach(["afterAdd", "afterRender", "as", "beforeRemove", "includeDestroyed", "templateEngine", "templateOptions"], function (option) {
            result[option] = options[option] || ko.bindingHandlers.sortable[option];
        });

        //use an afterRender function to add meta-data
        if (dataName === "foreach") {
            if (result.afterRender) {
                //wrap the existing function, if it was passed
                actualAfterRender = result.afterRender;
                result.afterRender = function(element, data) {
                    addMetaDataAfterRender.call(data, element, data);
                    actualAfterRender.call(data, element, data);
                };
            } else {
                result.afterRender = addMetaDataAfterRender;
            }
        }

        //return options to pass to the template binding
        return result;
    };

    var updateIndexFromDestroyedItems = function(index, items) {
        var unwrapped = unwrap(items);

        if (unwrapped) {
            for (var i = 0; i < index; i++) {
                //add one for every destroyed item we find before the targetIndex in the target array
                if (unwrapped[i] && unwrap(unwrapped[i]._destroy)) {
                    index++;
                }
            }
        }

        return index;
    };

    //remove problematic leading/trailing whitespace from templates
    var stripTemplateWhitespace = function(element, name) {
        var templateSource,
            templateElement;

        //process named templates
        if (name) {
            templateElement = document.getElementById(name);
            if (templateElement) {
                templateSource = new ko.templateSources.domElement(templateElement);
                templateSource.text($.trim(templateSource.text()));
            }
        }
        else {
            //remove leading/trailing non-elements from anonymous templates
            $(element).contents().each(function() {
                if (this && this.nodeType !== 1) {
                    element.removeChild(this);
                }
            });
        }
    };

    //connect items with observableArrays
    ko.bindingHandlers.sortable = {
        init: function(element, valueAccessor, allBindingsAccessor, data, context) {
            var $element = $(element),
                value = unwrap(valueAccessor()) || {},
                templateOptions = prepareTemplateOptions(valueAccessor, "foreach"),
                sortable = {},
                startActual, updateActual;

            stripTemplateWhitespace(element, templateOptions.name);

            //build a new object that has the global options with overrides from the binding
            $.extend(true, sortable, ko.bindingHandlers.sortable);
            if (value.options && sortable.options) {
                ko.utils.extend(sortable.options, value.options);
                delete value.options;
            }
            ko.utils.extend(sortable, value);

            //if allowDrop is an observable or a function, then execute it in a computed observable
            if (sortable.connectClass && (ko.isObservable(sortable.allowDrop) || typeof sortable.allowDrop == "function")) {
                ko.computed({
                    read: function() {
                        var value = unwrap(sortable.allowDrop),
                            shouldAdd = typeof value == "function" ? value.call(this, templateOptions.foreach) : value;
                        ko.utils.toggleDomNodeCssClass(element, sortable.connectClass, shouldAdd);
                    },
                    disposeWhenNodeIsRemoved: element
                }, this);
            } else {
                ko.utils.toggleDomNodeCssClass(element, sortable.connectClass, sortable.allowDrop);
            }

            //wrap the template binding
            ko.bindingHandlers.template.init(element, function() { return templateOptions; }, allBindingsAccessor, data, context);

            //keep a reference to start/update functions that might have been passed in
            startActual = sortable.options.start;
            updateActual = sortable.options.update;

            //initialize sortable binding after template binding has rendered in update function
            var createTimeout = setTimeout(function() {
                var dragItem;
                $element.sortable(ko.utils.extend(sortable.options, {
                    start: function(event, ui) {
                        //track original index
                        var el = ui.item[0];
                        dataSet(el, INDEXKEY, ko.utils.arrayIndexOf(ui.item.parent().children(), el));

                        //make sure that fields have a chance to update model
                        ui.item.find("input:focus").change();
                        if (startActual) {
                            startActual.apply(this, arguments);
                        }
                    },
                    receive: function(event, ui) {
                        dragItem = dataGet(ui.item[0], DRAGKEY);
                        if (dragItem) {
                            //copy the model item, if a clone option is provided
                            if (dragItem.clone) {
                                dragItem = dragItem.clone();
                            }

                            //configure a handler to potentially manipulate item before drop
                            if (sortable.dragged) {
                                dragItem = sortable.dragged.call(this, dragItem, event, ui) || dragItem;
                            }
                        }
                    },
                    update: function(event, ui) {
                        var sourceParent, targetParent, sourceIndex, targetIndex, arg,
                            el = ui.item[0],
                            parentEl = ui.item.parent()[0],
                            item = dataGet(el, ITEMKEY) || dragItem;

                        dragItem = null;

                        //make sure that moves only run once, as update fires on multiple containers
                        if (item && (this === parentEl) || (!hasNestedSortableFix && $.contains(this, parentEl))) {
                            //identify parents
                            sourceParent = dataGet(el, PARENTKEY);
                            sourceIndex = dataGet(el, INDEXKEY);
                            targetParent = dataGet(el.parentNode, LISTKEY);
                            targetIndex = ko.utils.arrayIndexOf(ui.item.parent().children(), el);

                            //take destroyed items into consideration
                            if (!templateOptions.includeDestroyed) {
                                sourceIndex = updateIndexFromDestroyedItems(sourceIndex, sourceParent);
                                targetIndex = updateIndexFromDestroyedItems(targetIndex, targetParent);
                            }

                            //build up args for the callbacks
                            if (sortable.beforeMove || sortable.afterMove) {
                                arg = {
                                    item: item,
                                    sourceParent: sourceParent,
                                    sourceParentNode: sourceParent && ui.sender || el.parentNode,
                                    sourceIndex: sourceIndex,
                                    targetParent: targetParent,
                                    targetIndex: targetIndex,
                                    cancelDrop: false
                                };

                                //execute the configured callback prior to actually moving items
                                if (sortable.beforeMove) {
                                    sortable.beforeMove.call(this, arg, event, ui);
                                }
                            }

                            //call cancel on the correct list, so KO can take care of DOM manipulation
                            if (sourceParent) {
                                $(sourceParent === targetParent ? this : ui.sender || this).sortable("cancel");
                            }
                            //for a draggable item just remove the element
                            else {
                                $(el).remove();
                            }

                            //if beforeMove told us to cancel, then we are done
                            if (arg && arg.cancelDrop) {
                                return;
                            }

                            //do the actual move
                            if (targetIndex >= 0) {
                                if (sourceParent) {
                                    sourceParent.splice(sourceIndex, 1);

                                    //if using deferred updates plugin, force updates
                                    if (ko.processAllDeferredBindingUpdates) {
                                        ko.processAllDeferredBindingUpdates();
                                    }
                                }

                                targetParent.splice(targetIndex, 0, item);
                            }

                            //rendering is handled by manipulating the observableArray; ignore dropped element
                            dataSet(el, ITEMKEY, null);

                            //if using deferred updates plugin, force updates
                            if (ko.processAllDeferredBindingUpdates) {
                                ko.processAllDeferredBindingUpdates();
                            }

                            //allow binding to accept a function to execute after moving the item
                            if (sortable.afterMove) {
                                sortable.afterMove.call(this, arg, event, ui);
                            }
                        }

                        if (updateActual) {
                            updateActual.apply(this, arguments);
                        }
                    },
                    connectWith: sortable.connectClass ? "." + sortable.connectClass : false
                }));

                //handle enabling/disabling sorting
                if (sortable.isEnabled !== undefined) {
                    ko.computed({
                        read: function() {
                            $element.sortable(unwrap(sortable.isEnabled) ? "enable" : "disable");
                        },
                        disposeWhenNodeIsRemoved: element
                    });
                }
            }, 0);

            //handle disposal
            ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
                //only call destroy if sortable has been created
                if ($element.data("ui-sortable") || $element.data("sortable")) {
                    $element.sortable("destroy");
                }

                //do not create the sortable if the element has been removed from DOM
                clearTimeout(createTimeout);
            });

            return { 'controlsDescendantBindings': true };
        },
        update: function(element, valueAccessor, allBindingsAccessor, data, context) {
            var templateOptions = prepareTemplateOptions(valueAccessor, "foreach");

            //attach meta-data
            dataSet(element, LISTKEY, templateOptions.foreach);

            //call template binding's update with correct options
            ko.bindingHandlers.template.update(element, function() { return templateOptions; }, allBindingsAccessor, data, context);
        },
        connectClass: 'ko_container',
        allowDrop: true,
        afterMove: null,
        beforeMove: null,
        options: {}
    };

    //create a draggable that is appropriate for dropping into a sortable
    ko.bindingHandlers.draggable = {
        init: function(element, valueAccessor, allBindingsAccessor, data, context) {
            var value = unwrap(valueAccessor()) || {},
                options = value.options || {},
                draggableOptions = ko.utils.extend({}, ko.bindingHandlers.draggable.options),
                templateOptions = prepareTemplateOptions(valueAccessor, "data"),
                connectClass = value.connectClass || ko.bindingHandlers.draggable.connectClass,
                isEnabled = value.isEnabled !== undefined ? value.isEnabled : ko.bindingHandlers.draggable.isEnabled;

            value = "data" in value ? value.data : value;

            //set meta-data
            dataSet(element, DRAGKEY, value);

            //override global options with override options passed in
            ko.utils.extend(draggableOptions, options);

            //setup connection to a sortable
            draggableOptions.connectToSortable = connectClass ? "." + connectClass : false;

            //initialize draggable
            $(element).draggable(draggableOptions);

            //handle enabling/disabling sorting
            if (isEnabled !== undefined) {
                ko.computed({
                    read: function() {
                        $(element).draggable(unwrap(isEnabled) ? "enable" : "disable");
                    },
                    disposeWhenNodeIsRemoved: element
                });
            }

            return ko.bindingHandlers.template.init(element, function() { return templateOptions; }, allBindingsAccessor, data, context);
        },
        update: function(element, valueAccessor, allBindingsAccessor, data, context) {
            var templateOptions = prepareTemplateOptions(valueAccessor, "data");

            return ko.bindingHandlers.template.update(element, function() { return templateOptions; }, allBindingsAccessor, data, context);
        },
        connectClass: ko.bindingHandlers.sortable.connectClass,
        options: {
            helper: "clone"
        }
    };

});
;
/// <reference path="~/Content/Editor/js/libs/tinycolor.js" />
/// <reference path="~/Scripts/knockout-3.0.0.debug.js" />
/// <summary>
///  knockout binding handler for spectrum color picker
/// 	<input data-bind='colorPicker: color', format: "toRgbString" ' />
/// </summary>
ko.bindingHandlers.colorPicker = {

	init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
		var value = valueAccessor(); 		
		$(element).val(ko.unwrap(value));

		var format = (allBindingsAccessor() !== null && "format" in allBindingsAccessor()) ?
						allBindingsAccessor().format : "toRgbString";	 //or toHexString
		
		var colorFormatAccessor = function (color) {
			if (tinycolor.prototype.hasOwnProperty(format)) {
			    return color[format]();
			} else {
				return color.toRgbString(); //default fallback
			}
		};

        $(element).spectrum({
			preferredFormat: "rgb",
			showAlpha: true,
			showInput: true,
			showInitial: true,
			showPalette: true,
			hideAfterPaletteSelect: true,
			showSelectionPalette: false,
			beforeShow: function () {
			    $(this).spectrum("option", 'palette', [UI.getSiteColors()]);
			    return true;
			}
        });

	    $(element).addClass('sp-color-input').show()
	        .change(function (event) {
	            var el = $(this);
	            el.spectrum('set', event.target.value);
	            value(colorFormatAccessor(el.spectrum('get')));
	        });
	},

	update: function (element, valueAccessor) {
		//$(element).val(ko.utils.unwrapObservable(valueAccessor()));
	}
};
	///usage:

	/*
	 * var palette = ko.computed(function() {
 
		return internalName() ?
			backend.getDisplayName(internalName()) :
			localStrings.none;
 
	}).extend({ async: localStrings.none }),
	 * 
	 * 
	 */
	ko.extenders.async = function (computedDeferred, initialValue) {

		var plainObservable = ko.observable(initialValue), currentDeferred;
		plainObservable.inProgress = ko.observable(false);

		ko.computed(function () {
			if (currentDeferred) {
				currentDeferred.reject();
				currentDeferred = null;
			}

			var newDeferred = computedDeferred();
			if (newDeferred &&
				(typeof newDeferred.done == "function")) {
				// It's a deferred
				plainObservable.inProgress(true);
				// Create our own wrapper so we can reject
				currentDeferred = $.Deferred().done(function (data) {
					plainObservable.inProgress(false);
					plainObservable(data);
				});
				newDeferred.done(currentDeferred.resolve);
			} else {
				// A real value, so just publish it immediately
				plainObservable(newDeferred);
			}
		});

		return plainObservable;
	};;
ko.extenders.applyBindings = function (node, model) {
    ko.applyBindings(model, node());
    BindingController.koAddElement(node());
};

ko.extenders.applyBindingsToDescendants = function (node, model) {
    ko.applyBindingsToDescendants(model, node());
    BindingController.koAddElement(node());
};

ko.extenders.applyBindingsToNode = function (node, options) {
    ko.applyBindingsToNode(node(), options.model, options.extension);
    BindingController.koAddElement(node());
};

ko.extenders.cleanNode = function (node) {
    BindingController.koRemoveElement(node());
    ko.cleanNode(node());
};
var paletteBindingProvider = (function (paletteBindingProvider) {

    paletteBindingProvider = paletteBindingProvider || function () { };
    paletteBindingProvider.palette = ko.observable();
    paletteBindingProvider.appliedComponents = ko.observableArray([]);

    paletteBindingProvider.subscribed = ko.observable(false);

    paletteBindingProvider.colorsMapIndex =
    [
        { 0: ['body', 'main'] },
        { 1: ['header', 'footer'] },
        { 2: ['div'] },
        { 3: ['button', 'menu'] },
        { 4: ['text', 'paragraph', 'headertext', 'heading'] }
    ];

    paletteBindingProvider.initColors = function() {
        //this gets binded in templates 
        paletteBindingProvider.colors = {
            "body": ko.observable(),
            "main": ko.observable(),

            "header": ko.observable(),
            "footer": ko.observable(),

            "div": ko.observable(),

            "button": ko.observable(),
            "menu": ko.observable(),

            "text": ko.observable(),
            "paragraph": ko.observable(),
            "headertext": ko.observable(),
            "heading": ko.observable(),
        };
    }

    paletteBindingProvider.initColors();

    paletteBindingProvider.PaleteChangeCallback = function (component, style, value) {
        $('#' + component.id).css(style, value);
    };

    paletteBindingProvider.colorsChangeSubscriber = function (newPalette) {
        if (newPalette) {
            var colors = newPalette.Colors();

            for (var i = 0; i < colors.length; i++) {
                var color = colors[i];
                var mappings = _.filter(_.flatten(paletteBindingProvider.colorsMapIndex, i), _.isString);
                ko.utils.arrayForEach(mappings,
                    function (hash) {
                        paletteBindingProvider.colors[hash](color);
                    });
            }
        } else {            
            paletteBindingProvider.initColors();
            paletteBindingProvider.applyBind = false;
            ko.cleanNode($('.site-wrapper')[0]);
            
        }
    };

    paletteBindingProvider.getAllComponents = ko.computed(function () {
        return function () { return UI.siteComponentRepository.getAll(); };
    });

    paletteBindingProvider.flatForNestedChildren = function (startPoint) {
        var result = [];
        var search = function (obj) {
            if (Array.isArray(obj)) {
                obj.forEach(function (node) {
                    result.push(node);
                    if (defined(node.children)) {
                        search(node.children);
                    }
                });
            } else {
                if (defined(obj.children)) {
                    search(obj.children);
                }
            }
        };

        search(startPoint);
        return result;
    }

    paletteBindingProvider.applyBind = false;

    /* UI click handler */
    paletteBindingProvider.bindPalette = function (palette) {
        /* initialization */
        if (!paletteBindingProvider.subscribed()) {

            //dispose for cleaning
            if (paletteBindingProvider.subscribable !== null && ko.isSubscribable(paletteBindingProvider.subscribable)) {
                paletteBindingProvider.subscribable.dispose();
            }

            paletteBindingProvider.subscribable = paletteBindingProvider.palette.subscribe(
                function (newPalette) {
                    //bind root node only once	
                    if (newPalette && (!newPalette.isInit && !paletteBindingProvider.applyBind)) {
                        setTimeout(function () {
                            try {
                                paletteBindingProvider.applyBind = true;
                                ko.observable($('.site-wrapper')[0]).extend({ applyBindings: paletteBindingProvider.colors });
                            }
                            catch (exception) {
                            }
                        }, 1);
                    }
                    paletteBindingProvider.colorsChangeSubscriber(newPalette);
                });

            //firstTime applying palette, call a subscriber 
            paletteBindingProvider.palette(palette);

            var cleaning = eventsystem.subscribe('/component/create/',
                function (component) {

                    if (paletteBindingProvider.subscribed()) {
                        paletteBindingProvider.appliedComponents.push(component);
                        ko.observable($(component.getUISelector())[0]).extend({ applyBindings: paletteBindingProvider.colors });
                    }
                });

            paletteBindingProvider.subscribed(true);

        } else {
            paletteBindingProvider.palette(palette);
        }
    };

    return paletteBindingProvider;

})(paletteBindingProvider);;
	var Color = function (color) {
		this.color = ko.observable(tinycolor(color).toRgbString());
	}

	var Palette = function (palette) {
		var self = this;
		this.editMode = ko.observable(false);

		if (palette !== null) {
		    ko.utils.extend(this, palette);
		    var colors = "";
		    if (this.Colors != 0)
		    {
		        colors = JSON.parse(this.Colors)
		    }
			ko.utils.extend(this, colors);

			this.Title = ko.observable(this.Title);
			this.Colors = ko.observableArray([]);

			ko.utils.arrayMap(self.colors, function (color) {
			    self.Colors.push(new Color(color));
			});

			console.log(this.Colors());

			this.ImageUrl = ko.computed({
				read: function() {
					var colors = ko.toJSON({
						colors: ko.utils.arrayMap(self.Colors(),
						function (d) { return tinycolor(d.color()).toHexString(); })
					});

					return "palettes/image/?colors=" + encodeURIComponent(colors);

				},
				deferEvaluation: true
			});

		}

		self.newColor = function () {

			if (self.Colors().length >= 5) return;

			var colors = self.Colors();
			var lastColor =colors[colors.length - 1];
			self.Colors.push(new Color(lastColor.color()));
		}
	
		self.removeColor = function (color) {
			if (self.Colors().length - 1 <= 0) return;
			self.Colors.remove(color);
		}

	}


	var PaletteController = function (search) {
		var self = this;

		this.palettes = ko.observableArray([]);
		     
		self.loading = ko.observable(false);
		self.error = ko.observable(false);
	
		self.canShow = ko.computed(function () {
			return !self.loading();
		});
		
		self.loadPalettes(search);

		PaletteController.prototype.replacePalette = function (palette, newPalette) {
			self.palettes.replace(palette, newPalette);
		}

		PaletteController.prototype.getPalette = function (id) {
		    return ko.utils.arrayFirst(self.palettes(), function (item) {
		        if (item.Id === id) return item;
		    });
		}
	};

	
	PaletteController.prototype.hideError = function () {
		this.error(false);
	}

	PaletteController.prototype.resetPalettes = function () {

	    var self = this;

	    self.loading(true);
	    self.hideError();

	    $.getJSON("/palettes/resetall").done(function (data) { Designer.paletteController.loadPalettes("") });
	};

	PaletteController.prototype.activePaletteId = function (id) {
        var body = UI.siteComponentRepository.getBody();
        if (defined(id)) {
            body.setProperty(ACTIVE_PALETTE, id);
        }
        else {
            return body.getProperty(ACTIVE_PALETTE).value;
        }
    }

	PaletteController.prototype.loadPalettes = function (search) {

	    var self = this;

	    self.loading(true);
	    self.hideError();
	    self.editMode(false);

	    if (defined(search) && !String.isNullOrEmpty(search)) {
	        var oldPlt = self.getPalette(search);
	        if (defined(oldPlt)) {
	            this.postJson("/palettes/details", ko.toJSON({ Id: search }), function (data) {
	                if (defined(data)) {
	                    var nPlt = new Palette(data);

	                    self.replacePalette(oldPlt, nPlt);

	                    if (self.isApplied(oldPlt)) {
	                        self.applyPalette(nPlt);
	                    }
	                }
	                self.loading(false);
	            });
	        } else self.loading(false);

	    } else {

	        $.getJSON("/palettes/getall").done(function (data) {
	            try {
	                if (data !== null) {
	                    if (data.length > 0) {
	                        self.palettes.removeAll();
	                        ko.utils.arrayMap(data, function (palette) {
	                            if (palette.Id === PaletteController.prototype.activePaletteId()) {
	                                self.applied(new Palette(palette));
	                                paletteBindingProvider.bindPalette(ko.utils.extend(new Palette(palette), { isInit: true }));
	                            }
	                            self.palettes.push(new Palette(palette));
	                        });

	                        //console.log("loaded palettes:" + JSON.stringify(self.palettes()));
	                    }
	                }
	            } catch (exception) {
	                self.error(true);
	            }

	            self.loading(false);

	        }).fail(function (data) {
	            self.error(true);
	        }).always(function (data) {
	            self.loading(false);
	        });

	    };
	};

	PaletteController.prototype.addPalette = function ($data, event) {
			var self = this;
			event.preventDefault();
			console.log("adding palette");

			$.getJSON("/Palettes/Create", function (palette) {
				var newPalette = new Palette(palette);
				self.palettes.push(newPalette);
				self.toggleEdit(newPalette);
				self.toggleDuplicate(newPalette);
			});

			return false;
	}

	PaletteController.prototype.unwrapPalette = function(palette) {
	    var newPalette = null;

	    if (defined(palette)) {
	        var unwrapPalette = ko.unwrap(palette);
	        var newPalette = {
	            Id: '00000000-0000-0000-0000-000000000000',
	            Title: H.duplicateNameValue(ko.unwrap(unwrapPalette.Title)),
	            Description: ko.unwrap(unwrapPalette.Description),
	            Colors: '{"colors":[' + '"' + unwrapPalette.colors.join('","') + '"' + ']}'
	        };
	    }
	    else {
	        var newPalette = {
	            Id: '00000000-0000-0000-0000-000000000000',
	            Title: "def",
	            Description: "def",
	            Colors: '{"colors":["#000000","#000000","#000000","#000000","#000000"]}'
	        };
	    }

	    return new Palette(newPalette);
    }

	var paletteCount = 0;
	ko.utils.extend(PaletteController.prototype, {
	    editMode: ko.observable(false),
	    duplicateMode: ko.observable(false),
		palette: ko.observable(null),
		applied: ko.observable(null),
		duplicatePalette: ko.observable(PaletteController.prototype.unwrapPalette(this.palette)),
        originPalette: null,

		toggleDelete: function (palette) {
		    var id = ko.unwrap(palette.Id);
		    this.postJson("/Palettes/Delete", ko.toJSON({ Id: id }),
					function () {
					    Designer.paletteController.loadPalettes("");
					},
					function (errorText) {

					});
		    
		}.bind(PaletteController.prototype),

		toggleDuplicate: function (palette) {
		    console.log("duplicate palette:" + JSON.stringify(ko.toJS(palette)));
		    
		    if (!this.editPaletteIsSet()) {
		        
		        var dupPalette = PaletteController.prototype.unwrapPalette(palette);

		        this.duplicatePalette(dupPalette);

		        this.setEditPalette(dupPalette); // that will hold latest mutated palette, saving will empty this	

		        this.duplicateMode(true);

		    } else {
		        //reflect changes to backend
		        this.updatePaletteChanges();
		        this.duplicateMode(false);
		    }
		}.bind(PaletteController.prototype),

		toggleEdit: function(palette) {

			console.log("edit palette:" + JSON.stringify(ko.toJS(palette)));
			if (palette.editMode()) {
				this.editMode(true);
			}

			if (this.editMode() === false && palette.editMode() === true) {
				this.editMode(true);
			}

			if (!this.editPaletteIsSet()) {
				this.setEditPalette(palette); // that will hold latest mutated palette, saving will empty this	
				this.originPalette = ko.unwrap(palette);

				palette.editMode(true);
				this.editMode(true);

			} else {			
				//reflect changes to backend
				this.updatePaletteChanges();
				this.editMode(false);
			}
		}.bind(PaletteController.prototype),

		setEditPalette: function(palette) {
			this.palette(palette);
		}.bind(PaletteController.prototype),
        		
		applyPalette: function (palette) {
		   paletteCount++;
		    $('#palete-undo').removeClass("not-active");
		    var oldPaletteObj = this.applied();
		    var newPaletteObj = palette;

		    var action = function (activePalete) {

		        PaletteController.prototype.applied(activePalete);
                PaletteController.prototype.activePaletteId(activePalete ? activePalete.Id : "");
		        paletteBindingProvider.bindPalette(activePalete);

		    }

		    //create undomanager Item
		    UI.undoManagerAddUpdatedStructSimpleArr(paletteBindingProvider.PaleteChangeCallback, paletteBindingProvider.PaleteChangeCallback);

		    //get and testing undomanager Item
		    var undoRedoItem = UI.undoManagerGetLatestCommand();
		    if (!undoRedoItem.structs && undoRedoItem.callbackredo && undoRedoItem.callbackundo)
		        undoRedoItem = null;

		    var struct = {
		        action: action,
		        oldvalue: oldPaletteObj,
		        newvalue: newPaletteObj
		    };
		                                                           
		    
		    console.log(oldPaletteObj);
		    console.log(newPaletteObj);
		    UI.undoManagerAddNewItemToUpdatedStructSimpleArr(undoRedoItem, struct, true);

		    paletteBindingProvider.bindPalette(newPaletteObj);

		}.bind(PaletteController.prototype),

		editPaletteIsSet: function() {
			return this.palette() !== null;
		}.bind(PaletteController.prototype),

		updatePaletteChanges: function() {
			if (this.palette() !== null) {

				//we have changes to update
				var palette = ko.toJS(ko.unwrap(this.palette()));
				var urlAction = "/Palettes/UpdatePalette";

				var toSend = ko.toJSON({
				    Id: palette.Id,
				    Title: palette.Title,
				    DateCreated: palette.DateCreated,
				    Description: palette.Description,
				    Colors: ko.utils.arrayMap(palette.Colors, function (d) {
				        return tinycolor(d.color).toRgbString();
				    })
				});

				if (this.duplicateMode() == true)
				{
				    toSend = ko.toJSON({
				        Id: '00000000-0000-0000-0000-000000000000',
				        Title: palette.Title,
				        Description: palette.Description,
				        Colors: ko.utils.arrayMap(palette.Colors, function (d) {
				            return tinycolor(d.color).toRgbString();
				        })
				    });
				}

				console.log(toSend);
			    if (palette && palette.Colors) {
			        palette.Colors.forEach(function (item) {
			            console.log(item.color);
			            UI.addSiteColor(item.color);
			        });
			    }
			    var self = this;

				this.postJson(urlAction, toSend,
					function(palette) {
					    console.log(ko.toJSON(palette));
					    Designer.paletteController.loadPalettes("");
					},
					function(errorText) {
						
					});

				this.palette().editMode(false);
				this.palette(null);
			}
		}.bind(PaletteController.prototype),

		postJson: function(url, jsonData, successClbk, errorClbk) {
			$.ajax({
				url: url,
				type: "POST",
				dataType: "json",
				data: jsonData,
				contentType: "application/json; charset=utf-8",
				success: function(result) {
					if (successClbk !== null && typeof(successClbk) === "function") {
						successClbk(result);
					}
				},
				error: function(objAjaxRequest, strError) {
					var respText = objAjaxRequest.responseText;
					console.log(strError);
					if (errorClbk !== null && typeof (errorClbk) === "function") {
						errorClbk(respText);
					}
				}
			});
		},

		getSearchString: function() {
			return this.search();
		}.bind(PaletteController.prototype),

		toggleCancel: function (cnclPalette) {
		    console.log("cancel edit palette");

		    if (this.duplicateMode() == false)
		    {
		        Designer.paletteController.loadPalettes(cnclPalette.Id);
		    }
		    this.editMode(false);
		    this.duplicateMode(false);
		    this.palette(null);
		    this.duplicatePalette(PaletteController.prototype.unwrapPalette());
		    
		}.bind(PaletteController.prototype),

		isApplied: function (palette) {
		    if (this.applied() == null) {
		        return false;
		    }
			return this.applied().Id === palette.Id;
		},

		getApplied: function() {
			return this.applied();
		},

		undoPalette: function (palette) {			
			var self = this;
			this.postJson("/palettes/undo", ko.toJSON({ Id: palette.Id }),
				function (data) {

					var newPalette = new Palette(data);
					newPalette.editMode(true);
					self.replacePalette(palette, newPalette);
					self.palette(newPalette);

					if (self.isApplied(palette)) {
						 self.applyPalette(newPalette);
					}

				}, function(data) {
					self.error(true);
				});

		}.bind(PaletteController.prototype)
	});
;
ko.bindingHandlers.palette = {
	propertyIds: {
		'background-color': "F7CFBF29-6780-475E-80A3-8FB8CA471547",
		'color': "4A40A145-C5A9-4F81-8AD8-04356FD0BA22"
	},
	init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {	    
			var colorMapping = ko.utils.unwrapObservable(valueAccessor());
			if (colorMapping != null) {
				var hash = colorMapping.element;
				var style = colorMapping.property;
				if (viewModel != null) {
					if (hash in viewModel) {
						var color = viewModel[hash];
					    //we get an Color instance here... unwrap it to get dependencies update

						var undoRedoItem = UI.undoManagerGetLatestCommand();
					    if (undoRedoItem) {
					        if (!undoRedoItem.structs && undoRedoItem.callbackredo && undoRedoItem.callbackundo) {
					            undoRedoItem = null;
					        }
					    }

					    var newvalue = ko.utils.unwrapObservable(color() ? color().color : null);
					    if (undoRedoItem == null) {
					        $(element).css(style, newvalue);
					    }

					    try {
							var component = UI.siteComponentRepository.lookupDataSet({ id: element.getAttribute("id") }).firstOrDefault();

							var oldvalue = $(element).css(style);
							
							if (component != null) {
							    var value = newvalue ? newvalue : (component.getProperty(style) ? component.getProperty(style).value : '');

							    var struct = {
							        component: component,
							        property: style,
							        oldvalue: oldvalue,
							        newvalue: value
							    };

							    if (undoRedoItem == null || oldvalue == value) {
							        if (component != null) {
							            component.setProperty(style, value, true);
							        }
							    } else {
							        UI.undoManagerAddNewItemToUpdatedStructSimpleArr(undoRedoItem, struct, true);
							    }

							    var property = component.getProperty(style).value;
								if (property != null) {
									property.componentId = component.name;
									property.controlId = component.id;

									property.propertyId = ko.bindingHandlers.palette.propertyIds[style];
										
									property.group = "common";
									property.type = "common";

									console.log(ko.toJSON(property));
								}
							}
						} catch (exception) {
							console.log('palette binding handler:' + exception);
						}
					}
				}
			}
			return { controlsDescendantBindings: false };
		},
	update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
			var colorMapping = ko.utils.unwrapObservable(valueAccessor());
			if (colorMapping != null) {
				var hash = colorMapping.element;
				var style = colorMapping.property;
				if (viewModel != null) {
					if (hash in viewModel) {
					    var color = viewModel[hash];
                       
					    var undoRedoItem = UI.undoManagerGetLatestCommand();
					    if (undoRedoItem) {
					        if (!undoRedoItem.structs && undoRedoItem.callbackredo && undoRedoItem.callbackundo) {
					            undoRedoItem = null;
					        }
					    }

					    //we get an Color instance here... unwrap it to get dependencies update
					    var newvalue = ko.utils.unwrapObservable(color() ? color().color : null);

					    if (undoRedoItem == null) {
					        $(element).css(style, newvalue);
					    }

					    try {
					        var component = UI.siteComponentRepository.lookupDataSet({ id: element.getAttribute("id") }).firstOrDefault();
					        var oldvalue = $(element).css(style);

					        if (component != null) {
					            var value = newvalue ? newvalue : (component.getProperty(style) ? component.getProperty(style).value : '');

					            var struct = {
					                component: component,
					                property: style,
					                oldvalue: oldvalue,
					                newvalue: value
					            };

					            if (undoRedoItem == null || oldvalue == value) {
					                component.setProperty(style, value, true);
					            } else {
					                UI.undoManagerAddNewItemToUpdatedStructSimpleArr(undoRedoItem, struct, true);
					            }
					        }
						} catch (exception) {
							console.log('palette binding handler:' + exception);
						}
					}
				}
			}
			return { controlsDescendantBindings: false };
		}	
}
;
ko.bindingHandlers['colorMapName'] = {	
    update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {

    	var colorsMapNamesIndex = ['Content area', 'Header, Footer', 'Panels', 'Buttons and Menu', 'Text'];
    	
    	var index = ko.utils.unwrapObservable(valueAccessor());
	    if (index >= 0 && index <= 5) {
		    var newValueAccessor = function() {
			    return colorsMapNamesIndex[index];
		    };

		    ko.bindingHandlers.text.update(element, newValueAccessor, allBindingsAccessor, viewModel, bindingContext);
	    }
    }
};
;
	var viewerPaletteController = (function() {
			
			viewerPaletteController.assignedPalette = ko.computed(function() {
					var templateid = UI.settings.hasOwnProperty("templateId") ? UI.settings.templateId :
						$('[data-templateid]').attr('data-templateid') ;

					if (templateid) {
						return $.getJSON("/palettes/template/" + templateid) ;
					}

				return null ;
			}).extend({ async: null });

		viewerPaletteController.assignedPalette.subscribe(function (palette) {
			if (palette !== null) {
				console.log(ko.toJSON(palette));
				
			    paletteBindingProvider.bindPalette(new Palette(palette)) ;								
			}
		});

		return viewerPaletteController;
	});
;
/// <reference path="repositories.js" />
var AdvertisementService = function () {
    var self = this;
    var advData = null;

    self.load = function () {
        try {
            advData = dealerAdvTemplate;
        }
        catch(ex) {
            console.log('no adv...' + ex);
        }
    }

    self.buildAdvTop = function () {
        var htmlAdvertisementTop = "";
        var width = parseInt(UI.getDevice().getWidth());
        var height = parseInt((width * 90) / 960);
        if (advData != null && advData.pictureUrl)
        {
            var target = '';
            if (advData.openInNewTab != null && advData.openInNewTab) {
                target = ' target="_blank"';
            }

            htmlAdvertisementTop = '<div>' +
                '<img src="' + advData.pictureUrl + '" usemap="#agentmap" width="' + width + '" height="' + height + '">';
            if (advData.areas != null) {
                htmlAdvertisementTop = htmlAdvertisementTop + '<map name="agentmap">';
                advData.areas.forEach(function (item) {
                    if (!UI.getDevice().isDesktop()) {
                        item.x0 *= width / 960;
                        item.x1 *= width / 960;
                        item.y0 *= height / 90;
                        item.y1 *= height / 90;
                    }
                    htmlAdvertisementTop = htmlAdvertisementTop + '<area shape="rect" coords="' + item.x0 + ',' + item.y0 + ',' + item.x1 + ',' + item.y1 + '" href="' + item.url + '"' + target + '>';
                });
                htmlAdvertisementTop = htmlAdvertisementTop + '</map>';
            }
            htmlAdvertisementTop = htmlAdvertisementTop +'</div>';     
        }
        var adv = $('.sitepreview .site .adv');
        if (adv.length != 0)
        {
            if (advData != null) {
                $('.site-wrapper').css({ 'margin-top': height });
                $('.header.std-component-fixed').css({ 'margin-top': height });
            }
            adv.html(htmlAdvertisementTop);            
        }
    }

    self.buildAdvBottom = function () {
        var htmlAdvertisementBottom = '';

        var htmlAdvertisementBottomMobile = '';
        var advbottom = $('.site-wrapper #wrapper-footer');
        if (UI.getDevice().isDesktop()) {
            advbottom.html(htmlAdvertisementBottom);
        } else {
            advbottom.html(htmlAdvertisementBottomMobile);
        }
    }

    this.load();
    this.buildAdvTop();
    this.buildAdvBottom();
}


;
