1 2Steam = { 3 sm_bInitialized: false, 4 sm_bUserInClient: false, 5 sm_bUserInGameOverlay: false, 6 sm_bUserInTenfootBrowser: false, 7 sm_bUserInGamepadUI: false, 8 sm_bUserInMobileChat: false, 9 sm_bUserInMobileApp: false, 10 11 BIsUserInSteamClient: function() 12 { 13 if ( !Steam.sm_bInitialized ) 14 Steam.Init(); 15 16 return Steam.sm_bUserInClient; 17 }, 18 19 BIsUserInGamepadUI: function() 20 { 21 if ( !Steam.sm_bInitialized ) 22 Steam.Init(); 23 24 return Steam.sm_bUserInGamepadUI; 25 }, 26 27 BIsUserInGameOverlay: function() 28 { 29 if ( !Steam.sm_bInitialized ) 30 Steam.Init(); 31 32 return Steam.sm_bUserInGameOverlay 33 }, 34 35 BIsUserInSteamTenfootBrowser: function() 36 { 37 if ( !Steam.sm_bInitialized ) 38 Steam.Init(); 39 40 return Steam.sm_bUserInTenfootBrowser; 41 }, 42 43 BIsUserInClientOrOverlay: function() 44 { 45 if ( !Steam.sm_bInitialized ) 46 Steam.Init(); 47 48 return Steam.sm_bUserInClient || Steam.sm_bUserInGameOverlay; 49 }, 50 51 BIsUserInSteamMobileChat: function() 52 { 53 if ( !Steam.sm_bInitialized ) 54 Steam.Init(); 55 56 return Steam.sm_bUserInMobileChat; 57 }, 58 59 BIsUserInSteamMobileApp: function() 60 { 61 if ( !Steam.sm_bInitialized ) 62 Steam.Init(); 63 64 return Steam.sm_bUserInMobileApp; 65 }, 66 67 GetClientPackageVersion: function() 68 { 69 if ( !Steam.BIsUserInClientOrOverlay() ) 70 return 0; 71 72 if ( typeof navigator != 'undefined' && navigator.userAgent ) 73 { 74 var matches = navigator.userAgent.match( /Valve (?<family>Steam (?:Client|GameOverlay|Tenfoot|ClientUI|Gamepad(?: VR)?(?:\/Steam Deck)?))( \[(?<betaid>[^\]]*)\])?(\/(?<launcher>[A-Za-z0-9_]+))?\/(?<version>[0-9]*)/ ); 75 if ( matches && matches.groups && matches.groups.version ) 76 return matches.groups.version; 77 } 78 79 return 0; 80 }, 81 82 Init: function() 83 { 84 var fnCheckAgent = function( strUAMatch, strURLParam ) 85 { 86 if ( window.location.href.match( '[?&]' + strURLParam + '=' ) ) 87 return true; 88 89 if ( typeof navigator != 'undefined' && navigator.userAgent && navigator.userAgent.indexOf( strUAMatch ) != -1 ) 90 return true; 91 92 return false; 93 }; 94 95 Steam.sm_bUserInGamepadUI = fnCheckAgent( 'Valve Steam Gamepad', 'force_tenfoot_client_view' ); 96 Steam.sm_bUserInTenfootBrowser = fnCheckAgent( 'Valve Steam Tenfoot', 'force_tenfoot_client_view' ); 97 Steam.sm_bUserInGameOverlay = fnCheckAgent( 'Valve Steam GameOverlay', 'force_overlay_view' ); 98 Steam.sm_bUserInClient = Steam.sm_bUserInTenfootBrowser || Steam.sm_bUserInGamepadUI || fnCheckAgent( 'Valve Steam Client', 'force_client_view' ); 99 Steam.sm_bUserInMobileChat = fnCheckAgent( 'Valve Steam Mobile Chat', 'force_mobile_chat_view' ); 100 Steam.sm_bUserInMobileApp = fnCheckAgent( 'Valve Steam App', 'force_mobile_app_view' ); 101 102 Steam.sm_bInitialized = true; 103 }, 104 105 LinkInNewWindow: function( $A ) 106 { 107 if ( Steam.BIsUserInSteamClient() && !Steam.BIsUserInSteamTenfootBrowser() ) 108 $A.attr( 'href', 'steam://openurl_external/' + $A.attr('href') ); 109 else 110 $A.attr( 'target', '_blank' ); 111 } 112}; 113 114function OpenFriendChat( steamid, accountid ) 115{ 116 if ( Steam.BIsUserInClientOrOverlay() ) 117 { 118 window.location = 'steam://friends/message/' + steamid; 119 } 120 else if ( Steam.BIsUserInSteamMobileChat() ) 121 { 122 window.location = 'steamchatmobile://friend/' + steamid; 123 } 124 else if ( Steam.BIsUserInSteamMobileApp() ) 125 { 126 window.location = 'https://steamcommunity.com/chat/friend/' + steamid; 127 } 128 else if ( typeof ClientConnectionAPI !== 'undefined' ) 129 { 130 ClientConnectionAPI.OpenFriendChatDialog( steamid ).then( function( result ) { 131 if ( !result.success ) 132 { 133 PromptContinueToWebChat( result, function() { 134 OpenFriendChatInWebChat( steamid, accountid ); 135 }, 'steam://friends/message/' + steamid ); 136 } 137 }); 138 } 139 else 140 { 141 OpenFriendChatInWebChat( steamid, accountid ); 142 } 143} 144 145function OpenFriendChatInWebChat( steamid, accountid ) 146{ 147 LaunchWebChat( { friend: accountid }, {command: 'ShowFriendChatDialog', steamid: steamid} ); 148} 149 150function OpenGroupChat( steamid ) 151{ 152 if ( Steam.BIsUserInSteamMobileChat() ) 153 { 154 window.location = 'steamchatmobile://group/' + steamid; 155 } 156 else if ( Steam.BIsUserInSteamMobileApp() ) 157 { 158 window.location = 'https://steamcommunity.com/chat/group/' + steamid; 159 } 160 else if ( !Steam.BIsUserInClientOrOverlay() && typeof ClientConnectionAPI !== 'undefined' ) 161 { 162 ClientConnectionAPI.OpenFriendChatDialog( steamid ).then( function( result ) { 163 if ( !result.success ) 164 { 165 PromptContinueToWebChat( result, function() { 166 LaunchWebChat( null, {command: 'ShowFriendChatDialog', steamid: steamid} ); 167 }
167, 'steam://friends/joinchat/' + steamid ); 168 } 169 }); 170 } 171 else 172 { 173 window.location = 'steam://friends/joinchat/' + steamid; 174 } 175} 176 177function PromptContinueToWebChat( result, fnLaunchWebchat, steamURL ) 178{ 179 ShowConfirmDialog( 'à¸à¸²à¸§à¸à¹à¹à¸«à¸¥à¸ Steam หรืà¸à¸¢à¸±à¸?', 'à¹à¸£à¸²à¹à¸¡à¹à¸à¸à¸§à¹à¸² Steam à¸à¸³à¸¥à¸±à¸à¸à¸³à¸à¸²à¸à¸à¸¢à¸¹à¹à¸à¸à¹à¸à¸£à¸·à¹à¸à¸à¸à¸à¸à¸à¸¸à¸ à¸à¸¸à¸à¸à¹à¸à¸à¸à¸²à¸£à¸à¸°à¹à¸£à¸´à¹à¸¡à¹à¸à¸´à¸à¹à¸§à¹à¸à¹à¸à¹à¸à¸«à¸£à¸·à¸à¹à¸¡à¹?', 180 'à¹à¸à¹à¹à¸§à¹à¸à¹à¸à¹à¸' , null, 'à¸à¸²à¸§à¸à¹à¹à¸«à¸¥à¸ Steam' ).done( function( choice ) { 181 if ( choice == 'OK' ) 182 fnLaunchWebchat(); 183 else 184 window.location = 'https://store.steampowered.com/about/' 185 }); 186} 187 188 189// proto functions used to accept an id or an element. 190// This can be used to migrate them to returning jquery instead of proto-wrapped element 191function $JFromIDOrElement( elem ) 192{ 193 if ( elem instanceof jQuery ) 194 return elem; 195 else if ( typeof elem == 'string' ) 196 return $J('#' + elem.replace( /\./, '\\.' ) ); 197 else 198 return $J( elem ); 199} 200 201/** Show a popup dialog like confirm(), with two buttons. Clicking ok resolves with done(), cancel or closing the window resolves with fail() 202 * 203 * @param strTitle Title bar text 204 * @param strDescription Message text 205 * @param strOKButton Text to show on OK button (default "OK") 206 * @param strCancelButton Text to show on Cancel button (default "Cancel") 207 * @param strSecondaryActionButton Add a secondary ok button (three buttons total). Resolves with done() like OK but passes 'SECONDARY' as argument to handler 208 * @returns CModal 209 */ 210function ShowConfirmDialog( strTitle, strDescription, strOKButton, strCancelButton, strSecondaryActionButton, rgModalParams ) 211{ 212 if ( !strOKButton ) 213 strOKButton = 'à¸à¸à¸¥à¸'; 214 if ( !strCancelButton ) 215 strCancelButton = 'ยà¸à¹à¸¥à¸´à¸'; 216 217 var deferred = new jQuery.Deferred(); 218 var fnOK = function() { deferred.resolve( 'OK' ); }; 219 var fnSecondary = function() { deferred.resolve( 'SECONDARY' ); }; 220 var fnCancel = function( bWasCancelButton ) { deferred.reject( bWasCancelButton ); }; 221 222 var rgButtons = []; 223 224 var $OKButton = _BuildDialogButton( strOKButton, true ); 225 $OKButton.click( fnOK ); 226 rgButtons.push( $OKButton ); 227 228 if ( strSecondaryActionButton ) 229 { 230 var $SecondaryActionButton = _BuildDialogButton( strSecondaryActionButton, false, {strClassName: 'btn_blue_steamui btn_medium' } ); 231 $SecondaryActionButton.click( fnSecondary ); 232 rgButtons.push( $SecondaryActionButton ); 233 } 234 235 var $CancelButton = _BuildDialogButton( strCancelButton ); 236 $CancelButton.click( function() { fnCancel( true ); } ); 237 rgButtons.push( $CancelButton ); 238 239 var Modal = _BuildDialog( strTitle, strDescription, rgButtons, fnCancel, rgModalParams ); 240 Modal.Show(); 241 242 _BindOnEnterKeyPressForDialog( Modal, deferred, fnOK ); 243 244 deferred.always( function() { Modal.Dismiss(); } ); 245 // attach the deferred's events to the modal 246 deferred.promise( Modal ); 247 248 return Modal; 249} 250 251/** Show a dialog with a single button, like alert(). Button click or closing the modal resolve deferred with done(). 252 * 253 * @param strTitle Title bar text 254 * @param strDescription Message text 255 * @param strOKButton Text on the OK button ("OK" by default) 256 * @returns CModal 257 */ 258function ShowAlertDialog( strTitle, strDescription, strOKButton, rgModalParams ) 259{ 260 if ( !strOKButton ) 261 strOKButton = 'à¸à¸à¸¥à¸'; 262 263 var deferred = new jQuery.Deferred(); 264 var fnOK = function( bWasCancelButton ) { deferred.resolve( bWasCancelButton ); }; 265 266 var $OKButton = _BuildDialogButton( strOKButton ); 267 $OKButton.click( function() { fnOK( true ); } ); 268 269 var Modal = _BuildDialog( strTitle, strDescription, [ $OKButton ], fnOK, rgModalParams ); 270 deferred.always( function() { Modal.Dismiss(); } ); 271 Modal.Show(); 272 273 _BindOnEnterKeyPressForDialog( Modal, deferred, fnOK ); 274 275 // attach the deferred's events to the modal 276 deferred.promise( Modal ); 277 278 return Modal; 279 280} 281 282/** Show a popup dialog. Has no buttons. Closing the dialog resolves deferred with done(). 283 * 284 * @param strTitle Title bar text 285 * @param strDescription Message text 286 * @param rgModalParams See CModal 287 * @returns CModal 288 */ 289function ShowDialog( strTitle, strDescription, rgModalParams ) 290{ 291 var deferred = new jQuery.Deferred(); 292 var fnOK = function() { deferred.resolve(); }; 293 294 var Modal = _BuildDialog( strTitle, strDescription, [], fnOK, rgModalParams ); 295 deferred.always( function() { Modal.Dismiss(); } ); 296 Modal.Show(); 297 298 // attach the deferred's events to the modal 299 deferred.promise( Modal ); 300 301 return Modal; 302} 303 304/** 305 * Show a dialog, but take the arguments as an object a la React. 306 * @returns CModal 307 */ 308function ShowPromptDialogWithProps( rgParams ) 309{ 310 let { strOKButton, strCancelButton, strTitle, strDescription, defaultValue, inputType, inputMaxSize, bNoPromiseDismiss, bInputReadOnly } = rgParams; 311 if ( !strOKButton ) 312 strOKButton = 'à¸à¸à¸¥à¸'; 313 if ( !strCancelButton ) 314 strCancelButton = 'ยà¸à¹à¸¥à¸´à¸'; 315 if ( !inputType ) 316 inputType = 'text'; 317 318 var $Body = $J('<form/>'); 319 var $Input = $J('<input/>', {type: inputType, 'class': '' } ).val( defaultValue );
320 if ( inputMaxSize ) 321 { 322 $Input.attr( 'maxlength', inputMaxSize ); 323 } 324 if ( bInputReadOnly ) 325 { 326 $Input.attr( 'readonly', true ); 327 } 328 $Body.append( $J('<div/>', {'class': 'newmodal_prompt_description' } ).append( strDescription ) ); 329 $Body.append( $J('<div/>', {'class': 'newmodal_prompt_input gray_bevel for_text_input fullwidth' } ).append( $Input ) ); 330 331 var deferred = new jQuery.Deferred(); 332 var fnOK = function() { deferred.resolve( $Input.val() ); }; 333 var fnCancel = function() { deferred.reject(); }; 334 335 $Body.submit( function( event ) { event.preventDefault(); fnOK(); } ); 336 337 var elButtonLabel = $J( '<span/>' ).text( strOKButton ); 338 var $OKButton = $J('<button/>', {type: 'submit', 'class': 'btn_green_white_innerfade btn_medium' } ).append( elButtonLabel ); 339 $OKButton.click( fnOK ); 340 var $CancelButton = _BuildDialogButton( strCancelButton ); 341 $CancelButton.click( fnCancel ); 342 343 var Modal = _BuildDialog( strTitle, $Body, [ $OKButton, $CancelButton ], fnCancel, rgParams ); 344 if( !bNoPromiseDismiss ) 345 deferred.always( function() { Modal.Dismiss(); } ); 346 347 Modal.Show(); 348 349 $Input.focus(); 350 351 // attach the deferred's events to the modal 352 deferred.promise( Modal ); 353 354 return Modal; 355} 356 357/** 358 * A wrapper around ShowDialogPrompt to be compatible with existing code. Prefer ShowPromptDialogWithProps instead. 359 * @returns CModal 360 */ 361function ShowPromptDialog( strTitle, strDescription, strOKButton, strCancelButton, rgModalParams, defaultValue ) 362{ 363 let rgParams = {}; 364 if ( rgModalParams ) 365 Object.assign( rgParams, rgModalParams ); 366 rgParams.strTitle = strTitle; 367 rgParams.strDescription = strDescription; 368 rgParams.strOKButton = strOKButton; 369 rgParams.strCancelButton = strCancelButton; 370 rgParams.defaultValue = defaultValue; 371 return ShowPromptDialogWithProps( rgParams ); 372} 373 374/** 375 * @returns CModal 376 */ 377function ShowPromptWithTextAreaDialog( strTitle, strInitialText, strOKButton, strCancelButton, textAreaMaxLength, strDescription ) 378{ 379 if ( !strOKButton ) 380 strOKButton = 'à¸à¸à¸¥à¸'; 381 if ( !strCancelButton ) 382 strCancelButton = 'ยà¸à¹à¸¥à¸´à¸'; 383 384 var $Body = $J('<form/>'); 385 var $TextArea = $J('<textarea/>', { 'class': 'newmodal_prompt_textarea' } ); 386 $TextArea.text( strInitialText ); 387 if ( textAreaMaxLength ) 388 { 389 $TextArea.attr( 'maxlength', textAreaMaxLength ); 390 $TextArea.bind( "keyup change", 391 function() 392 { 393 var str = $J(this).val(); 394 var mx = parseInt($J(this).attr('maxlength')); 395 if (str.length > mx) 396 { 397 $J(this).val(str.substr(0, mx)); 398 return false; 399 } 400 } 401 ); 402 } 403 404 if ( strDescription ) 405 { 406 $Body.append( $J('<div/>', {'class': 'newmodal_prompt_with_textarea newmodal_prompt_description' } ).text( strDescription ) ); 407 } 408 409 $Body.append( $J('<div/>', {'class': 'newmodal_prompt_with_textarea gray_bevel fullwidth' } ).append( $TextArea ) ); 410 411 var deferred = new jQuery.Deferred(); 412 var fnOK = function() { deferred.resolve( $TextArea.val() ); }; 413 var fnCancel = function() { deferred.reject(); }; 414 415 $Body.submit( function( event ) { event.preventDefault(); fnOK(); } ); 416 417 var elButtonLabel = $J( '<span/>' ).text( strOKButton ); 418 var $OKButton = $J('<button/>', {type: 'submit', 'class': 'btn_green_white_innerfade btn_medium' } ).append( elButtonLabel ); 419 $OKButton.click( fnOK ); 420 var $CancelButton = _BuildDialogButton( strCancelButton ); 421 $CancelButton.click( fnCancel ); 422 423 var Modal = _BuildDialog( strTitle, $Body, [ $OKButton, $CancelButton ], fnCancel ); 424 deferred.always( function() { Modal.Dismiss(); } ); 425 Modal.Show(); 426 427 $TextArea.focus(); 428 429 // attach the deferred's events to the modal 430 deferred.promise( Modal ); 431 432 return Modal; 433} 434 435/** 436 * @returns CModal 437 */ 438function ShowEditablePrompt( strTitle, obj, onOk, onCancel) 439{ 440 strEditButton = 'à¹à¸à¹à¹à¸'; 441 strOKButton = 'à¸à¸à¸¥à¸'; 442 strCancelButton = 'ยà¸à¹à¸¥à¸´à¸'; 443 444 var $Body = $J('<form/>'); 445 var $TextArea = $J('<textarea/>', {'class': 'newmodal_prompt_textarea', 'id': 'json_window'}); 446 $TextArea[0].readOnly = true; 447 $TextArea.text( JSON.stringify(obj, null, 2) ); 448 $Body.append( $J('<div/>', {'class': 'newmodal_prompt_with_textarea gray_bevel fullwidth ' } ).append( $TextArea ) ); 449 450 $Body.submit( function( event ) { event.preventDefault(); fnOK(); } ); 451 452 var elButtonLabel = $J( '<span/>' ).text( strOKButton ); 453 var deferredAction = new jQuery.Deferred(); 454 455 var editButtonLabel = $J( '<span/>' ).text( strEditButton ); 456 var $EditButton = $J('<button/>', {type: 'button', 'class': 'btn_darkred_white_innerfade btn_medium' } ).append( editButtonLabel ); 457 $EditButton.click( function() { 458 $EditButton[0].disable(); 459 $OKButton[0].enable(); 460 $TextArea[0].readOnly = false;
461 $TextArea.focus(); 462 }) 463 464 var $OKButton = $J('<button/>', {type: 'submit', 'class': 'btn_green_white_innerfade btn_medium' } ).append( elButtonLabel ); 465 $OKButton.click( function() { deferredAction.resolve( $TextArea.val() ); } ); 466 $OKButton[0].disabled = true; 467 468 fnCancel = function() { deferredAction.reject(); }; 469 var $CancelButton = _BuildDialogButton( strCancelButton ); 470 $CancelButton.click( fnCancel ); 471 472 var Modal = _BuildDialog( strTitle, $Body, [ $EditButton, $OKButton, $CancelButton ], fnCancel ); 473 deferredAction.always( function() { Modal.Dismiss(); } ); 474 475 if (onOk) 476 deferredAction.done(onOk); 477 if (onCancel) 478 deferredAction.fail(onCancel); 479 480 Modal.Show(); 481 482 $TextArea.focus(); 483 484 // attach the deferred's events to the modal 485 deferredAction.promise( Modal ); 486 487 return Modal; 488} 489 490/** 491 * @returns CModal 492 */ 493function ShowBlockingWaitDialog( strTitle, strDescription ) 494{ 495 var deferred = new jQuery.Deferred(); 496 var fnOK = function() { deferred.resolve(); }; 497 498 var container = $J('<div/>', {'class': 'waiting_dialog_container'} ); 499 var throbber = $J('<div/>', {'class': 'waiting_dialog_throbber'} ); 500 container.append( throbber ); 501 container.append( strDescription ); 502 if ( !strDescription ) 503 { 504 container.addClass( 'waiting_dialog_centered' ); 505 } 506 507 var Modal = _BuildDialog( strTitle, container, [], fnOK, { bExplicitDismissalOnly: true } ); 508 deferred.always( function() { Modal.Dismiss(); } ); 509 Modal.Show(); 510 511 // attach the deferred's events to the modal 512 deferred.promise( Modal ); 513 514 return Modal; 515} 516 517function _BindOnEnterKeyPressForDialog( Modal, deferred, fnOnEnter ) 518{ 519 var fnOnKeyUp = function( event ) { 520 if ( Modal.BIsActiveModal() && !event.isDefaultPrevented() && event.which == 13 && ( !event.target || event.target.nodeName != 'TEXTAREA' ) ) 521 fnOnEnter(); 522 }; 523 $J(document).on( 'keyup.SharedConfirmDialog', fnOnKeyUp ); 524 deferred.always( function() { $J(document).off( 'keyup.SharedConfirmDialog' ); } ); 525} 526 527/** 528 * @returns CModal 529 * @private 530 */ 531function _BuildDialog( strTitle, strDescription, rgButtons, fnOnCancel, rgModalParams ) 532{ 533 var $Dialog = $J('<dialog/>', {'class': 'newmodal'} ); 534 var $CloseButton = $J('<div/>', {'class': 'newmodal_close', 'data-panel': '{"focusable":true,"clickOnActivate":true}' } ); 535 var $Header = ( $J('<div/>', {'class': 'newmodal_header' }) ); 536 var $TopBar = ( $J('<div/>', {'class': 'modal_top_bar' }) ); 537 if ( strTitle ) 538 $Header.append( $CloseButton ).append( $J('<div/>', {'class': 'title_text' } ).text( strTitle ) ); 539 if ( rgModalParams && rgModalParams.strSubTitle ) { 540 var $SubTitle = (rgModalParams.strSubTitle); 541 $Header.append( $J('<div/>', {'class': 'subtitle_text' } ).text( $SubTitle ) ) 542 } 543 $Header = $J('<div/>', {'class': 'newmodal_header_border'}).append( $Header ); 544 $Dialog.append( $TopBar ).append( $Header ); 545 var $Content = $J('<div/>', {'class': 'newmodal_content' } ); 546 $Content.append( $J('<div/>').append( strDescription ) ); 547 548 if ( rgButtons.length > 0 ) 549 { 550 var $Buttons = $J('<div/>', {'class': 'newmodal_buttons', 'data-panel': '{"flow-children":"row"}' } ); 551 $Content.append( $Buttons ); 552 for( var i = 0; i < rgButtons.length; i++ ) 553 { 554 var $Button = rgButtons[i]; 555 if ( i == 0 ) 556 $Button.attr( 'data-panel', '{"autoFocus":true,"focusable":true,"clickOnActivate":true}' ); 557 else 558 $Button.attr( 'data-panel', '{"focusable":true,"clickOnActivate":true}' ); 559 $Buttons.append( $Button ); 560 } 561 } 562 563 $Dialog.append( $J('<div/>', {'class': 'newmodal_content_border' } ).append( $Content ) ); 564 565 if ( rgModalParams && rgModalParams.bExplicitDismissalOnly ) 566 $CloseButton.hide(); 567 568 var Modal = new CModal( $Dialog, rgModalParams ); 569 if ( fnOnCancel ) 570 { 571 Modal.OnDismiss( fnOnCancel ); 572 $CloseButton.click( function() { Modal.Dismiss(); } ); 573 } 574 575 // on responsive pages, the 'newmodal' element covers the whole viewable area (so that we can control scrolling 576 // if the modal is very tall). If the modal doesn't cover the whole area, we dismiss on clicks to this background 577 // area 578 if ( Modal.m_fnBackgroundClick ) 579 { 580 $Dialog.click( function(e) { if ( e.target == this ) Modal.m_fnBackgroun
580dClick(); } ); 581 } 582 583 Modal.SetRemoveContentOnDismissal( true ); 584 585 return Modal; 586} 587 588function _BuildDialogButton( strText, bActive, rgOptions ) 589{ 590 if ( !rgOptions ) 591 rgOptions = {}; 592 593 var strClassName = bActive ? 'btn_green_steamui btn_medium' : 'btn_grey_steamui btn_medium'; 594 if ( rgOptions.strClassName ) 595 strClassName = rgOptions.strClassName; 596 597 var elButtonLabel = $J( '<span/>' ).html( strText ); 598 var elButton = $J('<div/>', {'class': strClassName } ).append( elButtonLabel ); 599 return elButton; 600} 601 602/** Implemented for Gamepad: show content in a fullscreen, borderless, modal which gets navigation support. 603 * Used on the app details page to show screenshots in full screen. 604 * Closing the dialog resolves deferred with done(). 605 * 606 * @returns CModal 607 */ 608function GPShowFullScreenModal( content ) 609{ 610 var deferred = new jQuery.Deferred(); 611 var fnOK = function() { deferred.resolve(); }; 612 613 var Modal = _BuilGPFullScreenModal( content, fnOK ); 614 deferred.always( function() { Modal.Dismiss(); } ); 615 Modal.Show(); 616 617 // attach the deferred's events to the modal 618 deferred.promise( Modal ); 619 620 return Modal; 621} 622 623function _BuilGPFullScreenModal( content, fnOnCancel ) 624{ 625 var $Dialog = $J('<dialog/>').append( $J('<div/>', {'style': 'display:flex'} ).append( content ) ); 626 var Modal = new CModal( $Dialog ); 627 628 Modal.SetRemoveContentOnDismissal( true ); 629 if ( fnOnCancel ) 630 { 631 Modal.OnDismiss( fnOnCancel ); 632 } 633 634 return Modal; 635} 636 637/* modal params: 638 bExplicitDismissalOnly - by default, clicking outside of the modal dismisses it. Set this to true to override that behavior 639 bIgnoreResizeEvents - don't resize the modal when the window resizes 640 */ 641 642function CModal( $Content, rgParams ) 643{ 644 rgParams = rgParams || {}; 645 646 this.m_$Content = $Content; 647 this.m_bVisible = false; 648 649 this.m_bIgnoreResizeEvents = rgParams.bIgnoreResizeEvents; 650 this.m_fnSizing = null; 651 this.m_fnBackgroundClick = null; 652 this.m_fnOnResize = null; 653 this.m_bDismissOnBackgroundClick = !rgParams.bExplicitDismissalOnly; 654 this.m_nMaxWidth = rgParams.nMaxWidth || 0; 655 this.m_nMaxHeight = rgParams.nMaxHeight || 0; 656 657 this.m_fnOnDismiss = null; 658 this.m_fnGPOnCloseModal = null; 659 this.m_bRemoveContentOnDismissal = false;
660 661 this.m_nInitialOffsetTop = $J(window).scrollTop(); 662 this.m_nInitialOffsetLeft = $J(window).scrollLeft(); 663 this.m_$Content.css( 'position', 'fixed' ); 664 this.m_$Content.css( 'z-index', 1000 ); 665 this.m_$Content.css( 'outline', 'none' ); 666 667 /* default gamepad behavior is B button closes the dialog */ 668 if ( !this.m_$Content.attr('panel' ) ) 669 this.m_$Content.attr( 'data-panel', '{"onCancelButton":"CModal.DismissActiveModal()"}' ); 670 671 this.m_$StandardContent = null; 672 this.m_$SizedContent = null; 673 this.OnContentChanged(); //this will look for StandardContent and SizedContent in the modal body 674 675 676 var _modal = this; 677 this.m_fnBackgroundClick = function() { if ( _modal.BIsActiveModal() && _modal.m_bDismissOnBackgroundClick ) { _modal.Dismiss(); } }; 678 this.m_fnOnEscapeKeyPress = function( event ) { if ( _modal.BIsActiveModal() && event.which == 27 ) _modal.m_fnBackgroundClick(); }; 679 this.m_fnSizing = function() { _modal.AdjustSizing(); }; 680 681 /* make sure the content is parented correctly */ 682 $J(document.body).append( this.m_$Content ); 683} 684 685CModal.prototype.OnDismiss = function( fn ) 686{ 687 this.m_fnOnDismiss = fn; 688}; 689 690CModal.prototype.OnResize = function( fn ) 691{ 692 this.m_fnOnResize = fn; 693}; 694 695/** 696 * @returns jQuery 697 */ 698CModal.prototype.GetContent = function () 699{ 700 return this.m_$Content; 701}; 702 703CModal.prototype.GetBoundOnResizeEvent = function() 704{ 705 // in case someone outside needs to tell the modal to resize on certain events (eg images or iframes loading in the modal) 706 return this.m_fnSizing; 707}; 708 709CModal.prototype.OnContentChanged = function() 710{ 711 // make sure we're holding the right elements 712 this.m_$StandardContent = this.m_$Content.find( '.newmodal_content' ); 713 if ( !this.m_$StandardContent.length ) 714 this.m_$StandardContent = this.m_$Content; 715 this.m_$SizedContent = this.m_$Content.find( '.newmodal_sized_content' ); 716}; 717 718CModal.prototype.SetRemoveContentOnDismissal = function ( bRemoveContent ) 719{ 720 this.m_bRemoveContentOnDismissal = bRemoveContent; 721}; 722 723CModal.prototype.SetDismissOnBackgroundClick = function ( bDismissOnBackgroundClick ) 724{ 725 this.m_bDismissOnBackgroundClick = bDismissOnBackgroundClick; 726}; 727 728CModal.prototype.SetMaxWidth = function ( nMaxWidth ) 729{ 730 this.m_nMaxWidth = nMaxWidth; 731 if ( this.m_bVisible ) 732 this.AdjustSizing(); 733}; 734 735CModal.prototype.SetMaxHeight = function ( nMaxHeight ) 736{ 737 this.m_nMaxHeight = nMaxHeight; 738 if ( this.m_bVisible ) 739 this.AdjustSizing(); 740}; 741 742CModal.prototype.AdjustSizing = function( duration ) 743{ 744 if ( !this.m_$Content ) 745 return; 746 747 var bResponsiveScreen = window.UseTouchFriendlyMode && UseTouchFriendlyMode(); 748 var bUseGamepadScreenMode = window.UseGamepadScreenMode && window.UseGamepadScreenMode(); 749 750 var nViewportWidth = document.compatMode === 'BackCompat' ? document.body.clientWidth : $J(window).width(); 751 var nViewportHeight = document.compatMode === 'BackCompat' ? document.body.clientHeight : $J(window).height(); 752 753 var nMaxWidth = Math.max( nViewportWidth - ( bResponsiveScreen? 24 : 80 ), bResponsiveScreen ? 200 : 500 ); 754 var nMaxHeight = Math.floor( nViewportHeight - 120 ); 755 756 if ( this.m_nMaxWidth && nMaxWidth > this.m_nMaxWidth ) 757 { 758 nMaxWidth = this.m_nMaxWidth; 759 } 760 761 if ( this.m_nMaxHeight && nMaxHeight > this.m_nMaxHeight ) 762 { 763 nMaxHeight = this.m_nMaxHeight; 764 } 765 766 // if the modal has a 'newmodal_sized_content' div, it wants to be the max height, so set it now 767 // before we compute height ( "- 18" is a fudge for a possible horizontal scrollbar ) 768 this.m_$SizedContent.css( 'min-height', ( nMaxHeight - 18 ) + 'px' ); 769 if ( this.m_fnOnResize ) 770 { 771 this.m_fnOnResize( nMaxWidth - 40, nMaxHeight ); 772 } 773 774 if ( !duration ) 775 { 776 // set sizes right away so we can calculate a good left and top 777 this.m_$Content.css( 'max-width', nMaxWidth + 'px' ); 778 if ( !bResponsiveScreen ) 779 { 780 this.m_$StandardContent.css( 'max-height', nMaxHeight + 'px' ); 781 } 782 else 783 { 784 this.m_$StandardContent.css( 'max-height', '' ); 785 } 786 } 787 788 var nContentWidth = this.m_$Content.width(); 789 var nContentHeight = this.m_$Content.height(); 790 var nLeft = Math.max( Math.floor( ( nViewportWidth - nContentWidth ) / 2 ), 12 );
791 var nTop = Math.max( Math.floor( ( nViewportHeight - nContentHeight ) / 2 ), 12 ); 792 793 // only use absolute position on mobile screens 794 if ( bResponsiveScreen && !bUseGamepadScreenMode ) 795 { 796 nLeft += this.m_nInitialOffsetLeft; 797 nTop += this.m_nInitialOffsetTop; 798 this.m_$Content.css( 'position', 'absolute' ); 799 } 800 else 801 { 802 this.m_$Content.css( 'position', 'fixed' ); 803 } 804 805 if ( duration ) 806 { 807 this.m_$Content.animate( { 'max-width': nMaxWidth, left: nLeft, top: nTop }, duration ); 808 this.m_$StandardContent.animate( {'max-height': nMaxHeight }, duration ); 809 } 810 else 811 { 812 this.m_$Content.css( 'left', nLeft ); 813 this.m_$Content.css( 'top', nTop ); 814 } 815}; 816 817CModal.prototype.Show = function() 818{ 819 if ( this.m_bVisible ) 820 return; 821 822 CModal.ShowModalBackground(); 823 824 if ( !this.m_bIgnoreResizeEvents ) 825 { 826 $J(window).on( 'resize', null, this.m_fnSizing ); 827 } 828 CModal.s_$Background.on( 'click.CModal', this.m_fnBackgroundClick ); 829 $J(document).on( 'keyup.CModal', this.m_fnOnEscapeKeyPress ); 830 831 this.AdjustSizing(); 832 833 // if we're in gamepad, notify gamepad navigation of the modal 834 if ( typeof GPOnShowingModalWindow === "function" ) 835 this.m_fnGPOnCloseModal = GPOnShowingModalWindow( this.m_$Content.get( 0 ) ); 836 837 this.m_$Content[0].showModal(); 838 839 // resize as any child image elements load in. 840 this.m_$Content.find('img').load( this.m_fnSizing ); 841 842 this.m_bVisible = true; 843 CModal.PushActiveModal( this ); 844 845 var _this = this; 846 if ( typeof GPNavFocusChild !== 'undefined' ) 847 window.setTimeout( function() { GPNavFocusChild( _this.m_$Content ) }, 1 ); }; 848 849CModal.prototype.Dismiss = function() 850{ 851 if ( !this.m_bVisible ) 852 return; 853 854 this.m_bVisible = false; 855 856 // tell gamepad navigation we're closing this modal 857 if ( this.m_fnGPOnCloseModal ) 858 { 859 this.m_fnGPOnCloseModal(); 860 this.m_fnGPOnCloseModal = null; 861 } 862 863 this.m_$Content?.[0].close(); 864 865 if ( !this.m_bIgnoreResizeEvents ) 866 { 867 $J(window).off( 'resize', null, this.m_fnSizing ); 868 } 869 870 if ( this.m_fnOnDismiss ) 871 this.m_fnOnDismiss(); 872 873 if ( this.m_bRemoveContentOnDismissal ) 874 { 875 this.m_$Content.remove(); 876 this.m_$Content = null; 877 } 878 879 CModal.PopActiveModal( this ); 880 if ( !CModal.s_rgModalStack.length ) 881 { 882 CModal.s_$Background.off( 'click.CModal', this.m_fnBackgroundClick ); 883 $J(document).off( 'keyup.CModal', this.m_fnOnEscapeKeyPress ); 884 CModal.HideModalBackground(); 885 } 886}; 887 888CModal.prototype.BIsActiveModal = function() 889{ 890 return CModal.s_rgModalStack.length && CModal.s_rgModalStack[ CModal.s_rgModalStack.length - 1 ] == this; 891}; 892 893/* static */ 894CModal.ShowModalBackground = function() 895{ 896 if ( !CModal.s_$Background ) 897 { 898 CModal.s_$Background = $J('<div/>', {'class': 'newmodal_background'}); 899 CModal.s_$Background.css( 'opacity', 0 ); 900 $J(document.body).append( CModal.s_$Background ); 901 } 902 CModal.s_$Background.stop(); // stop running animations 903 CModal.s_$Background.fadeTo( 200, 0.8 ); 904}; 905 906CModal.HideModalBackground = function() 907{ 908 if ( CModal.s_$Background ) 909 { 910 CModal.s_$Background.stop(); // stop running animations 911 CModal.s_$Background.fadeOut( 200, 0 ); 912 } 913}; 914 915CModal.s_rgModalStack = []; 916CModal.GetActiveModal = function() 917{ 918 if ( CModal.s_rgModalStack.length ) 919 return CModal.s_rgModalStack[CModal.s_rgModalStack.length-1]; 920 else 921 return null; 922}; 923CModal.DismissActiveModal = function() 924{ 925 if ( CModal.s_rgModalStack.length ) 926 CModal.s_rgModalStack[CModal.s_rgModalStack.length-1].Dismiss(); 927}; 928 929CModal.PushActiveModal = function( Modal ) 930{ 931 for ( var i = 0; i < CModal.s_rgModalStack.length; i++ ) 932 { 933 // push below background 934 CModal.s_rgModalStack[i].m_$Content.css( 'z-index', 899 ); 935 } 936 CModal.s_rgModalStack.push( Modal ); 937}; 938 939CModal.PopActiveModal = function( Modal ) 940{ 941 for ( var i = 0; i < CModal.s_rgModalStack.length; i++ ) 942 { 943 if ( CModal.s_rgModalStack[i] == Modal ) 944 { 945 CModal.s_rgModalStack.splice( i, 1 ); 946 break; 947 } 948 } 949 950 if ( CModal.s_rgModalStack.length ) 951 CModal.s_rgModalStack[ CModal.s_rgModalStack.length - 1 ].m_$Content.css( 'z-index', 1000 ); 952}; 953 954// this will set the right headers for a cross-domain request to community 955function GetDefaultCommunityAJAXParams( path, method ) 956{ 957 var rgParams = { url: 'https://steamcommunity.com/' + path }; 958 if ( method ) 959 rgParams.type = method; 960 961 // if this js file was hosted off the store, add CORS request headers 962 if ( window.location.href.indexOf( 'https://steamcommunity.com/' ) != 0 ) 963 { 964 rgParams.crossDomain = true; 965 rgParams.xhrFields = { withCredentials: true }; 966 } 967 return rgParams; 968} 969 970function CrossDomainPost( url, params ) 971{ 972 return $J.ajax( { 973 type: 'post', 974 url: url, 975 data: params, 976 crossDomain: true, 977 xhrFields: { withCredentials: true } 978 } ); 979} 980 981// spped of the miniprofile fading in and out 982var MINIPROFILE_ANIM_SPEED = 150; 983// how long the mouse must remain over an element before we'll make an AJAX call 984var MINIPROFILE_DELAY_BEFORE_AJAX = 100; 985// the delay before we'll show the hover, must be longer than DELAY_BEFORE_AJAX 986var MINIPROFILE_DELAY_BEFORE_SHOW = 250; 987 988function CDelayedAJAXData( strURL, msDelayBeforeAJAX ) 989{ 990 this.m_$Data = null; 991 this.m_bAJAXFailed = false;
992 this.m_timerDelayedAJAX = null; 993 this.m_bAJAXRequestMade = false; 994 this.m_msDelayBeforeAJAX = msDelayBeforeAJAX; 995 this.m_strURL = strURL; 996 997 this.m_fnOnAJAXComplete = null; 998} 999 1000CDelayedAJAXData.prototype.GetAJAXParams = function() 1001{ 1002 return GetDefaultCommunityAJAXParams( this.m_strURL, 'GET' ); 1003}; 1004 1005CDelayedAJAXData.prototype.QueueAjaxRequestIfNecessary = function() 1006{ 1007 if ( !this.m_$Data && !this.m_bAJAXRequestMade ) 1008 { 1009 var _this = this; 1010 this.m_timerDelayedAJAX = window.setTimeout( function() { 1011 _this.m_timerDelayedAJAX = null; 1012 _this.m_bAJAXRequestMade = true; 1013 var rgAJAXParams = _this.GetAJAXParams(); 1014 $J.ajax( rgAJAXParams ) 1015 .done( function(data) { 1016 _this.m_$Data = $J(data); 1017 if ( _this.m_fnOnAJAXComplete ) 1018 _this.m_fnOnAJAXComplete(); 1019 }).fail( function() { 1020 _this.m_bAJAXFailed = true; 1021 }); 1022 }, this.m_msDelayBeforeAJAX ); 1023 } 1024}; 1025 1026CDelayedAJAXData.prototype.CancelAJAX = function() 1027{ 1028 if ( this.m_timerDelayedAJAX ) 1029 window.clearTimeout( this.m_timerDelayedAJAX ); 1030 1031 this.m_fnOnAJAXComplete = null; 1032}; 1033 1034CDelayedAJAXData.prototype.RunWhenAJAXReady = function( fnOnReady ) 1035{ 1036 if ( this.m_$Data ) 1037 fnOnReady(); 1038 else if ( !this.m_bAJAXFailed ) 1039 { 1040 this.m_fnOnAJAXComplete = fnOnReady; 1041 this.QueueAjaxRequestIfNecessary(); 1042 } 1043 // if ajax failed we will not call fnOnReady 1044}; 1045 1046CDelayedAJAXData.prototype.Show = function( $HoverContent ) 1047{ 1048 $HoverContent.children().detach(); 1049 $HoverContent.append( this.m_$Data ); 1050}; 1051 1052// time the cookie preferences popup waits before appearing 1053var COOKIE_PREFERENCES_POPUP_DELAY = 4000; 1054 1055function InitCookiePreferencesPopup() 1056{ 1057 1058 var $popupDialog = $J( '#cookiePrefPopup' ); 1059 if ( $popupDialog.length == 0 ) 1060 { 1061 $AllowURL = encodeURI( 'https://store.steampowered.com/' + 'account/ajaxsetcookiepreferences'); 1062 1063 var $CPopupContent = $J( "<style>\n.replyButton {\n\twidth: 100%;\n text-align: center;\n margin-bottom: 4px;\n}\n\n.buttonGroup {\n\tflex: 18%;\n\tmargin-left: 20px;\n\tmargin-right: 0px;\n}\n.cookieMessage {\n\tflex: 85%;\n\tmargin: auto;\n}\n.popupTextTitle {\n\tpadding-bottom: 10px;\n font-size: 13px;\n line-height: 17px;\n color: #c6d4df;\n}\n.popupTextTitle a {\n text-decoration: underline;\n}\n.cookiepreferences_popup {\n\tdisplay: none;\n\tposition: fixed;\n\tbottom: 0;\n\twidth: 100%;\n\tz-index: 2000;\n\tpadding-top: 10px;\n}\n.cookiepreferences_popup_content {\n\tdisplay: flex;\n box-sizing: border-box;\n\twidth: 90%;\n\tmargin: auto;\n\tmargin-bottom: 20px;\n\tpadding: 16px 16px 10px 16px;\n max-width: 980px;\n\tbackground: linear-gradient(90.85deg, #333840 0.58%, #25282E 74.92%);\n\tbox-shadow: 0px 0px 10px #000000;\n border-left: 2px solid #00CCFF;\n}\n@media all and (max-width: 910px) {\n body.responsive_page .cookiepreferences_popup_content {\n flex-direction: column;\n }\n body.responsive_page .cookiepreferences_popup_content .cookieMessage {\n margin-bottom: 20px;\n }\n body.responsive_page .cookiepreferences_popup_content .buttonGroup {\n flex-direction: row;\n margin-left: 0px;\n }\n body.responsive_page .cookiepreferences_popup_content .buttonGroup .replyButton {\n width: unset;\n }\n}\n<\/style>\n\n<div id=\"cookiePrefPopup\" class=\"cookiepreferences_popup\">\n\t<div class=\"cookiepreferences_popup_content\">\n\t\t<div class=\"cookieMessage\">\n\t\t\t<div class=\"popupTextTitle\">\u0e2b\u0e32\u0e01\u0e40\u0e23\u0e32\u0e43\u0e0a\u0e49\u0e04\u0e38\u0e01\u0e01\u0e35\u0e49\u0e17\u0e32\u0e07\u0e40\u0e25\u0e37\u0e2d\u0e01\u0e40\u0e1e\u0e37\u0e48\u0e2d\u0e08\u0e31\u0e14\u0e40\u0e2a\u0e19\u0e2d\u0e40\u0e19\u0e37\u0e49\u0e2d\u0e2b\u0e32\u0e2a\u0e48\u0e27\u0e19\u0e1a\u0e38\u0e04\u0e04\u0e25\u0e43\u0e2b\u0e49\u0e04\u0e38\u0e13 \u0e41\u0e25\u0e30\u0e40\u0e1e\u0e37\u0e48\u0e2d\u0e27\u0e34\u0e40\u0e04\u0e23\u0e32\u0e30\u0e2b\u0e4c\u0e01\u0e32\u0e23\u0e08\u0e23\u0e32\u0e08\u0e23\u0e02\u0e49\u0e2d\u0e21\u0e39\u0e25\u0e40\u0e27\u0e47\u0e1a\u0e44\u0e0b\u0e15\u0e4c \u0e04\u0e38\u0e13\u0e08\u0e30\u0e2d\u0e19\u0e38\u0e0d\u0e32\u0e15\u0e2b\u0e23\u0e37\u0e2d\u0e44\u0e21\u0e48?<\/div>\n\t\t\t<div class=\"popupTextTitle\">\u0e40\u0e23\u0e32\u0e43\u0e0a\u0e49\u0e04\u0e38\u0e01\u0e01\u0e35\u0e49\u0e44\u0e21\u0e48\u0e21\u0e32\u0e01\u0e19\u0e31\u0e01 \u0e04\u0e38\u0e13\u0e2a\u0e32\u0e21\u0e32\u0e23\u0e16\u0e14\u0e39\u0e41\u0e25\u0e30\u0e08\u0e31\u0e14\u0e01\u0e32\u0e23\u0e04\u0e38\u0e01\u0e01\u0e35\u0e49\u0e44\u0e14\u0e49\u0e15\u0e25\u0e2d\u0e14\u0e40\u0e27\u0e25\u0e32\u0e17\u0e35\u0e48 <a href=\"https:\/\/store.steampowered.com\/account\/cookiepreferences\">\u0e2b\u0e19\u0e49\u0e32\u0e01\u0e32\u0e23\u0e15\u0e31\u0e49\u0e07\u0e04\u0e48\u0e32\u0e04\u0e38\u0e01\u0e01\u0e35\u0e49<\/a> \u0e02\u0e2d\u0e07\u0e40\u0e23\u0e32 \u0e2b\u0e32\u0e01\u0e04\u0e38\u0e13\u0e04\u0e25\u0e34\u0e01 '\u0e22\u0e2d\u0e21\u0e23\u0e3
10631\u0e1a\u0e17\u0e31\u0e49\u0e07\u0e2b\u0e21\u0e14' \u0e04\u0e38\u0e13\u0e08\u0e30\u0e22\u0e34\u0e19\u0e22\u0e2d\u0e21\u0e43\u0e2b\u0e49\u0e43\u0e0a\u0e49\u0e04\u0e38\u0e01\u0e01\u0e35\u0e49\u0e1a\u0e19\u0e40\u0e27\u0e47\u0e1a\u0e44\u0e0b\u0e15\u0e4c Steam \u0e40\u0e23\u0e35\u0e22\u0e19\u0e23\u0e39\u0e49\u0e40\u0e1e\u0e34\u0e48\u0e21\u0e40\u0e15\u0e34\u0e21\u0e40\u0e01\u0e35\u0e48\u0e22\u0e27\u0e01\u0e31\u0e1a\u0e04\u0e38\u0e01\u0e01\u0e35\u0e49\u0e44\u0e14\u0e49\u0e43\u0e19 <a href=\"https:\/\/store.steampowered.com\/privacy_agreement\">\u0e19\u0e42\u0e22\u0e1a\u0e32\u0e22\u0e04\u0e27\u0e32\u0e21\u0e40\u0e1b\u0e47\u0e19\u0e2a\u0e48\u0e27\u0e19\u0e15\u0e31\u0e27<\/a> \u0e02\u0e2d\u0e07\u0e40\u0e23\u0e32<\/div>\n\t\t<\/div>\n\t\t<div class=\"buttonGroup\">\n\t\t\t<div id=\"acceptAllButton\" class=\"btn_blue_steamui btn_medium replyButton\"><span>\u0e22\u0e2d\u0e21\u0e23\u0e31\u0e1a\u0e17\u0e31\u0e49\u0e07\u0e2b\u0e21\u0e14<\/span><\/div>\n\t\t\t<div id=\"rejectAllButton\" class=\"btn_grey_steamui btn_medium replyButton\"><span>\u0e1b\u0e0f\u0e34\u0e40\u0e2a\u0e18\u0e17\u0e31\u0e49\u0e07\u0e2b\u0e21\u0e14<\/span><\/div>\n\t\t<\/div>\n\t<\/div>\n<\/div>\n" ); 1064 1065 var $onAcceptButton = $CPopupContent.find( '#acceptAllButton' ); 1066 var $onRejectButton = $CPopupContent.find( '#rejectAllButton' ); 1067 1068 if ( $onAcceptButton.length == 0 || $onRejectButton.length == 0 ) 1069 { 1070 // template appears broken 1071 console.error("unable to display preferences popup"); 1072 return; 1073 } 1074 1075 var fnPostPreference = function ( bAllowChoice ) 1076 { 1077 $J.ajax( { type: "POST", 1078 url: $AllowURL, 1079 data: { bAllow: bAllowChoice, sessionid: g_sessionID }, 1080 crossDomain: true, 1081 xhrFields: { withCredentials: true } 1082 } ).done( function ( data ) 1083 { 1084 if ( data && data.transfer_urls && data.transfer_params ) 1085 { 1086 for ( var i = 0; i < data.transfer_urls.length; i++ ) 1087 { 1088 $J.ajax( { type: "POST", 1089 url: data.transfer_urls[i], 1090 data: { transfer_params: data.transfer_params }, 1091 crossDomain: true, 1092 xhrFields: { withCredentials: true } 1093 } ); 1094 } 1095 } 1096 }); 1097 $J( '#cookiePrefPopup' ).hide(); 1098 1099 } 1100 1101 $onAcceptButton.on('click', '', function() { 1102 fnPostPreference( 1 ); 1103 } ); 1104 1105 $onRejectButton.on('click', '', function () 1106 { 1107 fnPostPreference( 0 ); 1108 } ); 1109 1110 $J('body').append( $CPopupContent ); 1111 1112 timerCookiePopup = window.setTimeout( function() { 1113 $J("#cookiePrefPopup").show(); 1114 }, COOKIE_PREFERENCES_POPUP_DELAY ); 1115 } 1116} 1117 1118// The /miniprofile/ is cacheable. We need to specify the origin URL of the request to avoid CORS see cl 5773210 1119function InitMiniprofileHovers( origin ) 1120{ 1121 var $Hover = $J('<div/>', {'class': 'miniprofile_hover'} ); 1122 var $HoverContent = $J('<div/>', {'class': 'miniprofile_hover_inner shadow_content'} ); 1123 1124 $Hover.append( $J('<div class="shadow_ul"></div><div class="shadow_top"></div><div class="shadow_ur"></div><div class="shadow_left"></div><div class="shadow_right"></div><div class="shadow_bl"></div><div class="shadow_bottom"></div><div class="shadow_br"></div>'), $HoverContent ); 1125 1126 $Hover.hide(); 1127 $J(document.body).append( $Hover ); 1128 1129 var fnDataFactory = function( key ) { 1130 // key is either a number (accountid) or a string of "accountid_appid" 1131 var rgKey = typeof key === "string" ? key.split( /_/ ) : [ key ]; 1132 var strURL = 'miniprofile/' + rgKey[0]; 1133 1134 if ( rgKey[1] ) 1135 { 1136 strURL += '?appid=' + rgKey[1]; 1137 1138 if( origin ) 1139 { 1140 strURL += '&origin=' + origin; 1141 } 1142 } 1143 else if( origin ) 1144 { 1145 strURL += '?origin=' + origin; 1146 } 1147 1148 return new CDelayedAJAXData( strURL, MINIPROFILE_DELAY_BEFORE_AJAX ); 1149 }; 1150 1151 var fnReadKey = function ( $Target ) { 1152 var appid = $Target.parents('[data-miniprofile-appid]').first().data('miniprofile-appid'); 1153 if( appid ) 1154 { 1155 return $Target.data('miniprofile') + '_' + appid; 1156 } 1157 else 1158 { 1159 return $Target.data('miniprofile') 1160 } 1161 }; 1162 1163 var rgCallbacks = BindAJAXHovers( $Hover, $HoverContent, { 1164 fnDataFactory: fnDataFactory, 1165 fnPositionHover: PositionMiniprofileHover, 1166 fnReadKey: fnReadKey, 1167 strDataName: 'miniprofile', 1168 strURLMatch: 'miniprofile' 1169 } ); 1170} 1171 1172function _RegisterAJAXHoverHideFunction( fnHide ) 1173{ 1174 if ( typeof g_rgfnHideAJAXHover == 'undefined' ) 1175 { 1176 g_rgfnHideAJAXHover = []; 1177 $J(window).blur( HideAJAXHovers ); 1178 } 1179 1180 g_rgfnHideAJAXHover.push( fnHide ); 1181} 1182 1183function HideAJAXHovers() 1184{ 1185 if ( typeof g_rgfnHideAJAXHover != 'undefined' ) 1186 { 1187 for ( var i = 0; i < g_rgfnHideAJAXHover.length; i++ ) 1188 g_rgfnHideAJAXHover[i](); 1189 } 1190} 1191 1192function BindAJAXHovers( $Hover, $HoverContent, oParams ) 1193{ 1194 var fnDataFactory = oParams.fnDataFactory; 1195 var fnPositionHover = oParams.fnPositionHover || PositionMiniprofileHover; 1196 var strDataName = oParams.strDataName; 1197 var strURLMatch = oParams.strURLMatch; 1198 var fnReadKey = function( $Element ) { return $Element.data( strDataName ); }; 1199 if ( oParams.fnReadKey ) 1200 fnReadKey = oParams.fnReadKey; 1201 var strSelector = oParams.strSelector || '[data-' + strDataName + ']'; 1202 var nDelayBeforeShow = oParams.nDelayBeforeShow || MINIPROFILE_DELAY_BEFORE_SHOW; 1203 1204 // indexed by accountid 1205 var rgHoverData = {}; 1206 var HoverTarget = null; 1207 var timerHover = null; 1208 1209 var fnOnHover = function( $Target, key ) { 1210 1211 var bHoverVisible = ( $Hover.css('display') != 'none' ); 1212 1213 var HoverData = rgHoverData[key]; 1214 if ( !HoverData ) 1215 { 1216 HoverData = rgHoverData[key] = fnDataFactory( key ); 1217 } 1218 1219 if ( HoverTarget == HoverData && bHoverVisible ) 1220 { 1221 //really only want to do this while fading out 1222 $Hover.stop(); 1223 fnPositionHover( $Hover, $Target ); 1224 $Hover.show(); // PositionMiniprofile toggles visibility 1225 $Hover.fadeTo( MINIPROFILE_ANIM_SPEED, 1.0 ); 1226 } 1227 else if ( !timerHover || HoverData != HoverTarget ) 1228 { 1229 // this is the new target 1230 if ( HoverTarget && HoverTarget != HoverData ) 1231 HoverTarget.CancelAJAX(); 1232 HoverTarget = HoverData; 1233 1234 if ( timerHover ) 1235 { 1236 window.clearTimeout( timerHover ); 1237 timerHover = null; 1238 } 1239 1240 HoverData.QueueAjaxRequestIfNecessary(); 1241 1242 timerHover = window.setTimeout( function() { 1243 window.clearTimeout( timerHover ); 1244 timerHover = null; 1245 1246 HoverData.RunWhenAJAXReady( function() { 1247 HoverData.Show( $HoverContent ); 1248 $Hover.stop(); 1249 $Hover.css( 'opacity', '' ); //clean up jquery animation 1250 fnPositionHover( $Hover, $Target ); 1251 $Hover.fadeIn( MINIPROFILE_ANIM_SPEED ); 1252 var videoElem = $Hover.find( 'video' ); 1253 if ( videoElem.length != 0 ) 1254 {
1255 var playPromise = videoElem[0].play(); 1256 if ( playPromise ) 1257 { 1258 playPromise.catch( function( e ) { 1259 } ); 1260 } 1261 } 1262 } ); 1263 }, nDelayBeforeShow ); 1264 } 1265 }; 1266 1267 var fnCancelHover = function( key ) 1268 { 1269 var bHoverVisible = ( $Hover.css('display') != 'none' ); 1270 1271 if ( key ) 1272 { 1273 var HoverData = rgHoverData[key]; 1274 if ( HoverData ) 1275 { 1276 HoverData.CancelAJAX(); 1277 } 1278 } 1279 1280 if ( timerHover ) 1281 { 1282 window.clearTimeout( timerHover ); 1283 timerHover = null; 1284 } 1285 1286 if ( bHoverVisible ) 1287 { 1288 $Hover.stop(); 1289 $Hover.fadeOut( MINIPROFILE_ANIM_SPEED ); 1290 } 1291 }; 1292 1293 var strEventNamespace = 'AjaxHover' + strDataName; 1294 $J(document ).on( 'mouseenter.' + strEventNamespace + ' touchend.' + strEventNamespace, strSelector, function(e) { 1295 var $Target = $J(this); 1296 fnOnHover( $Target, fnReadKey( $Target) ); 1297 1298 // Prevent click if we're sticky 1299 if ( typeof $Target.data('stickyhover') !== 'undefined' ) 1300 { 1301 e.preventDefault(); 1302 } 1303 } ); 1304 $J(document ).on( 'click.' + strEventNamespace + ' mouseleave.' + strEventNamespace, strSelector, function(e) { 1305 var $Target = $J(this); 1306 1307 // Cancel if we're not sticky 1308 if ( typeof $Target.data('stickyhover') == 'undefined' ) 1309 { 1310 fnCancelHover(); 1311 } 1312 } ); 1313 // Cancel when clicking/touching elsewhere regardless if sticky 1314 $J(document ).on( 'click', null, fnCancelHover ); 1315 1316 // register this hover so HideAJAXHovers() can hide it when invoked 1317 _RegisterAJAXHoverHideFunction( fnCancelHover ); 1318 1319 return { 1320 fnCancelHover: fnCancelHover 1321 }; 1322} 1323 1324function PositionMiniprofileHover( $Hover, $Target, oParams ) 1325{ 1326 if ( !oParams ) 1327 oParams = {}; 1328 var bPreferRightSide = oParams.bPreferRightSide || false; 1329 var nPxArrowOverlap = ( oParams.nPxArrowOverlap != undefined ) ? oParams.nPxArrowOverlap : 2; 1330 1331 $Hover.css( 'visibility', 'hidden' ); 1332 $Hover.show(); 1333 1334 var offset = $Target.offset(); 1335 $Hover.css( 'left', offset.left + 'px' ); 1336 $Hover.css( 'top', offset.top + 'px'); 1337 1338 var $HoverBox = $Hover.children( '.shadow_content' ); 1339 if ( !$HoverBox.length ) 1340 $HoverBox = $J( $Hover.children()[0] ); 1341 1342 var $HoverArrowLeft = $Hover.children( '.hover_arrow.left' ); 1343 var $HoverArrowRight = $Hover.children( '.hover_arrow.right' ); 1344 1345 var nWindowScrollTop = $J(window).scrollTop(); 1346 var nWindowScrollLeft = $J(window).scrollLeft(); 1347 var nViewportWidth = $J(window).width(); 1348 var nViewportHeight = $J(window).height(); 1349 1350 var $HoverArrow = $HoverArrowRight; 1351 var nBoxRightViewport = ( offset.left - nWindowScrollLeft ) + $Target.outerWidth() + $HoverBox.width() + 14; 1352 var nSpaceRight = nViewportWidth - nBoxRightViewport; 1353 var nSpaceLeft = offset.left - $Hover.width(); 1354 if ( ( ( nSpaceLeft > 0 || nSpaceLeft > nSpaceRight ) && !bPreferRightSide ) || ( bPreferRightSide && nSpaceRight < 14 && nSpaceLeft > nSpaceRight ) ) 1355 { 1356 $Hover.css( 'left', ( offset.left - $Hover.width() + nPxArrowOverlap + 3 ) + 'px' ); 1357 $HoverArrowLeft.hide(); 1358 $HoverArrowRight.show(); 1359 } 1360 else 1361 { 1362 $Hover.css( 'left', ( offset.left + $Target.outerWidth() - nPxArrowOverlap ) + 'px' ); 1363 $HoverArrow = $HoverArrowLeft; 1364 $HoverArrowLeft.show(); 1365 $HoverArrowRight.hide(); 1366 } 1367 1368 var nTopAdjustment = 0; 1369 1370 if ( $Target.height() < 48 ) 1371 nTopAdjustment = Math.floor( $Target.height() / 2 ) - 24; 1372 var nDesiredHoverTop = offset.top - 15 + nTopAdjustment; 1373 $Hover.css( 'top', nDesiredHoverTop + 'px' ); 1374 1375 // see if the hover is cut off by the bottom of the window, and bump it up if neccessary 1376 var nTargetTopViewport = ( offset.top - nWindowScrollTop ) + nTopAdjustment; 1377 if ( nTargetTopViewport + $HoverBox.height() + 35 > nViewportHeight ) 1378 { 1379 var nViewportAdjustment = ( $HoverBox.height() + 35 ) - ( nViewportHeight - nTargetTopViewport ); 1380 1381 // if the hover has the "in-game" block at the bottom, we need to have more space at the bottom of the hover 1382 // so that the arrow will appear in the blue part of the hover. This means the game info may be off-screen below. 1383 var bHaveInGameInfo = $HoverBox.find('.miniprofile_ingame').length > 0; 1384 var nHoverBoxBottomMinimum = ( bHaveInGameInfo ? 78 : 24 ); // the minimum amount of space we need below the arrow 1385 nViewportAdjustment = Math.min( $HoverBox.height() - nHoverBoxBottomMinimum, nViewportAdjustment ); 1386 1387 var nViewportAdjustedHoverTop = offset.top - nViewportAdjustment;
1388 $Hover.css( 'top', nViewportAdjustedHoverTop + 'px' ); 1389 1390 // arrow is normally offset 30pixels. we move it down the same distance we moved the hover up, so it is "fixed" to where it was initially 1391 $HoverArrow.css( 'top', ( 30 + nDesiredHoverTop - nViewportAdjustedHoverTop ) + 'px' ); 1392 } 1393 else 1394 { 1395 $HoverArrow.css( 'top', '' ); 1396 } 1397 1398 $Hover.hide(); 1399 $Hover.css( 'visibility', '' ); 1400} 1401 1402 1403/* Emoticon hovers */ 1404 1405function CEmoticonDelayedAJAXData( strEmoticonName, msDelayBeforeAJAX ) 1406{ 1407 CDelayedAJAXData.apply( this, [ 'economy/emoticonhover/' + strEmoticonName + '/jsonp.js', msDelayBeforeAJAX ]); 1408 this.m_strEmoticonName = strEmoticonName; 1409} 1410 1411// subclass CDelayedAJAXData so we can request via JSONP 1412CEmoticonDelayedAJAXData.prototype = new CDelayedAJAXData; 1413CEmoticonDelayedAJAXData.prototype.constructor = CEmoticonDelayedAJAXData; 1414 1415CEmoticonDelayedAJAXData.prototype.GetAJAXParams = function() 1416{ 1417 return { 1418 url: 'https://community.fastly.steamstatic.com/' + this.m_strURL, 1419 dataType: 'jsonp', 1420 jsonpCallback: 'OnLoadEmoticon_' + this.m_strEmoticonName, //consistent name for cachability 1421 cache: true, 1422 data: {l: 'thai' } 1423 } 1424}; 1425 1426function InitEmoticonHovers() 1427{ 1428 var $Hover = $J('<div/>', {'class': 'emoticon_hover'} ); 1429 var $HoverContent = $J('<div/>', {'class': 'emoticon_hover_content'} ); 1430 $Hover.append( $HoverContent, $J('<div/>', {'class': 'hover_arrow left emoticon_hover_arrow' } ), $J('<div/>', {'class': 'hover_arrow right emoticon_hover_arrow' } ) ); 1431 $Hover.hide(); 1432 1433 var fnOneTimeEmoticonSetup = function() { 1434 $J(document.body).append( $Hover ); 1435 }; 1436 1437 var fnReadKey = function ( $Element ) { 1438 if ( $Element.data('emoticon') ) 1439 { 1440 return $Element.data('emoticon'); 1441 } 1442 else if ( $Element.attr( 'src' ) ) 1443 { 1444 var rgMatches = $Element.attr( 'src' ).match( 'emoticon/(.*)$' ); 1445 if ( rgMatches && rgMatches[1] ) 1446 { 1447 var strEmoticon = rgMatches[1]; 1448 if ( strEmoticon.length > 1 ) 1449 { 1450 if ( strEmoticon[0] == ':' ) 1451 strEmoticon = strEmoticon.substr( 1 ); 1452 if ( strEmoticon[ strEmoticon.length - 1 ] == ':' ) 1453 strEmoticon = strEmoticon.substr( 0, strEmoticon.length - 1 ); 1454 } 1455 return strEmoticon; 1456 } 1457 } 1458 1459 return null; 1460 }; 1461 1462 var fnDataFactory = function( key ) { 1463 if ( fnOneTimeEmoticonSetup ) 1464 { 1465 fnOneTimeEmoticonSetup(); 1466 fnOneTimeEmoticonSetup = null; 1467 } 1468 1469 return new CEmoticonDelayedAJAXData( key, 50 ); 1470 }; 1471 1472 var rgCallbacks = BindAJAXHovers( $Hover, $HoverContent, { 1473 fnDataFactory: fnDataFactory, 1474 fnPositionHover: function( $Hover, $Target ) { 1475 PositionMiniprofileHover( $Hover, $Target, { 1476 bPreferRightSide: true, 1477 nPxArrowOverlap: 0 1478 } ); 1479 //slide it down a little for emoticon option popup 1480 if ( $Target.hasClass('emoticon_option') ) 1481 $Hover.css( 'top', parseInt( $Hover.css('top') ) + 5 ); 1482 }, 1483 fnReadKey: fnReadKey, 1484 strSelector: 'img.emoticon', 1485 strURLMatch: 'emoticonhover', 1486 nDelayBeforeShow: 50 1487 } ); 1488 1489 window.DismissEmoticonHover = rgCallbacks.fnCancelHover; 1490} 1491 1492function V_EscapeRegExp( str ) 1493{ 1494 return str.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' ); 1495} 1496 1497function V_EscapeHTML( str ) 1498{ 1499 return String(str).replace( /&/g, '&' ).replace( /["]/g, '"' ).replace( /[']/g, ''' ).replace( /</g, '<').replace( />/g, '>'); 1500} 1501 1502function v_trim( str ) 1503{ 1504 if ( str.trim ) 1505 return str.trim(); 1506 else 1507 { 1508 return str.replace(/^\s+/, '').replace(/\s+$/, ''); 1509 } 1510} 1511 1512function V_ParseJSON( str ) 1513{ 1514 if ( typeof JSON == 'object' && JSON.parse ) 1515 return JSON.parse( str ); // built-in / json2.js 1516 else 1517 str.evalJSON(); // prototype 1518} 1519 1520function V_ToJSON( object ) 1521{ 1522 if ( typeof JSON == 'object' && JSON.stringify ) 1523 return JSON.stringify( object ); // built-in / json2.js 1524 else 1525 Object.toJSON( object ); // prototype 1526} 1527 1528function V_IsJSON( str ) 1529{ 1530 try 1531 { 1532 if( typeof JSON == 'object' && JSON.parse ) 1533 { 1534 var o = JSON.parse(str); 1535 if ( o !== null ) 1536 return true; 1537 1538 } else { 1539 return str.isJSON(); 1540 } 1541 } 1542 catch (e) { } 1543 return false; 1544} 1545 1546 1547function V_GetCookie( strCookieName ) 1548{ 1549 var rgMatches = document.cookie.match( '(^|; )' + strCookieName + '=([^;]*)' ); 1550 if ( rgMatches && rgMatches[2] ) 1551 return rgMatches[2]; 1552 else 1553 return null; 1554} 1555 1556 1557function V_GetDecodedCookie( strCookieName ) 1558{ 1559 var value = V_GetCookie( strCookieName ); 1560 return value && decodeURIComponent( value ); 1561} 1562 1563function V_SetCookie( strCookieName, strValue, expiryInDays, path ) 1564{ 1565 if ( !path ) 1566 path = '/'; 1567 1568 var strDate = ''; 1569 1570 if( typeof expiryInDays != 'undefined' && expiryInDays ) 1571 { 1572 var dateExpires = new Date(); 1573 dateExpires.setTime( dateExpires.getTime() + 1000 * 60 * 60 * 24 * expiryInDays ); 1574 strDate = '; expires=' + dateExpires.toGMTString(); 1575 } 1576 1577 document.cookie = strCookieName + '=' + strValue + strDate + ';path=' + path; 1578} 1579 1580function _GetStorageFromCookie() 1581{ 1582 var oStorage = {}; 1583 1584 var strStorageJSON = V_GetCookie( 'storage' ); 1585 if ( strStorageJSON ) 1586 { 1587 try { 1588 oStorage = V_ParseJSON(decodeURIComponent(strStorageJSON)); 1589 } 1590 catch (e) { 1591 oStorage = {}; 1592 } 1593 } 1594 1595 return oStorage; 1596} 1597 1598function BInsideIFrame() 1599{ 1600 try 1601 { 1602 return window.self !== window.top; 1603 } 1604 catch( e ) 1605 { 1606 return true; 1607 } 1608} 1609 1610function SetValueLocalStorage( strPreferenceName, value ) 1611{ 1612 if ( !BInsideIFrame() && window.localStorage ) 1613 { 1614 window.localStorage[strPreferenceName] = value; 1615 } 1616 else 1617 { 1618 var oStorage = _GetStorageFromCookie(); 1619 1620 oStorage[strPreferenceName] = value; 1621 1622 V_SetCookie( 'storage', encodeURIComponent( V_ToJSON( oStorage ) ), 365 ) 1623 } 1624} 1625 1626function UnsetValueLocalStorage( strPreferenceName ) 1627{ 1628 if ( !BInsideIFrame() && window.localStorage ) 1629 { 1630 delete window.localStorage[strPreferenceName]; 1631 } 1632 else 1633 { 1634 var oStorage = _GetStorageFromCookie(); 1635 1636 delete oStorage[strPreferenceName]; 1637 1638 V_SetCookie( 'storage', encodeURIComponent( V_ToJSON( oStorage ) ), 365 ) 1639 } 1640} 1641 1642function GetValueLocalStorage( strPreferenceName, defaultValue ) 1643{ 1644 if ( !BInsideIFrame() && window.localStorage ) 1645 { 1646 return window.localStorage[strPreferenceName] || defaultValue; 1647 } 1648 else 1649 { 1650 var oStorage = _GetStorageFromCookie(); 1651 1652 return oStorage[strPreferenceName] || defaultValue; 1653 } 1654} 1655 1656function DynamicLink_PlayYouTubeVideoInline( elem, videoid ) 1657{ 1658 var el = $J(elem); 1659 var youtubeurl = location.protocol + '//www.youtube.com/embed/' + videoid + '?showinfo=0&autohide=1&fs=1&hd=1&modestbranding=1&rel=0&showsearch=0&wmode=direct&autoplay=1'; 1660 var wrapper = $J( '<div/>', { 'class': 'dynamiclink_youtubeviewvideo' } ); 1661 var embed_wrapper = $J( '<div/>', { 'class' : 'dynamiclink_youtubeviewvideoembedded', 'frameborder' : '0' } );
1662 var iframeContent = $J( '<iframe/>', { 'frameborder' : '0', src: youtubeurl, 'allowfullscreen': '' } ); 1663 if ( el.length ) 1664 { 1665 embed_wrapper.append( iframeContent ); 1666 wrapper.append( embed_wrapper ); 1667 el.after( wrapper ); 1668 el.remove(); 1669 } 1670} 1671 1672function DynamicLink_PlayVimeoVideoInline( elem, videoid ) 1673{ 1674 var el = $J(elem); 1675 var videourl = 'https://player.vimeo.com/video/' + videoid + '?badge=0&autopause=0&autoplay=1&player_id=0'; 1676 var wrapper = $J( '<div/>', { 'class': 'dynamiclink_youtubeviewvideo' } ); 1677 var embed_wrapper = $J( '<div/>', { 'class' : 'dynamiclink_youtubeviewvideoembedded', 'frameborder' : '0' } ); 1678 var iframeContent = $J( '<iframe/>', { 'frameborder' : '0', src: videourl, webkitallowfullscreen : '', mozallowfullscreen : '', allowfullscreen : '' } ); 1679 if ( el.length ) 1680 { 1681 embed_wrapper.append( iframeContent ); 1682 wrapper.append( embed_wrapper ); 1683 el.after( wrapper ); 1684 el.remove(); 1685 } 1686} 1687 1688function DynamicLink_ShowSketchfabModelInline( elem, details ) 1689{ 1690 var url = "https://sketchfab.com/models/" + details.modelid + "/embed?autostart=1"; 1691 var el = $J(elem); 1692 var wrapper = $J( '<div/>', { 'class' : 'dynamiclink_sketchfabmodelembedded', 'frameborder' : '0' } ); 1693 var iframeContent = $J( '<iframe/>', { 'frameborder' : '0', "mozallowfullscreen" : true, "webkitallowfullscreen" : true, src: url } ); 1694 if ( el.length ) 1695 { 1696 wrapper.append( iframeContent ); 1697 el.after( wrapper ); 1698 el.remove(); 1699 } 1700} 1701 1702function ReplaceDynamicLink( id, strHTML ) 1703{ 1704 var el = $J('#'+id); 1705 if ( el.length && strHTML.length > 0 ) 1706 { 1707 var newEl = $J( strHTML ); 1708 el.after( newEl ); 1709 el.remove(); 1710 1711 if ( typeof window['HandleNewDynamicLink'] === "function" ) 1712 { 1713 HandleNewDynamicLink( newEl ); 1714 } 1715 } 1716} 1717 1718function ShowBannedDynamicLink( el ) 1719{ 1720 el = $J( el ); 1721 if ( el.length > 0 ) 1722 { 1723 var bannedContentURL = el.data( 'bannedurl' ); 1724 var link = $J( "<a>", { href: bannedContentURL, text: bannedContentURL } ); 1725 el.html( link ); 1726 el.attr( 'onclick', '' ); 1727 } 1728} 1729 1730 1731 1732function CScrollOffsetWatcher( el, fnCallback ) 1733{ 1734 this.m_$Element = $J(el); 1735 this.nOffsetTop = this.m_$Element.offset().top; 1736 this.nBufferHeight = 500; 1737 1738 this.nOffsetTopTrigger = this.nOffsetTop - this.nBufferHeight; 1739 1740 this.fnOnHit = fnCallback; 1741 1742 CScrollOffsetWatcher.RegisterWatcher( this ); 1743} 1744 1745CScrollOffsetWatcher.prototype.SetBufferHeight = function( nHeight ) 1746{ 1747 this.nBufferHeight = nHeight; 1748 this.Recalc(); 1749}; 1750 1751CScrollOffsetWatcher.prototype.Recalc = function() 1752{ 1753 this.nOffsetTop = this.m_$Element.offset().top; 1754 this.nOffsetTopTrigger = this.nOffsetTop - this.nBufferHeight; 1755}; 1756 1757CScrollOffsetWatcher.sm_rgWatchers = []; 1758CScrollOffsetWatcher.m_nTimeoutInitialLoad = 0; 1759CScrollOffsetWatcher.RegisterWatcher = function( Watcher ) 1760{ 1761 var bHadWatchers = CScrollOffsetWatcher.sm_rgWatchers.length > 0; 1762 1763 // keep the list sorted by offset trigger 1764 var iInsertionPoint; 1765 for( iInsertionPoint = CScrollOffsetWatcher.sm_rgWatchers.length; iInsertionPoint > 0 ; iInsertionPoint-- ) 1766 { 1767 if ( Watcher.nOffsetTopTrigger > CScrollOffsetWatcher.sm_rgWatchers[iInsertionPoint - 1].nOffsetTopTrigger ) 1768 break; 1769 } 1770 CScrollOffsetWatcher.sm_rgWatchers.splice( iInsertionPoint, 0, Watcher ); 1771 1772 if ( !bHadWatchers ) 1773 { 1774 $J(window).on( 'scroll.ScrollOffsetWatcher', CScrollOffsetWatcher.OnScroll ); 1775 1776 var nRecalcTimer = 0; 1777 $J(window).on( 'resize.ScrollOffsetWatcher', function() { 1778 if ( nRecalcTimer ) 1779 window.clearTimeout( nRecalcTimer ); 1780 nRecalcTimer = window.setTimeout( CScrollOffsetWatcher.ForceRecalc, 500 ); 1781 } ); 1782 } 1783 1784 // use a 1ms timeout to roll these together as much as possible on page load 1785 if ( !CScrollOffsetWatcher.m_nTimeoutInitialLoad ) 1786 CScrollOffsetWatcher.m_nTimeoutInitialLoad = window.setTimeout( function() { CScrollOffsetWatcher.OnScroll(); CScrollOffsetWatcher.m_nTimeoutInitialLoad = 0; }, 1 ); 1787}; 1788 1789CScrollOffsetWatcher.ForceRecalc = function() 1790{ 1791 for ( var i = 0; i < CScrollOffsetWatcher.sm_rgWatchers.length; i++ ) 1792 { 1793 CScrollOffsetWatcher.sm_rgWatchers[i].Recalc(); 1794 } 1795 1796 CScrollOffsetWatcher.OnScroll(); 1797}; 1798 1799CScrollOffsetWatcher.OnScroll = function() 1800{ 1801 var supportPageOffset = window.pageYOffset !== undefined; 1802 var isCSS1Compat = ((document.compatMode || "") === "CSS1Compat"); 1803 1804 var nScrollY = supportPageOffset ? window.pageYOffset : isCSS1Compat ? document.documentElement.scrollTop : document.body.scrollTop; 1805 var nOffsetBottom = nScrollY + window.innerHeight; 1806 1807 for( let i = CScrollOffsetWatcher.sm_rgWatchers.length - 1; i >= 0; i-- ) 1808 { 1809 var Watcher = CScrollOffsetWatcher.sm_rgWatchers[i]; 1810 if ( nOffsetBottom > Watcher.nOffsetTopTrigger ) 1811 { 1812 // make sure the page hasn't changed and we really need to show content 1813 Watcher.Recalc(); 1814 if ( nOffsetBottom > Watcher.nOffsetTopTrigger ) 1815 { 1816 Watcher.fnOnHit(); 1817 CScrollOffsetWatcher.sm_rgWatchers.splice( i, 1 ); 1818 } 1819 } 1820 } 1821 1822 if ( CScrollOffsetWatcher.sm_rgWatchers.length === 0 ) 1823 { 1824 $J(window).off( 'scroll.ScrollOffsetWatcher' ); 1825 $J(window).off( 'resize.ScrollOffsetWatcher' ); 1826 } 1827}; 1828 1829function LoadImageGroupOnScroll( elTarget, strGroup ) 1830{ 1831 var $Element = $J( '#' + elTarget ); 1832 if ( $Element.length ) 1833 new CScrollOffsetWatcher( $Element, function() { LoadDelayedImages(strGroup); } ); 1834} 1835 1836function LoadDelayedImages( group, bForce ) 1837{ 1838 if ( ( typeof g_rgDelayedLoadImages != 'undefined' && g_rgDelayedLoadImages[group] ) || bForce ) 1839 { 1840 $J('[data-delayed-image-group=' + group + ']').each( function() 1841 { 1842 $J(this).attr('src', $J(this).attr('data-delayed-image') ); 1843 }); 1844 1845 delete g_rgDelayedLoadImages[group]; 1846 } 1847} 1848 1849WebStorage = { 1850 GetLocal: function ( key, bSessionOnly ) 1851 { 1852 var type = ( bSessionOnly ) ? 'session' : 'local'; 1853 1854 if ( !window[type + 'Storage'] )
1855 return WebStorage.GetCookie( key ); 1856 1857 var storage = window[type + 'Storage']; 1858 var value; 1859 try { 1860 value = storage.getItem(key); 1861 } catch(err) { 1862 // storage.getItem can throw SecurityError based on user's browser setting. 1863 value = null; 1864 } 1865 1866 try { 1867 value = V_ParseJSON( value ); 1868 } catch(err){ 1869 return null; 1870 } 1871 1872 if( value == null ) 1873 { 1874 // Check if we have the value stored in a cookie instead. If so, move that to LS and remove the cookie 1875 value = V_GetCookie( key ); 1876 if( value != null ) 1877 { 1878 WebStorage.SetLocal( key, value, bSessionOnly ); 1879 WebStorage.ClearCookie( key ); 1880 } 1881 } 1882 return value; 1883 }, 1884 GetLocalSession: function( key ) 1885 { 1886 return WebStorage.GetLocal( key, true ); 1887 }, 1888 SetLocal: function ( key, value, bSessionOnly ) 1889 { 1890 var type = ( bSessionOnly ) ? 'session' : 'local'; 1891 1892 if ( !window[type + 'Storage'] ) 1893 return WebStorage.SetCookie( key, value, ( bSessionOnly ) ? null : 365 ); 1894 1895 value = V_ToJSON( value ); 1896 1897 var storage = window[type + 'Storage']; 1898 storage.setItem( key, value ); 1899 }, 1900 SetLocalSession: function( key, value ) 1901 { 1902 WebStorage.SetLocal( key, value, true ); 1903 }, 1904 GetCookie: function( key ) 1905 { 1906 var keyValue = V_GetCookie( key ); 1907 if ( keyValue ) 1908 keyValue = decodeURIComponent( keyValue ); 1909 1910 return V_IsJSON( keyValue ) ? V_ParseJSON( keyValue ) : keyValue; 1911 }, 1912 SetCookie: function( key, value, duration ) 1913 { 1914 value = encodeURIComponent( V_ToJSON( value ) ); 1915 V_SetCookie( key, value, duration ); 1916 }, 1917 ClearCookie: function( key ) 1918 { 1919 WebStorage.SetCookie(key, null, -1 ); 1920 }, 1921 RemoveLocal : function ( key, bSessionOnly ) 1922 { 1923 var type = ( bSessionOnly ) ? 'session' : 'local'; 1924 1925 if ( !window[type + 'Storage'] ) 1926 return WebStorage.ClearCookie( key ); 1927 1928 var storage = window[type + 'Storage']; 1929 1930 storage.removeItem( key ); 1931 } 1932}; 1933 1934// takes an integer 1935function v_numberformat( n, decimals, strDecimalSymbol, strThousandsSeperator ) 1936{ 1937 if ( typeof strDecimalSymbol == 'undefined' && typeof strThousandsSeperator == 'undefined' && typeof Number != 'undefined' && typeof Number.toLocaleString != 'undefined' ) 1938 { 1939 var options = {}; 1940 if ( typeof decimals != 'undefined' && decimals !== 0 ) 1941 { 1942 options.minimumFractionDigits = decimals; 1943 options.maximumFractionDigits = decimals; 1944 } 1945 1946 var num = new Number(n); 1947 try 1948 { 1949 return num.toLocaleString( false, options ); 1950 } 1951 catch( e ) 1952 { 1953 return num.toString(); 1954 } 1955 } 1956 1957 var str = '' + ( n ? n : 0 ); 1958 var len = str.indexOf( '.' ); 1959 if ( len == -1 ) 1960 len = str.length; 1961 var out = ''; 1962 for ( var i = 0; i < len; i++ ) 1963 { 1964 var c = str.charAt(i); 1965 out += c; 1966 if ( i < len - 1 && (len - i - 1) % 3 == 0 && c != '-' ) 1967 out += ( typeof strThousandsSeperator == 'undefined' ? ',' : strThousandsSeperator ); 1968 } 1969 if ( ( len < str.length || decimals ) && typeof decimals != 'undefined' && decimals !== 0 ) 1970 { 1971 len++; 1972 out += ( typeof strDecimalSymbol == 'undefined' ? ',' : strDecimalSymbol ); 1973 for ( var i = 0; i < ( decimals ? decimals : str.length - len ); i++ ) 1974 { 1975 if ( len + i < str.length ) 1976 out += str.charAt( len + i ); 1977 else 1978 out += '0'; 1979 } 1980 } 1981 1982 return out; 1983} 1984 1985 1986function v_shuffle( rgArray ) 1987{ 1988 for ( var i = 0; i < rgArray.length; i++ ) 1989 { 1990 var iSwap = Math.floor( Math.random() * ( rgArray.length - i ) + i ); 1991 if ( iSwap != i ) 1992 { 1993 var temp = rgArray[iSwap]; 1994 rgArray[iSwap] = rgArray[i]; 1995 rgArray[i] = temp; 1996 } 1997 } 1998 1999 return rgArray; 2000} 2001 2002 2003function UpdateFormattedNumber( element, delta ) 2004{ 2005 var $Element = $J(element); 2006 $Element.text( v_numberformat( parseInt( $Element.text().replace( /,/, '' ) ) + delta ) ); 2007} 2008 2009function RateAnnouncement( rateURL, gid, bVoteUp, clanID ) 2010{ 2011 if ( bVoteUp && $J('#VoteUpBtn_' + gid).hasClass( "btn_active" ) ) 2012 { 2013 return; 2014 } 2015 if ( !bVoteUp && $J('#VoteDownBtn_' + gid).hasClass( "btn_active" ) ) 2016 { 2017 return; 2018 } 2019 2020 rateURL = rateURL + gid; 2021 $J.post( rateURL, { 2022 'voteup' : bVoteUp, 2023 'clanid' : clanID, 2024 'sessionid' : g_sessionID 2025 } 2026 ).done( function( json ) { 2027 2028 var votesUpCount = $J('#VotesUpCount_' + gid); 2029 if ( votesUpCount ) 2030 { 2031 var increment = 0; 2032 if ( bVoteUp ) 2033 { 2034 increment = 1; 2035 } 2036 else if ( $J('#VoteUpBtn_' + gid).hasClass( 'btn_active' ) ) 2037 { 2038 increment = -1; 2039 } 2040 UpdateFormattedNumber( votesUpCount, increment ); 2041 2042 if ( parseInt( votesUpCount.html().replace(/,/g, '') ) == 0 ) 2043 { 2044 $J('#VotesUpCountContainer_' + gid).hide(); 2045 } 2046 else 2047 {
2048 $J('#VotesUpCountContainer_' + gid).show(); 2049 } 2050 } 2051 2052 if ( bVoteUp ) 2053 { 2054 $J('#VoteUpBtn_' + gid).addClass( "btn_active" ); 2055 $J('#VoteDownBtn_' + gid).removeClass( "btn_active" ); 2056 } 2057 else 2058 { 2059 $J('#VoteDownBtn_' + gid).addClass( "btn_active" ); 2060 $J('#VoteUpBtn_' + gid).removeClass( "btn_active" ); 2061 } 2062 2063 2064 2065 } ) 2066 .fail( function( jqxhr ) { 2067 var responseJSON = jqxhr.responseText.evalJSON(); 2068 switch ( responseJSON.success ) 2069 { 2070 case 21: 2071 ShowAlertDialog( 'à¸à¹à¸à¸à¸´à¸à¸à¸¥à¸²à¸', 'à¸à¸¸à¸à¸à¹à¸à¸à¹à¸à¹à¸²à¸ªà¸¹à¹à¸£à¸°à¸à¸à¹à¸à¸·à¹à¸à¸à¸£à¸°à¸à¸³à¸à¸²à¸£à¸à¸±à¹à¸' ); 2072 break; 2073 case 24: 2074 ShowAlertDialog( 'à¸à¹à¸à¸à¸´à¸à¸à¸¥à¸²à¸', 2075 'à¸à¸±à¸à¸à¸µà¸à¸à¸à¸à¸¸à¸à¹à¸¡à¹à¸à¹à¸²à¸à¸à¸§à¸²à¸¡à¸à¹à¸à¸à¸à¸²à¸£à¸à¸±à¹à¸à¸à¹à¸³à¸à¸µà¹à¸à¸°à¹à¸à¹à¸à¸¸à¸à¸ªà¸¡à¸à¸±à¸à¸´à¸à¸µà¹ <a class="whiteLink" href="https://help.steampowered.com/th/wizard/HelpWithLimitedAccount" target="_blank" rel="noreferrer">à¸à¸²à¸£à¹à¸¢à¸µà¹à¸¢à¸¡à¸à¸¡à¸à¹à¸²à¸¢à¸ªà¸à¸±à¸à¸ªà¸à¸¸à¸ Steam</a> สำหรัà¸à¸à¹à¸à¸¡à¸¹à¸¥à¹à¸à¸´à¹à¸¡à¹à¸à¸´à¸¡' 2076 ); 2077 break; 2078 case 15: 2079 ShowAlertDialog( 'à¸à¹à¸à¸à¸´à¸à¸à¸¥à¸²à¸', 'à¸à¸¸à¸à¹à¸¡à¹à¸¡à¸µà¸ªà¸´à¸à¸à¸´à¹à¹à¸à¸à¸²à¸£à¸à¸£à¸°à¸à¸³à¸à¸²à¸£à¸à¸±à¹à¸' ); 2080 break; 2081 default: 2082 ShowAlertDialog( 'à¸à¹à¸à¸à¸´à¸à¸à¸¥à¸²à¸', 'à¸à¸£à¸§à¸à¸à¸à¸à¹à¸à¸à¸´à¸à¸à¸¥à¸²à¸à¸à¸à¸°à¸à¸³à¸¥à¸±à¸à¸à¸£à¸°à¸¡à¸§à¸¥à¸à¸¥à¸à¸³à¸£à¹à¸à¸à¸à¸à¸à¸à¸à¸à¸¸à¸: ' + responseJSON.success ); 2083 break; 2084 } 2085 } ); 2086 return false; 2087} 2088 2089function GetResponsiveHeaderFixedOffsetAdjustment() 2090{ 2091 // for responsive pages - we need to adjust for the menu 2092 var $ResponsiveHeader = $J('.responsive_header:visible'); 2093 if ( $ResponsiveHeader.length && $ResponsiveHeader.css('position') == 'fixed' ) 2094 return $ResponsiveHeader.outerHeight(); 2095 else 2096 return 0; 2097} 2098 2099/* Scroll to an element if it's not already in view. If it's at the bottom of the viewport, then it will be 2100 scrolled to the top if less than nRequiredPixelsToShow are visible (defaults to the height of the element) 2101 */ 2102function ScrollToIfNotInView( elem, nRequiredPixelsToShow, nSpacingBefore, nAnimationSpeed ) 2103{ 2104 var $Elem = $JFromIDOrElement(elem); 2105 2106 if ( typeof nSpacingBefore == 'undefined' ) 2107 nSpacingBefore = 0; 2108 2109 // for responsive pages - we need to adjust for the menu 2110 nSpacingBefore += GetResponsiveHeaderFixedOffsetAdjustment(); 2111 2112 var elemTop = $Elem.offset().top; 2113 var nViewportOffsetTop = elemTop - $J(window).scrollTop(); 2114 var bNeedToScroll = false; 2115 if ( nViewportOffsetTop < 0 ) 2116 { 2117 bNeedToScroll = true; 2118 } 2119 else 2120 { 2121 if ( !nRequiredPixelsToShow ) 2122 nRequiredPixelsToShow = $Elem.outerHeight(); 2123 2124 var nViewportOffsetBottom = nViewportOffsetTop + nRequiredPixelsToShow; 2125 2126 if ( nViewportOffsetBottom > $J(window).height() ) 2127 bNeedToScroll = true; 2128 } 2129 2130 if ( bNeedToScroll ) 2131 { 2132 if ( nSpacingBefore ) 2133 nViewportOffsetTop -= nSpacingBefore; 2134 2135 if ( typeof nAnimationSpeed != 'undefined' ) 2136 $J('html, body' ).animate( {scrollTop: nViewportOffsetTop}, nAnimationSpeed ); 2137 else 2138 window.scrollBy( 0, nViewportOffsetTop ); 2139 } 2140} 2141 2142 2143function CAjaxInfiniteScrollingControls( rgSearchData, url ) 2144{ 2145 this.m_strActionURL = null; 2146 this.m_cPageSize = null; 2147 this.m_strElementPrefix = ""; 2148 this.m_strClassPrefix = ""; 2149 this.m_StrRowsId = ""; 2150 this.m_rgStaticParams = null; 2151 2152 this.m_bLoading = false; 2153 2154 this.m_fnPreRequestHandler = null; 2155 this.m_fnResponseHandler = null; 2156 this.m_fnPageChangingHandler = null; 2157 this.m_fnPageChangedHandler = null; 2158 this.m_fnRawScrollHandler = null; 2159 2160 this.m_LoadingDialog = null; 2161 this.m_bRestoringScrollTop = false; 2162 2163 this.m_strActionURL = url; 2164 2165 this.m_strQuery = rgSearchData['query']; 2166 this.m_cTotalCount = rgSearchData['total_count']; 2167 this.m_iCurrentPage = rgSearchData['page'] || 0; 2168 this.m_cPageSize = rgSearchData['pagesize']; 2169 this.m_cMaxPages = Math.ceil( this.m_cTotalCount / this.m_cPageSize ); 2170 2171 this.m_iTriggerHeight = rgSearchData['trigger_height'] || 750; // How many px from the bottom should we trigger a page load. 2172 2173 this.m_iCooldownTime = 0; // ms from the epoch when we can next update. 2174 this.m_iCooldownInterval = 200; // Minimum time (in ms) between updates 2175 2176 if ( rgSearchData['prefix'] ) 2177 this.m_strElementPrefix = rgSearchData['prefix']; 2178 2179 // Rows element name that's updated when we scroll. 2180 this.m_StrRowsId = this.m_strElementPrefix + 'Rows'; 2181 2182 if ( rgSearchData['class_prefix'] ) 2183 this.m_strClassPrefix = rgSearchData['class_prefix']; 2184 2185 // We want our handlers to get a copy of our infiniteScroll object when they're called, so we save it 2186 // into a var and build a closure around it. 2187 var thisControl = this; 2188 2189 // By saving the bound scroll-handler to a variable, we can then unload it when our search 2190 // options are upadated. 2191 this.m_fnRawScrollHandler = function() { thisControl.OnScroll() }; 2192 $J(document).scroll( this.m_fnRawScrollHandler ); 2193 2194 // If we have a _scroll_top element, then set our OnUnload handler. 2195 if ( $J( "#" + this.m_strElementPrefix + '_scroll_top').length ) 2196 window.addEventListener('beforeunload', function() { thisControl.OnUnload() } ); 2197 2198 // Optional: Dynamic data returned from the last request, to provided to the next request. Lets them 2199 // being stringed together with some memory 2200 this.m_rgDynamicData = null; 2201 2202 this.RestoreScrollTop( true ); 2203} 2204 2205CAjaxInfiniteScrollingControls.prototype.DoneRestoreScrollTop = function() 2206{ 2207 this.ClearLoadingDialog(); 2208 this.m_bRestoringScrollTop = false;
2209}; 2210 2211CAjaxInfiniteScrollingControls.prototype.ClearLoadingDialog = function() 2212{ 2213 if ( this.m_LoadingDialog ) 2214 { 2215 this.m_LoadingDialog.Dismiss(); 2216 this.m_LoadingDialog = null; 2217 } 2218}; 2219 2220// Stops this infinite scroll handler. 2221CAjaxInfiniteScrollingControls.prototype.Stop = function() 2222{ 2223 // Clear loading notifiers. 2224 this.ClearLoadingDialog(); 2225 this.HideThrobber(); 2226 2227 // Clear scheduled scroll, if there is one. 2228 if ( this.m_oScheduledScroll ) 2229 clearTimeout( this.m_oScheduledScroll ); 2230 2231 // Unhook scroll handler. 2232 $J(document).off( "scroll", this.m_fnRawScrollHandler ); 2233 2234 // TODO: Do we need to call or clear BeforeUnload? 2235}; 2236 2237CAjaxInfiniteScrollingControls.prototype.RestoreScrollTop = function( bForce ) 2238{ 2239 this.m_bRestoringScrollTop |= bForce; 2240 if ( !this.m_bRestoringScrollTop ) 2241 { 2242 return; 2243 } 2244 var scrollTopPrevious = parseInt( $J( "#" + this.m_strElementPrefix + '_scroll_top').val() ); 2245 if ( scrollTopPrevious ) 2246 { 2247 var viewport = document.viewport.getDimensions(); // Gets the viewport as an object literal 2248 var windowHeight = viewport.height; // Usable window height 2249 var bodyHeight = $(document.body).getHeight(); 2250 2251 // done? 2252 if ( scrollTopPrevious < bodyHeight - windowHeight ) 2253 { 2254 this.DoneRestoreScrollTop(); 2255 window.scrollTo( 0, scrollTopPrevious ); 2256 } 2257 else 2258 { 2259 if ( this.m_LoadingDialog == null ) 2260 { 2261 this.m_LoadingDialog = ShowBlockingWaitDialog( 'à¹à¸à¸£à¸à¸£à¸à¸ªà¸±à¸à¸à¸£à¸¹à¹', 'à¸à¸³à¸¥à¸±à¸à¸à¸¥à¸±à¸à¹à¸à¸¢à¸±à¸à¸à¸µà¹à¸à¸¸à¸à¸à¸¢à¸¹à¹à¸¥à¹à¸²à¸ªà¸¸à¸à¸à¸à¸«à¸à¹à¸²à¸à¸µà¹...' ); 2262 } 2263 window.scrollTo( 0, scrollTopPrevious ); 2264 } 2265 } 2266}; 2267 2268CAjaxInfiniteScrollingControls.prototype.OnUnload = function() 2269{ 2270 var scrollOffset = document.viewport.getScrollOffsets(); 2271 var scrollTop = scrollOffset.top; 2272 $J( "#" + this.m_strElementPrefix + '_scroll_top').val( scrollTop ); 2273}; 2274 2275CAjaxInfiniteScrollingControls.prototype.OnScroll = function() 2276{ 2277 if ( this.m_bLoading ) 2278 return; 2279 2280 var iNow = new Date().getTime(); 2281 2282 // How soon can we scroll? 2283 var iScrollWait = this.m_iCooldownTime - iNow; 2284 2285 // If we haven't reached our cooldown, do nothing. 2286 if ( iScrollWait > 0 ) 2287 { 2288 // console.log("InfiniScrolling too fast; engaging throttle"); 2289 2290 // Schedule a scroll event for when they would be allowed to scroll. Without this, the 2291 // user needs to jiggle their scrollbar. 2292 if ( this.m_oScheduledScroll == null ) 2293 { 2294 this.m_oScheduledScroll = setTimeout( this.m_fnRawScrollHandler, iScrollWait ); 2295 this.ShowThrobber(); // Show throbber while waiting to begin load. 2296 } 2297 2298 return; 2299 } 2300 2301 this.m_oScheduledScroll = null; 2302 2303 // The bottom of our screen is equal to how far we've scrolled, plus the height of our window. 2304 var nCurrentScroll = $J(window).scrollTop() + $J(window).height(); 2305 2306 var rows = $J('#' + this.m_StrRowsId); 2307 var offset = rows.offset(); 2308 2309 // The bottom of our content is the height of our results, plus its offset from the top of the page. 2310 // We want to trigger a load this.m_iTriggerHeight before the user sees that. 2311 var nTriggerPoint = rows.height() + offset.top - this.m_iTriggerHeight; 2312 2313 if ( nCurrentScroll > nTriggerPoint ) 2314 { 2315 this.m_iCooldownTime = iNow + this.m_iCooldownInterval; 2316 2317 this.NextPage(); 2318 } 2319}; 2320 2321CAjaxInfiniteScrollingControls.prototype.GetActionURL = function( action ) 2322{ 2323 var url = this.m_strActionURL + action + '/'; 2324 return url; 2325}; 2326 2327CAjaxInfiniteScrollingControls.prototype.SetPreRequestHandler = function( fnHandler ) 2328{ 2329 this.m_fnPreRequestHandler = fnHandler; 2330}; 2331 2332CAjaxInfiniteScrollingControls.prototype.SetResponseHandler = function( fnHandler ) 2333{ 2334 this.m_fnResponseHandler = fnHandler; 2335}; 2336 2337CAjaxInfiniteScrollingControls.prototype.SetPageChangingHandler = function ( fnHandler ) 2338{ 2339 this.m_fnPageChangingHandler = fnHandler; 2340}; 2341 2342CAjaxInfiniteScrollingControls.prototype.SetPageChangedHandler = function ( fnHandler ) 2343{ 2344 this.m_fnPageChangedHandler = fnHandler; 2345}; 2346 2347CAjaxInfiniteScrollingControls.prototype.SetStaticParameters = function ( rgParams ) 2348{ 2349 this.m_rgStaticParams = rgParams; 2350}; 2351 2352CAjaxInfiniteScrollingControls.prototype.OnAJAXComplete = function() 2353{ 2354 this.m_bLoading = false;
2355}; 2356 2357CAjaxInfiniteScrollingControls.prototype.NextPage = function() 2358{ 2359 if ( this.m_iCurrentPage < this.m_cMaxPages - 1 ) { 2360 this.LoadPage( this.m_iCurrentPage + 1 ); 2361 } 2362 else 2363 { 2364 // We're reached the end of our results! 2365 this.Stop(); 2366 } 2367 2368}; 2369 2370CAjaxInfiniteScrollingControls.prototype.LoadPage = function( iPage, bForce ) 2371{ 2372 if ( typeof( bForce )== 'undefined' || !bForce ) 2373 { 2374 if ( this.m_bLoading || iPage >= this.m_cMaxPages || iPage < 0 ) 2375 { 2376 return false; 2377 } 2378 else if ( iPage == this.m_iCurrentPage ) 2379 { 2380 this.RestoreScrollTop( false ); 2381 } 2382 } 2383 2384 var params = { 2385 query: this.m_strQuery, 2386 start: this.m_cPageSize * iPage, 2387 count: this.m_cPageSize, 2388 dynamic_data: this.m_rgDynamicData, 2389 }; 2390 2391 if ( this.m_rgStaticParams != null ) 2392 { 2393 for ( var sParamName in this.m_rgStaticParams ) 2394 { 2395 2396 if ( typeof sParamName != "string" ) 2397 continue; 2398 2399 var typeOfParam = typeof this.m_rgStaticParams[sParamName]; 2400 2401 if ( typeOfParam != "string" && typeOfParam != "number" && typeOfParam != "boolean" ) 2402 continue; 2403 2404 params[sParamName] = this.m_rgStaticParams[sParamName]; 2405 } 2406 } 2407 2408 if ( this.m_fnPageChangingHandler != null ) 2409 this.m_fnPageChangingHandler( iPage ); 2410 2411 if ( this.m_fnPreRequestHandler != null ) 2412 this.m_fnPreRequestHandler( params ); 2413 2414 this.ShowThrobber(); 2415 2416 this.m_bLoading = true; 2417 2418 new Ajax.Request( this.GetActionURL( '' ), { 2419 method: 'get', 2420 parameters: params, 2421 onSuccess: this.OnResponseRenderResults.bind( this ), 2422 onComplete: this.OnAJAXComplete.bind( this ) 2423 }); 2424 2425 return true; 2426}; 2427 2428CAjaxInfiniteScrollingControls.prototype.GetThrobber = function() { 2429 // NB: This is using prototype, *not* jQuery, hence the lack of 'J'. 2430 return $(this.m_strElementPrefix + '_loading'); 2431}; 2432 2433CAjaxInfiniteScrollingControls.prototype.HideThrobber = function() { 2434 var throbber = this.GetThrobber(); 2435 2436 if (throbber) 2437 throbber.hide(); 2438}; 2439 2440CAjaxInfiniteScrollingControls.prototype.ShowThrobber = function() { 2441 var throbber = this.GetThrobber(); 2442 2443 if (throbber) 2444 throbber.show(); 2445}; 2446 2447CAjaxInfiniteScrollingControls.prototype.OnResponseRenderResults = function( transport ) 2448{ 2449 2450 if ( transport.responseJSON && transport.responseJSON.success ) 2451 { 2452 this.HideThrobber(); 2453 2454 if ( typeof RecordAJAXPageView !== "undefined" ) 2455 { 2456 RecordAJAXPageView( transport.request.url ); 2457 } 2458 2459 var response = transport.responseJSON; 2460 this.m_cTotalCount = response.total_count; 2461 this.m_cMaxPages = Math.ceil( response.total_count / this.m_cPageSize ); 2462 this.m_iCurrentPage = Math.floor( response.start / this.m_cPageSize ); 2463 2464 if ( this.m_iCurrentPage != 0 && this.m_cTotalCount <= response.start ) 2465 { 2466 // this page is no longer valid, flip back a page (deferred so that the AJAX handler exits and reset m_bLoading) 2467 this.DoneRestoreScrollTop(); 2468 return; 2469 } 2470 2471 var elResults = $(this.m_StrRowsId); 2472 2473 elResults.insert( response.results_html ); 2474 2475 this.m_rgDynamicData = ( response.dynamic_data ) ? response.dynamic_data : null; 2476 2477 if ( this.m_fnResponseHandler != null ) 2478 { 2479 this.m_fnResponseHandler( response ); 2480 } 2481 2482 if ( this.m_fnPageChangedHandler != null ) 2483 this.m_fnPageChangedHandler( this.m_iCurrentPage, elResults ); 2484 2485 this.m_bLoading = false; 2486 if ( this.m_iCurrentPage < this.m_cMaxPages - 1 ) 2487 { 2488 this.RestoreScrollTop( false ); 2489 } 2490 else 2491 { 2492 this.DoneRestoreScrollTop(); 2493 } 2494 } 2495 else 2496 { 2497 this.DoneRestoreScrollTop(); 2498 } 2499}; 2500 2501function CAjaxPagingControls( rgSearchData, url ) 2502{ 2503 this.m_strActionURL = null; 2504 this.m_cPageSize = null; 2505 this.m_strElementPrefix = ""; 2506 this.m_strClassPrefix = ""; 2507 this.m_rgStaticParams = null; 2508 2509 this.m_strQuery = null; 2510 this.m_cTotalCount = 0; 2511 this.m_iCurrentPage = 0; 2512 this.m_cMaxPages = 0; 2513 this.m_bLoading = false; 2514 2515 this.m_fnPreRequestHandler = null; 2516 this.m_fnResponseHandler = null; 2517 this.m_fnPageChangingHandler = null; 2518 this.m_fnPageChangedHandler = null; 2519 2520 2521 this.m_strActionURL = url; 2522 2523 this.m_strQuery = rgSearchData['query']; 2524 this.m_cTotalCount = rgSearchData['total_count']; 2525 this.m_iCurrentPage = 0; 2526 this.m_cPageSize = rgSearchData['pagesize']; 2527 this.m_cMaxPages = Math.ceil( this.m_cTotalCount / this.m_cPageSize ); 2528 this.m_strDefaultAction = typeof rgSearchData['action'] != 'undefined' ? rgSearchData['action'] : 'render'; 2529 this.m_rgAvailableSizes = []; 2530 2531 if ( rgSearchData['prefix'] ) 2532 this.m_strElementPrefix = rgSearchData['prefix']; 2533 2534 if ( rgSearchData['class_prefix'] ) 2535 this.m_strClassPrefix = rgSearchData['class_prefix']; 2536 2537 $(this.m_strElementPrefix + '_btn_prev').observe( 'click', this.PrevPage.bind( this ) ); 2538 $(this.m_strElementPrefix + '_btn_next').observe( 'click', this.NextPage.bind( this ) ); 2539 var elPageSizeCtn = $(this.m_strElementPrefix + '_paging_size_ctn'); 2540 for( var i = 0; elPageSizeCtn && i < $( elPageSizeCtn ).children.length; ++i ) 2541 { 2542 $(this.m_strElementPrefix + '_paging_size_' + i ).observe( 'click', this.OnChangeSize.bind( this ) ); 2543 2544 this.m_rgAvailableSizes.push( $( elPageSizeCtn ).children[i].dataset.size ); 2545 } 2546 2547 this.UpdatePagingDisplay(); 2548} 2549 2550CAjaxPagingControls.prototype.GetActionURL = function( action ) 2551{ 2552 var url = action ? this.m_strActionURL + action + '/' : this.m_strActionURL; 2553 return url; 2554}; 2555 2556CAjaxPagingControls.prototype.SetPreRequestHandler = function( fnHandler ) 2557{ 2558 this.m_fnPreRequestHandler = fnHandler; 2559}; 2560 2561CAjaxPagingControls.prototype.SetResponseHandler = function( fnHandler ) 2562{ 2563 this.m_fnResponseHandler = fnHandler; 2564}; 2565 2566CAjaxPagingControls.prototype.SetPageChangingHandler = function ( fnHandler ) 2567{ 2568 this.m_fnPageChangingHandler = fnHandler; 2569}; 2570 2571CAjaxPagingControls.prototype.SetPageChangedHandler = function ( fnHandler ) 2572{ 2573 this.m_fnPageChangedHandler = fnHandler; 2574}; 2575 2576CAjaxPagingControls.prototype.SetStaticParameters = function ( rgParams ) 2577{ 2578 this.m_rgStaticParams = rgParams; 2579}; 2580 2581CAjaxPagingControls.prototype.OnAJAXComplete = function() 2582{ 2583 this.m_bLoading = false;
2584}; 2585 2586CAjaxPagingControls.prototype.OnChangeSize = function( event ) 2587{ 2588 if( event.target && event.target.dataset.size != this.m_cPageSize ) 2589 { 2590 this.m_cPageSize = event.target.dataset.size; 2591 this.GoToPage( 0, true ); 2592 } 2593}; 2594 2595CAjaxPagingControls.prototype.NextPage = function() 2596{ 2597 if ( this.m_iCurrentPage < this.m_cMaxPages - 1 ) 2598 this.GoToPage( this.m_iCurrentPage + 1 ); 2599}; 2600 2601CAjaxPagingControls.prototype.PrevPage = function() 2602{ 2603 if ( this.m_iCurrentPage > 0 ) 2604 this.GoToPage( this.m_iCurrentPage - 1 ); 2605}; 2606 2607CAjaxPagingControls.prototype.GoToPage = function( iPage, bForce ) 2608{ 2609 if ( typeof( bForce )== 'undefined' || !bForce ) 2610 { 2611 if ( this.m_bLoading || iPage >= this.m_cMaxPages || iPage < 0 || iPage == this.m_iCurrentPage ) 2612 return false; 2613 } 2614 2615 var params = { 2616 query: this.m_strQuery, 2617 start: this.m_cPageSize * iPage, 2618 count: this.m_cPageSize 2619 }; 2620 2621 if ( this.m_rgStaticParams != null ) 2622 { 2623 for ( var sParamName in this.m_rgStaticParams ) 2624 { 2625 if ( typeof sParamName != "string" ) 2626 continue; 2627 2628 params[sParamName] = this.m_rgStaticParams[sParamName]; 2629 } 2630 } 2631 2632 if ( this.m_fnPageChangingHandler != null ) 2633 this.m_fnPageChangingHandler( iPage ); 2634 2635 if ( this.m_fnPreRequestHandler != null ) 2636 this.m_fnPreRequestHandler( params ); 2637 2638 this.m_bLoading = true; 2639 new Ajax.Request( this.GetActionURL( this.m_strDefaultAction ), { 2640 method: 'get', 2641 parameters: params, 2642 onSuccess: this.OnResponseRenderResults.bind( this ), 2643 onComplete: this.OnAJAXComplete.bind( this ) 2644 }); 2645 2646 return true; 2647}; 2648 2649CAjaxPagingControls.prototype.OnResponseRenderResults = function( transport ) 2650{ 2651 if ( transport.responseJSON && transport.responseJSON.success ) 2652 { 2653 if ( typeof RecordAJAXPageView !== "undefined" ) 2654 { 2655 RecordAJAXPageView( transport.request.url ); 2656 } 2657 2658 var response = transport.responseJSON; 2659 this.m_cTotalCount = response.total_count; 2660 this.m_cMaxPages = Math.ceil( response.total_count / this.m_cPageSize ); 2661 this.m_iCurrentPage = Math.floor( response.start / this.m_cPageSize ); 2662 2663 if ( this.m_iCurrentPage != 0 && this.m_cTotalCount <= response.start ) 2664 { 2665 // this page is no longer valid, flip back a page (deferred so that the AJAX handler exits and reset m_bLoading) 2666 this.GoToPage.bind( this, this.m_iCurrentPage - 1 ).defer(); 2667 2668 return; 2669 } 2670 2671 var elResults = $(this.m_strElementPrefix + 'Rows'); 2672 2673 if( elResults) 2674 elResults.update( response.results_html ); 2675 2676 if ( this.m_fnResponseHandler != null ) 2677 this.m_fnResponseHandler( response ); 2678 2679 this.UpdatePagingDisplay(); 2680 2681 ScrollToIfNotInView( $(this.m_strElementPrefix + 'Table'), 40 ); 2682 } 2683}; 2684 2685CAjaxPagingControls.prototype.UpdatePagingDisplay = function() 2686{ 2687 var elemNoResults = $(this.m_strElementPrefix + '_no_results'); 2688 if ( this.m_cTotalCount == 0 ) 2689 { 2690 $(this.m_strElementPrefix + '_ctn').hide(); 2691 if ( elemNoResults ) 2692 elemNoResults.show(); 2693 } 2694 else 2695 { 2696 $(this.m_strElementPrefix + '_ctn').show(); 2697 if ( elemNoResults ) 2698 elemNoResults.hide(); 2699 2700 $(this.m_strElementPrefix + '_total').update( v_numberformat( this.m_cTotalCount ) ); 2701 $(this.m_strElementPrefix + '_start').update( v_numberformat( this.m_iCurrentPage * this.m_cPageSize + 1 ) ); 2702 $(this.m_strElementPrefix + '_end').update( Math.min( ( this.m_iCurrentPage + 1 ) * this.m_cPageSize, this.m_cTotalCount ) ); 2703 } 2704 2705 if( this.m_rgAvailableSizes && this.m_rgAvailableSizes.length > 0 ) 2706 { 2707 if( this.m_cTotalCount <= this.m_rgAvailableSizes[0] ) 2708 { 2709 $(this.m_strElementPrefix + '_per_page_ctn').hide(); 2710 } 2711 else 2712 { 2713 $(this.m_strElementPrefix + '_per_page_ctn').show(); 2714 } 2715 } 2716 2717 if ( this.m_cMaxPages <= 1 ) 2718 { 2719 $(this.m_strElementPrefix + '_controls').hide(); 2720 } 2721 else 2722 { 2723 $(this.m_strElementPrefix + '_controls').show(); 2724 if ( this.m_iCurrentPage > 0 ) 2725 $(this.m_strElementPrefix + '_btn_prev').removeClassName('disabled'); 2726 else 2727 $(this.m_strElementPrefix + '_btn_prev').addClassName('disabled'); 2728 2729 if ( this.m_iCurrentPage < this.m_cMaxPages - 1 ) 2730 $(this.m_strElementPrefix + '_btn_next').removeClassName('disabled'); 2731 else 2732 $(this.m_strElementPrefix + '_btn_next').addClassName('disabled'); 2733 2734 var elPageLinks = $(this.m_strElementPrefix + '_links'); 2735 elPageLinks.update(''); 2736 // we always show first, last, + 3 page links closest to current page 2737 var cPageLinksAheadBehind = 2; 2738 var firstPageLink = Math.max( this.m_iCurrentPage - cPageLinksAheadBehind, 1 ); 2739 var lastPageLink = Math.min( this.m_iCurrentPage + (cPageLinksAheadBehind*2) + ( firstPageLink - this.m_iCurrentPage ), this.m_cMaxPages - 2 ); 2740 2741 if ( lastPageLink - this.m_iCurrentPage < cPageLinksAheadBehind ) 2742 firstPageLink = Math.max( this.m_iCurrentPage - (cPageLinksAheadBehind*2) + ( lastPageLink - this.m_iCurrentPage ), 1 ); 2743 2744 this.AddPageLink( elPageLinks, 0 ); 2745 if ( firstPageLink != 1 ) 2746 elPageLinks.insert( ' ... ' ); 2747 2748 for ( var iPage = firstPageLink; iPage <= lastPageLink; iPage++ ) 2749 { 2750 this.AddPageLink( elPageLinks, iPage ); 2751 } 2752 2753 if ( lastPageLink != this.m_cMaxPages - 2 ) 2754 elPageLinks.insert( ' ... ' ); 2755 this.AddPageLink( elPageLinks, this.m_cMaxPages - 1 ); 2756 } 2757 2758 if( this.m_rgAvailableSizes.indexOf( this.m_cPageSize ) > -1 ) 2759 { 2760 for( var i = 0; i < this.m_rgAvailableSizes.length; ++ i ) 2761 { 2762 var elSizeItem = $(this.m_strElementPrefix + '_paging_size_' + i ); 2763 if( elSizeItem.dataset.size == this.m_cPageSize ) 2764 { 2765 elSizeItem.addClassName( 'size_selected' ); 2766 } 2767 else 2768 {
2769 elSizeItem.removeClassName( 'size_selected' ); 2770 } 2771 } 2772 } 2773 2774 if ( this.m_fnPageChangedHandler != null ) 2775 this.m_fnPageChangedHandler( this.m_iCurrentPage ); 2776}; 2777 2778CAjaxPagingControls.prototype.AddPageLink = function( elPageLinks, iPage ) 2779{ 2780 var el = new Element( 'span', {'class': ( this.m_strClassPrefix != "" ? this.m_strClassPrefix : this.m_strElementPrefix ) + '_paging_pagelink' } ); 2781 el.update( (iPage + 1) + ' ' ); 2782 2783 if ( iPage == this.m_iCurrentPage ) 2784 el.addClassName( 'active' ); 2785 else 2786 el.observe( 'click', this.GoToPage.bind( this, iPage ) ); 2787 2788 elPageLinks.insert( el ); 2789}; 2790 2791 2792function CSlider( $Container, $Grabber, args ) 2793{ 2794 this.m_$Container = $Container; 2795 this.m_$Grabber = $Grabber || $Container.find('.handle'); 2796 this.m_nMinVal = args.min || 0; 2797 this.m_nMaxVal = args.max || 100; 2798 this.m_nIncrement = args.increment || 1; 2799 this.m_nValue = args.value || 0; 2800 this.m_fnOnChange = args.fnOnChange || function( value, bInDrag ) {}; 2801 2802 this.m_$Grabber.css( 'position', 'absolute' ); 2803 this.CalcRatios(); 2804 this.SetValue( this.m_nValue ); 2805 2806 var fnGetPageX = function( event ) 2807 { 2808 if ( event.type.indexOf( 'touch' ) == 0 ) 2809 { 2810 var TouchEvent = event.originalEvent; 2811 var rgTouches = TouchEvent ? TouchEvent.touches : null; 2812 if ( !rgTouches || rgTouches.length < 1 ) 2813 return event.pageX || 0; //probably wrong 2814 return rgTouches[0].pageX || 0; 2815 } 2816 else 2817 { 2818 return event.pageX || 0; 2819 } 2820 }; 2821 2822 var _this = this; 2823 this.m_$Container.on( 'mousedown touchstart', function( event ) { 2824 _this.CalcRatios(); 2825 2826 if ( !_this.m_$Grabber.is( event.target ) ) 2827 { 2828 // jump the grabber to this position and start the drag 2829 var nPosition = fnGetPageX( event ) - _this.m_$Container.offset().left; 2830 // we want the grabber centered under the mosue if possible 2831 nPosition -= Math.floor( _this.m_$Grabber.width() / 2 ); 2832 var nNewPosition = Math.min( Math.max( nPosition, 0 ), _this.m_nWidth ); 2833 2834 _this.m_$Grabber.css('left', nNewPosition + 'px' ); 2835 _this.m_nValue = nNewPosition / _this.m_flRatio; 2836 if ( _this.m_nIncrement > 1 && _this.m_nValue < _this.m_nMaxVal ) { 2837 //_this.m_nValue += _this.m_nIncrement - 1; 2838 _this.m_nValue = parseInt(_this.m_nValue / _this.m_nIncrement) * _this.m_nIncrement; 2839 } 2840 2841 _this.m_fnOnChange( _this.m_nValue, true ); 2842 } 2843 var nInitialPosition = parseInt( _this.m_$Grabber.css('left') ); 2844 var nStartDragX = fnGetPageX( event ); 2845 2846 $J(document).on( 'mousemove.CSlider touchmove.CSlider', function( event ) { 2847 var nDelta = fnGetPageX( event ) - nStartDragX; 2848 2849 var nNewPosition = Math.min( Math.max( nInitialPosition + nDelta, 0 ), _this.m_nWidth ); 2850 2851 _this.m_$Grabber.css('left', nNewPosition + 'px' ); 2852 _this.m_nValue = nNewPosition / _this.m_flRatio; 2853 if ( _this.m_nIncrement > 1 && _this.m_nValue < _this.m_nMaxVal ) { 2854 //_this.m_nValue += _this.m_nIncrement - 1; 2855 _this.m_nValue = parseInt(_this.m_nValue / _this.m_nIncrement) * _this.m_nIncrement; 2856 } 2857 2858 _this.m_fnOnChange( _this.m_nValue, true ); 2859 }); 2860 $J(document).on( 'mouseup.CSlider touchend.CSlider', function( event ) { 2861 $J(document).off('.CSlider'); 2862 _this.m_fnOnChange( _this.m_nValue, false ); 2863 }); 2864 2865 event.preventDefault(); 2866 }); 2867} 2868 2869CSlider.prototype.CalcRatios = function() 2870{ 2871 var nGrabberWidth = this.m_$Grabber.width(); 2872 this.m_nWidth = this.m_$Container.width() - nGrabberWidth; 2873 2874 this.m_flRatio = this.m_nWidth / ( this.m_nMaxVal - this.m_nMinVal ); 2875}; 2876 2877CSlider.prototype.SetValue = function( nValue, nAnimationSpeed ) 2878{ 2879 this.m_nValue = Math.min( Math.max( nValue, this.m_nMinVal ), this.m_nMaxVal ); 2880 2881 var nNewPosition = Math.floor( ( this.m_nValue - this.m_nMinVal ) * this.m_flRatio ); 2882 2883 this.m_$Grabber.stop(); 2884 if ( nAnimationSpeed ) 2885 this.m_$Grabber.animate( {left: nNewPosition }, nAnimationSpeed ); 2886 else 2887 this.m_$Grabber.css( 'left', nNewPosition + 'px' ); 2888}; 2889 2890CSlider.prototype.GetValue = function() 2891{ 2892 return this.m_nValue; 2893}; 2894 2895CSlider.prototype.GetMin = function() 2896{ 2897 return this.m_nMinVal; 2898}; 2899 2900CSlider.prototype.GetMax = function() 2901{ 2902 return this.m_nMaxVal; 2903}; 2904 2905CSlider.prototype.SetRange = function( nMinVal, nMaxVal, nValue ) 2906{ 2907 this.m_nMinVal = nMinVal; 2908 this.m_nMaxVal = nMaxVal; 2909 if ( typeof nValue != 'undefined' ) 2910 this.m_nValue = nValue; 2911 this.CalcRatios(); 2912 this.SetValue( this.m_nValue ); 2913}; 2914 2915CSlider.prototype.SetIncrement = function( nIncrement ) 2916{ 2917 this.m_nIncrement = nIncrement; 2918}; 2919 2920function CScrollSlider( $Scroll, $Container, $Grabber, args ) 2921{ 2922 this.m_$Scroll = $Scroll; 2923 this.m_$SliderCtn = $Container; 2924 2925 var $Slider = $Container.children('.slider'); 2926 2927 this.m_Slider = new CSlider( $Slider.length ? $Slider : $Container, $Grabber, { fnOnChange: $J.proxy( this.OnSliderChange, this )} ); 2928 2929 this.m_$Scroll.css('overflowX', 'scroll'); 2930 // add momentum on iOS 2931 this.m_$Scroll.css('-webkit-overflow-scrolling', 'touch'); 2932 2933 var _this = this; 2934 var bDidInitialRecalc = false;
2935 this.m_$Scroll.on( 'scroll.ScrollSlider', function() { 2936 if ( !bDidInitialRecalc ) 2937 { 2938 // we don't want to do this all the time, but on some browsers the first call to 2939 // update ranges is too early and values don't compute correctly. 2940 _this.UpdateRanges(); 2941 bDidInitialRecalc = true; 2942 } 2943 _this.m_Slider.SetValue( _this.m_$Scroll.scrollLeft() ); 2944 }); 2945 $J(window).on('resize.ScrollSlider', function() { 2946 _this.UpdateRanges(); 2947 } ); 2948 this.m_$Scroll.on( 'v_contentschanged', function() { 2949 _this.UpdateRanges(); 2950 } ); 2951 2952 this.UpdateRanges(); 2953} 2954 2955CScrollSlider.prototype.SetValue = function( value, nAnimationSpeed ) { 2956 if ( nAnimationSpeed ) 2957 { 2958 this.m_$Scroll.stop().animate( {'scrollLeft': value }, nAnimationSpeed ); 2959 } 2960 else 2961 { 2962 this.m_$Scroll.stop().scrollLeft( value ); 2963 } 2964}; 2965 2966CScrollSlider.prototype.GetValue = function() { 2967 return this.m_Slider.GetValue(); 2968}; 2969 2970CScrollSlider.prototype.UpdateRanges = function() 2971{ 2972 var nParentWidth = this.m_$Scroll.width(); 2973 var nScrollWidth = this.m_$Scroll[0].scrollWidth; 2974 2975 if ( nScrollWidth <= nParentWidth ) 2976 { 2977 this.m_$SliderCtn.hide(); 2978 } 2979 else 2980 { 2981 this.m_Slider.SetRange( 0, nScrollWidth - nParentWidth, this.m_$Scroll.scrollLeft() ); 2982 this.m_$SliderCtn.show(); 2983 } 2984}; 2985 2986CScrollSlider.prototype.OnSliderChange = function( value, bInDrag ) 2987{ 2988 this.m_$Scroll.stop().scrollLeft( value ); 2989}; 2990 2991function IsValidEmailAddress( email ) 2992{ 2993 var email_regex = /^(("[\w-\s]+")|([\w-]+(?:\.[\w-]+)*)|("[\w-\s]+")([\w-]+(?:\.[\w-]+)*))(@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$)|(@\[?((25[0-5]\.|2[0-4][0-9]\.|1[0-9]{2}\.|[0-9]{1,2}\.))((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\.){2}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\]?$)/i; 2994 return ( email != '' && email_regex.test(email) ); 2995} 2996 2997 2998(function ($) { 2999 $.deparam = function (params, coerce) { 3000 var obj = {}, 3001 coerce_types = { 'true': !0, 'false': !1, 'null': null }; 3002 3003 $.each(params.replace(/\+/g, ' ').split('&'), function (j,v) { 3004 var param = v.split('='), 3005 key = decodeURIComponent(param[0]), 3006 val, 3007 cur = obj, 3008 i = 0, 3009 3010 keys = key.split(']['), 3011 keys_last = keys.length - 1; 3012 3013 if (/\[/.test(keys[0]) && /\]$/.test(keys[keys_last])) { 3014 keys[keys_last] = keys[keys_last].replace(/\]$/, ''); 3015 3016 keys = keys.shift().split('[').concat(keys); 3017 3018 keys_last = keys.length - 1; 3019 } else { 3020 keys_last = 0; 3021 } 3022 3023 if (param.length === 2) { 3024 val = decodeURIComponent(param[1]); 3025 3026 if (coerce) { 3027 val = val && !isNaN(val) ? +val // number 3028 : val === 'undefined' ? undefined // undefined 3029 : coerce_types[val] !== undefined ? coerce_types[val] // true, false, null 3030 : val; // string 3031 } 3032 3033 if ( keys_last ) { 3034 for (; i <= keys_last; i++) { 3035 key = keys[i] === '' ? cur.length : keys[i]; 3036 3037 if ( typeof cur != 'object' ) 3038 break; 3039 3040 cur = cur[key] = i < keys_last 3041 ? cur[key] || (keys[i+1] && isNaN(keys[i+1]) ? {} : []) 3042 : val; 3043 } 3044 3045 } else { 3046 3047 if ($.isArray(obj[key])) { 3048 obj[key].push( val ); 3049 3050 } else if (obj[key] !== undefined) { 3051 obj[key] = [obj[key], val]; 3052 3053 } else { 3054 obj[key] = val; 3055 } 3056 } 3057 3058 } else if (key) { 3059 obj[key] = coerce 3060 ? undefined 3061 : ''; 3062 } 3063 }); 3064 3065 return obj; 3066 }; 3067})(jQuery); 3068 3069/** 3070 * Generic search field that handles: 3071 * 1.) Showing default text if the input field is empty 3072 * 2.) When the input field gets focus, the text field clears 3073 * 3.) Adding CSS class to the input field when it is default text 3074 * 4.) When the user presses return/enter in the field 3075 * 3076 * Call ClearIfDefaultValue() before submitting the form 3077 */ 3078function SearchFieldWithText( elemID, defaultSearchText, onEnterFunc, defaultTextCSSClass ) 3079{ 3080 var elem = $( elemID ); 3081 3082 this.elem = elem; 3083 this.defaultSearchText = defaultSearchText; 3084 this.defaultTextCSSClass = defaultTextCSSClass;
3085 this.onEnterFunc = onEnterFunc; 3086 3087 Event.observe( elem, 'click', this.handleClickOrFocus.bind(this)); 3088 Event.observe( elem, 'focus', this.handleClickOrFocus.bind(this)); 3089 Event.observe( elem, 'blur', this.handleBlur.bind(this)); 3090 Event.observe( elem, 'keypress', this.handleKeypress.bind(this)); 3091 Event.observe( elem.form, 'submit', this.ClearIfDefaultValue.bind(this)); 3092 3093 this.handleBlur(); 3094} 3095 3096SearchFieldWithText.prototype.handleClickOrFocus = function() 3097{ 3098 if ( this.elem.value == this.defaultSearchText ) 3099 { 3100 this.elem.value = ''; 3101 if ( this.defaultTextCSSClass ) 3102 this.elem.removeClassName( this.defaultTextCSSClass ); 3103 } 3104}; 3105 3106SearchFieldWithText.prototype.handleBlur = function() 3107{ 3108 if ( this.elem.value == '') 3109 { 3110 this.elem.value = this.defaultSearchText; 3111 if ( this.defaultTextCSSClass ) 3112 this.elem.addClassName( this.defaultTextCSSClass ); 3113 } 3114}; 3115 3116SearchFieldWithText.prototype.handleKeypress = function() 3117{ 3118 if ( !this.onEnterFunc ) 3119 return; 3120 3121 var keyCode = null; 3122 if( event.which ) 3123 { 3124 keyCode = event.which; 3125 } 3126 else if( event.keyCode ) 3127 { 3128 keyCode = evt.keyCode; 3129 } 3130 if ( 13 == keyCode ) 3131 { 3132 this.onEnterFunc(); 3133 } 3134}; 3135 3136SearchFieldWithText.prototype.ClearIfDefaultValue = function() 3137{ 3138 if ( this.elem.value == this.defaultSearchText ) 3139 { 3140 this.elem.value = ''; 3141 } 3142}; 3143 3144 3145function CWebAPI( strWebAPIHost, strSecureWebAPIHost, strOAuth2Token ) 3146{ 3147 this.m_strHost = strWebAPIHost; 3148 this.m_strSecureWebAPIHost = strSecureWebAPIHost; 3149 this.m_strOAuth2Token = strOAuth2Token; 3150} 3151 3152CWebAPI.prototype.BuildURL = function( strInterface, strMethod, bSecure, strVersion ) 3153{ 3154 if ( !strVersion ) 3155 strVersion = 'v0001'; 3156 3157 var strURL = ( bSecure ? this.m_strSecureWebAPIHost : this.m_strHost ); 3158 strURL += strInterface + '/' + strMethod + '/' + strVersion + '/'; 3159 3160 return strURL; 3161}; 3162 3163CWebAPI.prototype.ExecJSONP = function( strInterface, strMethod, rgParams, bSecure, strVersion, cTimeoutSecs ) 3164{ 3165 rgParams.access_token = this.m_strOAuth2Token; 3166 3167 var rgJQueryParams = { 3168 url: this.BuildURL( strInterface, strMethod, bSecure, strVersion ), 3169 dataType: 'jsonp', 3170 jsonp: 'jsonp', data: rgParams 3171 }; 3172 3173 if ( cTimeoutSecs ) 3174 rgJQueryParams['timeout'] = cTimeoutSecs * 1000; 3175 3176 return $J.ajax( rgJQueryParams ); 3177 3178 /* 3179 // using jsonp plugin instead of built-in jquery jsonp handling. this library supposedly 3180 // works around the firefox "waiting for host..." issue, but it doesn't work. 3181 return $J.jsonp({ 3182 url: this.BuildURL( strInterface, strMethod, bSecure, strVersion ), 3183 callbackParameter: 'jsonp', data: rgParams 3184 }); 3185 */ 3186}; 3187 3188// Send a "beacon", which is specifically intended for use in OnUnload events (as outsdanding AJAX/JSONP requests may be cancelled) 3189// as of sept 2015, only chrome and firefox support this, no iOS or IE support. Check for support before calling. 3190// Beacons are (per spec) always a POST request, and always include CORS headers. WebAPI respondes properly to CORS for Valve domains. 3191CWebAPI.prototype.ExecBeacon = function( strInterface, strMethod, rgParams, bSecure, strVersion ) 3192{ 3193 rgParams.access_token = this.m_strOAuth2Token; 3194 3195 var fdParams = new FormData(); 3196 for ( var key in rgParams ) 3197 fdParams.append( key, rgParams[key] ); 3198 3199 navigator.sendBeacon( this.BuildURL( strInterface, strMethod, bSecure, strVersion ), fdParams ); 3200}; 3201 3202 3203CWebAPI.prototype.ExecPOST = function( strInterface, strMethod, rgParams, bSecure, strVersion ) 3204{ 3205 rgParams.access_token = this.m_strOAuth2Token; 3206 rgParams.format = 'json'; 3207 3208 return $J.ajax( { 3209 url: this.BuildURL( strInterface, strMethod, bSecure, strVersion ), 3210 type: 'POST', 3211 data: rgParams 3212 }); 3213}; 3214 3215// register some events to dismiss popup (ie, user clicking elsewhere on the window, escape) 3216// cleans up event binds afterwards. clicks to children of "elemIgnore" will not dismiss popup 3217function RegisterPopupDismissal( dismissFunc, elemIgnore ) 3218{ 3219 var $Ignore = $JFromIDOrElement( elemIgnore ); 3220 // delay registration by one frame so that we don't catch the event that triggered this popup. 3221 window.setTimeout( function() { 3222 $J(document).on('click.RegisterPopupDismissal keydown.RegisterPopupDismissal', function (event) { 3223 3224 if (event.keyCode && event.keyCode != 27 /* KEY_ESC */) { 3225 return; 3226 } 3227 var elem = $J(event.target); 3228 3229 if ( $Ignore.length && $J.contains( $Ignore[0], elem[0] ) ) 3230 return; 3231 3232 dismissFunc(); 3233 UnregisterPopupDismissal( elemIgnore ); 3234 }); 3235 3236 // support gamepad B button to dismiss 3237 $Ignore.on( 'vgp_oncancel', function ( event ) { 3238 dismissFunc(); 3239 UnregisterPopupDismissal( elemIgnore ); 3240 3241 event.stopPropagation(); 3242 event.preventDefault(); 3243 }); 3244 }, 1 ); 3245} 3246 3247/* Cleanup function for the above RegisterPopupDismissal() 3248 This needs to be accessible to popup owners so they can call this if they're closing the popup */ 3249function UnregisterPopupDismissal( elemIgnore ) 3250{ 3251 $J(document).off( '.RegisterPopupDismissal' ); 3252 3253 if ( elemIgnore ) 3254 { 3255 // unregister gamepad B button press handling 3256 $JFromIDOrElement( elemIgnore ).off( 'vgp_oncancel' ); 3257 } 3258} 3259 3260 3261function ShowMenu( elemLink, elemPopup, align, valign, bLinkHasBorder ) 3262{ 3263 var $Popup = $JFromIDOrElement(elemPopup); 3264 3265 // If we're in tablet screen mode put the menu content in a modal dialog
3266 var bUseGamepadScreenMode = window.UseGamepadScreenMode && window.UseGamepadScreenMode(); 3267 if ( bUseGamepadScreenMode ) 3268 { 3269 // detach this element and when the dialog closes re-attach to the document body 3270 $Popup.detach(); 3271 var originalPopupPosition = $Popup.css( 'position' ); 3272 $Popup.css( 'position', 'static' ); // clear possible absolute positioning 3273 $Popup.show(); 3274 3275 ShowDialog( '', $Popup ).always( 3276 function() { 3277 // save it away again for later 3278 $Popup.hide(); 3279 $Popup.css( 'position', originalPopupPosition ); // restore positioning 3280 $J( document.body ).append( $Popup ); 3281 } 3282 ); 3283 3284 return; 3285 } 3286 3287 var $Link = $JFromIDOrElement(elemLink); 3288 if ( $Link.hasClass('focus') ) 3289 { 3290 HideMenu( elemLink, elemPopup ); 3291 return; 3292 } 3293 3294 AlignMenu( $Link, $Popup, align, valign, bLinkHasBorder ); 3295 3296 ShowWithFade( $Popup ); 3297 $Link.addClass('focus'); 3298 RegisterPopupDismissal( function() { HideMenu( elemLink, elemPopup ); }, $Popup ); 3299 3300 // If we use this control on gamepad (haven't found an example yet we're going to keep) then we'll need to 3301 // add calling: GPOnShowingModalWindow( $Popup[0] ). See shared_responsive_adapter.js for example 3302} 3303 3304function HideMenu( elemLink, elemPopup ) 3305{ 3306 var $Link = $JFromIDOrElement(elemLink); 3307 var $Popup = $JFromIDOrElement(elemPopup); 3308 3309 $Link.data( 'menu-active', false ); 3310 HideWithFade( $Popup ); 3311 $Link.removeClass( 'focus' ); 3312 3313 UnregisterPopupDismissal( elemPopup ); 3314} 3315 3316function HideMenuFast( elemLink, elemPopup ) 3317{ 3318 var $Link = $JFromIDOrElement(elemLink); 3319 var $Popup = $JFromIDOrElement(elemPopup); 3320 3321 $Popup.hide(); 3322 $Link.removeClass( 'focus' ); 3323 3324 UnregisterPopupDismissal( elemPopup ); 3325} 3326 3327 3328function RegisterFlyout( elemLink, elemPopup, align, valign, bLinkHasBorder ) 3329{ 3330 var $Link = $JFromIDOrElement( elemLink ); 3331 var $Popup = $JFromIDOrElement( elemPopup ); 3332 3333 $Link.on( 'mouseenter', function( event ) { 3334 FlyoutMenu( $Link, $Popup, align, valign, bLinkHasBorder ); 3335 }); 3336 3337 $Link.add( $Popup ).on( 'mouseleave', function( event ) { 3338 HideFlyoutMenu( event, $Link, $Popup ); 3339 }); 3340} 3341 3342function UseSmallScreenMenu() 3343{ 3344 // the new mobile app uses the small screen menu regardless of screen size 3345 return ( window.UseSmallScreenMode && window.UseSmallScreenMode() ) || ( window.UseNewMobileAppMode && window.UseNewMobileAppMode() ); 3346} 3347 3348function FlyoutMenu( elemLink, elemPopup, align, valign, bLinkHasBorder, elemAlternateAlignTo ) 3349{ 3350 var $Link = $JFromIDOrElement(elemLink); 3351 var $Popup = $JFromIDOrElement(elemPopup); 3352 3353 if ( !$Popup.is(':visible') || $Popup.css('opacity') < 1.0 ) 3354 { 3355 if ( $Popup.hasClass( 'responsive_slidedown') && UseSmallScreenMenu() ) 3356 { 3357 $Popup.stop().slideDown(); 3358 } 3359 else 3360 { 3361 // Responsive mode calls slideDown, which applies position: relative to the element and we need to clear it. 3362 // This will only happen if the user interacted with the menu in the responsive mode, then resized 3363 // their window to be large and hovered the elements. 3364 $Popup.css( { position: '' } ); 3365 AlignMenu( $Link, $Popup, align, valign, bLinkHasBorder, elemAlternateAlignTo || null ); 3366 ShowWithFade( $Popup ); 3367 } 3368 3369 $Link.addClass('focus'); 3370 } 3371 3372} 3373 3374function HideFlyoutMenu( event, elemLink, elemPopup ) 3375{ 3376 var $Link = $JFromIDOrElement(elemLink); 3377 var $Popup = $JFromIDOrElement(elemPopup); 3378 3379 if ( !$Link.hasClass('focus') ) 3380 return; 3381 3382 if ( event ) 3383 { 3384 var reltarget = $J( event.relatedTarget ); 3385 if ( !reltarget.length || 3386 ( $Link.length && $J.contains( $Link[0], reltarget[0] ) ) || 3387 ( $Popup.length && $J.contains( $Popup[0], reltarget[0] ) ) || 3388 $Link.is( reltarget ) ) 3389 return; 3390 } 3391 // start hiding in a little bit, have to let the fade in animation start before we can cancel it 3392 3393 if ( $Popup.hasClass( 'responsive_slidedown') && UseSmallScreenMenu() ) 3394 $Popup.stop().slideUp(); 3395 else 3396 window.setTimeout( function() { HideWithFade( $Popup ); }, 33 ); 3397 3398 $Link.removeClass('focus'); 3399 $J(document).off('.FlyoutDismiss'); 3400} 3401 3402function AlignMenu( elemLink, elemPopup, align, valign, bLinkHasBorder, elemAlternateAlignTo ) 3403{ 3404 // alternate align to is used to align the genre menu on the store; it's only used for horizontal alignment, 3405 // trying to align to it vertically messes up responsive view. 3406 var $LinkVertical = $JFromIDOrElement(elemLink); 3407 var $LinkHorizontal = elemAlternateAlignTo ? $JFromIDOrElement(elemAlternateAlignTo) : $LinkVertical; 3408 3409 var align = align ? align : 'left'; 3410 // flip alignments for RTL 3411 if ( $LinkVertical?.[0]?.matches( ':dir(rtl)' ) ?? document.dir === "rtl" ) 3412 { 3413 if ( align === 'left' ) 3414 { 3415 align = 'right'; 3416 } 3417 else if ( align === 'right' ) 3418 { 3419 align = 'left'; 3420 } 3421 else if ( align === 'leftsubmenu' ) 3422 { 3423 align = 'rightsubmenu'; 3424 } 3425 else if ( align === 'rightsubmenu' ) 3426 { 3427 align = 'leftsubmenu'; 3428 } 3429 } 3430 3431 var $Popup = $JFromIDOrElement(elemPopup); 3432 3433 3434 var offsetLinkVertical = $LinkVertical.offset(); 3435 var offsetLinkHorizontal = $LinkHorizontal.offset(); 3436 var nWindowScrollTop = $J(window).scrollTop();
3437 var nViewportHeight = $J(window).height(); 3438 3439 var nLinkViewportTop = offsetLinkVertical.top - nWindowScrollTop; 3440 3441 // add a little bit of padding so we don't position it flush to an edge if possible 3442 var nPopupHeight = $Popup.height() + 8; 3443 3444 if ( !valign ) 3445 { 3446 //if there's not enough room between our spot and the top of the document, we definitely want to drop down 3447 if ( nWindowScrollTop + offsetLinkVertical.top < nPopupHeight ) 3448 { 3449 valign = 'bottom'; 3450 } 3451 else 3452 { 3453 var nSpaceAbove = nLinkViewportTop; 3454 var nSpaceBelow = nViewportHeight - ( nLinkViewportTop + $LinkVertical.height() ); 3455 //otherwise we only want to drop down if we've got enough space below us (measured based on view area) 3456 // or if there's not enough space above to pop in either direction and there's more space below 3457 if ( nSpaceBelow > nPopupHeight || ( nSpaceAbove < nPopupHeight && nSpaceBelow > nSpaceAbove ) ) 3458 valign = 'bottom'; 3459 else 3460 valign = 'top'; 3461 3462 } 3463 } 3464 3465 var borderpx = bLinkHasBorder ? 1 : 0; 3466 var shadowpx = $Popup.hasClass( 'popup_block_new' ) ? 0 : 12; 3467 var offsetLeft = 0; 3468 3469 if ( align == 'left' ) 3470 { 3471 //elemPopup.style.left = ( elemLink.positionedOffset()[0] - 12 ) + 'px'; 3472 offsetLeft = -shadowpx - borderpx; 3473 } 3474 else if ( align == 'right' ) 3475 { 3476 //elemPopup.style.left = ( elemLink.positionedOffset()[0] + elemLink.getWidth() - elemPopup.getWidth() + 13 ) + 'px'; 3477 offsetLeft = $LinkHorizontal.outerWidth() - $Popup.outerWidth() + shadowpx + borderpx; 3478 } 3479 else if ( align == 'leftsubmenu' ) 3480 { 3481 //elemPopup.style.left = ( elemLink.positionedOffset()[0] - elemPopup.getWidth() + 12 ) + 'px'; 3482 offsetLeft = -$Popup.outerWidth() + shadowpx - borderpx; 3483 } 3484 else if ( align == 'rightsubmenu' ) 3485 { 3486 //elemPopup.style.left = ( elemLink.positionedOffset()[0] + elemLink.getWidth() - 12 ) + 'px'; 3487 offsetLeft = $LinkHorizontal.outerWidth() - shadowpx + 2 * borderpx; 3488 } 3489 3490 var offsetTop = 0; 3491 if ( valign == 'bottom' ) 3492 { 3493 //elemPopup.style.top = ( elemLink.positionedOffset()[1] + elemLink.getHeight() - 12 ) + 'px'; 3494 offsetTop = $LinkVertical.outerHeight() - shadowpx; 3495 } 3496 else if ( valign == 'top' ) 3497 { 3498 //elemPopup.style.top = ( elemLink.positionedOffset()[1] - elemPopup.getHeight() + 12 ) + 'px'; 3499 offsetTop = -$Popup.outerHeight() + shadowpx; 3500 } 3501 else if ( valign == 'bottomsubmenu' ) 3502 { 3503 //elemPopup.style.top = ( elemLink.positionedOffset()[1] - 12 ) + 'px'; 3504 offsetTop = -shadowpx; 3505 } 3506 3507 3508 var bPopupHidden = !$Popup.is(':visible'); 3509 3510 if ( bPopupHidden ) 3511 { 3512 // IE can't do this with display: none elements 3513 $Popup.css( 'visibility', 'hidden' ); 3514 $Popup.show(); 3515 } 3516 3517 $Popup.offset( { 3518 top: Math.max( offsetLinkVertical.top + offsetTop, 0 ), 3519 left: Math.max( offsetLinkHorizontal.left + offsetLeft, 0 ) 3520 }); 3521 3522 if ( bPopupHidden ) 3523 { 3524 // restore visibility 3525 $Popup.hide(); 3526 $Popup.css( 'visibility', 'visible' ); 3527 } 3528} 3529 3530function BindAutoFlyoutEvents() 3531{ 3532 var fnShowFlyout = function( $Tab, bIsHover, bTakeFocus ) 3533 { 3534 var $Content = $J('#' + $Tab.data('flyout') ); 3535 var bResponsiveSlidedownMenu = UseSmallScreenMenu() && $Content.hasClass('responsive_slidedown'); 3536 3537 if ( !$Content.length || $Content.data('flyout-event-running') || 3538 (bIsHover && bResponsiveSlidedownMenu ) ) 3539 return; 3540 3541 $Content.data( 'flyout-event-running', true ); 3542 window.setTimeout( function() { $Content.data('flyout-event-running', false ); }, 1 ); 3543 3544 if ( $Content.is(':visible') ) 3545 { 3546 if ( !bIsHover ) 3547 HideFlyoutMenu( null, $Tab, $Content ); 3548 3549 return; 3550 } 3551 3552 if ( !$Content.data('flyout-events-bound') ) 3553 { 3554 $Content.on('mouseleave.Flyout', function( e ) { 3555 if ( UseSmallScreenMenu() && $Content.hasClass('responsive_slidedown') ) 3556 return; 3557 3558 if ( $Tab.is( e.relatedTarget ) || $J.contains( $Tab[0], e.relatedTarget ) ) 3559 return; 3560 3561 HideFlyoutMenu( null, $Tab, $Content ); 3562 }); 3563 3564 $Content.add($Tab).on('vgp_oncancel', function( e ) { 3565 HideFlyoutMenu( null, $Tab, $Content ); 3566 if ( typeof GPNavFocusChild != 'undefined' ) 3567 GPNavFocusChild( $Tab ); 3568 }); 3569 3570 $Content.on('vgp_onblur', function( e ) { 3571 var node = e.originalEvent.detail.focusedNode; 3572 console.log( node ); 3573 if ( !node || !$J.contains( e.currentTarget, node.Element ) ) 3574 HideFlyoutMenu( null, $Tab, $Content ); 3575 }); 3576
3577 $Content.data('flyout-events-bound', true ); 3578 } 3579 3580 3581 FlyoutMenu( $Tab, $Content, $Tab.data('flyout-align'), $Tab.data('flyout-valign'), false, $Tab.data('flyout-align-to-element' ) ); 3582 3583 if ( bTakeFocus && typeof GPNavFocusChild !== 'undefined' ) 3584 { 3585 window.setTimeout( function() { 3586 console.log( 'Request focus in', $Content[0] ); 3587 GPNavFocusChild( $Content ); 3588 }, 5 ); 3589 } 3590 3591 if ( window.UseTouchFriendlyMode && window.UseTouchFriendlyMode() ) 3592 { 3593 window.setTimeout( function() { 3594 $J(document).on('click.FlyoutDismiss', function(e) { 3595 if ( $J.contains( $Content[0], e.target ) || $Content.is( e.target ) ) 3596 return; 3597 3598 HideFlyoutMenu( null, $Tab, $Content ); 3599 e.preventDefault(); 3600 }); 3601 }, 1 ); 3602 } 3603 }; 3604 3605 $J(document).on( 'mouseenter.Flyout click.Flyout', '.flyout_tab', function(e) { 3606 var $Tab = $J(this); 3607 var msDelay = $Tab.data('flyout-delay'); 3608 3609 var bIsHover = e.type == 'mouseenter'; 3610 3611 if ( !bIsHover || !msDelay ) 3612 { 3613 fnShowFlyout( $Tab, bIsHover ); 3614 } 3615 else if ( !$Tab.data( 'iFlyoutInterval' ) ) 3616 { 3617 var fnCleanupFlyoutInterval = function() { 3618 $Tab.removeData( 'iFlyoutInterval' ); 3619 $Tab.off( 'mouseleave.FlyoutCancel' ); 3620 } 3621 $Tab.on( 'mouseleave.FlyoutCancel', function(e) { 3622 window.clearTimeout( $Tab.data('iFlyoutInterval') ); 3623 fnCleanupFlyoutInterval(); 3624 }); 3625 $Tab.data( 'iFlyoutInterval', window.setTimeout( function() { 3626 fnCleanupFlyoutInterval(); 3627 fnShowFlyout( $Tab, bIsHover ); 3628 }, msDelay ) ); 3629 } 3630 3631 }); 3632 3633 $J(document).on('mouseleave.Flyout', '.flyout_tab', function(e) { 3634 var $Tab = $J(this); 3635 var $Content = $J('#' + $Tab.data('flyout') ); 3636 var bResponsiveSlidedownMenu = UseSmallScreenMenu() && $Content.hasClass('responsive_slidedown'); 3637 3638 if ( !$Content.length || $Content.data('flyout-event-running') || bResponsiveSlidedownMenu || 3639 $Content.is( e.relatedTarget ) || $J.contains( $Content[0], e.relatedTarget ) ) 3640 return; 3641 3642 if ( $Content.is(':visible') ) 3643 { 3644 HideFlyoutMenu( null, $Tab, $Content ); 3645 3646 return; 3647 } 3648 }); 3649 3650 $J(document).on('vgp_onok', '.flyout_tab', function( e ) { 3651 fnShowFlyout( $J(this), false, true ); 3652 e.stopPropagation(); 3653 }); 3654} 3655 3656function PollOnUserActionAfterInterval( strNamespace, nPollInterval, fnCallback, nPollImmediateInterval ) 3657{ 3658 var bCallbackInvoked = false; 3659 var tsLastUserAction = $J.now(); 3660 var fnInvokeCallbackOnNextUserAction; 3661 3662 var strEvents = ['touchstart','mousemove','focus','scroll',''].join( '.' + strNamespace + ' ' ); 3663 3664 var fnDoPoll = function() { 3665 if ( !bCallbackInvoked ) 3666 fnCallback(); 3667 bCallbackInvoked = true; 3668 fnInvokeCallbackOnNextUserAction(); 3669 }; 3670 3671 fnInvokeCallbackOnNextUserAction = function() { 3672 window.setTimeout( function() { 3673 bCallbackInvoked = false; 3674 if ( nPollImmediateInterval && $J.now() <= tsLastUserAction + nPollImmediateInterval ) 3675 { 3676 fnDoPoll(); 3677 } 3678 else 3679 { 3680 $J(window).on( strEvents, function() { 3681 $J(window ).off( '.' + strNamespace ); 3682 tsLastUserAction = $J.now(); 3683 fnDoPoll(); 3684 }); 3685 } 3686 }, nPollInterval ); 3687 }; 3688 3689 fnInvokeCallbackOnNextUserAction(); 3690} 3691 3692var DELAY_BETWEEN_NOTIFICATION_COUNT_POLLS_MS = 60 * 1000; 3693function EnableNotificationCountPolling() 3694{ 3695 PollOnUserActionAfterInterval( 'NotificationCountPoll', DELAY_BETWEEN_NOTIFICATION_COUNT_POLLS_MS, UpdateNotificationCounts ); 3696} 3697 3698function UpdateNotificationCounts() 3699{ 3700 if ( window.RefreshSteamNotifications ) 3701 window.RefreshSteamNotifications(); 3702} 3703 3704function PostToURLWithSession( url, rgParams ) 3705{ 3706 var $Form = $J('<form/>', {'action': url, 'method': 'POST' } ); 3707 3708 // site must set this js var or pass to this function 3709 if ( typeof g_sessionID != 'undefined' ) 3710 $Form.append( $J('<input/>', {'type': 'hidden', 'name': 'sessionid', 'value': g_sessionID } ) ); 3711 3712 if ( rgParams ) 3713 { 3714 for ( var name in rgParams ) 3715 $Form.append( $J('<input/>', {'type': 'hidden', 'name': name, 'value': rgParams[name] } ) ); 3716 } 3717 $Form.appendTo( 'body' ); 3718 $Form.submit(); 3719} 3720 3721 3722function ShowWithFade( elem, speed ) 3723{ 3724 var $Elem = $JFromIDOrElement(elem); 3725 3726 $Elem.stop(); 3727 $Elem.fadeTo( speed || 200, 1.0 ); //fadeTo rather than fadeIn in case it was already in a fade 3728} 3729 3730function HideWithFade( elem, speed ) 3731{ 3732 var $Elem = $JFromIDOrElement(elem); 3733 3734 $Elem.stop(); 3735 $Elem.fadeOut( speed || 200 ); 3736} 3737 3738 3739function LaunchWebChat( params, paramsFriendsUI ) 3740{
3741 var winChat = window.open( '', 'SteamWebChat', 'height=790,width=1015,resize=yes,scrollbars=yes' ); 3742 if ( !winChat ) 3743 { 3744 // popup blocked - this sometimes happens when chat is initiated from the store. just roll with it. 3745 return; 3746 } 3747 3748 var bNewPopup = false; 3749 var bCrossOrigin = false; 3750 3751 try { 3752 bNewPopup = (winChat.location == 'about:blank' ); 3753 } 3754 catch ( e ) 3755 { 3756 // cross-origin exception on http pages etc 3757 bCrossOrigin = true; 3758 } 3759 3760 if ( bNewPopup ) 3761 { 3762 if ( params ) 3763 SetValueLocalStorage( 'rgChatStartupParam', V_ToJSON( params ) ); 3764 3765 if ( paramsFriendsUI ) 3766 { 3767 var fnBoundMessageListener = function( event ) 3768 { 3769 if ( event.source == winChat && event.data == "FriendsUIReady" && 3770 event.origin == 'https://steamcommunity.com') 3771 { 3772 winChat.postMessage( paramsFriendsUI, 'https://steamcommunity.com/' ); 3773 window.removeEventListener( 'message', fnBoundMessageListener ); 3774 } 3775 }; 3776 window.addEventListener( 'message', fnBoundMessageListener ); 3777 } 3778 3779 // created a new window, set the url 3780 winChat.location = 'https://steamcommunity.com/chat/'; 3781 } 3782 else 3783 { 3784 if ( params && !bCrossOrigin && winChat.OnWebchatLaunchURL ) 3785 winChat.OnWebchatLaunchURL( params ); 3786 3787 if ( paramsFriendsUI ) 3788 winChat.postMessage( paramsFriendsUI, 'https://steamcommunity.com/' ); 3789 } 3790 winChat.focus(); 3791} 3792 3793function ShowSupportAlerts(url) 3794{ 3795 window.open( url, 'SupportAlerts', 'height=700,width=700,resize=yes,scrollbars=yes' ); 3796} 3797 3798function UnlockFamilyView( strURL ) 3799{ 3800 window.location = strURL; 3801} 3802 3803function LockFamilyView( bStore ) 3804{ 3805 3806 var urlFirst = ''; 3807 var urlSecond = ''; 3808 if ( bStore ) 3809 { 3810 urlFirst = 'https://store.steampowered.com/'; 3811 urlSecond = 'https://steamcommunity.com/'; 3812 } 3813 else 3814 { 3815 urlFirst = 'https://steamcommunity.com/'; 3816 urlSecond = 'https://store.steampowered.com/'; 3817 } 3818 3819 ShowConfirmDialog( 'à¸à¸¥à¸±à¸à¹à¸à¸¢à¸±à¸à¸¡à¸¸à¸¡à¸¡à¸à¸à¸à¸£à¸à¸à¸à¸£à¸±à¸§?', 3820 'à¸à¸¸à¸à¹à¸à¹à¹à¸à¸«à¸£à¸·à¸à¹à¸¡à¹à¸§à¹à¸²à¸à¸¸à¸à¸à¹à¸à¸à¸à¸²à¸£à¸à¸¥à¸±à¸à¹à¸à¸¢à¸±à¸à¸¡à¸¸à¸¡à¸¡à¸à¸à¸à¸£à¸à¸à¸à¸£à¸±à¸§?', 3821 'à¸à¸¥à¸±à¸à¹à¸à¸¢à¸±à¸à¸¡à¸¸à¸¡à¸¡à¸à¸à¸à¸£à¸à¸à¸à¸£à¸±à¸§?' 3822 ).done( function() { 3823 ShowBlockingWaitDialog( 'à¸à¸¥à¸±à¸à¹à¸à¸¢à¸±à¸à¸¡à¸¸à¸¡à¸¡à¸à¸à¸à¸£à¸à¸à¸à¸£à¸±à¸§?' ); 3824 3825 CrossDomainPost( urlFirst + '/parental/ajaxlock', {} ).then( function() { 3826 $J.when( 3827 CrossDomainPost( 'https://checkout.steampowered.com/parental/ajaxlock', {} ), 3828 CrossDomainPost( 'https://steam.tv/parental/ajaxlock', {} ), 3829 CrossDomainPost( urlSecond + 'parental/ajaxlock', {} ) 3830 ).always( function() 3831 { 3832 window.location = urlFirst; 3833 } ); 3834 }); 3835 } ); 3836} 3837 3838function setTimezoneCookies() 3839{ 3840 var now = new Date(); 3841 var expire = new Date(); 3842 3843 // One year expiration, this way we don't need to wait at least one page 3844 // load to have accurate timezone info each session, but only each time the user 3845 // comes with cleared cookies 3846 expire.setTime( now.getTime() + 3600000*24*365 ); 3847 var tzOffset = now.getTimezoneOffset() * -1 * 60; 3848 var isDST = 0; 3849 3850 var sameSite = ''; 3851 if ( window.location.protocol == 'https' ) 3852 sameSite = '; Secure; SameSite=None;' 3853 document.cookie = "timezoneOffset=" + tzOffset + "," + isDST + ";expires="+expire.toGMTString() + ";path=/" + sameSite; 3854} 3855 3856function FlushStyleChanges( element ) 3857{ 3858 $J( element ).css( 'opacity'); 3859} 3860 3861var k_EScrollbarDirectionVertical = 1; 3862var k_EScrollbarDirectionHorizontal = 2; 3863 3864window.VScrollbar = function( eleTarget, eleHandle, direction ) 3865{ 3866 this.m_eleHandle = eleHandle; 3867 this.m_eleTarget = eleTarget; 3868 3869 var instance = this; 3870 3871 var propOffset, propSize, propOuterSize, propDimension, propPage, directionInvert, propOffsetEvent; 3872 3873 if( direction == k_EScrollbarDirectionVertical) 3874 { 3875 propOffset = 'top'; 3876 propSize = 'height'; 3877 propOuterSize = 'outerHeight'; 3878 propDimension = 'y'; 3879 propPage = 'pageY'; 3880 propOffsetEvent = 'offsetY'; 3881 } else if( direction == k_EScrollbarDirectionHorizontal ) 3882 { 3883 propOffset = 'left'; 3884 propSize = 'width'; 3885 propOuterSize = 'outerWidth'; 3886 propDimension = 'x'; 3887 propPage = 'pageX'; 3888 propOffsetEvent = 'offsetX'; 3889 3890 this.m_eleTarget.first().css({'white-space': 'nowrap'}); 3891 } 3892
3893 // Set up some CSS properties we need 3894 this.m_eleHandle.css({position: 'absolute'}); 3895 if( this.m_eleHandle.parent().css('position') == 'static' ) 3896 this.m_eleHandle.parent().css({position: 'relative'}); // Needs to be relative or absolute, only set it if we didn't do it in CSS 3897 this.m_eleTarget.css({position: 'relative', float: 'left'}); 3898 this.m_eleTarget.css(propOffset, '0px'); 3899 this.m_eleTarget.addClass('animating'); 3900 this.m_eleTarget.parent().addClass('v_scrollbar_target'); 3901 this.m_eleTarget.parent().css({position: 'relative', overflow: 'hidden'}); 3902 this.m_nDesiredPosition = 0; 3903 this.m_flPercent = 0; 3904 3905 3906 var funcUpdate = function( bDisableTransitions ) 3907 { 3908 var bScrolled = true; 3909 if( instance.m_eleTarget[propSize]() - instance.m_eleTarget.parent()[propSize]() <= 0 ) 3910 { 3911 instance.m_eleHandle.parent().addClass('disabled'); 3912 instance.m_flPercent = 0; 3913 bScrolled = false; 3914 } else 3915 instance.m_eleHandle.parent().removeClass('disabled'); 3916 3917 if( instance.m_flPercent < 0 ) 3918 instance.m_flPercent = 0; 3919 3920 if( instance.m_flPercent > 1 ) 3921 instance.m_flPercent = 1; 3922 3923 3924 var percent = instance.m_flPercent; 3925 3926 if( bDisableTransitions ) 3927 instance.DisableTransitions(); 3928 3929 instance.m_nDesiredPosition = -percent * ( instance.m_eleTarget[propSize]() - instance.m_eleTarget.parent()[propSize]()); 3930 3931 // Update container 3932 instance.m_eleTarget[0].style[propOffset] = instance.m_nDesiredPosition + 'px'; 3933 3934 // Update scroll handle 3935 var handleMax = instance.m_eleHandle.parent()[propSize]() - instance.m_eleHandle[propOuterSize](); 3936 instance.m_eleHandle[0].style[propOffset] = ( percent * handleMax ) + 'px'; 3937 3938 if( bDisableTransitions ) 3939 instance.EnableTransitions(); 3940 3941 return bScrolled; 3942 }; 3943 3944 var funcMouseMove = function( event ) { 3945 var localDimension = event[propPage] - instance.m_eleHandle.parent().offset()[propOffset]; 3946 var localMax = instance.m_eleHandle.parent()[propSize]() - instance.m_eleHandle[propOuterSize](); 3947 3948 var percent = localDimension / localMax; 3949 instance.m_flPercent = percent; 3950 3951 if( funcUpdate(true) ) 3952 event.preventDefault(); 3953 3954 3955 }; 3956 3957 $J(eleTarget).bind('mousewheel DOMMouseScroll',function( event ){ 3958 var delta = event.originalEvent.wheelDelta || event.originalEvent.detail * -12; 3959 3960 var localY = instance.m_nDesiredPosition * -1; 3961 var localMax = instance.m_eleTarget[propOuterSize]() - instance.m_eleTarget.parent()[propSize](); 3962 3963 if( localY <= 0 && delta > 0 || localY == localMax && delta < 0 ) 3964 return; 3965 3966 localY -= delta; 3967 3968 if( localY < 0 ) localY = 0; 3969 if( localY > localMax ) localY = localMax; 3970 3971 var percent = localY / localMax; 3972 3973 instance.m_flPercent = percent; 3974 3975 if( funcUpdate() ) 3976 event.preventDefault(); 3977 }); 3978 3979 $J(eleHandle.parent()).bind('click',function( event ){ 3980 var localY = instance.m_eleTarget.position()[propOffset] * -1; 3981 var localMax = instance.m_eleTarget[propOuterSize]() - instance.m_eleTarget.parent()[propSize](); 3982 3983 var step = instance.m_eleTarget.parent()[propSize](); 3984 3985 if( event[propOffsetEvent] < instance.m_eleHandle.position()[propOffset] ) 3986 step *= -1; 3987 3988 localY += step; 3989 3990 if( localY < 0 ) localY = 0; 3991 if( localY > localMax ) localY = localMax; 3992 3993 var percent = localY / localMax; 3994 3995 instance.m_flPercent = percent; 3996 3997 if( funcUpdate() ) 3998 event.preventDefault(); 3999 }); 4000 4001 eleHandle.mousedown(function( event ){ 4002 $J(window).bind('mousemove.scroll', funcMouseMove); 4003 event.stopPropagation(); 4004 }); 4005 4006 eleHandle.click(function( event ){ 4007 event.stopPropagation(); 4008 }); 4009 4010 $J(window).mouseup(function( event ){ 4011 $J(window).unbind('mousemove.scroll'); 4012 event.stopPropagation(); 4013 }); 4014 4015 // Resets scroll position to 0 and updates the window. Useful when adding/removing elements or resizing the scroll area 4016 this.Reset = function() { 4017 instance.m_flPercent = 0; 4018 4019 return funcUpdate(true); 4020 }; 4021 4022 // Ensures target element is visible. This is taken from position() (NOT OFFSET) so the element must be a direct child of the scroll area 4023 this.EnsureVisible = function( ele ) { 4024 var $ele = $J(ele); 4025 var eleSize = $ele[propOuterSize](true); 4026 var minEdge = $ele.position()[propOffset] - (eleSize * 0.5); 4027 var maxEdge = minEdge + (eleSize * 2); 4028 4029 4030 var viewportMax = instance.m_eleTarget[propOuterSize]() - instance.m_eleTarget.parent()[propSize](); 4031 var viewportSize = instance.m_eleTarget.parent()[propSize](); 4032 var viewportPosition = instance.m_eleTarget.position()[propOffset] * -1; 4033 4034 if( viewportPosition > minEdge ) 4035 instance.m_flPercent = minEdge / viewportMax; 4036 else if( ( viewportPosition + viewportSize ) < maxEdge ) 4037 instance.m_flPercent = ( maxEdge - viewportSize ) / viewportMax; 4038 4039 return funcUpdate(); 4040 }; 4041 4042 this.EnableTransitions = function() { 4043 instance.m_eleTarget.addClass('animating'); 4044 }; 4045 4046 this.DisableTransitions = function() { 4047 instance.m_eleTarget.removeClass('animating'); 4048 }; 4049 4050 funcUpdate(true); 4051 4052 4053}; 4054 4055function InitAutoComplete( elInput, fnSearch, fnOnChange ) 4056{ 4057 return new CAutoComplete( elInput, fnSearch, fnOnChange ); 4058} 4059 4060function CAutoComplete( elInput, fnSearch, fnOnChange ) 4061{ 4062 this.m_bSuppresseNextKeyUp = false;
4063 this.m_hSearchTimeout = 0; 4064 this.m_strLastSearch = ''; 4065 4066 this.m_$Input = $J( elInput ); 4067 this.m_fnSearch = fnSearch; 4068 this.m_fnOnChange = fnOnChange; 4069 4070 var strName = this.m_$Input.attr( 'name' ); 4071 if ( !strName ) 4072 strName = 'unknown'; 4073 4074 strName = strName + '_autocomplete'; 4075 4076 this.m_$Popup = $J ( '<div class="' + strName + '"></div>' ); 4077 this.m_$Popup.css( 'width', this.m_$Input.outerWidth() + 'px' ); 4078 this.m_$Input.after( this.m_$Popup ); 4079 this.m_$Popup.hide(); 4080 4081 var _this = this; 4082 this.m_$Input.on( 'keydown.autocomplete', function( event ) { _this.OnInputKeyDown( event ); } ); 4083 this.m_$Input.on( 'keyup.autocomplete', function( event ) { _this.OnInputKeyUp( event ); } ); 4084 this.m_$Input.on( 'blur.autocomplete', function( event ) { _this.OnInputBlur( event ); } ); 4085 this.m_$Input.on( 'change input paste', function( event ) { _this.OnInputChange( event ); } ); 4086 4087 this.m_$Popup.on( 'mousedown.autocomplete', function( event ) { _this.m_$Popup.data( 'mousedown', true ); } ); 4088 this.m_$Popup.on( 'mouseup.autocomplete', function( event ) { _this.m_$Popup.data( 'mousedown', false ); } ); 4089} 4090 4091CAutoComplete.KEY_ENTER = 13; 4092CAutoComplete.KEY_UP = 38; 4093CAutoComplete.KEY_DOWN = 40; 4094 4095CAutoComplete.prototype.OnInputChange = function( event ) 4096{ 4097 if ( this.m_fnOnChange ) 4098 this.m_fnOnChange( this.m_$Input, null ); 4099 4100 if ( this.m_hSearchTimeout == 0 ) 4101 { 4102 var _this = this; 4103 var fnDoSearch = function() 4104 { 4105 _this.m_hSearchTimeout = 0; 4106 var strSearch = _this.m_$Input.val(); 4107 if ( strSearch == _this.m_strLastSearch ) 4108 return; 4109 4110 _this.m_strLastSearch = strSearch; 4111 _this.m_fnSearch( _this.m_$Input, strSearch, function( $Contents ) { _this.SetPopupContents( $Contents ); } ); 4112 }; 4113 4114 this.m_hSearchTimeout = window.setTimeout( fnDoSearch, 300 ); 4115 } 4116} 4117 4118CAutoComplete.prototype.SetPopupContents = function( $Contents ) 4119{ 4120 if ( !$Contents ) 4121 { 4122 this.ClosePopup(); 4123 return; 4124 } 4125 4126 this.m_$Popup.html( '' ); 4127 this.m_$Popup.append( $Contents ); 4128 4129 var _this = this; 4130 this.m_$Popup.children().on( 'click.autocomplete', function( event ) { _this.SelectSuggestion( $J( this ) ); } ); 4131 this.m_$Popup.show(); 4132} 4133 4134CAutoComplete.prototype.OnInputBlur = function( event ) 4135{ 4136 if ( !this.m_$Popup.data( 'mousedown' ) ) 4137 this.ClosePopup(); 4138} 4139 4140CAutoComplete.prototype.SelectSuggestion = function( $Suggestion ) 4141{ 4142 if ( $Suggestion.length == 0 ) 4143 return; 4144 4145 this.m_$Input.val( $Suggestion.data( 'suggestion' ) ); 4146 if ( this.m_fnOnChange ) 4147 this.m_fnOnChange( this.m_$Input, $Suggestion ); 4148 4149 this.ClosePopup(); 4150 this.m_$Input.focus(); 4151} 4152 4153CAutoComplete.prototype.ClosePopup = function() 4154{ 4155 if ( this.m_hSearchTimeout != 0 ) 4156 { 4157 window.clearTimeout( this.m_hSearchTimeout ); 4158 this.m_hSearchTimeout = 0; 4159 } 4160 4161 this.m_strLastSearch = ''; 4162 this.m_$Popup.hide(); 4163} 4164 4165CAutoComplete.prototype.OnInputKeyDown = function( event ) 4166{ 4167 var $CurSuggestion = this.m_$Popup.children('.focus'); 4168 var $NewSuggestion = $J(); 4169 4170 if ( event.keyCode == CAutoComplete.KEY_ENTER ) 4171 { 4172 this.SelectSuggestion( $CurSuggestion ); 4173 this.m_bSuppressNextKeyUp = true; 4174 event.preventDefault(); 4175 return; 4176 } 4177 4178 if ( event.keyCode == CAutoComplete.KEY_UP || event.keyCode == CAutoComplete.KEY_DOWN ) 4179 { 4180 if ( event.keyCode == CAutoComplete.KEY_UP ) 4181 { 4182 if ( $CurSuggestion.length ) 4183 $NewSuggestion = $CurSuggestion.prev(); 4184 if ( !$NewSuggestion.length ) 4185 $NewSuggestion = this.m_$Popup.children( 'div:last-child' ); 4186 } 4187 else 4188 { 4189 if ( $CurSuggestion.length ) 4190 $NewSuggestion = $CurSuggestion.next(); 4191 if ( !$NewSuggestion.length ) 4192 $NewSuggestion = this.m_$Popup.children( 'div:first-child' ); 4193 } 4194 4195 if ( $NewSuggestion.length ) 4196 { 4197 $CurSuggestion.removeClass( 'focus' ); 4198 $NewSuggestion.addClass( 'focus' ); 4199 } 4200 4201 event.preventDefault(); 4202 } 4203} 4204 4205CAutoComplete.prototype.OnInputKeyUp = function( event ) 4206{ 4207 if ( this.m_bSuppressNextKeyUp && event.keyCode == CAutoComplete.KEY_ENTER ) 4208 { 4209 this.m_bSuppressNextKeyUp = false; 4210 event.preventDefault(); 4211 } 4212} 4213 4214 4215 4216function GetCurrentScrollPercentage() 4217{ 4218 var s = $J(window).scrollTop(); 4219 var d = $J(document).height(); 4220 var c = $J(window).height(); 4221 var scrollPercent = (s / (d-c)) * 100; 4222 return scrollPercent; 4223} 4224 4225// @elemID id of the element 4226// @fixedOffsetTop offset from the top when fixed 4227// @bScrollWithPageIfTooTall if the element is taller than the page, then it will "scroll" with the page if this is true 4228// @docHeightOffset if bScrollWithPageIfTooTall is set to true, then this is how much the document height is reduced by (recommend this to be 130 for the typical footer) 4229function FixedElementOnScrollWrapper ( elemID, fixedOffsetTop, bScrollWithPageIfTooTall, docHeightOffset, params ) 4230{ 4231 params = $J.extend( { 4232 fixedClass: null 4233 }, params ); 4234 4235 this.fixedElement = $( elemID ); 4236 this.$FixedElement = $JFromIDOrElement( elemID ); 4237 this.fixedOffsetTop = typeof fixedOffsetTop != "undefined" ? fixedOffsetTop : 0; 4238 this.bScrollWithPageIfTooTall = typeof bScrollWithPageIfTooTall != "undefined" ? bScrollWithPageIfTooTall : false;
4239 this.docHeightOffset = typeof docHeightOffset != "undefined" ? docHeightOffset : 0; 4240 this.homePosn = { x: this.fixedElement.cumulativeOffset()[0], y: this.fixedElement.cumulativeOffset()[1] }; 4241 this.fixedClass = params.fixedClass; 4242 4243 4244 this.$FixedElementPadding = $J('<div/>', {'class': 'FixedElementOnScrollWrapper_padding', 'id': 'ScrollWrapperPadding_' + elemID } ).hide(); 4245 this.fixedElement.insert( { before: this.$FixedElementPadding[0] } ); 4246 4247 var _this = this; 4248 $J(window).on( 'scroll.FixedElementOnScrollWrapper hashchange.FixedElementOnScrollWrapper', function() { _this.handleScroll() } ); 4249 $J(window).on( 'resize.FixedElementOnScrollWrapper', function() { _this.handleScroll( true /* force recalc */ ) } ); 4250 this.handleScroll(); 4251} 4252 4253FixedElementOnScrollWrapper.prototype.BIsFixed = function() 4254{ 4255 if ( this.fixedClass ) 4256 return this.$FixedElement.hasClass( this.fixedClass ); 4257 else 4258 return this.$FixedElement.css( 'position' ) == 'fixed'; 4259} 4260 4261FixedElementOnScrollWrapper.prototype.handleScroll = function( bForceRecalc ) 4262{ 4263 this.scrollOffset = document.viewport.getScrollOffsets().top; 4264 var offsetTop = this.fixedOffsetTop + GetResponsiveHeaderFixedOffsetAdjustment(); 4265 4266 if ( bForceRecalc && !this.BIsFixed() ) 4267 this.homePosn = { x: this.fixedElement.cumulativeOffset()[0], y: this.fixedElement.cumulativeOffset()[1] }; 4268 4269 if ( this.scrollOffset > ( this.homePosn.y - offsetTop ) ) 4270 { 4271 if ( !this.BIsFixed() || bForceRecalc ) 4272 { 4273 if ( this.fixedClass ) 4274 this.$FixedElement.addClass( this.fixedClass ); 4275 else 4276 this.$FixedElement.css( 'position', 'fixed' ); 4277 4278 this.fixedElement.style.top = offsetTop + 'px'; 4279 this.fixedElement.style.left = this.homePosn.x; 4280 4281 // jquery show() sets display to block, which prevents css from hiding this element if needed. 4282 this.$FixedElementPadding.css( 'display', '' ); 4283 this.$FixedElementPadding.css( 'height', this.fixedElement.getHeight() + 'px' ); 4284 } 4285 4286 if ( this.bScrollWithPageIfTooTall ) 4287 { 4288 // this forces the element to scroll off the page, but there's enough that isn't on the page, "scroll" this guy percentage-wise 4289 var elemHeight = this.fixedElement.getHeight() + offsetTop; 4290 if ( elemHeight > document.viewport.getHeight() ) 4291 { 4292 var currentScrollPercentage = GetCurrentScrollPercentage(); 4293 var heightDiff = elemHeight - ( document.viewport.getHeight() - this.docHeightOffset ); 4294 offsetTop -= Math.floor( heightDiff * currentScrollPercentage / 100 ); 4295 this.fixedElement.style.top = offsetTop + 'px'; 4296 } 4297 } 4298 } 4299 else 4300 { 4301 if ( this.BIsFixed() ) 4302 { 4303 if ( this.fixedClass ) 4304 this.$FixedElement.removeClass( this.fixedClass ); 4305 else 4306 this.$FixedElement.css( 'position', '' ); 4307 4308 this.$FixedElement.css( 'top', '' ).css( 'left', '' ); 4309 this.$FixedElementPadding.css( 'display', 'none' ); 4310 } 4311 } 4312} 4313 4314// general text suggestion control 4315 4316// fnSuggestForTerm will be called with two values, the current string to get suggestions for, 4317// and the callback to invoke with the new values. You should always invoke the callback per call 4318// to fnSuggestForTerm, but can delay due to ajax if needed. 4319 4320// supported options: 4321// bDoEmptySearchOnFirstFocus - run a search for "" (or whatever is in the box) when the text input is first interacted with 4322function CTextInputSuggest( $InputElement, fnSuggestForTerm, fnOnSuggest, strCssClass, opts ) 4323{ 4324 this.Init( $InputElement, fnSuggestForTerm, fnOnSuggest, strCssClass, opts ); 4325} 4326 4327CTextInputSuggest.prototype.Init = function( $InputElement, fnSuggestForTerm, fnOnSuggest, strCssClass, opts ) 4328{ 4329 opts = opts ? opts : {}; 4330 if( !strCssClass ) 4331 strCssClass = 'popup_block_new'; 4332 this.m_bHaveSuggestions = false; 4333 this.m_$Input = $InputElement; 4334 this.m_fnSuggestForTerm = fnSuggestForTerm; 4335 this.m_fnOnSuggest = fnOnSuggest || function( term ) {}; 4336 this.m_strLastVal = opts.bDoEmptySearchOnFirstFocus ? undefined : ''; // '' is the value in the input when it's empty 4337 this.m_align = 'left'; 4338 this.m_valign = 'bottom'; 4339 4340 this.m_$Focus = $J(); 4341 this.m_strLastFocusVal = null; 4342 4343 this.m_nNextRequestID = 1; 4344 this.m_nRequestIDRecvd = 0; 4345 4346 4347 this.m_$SuggestionsCtn = $J('<div/>', {'class': strCssClass, style: 'display: none;' } ); 4348 this.m_$Suggestions = $J('<div/>', {'class': 'popup_body popup_menu' } ); 4349 this.m_$SuggestionsCtn.append( this.m_$Suggestions ); 4350 4351 this.m_$SuggestionsCtn.hide(); 4352 this.m_$Input[0].parentElement.appendChild( this.m_$SuggestionsCtn[0] ); 4353 4354 var zIndex = 200; //normal popup zindex 4355 this.m_$Input.parents().each( function() { 4356 var zIndexParent = $J(this).css('zIndex'); 4357 if ( zIndexParent != 'auto' && zIndexParent != 0 ) 4358 { 4359 zIndex = zIndexParent; 4360 } 4361 }); 4362 this.m_$SuggestionsCtn.css( 'zIndex', zIndex + 20 ); 4363 4364 var _this = this; 4365 this.m_$Input.on( 'keyup.CTextInputSuggest click.CTextInputSuggest', function( event ) { _this.OnTextChanged( event ) } ); 4366 this.m_$Input.on( 'paste.CTextInputSuggest cut.CTextInputSuggest', function() { window.setTimeout( function() { _this.OnTextChanged() }, 1 ); } ); 4367 this.m_$Input.on( 'keydown.CTextInputSuggest', function( event ) { _this.OnKeyDown( event ) } ); 4368 this.m_$Input.on( 'focus.CTextInputSuggest', function() { _this.ShowSuggestions() } ); 4369 this.m_$Input.on( 'blur.CTextInputSuggest', function() { _this.HideSuggestions() } ); 4370} 4371 4372CTextInputSuggest.prototype.SetAlignment = function( align, valign ) 4373{ 4374 this.m_align = align || 'left'; 4375 this.m_valign = valign || 'bottom'; 4376}; 4377 4378CTextInputSuggest.prototype.SetSuggestionsContainerId = function( strSuggestionsCtnId ) 4379{ 4380 this.m_$SuggestionsCtn.attr( 'id', strSuggestionsCtnId ); 4381}; 4382 4383CTextInputSuggest.prototype.ShowSuggestions = function() 4384{ 4385 if ( !this.m_$SuggestionsCtn.find(':visible').length ) 4386 { 4387 if ( this.m_bHaveSuggestions ) 4388 { 4389 AlignMenu(this.m_$Input[0], this.m_$SuggestionsCtn[0], this.m_align, this.m_valign, true); 4390 this.m_$SuggestionsCtn.fadeIn('fast'); 4391 } 4392 else if ( this.m_strLastVal === undefined ) 4393 { 4394 // run an initial query. This will set m_strLastVal to empty string (or whatever else is
4394in the box, 4395 // if something came from browser history) 4396 this.OnTextChanged( undefined ); 4397 } 4398 } 4399}; 4400 4401CTextInputSuggest.prototype.HideSuggestions = function() 4402{ 4403 if ( this.m_bHaveSuggestions ) 4404 this.m_$SuggestionsCtn.fadeOut( 'fast' ); 4405 else 4406 this.m_$SuggestionsCtn.hide(); 4407}; 4408 4409CTextInputSuggest.prototype.OnSuggestionSelected = function( $Suggestion ) 4410{ 4411 this.m_$Input.val( $Suggestion.text() ); 4412 4413 this.m_bHaveSuggestions = false; 4414 this.m_$Focus = $J(); 4415 this.HideSuggestions(); 4416 4417 this.m_fnOnSuggest( $Suggestion.text() ); 4418}; 4419 4420CTextInputSuggest.prototype.SetSuggestions = function( rgSuggestions ) 4421{ 4422 var strLastFocus = this.m_strLastFocusVal; 4423 4424 this.m_$Suggestions.empty(); 4425 4426 this.m_$Focus = $J(); 4427 this.m_strLastFocus = null; 4428 4429 if ( rgSuggestions && rgSuggestions.length ) 4430 { 4431 var _this = this; 4432 for ( var i = 0; i < rgSuggestions.length; i++ ) 4433 { 4434 var $Suggestion = $J('<div/>', {'class': 'suggestion_item popup_menu_item' } ); 4435 $Suggestion.text( rgSuggestions[i] ); 4436 $Suggestion.click( $J.proxy( this.OnSuggestionSelected, this, $Suggestion ) ); 4437 $Suggestion.mouseenter( $J.proxy( this.SetFocus, this, $Suggestion ) ); 4438 4439 this.m_$Suggestions.append( $Suggestion ); 4440 4441 if ( rgSuggestions[i] == strLastFocus ) 4442 this.SetFocus( $Suggestion ); 4443 } 4444 this.m_bHaveSuggestions = true; 4445 this.ShowSuggestions(); 4446 } 4447 else 4448 { 4449 this.m_bHaveSuggestions = false; 4450 this.HideSuggestions(); 4451 } 4452}; 4453 4454CTextInputSuggest.prototype.OnTextChanged = function( event ) 4455{ 4456 if ( event && ( event.which == 13 || event.which == 27 ) ) 4457 return; 4458 4459 var value = this.m_$Input.val().trim(); 4460 if ( value !== this.m_strLastVal ) 4461 { 4462 var _this = this; 4463 var nRequestID = this.m_nNextRequestID++; 4464 this.m_fnSuggestForTerm( value, function( rgSuggestions ) { 4465 // we've already recieved a more recent response, this is out-of-order 4466 if ( _this.m_nRequestIDRecvd > nRequestID ) 4467 return; 4468 4469 _this.m_nRequestIDRecvd = nRequestID; 4470 _this.SetSuggestions( rgSuggestions ); 4471 } ); 4472 4473 this.m_strLastVal = value; 4474 } 4475}; 4476 4477CTextInputSuggest.prototype.OnKeyDown = function( event ) 4478{ 4479 if ( event.which == 27 ) 4480 { 4481 this.HideSuggestions(); 4482 } 4483 else if ( this.m_bHaveSuggestions ) 4484 { 4485 var $NewSuggestion = null; 4486 if ( event.which == 13 ) 4487 { 4488 if ( this.m_$Focus.length && this.m_bHaveSuggestions ) 4489 { 4490 this.OnSuggestionSelected( this.m_$Focus ); 4491 event.stopPropagation(); 4492 event.preventDefault(); 4493 } 4494 } 4495 else if ( event.which == 38 /* up arrow */ ) 4496 { 4497 event.preventDefault(); 4498 if ( this.m_$Focus.length ) 4499 $NewSuggestion = this.m_$Focus.prev(); 4500 if ( !$NewSuggestion ) 4501 $NewSuggestion = this.m_$Suggestions.children().last(); 4502 } 4503 else if ( event.which == 40 /* down arrow */ ) 4504 { 4505 event.preventDefault(); 4506 if ( this.m_$Focus.length ) 4507 $NewSuggestion = this.m_$Focus.next(); 4508 if ( !$NewSuggestion ) 4509 $NewSuggestion = this.m_$Suggestions.children().first(); 4510 } 4511 4512 if ( $NewSuggestion ) 4513 this.SetFocus( $NewSuggestion ); 4514 } 4515}; 4516 4517CTextInputSuggest.prototype.SetFocus = function( $Suggestion ) 4518{ 4519 this.m_$Focus.removeClass( 'focus' ); 4520 this.m_$Focus = $Suggestion; 4521 this.m_$Focus.addClass( 'focus' ); 4522 this.m_strLastFocusVal = $Suggestion.text(); 4523}; 4524 4525CTextInputSuggest.prototype.Destroy = function() 4526{ 4527 this.m_$SuggestionsCtn.remove(); 4528 this.m_$Input.off( '.CTextInputSuggest' ); 4529}; 4530 4531/** 4532 * Similar to CTextInputSuggest, but applies styling using the app_type value of each suggestion 4533 * 4534 * Each suggestion should include the suggestion text, and the app_type 4535 * 4536 * @param $InputElement 4537 * @param fnSuggestForTerm 4538 * @param fnOnSuggest 4539 * @constructor 4540 */ 4541function CAppTypeInputSuggest( $InputElement, fnSuggestForTerm, fnOnSuggest, strCssClass ) 4542{ 4543 this.Init( $InputElement, fnSuggestForTerm, fnOnSuggest, strCssClass, { bDoEmptySearchOnFirstFocus: true } ); 4544} 4545 4546CAppTypeInputSuggest.prototype = Object.create( CTextInputSuggest.prototype ); 4547 4548CAppTypeInputSuggest.prototype.SetSuggestions = function( rgSuggestions ) 4549{ 4550 var strLastFocus = this.m_strLastFocusVal; 4551 4552 this.m_$Suggestions.empty(); 4553 4554 this.m_$Focus = $J(); 4555 this.m_strLastFocus = null; 4556 4557 if ( rgSuggestions && rgSuggestions.length ) 4558 { 4559 var _this = this; 4560 for ( var i = 0; i < rgSuggestions.length; i++ ) 4561 { 4562 var resultClass = 'suggestion_item popup_menu_item'; 4563 var suggestionText = rgSuggestions[i][0]; 4564 4565 if ( typeof rgSuggestions[i][1] !== 'undefined' ) 4566 { 4567 switch ( parseInt( rgSuggestions[i][1] ) ) 4568 { 4569 case 1: 4570 resultClass += ' app_Game'; 4571 suggestionText = '[à¹à¸à¸¡] ' + suggestionText; 4572 break; 4573 case 2: 4574 suggestionText = '[à¹à¸à¸à¸à¸¥à¸´à¹à¸à¸à¸±à¸] ' + suggestionText; 4575 break; 4576 case 4: 4577 suggestionText = '[à¹à¸à¸£à¸·à¹à¸à¸à¸¡à¸·à¸] ' + suggestionText; 4578 break; 4579 case 8: 4580 resultClass += ' app_Demo'; 4581 suggestionText = '[à¹à¸à¹à¸¡] ' + suggestionText; 4582 break; 4583 case 16: 4584 suggestionText = '[มีà¹à¸à¸µà¸¢] ' + suggestionText; 4585 break; 4586 case 32: 4587 resultClass += ' app_DLC'; 4588 suggestionText = '[à¹à¸à¸·à¹à¸à¸«à¸²à¸à¸²à¸§à¸à¹à¹à¸«à¸¥à¸] ' + suggestionText; 4589 break; 4590 case 256: 4591 suggestionText = '[à¸à¸à¸à¸à¸´à¸] ' + suggestionText; 4592 break; 4593 case 2048: 4594 resultClass += ' app_Movie'; 4595 suggestionText = '[วิà¸à¸µà¹à¸] ' + suggestionText; 4596 break; 4597 case 8192: 4598 resultClass += ' app_Music'; 4599 suggestionText = '[à¹à¸à¸¥à¸] ' + suggestionText; 4600 break; 4601 case 65536: 4602 resultClass += ' app_Beta'; 4603 suggestionText = '[à¹à¸à¸à¹à¸²] ' + suggestionText; 4604 break; 4605 } 4606 } 4607 4608 var $Suggestion = $J('<div/>', {'class': resultClass } ); 4609 $Suggestion.text( suggestionText ); 4610 4611 $Suggestion.click( $J.proxy( this.OnSuggestionSelected, this, $Suggestion ) ); 4612 $Suggestion.mouseenter( $J.proxy( this.SetFocus, this, $Suggestion ) ); 4613 4614 this.m_$Suggestions.append( $Suggestion ); 4615 4616 if ( rgSuggestions[i] == strLastFocus ) 4617 this.SetFocus( $Suggestion ); 4618 } 4619 this.m_bHaveSuggestions = true; 4620 this.ShowSuggestions(); 4621 } 4622 else 4623 { 4624 this.m_bHaveSuggestions = false;
4625 this.HideSuggestions(); 4626 } 4627}; 4628 4629 4630function InitBBCodeVideos( bAllowAutoPlay ) 4631{ 4632 var videos = $J( "video" ); 4633 if ( videos.length != 0 ) 4634 { 4635 for ( var i = 0; i < videos.length; ++i ) 4636 { 4637 var video = videos[i]; 4638 var $video = $J( video ); 4639 4640 if ( $video.hasClass("bb_video_loop") ) 4641 { 4642 video.setAttribute('loop', 'loop'); 4643 } 4644 4645 if ( $video.hasClass("bb_video_controls") ) 4646 { 4647 video.setAttribute( 'controls', 'controls' ); 4648 } 4649 4650 if ( bAllowAutoPlay ) 4651 { 4652 $video.unbind('mouseenter mouseleave'); 4653 4654 video.preload = $video.hasClass("bb_video_preload") ? 'auto' : 'metadata'; 4655 4656 if ( $video.hasClass("bb_video_autoplay") ) 4657 { 4658 video.autoplay = 'autoplay'; 4659 } 4660 } 4661 else 4662 { 4663 video.preload = 'metadata'; 4664 $video.hover( function toggleControls() { 4665 if ( this.hasAttribute("controls") ) { 4666 this.removeAttribute("controls") 4667 } else { 4668 this.setAttribute("controls", "controls") 4669 } 4670 } ); 4671 } 4672 } 4673 } 4674} 4675 4676 4677/** 4678 * Generic interface for handling ajax-driven sub-pages. 4679 * 4680 * @param elTarget Element to dump content into 4681 * @param strBaseURL Base URL to attach nav params 4682 * @param strInstanceId Unique flag to look for when scanning for state change events. also determines our data selector 4683 * Only needed if you're using more than one CAjaxSubPageController on the same page. 4684 * @param strDefaultLocation Used when no navid is provided 4685 * @constructor 4686 */ 4687var CAjaxSubPageController = function( elTarget, strBaseURL, strInstanceId, strDefaultLocation ) 4688{ 4689 this.elTarget = elTarget; 4690 this.strBaseURL = strBaseURL; 4691 this.strStateID = strInstanceId || 'navid'; 4692 this.strDefaultLocation = strDefaultLocation || ''; 4693 4694 this.rgOriginalEvent = {'html':this.elTarget.innerHTML,'title':document.title, 'id': this.strStateID}; 4695 4696 window.addEventListener('popstate', this.OnWindowPopState.bind(this)); 4697 4698 this.InstrumentLinks( document ); 4699 4700 var _this = this; 4701 setTimeout( function(){ $J(_this.elTarget).children().trigger('load'); }, 1); 4702 4703 var strLocation = window.location.href.substring(strBaseURL.length); 4704 this.PaintLinks(strLocation || this.strDefaultLocation); 4705 4706 4707}; 4708 4709/** 4710 * Register click handlers. This also sets the href for browser link preview and fallback for non-click navigation 4711 * events, such as opening in a new tab/window via middle click. 4712 * @param elTarget Element to query from. 4713 * @constructor 4714 */ 4715CAjaxSubPageController.prototype.InstrumentLinks = function( elTarget ) 4716{ 4717 //var rgLinks = elTarget.querySelectorAll('[data-'+this.strStateID+']'); 4718 var rgLinks = $J('[data-'+this.strStateID+']', elTarget); 4719 for( var i=0; i<rgLinks.length; i++) 4720 { 4721 rgLinks[i].addEventListener('click', this.Navigate.bind(this, rgLinks[i].dataset[ this.strStateID ], rgLinks[i].dataset[ 'title' ] ) ); 4722 rgLinks[i].href = this.strBaseURL + rgLinks[i].dataset[ this.strStateID ]; 4723 } 4724}; 4725 4726/** 4727 * Adds 'active' class to link that was clicked or is currently active. 4728 * @constructor 4729 */ 4730CAjaxSubPageController.prototype.PaintLinks = function( strLocation ) 4731{ 4732 // Figure out which link we clicked and paint it with the correct class 4733 var rgLinks = document.querySelectorAll('[data-'+this.strStateID+']'); 4734 for( var i=0; i<rgLinks.length; i++) 4735 { 4736 if( rgLinks[i].dataset[ this.strStateID ] == strLocation || rgLinks[i].dataset[ this.strStateID ] + '/' == strLocation ) 4737 rgLinks[i].classList.add('active'); 4738 else 4739 rgLinks[i].classList.remove('active'); 4740 } 4741}; 4742 4743/** 4744 * Call to navigate the sub-frame. 4745 * 4746 * @param strLocation Assumed to be strBaseURL + strLocation. Trailing slash should be on strBaseURL already. 4747 * @param strpageTitle Optional: Replace page title with this new value. 4748 * @constructor 4749 */ 4750CAjaxSubPageController.prototype.Navigate = function( strLocation, strPageTitle, event ) 4751{ 4752 var _this = this; 4753 var strURL = this.strBaseURL + strLocation; 4754 4755 this.elTarget.classList.add('loading'); 4756 4757 this.PaintLinks( strLocation ); 4758 4759 // Trigger the "saveform" event which we may have bound to do things. 4760 $J( 'form', this.elTarget ).trigger('saveform'); 4761 4762 $J.ajax({ 4763 url: strURL, 4764 dataType: "html", 4765 cache: true, /* Let the browser decide caching rules */ 4766 data: { 'ajax': 1 } 4767 }).done(function( result ) { 4768 4769 var elNewContents = $J( result ); 4770 4771 $J(_this.elTarget).empty(); 4772 $J(_this.elTarget).append( elNewContents ); 4773 4774 4775 _this.elTarget.classList.remove('loading'); 4776 4777 $J( elNewContents ).trigger('load'); 4778 4779 _this.InstrumentLinks( _this.elTarget ); 4780 if( strPageTitle ) 4781 document.title = strPageTitle; 4782 4783 $J('.tooltip', _this.elTarget).v_tooltip(); 4784 4785 window.history.pushState({'html':result,'title':strPageTitle, 'id': _this.strStateID}, '', strURL ); 4786 }); 4787 4788 if( event ) 4789 event.preventDefault(); 4790 4791}; 4792 4793/** 4794 * Internal event handler for the "back" button. 4795 * @param Event event 4796 * @constructor 4797 */ 4798CAjaxSubPageController.prototype.OnWindowPopState = function( event ) 4799{ 4800 // Revert to our original state if there's nothing in the stack. 4801 4802 if(event.state == null) 4803 { 4804 event.state = 5; 4805 } 4806 4807 var state = event.state || this.rgOriginalEvent; 4808
4809 if( state && state.id == this.strStateID) 4810 { 4811 var strLocation = window.location.href.substring(this.strBaseURL.length); 4812 this.PaintLinks( strLocation ); 4813 4814 var elNewContents = $J(state.html); 4815 $J(this.elTarget).empty(); 4816 $J(this.elTarget).append(elNewContents); 4817 if( state.title ) 4818 document.title = state.title; 4819 4820 $J( elNewContents ).trigger('load'); 4821 this.InstrumentLinks( elNewContents ); 4822 } 4823}; 4824 4825function SetupAnimateOnHoverImages() 4826{ 4827 var $Images = $J( '[data-animate-on-hover-src]', document ); 4828 if ( $Images.length ) 4829 { 4830 for ( var i = 0; i < $Images.length; ++i ) 4831 { 4832 var img = $J( $Images[i] ); 4833 img.data( 'static-src', img.attr( 'src') ); 4834 img.hover( 4835 function() { 4836 var thisImage = $J( this ); 4837 thisImage.attr( 'src', thisImage.data( 'animate-on-hover-src' ) ); 4838 }, 4839 function() { 4840 var thisImage = $J( this ); 4841 thisImage.attr( 'src', thisImage.data( 'static-src' ) ); 4842 } 4843 ); 4844 } 4845 } 4846} 4847 4848function BindTooltips(selector, rgOptions) 4849{ 4850 // Standard tooltips 4851 var $TextTooltips = $J( '[data-tooltip-text]', selector); 4852 if ( $TextTooltips.length ) 4853 $TextTooltips.v_tooltip( { 'tooltipClass': rgOptions.tooltipCSSClass, 'dataName': 'tooltipText', 'defaultType': 'text', 'replaceExisting': false, 'responsiveMode': window.UseSmallScreenMode && window.UseSmallScreenMode(), 'appearanceDelayMs': rgOptions.appearanceDelayMs ?? 0 } ); 4854 4855 var $HTMLTooltips = $J( '[data-tooltip-html]', selector); 4856 if ( $HTMLTooltips.length ) 4857 $HTMLTooltips.v_tooltip( { 'tooltipClass': rgOptions.tooltipCSSClass, 'dataName': 'tooltipHtml', 'defaultType': 'html', 'replaceExisting': false, 'responsiveMode': window.UseSmallScreenMode && window.UseSmallScreenMode(), 'appearanceDelayMs': rgOptions.appearanceDelayMs ?? 0 } ); 4858} 4859 4860function ShowTooltipMenuAsPopup( toolDiv ) 4861{ 4862 var $Popup = $J( toolDiv ); 4863 // detach this element and when the dialog closes re-attach to the document body 4864 $Popup.detach(); 4865 var originalPopupPosition = $Popup.css( 'position' ); 4866 $Popup.css( 'position', 'static' ); // clear possible absolute positioning 4867 $Popup.show(); 4868 4869 var dialog = ShowDialog( '', $Popup ).always( 4870 function() { 4871 // save it away again for later 4872 $Popup.hide(); 4873 $Popup.css( 'position', originalPopupPosition ); // restore positioning 4874 $J( document.body ).append( $Popup ); 4875 } 4876 ); 4877 return dialog; 4878} 4879 4880/** 4881 * Binds standard tooltips for the current site. This function also binds a mutationobserver to bind any future elements without additional work 4882 * @param strCSSClass 4883 * @constructor 4884 */ 4885var g_TooltipMutationObserver; 4886var g_bTooltipMutationObserverDisabled = false; 4887function SetupTooltips( rgOptions ) 4888{ 4889 function InnerSetupTooltips() { 4890 4891 BindTooltips(document, rgOptions); 4892 4893 if (g_TooltipMutationObserver || g_bTooltipMutationObserverDisabled) 4894 return; 4895 4896 try 4897 { 4898 var config = { 4899 attributes: true, // not clear if we actually support this, as we only look at addedNodes 4900 childList: true, 4901 subtree: true, 4902 attributeFilter: ["data-tooltip-html", "data-tooltip-text"] 4903 }; 4904 4905 var callback = function (mutationsList) 4906 { 4907 for (var i = 0; i < mutationsList.length; i++) 4908 { 4909 var mutation = mutationsList[i]; 4910 if (mutation.addedNodes && mutation.addedNodes.length) 4911 BindTooltips(mutation.addedNodes, rgOptions); 4912 } 4913 }; 4914 4915 // Create an observer instance linked to the callback function 4916 g_TooltipMutationObserver = new MutationObserver(callback); 4917 4918 // Start observing the target node for configured mutations 4919 g_TooltipMutationObserver.observe(document, config); 4920 } 4921 catch (e) 4922 { 4923 // Swallow exceptions for browsers that don't support mutationobservers 4924 } 4925 } 4926 4927 InnerSetupTooltips(); 4928 $J( window ).on( 'Responsive_SmallScreenModeToggled', InnerSetupTooltips ); 4929} 4930 4931// for perf sensitive pages 4932function DisableTooltipMutationObserver() 4933{ 4934 g_bTooltipMutationObserverDisabled = true; 4935 4936 if ( g_TooltipMutationObserver ) 4937 { 4938 g_TooltipMutationObserver.disconnect(); 4939 g_TooltipMutationObserver = null; 4940 } 4941} 4942 4943function ViewTitlesWithDescriptors( descid ) 4944{ 4945 var waitDialog = ShowBlockingWaitDialog( 'à¸à¸¥à¸´à¸à¸ ัà¸à¸à¹à¸à¸±à¸§à¸à¸¢à¹à¸²à¸', 'à¹à¸à¸£à¸à¸£à¸à¸ªà¸±à¸à¸à¸£à¸¹à¹à¹à¸à¸à¸à¸°à¸à¸µà¹à¹à¸£à¸²à¸à¹à¸à¸«à¸²à¸à¸¥à¸´à¸à¸ ัà¸à¸à¹à¸à¸±à¸§à¸à¸¢à¹à¸²à¸...' ); 4946 4947 $J.get( 'https://store.steampowered.com/search/results/', 4948 { 4949 'filter' : 'examplesforcontentdescriptors', 4950 'ignore_preferences' : 1, 4951 'category1' : '992,994,998', 'descids[]' : descid, 4952 'json' : 1, 4953 } 4954 ).done( function( response ) { 4955 waitDialog.Dismiss(); 4956 4957 var content = $J( "<div>" ); 4958 content.append( $J( "<div>", { class: 'content_descriptors_examples_desc', text: response.desc } ) ); 4959 4960 if ( response.items.length != 0 ) 4961 { 4962 for ( var i = 0; i < response.items.length && i < 10; ++i ) 4963 { 4964 var item = response.items[i]; 4965 var elem = $J( "<div>", { class: 'content_descriptors_example_app' } ); 4966 elem.append( $J( "<img>", { class: 'app_logo', src: item.logo } ) ); 4967 elem.append( $J( "<div>", { class: 'app_name', text: item.name } ) ); 4968 content.append( elem ); 4969 } 4970 } 4971 else 4972 {
4973 content.append( $J( "<div>", { class: "no_items", text: 'à¹à¸¡à¹à¸à¸à¸à¸¥à¸´à¸à¸ ัà¸à¸à¹' } ) ); 4974 } 4975 4976 var dialog = ShowAlertDialog( 'à¸à¸¥à¸´à¸à¸ ัà¸à¸à¹à¸à¸±à¸§à¸à¸¢à¹à¸²à¸', content ); 4977 } ); 4978} 4979 4980 4981 4982// Element appearance montior. Designed to replace jQuery.appear 4983 4984var CAppearMonitor = function( fnOnAppear, bCheckAnyIntersection ){ 4985 this.rgMonitoredElements = []; 4986 this.bRunning = false; 4987 this.unTimerFrequency = 500; 4988 this.bRegisteredWindowEvent = false; 4989 this.fnOnAppear = fnOnAppear; 4990 this.bCheckAnyIntersection = bCheckAnyIntersection || false; 4991}; 4992 4993CAppearMonitor.prototype.RegisterScrollEvent = function( elTarget ) 4994{ 4995 var instance = this; 4996 4997 $J(elTarget).on('scroll', function() 4998 { 4999 if( instance.bRunning || instance.rgMonitoredElements.length == 0 ) 5000 return; 5001 5002 instance.bRunning = true; 5003 setTimeout( instance.CheckVisibilityInternal.bind(instance), instance.unTimerFrequency ); 5004 5005 }); 5006} 5007 5008CAppearMonitor.prototype.CheckVisibility = function() 5009{ 5010 // when items are added they will schedule an immediate check. Delay for a bit 5011 if ( this.m_iCheckVisibilityRequestInterval ) 5012 { 5013 window.clearTimeout( this.m_iCheckVisibilityRequestInterval ); 5014 } 5015 5016 this.m_iCheckVisibilityRequestInterval = setTimeout( this.CheckVisibilityInternal.bind(this), this.unTimerFrequency ); 5017}; 5018 5019CAppearMonitor.prototype.CheckVisibilityInternal = function() 5020{ 5021 // Walk backwards so we can slice while we iterate 5022 for( var i=this.rgMonitoredElements.length-1; i>=0; i--) 5023 { 5024 var rgMonitored = this.rgMonitoredElements[i]; 5025 if( this.bIsElementVisible( rgMonitored.element ) ) 5026 { 5027 this.rgMonitoredElements.splice(i,1); 5028 this.fnOnAppear( rgMonitored.element ); 5029 } else if( !document.body.contains( rgMonitored.element ) ) 5030 { 5031 this.rgMonitoredElements.splice(i,1); 5032 } 5033 } 5034 5035 this.bRunning = false; 5036 this.m_iCheckVisibilityRequestInterval = undefined; 5037}; 5038 5039CAppearMonitor.prototype.bIsElementVisible = function( elElement ) 5040{ 5041 if( !elElement ) 5042 return; 5043 5044 // Check physical position vs viewport. This is likely the fastest early-out. 5045 var rectElement = elElement.getBoundingClientRect(); 5046 5047 if ( this.bCheckAnyIntersection ) 5048 { 5049 if ( rectElement.left > window.innerWidth || rectElement.right < 0 ) 5050 return false; 5051 5052 if ( rectElement.top > window.innerHeight || rectElement.bottom < 0 ) 5053 return false; 5054 } 5055 else 5056 { 5057 if ( !( rectElement.top >= 0 && rectElement.left >= 0 5058 && rectElement.bottom <= window.innerHeight && rectElement.right <= window.innerWidth ) ) 5059 return false; 5060 } 5061 5062 // Ask jQuery to compute visibility, since it knows all the edge cases. 5063 if( !$J(elElement).is(':visible')) 5064 return false; 5065 5066 return true; 5067 5068}; 5069 5070CAppearMonitor.prototype.RegisterElement = function( elTarget ) 5071{ 5072 if( $J(elTarget).data('manual-tracking' ) ) 5073 return; 5074 5075 this.rgMonitoredElements.push({ 5076 element: elTarget 5077 }); 5078 5079 // Register the window scroll event ONLY after we have something to monitor 5080 if( !this.bRegisteredWindowEvent) 5081 this.RegisterScrollEvent( window ); 5082}; 5083 5084CAppearMonitor.prototype.TrackAppearanceIfVisible = function( elTarget ) 5085{ 5086 var _this = this; 5087 window.setTimeout( function() { 5088 // Ensure we're actually in the viewport (Carousel may be scrolling out of view and calling this) 5089 // this triggers layout, so we delay this check a little bit to give the page time to settle whatever 5090 // change may currently be in-flight 5091 if( !_this.bIsElementVisible( elTarget ) ) 5092 return; 5093 5094 _this.TrackAppearance( elTarget ); 5095 }, 10); 5096}; 5097 5098CAppearMonitor.prototype.TrackAppearance = function( elTarget ) 5099{ 5100 // Find our element, splice it out if we were tracking it 5101 for( var i=this.rgMonitoredElements.length-1; i>=0; i--) 5102 { 5103 var rgMonitored = this.rgMonitoredElements[i]; 5104 if( rgMonitored.element === elTarget ) 5105 { 5106 this.rgMonitoredElements.splice(i,1); 5107 } 5108 } 5109 // Trigger it, even if we weren't actually tracking it 5110 this.fnOnAppear( elTarget ); 5111}; 5112 5113CAppearMonitor.prototype.ClearElements = function() 5114{ 5115 this.rgMonitoredElements = []; 5116} 5117 5118// gamepad navigation wrappers, the implementations of these are stored in legacy_web. 5119// once legacy web initializes, these functions will be replaced by the real thing. In the mean time, 5120// we will queue any commands and send them when ready. 5121var g_rgQueuedGamepadCommands = []; 5122var g_rgOnReadyCallbacks = []; 5123var g_bGamepadNavReady = false;
5124function RunWhenGamepadNavReady( callback ) 5125{ 5126 if ( g_bGamepadNavReady ) 5127 callback(); 5128 else 5129 g_rgOnReadyCallbacks.push( callback ); 5130} 5131window.addEventListener( 'vgp_gamepadnavready', function() { 5132 g_rgQueuedGamepadCommands.forEach( function( command ) { window[command.name].apply( window, command.args ); } ); 5133 g_rgQueuedGamepadCommands = []; 5134 5135 g_rgOnReadyCallbacks.forEach( function( fn ) { fn(); } ); 5136 g_rgOnReadyCallbacks = []; 5137}); 5138 5139function InstrumentFocusElements( element ) 5140{ 5141 g_rgQueuedGamepadCommands.push( { name: 'InstrumentFocusElements', args: arguments } ); 5142} 5143function ForceUpdateFocusElements( element ) 5144{ 5145 g_rgQueuedGamepadCommands.push( { name: 'ForceUpdateFocusElements', args: arguments } ); 5146} 5147function GPNavFocusChild( element ) 5148{ 5149 g_rgQueuedGamepadCommands.push( { name: 'GPNavFocusChild', args: arguments } ); 5150} 5151function GPOnShowingModalWindow( element ) 5152{ 5153 g_rgQueuedGamepadCommands.push( { name: 'GPOnShowingModalWindow', args: arguments } ); 5154} 5155function GPShowVirtualKeyboard( bShowKeyboard ) 5156{ 5157 g_rgQueuedGamepadCommands.push( { name: 'GPShowVirtualKeyboard', args: arguments } ); 5158} 5159function GPNavUpdateActionDescriptions( element, actionDescriptions ) 5160{ 5161 g_rgQueuedGamepadCommands.push( { name: 'GPNavUpdateActionDescriptions', args: arguments } ); 5162} 5163 5164var SetGPFocusRestoreTimeout = function(){}; // no op unless InitializeGPFocusRestoreTimeout is called 5165function InitializeGPFocusRestoreTimeout( bUseWindowOnload = true ) 5166{ 5167 var nGPFocusRestoreTimeoutID; 5168 SetGPFocusRestoreTimeout = function( delay = 200 ) 5169 { 5170 if ( nGPFocusRestoreTimeoutID ) 5171 window.clearTimeout( nGPFocusRestoreTimeoutID ); 5172 5173 this.nGPFocusRestoreTimeoutID = window.setTimeout( function(){ 5174 SetGPFocusRestoreTimeout = function(){}; // only fire once 5175 window.__bFocusRestoreReady = true; 5176 window.postMessage( 'FocusRestoreReady' ); 5177 }, delay ); 5178 } 5179 5180 // this waits for all images on the page to finish downloading 5181 if ( bUseWindowOnload ) 5182 window.addEventListener( "load", function(){ SetGPFocusRestoreTimeout(); } ); 5183} 5184 5185function HandleOverlayWindowPinnedView( bPinned, bShowPinnedView ) 5186{ 5187 $J( 'body' ).toggleClass( 'OverlayWindowPinned', bPinned ); 5188 $J( 'body' ).toggleClass( 'OverlayWindowPinnedView', bShowPinnedView ); 5189} 5190 5191var g_oReactNativeSetHeader; 5192function InitReactNativeSetHeaderData( rgInitialReactNativeHeaderData, fnPostData ) 5193{ 5194 g_oReactNativeSetHeader = { 5195 rgData: rgInitialReactNativeHeaderData, 5196 fnPostData: fnPostData 5197 }; 5198 UpdateReactNativeClientHeaderData(); 5199} 5200 5201function SetReactNativeHeaderCartItemCount( nCartItemCount ) 5202{ 5203 if ( !g_oReactNativeSetHeader ) 5204 return; // not in react native client 5205 5206 if ( nCartItemCount == g_oReactNativeSetHeader.rgData['cart_item_count'] ) 5207 return; 5208 5209 if ( nCartItemCount ) 5210 g_oReactNativeSetHeader.rgData['cart_item_count'] = nCartItemCount; 5211 else 5212 delete g_oReactNativeSetHeader.rgData['cart_item_count']; 5213 5214 UpdateReactNativeClientHeaderData(); 5215} 5216 5217function UpdateReactNativeClientHeaderData() 5218{ 5219 if ( g_oReactNativeSetHeader ) 5220 { 5221 g_oReactNativeSetHeader.fnPostData( g_oReactNativeSetHeader.rgData ); 5222 } 5223} 5224 5225 5226function InitAutoCollapseReadMore( elTargets ) 5227{ 5228 $J( elTargets ).each( function() { 5229 var content = this; 5230 var $Content = $J(content); 5231 $Content.wrap( $J('<div/>', {'class': 'shared_autocollapse_ctn' } ) ); 5232 5233 var $Container = $Content.parent(); 5234 5235 var $ReadMore = $J('<div/>', {'class': 'shared_autocollapse_readmore' }).text( 'à¸à¹à¸²à¸à¹à¸à¸´à¹à¸¡à¹à¸à¸´à¸¡' ); 5236 var $Fade = $J('<div/>', {'class': 'shared_autocollapse_fade' } ).append( $ReadMore ); 5237 $Container.append( $Fade ); 5238 5239 var nInterval = 0; 5240 var nMaxHeight = parseInt( $Content.css('max-height') ); 5241 var bMaxHeightSet = true; 5242 5243 $Content.on( 'shared_autocollapse_expand', function() { 5244 if ( $Container.hasClass( 'collapsed' ) ) 5245 { 5246 $Container.removeClass( 'collapsed' ); 5247 $Container.addClass( 'expanded' ); 5248 5249 if ( bMaxHeightSet ) 5250 { 5251 $Content.animate( {'max-height': content.scrollHeight + 20 + 'px'}, 'fast', null, function() { $Content.css('max-height', 'none' ); } ); 5252 } 5253 window.clearInterval( nInterval ); 5254 } 5255 }); 5256 5257 $ReadMore.click( function() { $Content.trigger('shared_autocollapse_expand'); } ); 5258 5259 var fnCheckHeight = function () { 5260 if ( content.scrollHeight > nMaxHeight + 30 ) 5261 { 5262 $Content.css( 'max-height', nMaxHeight + 'px' ); 5263 $Container.addClass( 'collapsed' ); 5264 window.clearInterval( nInterval ); 5265 bMaxHeightSet = true; 5266 } 5267 else if ( bMaxHeightSet ) 5268 { 5269 $Content.css( 'max-height', 'none' ); 5270 bMaxHeightSet = false;
5271 } 5272 }; 5273 5274 nInterval = window.setInterval( fnCheckHeight, 250 ); 5275 fnCheckHeight(); 5276 }); 5277} 5278 5279 5280let g_bUseNavigationAPI = !!( typeof window != 'undefined' && window.navigation ); 5281let g_bBoundNavEvent = false; 5282 5283function BindNavEvent() 5284{ 5285 if ( !g_bBoundNavEvent ) 5286 { 5287 window.navigation.addEventListener('navigate', function (e) 5288 { 5289 if ( e.info == 'local' ) 5290 { 5291 e.intercept({ 5292 async handler() { 5293 }, 5294 }); 5295 } 5296 }); 5297 g_bBoundNavEvent = true; 5298 } 5299} 5300 5301/** 5302 * Update history state, either the browser history API or navigation API, whichever we are using. 5303 * @param fnUpdater and updater that will receive current state and should return new state, eg `( state ) => ( { ...state, ...yourState } )` 5304 */ 5305function ReplaceHistoryState( fnUpdater /* : ( state: T ) => T */ ) 5306{ 5307 const newState = fnUpdater( GetHistoryState() ); 5308 if ( g_bUseNavigationAPI ) 5309 { 5310 window.navigation.updateCurrentEntry( { state: newState } ); 5311 } 5312 else 5313 { 5314 history.replaceState( newState, '' ); 5315 } 5316} 5317 5318 5319/** 5320 * Replace the current browser URL, navigation API-aware 5321 */ 5322function ReplaceHistoryURL( url /*: string | URL */ ) 5323{ 5324 let strURL = typeof url == "string" ? url : url.toString(); 5325 if ( g_bUseNavigationAPI ) 5326 { 5327 if ( window.navigation.currentEntry.url != strURL ) 5328 { 5329 BindNavEvent(); 5330 window.navigation.navigate( strURL, { state: window.navigation.currentEntry.getState(), history: "replace", info: "local" } ); 5331 } 5332 } 5333 else 5334 { 5335 if ( window.location.href != strURL ) 5336 history.replaceState( history.state, '', strURL ); 5337 } 5338} 5339 5340 5341/** 5342 * Replace the current browser URL and state, navigation API-aware 5343 * @param fnUpdater 5344 * @param url 5345 */ 5346function ReplaceHistoryStateAndURL( fnUpdater /*: ( state: T ) => T*/, url /*: string | URL*/, bPushState ) 5347{ 5348 let newState = fnUpdater( GetHistoryState() ); 5349 if ( g_bUseNavigationAPI ) 5350 { 5351 BindNavEvent(); 5352 window.navigation.navigate( typeof url == "string" ? url : url.toString(), { state: newState, history: bPushState ? "push" : "replace", info: "local" } ); 5353 } 5354 else 5355 { 5356 if ( bPushState ) 5357 history.pushState( newState, '', url ); 5358 else 5359 history.replaceState( newState, '', url ); 5360 } 5361} 5362 5363 5364/** 5365 * Get current state, Navigation API-aware. Should be paired with ReplaceHistoryState. 5366 * 5367 * Guarantted to return an object 5368 */ 5369function GetHistoryState() 5370{ 5371 let state; 5372 if ( g_bUseNavigationAPI ) 5373 state = window.navigation.currentEntry.getState(); 5374 else 5375 state = window.history && window.history.state; 5376 5377 return state ? state : {}; 5378} 5379
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.