Danish firm Zmags generates $12M annual revenue through their ecommerce solution, which converts clients' catalog print files into shoppable online experiences.

I built a customizeable deployment architecture using Javascript's revealing module pattern, resulting in many operational benefits.

/*****************
@
@ PSZ Framework Verision 1.0
@
@ Authors: Elias Carlston (elias@eliascarlston.com), Brian Doherty (bdo@zmags.com)
@
@
****************/


if (psz) {
	// this alert is a safety: will fire so we don't overwrite client var
		alert('psz taken!');
	} else {
        var viewer;

		// create the (intentionally) global object
		// wrapper for all out data + funcitons
		var psz = {
			// args contains data + variables during execution
			// seeded with defaults during init
			'args' : {},
			// client contains client-specific data
			'client' : {
				// config and catalog vars are merged into psz.args during init
				// client-specific settings are saved in config
				'config' : {
					// catalog-specific settings. optionally separate file from config.
					'catalog' : {}
				},
				// custom client-specific code is stored in logic
				'logic' : {}
			},
			'config' : {
				// PSZ default settings, also a placeholder object
				// new parameters need to be added to config.default before client
				'default' : {}
			},
			// models is an abstraction of data sources 
			'models' : {},
			// viewer is a collection of viewer functions
			'viewer' : {},
			// windows is a collection of window objects: DPWs, error msgs etc
			'windows' : {}
		};
	}
psz.client.config.global = (function client_config ($, psz_args) {

    var psz_client_config = {
        'CLIENT_NAME' : 'Kenneth Cole',
        'CLIENT_HOMEPAGE': '//www.kennethcole.com',
        'CLIENT_PATH': 'kennethcole/base/',
        'CATALOG_PATH': 'kennethcole/catalogs/050412/',
        'CHECKOUT_DOMAIN': '//www.kennethcole.com/',
        'CATALOG_WRAPPER' : '#psz-catalog-wrapper',
        'add_to_cart' : {
            //The commented out proxy page is the "new" one. It is designed to be used with easy XDM. It came along late in the development process, so we are using the "old" way of adding to cart for facebook until we have time to refactor.
            //'proxy_page' : '//www.kennethcole.com/zmags/zmagsnippet_04_03_2012.html',
            'proxy_page' : '//www.kennethcole.com/zmags/zmagsnippet.html',
            'script' : '//www.kennethcole.com/cartHandler/index.jsp',
            'mobile_script' : '//m.kennethcole.com/mt/www.kennethcole.com/cartHandler/index.jsp'
        }, 
        'CHECKOUT_URL': 'http://www.kennethcole.com/cart/index.jsp?clickid=topnav_cart_txt',
        'CHECKOUT_URL_MOBILE': 'http://m.kennethcole.com/mt/www.kennethcole.com/cart/index.jsp?clickid=topnav_cart_txt',
        'current_pages' : {
            'firstPage' : '1',
            'lastPage' : '2'
        },
        'mobile_enabled' : {
            'iPhone' : true,
            'iPad' : true,
            'Android' : false
        },
        'mobile' : {
            'viewerIframeElement' : '#zmagspsMobileViewer',
            'interfaceElements' : {
                'controlsWrapper' : '#psz-header',
                'goToPageInput' : '#zMobileGoToPage',
                'goToNextPage' : '#zMobileGoNextPage',
                'goToPrevPage' : '#zMobileGoPrevPage',
                'pageDisplay' : '#zMobilePageDisplay',
                'checkoutBtn'  : '#zMobileCheckout'
            },
            //client directory...
            'interfaceFile' : '/mobile/mobileInterface.php',
            'customInterface' : {
                'iPad' : true,
                'iPhone' : false,
                'Android' : false
            }
        },
        'customInterface' : {
            'desktop' : {
                'interfaceFile' : '/mobile/mobileInterface.php',
                'interfaceHeight' : '70',
                'interfaceElements' : {
                    'controlsWrapper' : '#psz-header',
                    'goToNextPage' : '.psz-navigation .psz-next',
                    'goToPrevPage' : '.psz-navigation .psz-previous',
                    // 'pinterest' : '.psz-toolbar .psz-pinterest',
                    'twitter' : '.psz-toolbar .psz-twitter',
                    'facebook' : '.psz-toolbar .psz-facebook',
                    'sendemail' : '.psz-toolbar .psz-sendemail',
                    'toc' : '.psz-toolbar .psz-toc',
                    'checkout' : '.psz-toolbar .psz-checkout',
                    'close' : '.psz-toolbar .psz-close-fullscreen'
                }
            },
            'hasCustomInterface' : {
                'Desktop' : true,
                'iPad' : true,
                'iPhone' : false,
                'Android' : false
            }
        },
        // TODO: turn debug off
        'debug': false,
        'ZMAGS_DOMAIN': '//ps.zmags.com/',
        'deep_link_variable_name' : '_zmagsProduct',
        'customer_analytics' : {
            'add_to_cart' : function(product){
                psz.client.analytics.track.addToCart(product);
            },
            'zmag_load' : function(){
                psz.client.analytics.track.zmagLoad();
            },
            'product_viewed' : function(product){
                psz.client.analytics.track.dpwLoad();
            }
        },
        'functions' : {
            'customHeaders' : function(){
                psz.client.preload.customHeaders();
            },
            'onShow' : function(){
                psz.client.logic.onWindowShow();
            },
            'onClose' : function(){
                psz.client.logic.onWindowClose();
            }
        },
        'handlebars' : {
            'compiledTemplate' : 'templates/kcCompiledTemplates.js',
            'registerClientHelpers' : function(){
            //psz.client.handlebarsHelper.register();
            },
            'productWindowTemplate' : 'kcDesktop',
            'modalWindowTemplate' : 'kcModal',
            'available': false
        },
        //Save a simplified version of product data object to psz.windows.DPW for life of DPW. Used to switch available sizes based on currently selected color.
        'productDataSimplifier' : function (product_data) {
            psz.resources.simple_data.init(product_data);
        },
        'product' : {
            'statusKey' : 'in_stock',
            'status_variants' : [
            {
                'value' : 'IN_STOCK',
                'behavior' : function(productData){
                    psz.windows.DPW.showProductWindow(productData);
                }
            },
            {
                'value' : 'NOT_AVAILABLE',
                'behavior' : function(productData){
                    psz.windows.DPW.showModal({
                        'message' : psz.args.messages.out_of_stock
                        });
                }
            },
            {
                'value' : 'NOT_IN_STOCK',
                'behavior' : function(productData){
                    psz.windows.DPW.showModal({
                        'message' : psz.args.messages.out_of_stock
                        });
                }
            }
            ]
        },
        'viewer_parent_id': '#zmagspsCatalogContainer',
        //Resource loading...
        'arrCssIncludeFiles': [
            {
                'url' : 'css/kennethcole.css',
                'resourceType' : 'client'
            }
        ],
        'arrJsIncludeFiles' : [
        {
            'url' : '/js/easyXDM.js',
            'resourceType' : 'core',
            'callbackFunction' : function(args){
                psz.resources.addScriptInclude(args);
            },
            'callbackArgs' : {
                'url' : '/js/psz_client_cart_integration.js',
                'resourceType' : 'client'
            }
        },
        {
            'url' : '/js/psz_client_analytics.js',
            'resourceType' : 'client'
        },

        {
            'url' : '/js/psz_client_logic.js',
            'resourceType' : 'client'
        },
         {
            'url' : '//ws.sharethis.com/button/buttons.js', 
            'resourceType' : 'external',
            'callbackFunction' : function(){
            }
        },
        {
            'url' : '//assets.pinterest.com/js/pinit.js',
            'resourceType' : 'external'
        }
        
        
        ]
    };

    return psz_client_config;

})(psz.jquery, psz.args);
psz.client.config.catalog = (function client_config () {

    var psz_client_config = {
        'catalog_name' : 'THE TANGERINE TREND',
        'current_pages' : {
            'firstPage' : '1',
            'lastPage' : '2'
        },
        'sharing' : {
            'catalog_url' : 'http://www.kennethcole.com/family/index.jsp?categoryId=12648845',
            'default_image' : 'http://ps.zmags.com/commerce/deploy/kennethcole/011011/images/logo-kcole.png',
            'default_title' : 'The Kenneth Cole Tangerine Trend',
            'twitter_title' : 'The New @KennethColePrd Tangerine Trend #KCTrend',
            'default_description' : 'Check out this latest trend at Kenneth Cole!'
        },
        'publicationIDs' : {
            'desktop' : 'e1b99bbd',
            'iPad' : 'e1b99bbd',
            'iPhone' : 'e1b99bbd'
        }
    };

    return psz_client_config;

})();
psz.jquery = (function() {

        return jQuery.noConflict();

})();
/*
    The purpose of this module is to report analytics and error logs
*/
psz.reporting = (function psz_reporting($){

    /*
        Analytics reporting
        Note: psz.args is used instead of a local copy because when this module loads psz_args isn't gaurenteed to be a complete config set (sometimes is undefined).
    */
    var currentProduct;
    var currentCartProducts = [];


    function _zAnalyticsAddToCart(qty){
        if(typeof(qty) === 'undefined'){
            alert('No Quantity defined for add to cart analytics!');
        } else {
            if(_validateQty(qty)){
                viewer.registerProductAddedToSiteShoppingCart(currentProduct, qty);
                currentCartProducts.push({'product':currentProduct,'quantity':qty});
                //client analytics call back
                if(psz.args.customer_analytics.add_to_cart){
                    psz.args.customer_analytics.add_to_cart({'product' : currentProduct, 'quantity' : qty});
                }
            } else { 
                alert('Invalid quantity for analytics!');
            }
        }
    }

    function _zAnalyticsProductViewed(){
        viewer.registerProductDetailsViewed(currentProduct);
        if(psz.args.customer_analytics.product_viewed){
            psz.args.customer_analytics.product_viewed(currentProduct);
        }
    }

    function _zAnalyticsCheckout(){
        viewer.registerSiteShoppingCartCheckedOut(currentCartProducts);
        if(psz.args.customer_analytics.checked_out){
            psz.args.customer_analytics.checked_out(currentCartProducts);
        }
    }

    function _zAnalyticsZmagLoad(){
        // zmags analytics tracked automatically...
        if(psz.args.customer_analytics.zmag_load){
            psz.args.customer_analytics.zmag_load();
        }
    }

    function _setCurrentProduct( productData ){
        var thisProduct = productData.product;
        currentProduct = {
            'productID' : thisProduct.product_id,
            'productName' : thisProduct.name,
            'price' : thisProduct.price
        };
    }
    function _getCurrentProduct(){
        return currentProduct;
    }

    /*
        Error Reporting
    */ 
    function _reportError(message){
        //stub function
    }

    function _validateQty(qty){
        var testQty = parseInt(qty);
        if(isNaN(testQty)){
            return false;
        } else if(testQty <= 0) {
            return false;
        } else {
            return true;
        }
    }
    
    return{
        'track' : {
            'addToCart' : function(qty){
                _zAnalyticsAddToCart(qty);
            },
            'productViewed' : function(){
                _zAnalyticsProductViewed();
            },
            'checkOut' : function(){
                _zAnalyticsCheckout();
            },
            'zmagLoad' : function(){
                _zAnalyticsZmagLoad();
            },
            'setCurrentProduct' : function (productData){
                _setCurrentProduct(productData);
            },
            'getCurrentProduct' : function(){
                return _getCurrentProduct();
            }
        },
        'error' : {
            'report' : function(message){
                _reportError(message);
            }
        }

    }


})(psz.jquery, psz.args);
psz.config.base = (function default_config ($, psz_platform, psz_client_configs) { 

    var default_vars = {
        'client_name' : 'No Client Name',
        'catalog_name' : 'No Catalog Name',
        'CODE_PATH': 'commerce/deploy/',
        'CHECKOUT_PROTOCOL' : 'https',
        /*Client Data Module specifies what data source to use.
          Ex: 'psz_product_db' will use the zmags product database
              'psz_magento_db' will query the magento database.
               |_ Note: if psz_magento_db is specified, another attribute of psz_args is expected. 'CLIENT_DATA_MAGENTO_PREFIX' should be set to the client prefix name used by magento to store the product skus.  
                'CLIENT_DATA_MAGENTO_PREFIX' : 'sokos',
        */
		'CLIENT_DATA_MODULE': 'psz_product_db',
		'MODULE_PATH': 'commerce/modules/psz/',
		'ZMAGS_DOMAIN': '//ps.zmags.com/',
		'ajax_setup_p': true,
		// In-memory copy of the cart contents for use in registering checkout statistics
		'cart_contents': [],
		// The current product link activate event object
		'current_event_object': '',
		'current_page': 1,
		// TODO: move this (debug) into its own function/module
		'debug': false,
        'dpw_id' : 'psz-dpw',
		'global_mobile_scrollers': {},
		'is_in_facebook': false,
		'is_mobile_viewer': false,
		'is_full_screen': false,
		'module_version': '1.1',
		'sharethis_id': '45d05e92-bde9-4546-a4f2-989a7a950f51',
                'addthis_id': 'ra-4f6b068d02681438',
		'viewer_parent_id': '#zmagspsCatalogContainer',
		'zoomtype': 'hover',
		'zoom_image_container_id': '',
                'deep_link' :{
                    'page_var_name': 'zpage',
                    'product_var_name': 'zproduct'
                },
        /*
            The product object determines how to handle various product states returned by the viewer when launching a window. 
        */
        'product' : { 
            'statusKey' : 'in_stock',
            'status_variants' : [ 
            {
                'value' : 'IN_STOCK',
                'behavior' : function(productData){
                    psz.windows.DPW.showProductWindow(productData);
                }
            },
            {
                'value' : 'NOT_IN_STOCK',
                'behavior' : function(productData){
                    psz.windows.DPW.showModal(psz.args.messages.outOfStock);
                }
            },
            {
                'value' : 'NOT_AVAILABLE',
                'behavior' : function(productData){
                    psz.windows.DPW.showModal(psz.args.messages.outOfStock);
                }
            }
            ]
        },
        /*
            The message object will store any reusable "static" message to display to the end user. Useful for out of stock message, error messages, etc. The default config specifies 'error' and 'notAvailable', but this object can overwrite the default object to support custom messages.
        */
        'messages' : {
            'error' : 'Sorry, an error has occured. Please try again later.',
            'out_of_stock' : 'This product is not currently available.',
            'cart_add_success' : "You've added this item to your cart."
        },
        //mobile specific config
        'mobile_enabled': false,
        //customer analytics, if enabled will conditionally provide call backs to implement customer analytics intergration (with omniture of google analytics)
        'customer_analytics' : false,
        /*
            The mobile object contains mobile specific configuration variables that are used when initializing te mobile viewer. This whole object can be overwritten by a client config. 
            -viewerIframeElement:
                The jQuery selector representing the iFrame the zmag will load in
            interfaceElements 
            |----controlsWrapper:
                    The jQuery selector representing the  outter most wrapping div containing the controls. This element is specified to use in calculating the height of the mobile viewer relative to the controls hieght. 
            |----goToPageInput:
                    The jQuery selector representing the input element that will have a "change" event binded to it, this will make the viewer jump to the page specified
            |----goToNextPage:
                    The jQuery selector representing the element that will have a click event that will make the viewer go to the next page when clicked.
            |----goToPrevPage:
                    The jQuery selector representing the element that will have a click event that will make the viewer go to the previous page when clicked.
            |----pageDisplay:
                    The jQuery selector representing the element that will have it's inner text updated to display the current page.
            |----checkoutBtn:
                    The jQuery selector representing the element that should have the zmags Analytics checkouted out event binded to it on click 
            -interfaceFile:
                The path to the file that contains the HTML markup that will be appended to the DOM above the mobile viewer. By default we should use PHP files so we can allow crossDomain access and do conditional platform checking for rendering interfaces for other mobile platforms (if we ever want to).
            -customInterface:
                Specified which platforms should have "custom" interfaces (that is, not the default one the mobile viewer comes with). If true is set,the interface specified will be loaded and viewer event listeners binded to the elements specified in the interfaceElements object
        */
        'mobile' : {
            'viewerIframeElement' : '#zmagspsMobileViewer',
            'interfaceElements' : {
                'controlsWrapper' : '#zMobileControls',
                'goToPageInput' : '#zMobileGoToPage',
                'goToNextPage' : '#zMobileGoNextPage',
                'goToPrevPage' : '#zMobileGoPrevPage',
                'pageDisplay' : '#zMobilePageDisplay',
                'checkoutBtn'  : '#zMobileCheckout'
            },
            //client directory...
            'interfaceFile' : '/mobile/mobileInterface.php',
            'customInterface' : {
                'iPad' : true,
                'iPhone' : false,
                'Android' : false
            },
            // phone interface
            'phoneInterface' : false       
        },
        //end mobile specific config
        // variable containers
        'dpw' : {
            'hide_class' : '.zmags-close-dpw',
            'id' : '#zmags-dpw',
            'overlay_id' : '#zmags-dpw-overlay',
            'overlay_parent' : '#zmags-dpw-wrapper',
            'wrapper_id' : '#zmags-dpw-wrapper',
            'add_to_cart_form' : 'form.psz-product-order-form',
            'onClose' : function () {},
            'onLoad' : function () {}
        },
        'viewer_container_size': {
            width: '900px', 
            height: '700px'
        },
        'arrCssIncludeFiles': [
        {
            'url' : '/css/zBox.1.0.css', 
            'resourceType' : 'core'
        },

        {
            'url' : '/css/psz.css', 
            'resourceType' : 'core'
        }
        ],
        'arrJsIncludeFiles': [
        /* Example resource definition
            {
                'url' : 'path/to/js',           (required) 
       'resourceType' : 'core',                 (optional) (core, client, external) 
           'scriptID' : 'myScriptId',           (optional)
   'callbackFunction' : function(){
                doMyFunction();
        },                                      (optional)
       'callbackArgs' : '{productData}'         (optional)
            }
            */
        {
            'url' :'/js/psz_zbox.js',
            'resourceType' : 'core',
            'callbackFunction' : function (args) {
                psz.resources.addScriptInclude(args);
            }, 
            'callbackArgs' : {
                'url' : '/js/handlebars.js', 
                'resourceType' : 'core', 
                'callbackFunction' : function(args){
                    psz.resources.addScriptInclude(args);
                }, 
                'callbackArgs' : {
                    'url' : '/js/psz_default_handlebars.js', 
                    'resourceType' : 'core',
                    'callbackFunction' : function(args){
                        psz.resources.addScriptInclude(args);
                    }, 
                    'callbackArgs' : {
                        'url' : '/js/psz_client_handlebars.js', 
                        'resourceType' : 'client',
                        'callbackFunction' : function (args) {
                            psz.resources.addScriptInclude(args);
                        }, 
                        'callbackArgs' : {
                            'url' : '/js/psz_dpw_extension.js',
                            'resourceType' : 'core'
                        }
                    }
                }
            }
        },
        {
            'url' : '/js/psz_linksJS.js', 
            'resourceType' : 'core',
            'callbackFunction' : function(args){
                psz.resources.addScriptInclude(args);
            },
            'callbackArgs' : {
                'url' : '/js/psz_mobile_module.js',
                'resourceType': 'core'
            }
        },
        {
            'url' : '/js/psz_default_product_data_simplifier.js', 
            'resourceType' : 'core'
        },
        {
            'url' : '/js/psz_productLinkModule.js', 
            'resourceType' : 'core'
        },

        {
            'url' : '/js/psz_customLinkModuleRouting.js', 
            'resourceType' : 'core'
        },

        {
            'url' : '/js/jquery.zoom_zmags_version.js', 
            'resourceType' : 'core'
        },

        {
            'url' : '/js/psz_models.js', 
            'resourceType' : 'core'
        },

        {
            'url' : '/js/psz_default_logic.js',
            'resourceType' : 'core'
        }
        ],
        'arrJsExternalFiles': [
        /* 
                Define path / resource object to load up from external site
                Ex.
                '//s7.addthis.com/js/250/addthis_widget.js'
            */
        ],
        //Define what functions to call based on the event.data key name
        'customLinkKeyMapping' : [
        {
            'productLinkId' : function(event){
                psz.viewer.productLink.magentoProductLinkHandler(event);
            }
        }
        ],
        'functions' : {
            // pointers to callbacks which are optionally stored in other files
            // falses are used as a convention here for the convenience of callback syntax
            'onShow' : false,
            'onShowModal' : false,
            'onClose' : false,
            'customHeaders' : false
        },
        //Optionally declare a function to save simplified versions of product event so it is in memory for duration of DPW
        'productDataSimplifier' : false
    }
	
    return default_vars;

}
)(psz.jquery, psz.platform, psz.client.config);
psz.platform = (function psz_platform ($, psz_args){

    var platform = {
        'mobile' : false,
        'social' : false,
        'desktop' : false
    };

    var supportedMobilePlatforms = [
        'iPhone',
        'iPad',
        'Android',
        'iPod Touch'
    ];    

    var supportedSocialPlatforms = [
        {
            'name' : 'facebook',
            'viewer_context' : 'facebook variant'
        }
    ];

    var userAgent = navigator.userAgent.toLowerCase(); 

    platform.userAgent = navigator.userAgent;

    //Figure out if this is a mobile platform...
    for (var i = 0; supportedMobilePlatforms.length > i; i++){
        if (userAgent.search(supportedMobilePlatforms[i].toLowerCase()) > -1){
        platform.mobile = {};
        platform.mobile.device = supportedMobilePlatforms[i];

        //If the platform is reported as ipod, replace it with iPhone. This is to simplify code in other modules.
        if (platform.mobile.device == 'iPod'){
            platform.mobile.device = 'iPhone';
        }
    
        platform.mobile.version = detectMobileVersion();
        break;
        } else {
            platform.mobile = false;
        }
    }
   
    function detectMobileVersion(){
        if(platform.mobile.device == 'iPad' || platform.mobile.device == 'iPhone' || platform.mobile.device == 'iPod Touch') {
            var uA = navigator.userAgent;
            return uA.substring(uA.indexOf("OS") + 3,uA.indexOf("_"));
        } else {
            return 'unknown';
        }
    } 

    //Figure out if this is a social platform 
    if(typeof(zmags_social_context) !== 'undefined'){
        platform.social = {};
        platform.social.network = zmags_social_context;
        for (var i = 0; i < supportedSocialPlatforms.length; i++){
            if(platform.social.network == supportedSocialPlatforms[i].name){
                platform.social.viewer_context = supportedSocialPlatforms[i].viewer_context;
            }
        }        
    }

    if(!platform.social && !platform.mobile){
        platform.desktop = true;
    }

    return platform;

})(psz.jquery, psz.args);
psz.args = (function psz_args (psz_client_config, psz_default_config, psz_platform, $) {

	var psz_args = {};
	
	function merge_arg_objects (primary_config, secondary_config) {

		// merge the objects wholesale 
		var merge_args = $.extend({}, secondary_config, primary_config);

		//set misc convenience stuff
		merge_args.BASE_PATH = merge_args.ZMAGS_DOMAIN + merge_args.CODE_PATH + merge_args.CLIENT_PATH;
		if (merge_args.CATALOG_PATH !== 'undefined') {
			merge_args.CATALOG_PATH = merge_args.ZMAGS_DOMAIN + merge_args.CODE_PATH + merge_args.CATALOG_PATH;
		}
		merge_args.PSZ_PATH = merge_args.ZMAGS_DOMAIN + merge_args.MODULE_PATH + merge_args.module_version;
        merge_args.fullModulePath = merge_args.ZMAGS_DOMAIN + merge_args.MODULE_PATH + merge_args.module_version; 
        merge_args.platform = psz_platform;
		if (merge_args.IS_IN_FACEBOOK) {
			merge_args.cssIncludeFiles.push('/css/fb.css');
			merge_args.cssIncludeFiles.push('/css/navbar.css');
			merge_args.PUBLICATION_ID = merge_args.FACEBOOK_PUBLICATION_ID; 
		} else {
			merge_args.PUBLICATION_ID = merge_args.DESKTOP_PUBLICATION_ID; 
		}

		// merging override and default arrays requires a little more finesse
		if (typeof primary_config.arrJsIncludeFiles !== 'undefined') {
			merge_args.arrJsIncludeFiles = merge_and_add_path_to_arrays('js', primary_config, secondary_config);
		}
		if (typeof primary_config.arrCssIncludeFiles !== 'undefined') {
			merge_args.arrCssIncludeFiles = merge_and_add_path_to_arrays('css', primary_config, secondary_config);
		}

		return merge_args;
	}

	function merge_and_add_path_to_arrays(loopType, primary_config, secondary_config) {

		var loop_array_new;

		// these need to be wrapped in arrays 
		// or else IE sees them as strings inside jQuery
		// and $.inArray fails
		if (loopType == 'js') {
			var loop_array_psz = primary_config.arrJsIncludeFiles;
			var loop_array_def = secondary_config.arrJsIncludeFiles;
		} else if (loopType == 'css') {
			var loop_array_psz = primary_config.arrCssIncludeFiles;
			var loop_array_def = secondary_config.arrCssIncludeFiles;
		}
		loop_array_new = loop_array_def;

		var loop_bound = loop_array_psz.length;
		for (var i = 0; i < loop_bound; i += 1) {
			loop_array_new.push(loop_array_psz[i]);
		}

		return loop_array_new;
	}

	// check for optional per-catalog override file
	if (psz_client_config.global) {
		// overwrite defaults with per-client settings
		psz_args = merge_arg_objects(psz_client_config.global, psz_default_config);
		// overwrite that object with per-catalog settings
		psz_args = merge_arg_objects(psz_client_config.catalog, psz_args);
	} else {
		// backwards compatible for single-config implementations
		psz_args = merge_arg_objects(psz_client_config, psz_default_config);
	}

	return psz_args;

})(psz.client.config, psz.config.base, psz.platform, psz.jquery);
/*
    The purpose of this file to to load client specific logic before the viewer is shown.
*/

psz.client.preload = (function psz_clienBusinessLogic($, psz_args) {

	// this funciton is called by psz_viewers before viewer.show()
	// handlebars may not bw available at this point, hence raw HTML
	function _customHeaders () {
        var custom_markup =
            '
' + ' ' + '
' + ' Previous' + ' ' + psz.client.config.catalog.catalog_name + '' + ' Next' + '
' + //Facbook like button currently inactive. Uncomment below to make facebook like button available /* '
' + ' ' + */ '
    ' + // Temporarily disable Pinterest in the header // '
  • ' + // Facbook like button is in place, uncomment line below to display // '
  • ' + '
  • ' + '
  • ' + '
  • ' + '
  • Table of Contents
  • ' + '
  • Check Out
  • ' + '
  • Close Shop
  • ' + '
' + '
'; // note the HTML fragment here is correct syntax for .wrap // http://api.jquery.com/wrap/ // TODO: should be variable // Insert title if (psz_args.catalog_name){ $('.psz-cat-title').innerHTML = psz_args.catalog_name; } // If this is iPhone, return before appending anything to the dom. if(psz.platform.mobile){ if(psz.platform.mobile.device == 'iPhone'){ return; } } $('#zmagspsCatalogContainer').wrap('
').before(custom_markup); } /** * Returns property for logo anchor. * If shown in a social app, opens the home page in a new window. * * @return {String} */ function getLogoTarget(){ if (psz.platform.social){ return 'target="_blank"'; } return ''; } function _toggleFullScreen(){ if(psz_args.is_full_screen){ $('*').removeClass('fullScreenHideHelper fullScreen IE-scrollbar-fix'); psz_args.is_full_screen = false; } else { $(psz_args.viewer_parent_id).parents().addClass('fullScreen IE-scrollbar-fix'); $(psz_args.viewer_parent_id).parents().siblings().addClass('fullScreenHideHelper'); psz_args.is_full_screen = true; } _desktopWindowResize(); } function _showHeader(){ $(psz_args.customInterface.desktop.interfaceElements.controlsWrapper).show(); } function _desktopWindowResize(){ $(psz_args.viewer_parent_id).css({ //take in account the height of the interface controls above the iframe "height": $(window).height() - psz_args.customInterface.desktop.interfaceHeight + "px", "width": "100%" }); } return { 'showHeader' : function() { _showHeader(); }, 'customHeaders': function () { _customHeaders(); }, 'toggleFullScreen': function() { _toggleFullScreen(); }, 'desktopWindowResize': function(){ _desktopWindowResize(); } } })(psz.jquery, psz.args); psz.resources = (function psz_resources (psz_args, $){ /** * Add JS include to the page. * @param filename The filename to include. */ function addScriptInclude( args ) { var filename; //check to see if this is a javascript object, else use args as a string to load if (typeof(args) === 'string'){ filename = args; //reset empty args object args = {}; } else { //else figure out what kind of resource this is, use the approriate path if (typeof(args.resourceType) === "undefined") { filename = psz_args.PSZ_PATH + args.url; } else { if (args.resourceType == 'core'){ filename = psz_args.PSZ_PATH + args.url; } else if (args.resourceType == 'client'){ filename = psz_args.BASE_PATH + args.url; } else if (args.resourceType == 'catalog'){ filename = psz_args.CATALOG_PATH + args.url; } else if (args.resourceType == 'external'){ filename = args.url; } else { filename = psz_args.PSZ_PATH + args.url; } } } if (typeof(args.scriptID) === "undefined"){ args.scriptID = false; } if (typeof(args.script_type) === "undefined"){ args.script_type = false; } var scriptnode = document.createElement('script'); scriptnode.setAttribute('src', filename); //set optional script element id if (args.scriptID){ scriptnode.setAttribute('id', args.scriptID); } if (args.script_type){ scriptnode.setAttribute('type', args.script_type); } registerOptionalScriptCallback(args, scriptnode); document.getElementsByTagName('head')[0].appendChild(scriptnode); } /** * Checks and registers optional callback. * * @param args * @param scriptnode */ function registerOptionalScriptCallback( args, scriptnode ){ if (args.callbackFunction && typeof(args.callbackFunction) === "function") { if (typeof(args.callbackArgs) === "undefined"){ args.callbackArgs = ''; } if (scriptnode.onreadystatechange === undefined) { scriptnode.onload = function () { args.callbackFunction(args.callbackArgs); }; } else { // lazy load IE7 and 8 scriptnode.timer = setInterval(function () { if (scriptnode.readyState == 'loaded' || scriptnode.readyState == 'complete') { args.callbackFunction(args.callbackArgs); clearInterval(scriptnode.timer); } },100); } } } /** * Add a CSS include to the page. * @param filename The filename to include. */ function addStyleInclude( args ) { var filename; //check to see if this is a javascript object, else use args as a string to load if (typeof(args) === 'string'){ filename = args; //reset empty args object args = {}; } else { // else figure out what kind of resource this is, use the approriate path // default is to client (not catalog) folder if (typeof(args.resourceType) === "undefined") { filename = psz_args.PSZ_PATH + args.url; } else { if (args.resourceType == 'core'){ filename = psz_args.PSZ_PATH + args.url; } else if (args.resourceType == 'client'){ filename = psz_args.BASE_PATH + args.url; } else if (args.resourceType == 'catalog'){ filename = psz_args.CATALOG_PATH + args.url; } else if (args.resourceType == 'external'){ filename = args.url; } else { filename = psz_args.CATALOG_PATH + args.url; } } } var cssnode = document.createElement('link'); cssnode.setAttribute('rel','stylesheet'); cssnode.setAttribute('type','text/css'); cssnode.setAttribute('href', filename); document.getElementsByTagName('head')[0].appendChild(cssnode); if (args.callbackFunction && typeof(args.callbackFunction) === "function"){ args.callbackFunction(args.callbackArgs === undefined ? null : args.callbackArgs); } } function loadIncludeFiles() { // CSS for (var i=0; i < psz_args.arrCssIncludeFiles.length; i++) { psz.resources.addStyleInclude(psz_args.arrCssIncludeFiles[i]); } // Internal JS for (var i=0; i < psz_args.arrJsIncludeFiles.length; i++) { psz.resources.addScriptInclude(psz_args.arrJsIncludeFiles[i]); } // External JS for (var i=0; i < psz_args.arrJsExternalFiles.length; i++) { psz.resources.addScriptInclude(psz_args.arrJsExternalFiles[i]); } } return{ 'addScriptInclude' : function ( file ){ addScriptInclude( file ); }, 'addStyleInclude' : function ( file ){ addStyleInclude( file ); }, 'loadIncludeFiles': function() { loadIncludeFiles(); } } })(psz.args, psz.jquery); /* The purpose of this module is to abstract away the flash viewer initialization. */ psz.viewer = (function psz_viewer(psz_args, $, psz_client){ var is_loaded = false; var is_full_screen = false; function _initialize(){ _customHeaders(); //if this is a mobile device, stop now, the mobile module handles the viewer initialization for mobile devices. if(psz.platform.mobile){ return; } //Figure out pub id to use.. var pubId; if(psz.platform.social){ if(psz_args.publicationIDs[psz.platform.social.network]){ pubId = psz_args.publicationIDs[psz.platform.social.network]; } else { pubId = psz_args.publicationIDs['desktop']; } } else if(psz.platform.desktop){ pubId = psz_args.publicationIDs['desktop']; } else { alert('No publication ID specified for this platform!'); return; } viewer = new ZMAGS.ui.Viewer(); viewer.setPublicationID(pubId); viewer.setParentElementID(psz_args.viewer_parent_id.substr(1)); viewer.setWindowMode('opaque'); //add social context to viewer for publicator analytics sake if(psz.platform.social){ viewer.addContext(psz.platform.social.viewer_context); } //on done loading zmag, bind abstracted link event listeners... viewer.addEventListener(ZMAGS.ui.Viewer.PUBLICATION_OPEN, function(){ is_loaded = true; psz.reporting.track.zmagLoad(); psz.viewer.productLink.bindProductLinkHandler(); psz.viewer.customLink.bindCustomLinkHandler(); }); //Fix for IE Facebook. Originally developed by Bruce Mackenzie to solve issues with the viewer not loading at the right time in IE in a Facebook iFrame. if(psz.platform.social){ // On IE, wait 2 seconds before displaying the viewer if ($.browser.msie) { setTimeout(function(){ viewer.show(); }, 2000); // In 12 seconds, check if the viewer is loaded properly setTimeout(function() { try { viewer.getCurrentPages(); } catch (e) { document.location.href = document.location.href; } }, 12000); } else { viewer.show(); } } else { viewer.show(); } } function _toggleFullScreen(){ if(is_full_screen){ $('*').removeClass('fullScreenHideHelper fullScreen IE-scrollbar-fix'); is_full_screen = false; } else { $(psz_args.viewer_parent_id).addClass('fullScreen'); if($.browser.msie){ $(psz_args.viewer_parent_id).addClass('IE-scrollbar-fix'); } $(psz_args.viewer_parent_id).parents().addClass('fullScreen'); $(psz_args.viewer_parent_id).parents().siblings().addClass('fullScreenHideHelper'); is_full_screen = true; } } function _customHeaders () { if (psz_args.functions.customHeaders) { psz_args.functions.customHeaders.call(); } } function _setViewerIsReady () { is_loaded = true; if(psz_args.functions.onViewerReady){ psz_args.functions.onViewerReady(); } } return { 'initialize' : function(){ _initialize(); }, 'is_loaded' : function(){ return is_loaded; }, 'setViewerIsReady' : function(){ _setViewerIsReady(); }, 'show' : function(){ _show(); }, 'toggleFullScreen' : function(){ _toggleFullScreen(); } } })(psz.args, psz.jquery, psz.client); // Turn on caching for AJAX requests psz.jquery.ajaxSetup({ cache: psz.args.ajax_setup_p }); psz.resources.loadIncludeFiles(); psz.viewer.initialize();