blds/BldsClient.js

/**
 * @fileoverview Client layer for Blds - Ballingslöv Digital Showroom.
                 The VDS stack will roughly consist of
 *               Layer 1: GUI, Layer 2: Client, Layer 3: Server
 *
 * NOTE: All public/exported functions in this module should implement runtime
 *       type checking for arguments to allow easier debugging when calling
 *       from uncompiled code.
 *
 * @author Kim Simmons (kim.simmons@spark-vision.com)
 */



goog.provide('spv.blds.BldsClient');

goog.require('spv.assert');
goog.require('spv.ds.assert');
goog.require('spv.ds.Config');
goog.require('spv.ds.DsClient');
goog.require('spv.ds.MenuItem');
goog.require('spv.ds.ImageSerieInfo');
goog.require('spv.ds.Summary');



/**
 * @export
 * @constructor
 * @extends {spv.ds.DsClient}
 * @param {string} service_url
 * @param {spv.ds.DsClientSettings=} in_settings
 */
spv.blds.BldsClient = function(service_url, in_settings)
{
    spv.assert.isNonEmptyString(service_url, 'service_url');
    // Default settings.
    var default_settings = new spv.ds.DsClientSettings();
    default_settings.use_jsonp = false;
    default_settings.use_nova_protocol = true;
    default_settings.debug_print = false;
    default_settings.initial_category = "SdsMain";

    /** @type {!spv.ds.DsClientSettings} */
    var settings = in_settings ? default_settings.overloadWith( in_settings ) : default_settings;
    spv.ds.DsClient.call( this, service_url, settings );

    /**
      * No specialized protocol for this client as of yet.
      */
};

goog.inherits(spv.blds.BldsClient, spv.ds.DsClient);



/**
 * Initialize session.
 * This must be called before calling any other member function with "session".
 * For most other session calls a configuration needs to be loaded as well.
 *
 * This is not done in the constructor in order to allow state-/session-less
 * calls without initializing a session.
 *
 * This override makes sure the initial session state is updated.
 *
 * Preconditions: None.
 *
 * @export
 * @override
 * @param {string} menu_query
 * @param {spv.ds.ipprot_nova.input.BopReadMoreMode} readmore_mode
 * @param {function(spv.ds.IntroPage)} on_success
 * @param {function(Error)} on_failure
 */
spv.blds.BldsClient.prototype.sessionInit = function(
		menu_query,
		readmore_mode,
		on_success,
		on_failure)
{
    var that = this;
    /**
     * @param {spv.ds.IntroPage} intro_page
     */
	var on_session_inited = function(intro_page)
	{
		var on_update_bop = function()
		{
			on_success(intro_page);
		}
		
		var session_id = that.getSessionId();
		that.sessionUpdateBop(on_update_bop, on_failure);
	}
	
    var state_update = goog.base(
		this,
		'sessionInit',
		menu_query,
		readmore_mode,
		on_session_inited,
		on_failure);
};



/**
 * Get the last state update. Call this function on successful load of configs
 * or on successful sessionToggleItem.
 *
 * Preconditions: Initialized session, loaded configuration.
 *
 * @export
 * @override
 * @return {spv.ds.SessionStateUpdate}
 */
spv.blds.BldsClient.prototype.sessionGetStateUpdate = function()
{
    /* Filter away root menus from incoming bops. */

    /** @type {spv.ds.SessionStateUpdate} */
    var state_update = goog.base( this, 'sessionGetStateUpdate' );
    /** @type {Array.<spv.ds.MenuItem>} */
    var root_menus = spv.ds.DsClient.prototype.sessionGetRootMenuItems.call( this );
    /** @type {Object.<string, boolean>} */
    var is_root_menu = {};
    for( var i=0; i < root_menus.length; ++i )
        is_root_menu[ root_menus[i].id ] = true;

    /** @type {Array.<spv.ds.MenuItem>} */
    var filtered_items = [];
    // If a menu is encountered, then this collection of menu items should be menus only.
    /** @type {Array.<spv.ds.MenuItem>} */
    var filtered_menus = [];
    for( var i=0; i < state_update.menu_items.length; ++i )
    {
        var item = state_update.menu_items[i];
        if( !is_root_menu[ item.id ] )
        {
            if( spv.blds.BldsClient.isMenu( item.id ) )
                filtered_menus.push( item );
            else
                filtered_items.push( item );
        }
    }

    state_update.menu_items =
        filtered_menus.length > 0 ?
        filtered_menus : filtered_items;
    return state_update;
};



/**
 * Get the summary for the current session and configuration.
 * This override excludes the item summaries with no text.
 *
 * Preconditions: Initialized session, loaded configuration.
 *
 * @export
 * @override
 * @param {function(spv.ds.Summary)} on_success
 * @param {function(Error)} on_failure
 */
spv.blds.BldsClient.prototype.sessionGetSummary = function(on_success, on_failure)
{
    spv.assert.isFunction(on_success, 'on_success');
	
	/**
	 * @param {spv.ds.Summary} summary
	 */
	var on_success_wrapped = function(summary)
	{
		var filtered = [];
		for(var i = 0; i < summary.item_summaries.length; i++)
		{
			var item_summary = summary.item_summaries[i];
			if(item_summary.item_text)
				filtered.push(item_summary);
		}
		summary.item_summaries = filtered;
		
		on_success(summary);
	}
	
	goog.base( this, 'sessionGetSummary', on_success_wrapped, on_failure );
};



/**
 * Finds the article number for the items in a summary.
 * Use BldsClient.sessionGetSummary() to fetch the configuration's summary.
 * @export
 * @param {spv.ds.Summary} summary
 * @return {Array.<string>}
 */
spv.blds.BldsClient.prototype.sessionGetSummaryArticleNumbers = function( summary )
{
    spv.assert(
        summary.constructor === spv.ds.Summary,
        "spv.ds.Summary object required for argument 'summary'." );
    var summaries = summary.item_summaries;
    /** @type {Array.<string>} */
    var arts = [];
    for( var i=0, ii=summaries.length; i < ii; ++i )
    {
        var additionals = summaries[i].aux_data['additional_item_data'];
		var payload = additionals ? additionals['Payload'] : null;
        var art = payload ? payload['art_no'] : null;
        if( art ) arts.push(art);
    }
    return arts;
};




/**
 * @export
 * @param {spv.ds.ItemSummary} item_summary
 * @return {?string}
 */
spv.blds.BldsClient.prototype.sessionGetItemSummaryArticleNumber = function( item_summary )
{
    spv.assert(
        item_summary.constructor === spv.ds.ItemSummary,
        "spv.ds.SummaryItem object required for argument 'item_summary'." );
    var result = spv.blds.BldsClient.extractArticleNumberFromAux( item_summary.aux_data );
    if( result.state === "FOUND" )
        return result.value;
    else if( result.state === "NO_AUX_DATA" )
        return null;
    else
        throw Error( "Unexpected aux data layout for item: " + item_summary.item_id + "\n" + result.state );
};



/**
 *
 * @param {Object} item_aux_obj
 * @return {Object.<string,string>}
 */
spv.blds.BldsClient.extractArticleNumberFromAux = function( item_aux_obj )
{
    if( item_aux_obj === undefined )
        return {state: "NO_AUX_DATA"};
    var additionals = item_aux_obj['additional_item_data'];
    if( additionals === undefined )
        return {state: "NO_AUX_DATA"};
	var payload = additionals['Payload'];
	if( payload === undefined )
		return {state: "NO_AUX_DATA"};
    var art_no = payload['art_no'];
    if( art_no === undefined )
        return {state: "NO_ARTNO_AUX_DATA"};

    return {state: "FOUND", value: art_no};
};




/**
 * Makes a lookup for the given item id to see if it has an article number.
 * An object is returned with {state: somestate, value: thearticlenumber}.
 * Possible state values:
 *   "FOUND": An article number was found and is returned in the value field.
 *   "NO_AUX_DATA": There's no additional data for this item, what so ever.
 *   "NO_SPECIFIC_AUX_DATA": There's no Blds specific aux data for this item.
 *   "NO_ARTNO_AUX_DATA": It has Blds aux data, but no article number in it.
 *
 * Only if state === "FOUND" will the value contain a string with the article number.
 * .value will be undefined in all other cases.
 * @export
 * @param {string} item_id
 * @return {Object.<string,string>}
 */
spv.blds.BldsClient.prototype.sessionCheckForArticleNumber = function( item_id )
{
    spv.assert.isNonEmptyString( item_id, "item_id" );
    var aux = this.sessionGetAuxiliaryData();
    var item_aux = aux.item_aux[ item_id ];
    return spv.blds.BldsClient.extractArticleNumberFromAux( item_aux );
};




/**
 * Gets the article number for an item or null if it doesn't have one.
 * @export
 * @param {string} item_id
 * @return {?string}
 */
spv.blds.BldsClient.prototype.sessionGetArticleNumber = function( item_id )
{
    spv.assert.isNonEmptyString( item_id, "item_id" );

    var result = this.sessionCheckForArticleNumber( item_id );
    if( result.state === "FOUND" )
        return result.value;
    else if( result.state === "NO_AUX_DATA" )
        return null;
    else
        throw Error( "Unexpected aux data layout for item: " + item_id + "\n" + result.state );
};



/**
 * Gets the current configuration.
 *
 * Preconditions: Initialized session, loaded configuration.
 * This requires the CurrentConfigAuxPlugin to work.
 *
 * @export
 * @return {spv.ds.Config}
 */
spv.blds.BldsClient.prototype.sessionGetCurrentConfig = function()
{
    var aux = this.sessionGetAuxiliaryData();
	if(!aux)
		throw new Error("Auxiliary data missing");
	
	var config_aux = aux.bop_aux["CurrentConfig"];
	if(!config_aux)
		throw new Error("CurrentConfig auxiliary not found");
	
	var encoding = config_aux["encoding"] || "";
	var config_string = config_aux["config_string"];
	if(!config_string)
		throw new Error("Current config missing from auxiliary data");
	
	return new spv.ds.Config(encoding, config_string);
};



/**
 * Try to change item state for one item in the current configuration.
 * An item has two states, On or Off, eg. an item is part of the current
 * configuration or it is not. The result of this operation can only be
 * determined by the server. Any call to this function may result in suggestions
 * being returned through the callback on_suggestions.
 *
 * Use DsClient.setLanguage in order to specify language. Leaving it undefined
 * will cause the backend to resolve a preferred instead.
 *
 * Preconditions: Initialized session, loaded configuration, caller code has a
 *                valid instance of spv.ds.MenuItem.
 *
 * @export
 * @override
 * @param {spv.ds.MenuItem} menu_item
 *        Menu item to toggle on or off. User code should not modify or
 *        construct instances of MenuItem, only pass MenuItem instances returned
 *        by this class.
 *
 * @param {Function} on_success
 *        Will be called on toggle success if toggle has no important side
 *        effects. Caller code should call sessionGetStateUpdate on this object
 *        to get the latest session state update.
 *
 * @param {function(Array.<spv.ds.Suggestion>, spv.ds.BipAttemptResultCode, string)} on_suggestions
 *        Will be called if item toggle has important side effects or if
 *        relevant alternative configuration states has been found.
 *
 * @param {function(Error)} on_failure
 *        Called on any kind of failure.
 */
spv.blds.BldsClient.prototype.sessionToggleItem = function(
		menu_item,
		on_success,
		on_suggestions,
		on_failure)
{
	spv.ds.assert.isMenuItem(menu_item, 'menu_item');
	spv.assert.isFunction(on_success, 'on_success');
	spv.assert.isFunction(on_failure, 'on_failure');
	this.assertInitializedSessionWithConfig();

	var that = this;
	var category_id = menu_item.category;
	var item_id = menu_item.id;

	/** @param {spv.ds.SessionStateUpdate} ssu
	  * @param {spv.ds.impl.AuxData} aux_data */
	var on_success_wrapper = function(ssu, aux_data)
	{
		that._session_state.update(ssu, aux_data);
		on_success();
		that.sessionDequeueNextRequest();
	};
	var on_failure_wrapper =
		this.constructFailureWrapperForQueue( this.errorHandler(on_failure) );

	var session_request = function()
	{
		try
		{
			that._protocol.sessionBip(
					that.getSessionId(),
					category_id,
					[item_id],
					that.getLanguage(),
					on_success_wrapper,
					on_failure_wrapper);
		}
		catch (err)
		{
			on_failure_wrapper(err);
		}
	};

	this.sessionEnqueueRequest(session_request);
};



/**
 * @private
 * @param {string} item_id
 * @return {boolean}
 */
spv.blds.BldsClient.isMenu = function( item_id )
{
    spv.assert.isNonEmptyString( item_id, "item_id" );
    return /^SUBMENU|^MENU/.test( item_id );
};