﻿///////////////////////////////////////////////////////////////////////////////
//
//  Grid
//
//  Shows a grid of elements with paging and hover-over in-place previews.
//
///////////////////////////////////////////////////////////////////////////////

 
//var BASEURL = window.location.protocol + '//' + window.location.host + (( (window.location.toString()).indexOf( "endlessweb" ) > 1 ) ? ":3284/endlessweb/" : "/" ) ;
var BASEURL = "http://www.theendlesswave.com/" ;
try {
if( (window.location.toString()).indexOf( "localhost" ) > 0 ) 
    BASEURL = "http://localhost:3284/endlessweb/" ;
} catch(u){}

if (!window.Silverlight) {
    window.Silverlight = {};
}
if (!window.GridCtrl) {
    window.GridCtrl = {};
}

/*
Silverlight.createSilverlight

Creates the Silverlight control and instantiates a scene object to drive it.
*/

//Silverlight.createSilverlight = function(scene, parentElementId, queryType, queryValue, geoValue, latlongValue, distValue, elementsRecievedFunc, elementsOverFunc, elementsOutFunc, elementType, configuration, SrchResDiv, selectedIndex, isMyPage, isLimitedUserInstall) {
//    scene = new GridCtrl.Scene(queryType, queryValue, geoValue, latlongValue, distValue, elementsRecievedFunc, elementsOverFunc, elementsOutFunc, elementType, configuration, SrchResDiv, selectedIndex, isMyPage, isLimitedUserInstall);
//    Silverlight.createHostedObjectEx( {
//        source: "GridCtrl/Grid.xaml",
//        parentElement: document.getElementById(parentElementId),
//        id: "GridCtrl_" + parentElementId,
//        properties: {
//            width: scene._layout.sceneWidth.toString(),
//            height: scene._layout.sceneHeight.toString(),
//            inplaceInstallPrompt:true,
//            background:'#FFFFFFFF',
//            isWindowless:'true',
//            framerate:'24',
//            version:'1.0'
//        },
//        events: {
//            onLoad: Silverlight.createDelegate(scene, scene.onLoad),  // function(control, userContext, rootElement) { return scene.onLoad( control, userContext, rootElement ) ; }, // Silverlight.createDelegate(scene, scene.onLoad)
//            onError: Silverlight.createDelegate(scene, scene.onError) // function(sender, errorEventArgs) { return scene.onError( sender, errorEventArgs) ; }
//        },
//        initParams:null,
//        context:null
//    } );

Silverlight.createSilverlight = function(scene, parentElementId, queryType, queryValue, geoValue, latlongValue, distValue, elementsRecievedFunc, elementsOverFunc, elementsOutFunc, elementType, configuration, SrchResDiv, selectedIndex, isMyPage, isLimitedUserInstall) {
    scene = new GridCtrl.Scene(queryType, queryValue, geoValue, latlongValue, distValue, elementsRecievedFunc, elementsOverFunc, elementsOutFunc, elementType, configuration, SrchResDiv, selectedIndex, isMyPage, isLimitedUserInstall);
    Silverlight.createObjectEx( {
        source: "GridCtrl/Grid.xaml",
        parentElement: document.getElementById(parentElementId),
        id: "GridCtrl_" + parentElementId,
        properties: {
            width: scene._layout.sceneWidth.toString(),
            height: scene._layout.sceneHeight.toString(),
            inplaceInstallPrompt:true,
            background:'#FFFFFFFF',
            isWindowless:'true',
            framerate:'24',
            version:'1.0'
        },
        events: {
            onLoad: Silverlight.createDelegate(scene, scene.onLoad),  // function(control, userContext, rootElement) { return scene.onLoad( control, userContext, rootElement ) ; }, // Silverlight.createDelegate(scene, scene.onLoad)
            onError: Silverlight.createDelegate(scene, scene.onError) // function(sender, errorEventArgs) { return scene.onError( sender, errorEventArgs) ; }
        }
    } );
};

/*
Silverlight.createDelegate

Helper method for creating delegates from object-based methods.
*/
Silverlight.createDelegate = function(instance, method) {
    return function() {
        return method.apply(instance, arguments);
    };
};

/*
Silverlight.installAndCreateSilverlight

Checks for installation of Microsoft Silverlight. 
If installed, it calls the CreateSilverlight method.
If not, it displays the content preview div and prompts the user to install Silverlight
If the user is running IE, a timout-driven loop will automatically load the Silverlight upon installation.

This method is based on recommendations from 
"Optimizing the Silverlight Installation Experience," 
Microsoft Silverlight, authors: PiotrP & JohnPay
*/

Silverlight.installAndCreateSilverlight = function(version, scene, parentElementId, installExperienceHTML, installPromptDivID, createSilverlightDelegate, queryType, queryValue, geoValue, latlongValue, distValue, elementsRecievedFunc, elementsOverFunc, elementsOutFunc, elementType, configuration, SrchResDiv, selectedIndex, isMyPage, isLimitedUserInstall) {
    var RetryTimeout=6400;	              //The interval at which Silverlight instantiation is attempted(ms)	
    if ( Silverlight.isInstalled(version) ) {
        createSilverlightDelegate(scene, parentElementId, queryType, queryValue, geoValue, latlongValue, distValue, elementsRecievedFunc, elementsOverFunc, elementsOutFunc, elementType, configuration, SrchResDiv, selectedIndex, isMyPage, isLimitedUserInstall);
    } else {
        var parentElement = document.getElementById(parentElementId);
        if ( installExperienceHTML && parentElement ) {
            parentElement.innerHTML=installExperienceHTML;
        } 
        if (installPromptDivID) {
            var installPromptDiv = document.getElementById(installPromptDivID);
            if ( installPromptDiv ) {
                installPromptDiv.innerHTML = Silverlight.createObject(null, null, null, {version: version, inplaceInstallPrompt:true},{}, null);
            }
        }

        if ( ! (Silverlight.available || Silverlight.ua.Browser != 'MSIE' ) ) {
            TimeoutDelegate = function() {
                Silverlight.installAndCreateSilverlight(version, scene, parentElementId, installExperienceHTML, installPromptDivID, createSilverlightDelegate, queryType, queryValue, geoValue, distValue, elementsRecievedFunc, elementsOverFunc, elementsOutFunc, elementType, configuration, SrchResDiv, selectedIndex, isMyPage, isLimitedUserInstall);
                if ( Silverlight.isInstalled(version) ) window.location = window.location ;
            };
            setTimeout(TimeoutDelegate, RetryTimeout);
        }

    }
};

/*
Grid.Scene

Represents the overall video grid. 
Handles overall initialization, paging, and web service data access 
*/
GridCtrl.Scene = function(queryType, queryValue, geoValue, latlongValue, distValue, elementsRecievedFunc, elementsOverFunc, elementsOutFunc, elementType, configuration, SrchResDiv, selectedIndex, isMyPage, isLimitedUserInstall) {
    this._queryType = queryType;
    this._queryValue = queryValue;
    this._geoValue = geoValue ;
    this._latlongValue = latlongValue;
    this._distValue = distValue ;
    this._elementsRecievedFunc = elementsRecievedFunc ;
    this._elementsOverFunc = elementsOverFunc ;
    this._elementsOutFunc = elementsOutFunc; 
    this._elementType = elementType ;
    this._isMyPage = isMyPage;
    this._isLimitedUserInstall = isLimitedUserInstall;
    this._srchResDiv = ( (SrchResDiv == null || SrchResDiv.length < 1) ? null : document.getElementById(SrchResDiv) );

    this._layout = new GridCtrl.Layout(configuration);
    this._currentPage = Math.floor(selectedIndex/this._layout.gridMaxItems) + 1;
    this._selectedIndex = selectedIndex % this._layout.gridMaxItems;
    
    this._inactiveGridId = '2['+elementType+']['+geoValue+']['+distValue+']['+queryType+']';
    this._activeGridId = '1['+elementType+']['+geoValue+']['+distValue+']['+queryType+']';
    this._sortBy = "" ;
    var sortby = document.getElementById("SortBy");
    if( sortby != null && sortby.value != null )
        this._sortBy = sortby.value ;
};


GridCtrl.Scene.prototype = {
    /* 
        Scene resources
    */
    _control: null, // Silverlight plugin
    _rootElement: null, // Silverlight root canvas element
    _xamlResources: null, // Downloaded XAML resource zip archive
    _fonts: null, // Download font zip archive
    _layout: null, // Layout object containing layout paramaters for the video grid
    
    /* 
        Video metadata
        (Two grids are created to achieve the paging effect. When the user pages the results,
        the scene populates the inactive grid. It then slides the active grid out and slides
        the inactive grid in to accomplish the page.  Active and inactive states are then 
        switched.
    */
    _elementList: null, // Collection of metadata for current grid (received from element web service)
    _queryType: null, // The type of query. Values: Tag, Owner, Favorite, RecentViews
    _queryValue: null, // The value of the query
    _geoValue: null, // The value of the location query
    _latlongValue: null, // the value of a specific lat long query
    _distValue: null, // The value of the search radius
    _elementsRecievedFunc: null, // Func to call when the grid items have arrived
    _elementsOverFunc: null, // Func to call when the mouse enters grid item
    _elementsOutFunc: null, // Func to call when the mouse leaves grid item
    _elementType: null, // The ElementType for the Query
    _sortBy: null, // The sort order for the Query
    _ownerName: null, // For Owner and Favorite queries, the owner/favoriter's user name. Used for no-results messaging.
    _isMyPage: false, // Specifies if an Owner query is for the current user. Used for no-results messaging.
    _srchResDiv: null, // Specifies div to update with Search Results Information (can be null)
    _isLimitedUserInstall: true, // Indicates whether new users are giveen upload video permission.
    _noResultsUrl: null, // Specifies the destination of the no-results link.
    
    /* 
        Grids & Paging
        (Two grids are created to achieve the paging effect. When the user pages the results,
        the scene populates the inactive grid. It then slides the active grid out and slides
        the inactive grid in to accomplish the page.  Active and inactive states are then 
        switched.
    */
	_grids: [2], // grid array of Grid objects
    _sceneEnabled: true, // Indicates whether the scene disabled during paging
    _activeGridId: null, // Specifies which grid is currently being displayed
    _inactiveGridId: null, // Specifies which grid is currently hidden 
    _totalPages: null, // Number of pages in the current result set 
    _totalElements: null, // Number of videos in the current result set 
    _currentPage: 1, // Current result page
    _currentPageAction: 'none', // specifies whether the current/last paging action is next, previous, or none
                                // stores the state across asynchronous calls.

    /*
        onLoad
        handles the scene canvas load event. Begins grid initialization.
    */
	onLoad: function(control, userContext, rootElement) {
	    this._control = control;
	    this._rootElement = rootElement;

        this._rootElement.Width = this._layout.sceneWidth;
        this._rootElement.Height = this._layout.sceneHeight;
        
        mask = this._rootElement.findName('SceneMask');
        mask.Width = this._layout.gridWidth;
        mask.Height = this._layout.gridHeight;

        pagingSeparator = this._rootElement.findName('PagingSeparator');
        pagingSeparator.X1 = this._layout.sceneBufferLeft;
        pagingSeparator.X2 = this._layout.sceneBufferLeft + this._layout.gridWidth;
        pagingSeparator.Y1 = this._layout.gridHeight + 20;
        pagingSeparator.Y2 = this._layout.gridHeight + 20;

        previousButton = this._rootElement.findName('PreviousPage');
        previousButton['Canvas.Left'] = this._layout.sceneBufferLeft;
        previousButton['Canvas.Top'] = this._layout.gridHeight + 30;

        nextButton = this._rootElement.findName('NextPage');
        nextButton['Canvas.Left'] = this._layout.sceneWidth - this._layout.sceneBufferRight - nextButton.ActualWidth;
        nextButton['Canvas.Top'] = previousButton['Canvas.Top'];

        var pageCount = this._rootElement.findName('PageCount');
        pageCount['Canvas.Top'] = previousButton['Canvas.Top'];

		this.wireupSceneEvents();
        this.disableScene();
        
        this.getElements(); // kick off the call to get elements
        
		var resourceDownloader = control.CreateObject('downloader');
		resourceDownloader.addEventListener('completed', Silverlight.createDelegate(this, this.resourceDownloadComplete) ); //function(sender, eventArgs) { scene.resourceDownloadComplete(sender, eventArgs); } );
		resourceDownloader.open('GET', 'GridCtrl/GridCtrl.zip');
		resourceDownloader.send();

        //Can use the following to download specific fonts using downloader object
        /*
		var fontDownloader = control.CreateObject('downloader');
		fontDownloader.addEventListener('completed', Silverlight.createDelegate(this, this.fontDownloadComplete));
		fontDownloader.open('GET', '/GridCtrl/Fonts.zip');
		fontDownloader.send();
		*/
	},
	
    /*
        wireupSceneEvents
        Initializes all scene event handlers. Called on load
    */
    wireupSceneEvents: function() {
        this._nextPage = this._rootElement.findName('NextPage');
        this._previousPage = this._rootElement.findName('PreviousPage');
        var mask = this._rootElement.findName('SceneMask');
        var storyboard = mask.findName('HideSceneMask');
        var nextButton = mask.findName('NextPage');
        var previousButton = mask.findName('PreviousPage');
        var secondaryMessage = this._rootElement.findName('SecondaryMessageBtn');

        this._nextPage.addEventListener('MouseLeftButtonUp', Silverlight.createDelegate(this, this.pageNext));
        this._previousPage.addEventListener('MouseLeftButtonUp', Silverlight.createDelegate(this, this.pagePrevious));
        storyboard.addEventListener('Completed', Silverlight.createDelegate(this, this.onSceneEnabled));
        nextButton.addEventListener('MouseEnter', Silverlight.createDelegate(this, this.onMouseEnterPageButton));
        previousButton.addEventListener('MouseEnter', Silverlight.createDelegate(this, this.onMouseEnterPageButton));
        nextButton.addEventListener('MouseLeave', Silverlight.createDelegate(this, this.onMouseLeavePageButton));
        previousButton.addEventListener('MouseLeave', Silverlight.createDelegate(this, this.onMouseLeavePageButton));

        secondaryMessage.addEventListener('MouseLeftButtonUp', Silverlight.createDelegate(this, this.onNoResultsClick));
    },
    
    /*
        resourceDownloadComplete
        Called when xaml resources have been downloaded. Completes grid initialization.
    */
	resourceDownloadComplete: function(sender, eventArgs) {
	    // Store downloaded XAML templates
	    this._xamlResources = sender;
	    // Create grids
	    this._grids[this._activeGridId] = new GridCtrl.Grid(this, true);
	    this._grids[this._inactiveGridId] = new GridCtrl.Grid(this, false);

        this.setFonts();
        this.addGridItems();
    },
    
    /*
        fontDownloadComplete
        Called when fonts have been downloaded. Begins font initialization.
    */
	fontDownloadComplete: function(sender, eventArgs) {
        this._fonts = sender;
        this.setFonts();
    },
    
    /*
        setFonts
        Completes font initialization. 
        This function can occur only after two asynchronous events have completed:
        1) xaml resources must have downloaded (resourceDownloadComplete)
        2) fonts must have downloaded (fontDownloadComplete)
        To accomplish this, both methods above call setFonts, which only executes
        after the second call.
    */
    setFonts: function() {
//        if (this._xamlResources !== null && this._fonts !== null) {
//            var nextPage = this._rootElement.findName('NextPage');
//            var previousPage = this._rootElement.findName('PreviousPage');
//            var primaryMessage = this._rootElement.findName('PrimaryMessage');
//            var secondaryMessage = this._rootElement.findName('SecondaryMessage');
//            var pageCount = this._rootElement.findName('PageCount');

//            nextPage.setFontSource(this._fonts);
//            previousPage.setFontSource(this._fonts);
//            primaryMessage.setFontSource(this._fonts);
//            secondaryMessage.setFontSource(this._fonts);
//            pageCount.setFontSource(this._fonts);

//            nextPage.fontFamily = 'Segoe UI';
//            previousPage.fontFamily = 'Segoe UI';
//            primaryMessage.fontFamily = 'Segoe UI';
//            secondaryMessage.fontFamily = 'Segoe UI';
//            pageCount.fontFamily = 'Segoe UI';

//	        this._grids[this._activeGridId].setFonts(this._fonts);
//	        this._grids[this._inactiveGridId].setFonts(this._fonts);
//        }
    },
    
    /*
        getElements
        Makes an asynchronous call to element services for a new collection of elements
        Called at initialization and on paging actions (next/previous)
    */
    getElements: function() {
        //load elements
        switch (this._queryType) {
            case 'Tag':
                if (this._latlongValue != null && this._latlongValue.length > 2) {
        	        SearchWebService.GetElementsByTagLatLon(this._layout.gridMaxItems,this._currentPage, this._elementType, this._sortBy, this._queryValue, this._latlongValue, this._distValue, Silverlight.createDelegate(this, this.onElementListReceived));
        	    } else if (this._geoValue == '') {
        	        SearchWebService.GetElementsByTag(this._layout.gridMaxItems,this._currentPage, this._elementType, this._sortBy, this._queryValue, Silverlight.createDelegate(this, this.onElementListReceived));
        	    } else {
        	        SearchWebService.GetElementsByTagGeo(this._layout.gridMaxItems,this._currentPage, this._elementType, this._sortBy, this._queryValue, this._geoValue, this._distValue, Silverlight.createDelegate(this, this.onElementListReceived));
        	    }
        	    break;
            case 'Owner':
        	    SearchWebService.GetElementsByOwner(this._layout.gridMaxItems,this._currentPage, this._sortBy, this._queryValue, Silverlight.createDelegate(this, this.onElementListReceived));
        	    break;
            case 'Favorite':
        	    SearchWebService.GetElementsByFavorite(this._layout.gridMaxItems,this._currentPage, this._elementType, this._sortBy, this._queryValue, Silverlight.createDelegate(this, this.onElementListReceived));
        	    break;
            case 'RecentViews':
        	    SearchWebService.GetElementsByRecentViews(this._layout.gridMaxItems,this._currentPage, this._elementType, this._sortBy, this._queryValue, Silverlight.createDelegate(this, this.onElementListReceived));
        	    break;
        }
    },
    
    /*
        onElementListReceived
        Handles web service callback. Begins logic for populating a grid with videos.
    */
    onElementListReceived: function(result) {
        var streamingUrls = '';
        this._totalElements = result.TotalElements;
        this._totalPages = result.TotalPages;
        this._ownerName = result.OwnerName;
        this._elementList = result.Items;
        var sHTML = 'Searching ' + (( this._elementType == "All" ) ? "" : this._elementType ) ;
        if( this._queryValue != null && this._queryValue.length > 0 )
            sHTML += ' for keywords <b>' + this._queryValue + '</b> ' ;
        if( this._geoValue != null && this._geoValue.length > 0 )
            sHTML += ' within ' + this._distValue + ' miles of <b>' + this._geoValue + '</b> ' ;
        else if( this._latlongValue != null && this._latlongValue > 0 )
            sHTML += ' within ' + this._distValue + ' miles of point <b>' + this._latlongValue + '</b> ' ;
            
        sHTML += '...  ' + this._totalElements + ' ' + this._elementType + ' found.' ;
        if( this._srchResDiv != null && sHTML != null )
            this._srchResDiv.innerHTML = sHTML ;
            
        if (this._elementList && this._elementList.length > 0) {
            var len = result.Items.length;
            var bVideoAdded = false;
            for (var i=0; i<len; i++) {
                if( result.Items[i] != null && result.Items[i].Type == 1 /*Video*/ ) {
                    streamingUrls += result.Items[i].PreviewUrl + ',';
                    bVideoAdded = true ;
                }
            }
            
            if( bVideoAdded == true ) SLStreaming.translateUrls(streamingUrls,Silverlight.createDelegate(this, this.onSLStreamingUrlsTranslated));
            else this.addGridItems();
        } else {
            this.showNoResultsMessage();
        }
        
        if( this._elementsRecievedFunc != null )
        {
            try { (this._elementsRecievedFunc)( result ) ; } catch(u) { window.status = u.toString(); }
        }
    },


    /*
        onSLStreamingUrlsTranslated
        Handles callback for Silverlight Streaming "translate URLs" method. 
        Updates the video collection with absolute URLs for Silverlight Streaming
        and begins grid initialization.
    */
    onSLStreamingUrlsTranslated: function(result) {
        var len = this._elementList.length;
        for (var i=0; i < len; i++) {
            if( this._elementList[i] != null && this._elementList[i].Type == 1 /*Video*/ ) {
            //this._elementList[i].thumbnailUrl = result[this._elementList[i].thumbnailUrl];
            this._elementList[i].PreviewUrl = result[this._elementList[i].PreviewUrl];
            }
        }
    },
    

    /*
        addGridItems
        Initializes a grid of grid items based on the current video list.
        This function can occur only after two asynchronous events have completed:
        1) xaml resources must have downloaded (resourceDownloadComplete)
        2) the video list has been received and translated (onSLStreamingUrlsTranslated)
        To accomplish this, both methods above call addGridItems, which only executes
        after the second call.
    */
    addGridItems: function() {
        // if both async events (creating grids and receiving video list) have occurred
        if (this._xamlResources !== null && this._elementList !== null ) {
            this._grids[this._activeGridId].initializeGridItems(this._elementList, this._currentPageAction == 'none');

            // set new state of paging buttons
            var nextButton = this._rootElement.findName('NextPage');
            var previousButton = this._rootElement.findName('PreviousPage');
            var pageCount = this._rootElement.findName('PageCount');
            
            if (this._currentPage < this._totalPages) {
                nextButton.Visibility = "Visible";
            } else {
                nextButton.Visibility = "Collapsed";
            }
            if (this._currentPage > 1) {
                previousButton.Visibility = "Visible";
            } else {
                previousButton.Visibility = "Collapsed";
            }   
            pageCount.Text = ((this._currentPage-1) * this._layout.gridMaxItems + 1).toString();
            pageCount.Text += '-';
            if (this._currentPage < this._totalPages) {
                pageCount.Text += (this._currentPage * this._layout.gridMaxItems).toString();
            } else {
                pageCount.Text += this._totalElements.toString();
            }
            pageCount.Text += ' of ';
            pageCount.Text += this._totalElements.toString();
            pageCount['Canvas.Left'] = this._rootElement.Width / 2 - pageCount.ActualWidth / 2;

            switch(this._currentPageAction) {
                case 'next':
                    this._currentPageAction = 'none';
                    this.completePageNext();
                    break;
                case 'previous':
                    this._currentPageAction = 'none';
                    this.completePagePrevious();
                    break;
            }
            
            setTimeout(Silverlight.createDelegate(this, this.enableScene), 800);
        }
    },
    
    /*
        pageNext and pagePrevious
        Handles "paging" click events--begins paging process 
    */
    pageNext: function(sender, mouseEventArgs) {
        // disable scene
        this.disableScene();

        this._currentPageAction = 'next';
        this._currentPage++;
        this.getElements();
        var tmp =  this._activeGridId ;
        this._activeGridId = this._inactiveGridId;
        this._inactiveGridId = tmp;
    },
    pagePrevious: function(sender, mouseEventArgs) {
        // disable scene
        this.disableScene();

        this._currentPageAction = 'previous';
        this._currentPage--;
        this.getElements();

        // slide grids
        var tmp =  this._activeGridId ;
        this._activeGridId = this._inactiveGridId;
        this._inactiveGridId = tmp;
    },

    /*
        completePageNext and completePagePrevious
        Completes paging process by starting paging animations after new grid has been initialized.
    */
    completePageNext: function() {
        // page grids
        this._grids[this._inactiveGridId].pageOutToLeft();
        this._grids[this._activeGridId].pageInFromLeft();
    },
    completePagePrevious: function() {
        // page grids
        this._grids[this._inactiveGridId].pageOutToRight();
        this._grids[this._activeGridId].pageInFromRight();
    },

    /*
        disableScene, onDisableSceneTimeout, enableScene, and onSceneEnabled

        Disables and enables scene during paging (dims the grid and prevents other events from occurring)
        Paging relies on a successful asynchronous web service call to occur
        before the scene can be paged and re-enabled. If this does not occur, we
        set a 5 second timeout to re-enable the scene so the user isn't frozen.
    */

    disableScene: function() {
        this._sceneEnabled = false;

        var mask = this._rootElement.findName('SceneMask');
        mask.Visibility = "Visible";

        var loading = mask.findName('rotation');
        loading.Visibility = "Visible";

        var storyboard = mask.findName('ShowSceneMask');
        storyboard.begin();
        
        setTimeout(Silverlight.createDelegate(this, this.onDisableSceneTimeout), 7000);
    },
    
    onDisableSceneTimeout: function() {
        this.enableScene();  
    },

    enableScene: function() {
        if (!this._sceneEnabled) {
            this._sceneEnabled = true;
            var mask = this._rootElement.findName('SceneMask');
            var storyboard = mask.findName('HideSceneMask');
            storyboard.begin();
            var loading = mask.findName('rotation');
            loading.Visibility = "Collapsed";
            
            this.addGridItems();
        }
    },

    onSceneEnabled: function(sender, eventArgs) {
        var mask = this._rootElement.findName('SceneMask');
        mask.Visibility = "Collapsed";
        
        var nextButton = this._rootElement.findName('NextPage');
        nextButton.textDecorations = 'None';

        var previousButton = this._rootElement.findName('PreviousPage');
        previousButton.textDecorations = 'None';
    },
    
    /*
        onMouseEnterPageButton and onMouseLeavePageButton
        Handles hover behavior for paging links
    */

    onMouseEnterPageButton: function(sender, eventArgs) {
        sender.textDecorations = 'Underline';
    },
    
    onMouseLeavePageButton: function(sender, eventArgs) {
        sender.textDecorations = 'None';
    },

    /*
        showNoResultsMessage and onNoResultsClick
        handles the "no results" case, where the video grid has no videos.
        A simple message and link fill the grid.
    */
    
    showNoResultsMessage: function() {
        var noResultsCanvas = this._rootElement.findName('NoResultsMessage');
        var primaryMessage = this._rootElement.findName('PrimaryMessage');
        var secondaryMessage = this._rootElement.findName('SecondaryMessage');
        var storyboard = this._rootElement.findName('ShowNoResultsMessage');
        if( noResultsCanvas ) noResultsCanvas.Visibility = "Visible" ;
        
        switch (this._queryType) {
            case 'Tag':
                if (this._queryValue === '') {
                    primaryMessage.Text = GridCtrl.Strings.prototype.noResults_allPrimary;
                    secondaryMessage.Text = GridCtrl.Strings.prototype.noResults_allSecondary;
                    this._noResultsUrl = GridCtrl.Strings.prototype.noResults_allSecondaryUrl;
                } else {
                    primaryMessage.Text = GridCtrl.Strings.prototype.noResults_tagPrimary.replace(/\$tag/g, this._queryValue);
                    secondaryMessage.Text = GridCtrl.Strings.prototype.noResults_tagSecondary;
                    this._noResultsUrl = GridCtrl.Strings.prototype.noResults_tagSecondaryUrl;
                }
        	    break;
            case 'Owner':
                if (this._isMyPage) {
                    primaryMessage.Text = GridCtrl.Strings.prototype.noResults_myOwnerPrimary.replace(/\$userName/g, this._ownerName);
                    secondaryMessage.Text = GridCtrl.Strings.prototype.noResults_myOwnerSecondary;
                    this._noResultsUrl = GridCtrl.Strings.prototype.noResults_myOwnerSecondaryUrl;
                } else {
                    primaryMessage.Text = GridCtrl.Strings.prototype.noResults_ownerPrimary.replace(/\$userName/g, this._ownerName);
                    secondaryMessage.Text = GridCtrl.Strings.prototype.noResults_ownerSecondary;
                    this._noResultsUrl = GridCtrl.Strings.prototype.noResults_ownerSecondaryUrl;
                }
        	    break;
            case 'Favorite':
                if (this._isMyPage) {
                    primaryMessage.Text = GridCtrl.Strings.prototype.noResults_myFavoritesPrimary.replace(/\$userName/g, this._ownerName);
                    secondaryMessage.Text = GridCtrl.Strings.prototype.noResults_myFavoritesSecondary;
                    this._noResultsUrl = GridCtrl.Strings.prototype.noResults_myFavoritesSecondaryUrl;
                } else {
                    primaryMessage.Text = GridCtrl.Strings.prototype.noResults_favoritesPrimary.replace(/\$userName/g, this._ownerName);
                    secondaryMessage.Text = GridCtrl.Strings.prototype.noResults_favoritesSecondary;
                    this._noResultsUrl = GridCtrl.Strings.prototype.noResults_favoritesSecondaryUrl;
                }
        	    break;
            case 'RecentViews':
                primaryMessage.Text = GridCtrl.Strings.prototype.noResults_recentViewsPrimary.replace(/\$userName/g, this._ownerName);
                secondaryMessage.Text = GridCtrl.Strings.prototype.noResults_recentViewsSecondary;
                this._noResultsUrl = GridCtrl.Strings.prototype.noResults_recentViewsSecondaryUrl;
        	    break;
        }

        // if new users can't upload videos, don't taunt them
        if(this._isLimitedUserInstall)
        {
            secondaryMessage.Visibility = "Collapsed";
        }
        
        // center message on canvas
        noResultsCanvas.Width = (primaryMessage.ActualWidth > secondaryMessage.ActualWidth) ? primaryMessage.ActualWidth : secondaryMessage.ActualWidth;
        noResultsCanvas.Height = primaryMessage.ActualHeight + secondaryMessage.ActualHeight;
        noResultsCanvas['Canvas.Left'] = this._rootElement.Width / 2 - noResultsCanvas.Width / 2;
        noResultsCanvas['Canvas.Top'] = this._rootElement.Height / 2 - noResultsCanvas.Height / 2;
        primaryMessage['Canvas.Left'] = noResultsCanvas.Width / 2 - primaryMessage.ActualWidth / 2;
//        secondaryMessage['Canvas.Left'] = noResultsCanvas.Width / 2 - secondaryMessage.ActualWidth / 2;
//        secondaryMessage['Canvas.Top'] = primaryMessage.ActualHeight;
//        
        storyboard.begin();
    },

    onNoResultsClick: function( sender, mouseEventArgs ) {
        if( this._noResultsUrl && this._noResultsUrl != null && this._noResultsUrl != 'null' )
            window.location = window.location.protocol + '//' + window.location.host + '/' + this._noResultsUrl;
    },
    
    /*
        goToElement
        redirects the browser to the player page for the selected video
    */
    goToElement: function( url ) {
        if( url && url != null && url != "null" ) {
            window.location = url;
        }
    },
    
    /*
        onError
        Silverlight plugin generic error handler
        (Using sample error handler from Silverlight SDK)
    */
	onError: function(sender, errorEventArgs) {
        // The error message to display.
        var errorMsg = "Silverlight Error: \n\n"; 
        // Determine the type of error and add specific error information.
        switch(errorEventArgs.errorType) {
            case "ImageError":
            case "DownloadError":
                // Error information common to all errors.
                //errorMsg += "Error Type(" + errorEventArgs.errorType + ") - ";
                errorMsg += "Message(" + errorEventArgs.errorMessage + ") - ";
                //errorMsg += "Code(" + errorEventArgs.errorCode + ")";
                window.status = errorMsg ;
                return ; // ignore this error
            case "RuntimeError":
                // Error information common to all errors.
                errorMsg += "Error Type:    " + errorEventArgs.errorType + "\n";
                errorMsg += "Error Message: " + errorEventArgs.errorMessage + "\n";
                errorMsg += "Error Code:    " + errorEventArgs.errorCode + "\n";
              
                // Display properties specific to RuntimeErrorEventArgs.
                if (errorEventArgs.lineNumber != 0) {
                    errorMsg += "Line: " + errorEventArgs.lineNumber + "\n";
                    errorMsg += "Position: " +  errorEventArgs.charPosition + "\n";
                }
                errorMsg += "MethodName: " + errorEventArgs.methodName + "\n";
                window.status = errorMsg ;
                return ; // ignore this error
                //break;
            case "ParserError":
                // Error information common to all errors.
                errorMsg += "Error Type:    " + errorEventArgs.errorType + "\n";
                errorMsg += "Error Message: " + errorEventArgs.errorMessage + "\n";
                errorMsg += "Error Code:    " + errorEventArgs.errorCode + "\n";
                // Display properties specific to ParserErrorEventArgs.
                errorMsg += "Xaml File:      " + errorEventArgs.xamlFile      + "\n";
                errorMsg += "Xml Element:    " + errorEventArgs.xmlElement    + "\n";
                errorMsg += "Xml Attribute:  " + errorEventArgs.xmlAttribute  + "\n";
                errorMsg += "Line:           " + errorEventArgs.lineNumber    + "\n";
                errorMsg += "Position:       " + errorEventArgs.charPosition  + "\n";
                break;
            default:
                // Error information common to all errors.
                errorMsg += "Error Type:    " + errorEventArgs.errorType + "\n";
                errorMsg += "Error Message: " + errorEventArgs.errorMessage + "\n";
                errorMsg += "Error Code:    " + errorEventArgs.errorCode + "\n";
                window.status = errorMsg ;
                return ; // ignore this error
                //break;
        }

        // Display the error message.
        alert(errorMsg);
    }
};


/*
GridCtrl.Grid

Represents a grid of videos.
At any one time, one grid is actively displayed (the other is used for paging in a new set of results).
*/

GridCtrl.Grid = function(scene, isActive) {
    this._scene = scene;
    this._layout = scene._layout;
    this.createGridFromTemplate(isActive);
};

GridCtrl.Grid.prototype = {
    /* 
        Grid members
    */
    _scene: null, // Scene parent canvas
    _layout: null, // Layout parameters
    _canvas: null, // Grid canvas object
    _gridCanvasTemplate: null, // Grid xaml template (downloaded in xaml resources)
    _gridItems: null, // grid metadata
    
    /* 
        createGridFromTemplate
        Creates a new grid from the XAML template.
        Called at construction
    */
    createGridFromTemplate: function(isActive) {
	    var gridCanvasXaml = this._scene._xamlResources.getResponseText('GridCanvas.xaml');

	    this._canvas = this._scene._control.content.createFromXaml(gridCanvasXaml, true);

	    this._canvas.Width = this._layout.gridWidth; 
	    this._canvas.Height = this._layout.gridHeight;
        this._canvas['Canvas.Left'] = this._layout.sceneBufferLeft;
	    this._canvas['Canvas.Top'] = this._layout.sceneBufferTop;

        // initial position is determined by active state
        translateTransform = this._canvas.findName('GridCanvasTranslateTransform');
        if (isActive) {
            translateTransform.X = 0;
	    } else {
            translateTransform.X = this._layout.sceneWidth;
        }
        this.wireupGridEvents();

        this._scene._rootElement.children.add(this._canvas);
        
        this._gridItems = [this._layout.gridMaxItems];
        var len = this._layout.gridMaxItems;
        for (i = 0; i < len; i++) {
            // grid items are initially hidden if this is the active grid.
            // if not, grid items will be visible (no "onload" animation)
            this._gridItems[i] = new GridCtrl.GridItem(this, i, !isActive);
        }
    },

    /* 
        wireupGridEvents
        Adds event listeners for grid.  Called at initialization
    */
    wireupGridEvents: function() {
        var storyboard = this._canvas.findName('GridCanvasStoryboardInFromLeft');
        storyboard.addEventListener('Completed', Silverlight.createDelegate(this, this.onPagingComplete));
        storyboard = this._canvas.findName('GridCanvasStoryboardInFromRight');
        storyboard.addEventListener('Completed', Silverlight.createDelegate(this, this.onPagingComplete));
    },
    
    /* 
        setFonts
        Sets font resources for the grid
    */
    setFonts: function(fontDownloader) {
        var len = this._layout.gridMaxItems;
        for (i = 0; i < len; i++) {
            this._gridItems[i].setFonts(fontDownloader);
        }
    },

    /* 
        initializeGridItems
        Initializes the grid with new video data.
        Called at initialization or on paging requests.
    */
    initializeGridItems: function(elementList, showInitialAnimation) {
        this._numItems = elementList.length;
        var len = this._layout.gridMaxItems;
        for (var i = 0; i < len; i++) {
            if (i < this._numItems) {
                this._gridItems[i].initializeGridItem(elementList[i], true);
            } else {
                this._gridItems[i].disableGridItem();
            }
        }
    },
    
    /* 
        pageOutToLeft, pageOutToRight, pageInFromLeft, pageInFromRight, pageHelper, and onPagingComplete
        Paging methods. Triggers paging animations depending on whether the grid is 
        being paged in or out of view, and in which direction.
    */

    pageOutToLeft: function() {
        this.pageHelper(this._canvas.findName('GridCanvasStoryboardOutToLeft'),0);
    },

    pageOutToRight: function() {
        this.pageHelper(this._canvas.findName('GridCanvasStoryboardOutToRight'),0);
    },
    
    pageInFromLeft: function() {
        this.pageHelper(this._canvas.findName('GridCanvasStoryboardInFromLeft'),this._layout.sceneWidth);
    },
    
    pageInFromRight: function() {
        this.pageHelper(this._canvas.findName('GridCanvasStoryboardInFromRight'),this._layout.sceneWidth*-1);
    },
    
    pageHelper: function(storyboard, startingX) {
        translateTransform = this._canvas.findName('GridCanvasTranslateTransform');
        translateTransform.X = startingX;
        storyboard.begin();
    },
    
    onPagingComplete: function(sender, eventArgs) {
        this._scene.enableScene();
    }
};


/*
GridCtrl.GridItem

Represents a single video in the video grid, as well as its hover state.
*/

GridCtrl.GridItem = function(grid, itemIndex, isVisible) {
    this._grid = grid;
    this._layout = grid._layout;
    this._itemIndex = itemIndex;
//    this.createItemFromTemplate(isVisible);
};

GridCtrl.GridItem.prototype =  {
    /* 
        GridItem members
    */
    _grid: null, // GridItem parent Grid object
    _layout: null, // Layout parameters
    _itemIndex: null, // Specifies the position of the item in the grid
    _elementInfo: null, // Element metadata 
    _showInitialAnimation: false, // Boolean which indicates if the gridItem should show 
                                  // it's "load" animation, which only occurs when the page
                                  // is first loaded (not on paging)
    _gridItemTemplate: null, // xaml template for grid item (downloaded)
    _gridItemHoverTemplate: null, // xaml template for grid item hover (downloaded)
    _canvas: null, // grid item canvas object
    _hoverCanvas: null, // grid item hover canvas object
    
    /* 
        createItemFromTemplate
        Creates a GridItem from xaml and adds it to the parent canvas.
        Called only at item creation by constructor.
    */
    createItemFromTemplate: function(isVisible) {
        var gridItemXaml = this._grid._scene._xamlResources.getResponseText('GridItem.xaml');
        
        var itemHoverXaml = null ;
        if( this._elementInfo != null && this._elementInfo.Type == 1 ) /* video */
            itemHoverXaml = this._grid._scene._xamlResources.getResponseText('GridItemHoverVideo.xaml');
        else if( this._elementInfo != null && this._elementInfo.Type == 2 ) /* pic */
            itemHoverXaml = this._grid._scene._xamlResources.getResponseText('GridItemHover.xaml');
        else if( this._elementInfo != null && this._elementInfo.Type == 4 ) /* External video */
            itemHoverXaml = this._grid._scene._xamlResources.getResponseText('GridItemHoverVideoExternal.xaml');
        else 
            itemHoverXaml = this._grid._scene._xamlResources.getResponseText('GridItemHoverBlog.xaml');

        gridItemXaml = gridItemXaml.replace("Name=\"XXX","Name=\"gi_" + this._itemIndex.toString());
        gridItemXaml = gridItemXaml.replace("Storyboard.TargetName=\"XXX", "Storyboard.TargetName=\"gi_" + this._itemIndex.toString());
        gridItemXaml = gridItemXaml.replace("Storyboard.TargetName=\"XXX", "Storyboard.TargetName=\"gi_" + this._itemIndex.toString());
        gridItemXaml = gridItemXaml.replace("Storyboard.TargetName=\"XXX", "Storyboard.TargetName=\"gi_" + this._itemIndex.toString());

        // remove the old item...
        if( this._canvas != null )
            this._grid._canvas.children.remove(this._canvas) ;
        
        // create Silverlight object
        this._canvas = this._grid._scene._control.content.createFromXaml(gridItemXaml, true); 
        this._hoverCanvas = this._grid._scene._control.content.createFromXaml(itemHoverXaml, false); 

        // now add it
        this._canvas.children.add(this._hoverCanvas);
        this._grid._canvas.children.add(this._canvas);
        
        // place item on Grid
        this._canvas['Canvas.Left'] = (this._canvas.Width + this._layout.itemPaddingX)*(this._itemIndex % this._layout.gridNumColumns);
        this._canvas['Canvas.Top'] = (this._canvas.Height+this._layout.itemPaddingY)*Math.floor(this._itemIndex/this._layout.gridNumColumns);

        this._hoverCanvas['Canvas.Left'] = -15;
        this._hoverCanvas['Canvas.Top'] = -5;

        this._canvas['Canvas.ZIndex'] = 100;
        
        // if this is the last column, the hover state should pop from the upper right.
        if (this._itemIndex % this._layout.gridNumColumns == this._layout.gridNumColumns - 1) {
            var scaleTransform = this._hoverCanvas.findName('ItemHoverScaleTransform');
            this._hoverCanvas['Canvas.Left'] = this._canvas.Width - this._hoverCanvas.Width;
            scaleTransform.CenterX = this._canvas.Width;
        }

        //this._canvas.findName('ItemHoverBackground').Source = BASEURL +  'GridCtrl/Images/video-popup-squat.png';
        this._canvas.findName('ItemHoverBackground').Source = 'GridCtrl/Images/video-popup-squat.png';
        this.wireupItemEvents();
    },
    
    /* 
        wireupItemEvents
        Adds event listeners for the grid item.  
        Called at item construction
    */
    wireupItemEvents: function() {
        var thumbnail = this._canvas.findName("ItemThumbnail");
        thumbnail.addEventListener("downloadProgressChanged", Silverlight.createDelegate(this, this.onImageProgressChanged));
        var mask = this._canvas.findName("ItemMask");
        mask.addEventListener("mouseEnter", Silverlight.createDelegate(this, this.onMouseEnterThumbnail));
        this._canvas.addEventListener("mouseLeave", Silverlight.createDelegate(this, this.onMouseLeaveItemCanvas));

        this._hoverCanvas.addEventListener("mouseLeftButtonUp", Silverlight.createDelegate(this, this.onClick));
        if( mask != null ) mask.addEventListener("mouseLeftButtonUp", Silverlight.createDelegate(this, this.onClick));
        if( thumbnail != null ) thumbnail.addEventListener("mouseLeftButtonUp", Silverlight.createDelegate(this, this.onClick));
        try { thumbnail = this._canvas.findName("ItemHoverThumbnailThree"); 
            if( thumbnail != null ) thumbnail.addEventListener("mouseLeftButtonUp", Silverlight.createDelegate(this, this.onClick));
        } catch( u ) {}
        if( this._elementInfo != null && this._elementInfo.Type == 1 ) {  
            var preview = this._canvas.findName("ItemHoverPreview");
            preview.addEventListener("currentStateChanged", Silverlight.createDelegate(this, this.onPreviewStateChanged));
            preview.addEventListener("bufferingProgressChanged", Silverlight.createDelegate(this, this.onPreviewBufferingProgressChanged));
        }
        this._canvas.findName("ItemHoverShowStoryboard").addEventListener("completed", Silverlight.createDelegate(this, this.onShowHoverComplete));
        this._canvas.findName("ItemHoverHideStoryboard").addEventListener("completed", Silverlight.createDelegate(this, this.onHideHoverComplete));
    },
    
    /* 
        setFonts
        Sets font resources for the GridItem
    */
    setFonts: function(fontDownloader) {
        var title = this._canvas.findName("ItemTitle");
        var desc = this._canvas.findName("ItemDesc");
        var rating = this._canvas.findName("ItemRating");
        var tags = this._canvas.findName("ItemTags");
        var hoverTitle = this._canvas.findName("ItemHoverTitle");
        var hoverDetail = this._canvas.findName("ItemHoverInfoDetail");
        var previewProgress = this._canvas.findName("ItemHoverLoadingProgress");
        var hoverTags = this._canvas.findName("ItemHoverTags");
        title.setFontSource(fontDownloader);
        desc.setFontSource(fontDownloader);
        rating.setFontSource(fontDownloader);
        tags.setFontSource(fontDownloader);
        hoverTitle.setFontSource(fontDownloader);
        hoverDetail.setFontSource(fontDownloader);
        previewProgress.setFontSource(fontDownloader);

        hoverTags.setFontSource(fontDownloader);
        title.FontFamily = "Segoe UI";
        desc.FontFamily = "Segoe UI";
        rating.FontFamily = "Segoe UI";
        tags.FontFamily = "Segoe UI";
        hoverTitle.FontFamily = "Segoe UI";
        hoverDetail.FontFamily = "Segoe UI";
        previewProgress.FontFamily = "Segoe UI";
        hoverTags.FontFamily = "Segoe UI";
    },
    
    /* 
        initializeGridItem
        Initializes a grid item with new video metadata
    */
    initializeGridItem: function(elementInfo, showInitialAnimation) {
        this._elementInfo = elementInfo;
        this._showInitialAnimation = showInitialAnimation;

        this.createItemFromTemplate(elementInfo != null);
        
        this.hideItem();

        //this._canvas.findName("Item").Visibility = "Visible";
        if( elementInfo != null )
        {
            this._canvas.findName("ItemTitle").Text = ( elementInfo.Title.length > 20 ? elementInfo.Title.substring(0,19) + "..." : elementInfo.Title ) ;
            this._canvas.findName("ItemHoverTitle").Text = ( elementInfo.Title.length > 28 ? elementInfo.Title.substring(0,27) + "..." : elementInfo.Title ) ;
            this._canvas.findName("ItemDesc").Text = ( elementInfo.Description.length > 142 ? elementInfo.Description.substring(0,141) + "..." : elementInfo.Description ) ;
            this._canvas.findName("ItemRating").Text = ( elementInfo.Rating > 0 ? elementInfo.Rating : "-") + " Stars" ;
            this._canvas.findName("ItemRatingClip").Rect = "0.0,0.0," + ( elementInfo.Rating * 12 ) + ",12.0" ;
            this._canvas.findName("ItemHoverRatingClip").Rect = "0.0,0.0," + ( elementInfo.Rating * 25 ) + ",25.0" ;
            
            this._canvas.findName("ItemThumbnail").Source = elementInfo.ThumbnailUrl;
            try {
                var th = this._canvas.findName("ItemHoverThumbnailTwo") ;
                if( th != null ) th.Source = elementInfo.ThumbnailUrl2;
                th = this._canvas.findName("ItemHoverThumbnailThree");
                if( th != null ) th.Source = elementInfo.ThumbnailUrl3;
            } catch( f ) {}

            if( elementInfo.Type == 2 /* pic */)
                this._canvas.findName("ItemHoverThumbnail").Source = elementInfo.PreviewUrl;
            else
                this._canvas.findName("ItemHoverThumbnail").Source = elementInfo.ThumbnailUrl;
            
            //this._canvas.findName("ItemStars").Source = this._canvas.findName("ItemHoverStars").Source = BASEURL + 'GridCtrl/Images/stars_yellow.png';
            this._canvas.findName("ItemStars").Source = this._canvas.findName("ItemHoverStars").Source = 'GridCtrl/Images/stars_yellow.png';
            this._canvas.findName("ItemHoverInfoAvatar").Source = elementInfo.Owner.Avatar.Url;
            this._canvas.findName("ItemHoverInfoOwner").Text = ( elementInfo.Owner.UserName.length > 26 ? elementInfo.Owner.UserName.substring(0,25) + "..." : elementInfo.Owner.UserName ) ;
            this._canvas.findName("ItemHoverInfoProperties").Text = elementInfo.Views + (elementInfo.Views != 1 ? " views | " : " view | ") + elementInfo.Reviews + (elementInfo.Reviews != 1 ? " reviews" : " review");
            
            try {   var HoverDesc = this._canvas.findName("ItemHoverDesc");
                    if( HoverDesc != null ) HoverDesc.Text = ( elementInfo.Description.length > 256 ? elementInfo.Description.substring(0,255) + "..." : elementInfo.Description ) ;
                } catch( u ) {}
                   
            
            var tagString = "Tags: ";
            var tagCount = elementInfo.Tags.length > 3 ? 3 : elementInfo.Tags.length;
            for (i=0; i<tagCount-1; i++) {
                tagString += elementInfo.Tags[i].Text + ', ';
            }
            tagString += tagCount > 0 ? elementInfo.Tags[i].Text : ' (none)';
            
            tagString = ( tagString.length > 26 ? tagString.substring(0,25) + "..." : tagString ) ;
               
            this._canvas.findName("ItemTags").Text = tagString;
            this._canvas.findName("ItemHoverTags").Text = tagString;
            this._canvas.Visibility = "Visible";
        }
        else
            this._canvas.Visibility = "Collapsed";
    },
    
    /* 
        disableGridItem
        When a grid page doesn't have enough videos to fill the grid,
        grid items are disabled.  They appear blank on the grid.
    */
    disableGridItem: function() {
        this._elementInfo = null;
        var item = ( this._canvas == null ) ? null : this._canvas ; //.findName("Item") ;
        if( item != null )
            item.Visibility = "Collapsed";
    },
    
    /* 
        showItem
        Makes the gridItem visible after loading its image thumbnail.
        Triggers either the initial-load storyboard or the paging storyboard
    */
    showItem: function() {
        if (this._elementInfo != null) {
            var storyboard;
            /*if (this._showInitialAnimation) {*/
                storyboard = this._canvas.findName('LoadStoryboard');
                storyboard.beginTime = '0:0:' + (Math.floor(Math.random()*20))/20;
                storyboard.begin();
            /*} else {
                storyboard = this._canvas.findName('PageStoryboard');
                storyboard.begin();
            }*/
        }
    },
    
    /* 
        hideItem
        Called at beginning of paging to make sure the item isn't seen until the new
        video info is loaded
    */
    hideItem: function() {
        var item = this._canvas ; //.findName("Item") ;
        if( item != null ) item.Opacity = 0;
    },
    
    /* 
        onImageProgressChanged
        Event handler for thumbnail image download. Shows the item only after
        the image is downloaded (prevents html-like "popping" of resources after load).
    */
    onImageProgressChanged: function(sender, eventArgs) {
        if (sender.DownloadProgress == 1) {
            this.showItem();
        }
    },
    
    /* 
        onMouseEnterThumbnail
        Triggers the item's hover state
    */
    onMouseEnterThumbnail: function(sender, mouseEventArgs) { 
        this._STOP = false;
        this._hoverCanvas.Visibility = "Visible";
        this._canvas['Canvas.ZIndex'] = 200;
        this._canvas.findName("ItemHoverShowStoryboard").begin();
        
        if( this._grid._scene._elementsOverFunc != null )
        {
            try { (this._grid._scene._elementsOverFunc)( this._elementInfo ) ; } catch(u) { window.status = u.toString(); }
        }

    },

    /* 
        onMouseLeaveItemCanvas
        Hides the item's hover state. This event is tied to the overall item canvas, 
        so that the event fires only if the mouse leaves either the griditem or the 
        griditem's hover area (which is a child)
    */
    onMouseLeaveItemCanvas: function(sender) { 
        this._STOP = true;
        this._canvas['Canvas.ZIndex'] = 1;
        var preview = this._canvas.findName("ItemHoverPreview");
        if (this._elementInfo.Type == 1 ) {
            if (preview.downloadProgress != null && preview.downloadProgress < 1) {
                preview.Source = '';
            } else {
                preview.stop();
            }
        }
        else 
            this._canvas.findName("ItemHoverPreview").stop();
        
        this._canvas.findName("ItemHoverHideStoryboard").begin();

        if( this._grid._scene._elementsOutFunc != null )
        {
            try { (this._grid._scene._elementsOutFunc)( this._elementInfo ) ; } catch(u) { window.status = u.toString(); }
        }
        
    },
    
    /* 
        onShowHoverComplete
        Called when the hover state show animation completes.  Triggers 
        loading/playing of the video preview.
    */
    onShowHoverComplete: function( sender, eventArgs ) {
        if (!this._STOP) {
            var preview = this._canvas.findName("ItemHoverPreview");
            if (this._elementInfo.Type == 1 ) {
                if (preview.Source == '') {
                    preview.Source = (this._elementInfo == null) ? '' : this._elementInfo.PreviewUrl ;
                } else {
                   try { preview.play(); }catch(u){}
                }
            }
            else
                this._canvas.findName("ItemHoverPreview").begin();
        }
    },
    
    /* 
        onHideHoverComplete
        Called after the hover state is hidden. 
        Collapses the hover area, ensuring it can't be clicked.
    */
    onHideHoverComplete: function( sender, eventArgs ) {
        this._hoverCanvas.Visibility = "Collapsed";
    },

    /* 
        onPreviewStateChanged
        Hides/shows the buffering progress indicator for the video preview
    */
    onPreviewStateChanged: function(sender, eventArgs) {
        if (sender.CurrentState == 'Buffering') {
            this._canvas.findName("ItemHoverLoadingPreview").Visibility = "Visible";
        } else {
            this._canvas.findName("ItemHoverLoadingPreview").Visibility = "Collapsed";
        }
    },
    
    /* 
        onPreviewBufferingProgressChanged
        Sets the buffering progress status for the video preview
    */
    onPreviewBufferingProgressChanged: function(sender, eventArgs) {
        try { this._canvas.findName("ItemHoverLoadingProgress").Text = "buffering: " + Math.round(sender.bufferingProgress*100).toString() + "%"; }catch(u){}
    },
    
    /* 
        onClick
        Navigates to the player page for the item's video.
    */
    onClick: function( sender, mouseEventArgs ) {
        if( this._elementInfo && this._elementInfo != null && this._elementInfo.DetailsURL != null)
            this._grid._scene.goToElement(this._elementInfo.DetailsURL);
    }
};






/*
GridCtrl.Layout

Stores layout parameters for the GridCtrl
*/



GridCtrl.Layout = function(configuration) {
    // constants
    this.sceneBufferLeft = 14;
    this.sceneBufferRight = 14;
    this.sceneBufferTop = 10;
    this.sceneBufferBottom = 50;

    // configuration-specific settings
    this.configuration = configuration;
    switch (configuration) {
        case 'Playlist':
            this.sceneWidth = 715;
            this.sceneHeight = 203;
            this.itemWidth = 162.4;
            this.itemHeight = 137;
            this.gridNumRows = 1;
            this.gridNumColumns = 4;
            this.gridWidth = 715;
            this.gridHeight = 150;
            this.itemPaddingX = 4;
            this.itemPaddingY = 4;
            break;
        case 'Home':
            this.sceneWidth = 515;
            this.sceneHeight = 203;
            this.itemWidth = 162.4;
            this.itemHeight = 137;
            this.gridNumRows = 1;
            this.gridNumColumns = 3;
            this.gridWidth = 515;
            this.gridHeight = 150;
            this.itemPaddingX = 4;
            this.itemPaddingY = 4;
            break;
        case 'Geo':
            this.sceneWidth = 715;
            this.sceneHeight = 380;
            this.itemWidth = 162.4;
            this.itemHeight = 137;
            this.gridNumRows = 2;
            this.gridNumColumns = 4;
            this.gridWidth = 700;
            this.gridHeight = 330;
            this.itemPaddingX = 12;
            this.itemPaddingY = 12;
            break;
        case 'Member':
            this.sceneWidth = 350;
            this.sceneHeight = 480;
            this.itemWidth = 162.4;
            this.itemHeight = 137;
            this.gridNumRows = 3;
            this.gridNumColumns = 2;
            this.gridWidth = 350;
            this.gridHeight = 430;
            this.itemPaddingX = 12;
            this.itemPaddingY = 12;
            break;
            
        default:
            this.sceneWidth = 715;
            this.sceneHeight = 654;
            this.itemWidth = 162.4;
            this.itemHeight = 137;
            this.gridNumRows = 4;
            this.gridNumColumns = 4;
            this.gridWidth = 700;
            this.gridHeight = 604;
            this.itemPaddingX = 12;
            this.itemPaddingY = 12;
    }

    this.gridMaxItems = this.gridNumRows * this.gridNumColumns;
    
};

GridCtrl.Layout.prototype = 
{
};






/*
GridCtrl.Strings

Stores static strings for the GridCtrl
*/



GridCtrl.Strings = function()
{
};

GridCtrl.Strings.prototype = {
    noResults_tagPrimary: "Nothing has the tag \"$tag\"",
    noResults_tagSecondary: "Check out popular tags.",
    noResults_tagSecondaryUrl: "Search.aspx",
    
    noResults_ownerPrimary: "User has not reviewed or added any stuff.",  
    noResults_ownerSecondary: "Check out other stuff.",
    noResults_ownerSecondaryUrl: "Search.aspx",

    noResults_favoritesPrimary: "User has not reviewed or added any stuff.",
    noResults_favoritesSecondary: "Check out other stuff.",
    noResults_favoritesSecondaryUrl: "Search.aspx",

    noResults_myOwnerPrimary: "You have no stuff shared.",  
    noResults_myOwnerSecondary: "Share something.",
    noResults_myOwnerSecondaryUrl: "Search.aspx",

    noResults_myFavoritesPrimary: "You have not reviewed anything.",
    noResults_myFavoritesSecondary: "Check out other stuff.",
    noResults_myFavoritesSecondaryUrl: "Search.aspx",

    noResults_recentViewsPrimary: "You have not viewed any stuff.",  
    noResults_recentViewsSecondary: "Watch some stuff.",
    noResults_recentViewsSecondaryUrl: "Search.aspx?type=Videos",

    noResults_allPrimary: "Nothing has been shared.",
    noResults_allSecondary: "Share something.",
    noResults_allSecondaryUrl: "Search.aspx"
};
