/*-------------------------------------------------------------------------------------
       Name: nsb_portal.js
Description: javascript for NSBPortal
         NB: most of this originally appeared in NDF 
         By: MAJW - mayjay@blueyonder.co.uk
  Copyright: (c)2007 - Nomad Digital Ltd All Rights Reserved
             www.nomadrail.co.uk.com
-------------------------------------------------------------------------------------*/


//  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// some flags to help debugging
DBG_SHOW_UPDATES = false;

// this is the name space for all our code
NSB = {};

//  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// for the moment, we'll keep the required sizes of the popup panel here
var k_popup_panel_dims = { width: 304, height: 365 };

// --------------------------------------------------------------------------------------
// called every time a page is loaded
// --------------------------------------------------------------------------------------
// 090807 majw - removed cos its not used
// function loadPage()
//     {
// 
//     }

// --------------------------------------------------------------------------------------
// bookmark - from 
//          http://labnol.blogspot.com/2006/01/add-to-favorites-ie-bookmark-firefox.html
//     may need extra work
// --------------------------------------------------------------------------------------

function bookmarkThePortal( ilocale )
    {
    url = "http://interaktiv.nsb.no/";
    if( ilocale == 'nb_no' )
        title = "NSB INTERAKTIV"; 
    else    
        title = "NSB INTERAKTIV"; 
    
    if( window.sidebar ) 
        {           // Mozilla Firefox Bookmark
        window.sidebar.addPanel(title, url,"");
        }
    else if( window.external ) 
        {           // IE Favorite
        window.external.AddFavorite( url, title); 
        }
    else if( window.opera && window.print ) 
        {           // Opera Hotlist
        return true; 
        }
    };

// --------------------------------------------------------------------------------------
// General spinner
// --------------------------------------------------------------------------------------
NSB.GenericSpinner = Class.create(
    {
    initialize: function( ispinner_div, iconfig )
        {
        var dims = $( ispinner_div ).getDimensions();
        // square box 
        this.canvas_height = this.canvas_width = dims.width;
        radius_percent = iconfig.percent || 100;
        this.calcRadii( dims, radius_percent );
        this.canvas = new Raphael( $( ispinner_div ),  
                                   this.canvas_width,
                                   this.canvas_height );
        this.configureSpinner( iconfig );
             
        // x and y of the center of spinner 
        var center = this.calcCenter();
        this.center_x = center.x;   
        this.center_y = center.y;

        this.sectors = [];          // sector lines
        this.opacity = [];          // opacity of sector lines
        this.beta = 2 * Math.PI / this.sectors_count;  
        this.path_params = { "stroke": this.ticker_color, 
                             "stroke-width": this.sector_width, 
                             "stroke-linecap": this.sector_line_cap };
        this.drawSpinner();
        },

    calcRadii: function( idiv_dims, ipercent )
         {
         /* We calculate inner radius as a percentage outer radius */
         outer_radius = idiv_dims.width / 2;
         this.inner_radius = ( ( 100 - ipercent ) / 100 ) * outer_radius;
         this.outer_radius = outer_radius;  
         }, 
 
    
    configureSpinner: function( iconfig )
        {
        /* NB override this method from child class 
           with right config object. Default values
           enable us to change certain properties alone */ 
        this.sectors_count = iconfig.sectors_count || 8;
        this.ticker_color = iconfig.color || "#000";
        this.sector_width = iconfig.sector_width || 2;
        this.sector_line_cap = iconfig.sector_line_cap || "round";    
        }, 

    calcCenter: function()
        {
        var center = { 'x': this.canvas_width / 2,
                       'y': this.canvas_height / 2  };
        return center;
        },  

    drawSpinner: function()
        {
        for( var i = 0; i < this.sectors_count; i++ ) 
            {
            // angle between current dash and initial state
            var teta = this.beta * i - Math.PI / 2; 
            cos_teta = Math.cos( teta );
            sin_teta = Math.sin( teta );
            // initial opacity for current dash
            this.opacity[ i ] = ( 1 / this.sectors_count * i );  
            r1 = this.inner_radius;
            r2 = this.outer_radius;
            cx = this.center_x;
            cy = this.center_y;  

            /* NB This is a really cool formula to determine a point on circle 
                  using parametric equation. Move to a point and draw a 
                  line from inner circle to outer */
            this.sectors[ i ] = this.canvas.path( this.path_params )     
                                            .moveTo( cx + r1 * cos_teta, cy + r1 * sin_teta )   
                                            .lineTo( cx + r2 * cos_teta, cy + r2 * sin_teta );  
            
            } 

        /* NB Time here determines the speed of the spinner */
        this.animate_spinner_interval = this.sectors_count / 100;
        new PeriodicalExecuter( this.animateSpinner.bind( this ),
                                this.animate_spinner_interval ); 
        },

    animateSpinner: function()
        {
        // last element is made first element here 
        this.opacity.unshift( this.opacity.pop() );
        for ( var i = 0; i < this.sectors_count; i++ ) 
            {
            this.sectors[ i ].attr( "opacity", this.opacity[ i ] ); 
            }
        }
 
    } );

// --------------------------------------------------------------------------------------
// small spinner
// --------------------------------------------------------------------------------------
NSB.SmallSpinner = Class.create( NSB.GenericSpinner,
    {
    initialize: function( $super, ispinner_div )
        {
        /* 
          sector count - number of lines in spinner
          sector width - thickness of the lines in spinner
          percent - [ inner radius = percentage of outer radius ]
          sector_line_cap - finishing of the sector lines, it can be
                            butt, square or round     
        */ 
        var config = { 'sectors_count': 12,
                       'color': '#990000',
                       'sector_width': 2, 
                       'percent': 50,
                       'sector_line_cap': "butt" };

        $super( ispinner_div, config );
        }  
    } );


// --------------------------------------------------------------------------------------
// here for debugging, belongs in mac_purchase.pt
// --------------------------------------------------------------------------------------

doShowPurchaseVoucher = function( ishow )
    {
    var show_already = $( 'purchase_choose_voucher' ).getStyle( 'display' ) != 'none';
    if( show_already == ishow )
        return;                 // no change required
    
    doAjaxAdapterCallJS( 'user_state', 'updateUSValue',
                                {
                                igname : 'purchase',
                                icname : 'show_voucher',
                                ivalue : ishow
                                } );
                                
    $( 'select_voucher_arr' ).toggleClassName( 'open' );
    $( 'select_voucher_lbl' ).toggleClassName( 'open' );
    $( 'select_ccard_arr' ).toggleClassName( 'open' );
    $( 'select_ccard_lbl' ).toggleClassName( 'open' );
    
    if( ishow )
        {
        new Effect.SlideDown( 'purchase_choose_voucher' );
        new Effect.SlideUp( 'purchase_choose_ccard' );
        }
    else
        {
        new Effect.SlideUp( 'purchase_choose_voucher' );
        new Effect.SlideDown( 'purchase_choose_ccard' );
        }
    };


// --------------------------------------------------------------------------------------
// here for debugging, belongs in mac_feedback.pt, but not actually used
// --------------------------------------------------------------------------------------
submitFeedbackForm = function( ievent )
    {
    var form_vals = Form.serializeElements( $( "feedback_form_form" ).getElements(), true );
    
    doAjaxAdapterCallJS( 'feedback', 'submitFeedbackForm', 
        form_vals,
        'feedback_form',
        onFeedbackFormSubmit );           
    };
    
onFeedbackFormSubmit = function( iresponse )
    {   
    };

closeFeedbackForm = function( ievent )
    {
    NSB.popup_overlay.onCloseButton();  // this is a bit of a liberty :-)
        
    };
    
// --------------------------------------------------------------------------------------
logoutUser = function( ilang_flag )
    {
    var msg = '';
    /* NB This translation must have
          come from server,looks for mom OK tho */
    if ( ilang_flag == 'True' )
        var msg = 'Are you sure you want to logout?';
    else
        var msg = 'Er du sikker på at du vil logge av';

    var ok = confirm( msg );
    if( ok )
        {
        doAjaxAdapterCallJS( 'purchase', 'logoutUser', {} );
        //doAjaxElemInvalidates( $( 'user_summary_bar' ) );
        setTimeout( "window.location =  '/view_entertainment'", 3000 );
        }
    };

// --------------------------------------------------------------------------------------
// Redirect
// --------------------------------------------------------------------------------------
doRedirect = function( iname )
    {
    /* mainly for the movies :-) */
    
    var type = iname.split("_");
    if( type[ 0 ] == "view")  
        window.location = "/" + iname;
    if( iname == 'entertainment' )
        window.location = '/view_entertainment';

    else if( iname == 'login' )
        window.location = '/view_purchase';

    else
        {
        // this is for movies
        window.location = '/media/sfa/playlists/film_'+iname+'.asx';
        }
    };

// --------------------------------------------------------------------------------------
// play movie
// --------------------------------------------------------------------------------------
playMovie = function( iid, ilanguage )
    {
    new NSB.DRMLicensingInfoLayer( iid, ilanguage );
    };

// --------------------------------------------------------------------------------------
// Movie player class
// --------------------------------------------------------------------------------------
NSB.DRMKeyGetter = Class.create(
    {
    initialize: function()
        {
        this.component = document.netobj;
        this.key_getter = new PeriodicalExecuter( this.keyGetter.bind( this ), 30 );
        /* NB onTimerEvent called directly so we don't have
              concurrent instances of key_getter. It would try 
              and fetch the key immediately, ONLY on error it 
              should wait for 30 secs */
        this.key_getter.onTimerEvent();
        this.got_key = false; 
        this.active_key_present_already = false;
        },

    keyGetter: function()
        {
        /* Try fetching key here, if it works kill the periodical executer */
        var client_info = 'clientinfo=' + this.component.GetSystemInfo();
        var req_vars = $H({});
        req_vars.set( 'zmethod', 'getWMKey' );
        req_vars.set( 'zadapter', 'media_movies' );
        req_vars.set( 'zmethod_args', 'iclient' );
        req_vars.set( 'iclient', client_info );

        var rqst = new Ajax.Request( "handleAjaxAdapterCall",
                                            {
                                            method: 'post',
                                            parameters: req_vars,
                                            onSuccess: this.onKeyGetter.bind( this ),
                                            onFailure: this.onDRMLicenseGetterFailure.bind( this ),
                                            onException: onAjaxFailClient,
                                            asynchronous: true
                                            } );
        },

    onKeyGetter: function( iresponse )
        {
        /* NB '1' - Movie already watched
              Any other response is key, and if
              nothing comes back send req back
              to server */
        if ( iresponse.responseJSON && iresponse.responseJSON != '1' )
            {
            this.key_getter.stop();
            var key = iresponse.responseJSON;
            this.component.StoreLicense( key );
            this.got_key = true;
            }
             
        else if ( iresponse.responseJSON == '1' )
            {
            this.key_getter.stop();
            this.active_key_present_already = true;
            }
        },

    onDRMLicenseGetterFailure: function( iresponse )
        {
        this.key_getter.stop();
        this.key_getter = null;  
        this.drm_failure_msg = iresponse.getResponseHeader( 'nsb-exception-val' );
        }, 

    isKeyGetterRunning: function()
        {
        if( this.key_getter )
            return true;

        return false;
        },

    getDRMKeyGetterFailureMsg: function() 
        {
        return this.drm_failure_msg;
        },

    getGotKey: function()
        {
        // this sets to true when client got key 
        return this.got_key;
        },

    isKeyPresentAlready: function()
        {
        return this.active_key_present_already;
        }   

    } );

// --------------------------------------------------------------------------------------
//   Update Media Credits 
// --------------------------------------------------------------------------------------
NSB.UpdateMediaCredits = Class.create(
    {
    initialize: function()
        {
        this.credit_updated = false; 
        this.updateCredits();
        },

    updateCredits: function()
        {
        /* NB To avoid busy layer we send a generic req than doAjaxAdapterCallJS. */ 
        var req_vars = $H({});
        req_vars.set( 'zmethod', 'updateMediaCredits' );
        req_vars.set( 'zadapter', 'media_movies' );
        this.credits_updater = new Ajax.Request( "handleAjaxAdapterCall",
                                            {
                                            method: 'GET',
                                            parameters: req_vars,
                                            onSuccess: this.onUpdateMediaCredits.bind( this ),
                                            onFailure: this.onUpdateMediaCreditsFail.bind( this ),
                                            onException: onAjaxFailClient,
                                            asynchronous: true
                                            } ); 
        
        }, 

    onUpdateMediaCredits: function()
        {
        // redraw user summary bar to show new media credit
        this.credit_updated = true;
        doAjaxElemInvalidate( $( 'user_summary_bar' ) );
        },

    onUpdateMediaCreditsFail: function( iresponse )
        {
        this.credits_updater = null; 
        this.failure_msg = iresponse.getResponseHeader( 'nsb-exception-val' );
        },

    getMediaCreditUpdaterFailureMsg: function()
        {
        return this.failure_msg;  
        },

    getMediaCreditsUpdated: function()
        {
        return this.credit_updated; 
        },

    isCreditUpdaterRunning: function()
        {
        return this.credits_updater;
        }  
 
    } );

// --------------------------------------------------------------------------------------
//   Movie Emailer class
// --------------------------------------------------------------------------------------
NSB.MovieLinkEmailer = Class.create(
    {
    initialize: function()
        {
        this.movie_link_emailed = false; 
        this.emailMovieLink();
        },

    emailMovieLink: function()
        {
        /* NB To avoid busy layer we send a generic req than doAjaxAdapterCallJS. */ 
        var req_vars = $H({});
        req_vars.set( 'zmethod', 'emailMovieLink' );
        req_vars.set( 'zadapter', 'media_movies' );
        this.movie_emailer = new Ajax.Request( "handleAjaxAdapterCall",
                                            {
                                            method: 'GET',
                                            parameters: req_vars,
                                            onSuccess: this.onMovieLinkEmailed.bind( this ),
                                            onFailure: this.onMovieLinkEmailerFailed.bind( this ),
                                            onException: onAjaxFailClient,
                                            asynchronous: true
                                            } ); 
        
        },

    onMovieLinkEmailed: function()
        {
        this.movie_link_emailed = true;
        },

    onMovieLinkEmailerFailed: function( iresponse )
        {
        this.movie_emailer = null; 
        this.failure_msg = iresponse.getResponseHeader( 'nsb-exception-val' );
        },

    getMovieLinkEmailerFailureMsg: function()
        {
        return this.failure_msg;
        },

    getMovieLinkEmailed: function()
        {
        return this.movie_link_emailed; 
        },

    isMovieLinkEmailerRunning: function()
        {
        return this.movie_emailer;
        }

    } );

                        
// --------------------------------------------------------------------------------------
NSB.GeneralPopup = Class.create(
    {
    initialize: function( iopener_id, iopened_dims, imacro_name )
        {   /* iopener_id = id of elem that does the opening; iopened_dims =
                width / height of opened popup; imacro_name name (path) of
                macro to be rendered in the popup */
        this.opener_elem = $( iopener_id );
        var opener_pos = this.opener_elem.viewportOffset();
        var opener_dims = this.opener_elem.getDimensions();
        this.opener_bnds = { 
                    left: opener_pos.left, 
                    top: opener_pos.top,
                    wid: opener_dims.width,
                    hgt: opener_dims.height
                    };
        this.opened_bnds = NSB.calcCenterBounds( iopened_dims );
        this.macro_name = imacro_name;
        },
        
    openPopup: function()
        {
        new NSB.ZoomRect( $( 'popup_overlay_effect_img' ),
                {
                bgn_bnds: this.opener_bnds, 
                end_bnds: this.opened_bnds,
                afterFinish: this.onPopupOpened.bind( this )
                } );        
        },
    
    onPopupOpened: function( iresponse )
        {
        doAjaxAdapterCallJS( 'render', 'renderMacro', 
            { imacro_path: this.macro_name }, '', this.onPopupRendered.bind( this ) );
        },

    onPopupRendered: function( iresponse )
        {   /* I really think its a lot clearer to have explicit callback 
                functions even though its a bit more code (easier to debug too) */
        NSB.popup_overlay.setContents( eval( iresponse.responseText ) );
        NSB.popup_overlay.setBounds( this.opened_bnds );
        NSB.popup_overlay.setCloseZoomBounds( this.opener_bnds );
        $( 'popup_overlay_effect_img' ).setStyle( { display: 'none' } );
        }
    } );
    
    
//  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NSB.SubtitleInfoPopup = Class.create( NSB.GeneralPopup,
    {
    initialize: function( $super )
        {
        $super( 'subtitle_info', { width: 400, height: 420 }, 
                                            'mac_movies/macros/mac_subtitles_info' );
        }
    } );
    
//  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NSB.ChooseMediaPlayerPopup = Class.create( NSB.GeneralPopup,
    {
    initialize: function( $super )
        {
        $super( 'choose_media_player', { width: 360, height: 220 }, 
                                            'mac_movies/macros/choose_media_player' );
        }
    } );

//  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NSB.ChooseFlashPlayerPopup = Class.create( NSB.GeneralPopup,
    {
    initialize: function( $super )
        {
        $super( 'choose_flash_player', { width: 360, height: 340 }, 
                                            'mac_games/macros/choose_flash_player' );
        }
    } );
    
// --------------------------------------------------------------------------------------
// download movie
// --------------------------------------------------------------------------------------
downloadMovie = function( iid )
    {
    //doAjaxAdapterCallJS( 'media_movies', 'downloadMovieLink',
    //                                   {
    //                                   ivalue : iid + '.asx'
    //                                   } );
    };

// --------------------------------------------------------------------------------------
// play trailer
// --------------------------------------------------------------------------------------
playTrailer = function( iid )
    {
    window.location = '/media/sfa/playlists/trailer_'+iid+'.asx';
    };

// --------------------------------------------------------------------------------------
// Display Error Msg
// --------------------------------------------------------------------------------------
displayMsg = function( imsg )
    {
    // FIXME Temporary solution to show err_msg
    gBusyLayer.startBusy( imsg );
    //alert( imsg );
    };

// --------------------------------------------------------------------------------------
// Msg Display for portal
// --------------------------------------------------------------------------------------
NSB.MessageDisplay = Class.create(
    {
    initialize: function( imsg )
        {
        this.display_el = gBusyLayer;
        if( !this.display_el )
            this.display_el = new NSB.BusyLayer();

        this.display_el.startBusy( imsg );
        }

    } );


// --------------------------------------------------------------------------------------
// play movie
// --------------------------------------------------------------------------------------
openGameWindow = function ( ielem )
    {
    var igname = ielem.getAttribute( 'game' );
    // send the name of the game
    doAjaxAdapterCallJS( 'media_games', 'setGameToOpen', {
                                        igname : 'media_games',
                                        icname : 'play_game',
                                        ivalue : igname 
                                        },
                        '',
                        function() { window.open("view_game_player", "game_window","width=640,height=450"); }
                        );
    // now open it in a new window
    
    // window.open("view_game_player", "width=640,height=450");
    };

// --------------------------------------------------------------------------------------
// play movie
// --------------------------------------------------------------------------------------
openMusicoveryWindow = function ()
    {
    /*
    Do we really need this call??
    // send the name of the game
    doAjaxAdapterCallJS( 'media_games', 'setGameToOpen', {
                                        igname : 'media_games',
                                        icname : 'play_music',
                                        ivalue : 'openMusicovery' 
                                        } 
                        );
    */
    // now open it in a new window
    
    window.open("view_music_player", "music", 'width=' + screen.availWidth + ',height=' + screen.availHeight );
    };
    
// --------------------------------------------------------------------------------------
// User Login
// --------------------------------------------------------------------------------------
submitVoucherAccess2 = function()
    {   /* 090821 majw - this is only called from the purchase_voucher form */
    doAjaxAdapterCallJS( 'purchase', 'loginUser2',
                                            {
                                            iaccess_code: $( 'voucher_code' ).value,
                                            iemail_address: $( 'email_address' ).value
                                            },
                                            '',
                                            onSubmitVoucherAccess );
    };

/* the original */
submitVoucherAccess = function( itext_box )
    {
    val = $( itext_box ).value;
    // send an ajax request
    doAjaxAdapterCallJS( 'purchase', 'loginUser',
                                            {
                                            iaccess_code: val
                                            },
                                            '',
                                            onSubmitVoucherAccess );
    };

onSubmitVoucherAccess = function( iresponse )
    {
    var xxx = iresponse.responseJSON;
    // FIXME do we need to check the return code.
    doRedirect( 'login' );
    };
    
redirectToVoucherEntry = function()
    {
    doAjaxAdapterCallJS( 'purchase', 'setupForVoucherEntry', {}, '',
                function() { window.location = '/view_purchase'; } );
    };
    
// --------------------------------------------------------------------------------------
// User Countdown 
// --------------------------------------------------------------------------------------
NSB.UserCountdown = Class.create(
    {
    initialize: function( ijelem_id, itime )
        {
        this.timer_elem = $( ijelem_id );
        
        var parts = itime.split(':');
        this.hours = parts[ 0 ];
        this.minutes = parts[ 1 ];
        this.seconds = parts[ 2 ];
        this.updater = new PeriodicalExecuter( this.UpdateTime.bind( this ), 1 );
        },
        
    UpdateTime: function()
        {
        if(this.hours != 0 || this.minutes != 0 || this.seconds != 0)   
            {
            this.seconds--;
            if( this.seconds < 0 )  
                {
                this.seconds = 59;
                this.minutes--;
                if( this.minutes < 0 )  
                    {
                    this.minutes = 59;
                    this.hours--;
                    if( this.hours < 0 )
                        this.hours = 0;
                    }
                }
            var hours = this.hours + "";
            var minutes = this.minutes + "";
            var seconds = this.seconds + "";
            
            if( hours.length < 2)
                hours = "0" + hours;
            if( minutes.length < 2)
                minutes = "0" + minutes;
            if( seconds.length < 2 )
                seconds = "0" + seconds;
                
            this.timer_elem.update( hours + ":" + minutes + ":" + seconds );
            }
        }
    });
    
// --------------------------------------------------------------------------------------
// Movies slider
// --------------------------------------------------------------------------------------
NSB.MoviesSlider = Class.create(
    {
    initialize: function( iscroll_handle_id, iscroll_track_id, ijelem_id, ileft )
        {
        this.movie_elem = $( ijelem_id );
        this.slide_contents = $( 'movies_slide' ).selectPart( '#movies_slide_contents' );
        this.slide_contents.setStyle( { 'left': parseInt( ileft ) + 'px' } );
        this.slider_max = parseInt( this.slide_contents.getStyle( 'width' ) ) - ( 4 * 187 );
        this.prev_val = 0;
        this.slider = '';

        /* ------- Initiate the slider -------- */
        // may use this class variable later on??
        this.movie_slider = new Control.Slider( iscroll_handle_id, iscroll_track_id,
                                            {
                                            axis: 'horizontal',
                                            range: $R( 0, this.slider_max ),
                                            sliderValue: Math.abs( ileft ),
                                            onSlide: this.onMovieSlide.bind( this ),
                                            onChange: this.onMovieChange.bind( this )
                                            } );
        },

    onMovieSlide: function( ivalue )
        {
        this.mover_increment = 0;
        this.next_val = ivalue;
        if( this.slider )
            return;

        this.mover = this.prev_val;
        this.slider = new PeriodicalExecuter( this.doSlide.bind( this ), 0.05 );
        this.prev_val = ivalue;
        },

    onMovieChange: function( ivalue )
        {
        this.onMovieSlide( ivalue );
        // send an ajax request
        doAjaxAdapterCallJS( 'user_state', 'updateUSValue',
                                            {
                                            igname: 'media_movies',
                                            icname: 'movies_slide_pos',
                                            ivalue: parseInt( this.slide_contents.getStyle( 'left' ) )
                                            } );
        },

    doSlide: function()
        {
        this.mover = ( this.next_val + this.mover ) / 2;
        this.slide_contents.setStyle( { 'left': '-' + this.mover + 'px' } );
        }

    } );

// --------------------------------------------------------------------------------------
// Games slider
// --------------------------------------------------------------------------------------
NSB.GameSlider = Class.create(
    {
    initialize: function( iscroll_handle_id, iscroll_track_id, ijelem_id, ileft )
        {
        this.movie_elem = $( ijelem_id );
        this.slide_contents = $( 'movies_slide' ).selectPart( '#movies_slide_contents' );
        this.slide_contents.setStyle( { 'left': parseInt( ileft ) + 'px' } );
        this.slider_max = parseInt( this.slide_contents.getStyle( 'width' ) ) - ( 4 * 192 );
        this.prev_val = 0;
        this.slider = '';

        /* ------- Initiate the slider -------- */
        // may use this class variable later on??
        this.movie_slider = new Control.Slider( iscroll_handle_id, iscroll_track_id,
                                            {
                                            axis: 'horizontal',
                                            range: $R( 0, this.slider_max ),
                                            sliderValue: Math.abs( ileft ),
                                            onSlide: this.onMovieSlide.bind( this ),
                                            onChange: this.onMovieChange.bind( this )
                                            } );
        },

    onMovieSlide: function( ivalue )
        {
        this.mover_increment = 0;
        this.next_val = ivalue;
        if( this.slider )
            return;

        this.mover = this.prev_val;
        this.slider = new PeriodicalExecuter( this.doSlide.bind( this ), 0.05 );
        this.prev_val = ivalue;

        },

    onMovieChange: function( ivalue )
        {
        this.onMovieSlide( ivalue );
        // send an ajax request
        doAjaxAdapterCallJS( 'user_state', 'updateUSValue',
                                            {
                                            igname: 'media_movies',
                                            icname: 'movies_slide_pos',
                                            ivalue: parseInt( this.slide_contents.getStyle( 'left' ) )
                                            } );
        },

    doSlide: function()
        {
        this.mover = ( this.next_val + this.mover ) / 2;
        this.slide_contents.setStyle( { 'left': '-' + this.mover + 'px' } );
        }

    } );
// --------------------------------------------------------------------------------------
// Vertical slider
// --------------------------------------------------------------------------------------
NSB.VerticalSlider = Class.create(
    {
    initialize: function( iscroll_handle_id, iscroll_track_id, ijelem_id, ileft, ipage_name, iparent_elem, islider_contents )
        {
        this.movie_elem = $( ijelem_id );
        this.slide_contents = $( 'vertical_slide' ).selectPart( islider_contents );
        this.slide_contents.setStyle( { 'top': parseInt( ileft ) + 'px' } );
        this.slider_max = parseInt( this.slide_contents.getStyle( 'height' ) ) ;
        this.prev_val = 0;
        this.slider = '';

        /* ------- Initiate the slider -------- */
        // may use this class variable later on??
        this._slider = new Control.Slider( iscroll_handle_id, iscroll_track_id,
                                            {
                                            axis: 'vertical',
                                            range: $R( 0, this.slider_max ),
                                            sliderValue: Math.abs( ileft ),
                                            onSlide: this.onSlide.bind( this ),
                                            onChange: this.onChange.bind( this )
                                            } );
        },

    onSlide: function( ivalue )
        {
        this.mover_increment = 0;
        this.next_val = ivalue;
        if( this.slider )
            return;

        this.mover = this.prev_val;
        this.slider = new PeriodicalExecuter( this.doSlide.bind( this ), 0.05 );
        this.prev_val = ivalue;
        },

    onChange: function( ivalue )
        {
        this.onSlide( ivalue );
        // send an ajax request
        doAjaxAdapterCallJS( 'user_state', 'updateUSValue',
                                            {
                                            igname: 'media_forkids',
                                            icname: 'forkids_games_slide_pos',
                                            ivalue: parseInt( this.slide_contents.getStyle( 'top' ) )
                                            } );
        },

    doSlide: function()
        {
        this.mover = ( this.next_val + this.mover ) / 2;
        this.slide_contents.setStyle( { 'top' : '-' + this.mover + 'px' } );
        }

    } );
 
// --------------------------------------------------------------------------------------
// Movies DRM Licensing info layer
// --------------------------------------------------------------------------------------
NSB.DRMLicensingInfoLayer = Class.create(
    {

    /* Intermediate Layer showing information related to DRM key fetching. 
       There are four stages. 
            1. Check activex component compatibility 
            2. Check if key is fetched 
            3. Update credits in AAA 
            4. Send email through AAA 
       On failure of a stage the process stops there */

    initialize: function( imovie_id, ilanguage )
        {
        this.movie_id = imovie_id;
        this.lang = ilanguage;  
        this.setMessagesForLang();
        this.renderLayer();
        },

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    renderLayer: function()
        {
        doAjaxAdapterCallJS( 'render', 'renderMacro', 
                                            { imacro_path: 'mac_movies/macros/movies_licensing_layer' }, 
                                            '', 
                                            this.onInfoLayerRendered.bind( this ) );
        },

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    onInfoLayerRendered: function( iresponse )
        {
        this.layer_container_element = $( 'licensingInfoLayerOuterContainer' );
        this.layer_container_element.update( eval( iresponse.responseText ) );
        this.updateActiveXStatus(); 
        },

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    reportBrowserCompatibility: function()
        {
        // may be do the browser compatibility tests??        
        }, 

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    updateActiveXStatus: function()
        {
        new NSB.SmallSpinner( 'activex_status' ); 
        var activex_el = $( 'activex_status' );       
        if( this.isActiveXEnabled() )
            {

            if( this.isDRMRightVersion() )
                { 
                activex_el.update( this.success_msg );
                this.addContainerHeight( this.reportDRMStatus );
                }

            else
                {
                activex_el.update( this.failure_msg );
                if( this.lang == 'en_gb' )
                    failure_msg = 'Please click <a href="http://drmlicense.one.microsoft.com/Indivsite/en/indivit.asp" target="_blank"> here </a>, you need an update to play movie';
                
                else
                    failure_msg = 'Du trenger en oppdatering for å spille film, vennligst oppdater <a href="http://drmlicense.one.microsoft.com/Indivsite/en/indivit.asp" target="_blank"> her </a>';
                
                this.takeFailureActions( failure_msg, 'activex_failure_msg' );                
                }  
            }
  
        else
            {
            activex_el.update( this.failure_msg );
            /* This msg cannot come from server, we might 
               change some of the words as links!! */
            if( this.lang == 'en_gb' )
                {
                failure_msg = 'Please check your browser/operating system settings. If you are unsure please consult our FAQ'
                }
            else
                {
                failure_msg = 'Vennligst sjekk oppsett av din nettleser/operativsystem. Hvis du er usikker, vennligst se ofte stilte spørsmål (OSS).'
                } 
            this.takeFailureActions( failure_msg, 'activex_failure_msg' );
            } 
        
        }, 

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    isActiveXEnabled: function()
        {
        // netobj is actual embedded drm activex component!
        try
            {
            document.netobj.GetSystemInfo();
            return true;
            }
        catch( Exception )
            {
            return false;   
            } 
        },

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    isDRMRightVersion: function()
        {
        drm_version = document.netobj.GetDRMSecurityVersion();
        drm_version_array = drm_version.split( "." ); 

        if( drm_version_array[ 0 ] >= 2 )
            {
            if( drm_version_array[ 1 ] >= 9 )
                return true;
            }

        return false;
        }, 

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    reportDRMStatus: function()
        {
        this.showElements( 'drmkey_msg_container' );
        new NSB.SmallSpinner( 'drm_status' );
        this.key_getter = new NSB.DRMKeyGetter();
        this.check_gotkey_periodically = new PeriodicalExecuter( this.updateDRMStatus.bind( this ), 3 );
        }, 

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    updateDRMStatus: function()
        {
        var drm_status_el = $( 'drm_status' ); 
        if( this.isDRMKeyFetched() )
            {
            this.showSuccessDRMStatus( drm_status_el );
            this.addContainerHeight( this.reportMediaCreditsUpdateStatus );
            }  
        else if( !this.isDRMKeyFetched() && this.isDRMKeyPresentAlready() )
            {
            this.showDRMKeyAlreadyPresentMsg( drm_status_el );      
            doRedirect( this.movie_id );
            } 
        else if( !this.isDRMKeyFetched() && !this.isDRMKeyGetterRunning() )
            {
            this.showFailureDRMStatus( drm_status_el );    
            } 
        },

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    showSuccessDRMStatus: function( idrm_status_element )
        { 
        idrm_status_element.update( this.success_msg );
        this.killExecuter( this.check_gotkey_periodically ); 
        },

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    showFailureDRMStatus: function( idrm_status_element )
        {
        idrm_status_element.update( this.failure_msg ); 
        var failure_msg = this.key_getter.getDRMKeyGetterFailureMsg();
        this.takeFailureActions( failure_msg, 'drm_failure_msg' );     
        this.killExecuter( this.check_gotkey_periodically );
        }, 

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    showDRMKeyAlreadyPresentMsg: function( idrm_status_element )
        {
        idrm_status_element.update( 'Stopped' ); 
        var failure_msg;
        if( this.lang == "en_gb" )
            failure_msg = 'Control of media rights (DRM) is already downloaded for this movie';
        else
            failure_msg = 'Kontroll av rettighetsinformasjon (DRM) er allerede lastet ned for denne filmen';
        this.takeFailureActions( failure_msg, 'drm_failure_msg' );     
        this.killExecuter( this.check_gotkey_periodically );
        }, 

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    showButtonOnFailure: function()
        {
        var button_div = $( 'movie_cancel_button' );
        button_div.setStyle( 'display:block' );  
        },

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    updateFailureMsg: function( imsg, ielement_id )
        {
        var failure_el = $( ielement_id );
        failure_el.update( imsg ); 
        failure_el.setStyle( 'display:block' );
        },

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    takeFailureActions: function( imsg, imsg_element )
        {
        this.showButtonOnFailure();
        this.updateFailureMsg( imsg, imsg_element );    
        }, 

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    isDRMKeyFetched: function()
        {
        return this.key_getter.getGotKey();
        },

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    isDRMKeyPresentAlready: function()
        {
        return this.key_getter.isKeyPresentAlready();
        },  

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    isDRMKeyGetterRunning: function() 
        {
        return this.key_getter.isKeyGetterRunning();
        },  

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    reportMediaCreditsUpdateStatus: function()
        {
        this.showElements( 'mediacredits_msg_container' );
        new NSB.SmallSpinner( 'mediacredits_status' );
        this.media_credits_updater = new NSB.UpdateMediaCredits();
        this.credit_updated_checker = new PeriodicalExecuter( this.updateMediaCreditsStatus.bind( this ), 5 ); 
        },   

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    updateMediaCreditsStatus: function()
        {
        var mediacredits_status_el = $( 'mediacredits_status' ); 
        if( this.isMediaCreditsUpdated() )
            {
            this.showSuccessMediaCreditsStatus( mediacredits_status_el );
            this.addContainerHeight( this.reportEmailerStatus );
            }  
        else if( !this.isMediaCreditsUpdated() && !this.isMediaCreditUpdaterRunning() )
            {
            this.showFailureMediaCreditsStatus( mediacredits_status_el );    
            } 
        },

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    showSuccessMediaCreditsStatus: function( imedia_status_element )
        { 
        imedia_status_element.update( this.success_msg );
        this.killExecuter( this.credit_updated_checker ); 
        },

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    showFailureMediaCreditsStatus: function( imedia_status_element )
        {
        imedia_status_element.update( this.failure_msg ); 
        var failure_msg = this.media_credits_updater.getMediaCreditUpdaterFailureMsg();
        this.takeFailureActions( failure_msg, 'mediacredits_failure_msg' );     
        this.killExecuter( this.credit_updated_checker );
        },

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    reportEmailerStatus: function()
        {
        this.showElements( 'email_msg_container' );
        new NSB.SmallSpinner( 'email_status' );
        this.movie_link_emailer = new NSB.MovieLinkEmailer();
        this.movie_link_emailer_checker = new PeriodicalExecuter( 
                                            this.updateEmailerStatus.bind( this ), 5 );  
        },
 
    updateEmailerStatus: function()
        {
        var emailer_status_el = $( 'email_status' ); 
        if( this.isMovieEmailerUpdated() )
            {
            this.showSuccessMovieEmailerStatus( emailer_status_el );
            doRedirect( this.movie_id );
            }  
        else if( !this.isMovieEmailerUpdated() && !this.isMovieEmailerUpdaterRunning() )
            {
            this.showFailureMovieEmailerStatus( emailer_status_el );    
            }
        },  

    isMovieEmailerUpdated: function()
        {
        return this.movie_link_emailer.getMovieLinkEmailed();
        },
    
    isMovieEmailerUpdaterRunning: function()
        {
        return this.movie_link_emailer.isMovieLinkEmailerRunning();
        },
     
    showSuccessMovieEmailerStatus: function( iemailer_status_el )
        {
        iemailer_status_el.update( this.success_msg );
        this.killExecuter( this.movie_link_emailer_checker ); 
        clearDRMInfoLayer();
        },

    showFailureMovieEmailerStatus: function( iemailer_status_el )
        {
        iemailer_status_el.update( this.failure_msg ); 
        var failure_msg = this.movie_link_emailer.getMovieLinkEmailerFailureMsg();
        this.takeFailureActions( failure_msg, 'email_failure_msg' );     
        this.killExecuter( this.movie_link_emailer_checker ); 
        },

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    showElements: function( idiv_name )
        {
        var status_element = $( idiv_name );
        status_element.setStyle( 'display:block' ); 
        }, 

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    isMediaCreditsUpdated: function()
        {
        return this.media_credits_updater.getMediaCreditsUpdated();
        },

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    isMediaCreditUpdaterRunning: function()
        {
        return this.media_credits_updater.isCreditUpdaterRunning();
        }, 

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    killExecuter: function( iperiodical_executer )
        {
        iperiodical_executer.stop();
        },  

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    showPartialMovieLink: function()
        {
        // show 10 mins movie link ??
        },
    
    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    setMessagesForLang: function()
        {
        if ( this.lang == 'en_gb' )
            {
            this.success_msg = 'Done';
            this.failure_msg = 'Failed'; 
            }
        else
            {
            this.success_msg = 'Ferdig';
            this.failure_msg = 'Mislyktes';  
            }  
        },
 
    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    addContainerHeight: function( ifunction )
        {
        var main_container = $( 'licensingInfoLayerContent' ); 
        var height = main_container.getStyle( 'height' );
        var new_height = parseInt( height ) + 57; 
        new_height = new_height + 'px;'; 
               
        new Effect.Morph( 'licensingInfoLayerContent', 
                                            { 
                                            style:'height:' + new_height, 
                                            duration: 0.6,
                                            afterFinish: ifunction.bind( this )
                                            } );
                 
        }
    
    } );

// --------------------------------------------------------------------------------------
clearDRMInfoLayer = function()
    {
    $( 'licensingInfoLayerContainer' ).setStyle( 'display:none' );
    };

// --------------------------------------------------------------------------------------
// Generic popup panel - uses raphaeljs
// --------------------------------------------------------------------------------------
NSB.SVGPanel = Class.create(
    {
    initialize: function( icomponent )
        {
        this.component = icomponent;
        this.path_params = { "stroke": "#000", 
                             "stroke-width": 5,
                             "stroke-linecap": "round",
                             "gradient": "90-#fcfcfc-#000" };
        this.drawPopup();
        },

    drawPopup: function()
        {
        var div_hgt = this.component.style.height;
        var div_width = this.component.style.width;
        var div_top = this.component.offsetTop;    // y
        var div_left = this.component.offsetLeft;  // x
        var canvas = Raphael( this.component,
                                            280, 160 );
        div_width = parseInt( div_width );
        div_hgt = parseInt( div_hgt ); 
        var rect_p = canvas.rect( div_left + 15, div_top + 15, div_width - 20, div_hgt - 20 );
        //var rect_p = canvas.rect( div_left, div_top, div_width, div_hgt ); 
        rect_p.attr( { fill: "#f3f3f4",
                       gradient: "90-#fcfcfc-#000",
                       stroke: "#f3f3f4",
                       "stroke-width": 10,
                       "stroke-linejoin": "round", 
                       "stroke-linecap": "round" } );

   
        } 
    } );


// --------------------------------------------------------------------------------------
// Popup for POI class
// --------------------------------------------------------------------------------------
NSB.POI = Class.create( 
    {
    initialize: function() 
        {
        this.checker = new PeriodicalExecuter( this.checkForPOI.bind( this ), 45 ); 
        this.main_container = $( 'main_poi_el' );
        this.panel_container = $( 'panel_for_poi' );
        this.content_container = $( 'content_for_poi' );
        this.panel = "";
        },

    checkForPOI: function()
        {
        doAjaxAdapterCallJS( 'layers_map', 'checkAnyPOIInRangeFromTrain',
                                            {},
                                            '',
                                            this.onCheckedForPOI.bind( this ) );
        },

    onCheckedForPOI: function( iresponse )
        {
        var poi_html = iresponse.responseJSON[ 'html' ];
        var poi_title = iresponse.responseJSON[ 'title' ]; 
        if( poi_html )
            {
            this.setLowerRightBounds();
            if( !this.panel ) 
                this.showPopup( poi_html );
                this.setTitle( poi_title );
            }  
            
        },

    setTitle: function( ititle )
        {
        this.page_title = ititle; 
        this.curr_title = document.title;  
        this.duration = 0.3;
        this.pos = 0; 
        this.title_scroller = new PeriodicalExecuter( this.scrollTitle.bind( this ), this.duration ); 
        },

    scrollTitle: function()
        {
        spacer = "   ";
        document.title = 'NSB Info - ' + this.page_title.substring( this.pos, 
                            this.page_title.length ) + spacer + this.page_title.substring( 0, this.pos );
        this.pos++;
        if ( this.pos > this.page_title.length ) 
            this.pos = 0;
        },
   
    showPopup: function( ihtml )
        {
        this.panel = new NSB.SVGPanel( this.panel_container );
        this.content_container.update( ihtml );
        new Effect.Parallel( [ 
            new Effect.Move( this.main_container, { x:0, y: -150 } ),
            new Effect.Opacity( this.main_container, { from:0.0, to:1.0, duration:1.0 } ) ] );  
        this.timer = new PeriodicalExecuter( this.closePopup.bind( this ), 30 );
        }, 
      
    closePopup: function()
        {
        new Effect.Parallel( [
            new Effect.Move( this.main_container, { x:0, y: 150 } ),
            new Effect.Opacity( this.main_container, { from:1.0, to:0.0, duration:1.0 } ) ], 
            { afterFinish: this.cleanAfterClose.bind( this ) } ); 
        },  

    cleanAfterClose: function()
        {
        this.content_container.update( "" );
        this.panel_container.update( "" ); 
        this.main_container.setStyle( { 'left': '0px', 'top':'0px' } ); 
        this.panel = "";
        this.timer.stop();
        this.title_scroller.stop(); 
        document.title = this.curr_title;  
        }, 
  
    setLowerRightBounds: function()
        {
        var main_el_width = parseInt( this.main_container.getWidth() );
        var main_el_height = parseInt( this.main_container.getHeight() );  
        var avail_screen_size = this.getWindowSize();
        var start_left = avail_screen_size[ 'w_width' ] - 320;
        var start_top = avail_screen_size[ 'w_height' ]; 
        this.main_container.setStyle( { 'zIndex':'10001', 'left': start_left + 'px', 'top': start_top + 'px' } ); 
        },
 
    getWindowSize: function()
        {
        var window_height = 0; 
        var window_width = 0;  
        if( typeof( window.innerWidth ) == 'number' )
            {
            window_height = window.innerHeight;
            window_width = window.innerWidth;
            } 
        else 
            {
            /* IE 7+  */ 
            window_height = document.getElementsByTagName( 'body' )[ 0 ].clientHeight;
            window_width = document.getElementsByTagName( 'body' )[ 0 ].clientWidth;
            }   
        return { 'w_height': window_height,
                 'w_width': window_width };    
        }

    } );

// --------------------------------------------------------------------------------------
// Redirect to travel info
// --------------------------------------------------------------------------------------
NSB.SendToTravelInfo = Class.create( { 
    initialize: function()
        {
        //doAjaxAdapterCallJS( 'layers_map', 'cancelCurrentPopup',
        //                                    {},
        //                                    '',
        //                                    function(){ window.location = '/view_layers_map' } );
        window.location = '/view_layers_map';
        }
    
    } ); 


// --------------------------------------------------------------------------------------
// Movies scroll
// --------------------------------------------------------------------------------------
NSB.MovieScroll = Class.create(
    {   /* movies in a category should be visible as user scrolls */
    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    initialize: function( ijelem_id )
        {
        this.panels_elem = $( ijelem_id );

        this.scroll_idx = parseInt( this.panels_elem.readAttribute( 'movies_scroll_idx' ) );
        this.max_scroll = parseInt( this.panels_elem.readAttribute( 'movies_max_scroll' ) );

        // next scroll button
        this.scroll_next_btn = this.panels_elem.selectPart( '#movies_scroll_next_btn' );
        Event.observe( this.scroll_next_btn, 'click', this.onClickScroll.bindAsEventListener( this, +1 ) );

        // prev scroll button
        this.scroll_prev_btn = this.panels_elem.selectPart( '#movies_scroll_prev_btn' );
        Event.observe( this.scroll_prev_btn, 'click', this.onClickScroll.bindAsEventListener( this, -1 ) );

        // panel objects in our scroll area
        this.scroll_contents = this.panels_elem.selectPart( '#movies_scroll_contents' );

        },

    onClickScroll: function( ievent, idirection )
        {
        if( idirection < 0 && this.scroll_idx <= 0 )
            return;
        else if( idirection > 0 && this.scroll_idx >= this.max_scroll )
            return;

        doAjaxAdapterCallJS( 'user_state', 'updateUSValue', 
                                            {
                                            igname: 'media_movies',
                                            icname: 'movies_scroll_idx',
                                            ivalue: this.scroll_idx + idirection
                                            } );
        this.scroll_idx += idirection;

        if( this.scroll_idx > 0 )                  // first element 
            this.scroll_prev_btn.setStyle( { opacity: '1.0' } );
        else
            {
            this.scroll_prev_btn.setStyle( { opacity: '0.4' } );
            this.scroll_next_btn.setStyle( { opacity: '1.0' } );
            } 

        if( this.scroll_idx < this.max_scroll ) // last element
            this.scroll_next_btn.setStyle( { opacity: '1.0' } );
        else
            {
            this.scroll_next_btn.setStyle( { opacity: '0.4' } );
            this.scroll_prev_btn.setStyle( { opacity: '1.0' } );  
            }  

        new Effect.Move( this.scroll_contents,
                                            { 
                                            duration: 0.3, 
                                            x: idirection * -199, 
                                            y: 0, 
                                            mode: 'relative' 
                                            } );
        }

    } );

// --------------------------------------------------------------------------------------
// left accordion stuff
// --------------------------------------------------------------------------------------

NSBMenuLeftAccordion = Class.create( accordion,
    {
    initialize: function( $super, ijelem_id )
        {        
        var options =
            {
            classNames: 
                {
                toggle: 'menuLeftItemHead',
                toggleActive: 'menuLeftItemHeadActive',
                content: 'menuLeftSubItems'
                },
            afterFinish:
                this.onAccordionOpened.bind( this )
            };
                
        $super( ijelem_id, options );
        
        this.showAccordion = $( ijelem_id ).selectPart( ".menuLeftItemHeadActive" ).next();
        },

    activate : function( $super, iaccordion ) 
        {
        this.sel_menu_elem = iaccordion;
        $super( iaccordion );
        },
        
    onAccordionOpened: function( ielem )
        {
        // var view_name = this.sel_menu_elem.readAttribute( 'zredirect' );
        doAjaxAdapterCall( this.sel_menu_elem );
        }
        
    } );

// --------------------------------------------------------------------------------------
// low panels
// --------------------------------------------------------------------------------------

NSB.PanelsLow = Class.create(
    {   /* scrolling panels at bottom of page */
    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    initialize: function( ijelem_id )
        {
        this.panels_elem = $( ijelem_id );

        this.scroll_idx = parseInt( this.panels_elem.readAttribute( 'scroll_idx' ) );
        this.max_scroll = parseInt( this.panels_elem.readAttribute( 'max_scroll' ) );

        // next scroll button
        this.scroll_next_btn = this.panels_elem.selectPart( '#scroll_next_btn' );
        Event.observe( this.scroll_next_btn, 'click', this.onClickScroll.bindAsEventListener( this, +1 ) );

        if( this.scroll_idx >= ( this.max_scroll - 1 ) )
            this.scroll_next_btn.setStyle( { opacity: '0.4' } );
        else
            this.scroll_next_btn.setStyle( { opacity: '1.0' } ); 

        // prev scroll button
        this.scroll_prev_btn = this.panels_elem.selectPart( '#scroll_prev_btn' );
        Event.observe( this.scroll_prev_btn, 'click', this.onClickScroll.bindAsEventListener( this, -1 ) );
        if( this.scroll_idx <= ( 0 + 1 ) )
            this.scroll_prev_btn.setStyle( { opacity: '0.4' } );
        else
            this.scroll_prev_btn.setStyle( { opacity: '1.0' } );

        // panel objects in our scroll area
        this.scroll_contents = this.panels_elem.selectPart( '#panels_low_scroll_contents' );
        this.panel_objs = this.scroll_contents.select( '.panelsLowItem' ).collect(
                                    function( ielem )
                                        {
                                        return new NSB.PanelLow( ielem.readAttribute( 'id' ) );
                                        } );
        
        },
        
    onClickScroll: function( ievent, idirection )
        {
        if( idirection < 0 && this.scroll_idx <= 0 )
            return;
        else if( idirection > 0 && this.scroll_idx >= this.max_scroll )
            return;

        doAjaxAdapterCallJS( 'user_state', 'updateUSValue', 
                                            {
                                            igname: 'panels_low',
                                            icname: 'scroll_idx',
                                            ivalue: this.scroll_idx + idirection
                                            } );
        this.scroll_idx += idirection;
        
        if( this.scroll_idx > 0 )                  // first element 
            this.scroll_prev_btn.setStyle( { opacity: '1.0' } );
        else
            {
            this.scroll_prev_btn.setStyle( { opacity: '0.4' } );
            this.scroll_next_btn.setStyle( { opacity: '1.0' } );
            } 
            
        if( this.scroll_idx < this.max_scroll ) // last element
            this.scroll_next_btn.setStyle( { opacity: '1.0' } );
        else
            {
            this.scroll_next_btn.setStyle( { opacity: '0.4' } );
            this.scroll_prev_btn.setStyle( { opacity: '1.0' } );  
            }  
              
        new Effect.Move( this.scroll_contents,
                                            { 
                                            duration: 0.3, 
                                            x: idirection * -199, 
                                            y: 0, 
                                            mode: 'relative' 
                                            } );
        }
            
    } );

// --------------------------------------------------------------------------------------
// low panels
// --------------------------------------------------------------------------------------

NSB.PanelLow = Class.create(
    {
    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    initialize: function( ijelem_id )
        {
        this.panel_elem = $( ijelem_id );
        
                                // hackerage for release 1 demo
        //if( ijelem_id == 'panels_low_train_internet' )
        //    Event.observe( this.panel_elem, 'click', 
        //                                    function() { window.location = 'view_train_internet'; } );
        if( ijelem_id == 'panels_low_your_journey' )
            Event.observe( this.panel_elem, 'click', 
                                            function() { window.location = 'view_layers_map'; } );
        else
            Event.observe( this.panel_elem, 'click', this.onClickPanel.bindAsEventListener( this ) );
        },
        
    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    onClickPanel: function( ievent )
        {
        var popup = $( 'popup_overlay' );
        popup.setStyle( { display: 'none' } );
        
        this.popup_bnds = NSB.calcCenterBounds( k_popup_panel_dims );

        new NSB.ZoomRect( $( 'popup_overlay_effect_img' ),
                {
                bgn_bnds: this.getPanelBounds(), 
                end_bnds: this.popup_bnds,
                afterFinish: this.onPopupOpened.bind( this ) 
                } );
        },
        
    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    onPopupOpened: function( iresponse )
        {
        doAjaxAdapterCallJS( 'render', 'renderPopup', 
            {
            ipopup_id: this.panel_elem.readAttribute( 'id' )
            },
            '',
            this.onPopupRendered.bindAsEventListener( this ) );
        },

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
     onPopupRendered: function( iresponse )
        {
        NSB.popup_overlay.setContents( eval( iresponse.responseText ) );
        NSB.popup_overlay.setBounds( this.popup_bnds );
        NSB.popup_overlay.setCloseZoomBounds( this.getPanelBounds() );
        $( 'popup_overlay_effect_img' ).setStyle( { display: 'none' } );
        },
                
    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    getPanelBounds: function()
        {       /* return the bounds of our panel element in window coords */
        var panel_pos = this.panel_elem.viewportOffset();
        var panel_dims = this.panel_elem.getDimensions();
        var panel_bnds = { 
                    left: panel_pos.left, 
                    top: panel_pos.top,
                    wid: panel_dims.width,
                    hgt: panel_dims.height
                    };
        return panel_bnds;
        }
        
    } );
    
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        
NSB.PopupOverlay = Class.create(
    {
    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    initialize: function( ijelem_id )
        {
        this.popup_elem = $( ijelem_id );
        this.close_button = this.popup_elem.select( '#close_button' )[ 0 ];
        this.contents_elem = this.popup_elem.select( '#contents_elem' )[ 0 ];
        Event.observe( this.close_button, 'click', this.onCloseButton.bindAsEventListener( this ) );
        this.close_zoom_bounds = null;
        },
        
    setContents: function( ihtml )
        {
        this.contents_elem.update( ihtml );
        },
        
    getBounds: function()
        {       /* return the bounds of our popup element in window coords */
        var popup_dims = this.popup_elem.getDimensions();
        var popup_bnds = { 
                    left: parseInt( this.popup_elem.getStyle( 'left' ) ),
                    top: parseInt( this.popup_elem.getStyle( 'top' ) ),
                    wid: popup_dims.width,
                    hgt: popup_dims.height
                    };
        return popup_bnds;
        },

    setBounds: function( ibounds )
        {
        this.popup_elem.setStyle( 
                    {
                    left: ibounds[ 'left' ] + 'px', 
                    top: ibounds[ 'top' ] + 'px',
                    width: ibounds[ 'wid' ] + 'px',
                    height: ibounds[ 'hgt' ] + 'px',
                    display: 'block'
                    } );            
        },
    
    setVisible: function( ivis )
        {
        this.popup_elem.setStyle( { display: ivis ? 'block' : 'none' } );
        },    
    
    setCloseZoomBounds: function( ibounds )
        {   /* the bounds in viewport coords of where we want the zoom
                effect to end when we close
                NB ibounds may be null in which case no zoom effect */
        this.close_zoom_bounds = ibounds;
        },
        
    onCloseButton: function( ievent )
        {
        this.setVisible( false );
        
        if( this.close_zoom_bounds )
            {
            new NSB.ZoomRect( $( 'popup_overlay_effect_img' ),
                        {
                        bgn_bnds: this.getBounds(),
                        end_bnds: this.close_zoom_bounds,
                        afterFinish: function()
                                {
                                $( 'popup_overlay_effect_img' ).setStyle( { display: 'none' } );
                                }
                        } );
            }
        }

    } );
    
    
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// after a bit of googling around and studying effects.js, decided
//    writing our own subclass of Effect was the easiest and best way
//    to get what we want. Doesn't look too bad so far.

NSB.ZoomRect = Class.create( Effect.Base,  
    {   /* NB this class works in view-port coordinates; the element used for
              the effect should be in directly within the body of the 
              document (ie not nested within anything else) */
              
    initialize: function( element, ioptions )  
        {       /* ioptions must have bgn_bnds and end_bnds, both of which contain
                    left, top, wid and hgt values for the begin, end of the effect.
                    NB other vals are as per effects.js */
        this.element = $( element );
        if ( !this.element ) 
            throw( Effect._elementDoesNotExistError );

        var options = Object.extend( 
                    {
                    duration: 0.3
                    }, ioptions );
                    
        this.start( options );
        },
        
    setup: function()  
        {
        this.vp_offset = this.element.getOffsetParent().viewportOffset();
        var opts = this.options;
        this.ranges = {
                    left: opts.end_bnds[ 'left' ] - opts.bgn_bnds[ 'left' ],
                    top: opts.end_bnds[ 'top' ] - opts.bgn_bnds[ 'top' ],
                    wid: opts.end_bnds[ 'wid' ] - opts.bgn_bnds[ 'wid' ],
                    hgt: opts.end_bnds[ 'hgt' ] - opts.bgn_bnds[ 'hgt' ]
                    };
                    
        this.element.setStyle( { display: 'block' } );
        // this.update( 0 );
        },
        
    update: function( iposition ) 
        {       /* iposition is a float giving pos thru the effect */
        // console.log( iposition );

        var pleft = this.options.bgn_bnds[ 'left' ] + this.ranges[ 'left' ] * iposition;
        var ptop = this.options.bgn_bnds[ 'top' ] + this.ranges[ 'top' ] * iposition;
        var pwid = this.options.bgn_bnds[ 'wid' ] + this.ranges[ 'wid' ] * iposition;
        var phgt = this.options.bgn_bnds[ 'hgt' ] + this.ranges[ 'hgt' ] * iposition;
        
        this.element.setStyle(
                    {
                    left: pleft.round() - this.vp_offset[ 'left' ] + 'px',
                    top: ptop.round() - this.vp_offset[ 'top' ] + 'px',
                    width: pwid.round() + 'px',
                    height: phgt.round() + 'px'
                    } );
        },

    finish: function( position )
        {
        // pass
        }

    } );


NSB.calcCenterBounds = function( idims )
    {   /* idims is a width / height; calculate values so that a box of 
            that size would appear centered in the main part of the page */
    var vdims = document.viewport.getDimensions();
    var cdims = $( 'left_column' ).getDimensions(); 
    var mdims = $( 'content' ).getDimensions();
    
    var obnds = {
            left: parseInt( ( vdims.width + cdims.width - idims.width ) / 2 ),
            top: parseInt( ( mdims.height - idims.height ) / 2 ),
            wid: idims.width,
            hgt: idims.height
            };

    return obnds;
    };
    
    
// --------------------------------------------------------------------------------------
// ticker
// --------------------------------------------------------------------------------------

NSB.Ticker = Class.create(
    {   /* scrolling ticker-tape news message */
    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    initialize: function( ijelem_id )
        {
        this.ticker_elem = $( ijelem_id );
        this.ticker_container = this.ticker_elem.selectPart( '#ticker_container' );
        this.ticker_contents = this.ticker_elem.selectPart( '#ticker_contents' );
        
        this.scroll_pix_per_sec = this.ticker_elem.readAttribute( 'scroll_pix_per_sec' );
        this.scroll_update_secs = this.ticker_elem.readAttribute( 'scroll_update_secs' );
        
        this.check_count = 0;
        this.tupdater = new PeriodicalExecuter( this.checkTicker.bind( this ), 1 );
        
        this.startTicker();
        },
        
    startTicker: function( iticker_text )
        {
        if( iticker_text )
            this.ticker_contents.update( iticker_text );

        this.ticker_wid = this.ticker_contents.getWidth();
        var ticker_scroll = this.ticker_wid + parseInt( this.ticker_contents.getStyle( 'left' ) );

        this.ticker_effect = new Effect.Move( this.ticker_contents,
                                            { 
                                            duration: ticker_scroll / this.scroll_pix_per_sec, 
                                            x: -ticker_scroll, 
                                            y: 0, 
                                            mode: 'relative',
                                            transition: Effect.Transitions.linear
                                            } );
        
        },
        
    checkTicker: function()
        {       /* called every sec - sends scroll val to server every this.scroll_update_secs */
            
                // hmmmmmm - we still often get the case where after a page load, our
                //   this.ticker_effect says its finished - it may be that a previous
                //   tupdater is still running ???
        if( this.ticker_effect.state == 'finished' )
            {
            doAjaxAdapterCallJS( 'ticker', 'getFreshTickerText', {}, '',
                                        function( iresponse )
                                            {
                                            this.ticker_effect = null;
                                            twid = parseInt( this.ticker_container.getStyle( 'width' ) );
                                            this.ticker_contents.setStyle( { left: twid + 'px' } );
                                            this.startTicker( iresponse.responseJSON );
                                            }.bind( this ) );     
            }
        else
            {
            this.check_count++;
            if( this.check_count > this.scroll_update_secs )
                {
                var tscroll = parseInt( this.ticker_contents.getStyle( 'left' ) );
                doAjaxAdapterCallJS( 'ticker', 'updateTickerScroll', 
                                            { iscroll: tscroll } );
                this.check_count = 0;   
                }
            }
        }
    } );
    
// --------------------------------------------------------------------------------------
// Ajax adapter calling - javascript version with history updates
// interesting but ultimately not used
// --------------------------------------------------------------------------------------
// function doAjaxAdapterCallHistoried( ievent )
//     {
//     var elem = getEventElem( ievent );
//     
//     NSB.gHistorier().histSave( ievent );
// 
//     doAjaxAdapterCall( ievent );
//     };
//     
// NSB.gHistorier = function()
//     {
//     if( !NSB._Historier )
//         NSB._Historier = new NSB.Historier();
//     return NSB._Historier;
//     };
// NSB._Historier = null;
// 
// 
// NSB.Historier = Class.create( 
//     {
//     initialize: function()
//         {
//         var hist_elem = $( 'history_element' );
//         this.hist_frame = hist_elem.down( "iframe" );
//         this.hist_form = hist_elem.down( "form" );    
//         
//         this.hist_index = 0;
//         this.hist_states = [];
//         // this.hist_states.push( null ); 
//         
//         Event.observe( this.hist_frame, "load", this.histLoad.bindAsEventListener( this ) );
//         },
//         
//     histLoad: function( ievent )
//         {   /* called when the user hits back or forward browser buttons 
//                 (via the load event that's sent to this.hist_frame) */
//         var hist_loc = this.hist_frame.contentWindow.location.toString();
//         var hist_idx = parseInt( hist_loc.replace( /.*iindex=/gi, "" ) );
//         
//         if( hist_idx == this.hist_index )
//             return;
//             
//         this.hist_index = hist_idx;        
//         var hist_elem = this.hist_states[ hist_idx ];
//         
//         if( !hist_elem )
//             alert( "back to first" );
//         else
//             doAjaxAdapterCall( hist_elem );
//         },
//         
//     histSave: function( ievent )
//         {   /* called by code to save a point from which the user can move 
//                 back or forward */
//         var hist_elem = findAdapterMethodElem( getEventElem( ievent ) );
//         
//         if( !this.hist_index )
//             this.makeHistFirst( hist_elem );
//         else if( this.hist_index < this.hist_states.length - 1 )
//             this.hist_states.length = this.hist_index + 1;
//                 
//                 this.hist_index = this.hist_form.iindex.value = this.hist_states.length;        
// 
//                 this.hist_states.push( hist_elem );      // this is pushing it :-)
//                                                                                 
//                 this.hist_form.submit();
//         },
// 
//     makeHistFirst: function( ielem )
//         {
//         var first = ielem.cloneNode( true );    // this saves us a lot of work
//         var gname = ielem.readAttribute( 'igname' );
//         var cname = ielem.readAttribute( 'icname' );
//         
//         this.hist_states.push( null );          // placeholder
//         doAjaxAdapterCallJS( 'user_state', 'getUSValue', 
//                                     { igname: gname, icname: cname },
//                                     '',
//                                     function( iresponse )
//                                         {
//                                         first.writeAttribute( 'ivalue', iresponse.responseJSON );
//                                         this.hist_states[ 0 ] = first;
//                                         }.bind( this )
//                                      );           
// 
//         },
//         
//     zzz_makeHistFirst: function( ielem )
//         {
//         var zadapter = ielem.readAttribute( 'zadapter' );
//         var zmethod = ielem.readAttribute( 'zmethod' );
//         var zmethod_args = ielem.readAttribute( 'zmethod_args' );        
//         var jinvalids = ielem.readAttribute( 'jinvalids' );
//         var gname = ielem.readAttribute( 'igname' );
//         var cname = ielem.readAttribute( 'icname' );
//         
//         this.hist_states.push( null );          // placeholder
//         doAjaxAdapterCallJS( 'user_state', 'getUSValue', 
//                                     { igname: gname, icname: cname },
//                                     '',
//                                     function( iresponse )
//                                         {
//                                         var first = document.createElement( 'a' );
//                                         first.writeAttribute( 'zadapter', zadapter );
//                                         first.writeAttribute( 'zmethod', zmethod );
//                                         first.writeAttribute( 'zmethod_args', zmethod_args );
//                                         first.writeAttribute( 'jinvalids', jinvalids );
//                                         first.writeAttribute( 'igname', gname );
//                                         first.writeAttribute( 'icname', cname );
//                                         first.writeAttribute( 'ivalue', iresponse.responseJSON );
//                                         this.hist_states[ 0 ] = first;
//                                         }.bind( this )
//                                      );           
// 
//         },
//         
//     zzz_histSave: function( ievent )
//         {   /* called by code to save a point from which the user can move 
//                 back or forward */
//                 if( this.hist_index && this.hist_index < this.hist_states.length - 1 )
//             this.hist_states.length = this.hist_index + 1;
//                 
//                 this.hist_index = this.hist_form.iindex.value = this.hist_states.length;        
// 
//                 var hist_elem = findAdapterMethodElem( getEventElem( ievent ) );
//                 this.hist_states.push( hist_elem );      // this is pushing it :-)
//                                                                                 
//                 this.hist_form.submit();
//         }
//         
//     } );

// --------------------------------------------------------------------------------------
// Ajax adapter calling - javascript version
// --------------------------------------------------------------------------------------
doAjaxAdapterCallJS = function( iadapter, imethod, imethod_args, ijinvalids, ion_success )
    {
    earea = $( 'page_error_area' );
    if( earea )
        earea.update( '' );
        
    gBusyLayer().startBusy( "" );
    
    imethod_args = $H( imethod_args );
    
    var req_vars = $H( {} );                
    zmethod_args = imethod_args.keys().join( '|' );
    if( zmethod_args )
        {
        req_vars.set( 'zmethod_args', zmethod_args );
        req_vars.update( imethod_args );
        }
    
                                 // what to call
    req_vars.set( 'zadapter', iadapter );
    req_vars.set( 'zmethod', imethod );
    
                                // info for onAjaxSucceedAdapterCall()
    if( ijinvalids )
        req_vars.set( 'jinvalids', ijinvalids );
        
    var success_func = null;
    
    if( !ion_success )
        success_func = onAjaxSucceedAdapterCall;
    else
        {                        // we must call onAjaxSucceedAdapterCall()
        success_func = function( iresponse )
                {
                onAjaxSucceedAdapterCall( iresponse );
                ion_success( iresponse );
                };
        }
    
    var rqst = new Ajax.Request
        (
        "handleAjaxAdapterCall",
        {
        method: 'get',
        parameters: req_vars,
        onSuccess: success_func,
        onFailure: onAjaxFailServer,
        onException: onAjaxFailClient,
        asynchronous: true
        } );    

    };

readInitVals = function( ijelem, ithis )
    {   /* transfer init vals from element to object - see NSBPInfo.isJSInstanceOf()
            NB values are "convertified" (tm) */
    var jinits = ijelem.readAttribute( 'jinit_vals' );
    var inits = $H( {} );
    $A( jinits.split( '|' ) ).each( function( iname ) 
        {
        var val = ijelem.readAttribute( iname );
        var cval;
        if( val == 'False' )
            cval = false;
        else if( cval == 'True' )
            cval = true;
        else if( val.charAt( 0 ) == '0' )
            cval = val;
        else if( val.indexOf( '.' ) != -1 )
            cval = parseFloat( val );
        else
            cval = parseInt( val );
        if( isNaN( cval ) )
            cval = val;
        inits.set( iname, cval );                
        } );
    Object.extend( ithis, inits._object );   // a bit suspect but ok really
    };

// --------------------------------------------------------------------------------------
// Ajax adapter calling - element based routines - used for general UI
// --------------------------------------------------------------------------------------

//  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
doAjaxAdapterCallFromCombo = function( ievent )
    /* called from onchange for combo boxes - picks up value from current
        selection */
    {
    var jelem = getEventElem( ievent );    
    var val = jelem.options[ jelem.selectedIndex ].value;
    jelem = findAdapterMethodElem( jelem );

    var arg_name = jelem.readAttribute( 'zcombo_arg' );
    if( !arg_name )
        arg_name = 'icombo_val';
                    
    var extra_arg = $H( {} ); 
    extra_arg.set( arg_name, val );
    doAjaxAdapterCall( jelem, extra_arg );    
    };

//  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
doAjaxAdapterCall = function( ievent, iextra_args, ion_success )
    /*
        call one of the server's "adapter" methods
        NB handles redirects too - slightly blech but will do
    */
    {
    earea = $( 'page_error_area' );
    if( earea )
        earea.update( '' );
        
    gBusyLayer().startBusy( "" );
    
    var jelem = getEventElem( ievent );
    jelem = findAdapterMethodElem( jelem );
        
    var req_vars = $H( {} );                
                                // transfer arguments to adapter method that were 
                                //    bolted onto the adapter element
    var zmethod_args = jelem.readAttribute( 'zmethod_args' );
    if( zmethod_args )
        {
        req_vars.set( 'zmethod_args', zmethod_args );
        $A( zmethod_args.split( '|' ) ).each
            ( 
            function( rname ) 
                {
                var rv = jelem.readAttribute( rname );
                if( rv )        // nessary for some rare situations (file upload)
                    req_vars.set( rname, rv ); 
                } 
            );
        }
                                 // _must_ be after above - caller code may rely on
                                 //     extra_args overwriting zmethod_args
    if( iextra_args )
        {                        // gotta watch if mebbe iextra_args has zmethod_args?
        var rvars = req_vars.get( 'zmethod_args' ); 
        var rkeys = iextra_args.keys().join( '|' );
        req_vars.update( iextra_args );        
        if( rvars )
            req_vars.set( 'zmethod_args', rvars + '|' + rkeys );
        else
            req_vars.set( 'zmethod_args', rkeys );
        }
                                 // what to call
    req_vars.set( 'zadapter', jelem.readAttribute( 'zadapter' ) );
    req_vars.set( 'zmethod', jelem.readAttribute( 'zmethod' ) );
    req_vars.set( 'jelemref', jelem.readAttribute( 'id' ) );  // passed back with response
    
                                // info for onAjaxSucceedAdapterCall()
    var jinvalids = jelem.readAttribute( 'jinvalids' );
    if( jinvalids )
        req_vars.set( 'jinvalids', jinvalids );
    var zredirect = jelem.readAttribute( 'zredirect' );
    if( zredirect )
        req_vars.set( 'zredirect', zredirect );
    
    if( !ion_success )
        ion_success = onAjaxSucceedAdapterCall;
        
    var rqst = new Ajax.Request
        (
        "handleAjaxAdapterCall",
        {
        method: 'get', 
        parameters: req_vars,
        onSuccess: ion_success,
        onFailure: onAjaxFailServer,
        onException: onAjaxFailClient,
        asynchronous: true
        }
        );
    };
    
//  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -    
onAjaxSucceedAdapterCall = function( iresponse )
    {
    //var except_type = iresponse.getResponseHeader( 'nsb-exception-type' );
    var except_val = iresponse.getResponseHeader( 'nsb-exception-val' );
    if( !except_val )
        showErrorMsg( "" );     // zap any current error message
    else
        onAjaxFailServer( iresponse );      // this branch not used very often

    gBusyLayer().stopBusy();
    
    // NB we have either jinvalids or zredirect, never both but often neither
    var jinvalids = iresponse.getResponseHeader( 'X-JInvalids' );
    if( jinvalids )
        doAjaxInvalidates( jinvalids );
        
                // FIXME - do we want to send req_vars with the redirect?
    var zredirect = iresponse.getResponseHeader( 'X-ZRedirect' );
    if( zredirect )
        {       // NB seems seriously weird to do such a significant thing as 
                //    change the user's entire environment using an = sign ;-)
        window.location = zredirect;
        }
    };
    
// --------------------------------------------------------------------------------------
// adapter call element access

getEventElem = function( ievent )
    /* this function takes an event - in which case it returns the event's
        element - or an element itself - which is just returned. 
        NB as a convenience, if an event is passed in, the event is stopped
        NB this method is simply motivated by us not wanting to have 2
            versions of event-handling methods 
        NB we need to use the $() func for IE otherwise readAttribute() doesn't
            work. I think this is the best place for a fix since its used by
            most functions right at the start */
    {
    if( Object.isElement( ievent ) )
        return ievent;
    Event.stop( ievent );
    return $( Event.element( ievent ) );
    
    var jelem = $( Event.element( ievent ) );   // for IE 
    if( !jelem )
        return ievent;      // in this case ievent will actually be an element
    Event.stop( ievent );
    return jelem;
    };

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
findAdapterMethodElem = function( ielem )
    /* return the first element containing the necessary attributes for
        adapter calling 
        NB for the short-term, it makes most sense for this guy to
            fail if the element isn't found. We should add default 
            argument at some point 
        NB we used to check for zmethod, but removed for redirect 
        NB the $()s are nessary for IE */
    {
    var jelem = $( ielem );             
    while( jelem.tagName.toUpperCase() != "BODY" ) 
        {  
        if( jelem.readAttribute( 'zadapter' ) ) //  && 
            // jelem.readAttribute( 'zmethod' ) )
            return jelem;
        jelem = $( jelem.parentNode );
        }
    throw ReferenceError( "NSB Portal-JS - unable to find adapter element for " + ielem );
    };
    
// --------------------------------------------------------------------------------------
// ajax invalidation

doAjaxInvalidates = function( ijinvalids )
    {   /* take a '|' separated string of element names and invalidate them all
            NB a convenience function - not used everywhere it could be */
    $A( ijinvalids.split( '|' ) ).each
        ( 
        function( inval_name ) 
            {
            var inval = $( inval_name );
            if( !inval )
                // dbug.error( "doAjaxElemInvalidate() - invalid element " + inval_name );
                alert( "doAjaxElemInvalidate() - invalid element " + inval_name );
            else
                doAjaxElemInvalidate( $( inval_name ) ); 
            } 
        );
    };

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
doAjaxElemInvalidate = function( ijelem )
    {
    if( !ijelem )
        throw TypeError( "NSB_PORTAL-JS - doAjaxElemInvalidate() - invalid element" );
    
    gWaitAnim().incWaiting();
    
    if( DBG_SHOW_UPDATES )      // debug messin
        {
        ijelem.removeClassName( 'nsbAjaxJustUpdated' );
        ijelem.addClassName( 'nsbAjaxJustInvalidated' );
        }
    
    var req_vars = $H( 
        {
        'pinfo_klass': ijelem.readAttribute( 'pinfo_klass' ),
        'pinfo_name': ijelem.readAttribute( 'pinfo_name' ),
        'jelemref': ijelem.readAttribute( 'id' )  // passed back with response
        } );
    
    var pinfo_args = ijelem.readAttribute( 'pinfo_args' );
    if( pinfo_args )
        {
        req_vars.set( 'pinfo_args', pinfo_args );
        $A( pinfo_args.split( '|' ) ).each
            ( 
            function( rname ) 
                { req_vars.set( rname, ijelem.readAttribute( rname ) ); } 
            );
        }
        
    var rqst = new Ajax.Request
        (
        "handleAjaxElemInvalidate",
        {
        method: 'get', 
        parameters: $H( req_vars ),
        
        onSuccess: onAjaxElemUpdate,            
        onFailure: onAjaxFailServer,
        onException: onAjaxFailClient,
        
        evalScripts: true              // we want this
        } );
    };
    
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
onAjaxElemUpdate = function( iresponse )
    {
                            // grab our element based on our magic headers
    var jelemref = iresponse.getResponseHeader( 'X-JElemRef' );
    var jelem = $( jelemref );
        
                            // update its contents
    jelem.update( iresponse.responseText );
    

    if( DBG_SHOW_UPDATES )  // debug messin
        {
        jelem.addClassName( 'nsbAjaxJustUpdated' );
        }
    
    gWaitAnim().decWaiting();
    };
        
// --------------------------------------------------------------------------------------
// Wait Anim
// --------------------------------------------------------------------------------------
NSB.WaitAnim = Class.create( 
    {
    initialize: function()
        {
        this.wait_elem = $( 'wait_indicate' );
        this.wait_counter = 0;
        },
        
    incWaiting: function()
        {
        if( !this.wait_counter )
            this.wait_elem.setStyle( { visibility: 'visible' } );
        this.wait_counter++;
        },

    decWaiting: function()
        {
        this.wait_counter--;
        if( !this.wait_counter )
            this.wait_elem.setStyle( { visibility: 'hidden' } );
        }

    } );

_gWaitAnim = null;
gWaitAnim = function()
    {
    if( !_gWaitAnim )
        _gWaitAnim = new NSB.WaitAnim();
    return _gWaitAnim;
    };
        
// --------------------------------------------------------------------------------------
// Busy Layer
// --------------------------------------------------------------------------------------
NSB.BusyLayer = Class.create( 
    {
    initialize: function()
        {
        this.busy_elem = $( 'busy_layer' );
        this.busy_message = this.busy_elem.selectPart( ".busy_message" );
        this.orig_message = this.busy_message.innerHTML;
        this.busy_counter = 0;
        this.busy_delay = 6;         // secs to wait before showing layer
        this.busy_timeout = null;    // a timeout object that we may want to clear
        },
        
    startBusy: function( imsg )
        {
        if( imsg )
            this.busy_message.update( imsg );
        if( !this.busy_counter )
            this.busy_timeout = this.showBusy.bind( this ).delay( this.busy_delay );
        this.busy_counter++;
        },
        
    stopBusy: function()
        {
        this.busy_counter--;
        if( !this.busy_counter )
            {
            this.busy_elem.setStyle( { display: 'none' } );
            this.busy_message.update( this.orig_message );
            
            if( this.busy_timeout )
                clearTimeout( this.busy_timeout );
            this.busy_timeout = null;
            if( this.update_timer )
                this.update_timer.stop();
            this.update_timer = null;
            }
        },
        
    showBusy: function()
        {   /* shows the busy layer - called after this.busy_delay seconds 
                unless stopBusy() pre-empts it */
        this.busy_elem.setStyle( { display: '' } );
        this.update_timer =  new PeriodicalExecuter( this.updateBusyMessage.bind( this ), 2 );
        },
        
    updateBusyMessage: function()
        {
        doAjaxAdapterCallJS( 'user_state', 'readBusyMessage', 
                {}, '', this.onUpdateBusyMessage.bind( this ) );
           
        },
        
    onUpdateBusyMessage: function( iresponse )
        {
        var msg = iresponse.responseJSON;
        if( msg )
            this.busy_message.update( msg );
        },
        
    startCCBusy: function()
        {   /* called when we detect we are rendering a page after being redirected
                after credit card details have been submitted */
        this.busy_elem.setStyle( { display: 'block' } );
        this.busy_counter++;
        this.monitor_timer = new PeriodicalExecuter( this.monitorCCBusy.bind( this ), 2 );
        },
        
    monitorCCBusy: function()
        {
        doAjaxAdapterCallJS( 'user_state', 'getUSValue', 
                                            {
                                            igname: 'purchase', 
                                            icname: 'cc_processing' 
                                            }, 
                                            '', 
                                            this.onMonitorCCBusy.bind( this ) );
        },
        
    onMonitorCCBusy: function( iresponse )
        {
        var proc = iresponse.responseJSON;
        if( !proc )
            {
            this.stopBusy();
            this.monitor_timer.stop();
            this.monitor_timer = null;
            }
        }
        
    } );

_gBusyLayer = null;
gBusyLayer = function()
    {
    if( !_gBusyLayer )
        _gBusyLayer = new NSB.BusyLayer();
    return _gBusyLayer;
    };
    
// ----------------------------------------------------------------------------------
// error layer handling - doesn't warrant a class methinks

function showErrorLayer( imessage )
    {
                                // in case error occurs in popup
    $( 'popup_overlay_effect_img' ).setStyle( { display: 'none' } );

    var elayer = $( 'error_layer' );
    elayer.selectPart( '#error_contents_elem' ).update( imessage );
    elayer.setStyle( { display: 'block' } );
    };

function clearErrorLayer()
    {
    elayer = $( 'error_layer' ).setStyle( { display: 'None' } );
    };

// ----------------------------------------------------------------------------------
// Ajax.Request error handlers (basic)

function onAjaxFailServer( itransport )
    {   /* called when there's a failure server-side */

    gBusyLayer().stopBusy();
    
    var except_info = 
        {
        except_type: itransport.getResponseHeader( 'nsb-exception-type' ),
        except_val: itransport.getResponseHeader( 'nsb-exception-val' )
        };
        
    // 090820 MAJW - use error layer but leave old code in-place
    // if( except_info[ 'except_type' ] == "nsbUserErr" )
    //     var failmsg = new Template( "#{except_val}" );
    // else
    //     var failmsg = new Template( "<b><em>Server #{except_type}</em></b> - #{except_val}" );
    // showErrorMsg( failmsg.evaluate( except_info ) );
    
    var failmsg = new Template( "#{except_val}" );
    showErrorLayer( failmsg.evaluate( except_info ) );
    };
  
    
function onAjaxFailClient( itransport, iexcept )
    {   /* called when there's a failure client-side */
    /* in practice, these client errors are generally spurious and not 
          really much use / interest to end-users so logging is more 
          appropriate. NB we'll still see these appear in FF / firebug 
          if its installed */
    // showErrorMsg( "<b>Client Error</b> - " + iexcept.toString() );
    gBusyLayer().stopBusy();
    
    // 090819 MAJW - commented out so it doesn't mess with IE
    // dbug.error( "Client Error - " + iexcept.toString() );
    };

function showErrorMsg( ierrmsg )
    {   /* we need to show / hide error area for sake of IE */
    earea = $( 'page_error_area' );
    if( !earea )
        {
        if( ierrmsg )
            alert( ierrmsg );
        }
    else
        {
        if( ierrmsg )
            {
            earea.update( ierrmsg );
            earea.setStyle( { display: 'block' } );
            }
        else
            {
            earea.setStyle( { display: 'none' } );
            }
        }
    };

// ------------------------------------------------------------------------------------
NSB.GMapBasic = Class.create( 
    {
    initialize: function( ijelem_id, imap_elem_id )
        /* ijelem_id ie the element that contains the "this" attributes, 
            whilst imap_elem_id is the element in which we display the map.
            These can be the same, but making them separate elements makes
            updates cleaner */
        {
        var jelem = $( ijelem_id );
        readInitVals( jelem, this );        
        
        this.moving_map = false;        // used by showMarkerAt()
        
        var map_elem = imap_elem_id ? $( imap_elem_id ) : jelem;
        this.gmap = new GMap2( map_elem );
        
        if( !this.enable_map_move )
            this.gmap.disableDragging();
        else
            {                           // considered having a "mini" flag but this
                                        //    may be better. NB the figure of 270
                                        //    is correct for both types of control
            var map_size = this.gmap.getSize();
            if( map_size.height >= 270 )
                this.gmap.addControl( new GLargeMapControl() );
            else
                this.gmap.addControl( new GSmallMapControl() );
        
            // if( map_size.width >= 270 )
            //     this.gmap.addControl( new GMapTypeControl() );
            }
        
        var map_cen = new GLatLng( this.map_lat, this.map_long );
        this.gmap.setCenter( map_cen, this.map_zoom );
        
        if( this.map_type == "Satellite" )
            this.gmap.setMapType( G_SATELLITE_MAP ); 
        else if( this.map_type == "Hybrid" )
            this.gmap.setMapType( G_HYBRID_MAP ); 
        
        GEvent.bind( this.gmap, "zoomend", this, this.onMapZoomEnd );
        GEvent.bind( this.gmap, "moveend", this, this.onMapMoveEnd );
        GEvent.bind( this.gmap, "maptypechanged", this, this.onMapTypeChange );
        
        if( typeof( this.onMapClick ) != "undefined" )
            GEvent.bind( this.gmap, "click", this, this.onMapClick );
            
        if( typeof( this.onMapDoubleClick ) != "undefined" )
            {
            this.gmap.disableDoubleClickZoom();
            GEvent.bind( this.gmap, "dblclick", this, this.onMapDoubleClick );
            }

        return jelem;
        },
        
    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    /* NB we implement 3 separate meths so subclasses can override particular
        ones. However, if all map move / zoom ops should do the same thing
        then subclasses should override onMapViewChange() */
    onMapMoveEnd: function( )
        {
        this.onMapViewChange();
        },

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    onMapZoomEnd: function( iold_level, inew_level )
        {
        this.onMapViewChange();
        },

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    onMapTypeChange: function()
        {
        this.onMapViewChange();
        },

        
    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    onMapViewChange: function()
        {
        var center = this.gmap.getCenter();
        
        doAjaxAdapterCallJS( 'user_state', 'updateUSMapValues', 
            {
            igname: this.jobj_name,
            imap_lat: center.lat(),
            imap_long: center.lng(),
            imap_zoom: this.gmap.getZoom(),
            imap_type: this.gmap.getCurrentMapType().getName()
            } );
        },

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    showMarkerAt: function( imarker, ilat_long )
        {
        if( imarker )       // short term hack
            imarker.setLatLng( ilat_long );

                /* move the map so that the marker is always visible...
                    this was _much_ more painful than anticipated */
        var pix_pos = this.gmap.fromLatLngToDivPixel( ilat_long );
        var ltlg_size = this.gmap.getBounds();
        var pix_rt = this.gmap.fromLatLngToDivPixel( ltlg_size.getNorthEast() );
        var pix_lb = this.gmap.fromLatLngToDivPixel( ltlg_size.getSouthWest() );
        
        var margin = 100;       // distance from edge to initiate panning
        var do_pan = false;

        if( pix_pos.x < pix_lb.x + margin || pix_pos.x > pix_rt.x - margin )
            do_pan = true;
        else if( pix_pos.y < pix_rt.y + margin || pix_pos.y > pix_lb.y - margin )
            do_pan = true;

        if( do_pan )
            {
            GEvent.addListener      // need to re-set the center when pan complete
                (
                this.gmap,
                "moveend",
                function()
                    {
                    if( this.moving_map )
                        return;
                    this.moving_map = true;   // nessary
                    this.gmap.setCenter( ilat_long );
                    GEvent.clearListeners( this.gmap, "moveend" );
                    this.moving_map = false;
                    }.bind( this )
                );
            this.gmap.panTo( ilat_long );
            }
        },

    ensureLocVisible: function( ilat_long, imargin_pix )
        {       /* move the map so that the ilat_long is visible, panning
                    if possible... this was _much_ more painful than anticipated 
                NB this code takes account of the fact that if the map has been
                   scrolled at all then we can't just use the bounds of the
                   map div  */
        var pix_pos = this.gmap.fromLatLngToDivPixel( ilat_long );
        var ltlg_size = this.gmap.getBounds();
        var pix_rt = this.gmap.fromLatLngToDivPixel( ltlg_size.getNorthEast() );
        var pix_lb = this.gmap.fromLatLngToDivPixel( ltlg_size.getSouthWest() );

        var margin = imargin_pix;     // distance from edge to initiate panning
        var do_pan = false;

        if( pix_pos.x < pix_lb.x + margin || pix_pos.x > pix_rt.x - margin )
            do_pan = true;
        else if( pix_pos.y < pix_rt.y + margin || pix_pos.y > pix_lb.y - margin )
            do_pan = true;
        
        if( do_pan )
            {
            GEvent.addListener      // need to re-set the center when pan complete
                (
                this.gmap,
                "moveend",
                function()
                    {
                    if( this.moving_map )
                        return;
                    this.moving_map = true;   // nessary
                    this.gmap.setCenter( ilat_long );
                    GEvent.clearListeners( this.gmap, "moveend" );
                    this.moving_map = false;
                    }.bind( this )
                );
            this.gmap.panTo( ilat_long );
            }
        },
        
    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    offsetLocByPixels: function( iloc, ipixh, ipixv )
        {   /* handy */
        var loc_pix = this.gmap.fromLatLngToDivPixel( iloc );
        return this.gmap.fromDivPixelToLatLng( 
                        new GPoint( loc_pix.x + ipixh, loc_pix.y + ipixv ) );
        },
        
    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    getMovedOffsets: function()
        {   /* the distances x, y that the map has been moved since initialization */
        var ltlg_size = this.gmap.getBounds();
        return {
                x: this.gmap.fromLatLngToDivPixel( ltlg_size.getSouthWest() ).x,
                y: this.gmap.fromLatLngToDivPixel( ltlg_size.getNorthEast() ).y
                };
        }
    } );

// ====================================================================================
NSB.DivOverlay = Class.create(
    {       /* NB the initialization method is a bit weird but it appears to work ok */

    initialize: function( inw_loc, ise_loc, iclassname, iopacity, itooltip, iclickable )
        {   /* we need opacity as an arg here, but it may be omitted if no tooltip 
            NB ise_loc may be null for overlays that use pixel size instead of map area */
        if( typeof arguments[ 0 ].addMapType == "function" )
            {
            this.addToMap( arguments[ 0 ] );    // first arg is in fact the map
            return true;
            }
            
        if( typeof( iopacity ) == "undefined" )
            iopacity = 1.0;
        
        this.nw_loc_ = inw_loc;
        this.se_loc_ = ise_loc;
        this.tooltip_ = itooltip;
        this.clickable = iclickable;            // 080910 new
        
        this.div_ = $( document.createElement( "DIV" ) );
        this.div_.setStyle( { 
                position: "absolute",
                overflow: "hidden",
                width: "auto",
                height: "auto",
                opacity: iopacity } );
        
        if( typeof( iclassname ) != "undefined" )
            this.div_.addClassName( iclassname );

        if( this.tooltip_ != null )
            {
            this.div_.style.cursor = "help";
            this.div_.title = this.tooltip_;
            }  
                          
        return false;
        },
        
    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    addToMap: function( imap )
        {   /* important: if we're added to the G_MAP_MAP_PANE then I'm 
                convinced we get better performance, but we can't recieve
                clicks - hence the this.clickable flag */
        this.gmap = imap;
        var pane;
        if ( this.floating )
            pane = G_MAP_FLOAT_PANE;
        else if ( this.clickable )
            pane = G_MAP_MARKER_PANE;
        else
            pane = G_MAP_MAP_PANE;
        this.gmap.getPane( pane ).appendChild( this.div_ );
        },

    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    setLatLngPos: function( iloc )
        {   /* was called setLatLng() in NDF 
                NB FIXME - this is a bit useless / unclear. Sets top left pos only */
        this.nw_loc_ = iloc;
        var nw_pix = this.gmap.fromLatLngToDivPixel( this.nw_loc_ );        
        // for now we'll not worry about borders
        // var bwid = parseInt( this.div_.getStyle( 'border-left-width' ) );
        var bwid = 0;
                            // NB direct access to this.div_.style - old style (pre $())
        this.div_.style.left = nw_pix.x - bwid + "px";
        this.div_.style.top = nw_pix.y - bwid + "px";
        },
        
    setLatLngBounds: function( inw_loc, ise_loc, ino_redraw )
        {   /* was called setLLBounds() in NDF */
        this.nw_loc_ = inw_loc;
        this.se_loc_ = ise_loc;
        if( !ino_redraw )
            this.redraw( true );
        },
        
    setPixSize: function( iwid, ihgt, ino_redraw )
        {
        this.div_.setStyle( { 
                width: iwid + "px",
                height: ihgt + "px" } );
        if( !ino_redraw )
            this.redraw( true );
        },
        
    setOpacity: function( iopacity, ino_redraw )
        {
        this.div_.setStyle( { opacity: iopacity } );        
        if( !ino_redraw )
            this.redraw( true );
        },
        
    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    getVisible: function()
        {
        return this.div_.getStyle( 'visibility' ) != 'hidden';
        },
        
    setVisible: function( ivis )
        {
        this.div_.setStyle( { visibility: ivis ? 'visible' : 'hidden' } );
        },
        
    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    removeFromMap: function() 
        {   /* was called remove() in NDF */
        this.div_.parentNode.removeChild( this.div_ );
        },
        
    //  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    redraw: function( force )
        {
        if (!force) 
            return;

        var nw_pix = this.gmap.fromLatLngToDivPixel( this.nw_loc_ );        
        // for now we'll not worry about borders
        // var bwid = parseInt( this.div_.getStyle( 'border-left-width' ) );
        var bwid = 0;
                            // NB direct access to this.div_.style - old style (pre $())
        this.div_.style.left = nw_pix.x - bwid + "px";
        this.div_.style.top = nw_pix.y - bwid + "px";
        
        if( this.se_loc_ )
            {
            var se_pix = this.gmap.fromLatLngToDivPixel( this.se_loc_ );
            this.div_.style.width = se_pix.x - nw_pix.x + "px";
            this.div_.style.height = se_pix.y - nw_pix.y + "px";
            }
        }
    } );
