1"use strict"; 2 3class PageManager { 4 constructor(model, anonUserId) { 5 this.ApplicationId = model.applicationId; 6 this.PageId = model.pageId; 7 this.Url = encodeURI(window.location.href); 8 this.ObjectId = model.objectId; 9 this.OriginalObjectId = model.originalObjectId; 10 this.OriginalParentObjectId = model.originalParentObjectId; 11 this.LoginProfileId = model.loginProfileId; 12 this.IpAddress = model.ipAddress; 13 this.Unpub = model.unpub; 14 this.SectionToLoadCount = 0; 15 this.SectionLoadedCount = 0; 16 this.WidgetsToLoadCount = 0; 17 this.WidgetsLoadedCount = 0; 18 this.AnonUserId = anonUserId; 19 this.CartCount = -1; 20 this.CartTotal = -1.0; 21 this.GetCartCount(); //This will be populated the first time cart count is retrieved by something and available thereafter. 22 this.AutoSuggestTitles = new Array(); 23 this.Institutions = new Array(); 24 this.eaPropToggles = new Array(); 25 if (model.model) { 26 this.RegisteredOrganizationId = model.model.registeredOrganizationId; 27 this.RegisteredOrg = model.model.registeredOrg; 28 this.AppName = model.model.appName; 29 if (model.model.object != null) { 30 this.HasAssociatedObject = true; 31 this.AssociatedObjectPrimaryTitle = model.model.primaryTitle; 32 this.DataType = model.model.dataType; 33 this.YOP = model.model.yop; 34 } 35 else { 36 this.HasAssociatedObject = false; 37 this.AssociatedObjectPrimaryTitle = ""; 38 this.DataType = model.model.dataType; 39 this.YOP = model.model.yop; 40 } 41 } 42 //This gets and stores url parameters that are on the page for use in whatever code you need. 43 this.UrlParams = new URLSearchParams(window.location.search); 44 //Execute any on load TOAST. 45 //New feature where if query string parameters for TOAST exist, we can display the toast. 46 if (this.UrlParams.get("TOAST") != null) { 47 //parse the toast message. 48 //_pageManager.Toast("Link copied to clipboard", "success", 3000); 49 //_pageManager.Toast("Info test", "info", 3000); 50 //_pageManager.Toast("Warning test", "warning", 3000); 51 //_pageManager.Toast("Error test", "error", 3000); 52 var toastParamSplit = this.UrlParams.get("TOAST").split('|'); 53 //0 = Message 54 //1 = Level 55 //2 time to display 56 this.Toast(toastParamSplit[0], toastParamSplit[1], toastParamSplit[2]); 57 } 58 59 } 60 61 ////Page Scaffolding 62 ScaffoldPage() { 63 let obj = this; 64 65 //To load synchronously, first we need to get the count of sections on page to load. 66 //obj.SectionToLoadCount = $('.sectionPlaceHolder').length; 67 const $sections = $('.sectionPlaceHolder').not('[data-ssr="1"]'); 68 obj.SectionToLoadCount = $sections.length; 69 70 if (obj.SectionToLoadCount == 0) { 71 obj.SectionFinished(); 72 } 73 else { 74 //Sections the server already rendered (data-ssr="1") are left as they are - their request has nothing the server didn't have. 75 //(Server-rendered widgets are still re-fetched by LoadWidget: the browser sends things the server render doesn't have, 76 //such as the page's objectId, AnonUserId and data-properties, so its version can differ.) 77 $sections.each(function () { 78 79 var $section = $(this); 80 var sectionId = $section.attr('data-sectionId'); 81 obj.AjaxWithRetry({ 82 type: "GET", 83 url: "/Page/SectionGet?sectionId=" + sectionId + "&applicationId=" + obj.ApplicationId + "&unpub=" + obj.Unpub, 84 async: true, 85 dataType: 'json', 86 success: function (data) { 87 88 89 90 $section.html(data.sectionModel.html); 91 //Increment section loaded count, then call routine to see if all sections loaded yet or not. 92 93 obj.SectionLoadedCount++; 94 obj.SectionFinished(); 95 }, 96 error: function () { 97 $section.html("Error loading section"); 98 obj.SectionLoadedCount++; 99 obj.SectionFinished(); 100 } 101 }); 102 }); 103 } 104 105 obj.AllLoadActions(); 106 107 obj.PostLoadBindings(); 108 109 return true; 110 } 111 112 //This routine gets called each time a section finishes loading. When all have finished loading, it will load widgets 113 SectionFinished() { 114 let obj = this; 115 if (obj.SectionLoadedCount >= obj.SectionToLoadCount) { 116 117 //All sections loaded, so load any widgets 118 119 obj.WidgetsToLoadCount = $('.widgetPlaceHolder').length; 120 if (obj.WidgetsToLoadCount == 0) { 121 //Got no widgets 122 obj.WidgetLoaded(); 123 } 124 else { 125 126 /*Load Widgets ASYNC*/ 127
128 if ($('.widgetPlaceHolder[data-priority]').length == 0) { 129 //No priority widgets, so do them all 130 $('.widgetPlaceHolder').each(function () { 131 var $widgetPlaceHolder = $(this); 132 $widgetPlaceHolder.attr('data-objectId', obj.ObjectId); 133 obj.LoadWidget($widgetPlaceHolder); 134 }); 135 } 136 else { 137 138 var widgetDelay = 750; 139 140 $('.widgetPlaceHolder[data-priority="1"]').each(function () { 141 var $widgetPlaceHolder = $(this); 142 $widgetPlaceHolder.attr('data-objectId', obj.ObjectId); 143 obj.LoadWidget($widgetPlaceHolder); 144 }); 145 146 setTimeout(function () { 147 $('.widgetPlaceHolder[data-priority="2"]').each(function () { 148 var $widgetPlaceHolder = $(this); 149 $widgetPlaceHolder.attr('data-objectId', obj.ObjectId); 150 obj.LoadWidget($widgetPlaceHolder); 151 }); 152 153 setTimeout(function () { 154 $('.widgetPlaceHolder[data-priority="3"]').each(function () { 155 var $widgetPlaceHolder = $(this); 156 $widgetPlaceHolder.attr('data-objectId', obj.ObjectId); 157 obj.LoadWidget($widgetPlaceHolder); 158 }); 159 160 setTimeout(function () { 161 $('.widgetPlaceHolder:not([data-priority="1"]):not([data-priority="2"]):not([data-priority="3"])').each(function () { 162 var $widgetPlaceHolder = $(this); 163 $widgetPlaceHolder.attr('data-objectId', obj.ObjectId); 164 obj.LoadWidget($widgetPlaceHolder); 165 }); 166 167 try { 168 PostLoadActions(); 169 } 170 catch (err) { 171 //ignore 172 } 173 174 }, widgetDelay); 175 176 }, widgetDelay); 177 178 }, widgetDelay); 179 180 } 181 182 } 183 184 //Make skiplinks if needed 185 obj.MakeSkipLinks(); 186 } 187 188 obj.PostLoadBindings(); 189 } 190 191 LoadWidget($widgetPlaceHolder) { 192 let obj = this; 193 194 195 196 //Get HTML attributes 197 var widgetId = $widgetPlaceHolder.attr('data-widgetId'); 198 199 var widgetObjectId = obj.ObjectId; 200 201 //On occasion, we may want the widget to operate on a different ObjectId than the one on the page, for example Quizzes, where the page deals with the LA/LAS Objects etc 202 var overridingObjectId = $widgetPlaceHolder.attr('data-objectId'); 203 if (typeof overridingObjectId !== 'undefined' && overridingObjectId != null) { 204 widgetObjectId = overridingObjectId; 205 } 206 207 208 var widgetAutoLoad = $widgetPlaceHolder.attr('data-autoLoad'); 209 var widgetObjectIdsFilter = $widgetPlaceHolder.attr('data-objectIdsFilter'); 210 var widgetGalleryIdFilter = $widgetPlaceHolder.attr('data-galleryIdFilter'); 211 var widgetObjectDefinitionIdsFilter = $widgetPlaceHolder.attr('data-objectDefinitionIdsFilter'); 212 var widgetHardTypesFilter = $widgetPlaceHolder.attr('data-hardTypesFilter'); 213 var widgetCategoryObjectIdsFilter = $widgetPlaceHolder.attr('data-categoryObjectIdsFilter'); 214 var widgetTagIdsFilter = $widgetPlaceHolder.attr('data-tagIdsFilter'); 215 var widgetListIdsFilter = $widgetPlaceHolder.attr('data-listIdsFilter'); 216 var widgetSearchTermFilter = $widgetPlaceHolder.attr('data-searchTermFilter') ?? ""; 217 var widgetCustomFilter = $widgetPlaceHolder.attr('data-customFilter') ?? ""; 218 var widgetOrderBy = $widgetPlaceHolder.attr('data-orderBy'); 219 var widgetFavorites = $widgetPlaceHolder.attr('data-favorites'); 220 var widgetRecentlyWatched = $widgetPlaceHolder.attr('data-RecentlyWatched'); 221 var widgetWatchedNotFinished = $widgetPlaceHolder.attr('data-WatchedNotFinished'); 222 if (!widgetWatchedNotFinished) { 223 //Backward compatibaility for typo that may exist in existing widgets 224 widgetWatchedNotFinished = $widgetPlaceHolder.attr('data-WatchedNotFinshed'); 225 } 226 227 var widgetLearningNotComplete = $widgetPlaceHolder.attr('data-learningNotComplete'); 228 var widgetLearningComplete = $widgetPlaceHolder.attr('data-learningComplete'); 229 var widgetTake = $widgetPlaceHolder.attr('data-Take'); 230 var widgetMeta = $widgetPlaceHolder.attr('data-meta'); 231 var widgetSearchResultCallBack = $widgetPlaceHolder.attr('data-SearchResultCallBack'); //This gets set as the call back for a search results widget which typically is calling back to the paging parent widget to set search results. 232 var widgetTargetWidgetId = $widgetPlaceHolder.attr('data-TargetWidgetId'); 233 var widgetHeaderTitle = ($widgetPlaceHolder.attr('data-widgetheadertitle') ?? $widgetPlaceHolder.attr('data-title')) ?? ""; 234 var widgetHeaderUrl = $widgetPlaceHolder.attr('data-WidgetHeaderUrl') ?? ""; 235 var overrideSafeId = $widgetPlaceHolder.attr('data-overridesafeid') ?? widgetId; 236 var relatedObjectDefinitionsFilter = $widgetPlaceHolder.attr('data-relatedObjectDefinitionsFilter'); 237 var widgetAuthedOnly = $widgetPlaceHolder.attr('data-authedonly'); 238 var widgetPublicOnly = $widgetPlaceHolder.attr('data-publiconly'); 239 var widgetMostWatched = $widgetPlaceHolder.attr('data-MostWatched'); 240 var widgetSearchType = $widgetPlaceHolder.attr('data-SearchType') ?? ""; 241 var widgetFilterOptions = $widgetPlaceHolder.attr('data-FilterOptions') ?? ""; 242 var widgetSpecificListSearch = $widgetPlaceHolder.attr('data-SpecificListSearch') ?? ""; 243 var widgetCarouselAutoScroll = $widgetPlaceHolder.attr('data-carouselautoscroll') ?? ""; 244 var widgetProperties = $widgetPlaceHolder.attr('data-properties') ?? ""; 245 var widgetLoad = $widgetPlaceHolder.attr('data-Load') ?? ""; 246 247 248 var widgetRequestModel = { 249 WidgetId: widgetId, 250 ApplicationId: obj.ApplicationId, 251 ObjectId: widgetObjectId, 252 EntitySummary: '', 253 AutoLoad: widgetAutoLoad, 254 ObjectIdsFilter: widgetObjectIdsFilter, 255 GalleryIdFilter: widgetGalleryIdFilter, 256 ObjectDefinitionIdsFilter: widgetObjectDefinitionIdsFilter, 257 HardTypesFilter: widgetHardTypesFilter, 258 CategoryObjectIdsFilter: widgetCategoryObjectIdsFilter, 259 ListIdsFilter: widgetListIdsFilter, 260 TagIdsFilter: widgetTagIdsFilter, 261 SearchTermFilter: widgetSearchTermFilter, 262 CustomFilter: widgetCustomFilter, 263 OrderBy: widgetOrderBy, 264 Favorites: widgetFavorites, 265 RecentlyWatched: widgetRecentlyWatched, 266 WatchedNotFinished: widgetWatchedNotFinished, 267 LearningNotComplete: widgetLearningNotComplete, 268 LearningComplete: widgetLearningComplete, 269 Take: widgetTake, 270 Unpub: obj.Unpub, 271 AnonUserId: obj.AnonUserId, 272 Meta: widgetMeta, 273 SearchResultCallBack: widgetSearchResultCallBack, 274 TargetWidgetId: widgetTargetWidgetId, 275 WidgetHeaderTitle: widgetHeaderTitle, 276 WidgetHeaderUrl: widgetHeaderUrl, 277 OverrideSafeId: overrideSafeId, 278 RelatedObjectDefinitionsFilter: relatedObjectDefinitionsFilter, 279 AuthedOnly: widgetAuthedOnly, 280 PublicOnly: widgetPublicOnly, 281 MostWatched: widgetMostWatched, 282 SearchType: widgetSearchType, 283 FilterOptions: widgetFilterOptions, 284 SpecificListSearch: widgetSpecificListSearch, 285 CarouselAutoScroll: widgetCarouselAutoScroll, 286 Properties: widgetProperties, 287 Load: widgetLoad 288 }; 289
290 var safeId = obj.GetSafeId(overrideSafeId); 291 var thisWidget = $widgetPlaceHolder; 292 293 //Routine to prevent accidental double loading. 294 if ($(thisWidget).hasClass('widget-loaded')) { 295 return; 296 } 297 else { 298 $(thisWidget).addClass('widget-loaded'); 299 } 300 301 obj.AjaxWithRetry({ 302 type: "Post", 303 url: "/Page/WidgetGet", 304 async: true, 305 data: widgetRequestModel, 306 dataType: 'json', 307 success: function (data) { 308 309 if ($(thisWidget)[0].hasAttribute("data-removewidget")) { 310 $(thisWidget).before(data.widgetModel.html); 311 } 312 else { 313 $widgetPlaceHolder.html(data.widgetModel.html); 314 } 315 316 317 try { 318 //Call the widget specific Javascript function 319 //Function name will be PostLoadActions_{safeId} - this avoids duplication of events ie if more than 1 per page 320 //safeId will be the converted widgetId; Javascript Function names don't allow "-", so they're replaced with "_" 321 setTimeout(function () { 322 try { 323 //Check if function exists before calling. 324 if (typeof window["PostLoadActions_" + safeId] === "function") { 325 window["PostLoadActions_" + safeId](widgetId); 326 } 327 } 328 catch (err) { 329 //ignore - as its not mandatory 330 console.log("Post Load Actions Error from Page Manager"); 331 console.log("Calling Widget"); 332 console.log(widgetId); 333 console.log(err); 334 } 335 336 //update next, we want to add any child widgets to the queue. These are widgets within widgets 337 //that need to load after the parent loads. 338 if ($(thisWidget)[0].hasAttribute("data-removewidget")) { 339 //We need to do the parent. 340 var thisWidgetParent = $(thisWidget).parent(); 341 $(thisWidget).remove(); 342 $(thisWidgetParent).find('.widgetPlaceHolder').each(function () { 343 obj.LoadWidget($(this)); 344 }); 345 } 346 else { 347 $(thisWidget).find('.widgetPlaceHolder').each(function () { 348 obj.LoadWidget($(this)); 349 }); 350 } 351 352 obj.PostLoadBindings(); 353 354 }, 10); 355 } 356 catch (err) { 357 //ignore 358 console.log("Page Manager Widget Load Error") 359 console.log(err); 360 } 361 362 obj.WidgetLoaded(); 363 }, 364 error: function () { 365 $widgetPlaceHolder.html("Error Loading Widget"); 366 obj.WidgetLoaded(); 367 } 368 }); 369 370 } 371 372 //This routine gets called each time a widget gets loaded. When all have finished loading, it will run accessibility 373 WidgetLoaded() { 374 let obj = this; 375 376 obj.WidgetsLoadedCount++; 377 378 if (obj.WidgetsLoadedCount >= obj.WidgetsToLoadCount) { 379 setTimeout(function () { 380 obj.MakePageAccessible(); 381 }, 100); 382 } 383 } 384 385 PostLoadBindings() { 386 if (typeof SiteWideBinding !== 'undefined' && SiteWideBinding != null) { 387 SiteWideBinding(); 388 } 389 390 391 if (typeof BindSiteWideMessaging != 'undefined' && BindSiteWideMessaging != null) { 392 BindSiteWideMessaging(); 393 } 394 } 395 396 GetSafeId(id) { 397 //Used to append WidgetId (for example) to Javascript functions, to avoid duplication. 398 //Javascript Function names don't allow "-", so they're replaced with "_" 399 return id.replaceAll("-", "_").toLowerCase(); 400 } 401 402 //Sends a $.ajax request, and if the server says it's busy (429/503 from MediaSite's rate limiting), tries it again - up to 2 more times. 403 //Waits for the server's Retry-After time plus a random extra, so a page's widgets don't all retry at the same moment. 404 //Any other error, or still busy after the retries, goes to the caller's error handler as before. 405 AjaxWithRetry(ajaxOptions, retriesLeft = 2) { 406 let obj = this; 407 let callerError = ajaxOptions.error; 408 409 $.ajax($.extend({}, ajaxOptions, { 410 error: function (xhr) { 411 if (retriesLeft > 0 && (xhr.status === 429 || xhr.status === 503)) { 412 var retryAfterSeconds = parseInt(xhr.getResponseHeader("Retry-After"), 10); 413 if (isNaN(retryAfterSeconds) || retryAfterSeconds < 1) { 414 retryAfterSeconds = 2; 415 } 416 retryAfterSeconds = Math.min(retryAfterSeconds, 10); 417 418 var delayMs = (retryAfterSeconds + Math.random() * retryAfterSeconds) * 1000; 419 setTimeout(function () { 420 obj.AjaxWithRetry(ajaxOptions, retriesLeft - 1); 421 }, delayMs); 422 return; 423 } 424 425 if (typeof callerError === "function") { 426 callerError.apply(this, arguments); 427 } 428 } 429 })); 430 } 431 432 //Widget Events 433 RefreshWidget(targetWidgetId) { 434 var obj = this; 435 436 var $widgetPlaceHolder = $('.widgetPlaceHolder[data-widgetId=' + targetWidgetId + ']'); 437 438 obj.LoadWidget($widgetPlaceHolder); 439 } 440 441 TriggerWidgetEvent(targetWidgetId, event, params) { 442 var obj = this; 443 444 try { 445 446 //Make sure we've got a widget with this Id on the page' 447 var $widgetPlaceHolder = $('.widgetPlaceHolder[data-widgetId=' + targetWidgetId + ']'); 448 449 //Now call the Widget-specific functions, passing on any params 450 if ($widgetPlaceHolder) { 451 //ExternalTrigger(targetWidgetId, event.toLowerCase(), params); 452 //Call the widget specific Javascript function
453 //Function name will be ExternalTrigger_{safeId} - this avoids duplication of events ie if more than 1 per page 454 //safeId will be the converted widgetId; Javascript Function names don't allow "-", so they're replaced with "_" 455 //That event will locally respond to the actual event name (event - which will always be expected to be lowercase) 456 window['ExternalTrigger_' + obj.GetSafeId(targetWidgetId)](targetWidgetId, event.toLowerCase(), params); 457 } 458 } 459 catch (err) { } 460 461 } 462 463 //Login (Cadmore Login) 464 Login() { 465 let obj = this; 466 467 window.location = location.origin + "/Authenticate/ConnectToAuth?applicationId=" + obj.ApplicationId + "&loginProfileId=" + obj.LoginProfileId + "&ipAddress=" + obj.IpAddress + "&returnUrl=" + obj.Url; 468 } 469 470 //Register (Cadmore Login) - similar to Login, but auto goes to 'User Register' mode 471 Register() { 472 let obj = this; 473 474 window.location = location.origin + "/Authenticate/ConnectToAuthRegister?applicationId=" + obj.ApplicationId + "&loginProfileId=" + obj.LoginProfileId + "&ipAddress=" + obj.IpAddress + "&returnUrl=" + obj.Url; 475 } 476 477 //Login OAuth (Client Specific) 478 LoginOAuth() { 479 let obj = this; 480 481 $.post("/Authenticate/RedirectToOAuthUrl", { loginProfileId: obj.LoginProfileId }, function (data) { 482 window.location = data.url; 483 }); 484 } 485 486 //Login OIDC (Client Specific) 487 LoginOidc() { 488 let obj = this; 489 490 $.post("/Authenticate/RedirectToOidcUrl", { loginProfileId: obj.LoginProfileId }, function (data) { 491 window.location = data.url; 492 }); 493 } 494 495 //Login SSO 496 //creates the idp url and redirects the user to the SAML provider, who should then Authenticate user (if not already authenticated) and send SAML Response to {appUrl}/SAML/AssertionService which'll get redirected to {login.web}/SAML/AssertionService 497 LoginSSO() { 498 let obj = this; 499 500 $.post("/Authenticate/RedirectToSSOUrl", { loginProfileId: obj.LoginProfileId }, function (data) { 501 window.location = data.url; 502 }); 503 } 504 505 Logout(entityId) { 506 //if entity = null, logs everything out 507 508 let obj = this; 509 510 window.location = location.origin + "/Authenticate/ConnectToAuthLogout?applicationId=" + obj.ApplicationId + "&loginProfileId=" + obj.LoginProfileId + "&entityId=" + (entityId ?? "") + "&returnUrl=" + obj.Url; 511 } 512 513 //LoginIp 514 LoginIp() { 515 //Basically, clears out lastIp checked, and reloads page - which should ultimately trigger IP check on next page load (PageController) 516 let obj = this; 517 $.post("/Authenticate/ResetLastIp", null, function (data) { 518 obj.ReloadPage("Your IP Authentication has been refreshed|info|3000"); 519 }); 520 } 521 522 ReloadPage(toast = "") { 523 let obj = this; 524 $("#iframeLogin").remove(); 525 526 527 if (toast !== "") { 528 var url = new URL(location.href); 529 url.searchParams.set('TOAST', toast); 530 location.assign(url.search); 531 } 532 else { 533 window.location.reload(); 534 } 535 } 536 537 ReloadPageWithoutQueryStringParameters() { 538 var url = window.location.origin + window.location.pathname; 539 window.location.href = url; 540 } 541 542 //RefreshLoginContext 543 RefreshLoginContext() { 544 $.post("/Page/RefreshLoginContext", null, null); 545 } 546 547 Authed() { 548 let obj = this; 549 //Kill the iframe. Firefox bug. 550 $("#iframeLogin").remove(); 551 $("#pageAuthModal").remove(); 552 window.location.reload(); 553 } 554 555 //Toasts/Alerts 556 Alert(message) { 557 alert('***pagemanager*** ' + message); 558 } 559 560 Toast(text, type, durationMilliSeconds) { 561 562 563 //type = "success", "info, "warning or "error 564 //type determines the color and icon, typically: success=green, info=blue, warning=yellow, error=red, but ultimately set in client-specific css 565 566 //Examples; 567 //_pageManager.Toast("Link copied to clipboard", "success", 3000); 568 //_pageManager.Toast("Info test", "info", 3000); 569 //_pageManager.Toast("Warning test", "warning", 3000); 570 //_pageManager.Toast("Error test", "error", 3000); 571 572 var fadeTime = 1000; //How long it takes to fade out after being removed 573 574 //Get, or create the toast container - doesn't really matter where it is, as client-specific css will control it 575 var $toastContainer = $('.toast-container'); 576 if ($toastContainer.length === 0) { 577 $('body').append($('<div>').addClass('toast-container')); 578 } 579 580 //Configure the closing/fade-out of the toast 581 var closeToast = function ($toast) { 582 //Add the fading class and remove the 'click' binding 583 $toast 584 .addClass('fading') 585 .off('click'); 586 587 // Fade the toast message out and remove it 588 $toast.fadeOut(fadeTime, function () { 589 $toast.remove(); 590 }); 591 } 592 593 //Create the type-specific icon 594 var iconName = ''; 595 switch (type) { 596 case 'success': 597 iconName = 'fa-circle-check'; 598 break; 599 case 'info': 600 iconName = 'fa-circle-info'; 601 break; 602 case 'warning': 603 iconName = 'fa-triangle-exclamation'; 604 break; 605 case 'error': 606 iconName = 'fa-circle-exclamation'; 607 break; 608 } 609 var $icon = ""; 610 if (iconName.length > 0) { $icon = $('<div>').addClass('toast-icon').append($('<i>').addClass('fa ' + iconName)) }; 611 612 //Build the toast message 613 var $toast = $('<div>') 614 .addClass('toast ' + type) 615 .append($('<div>') 616 .addClass('toast-inner') 617 .append($icon) 618 .append($('<div>') 619 .addClass('text') 620 .text(text))); 621 622 //Add message to toast-container 623 $toastContainer.append($toast); 624 625 //Add event listener to close toast if clicked 626 $toast.on('click', function () { closeToast($toast); }); 627 628 // Automatic close after durationMilliSeconds 629 setTimeout(function () { closeToast($toast); }, durationMilliSeconds); 630 631 } 632 633 634 // Generic Dialog Modal 635 // Example use
636 // _pageManager.genericDialogModal_Open('Confirm Delete', 'Are you sure you want to DELETE this?', 'No', '', 'Yes', 'deleteSomething(`id`)'); 637 // _pageManager.genericDialogModal_Open("Confirm Delete", "Are you sure you want to DELETE this clip?", "No", "", "Yes", "$('#clipDeleteForm').submit();"); 638 genericDialogModal_Open(title, prompt, button1Text, button1OnClick, button2Text, button2OnClick) { 639 $("#genericDialogModalTitle").html(title); 640 $("#genericDialogModalPrompt").html(prompt); 641 642 if (button1Text && button1Text.length) { 643 $("#genericDialogModalButton1").html(button1Text); 644 $("#genericDialogModalButton1").attr("aria-label", button1Text); 645 $("#genericDialogModalButton1").attr("onclick", "_pageManager.genericDialogModal_Close();" + (button1OnClick ?? "")); 646 $("#genericDialogModalButton1").show(); 647 } 648 else { 649 $("#genericDialogModalButton1").html(""); 650 $("#genericDialogModalButton1").attr("aria-label", ""); 651 $("#genericDialogModalButton1").attr("onclick", ""); 652 $("#genericDialogModalButton1").hide(); 653 } 654 655 if (button2Text && button2Text.length) { 656 $("#genericDialogModalButton2").html(button2Text); 657 $("#genericDialogModalButton2").attr("aria-label", button1Text); 658 $("#genericDialogModalButton2").attr("onclick", "_pageManager.genericDialogModal_Close();" + (button2OnClick ?? "")); 659 $("#genericDialogModalButton2").show(); 660 } 661 else { 662 $("#genericDialogModalButton2").html(""); 663 $("#genericDialogModalButton2").attr("aria-label", ""); 664 $("#genericDialogModalButton2").attr("onclick", ""); 665 $("#genericDialogModalButton2").hide(); 666 } 667 668 //Show Overlay 669 $(".genericDialogModal_overlay").css("opacity", "0.5"); 670 $(".genericDialogModal_overlay").css("visibility", "visible"); 671 672 //Show Dialog 673 $("#genericDialogModal").show(); 674 } 675 676 genericDialogModal_Close() { 677 //Reset Values 678 $("#genericDialogModalTitle").html(""); 679 $("#genericDialogModalPrompt").html(""); 680 $("#genericDialogModalButton1").html(""); 681 $("#genericDialogModalButton1").attr("aria-label", ""); 682 $("#genericDialogModalButton1").attr("onclick", ""); 683 $("#genericDialogModalButton1").hide(); 684 $("#genericDialogModalButton2").html(""); 685 $("#genericDialogModalButton2").attr("aria-label", ""); 686 $("#genericDialogModalButton2").attr("onclick", ""); 687 $("#genericDialogModalButton2").hide(); 688 689 //Show Overlay 690 $(".genericDialogModal_overlay").css("opacity", "0"); 691 $(".genericDialogModal_overlay").css("visibility", "hidden"); 692 693 //Hide Dialog 694 $("#genericDialogModal").hide(); 695 } 696 697 698 //Cadmore Password Reset 699 PasswordReset() { 700 $("#modalPasswordReset").show(); 701 } 702 703 PasswordResetRedirect() { 704 var url = "/Authenticate/PasswordReset"; 705 window.location.href = url; 706 707 } 708 709 710 711 //Favorites 712 ///Callback is a functon to call after the favorite or unfavorite is done. it will pass True if just favorited and False if not. 713 //RegisterForEventObjectId is the evnet object id when favoriting is also tied to Reminders/RSVP feature. 714 FavoriteToggle(element, entityId, objectId, toggle = false, registerForEventObjectId = "", callback = null) { 715 716 var obj = this; 717 try { 718 var $element = $(element); 719 if ($element) { 720 721 //This function can be called by any element with toggle true, and if so, will set the toggle status to the opposite of whatever it is now. 722 //This is useful to just call this function favorite without more jscript needed to set status first. Not always applicable. 723 if (toggle) { 724 if ($element.hasClass('isfavorite')) { 725 $element.removeClass('isfavorite'); 726 } 727 else { 728 $element.addClass('isfavorite'); 729 } 730 } 731 732 733 var fav = false; 734 //Determine desired Favorite status (ie value after the current toggle action) 735 //Different widgets use different methods, ie ACAMH uses checkbox, Endocrine use buttons (using a class isfavorite) 736 737 if ($element.is(":checkbox")) { 738 fav = ($element.prop('checked')); 739 } else { 740 fav = $element.hasClass("isfavorite"); 741 } 742 743 //Save 744 $.post("/Page/FavoriteSet", { ApplicationId: obj.ApplicationId, EntityId: entityId, ObjectId: objectId, IsFavorite: fav, RegisterForEventObjectId: registerForEventObjectId }, function (data) { 745 746 if (data) { 747 748 //Saved ok, so update UI attribute of other instances of this object 749 if (fav) { 750 //Add favorite to 751 //$element.attr("data-isfavorite", "true"); 752 $('.check-fav-' + objectId).prop('checked', true); 753 $('.fav-tooltiptext-' + objectId).html('Remove from favorites'); 754 $('.object-favorite-' + objectId).attr('data-tip', 'Remove from favorites'); 755 756 } 757 else { 758 //Remove Favorite 759 //$element.attr("data-isfavorite", "false"); 760 $('.check-fav-' + objectId).prop('checked', false); 761 $('.fav-tooltiptext-' + objectId).html('Add to favorites'); 762 $('.object-favorite-' + objectId).attr('data-tip', 'Add to favorites'); 763 764 } 765 766 if (callback != null) { 767 try { 768 callback(fav); 769 } 770 catch (ex) { 771 console.log(ex); 772 } 773 } 774 } 775 }); 776 777 } 778 } 779 catch (err) { } 780 } 781
782 VoteBestToggle(element, entityId, objectId, toggle = false) { 783 784 var obj = this; 785 try { 786 var $element = $(element); 787 if ($element) { 788 789 //This function can be called by any element with toggle true, and if so, will set the toggle status to the opposite of whatever it is now. 790 //This is useful to just call this function favorite without more jscript needed to set status first. Not always applicable. 791 if (toggle) { 792 if ($element.hasClass('isvotebest')) { 793 $element.removeClass('isvotebest'); 794 } 795 else { 796 $element.addClass('isvotebest'); 797 } 798 } 799 800 801 var votebest = false; 802 //Determine desired isvotebest status (ie value after the current toggle action) 803 //Different widgets use different methods, ie ACAMH uses checkbox, Endocrine use buttons (using a class isfavorite) 804 805 if ($element.is(":checkbox")) { 806 votebest = ($element.prop('checked')); 807 } else { 808 votebest = $element.hasClass("isvotebest"); 809 } 810 811 812 813 //Save 814 $.post("/Page/VoteBestSet", { ApplicationId: obj.ApplicationId, EntityId: entityId, ObjectId: objectId, VoteBest: votebest }, function (data) { 815 816 if (data) { 817 if (data.resultsModel == true) { 818 //Saved ok, so update UI attribute of other instances of this object 819 if (votebest) { 820 //Add favorite to 821 //$element.attr("data-isfavorite", "true"); 822 $('.check-votebest-' + objectId).prop('checked', true); 823 $('.votebest-tooltiptext-' + objectId).html('Remove Vote for Best'); 824 $('.object-votebest-' + objectId).attr('data-tip', 'Remove from Vote for Best'); 825 826 } 827 else { 828 //Remove Favorite 829 //$element.attr("data-isfavorite", "false"); 830 $('.check-votebest-' + objectId).prop('checked', false); 831 $('.votebest-tooltiptext-' + objectId).html('Vote for the Best'); 832 $('.object-votebest-' + objectId).attr('data-tip', 'Vote for the Best'); 833 } 834 } 835 } 836 }); 837 838 } 839 } 840 catch (err) { } 841 } 842 843 //Lists 844 //Relies on MediaSite-specific modal (see ProjectR:Film for example) 845 ShowListModal(objectId, objectName, userClip = false) { 846 //Opens a MediaSite-specific AddToList modal 847 //The following javascript funtion "showListModal(objectId)" is required in a client specific widget - see Projector 848 //objectId is the chosen object, so the lists will show whether this objectId is already in the lists - ok to send nothing 849 showListModal(objectId, objectName, userClip); 850 } 851 852 //Relies on MediaSite-specific modal 853 ShowAddListModal() { 854 //Opens a MediaSite-specific modal 855 showAddListModal(); 856 } 857 858 //Relies on MediaSite-specific modal 859 ShowDeleteListModal(listId, listName) { 860 //Opens a MediaSite-specific modal 861 showDeleteListModal(listId, listName); 862 } 863 864 //Relies on MediaSite-specific modal 865 ShowRenameListModal(listId, listName) { 866 //Opens a MediaSite-specific modal 867 showRenameListModal(listId, listName); 868 } 869 870 LoadCadmoreEntityLists(entityId, listContainer, template, objectId, onCompleteFunction) { 871 //objectId is the object being added to a list (if required - otherwise pass null) 872 //onCompleteFunction - name of js function (eg on widget) that needs to run on completion - eg a carousel splide mount 873 let obj = this; 874 var listCount = 0; 875 876 try { 877 if (entityId) { 878 //Get Entity's Lists 879 $.post("/Page/EntityLists", { entityId: entityId, applicationId: obj.ApplicationId }, function (data) { 880 if (data) { 881 $(listContainer).empty(); 882 883 if (data.resultsModel && data.resultsModel.length > 0) { 884 listCount = data.resultsModel.length; 885 var templateMarkup = $(template).html(); 886 887 //Loop thru results, adding to UI 888 for (let i = 0; i < data.resultsModel.length; i++) { 889 try { 890 var $itemMarkup = obj.BuildCadmoreListItemMarkup(data.resultsModel[i], templateMarkup, i, objectId); 891 $(listContainer).append($itemMarkup); 892 893 //Set Checkbox, if objectId is in the list 894 if (objectId && data.resultsModel[i].objectList && data.resultsModel[i].objectList.length) { 895 var objectIsInList = false; 896 var listIncludesObject = data.resultsModel[i].objectList.find(t => t.objectId === objectId); 897 if (listIncludesObject) { objectIsInList = true; } 898 $('#list_' + data.resultsModel[i].id).prop('checked', objectIsInList); 899 } 900 901 902 } 903 catch (err) { } 904 } 905 } 906 907 //onCompleteFunction 908 if (onCompleteFunction && onCompleteFunction.length) { 909 window[onCompleteFunction](listCount); 910 } 911 912 return data; 913 } 914 915 //onCompleteFunction 916 if (onCompleteFunction && onCompleteFunction.length) { 917 window[onCompleteFunction](listCount); 918 } 919 }); 920 921 } 922 } 923 catch (err) { } 924 925 } 926 927 LoadCadmoreEntityListsLimited(entityId, listContainer, template, objectId, take, onCompleteFunction) { 928 //objectId is the object being added to a list (if required - otherwise pass null) 929 //onCompleteFunction - name of js function (eg on widget) that needs to run on completion - eg a carousel splide mount 930 let obj = this; 931 var listCount = 0; 932 933 try { 934 if (entityId) { 935 //Get Entity's Lists 936 $.post("/Page/EntityLists", { entityId: entityId, applicationId: obj.ApplicationId }, function (data) { 937 if (data) { 938 $(listContainer).empty(); 939 940 if (data.resultsModel && data.resultsModel.length > 0) { 941 listCount = data.resultsModel.length; 942 if (take > listCount) { 943 take = listCount; 944 } 945 946 var templateMarkup = $(template).html(); 947 948 //Loop thru results, adding to UI 949 for (let i = 0; i < take; i++) { 950 try { 951 var $itemMarkup = obj.BuildCadmoreListItemMarkup(data.resultsModel[i], templateMarkup, i, objectId); 952 $(listContainer).append($itemMarkup); 953 954 //Set Checkbox, if objectId is in the list 955 if (objectId && data.resultsModel[i].objectList && data.resultsModel[i].objectList.length) { 956 var objectIsInList = false; 957 var listIncludesObject = data.resultsModel[i].objectList.find(t => t.objectId === objectId); 958 if (listIncludesObject) { objectIsInList = true; } 959 $('#list_' + data.resultsModel[i].id).prop('checked', objectIsInList); 960 } 961 } 962 catch (err) { } 963 } 964 } 965 966 //onCompleteFunction 967 if (onCompleteFunction && onCompleteFunction.length) { 968 window[onCompleteFunction](listCount); 969 } 970 971 return data; 972 } 973 974 //onCompleteFunction 975 if (onCompleteFunction && onCompleteFunction.length) { 976 window[onCompleteFunction](listCount); 977 } 978 }); 979 980 } 981 } 982 catch (err) { } 983 984 } 985 986 BuildCadmoreListItemMarkup(item, template, itemIndex, objectId) { 987 var obj = this; 988 //if (item.imageUrl == '') { item.imageUrl = 'https://placehold.co/960x540/46647A/fff?text=Image not available'; } 989 990 var $itemMarkup = template.replaceAll('{{ListId}}', item.id) 991 .replaceAll('{{EntityId}}', item.entityId) 992 .replaceAll('{{ApplicationId}}', item.applicationId) 993 .replaceAll('{{Name}}', item.name) 994 .replaceAll('{{NameNoQuotes}}', obj.RemoveQuotes(item.name)) //Used when passing as a JS parameter, so quotes don't break syntax 995 .replaceAll('{{NameFriendly}}', obj.UrlFriendlyText(item.name)) //Used for url, eg to List page 996 .replaceAll('{{ImageUrl}}', '') 997 .replaceAll('{{ItemCount}}', item.itemCount); 998 999 //Remove any redundant placeholders, eg {{anything}} 1000 var placeholderPattern = /\{\{.*?\}\}/g; 1001 $itemMarkup = $itemMarkup.replace(placeholderPattern, ""); 1002 1003 return $itemMarkup; 1004 } 1005 1006 ListAdd(entityId, newListName, listContainer, template, objectId) { 1007 var obj = this; 1008 try { 1009 if (entityId && newListName) { 1010 //Save 1011 $.post("/Page/ListAdd", { EntityId: entityId, ApplicationId: obj.ApplicationId, Name: newListName }, function (data) { 1012 1013 if (data) { 1014 if (data.resultsModel) { 1015 _pageManager.Toast("New list created : " + newListName, "success", 3000); 1016 //Added, so refresh whole list of lists 1017 if (listContainer) { 1018 obj.LoadCadmoreEntityLists(entityId, listContainer, template, objectId); 1019 } 1020 } 1021 else { 1022 _pageManager.Toast("Error creating list : " + newListName, "error", 3000); 1023 } 1024 } 1025 else { 1026 _pageManager.Toast("Error creating list : " + newListName, "error", 3000); 1027 } 1028 } 1029 ); 1030 1031 } 1032 else { 1033 _pageManager.Toast("Error creating list", "error", 3000); 1034 } 1035 } 1036 catch (err) { } 1037 } 1038 1039 ListAddObject(entityId, listId, objectId, objectName, listName, itemCountLabel, userClip = false) { 1040 var obj = this; 1041 try { 1042 if (listId) { 1043 var origCount = -1; 1044 if (itemCountLabel) { origCount = parseInt($(itemCountLabel).html()); } 1045 1046 //Add Object to List 1047 $.post("/Page/ListAddObject", { EntityId: entityId, ApplicationId: obj.ApplicationId, EntityMediaLibraryListId: listId, ObjectId: objectId, UserClip: userClip }, function (data) { 1048 1049 if (data) { 1050 if (data.resultsModel == true) { 1051 if (origCount >= 0 && itemCountLabel) { 1052 var newCount = origCount + 1;
1053 $(itemCountLabel).html(newCount); 1054 var toastMessage = "'" + objectName + "' added to '" + listName + "'<br/>View your lists <a href='/Lists' class='toast-link'>here</a>"; 1055 obj.Toast(toastMessage, "success", 3000); 1056 } 1057 } 1058 else { 1059 obj.Toast("Error adding '" + objectName + "' to '" + listName + "'", "error", 3000); 1060 } 1061 } 1062 else { 1063 obj.Toast("Error adding '" + objectName + "' to '" + listName + "'", "error", 3000); 1064 } 1065 }); 1066 1067 } 1068 } 1069 catch (err) { } 1070 } 1071 1072 ListRemoveObject(entityId, listId, objectId, objectName, listName, itemCountLabel) { 1073 var obj = this; 1074 try { 1075 if (listId) { 1076 var origCount = -1; 1077 if (itemCountLabel) { origCount = parseInt($(itemCountLabel).html()); } 1078 1079 //remove Item from List 1080 $.post("/Page/ListRemoveObject", { EntityId: entityId, ApplicationId: obj.ApplicationId, EntityMediaLibraryListId: listId, ObjectId: objectId }, function (data) { 1081 1082 if (data) { 1083 if (data.resultsModel == true) { 1084 if (origCount > 0 && itemCountLabel) { 1085 var newCount = origCount - 1; 1086 $(itemCountLabel).html(newCount); 1087 _pageManager.Toast("'" + objectName + "' removed from '" + listName + "'", "success", 3000); 1088 } 1089 } 1090 else { 1091 _pageManager.Toast("Error removing '" + objectName + "' from '" + listName + "'", "error", 3000); 1092 } 1093 } 1094 else { 1095 _pageManager.Toast("Error removing '" + objectName + "' from '" + listName + "'", "error", 3000); 1096 } 1097 }); 1098 1099 } 1100 } 1101 catch (err) { } 1102 } 1103 1104 ListRename(entityId, listId, newListName, listContainer, template, objectId) { 1105 var obj = this; 1106 try { 1107 if (entityId && listId && newListName) { 1108 //Rename 1109 $.post("/Page/ListRename", { Id: listId, EntityId: entityId, ApplicationId: obj.ApplicationId, Name: newListName }, function (data) { 1110 1111 if (data) { 1112 if (data.resultsModel) { 1113 _pageManager.Toast("List renamed", "success", 3000); 1114 //Renamed, so refresh whole list of lists 1115 if (listContainer) { 1116 obj.LoadCadmoreEntityLists(entityId, listContainer, template, objectId); 1117 } 1118 } 1119 else { 1120 _pageManager.Toast("Error renaming list", "error", 3000); 1121 } 1122 } 1123 else { 1124 _pageManager.Toast("Error renaming list", "error", 3000); 1125 } 1126 }); 1127 1128 } 1129 else { 1130 _pageManager.Toast("Error renaming list", "error", 3000); 1131 } 1132 } 1133 catch (err) { } 1134 } 1135 1136 ListDelete(entityId, listId, listContainer, template, objectId) { 1137 var obj = this; 1138 try { 1139 if (entityId && listId) { 1140 //Delete 1141 $.post("/Page/ListDelete", { Id: listId, EntityId: entityId, ApplicationId: obj.ApplicationId, Name: "delete" }, function (data) { 1142 1143 if (data) { 1144 if (data.resultsModel == true) { 1145 _pageManager.Toast("List deleted", "success", 3000); 1146 //Deleted, so refresh whole list of lists 1147 if (listContainer) { 1148 obj.LoadCadmoreEntityLists(entityId, listContainer, template, objectId); 1149 } 1150 } 1151 else { 1152 _pageManager.Toast("Error deleting list", "error", 3000); 1153 } 1154 } 1155 else { 1156 _pageManager.Toast("Error deleting list", "error", 3000); 1157 } 1158 }); 1159 1160 } 1161 else { 1162 _pageManager.Toast("Error deleting list", "error", 3000); 1163 } 1164 } 1165 catch (err) { } 1166 } 1167 1168 1169 //Misc Functions 1170 HtmlEncodeSimple(html) { 1171 return html.replaceAll(/</g, "<").replaceAll(/>/g, ">"); 1172 } 1173 1174 HtmlDecodeSimple(html) { 1175 return html.replaceAll("<", /</g).replaceAll(">", />/g); 1176 } 1177 1178 RemoveQuotes(text) { 1179 return text.replaceAll("'", ""); 1180 } 1181 1182 UrlFriendlyText(text) { 1183 return text.replaceAll("'", "").replaceAll(" ", "-").replaceAll("/", "-"); 1184 } 1185 1186 TruncateString(text, length, ellipse) { 1187 if (text.length > length) { 1188 text = text.substring(0, length); 1189 1190 if (ellipse) { 1191 text = text + '...'; 1192 } 1193 1194 } 1195 1196 return text; 1197 } 1198 1199 Cart() { 1200 let obj = this; 1201 $("#modalCart").show(); 1202 } 1203 1204 1205 //Intended to be called when any element on a page finishes loading. Considered a global loading rutine. 1206 AllLoadActions() { 1207 let obj = this; 1208 obj.ReplaceAnonUserId(); 1209 obj.ReplaceCartCount(); 1210 obj.ReplaceCartTotal(); 1211 1212 setTimeout(function () { 1213 obj.MakePageAccessible(); 1214 }, 100); 1215 1216 } 1217 1218 //Function used to saturate dynamic elements for forms and such with Anon User Id 1219 ReplaceAnonUserId() { 1220 let obj = this; 1221 $('.AnonUserId').each(function () { 1222 $(this).val(obj.AnonUserId); 1223 }); 1224 } 1225 1226 ReplaceCartCount() { 1227 let obj = this; 1228 $('.cart_item_count').each(function () {
1229 if (!$(this).find('.cart-count').length && obj.CartCount > 0) 1230 { 1231 $(this).append(`<div class="cart-count">${obj.CartCount}</div>`); 1232 } 1233 }); 1234 $('.cart_item_count_span').each(function () { 1235 if (!$(this).find('.cart-count-span').length) { 1236 $(this).append(`<span class="cart-count-span">${obj.CartCount}</span>`); 1237 } 1238 }); 1239 $('.cart_item_count_hidezero').each(function () { 1240 $(this).html(`<span class="cart-count-span cart-link__count">${obj.CartCount}</span>`); 1241 1242 if (obj.CartCount > 0) { 1243 $(this).show(); 1244 } 1245 else { 1246 $(this).hide(); 1247 } 1248 }); 1249 } 1250 1251 ReplaceCartTotal() { 1252 let obj = this; 1253 $('.cart_total_inject').each(function () { 1254 if (!$(this).find('.cart-total').length) { 1255 $(this).append(`<span class="cart-total">${obj.CartTotal}</span>`); 1256 } 1257 }); 1258 } 1259 1260 GetCartCount() { 1261 let obj = this; 1262 1263 $.ajax({
1264 url: `/Commerce/GetCartItemCount?AnonUserId=${obj.AnonUserId}`, 1265 type: "GET", 1266 async: true, 1267 success: function (data) { 1268 obj.CartCount = data.cartItemCount; 1269 obj.CartTotal = data.cartTotal; 1270 obj.ReplaceCartCount(); 1271 }, 1272 error: function (x, y, z) { 1273 console.log("Failed to get cart count."); 1274 console.log(x.responseText); 1275 1276 obj.CartCount = 0; 1277 obj.CartTotal = 0; 1278 obj.ReplaceCartCount(); 1279 } 1280 }); 1281 } 1282 1283 GetCartItemsList(element) { 1284 let obj = this; 1285 1286 $.ajax({ 1287 url: `/Commerce/GetCartItemsList?AnonUserId=${obj.AnonUserId}`, 1288 type: "GET", 1289 async: false, 1290 success: function (data) { 1291 $("#" + element).val(data.cartItemsList); 1292 }, 1293 error: function (x, y, z) { 1294 console.log("Failed to get cart count."); 1295 console.log(x.responseText); 1296 } 1297 }); 1298 } 1299 1300 //AutoSuggest 1301 AutoSuggestGetTitles(objectDefinitionIds) { 1302 let obj = this; 1303 obj.AutoSuggestTitles = new Array(); 1304 1305 try { 1306 $.post("/Page/AutoSuggestGetTitles", { applicationId: obj.ApplicationId, objectDefinitionIds: objectDefinitionIds }, function (data) { 1307 obj.AutoSuggestTitles = data.results; 1308 }); 1309 } 1310 catch (err) { } 1311 } 1312 1313 AutoSuggest(searchElement, autosuggestContainerId, take) { 1314 let obj = this; 1315 var $autosuggestContainer = $("#" + autosuggestContainerId); 1316 1317 try { 1318 var searchTerm = $(searchElement).val(); 1319 1320 if (searchTerm.length > 1 && obj.AutoSuggestTitles.length > 0) { 1321 1322 var filteredList = obj.AutoSuggestTitles.filter(function (obj) { 1323 // Convert field value to lowercase for case-insensitive search 1324 return obj["title"].toLowerCase().includes(searchTerm.toLowerCase()); 1325 }); 1326 1327 filteredList = filteredList.slice(0, take); 1328 1329 if (filteredList.length > 0) { 1330 //Got results, so build html 1331 var asHtml = ""; 1332 filteredList.forEach(function (result) { 1333 asHtml += "<div class='autosuggest-listitem' title='" + result.title + "'><a href='" + result.navToUrl + "'><span class='autosuggest-listitem-type'>" + result.objectType + "</span> : <span class='autosuggest-listitem-title'>" + obj.AutoSuggestHighlightText(result.title, searchTerm) + "</span></a></div>"; 1334 }); 1335 $autosuggestContainer.empty(); 1336 $autosuggestContainer.append(asHtml); 1337 $autosuggestContainer.show(); 1338 1339 $(searchElement) 1340 .on("focusout", function () { 1341 setTimeout(function () { 1342 $autosuggestContainer.hide(); 1343 }, 500); //Give it half a second before hiding, so it can process any auto-suggest link clicks 1344 }) 1345 .on("blur", function () { 1346 setTimeout(function () { 1347 $autosuggestContainer.hide(); 1348 }, 500); //Give half a second before hiding, so it can process any auto-suggest link clicks 1349 }); 1350 } 1351 else { 1352 $autosuggestContainer.hide(); 1353 $autosuggestContainer.empty(); 1354 } 1355 } 1356 else { 1357 $autosuggestContainer.hide(); 1358 $autosuggestContainer.empty(); 1359 } 1360 } 1361 catch (err) { } 1362 } 1363 1364 AutoSuggestHighlightText(text, searchTerm) { 1365 var regex = new RegExp('(' + searchTerm + ')', 'gi'); 1366 return text.replace(regex, '<span class="autosuggest-highlight">$1</span>'); 1367 } 1368 1369 /************ COUNTER *************************/ 1370 1371 RecordSearch(searchTerm) { 1372 $.ajax({
1373 url: `/COUNTER/RecordSearch?searchTerm=${encodeURIComponent(searchTerm)}`, 1374 async: true, 1375 type: "POST", 1376 success: function () { }, 1377 error: function (x, y, z) { console.log(x.responseText); console.log("Failed to record SEARCH for COUNTER."); } 1378 }); 1379 } 1380 1381 RecordRequest() { 1382 if (this.HasAssociatedObject) { 1383 //Build a form submission. 1384 //string platform, string item, Guid objectId, Data_Type dataType, string regOrgName, int ? YOP, string URI 1385 var recordRequestModel = { 1386 platform: this.AppName, 1387 regOrgName: this.RegisteredOrg, 1388 item: this.AssociatedObjectPrimaryTitle, 1389 objectId: this.ObjectId, 1390 dataType: this.DataType, 1391 YOP: this.YOP, 1392 URI: window.location.href 1393 }; 1394 1395 $.ajax({ 1396 url: `/COUNTER/RecordRequest`, 1397 async: true, 1398 data: recordRequestModel, 1399 dataType: 'json', 1400 type: "POST", 1401 success: function () { }, 1402 error: function (x, y, z) { console.log(x.responseText); console.log("Failed to record REQUEST for COUNTER."); } 1403 }); 1404 } 1405 } 1406 1407 /************************************************/ 1408 1409 1410 1411 /************ LEARNING *************************/ 1412 1413 LearningActivityStart(laObjectId) { 1414 let obj = this; 1415 1416 try { 1417 $.post("/Page/LearningActivityStart", { laObjectId: laObjectId, regOrgId: obj.RegisteredOrganizationId }, function (data) { 1418 }); 1419 } 1420 catch (err) { } 1421 } 1422 1423 LearningActivitySectionComplete(lasObjectId, refresh = true) { 1424 let obj = this; 1425 1426 try { 1427 $.post("/Page/LearningActivitySectionComplete", { lasObjectId: lasObjectId, regOrgId: obj.RegisteredOrganizationId }, function (data) { 1428 obj.Toast('Completed', 'succes', 1000); 1429 1430 if (refresh) { 1431 window.location.reload(); 1432 } 1433 }); 1434 } 1435 catch (err) { } 1436 1437 if (refresh) { 1438 window.location.reload(); 1439 } 1440 } 1441 1442 1443 LearningActivitySectionCompleteReset(lasObjectId) { 1444 let obj = this; 1445 1446 try { 1447 $.post("/Page/LearningActivitySectionCompleteReset", { lasObjectId: lasObjectId, regOrgId: obj.RegisteredOrganizationId }, function (data) { 1448 obj.Toast('Completed', 'succes', 1000); 1449 window.location.reload(); 1450 }); 1451 } 1452 catch (err) { } 1453 window.location.reload(); 1454 } 1455 1456 ArrayToParameterString(array) { 1457 var s = ""; 1458 for (var i = 0; i < array.length; i++) { 1459 if (s == "") { 1460 s = array[i]; 1461 } 1462 else { 1463 s += "|" + array[i]; 1464 } 1465 } 1466 return s; 1467 } 1468 1469 /************************************************/ 1470 1471 ///This gets used by the search results widget paradigm to append a widget onto the screen and then load it 1472 ///to execute a search and return. 1473 AppendSearchResultsWidget(searchModel) { 1474 let obj = this; 1475 //Clear any previous results. 1476 $('#' + searchModel.SearchResultsContainerId).empty(); 1477 1478 if (!searchModel.Take) { 1479 searchModel.Take = 24; 1480 } 1481 1482 //Create search results widget.
1483 const $widget = $('<div>', { 'class': 'widgetPlaceHolder search-results-widget' }); 1484 1485 1486 console.log("SEARCH WIDGET"); 1487 console.log(obj.ArrayToParameterString(searchModel.RelatedObjectDefinitionFilter) ?? ''); 1488 1489 $widget.attr('data-widgetName', 'SearchResults'); 1490 $widget.attr('data-widgetId', searchModel.TargetWidgetId ?? ''); 1491 $widget.attr('data-objectIdsFilter', obj.ArrayToParameterString(searchModel.ObjectIdsFilter) ?? ''); 1492 $widget.attr('data-objectDefinitionIdsFilter', obj.ArrayToParameterString(searchModel.ObjectDefinitionIdsFilter) ?? ''); 1493 $widget.attr('data-tagIdsFilter', obj.ArrayToParameterString(searchModel.TagIdsFilter) ?? ''); 1494 $widget.attr('data-hardTypesFilter', obj.ArrayToParameterString(searchModel.HardTypesFilter) ?? ''); 1495 $widget.attr('data-categoryObjectIdsFilter', obj.ArrayToParameterString(searchModel.CategoryObjectIdsFilter) ?? ''); 1496 $widget.attr('data-relatedObjectDefinitionsFilter', obj.ArrayToParameterString(searchModel.RelatedObjectDefinitionFilter) ?? ''); 1497 $widget.attr('data-listIdsFilter', obj.ArrayToParameterString(searchModel.ListIdsFilter) ?? ''); 1498 $widget.attr('data-searchTermFilter', searchModel.SearchTermFilter ?? ''); 1499 $widget.attr('data-meta', `${searchModel.ViewMode ?? ''}|${searchModel.PageSize ?? ''}|${searchModel.Page ?? ''}`); 1500 $widget.attr('data-SearchResultCallBack', searchModel.SearchResultCallBack ?? ''); 1501 $widget.attr('data-orderBy', searchModel.OrderBy ?? ''); 1502 $widget.attr('data-favorites', String(searchModel.Favorites ?? false)); 1503 $widget.attr('data-RecentlyWatched', String(searchModel.RecentlyWatched ?? false)); 1504 $widget.attr('data-WatchedNotFinished', String(searchModel.WatchedNotFinished ?? false)); 1505 $widget.attr('data-LearningNotComplete', String(searchModel.LearningNotComplete ?? false)); 1506 $widget.attr('data-LearningComplete', String(searchModel.LearningComplete ?? false)); 1507 $widget.attr('data-MostWatched', String(searchModel.MostWatched ?? false)); 1508 $widget.attr('data-SearchType', searchModel.SearchType ?? ''); 1509 $widget.attr('data-FilterOptions', searchModel.FilterOptions ?? ''); 1510 $widget.attr('data-customFilter', searchModel.CustomFilter ?? ''); 1511 $widget.attr('data-SpecificListSearch', searchModel.SpecificListSearch ?? ''); 1512 $widget.attr('data-authedonly', String(searchModel.AuthedOnly ?? false)); 1513 $widget.attr('data-publiconly', String(searchModel.PublicOnly ?? false)); 1514 $widget.attr('data-Take', searchModel.Take ?? ''); 1515 $widget.attr('data-galleryIdFilter', searchModel.GalleryIdFilter ?? ''); 1516 1517 //Append search results widget. 1518 $('#' + searchModel.SearchResultsContainerId).append($widget); 1519 1520 console.log("LOAD"); 1521 console.log(searchModel.ObjectDefinitionIdsFilter); 1522 1523 //load this widget. 1524 $('.search-results-widget').each(function () { 1525 obj.LoadWidget($(this)); 1526 }); 1527 } 1528 1529 1530 1531 /************ ACCESSIBILITY *************************/ 1532 1533 MakePageAccessible() { 1534 let obj = this; 1535 1536 //Set TabIndex for all elements to 0, unless already set to -1 1537 /*$("[aria-label][tabindex!='-1']").each(function ()*/ 1538 $("input[tabindex!='-1'], select[tabindex!='-1'], textarea[tabindex!='-1'], button[tabindex!='-1'], a[tabindex!='-1']").each(function () { 1539 $(this).attr("tabindex", "0"); 1540 }); 1541 1542 //Set aria-expanded to false on popup triggers 1543 $('[aria-haspopup="true"], [aria-haspopup="menu"]').attr("aria-expanded", "false"); 1544 1545 //Set tabindex="-1" on all menu/popup items 1546 $('[aria-haspopup="true"], [aria-haspopup="menu"]').each(function () { 1547 var ownsTarget = $(this).attr("aria-owns"); 1548 if (ownsTarget && ownsTarget.length) { 1549 var target = $("#" + ownsTarget); 1550 if (!target.length) { 1551 target = $("." + ownsTarget); 1552 } 1553 if (target.length) { 1554 $(target).find("input, select, textarea, button, a").attr("tabindex", "-1"); 1555 } 1556 } 1557 }); 1558
1559 //EnterKey open Popups, after removing any existing keydown listeners 1560 $('[aria-haspopup="true"], [aria-haspopup="menu"]').off("keydown"); 1561 $('[aria-haspopup="true"], [aria-haspopup="menu"]').on("keydown", function (event) { 1562 1563 /*Enter Key = 13, Space Key = 32, Escape Key = 27 */ 1564 //if (event.which == 13 || event.which == 32 || event.which == 27) { 1565 if (event.which == 13 || event.which == 27) { 1566 1567 var parentElement = $(this); 1568 var affectVisibilityStr = $(parentElement).attr("affect-visibility") ?? ""; 1569 var openFunctionStr = $(parentElement).attr("post-open-function"); 1570 var closeFunctionStr = $(parentElement).attr("post-close-function"); 1571 var displayStyle = $(parentElement).attr("display-style"); 1572 1573 if (!displayStyle || !displayStyle.length) { 1574 displayStyle = "unset"; 1575 } 1576 1577 var affectVisibility = !(affectVisibilityStr.length && affectVisibilityStr.toLowerCase() == "false"); 1578 var openFunction = null; 1579 var closeFunction = null; 1580 1581 //Check the functions are actually functions, otherwise leave as null 1582 if (typeof window[openFunctionStr] === 'function') { 1583 openFunction = openFunctionStr; 1584 } 1585 1586 if (typeof window[closeFunctionStr] === 'function') { 1587 closeFunction = closeFunctionStr; 1588 } 1589 1590 var target = $(this).attr("aria-owns"); 1591 1592 if (target && target.length) { 1593 var popup = $("#" + target); 1594 if (!popup.length) { 1595 popup = $("." + target).first(); 1596 } 1597 1598 if (popup.length) { 1599 // Check if Enter or Space key is pressed 1600 //if (event.which == 13 || event.which == 32) { 1601 if (event.which == 13) { 1602 1603 //Set aria-expanded to true 1604 parentElement.attr('aria-expanded', 'true'); 1605 1606 //Display the popup/menu 1607 if (affectVisibility) { 1608 popup.css("visibility", "visible"); 1609 popup.css("opacity", "1.0"); 1610 popup.css("display", displayStyle); 1611 } 1612 1613 if (openFunction) { window[openFunction](parentElement); } 1614 1615 // Get focusable items 1616 var focusableItems = popup.find("input, select, textarea, button, a"); 1617 if (focusableItems && focusableItems.length) { 1618 focusableItems.attr("tabindex", "0");// Make items tabable 1619 var firstItem = focusableItems.first(); 1620 var lastItem = focusableItems.last(); 1621 event.preventDefault(); 1622 1623 //Focus on first item, after a slight delay before setting focus to ensure visibility 1624 setTimeout(function () { 1625 //Focus on first item 1626 firstItem.focus(); // Set focus to the first item 1627 }, 100); // 100ms delay to ensure popup is fully rendered 1628 1629 // Tab Trap within the popup 1630 popup.off("keydown"); 1631 1632 popup.on('keydown', function (e) { 1633 // If the user presses the "Tab" key 1634 if (e.which == 9) { 1635 if (e.shiftKey) { // Shift+Tab (backward) 1636 if (document.activeElement === firstItem[0]) { 1637 e.preventDefault(); 1638 lastItem.focus(); // Loop back to the last item 1639 } 1640 } 1641 else { // Tab (forward) 1642 if (document.activeElement === lastItem[0]) { 1643 e.preventDefault(); 1644 firstItem.focus(); // Loop back to the first item 1645 } 1646 } 1647 } 1648 1649 // Close the popup on "Escape" 1650 if (e.which == 27) { 1651 parentElement.attr('aria-expanded', 'false'); 1652 1653 if (affectVisibility) { 1654 popup.css("visibility", "hidden"); 1655 popup.css("display", "none"); 1656 } 1657 if (closeFunction) { window[closeFunction](parentElement); } 1658 1659 popup.off('keydown'); // Remove keydown handler 1660 popup.find("input, select, textarea, button, a").attr("tabindex", "-1");// Make items un-tabable 1661 parentElement.focus(); // Set focus back to the triggering element 1662 } 1663 1664 //// Handle "Enter or Space" key press 1665 //if (e.which == 13 || e.which == 32) { 1666 // parentElement.attr('aria-expanded', 'false'); 1667 1668 // if (affectVisibility) { popup.css("visibility", "hidden"); } 1669 // if (closeFunction) { window[closeFunction](parentElement); } 1670 1671 // popup.find("input, select, textarea, button, a"); 1672 // popup.off('keydown'); // Remove keydown handler 1673 //} 1674 1675 }); 1676 } 1677 1678 } 1679 1680 // Check if Escape key is pressed (keyCode 27), if so hide it 1681 if (event.which == 27) { 1682 parentElement.attr('aria-expanded', 'false'); 1683 1684 if (affectVisibility) { 1685 popup.css("visibility", "hidden"); 1686 popup.css("display", "none"); 1687 } 1688 1689 if (closeFunction) { 1690 window[closeFunction]($(this)); 1691 } 1692 1693 popup.off('keydown'); // Remove keydown handler 1694 popup.find("input, select, textarea, button, a").attr("tabindex", "-1");// Make items un-tabable 1695 parentElement.focus(); 1696 } 1697 } 1698 } 1699 } 1700 }); 1701 1702 //Focus to top of page, unless any containers (eg widgetPlaceHolders) have the attribute auto-focus="true" 1703 var $autoFocusElements = $("[auto-focus='true']"); 1704 if (!$autoFocusElements.length) { 1705 setTimeout(function () { 1706 $('#topOfPage').focus(); 1707 }, 100); // 100ms delay to ensure skiplink is fully rendered 1708 } 1709 else { 1710 //Focus on first tabable item in the first container with the attribute auto-focus="true" 1711 setTimeout(function () { 1712 var $firstAutoFocusContainer = $autoFocusElements.first(); 1713 if ($firstAutoFocusContainer.length) { 1714 var tabableItem = $firstAutoFocusContainer.find(".auto-focus-item").first(); 1715 if (tabableItem.length) { 1716 tabableItem.focus(); 1717 1718 //Reset auto-focus to false 1719 //This allows for initial page load to NOT select first, then any subsequent searches/pagination etc DO select 1st item 1720 //$autoFocusElements.attr("auto-focus", "false"); 1721 } 1722 } 1723 }, 100); // 1000ms delay to ensure items are rendered 1724 } 1725 } 1726 1727 MakeSkipLinks() { 1728 1729 1730 //Add SkipLinks, if none found (allows for custom ones to be added) 1731 var skipLinks = $("#skiplinks"); 1732 if (!skipLinks.length) { 1733 1734 var linkMainContent = ""; 1735 var linkMainFooter = ""; 1736 1737 //See if a #maincontent or .maincontent exists 1738 //Some <main> tags already have id's other than 'maincontent', so in that case we can use classes (then get it's id) 1739 var mainContent = $("#maincontent[id], .maincontent[id], #main-content, .main-content").first(); 1740 if (mainContent.length) { 1741 //Get id of mainContent Elemen tId 1742 var mainContentElementId = mainContent.attr('id'); 1743 1744 if (mainContentElementId && mainContentElementId.length) { 1745 //Yes, found a '#maincontent' element, so create a skiplinks div with a link to the main content 1746 linkMainContent = "<a href='#" + mainContentElementId + "' aria-label='skip to main content'>skip to main content</a>"; 1747 } 1748 } 1749 1750 //See if a #mainfooter or .mainfooter exists 1751 //Some <main> tags already have id's other than 'mainfooter', so in that case we can use classes (then get it's id)
1752 var mainFooter = $("#mainfooter[id], .mainfooter[id]").first(); 1753 if (mainFooter.length) { 1754 //Get id of mainFooter Elemen tId 1755 var mainFooterElementId = mainFooter.attr('id'); 1756 1757 if (mainFooterElementId && mainFooterElementId.length) { 1758 //Yes, found a '#mainfooter' element, so create a skiplinks div with a link to the footer 1759 linkMainFooter = "<a href='#" + mainFooterElementId + "' aria-label='skip to footer'>skip to footer</a>"; 1760 } 1761 } 1762 1763 //If we have a Content and/or Footer link, create a skiplink 1764 if (linkMainContent.length || linkMainFooter.length) { 1765 var genericSkipLinkHtml = "<div id='skiplinks'>" + linkMainContent + linkMainFooter + "</div >"; 1766 $("body").prepend(genericSkipLinkHtml); 1767 } 1768 } 1769 1770 //Add top of page, off-screen input... if not already there 1771 var $topOfPage = $('#topOfPage'); 1772 if (!$topOfPage.length) { 1773 var currentPageTitle = $(document).find("title").text().replace(/['"]/g, ''); 1774 var topOfPageHtml = "<input id='topOfPage' aria-label='top of page:" + currentPageTitle + "' value='' style='position:absolute; left:-9999px;'/>"; 1775 $("body").prepend(topOfPageHtml); 1776 } 1777 1778 /* HomeKey sets focus to TopOfPage, one tab away from skiplink */ 1779 $(document).on("keydown", function (event) { 1780 1781 /*Home Key = 36 */ 1782 if (event.which === 36 || event.key === "Home") { 1783 $("#topOfPage").focus(); 1784 } 1785 1786 }); 1787 } 1788 1789 MakeUnTabable(element) { 1790 // Get all the element's child tabable items and set tabindex's to -1 1791 setTimeout(function () { 1792 $(element).find("input, select, textarea, button, a").attr("tabindex", "-1"); 1793 //Just in case element is tabable itself, set that too 1794 $(element).attr("tabindex", "-1"); 1795 }, 1000); // 1000ms delay to ensure popup is fully rendered 1796 1797 1798 } 1799 1800 TabTrapPopup(popupId, closeFunction) { 1801 var popup = $("#" + popupId); 1802 1803 // Get focusable items 1804 var focusableItems = popup.find("[aria-label]"); 1805 if (focusableItems && focusableItems.length) { 1806 focusableItems.attr("tabindex", "0");// Make items tabable 1807 var firstItem = focusableItems.first(); 1808 var lastItem = focusableItems.last(); 1809 1810 //Focus on first item, after a slight delay before setting focus to ensure visibility 1811 setTimeout(function () { 1812 //Focus on first item 1813 firstItem.focus(); // Set focus to the first item 1814 }, 100); // 100ms delay to ensure popup is fully rendered 1815 1816 // Tab Trap within the popup 1817 popup.off("keydown"); 1818 1819 popup.on('keydown', function (e) { 1820 // If the user presses the "Tab" key 1821 if (e.which == 9) { 1822 if (e.shiftKey) { // Shift+Tab (backward) 1823 if (document.activeElement === firstItem[0]) { 1824 e.preventDefault(); 1825 lastItem.focus(); // Loop back to the last item 1826 } 1827 } 1828 else { // Tab (forward) 1829 if (document.activeElement === lastItem[0]) { 1830 e.preventDefault(); 1831 firstItem.focus(); // Loop back to the first item 1832 } 1833 } 1834 } 1835 1836 // Close the popup on "Escape" 1837 if (e.which == 27) { 1838 if (closeFunction) { window[closeFunction](); } 1839 1840 popup.off('keydown'); // Remove keydown handler 1841 //popup.find("[aria-label]").attr("tabindex", "-1");// Make items un-tabable 1842 } 1843 1844 }); 1845 } 1846 } 1847 1848 /************************************************/ 1849 1850 1851 /************ Application Institutions *************************/ 1852 1853 ApplicationInstitutionsGet() { 1854 let obj = this; 1855 obj.Institutions = new Array(); 1856 1857 try { 1858 $.post("/Page/ApplicationInstitutionsGet", { applicationId: obj.ApplicationId }, function (data) { 1859 obj.Institutions = data.institutions; 1860 }); 1861 } 1862 catch (err) { } 1863 } 1864 1865 ApplicationInstitutionsSearch(take) { 1866 let obj = this; 1867 var $institutionContainer = $("#institutionsContainer"); 1868 1869 try { 1870 var searchTerm = $("#institutionSearch").val(); 1871 1872 if (searchTerm.length > 0 && obj.Institutions.length > 0) { 1873 1874 var filteredList = obj.Institutions.filter(function (obj) { 1875 // Convert field value to lowercase for case-insensitive search 1876 return obj["name"].toLowerCase().includes(searchTerm.toLowerCase()); 1877 }); 1878 1879 filteredList = filteredList.slice(0, take); 1880 1881 if (filteredList.length > 0) { 1882 //Got results, so build html 1883 var asHtml = ""; 1884 filteredList.forEach(function (result) { 1885 asHtml += "<a href='javascript:void(0);' class='institution-listitem' title='" + result.cleanName + "' aria-label='select " + result.cleanName + "' data-entityId='" + result.id + "' onclick='_pageManager.SelectInstitutionForLogin(`" + result.id + "`);'>" + result.name + "</a>"; 1886 }); 1887 $institutionContainer.empty(); 1888 $institutionContainer.append(asHtml); 1889 $institutionContainer.addClass("show"); 1890 1891 $(searchElement) 1892 .on("focusout", function () { 1893 setTimeout(function () { 1894 $institutionContainer.removeClass("show"); 1895 }, 500); //Give it half a second before hiding, so it can process any auto-suggest link clicks 1896 }) 1897 .on("blur", function () { 1898 setTimeout(function () { 1899 $institutionContainer.removeClass("show"); 1900 }, 500); //Give half a second before hiding, so it can process any auto-suggest link clicks 1901 }); 1902 } 1903 else { 1904 $institutionContainer.removeClass("show"); 1905 $institutionContainer.empty(); 1906 } 1907 } 1908 else { 1909 $institutionContainer.removeClass("show"); 1910 $institutionContainer.empty(); 1911 } 1912 } 1913 catch (err) { } 1914 } 1915 1916 SelectInstitutionForLogin(entityId) { 1917 let obj = this; 1918 1919 // Find the object with the unique ID 1920 var institute = obj.Institutions.find(function (item) { 1921 return item.id === entityId; 1922 }); 1923 1924 if (institute) { 1925 $("#institutionSearch").val(institute.name); 1926 $("#institutionsContainer").removeClass("show"); 1927 $("#institutionsContainer").empty(); 1928 1929 if (institute.hasOpenAthensIdentifier) { 1930 var openAthensUrl = $("#openAthensUrlWithoutEntityValue").val() + institute.openAthensIdentifier; 1931 $("#institution-login-button-openathens .button-login").attr("href", openAthensUrl); 1932 $("#institution-login-button-openathens").addClass("show"); 1933 } 1934 else { 1935 $("#institution-login-button-openathens").removeClass("show"); 1936 } 1937 1938 if (institute.hasProxyEntryUrl) { 1939 $("#institution-login-button-proxy .button-login").attr("href", institute.proxyEntryUrl); 1940 $("#institution-login-button-proxy").addClass("show"); 1941 } 1942 else { 1943 $("#institution-login-button-proxy").removeClass("show"); 1944 } 1945 1946 if (institute.hasIPs) {
1947 $("#institution-login-button-ip").addClass("show"); 1948 } 1949 else { 1950 $("#institution-login-button-ip").removeClass("show"); 1951 } 1952 1953 } 1954 else { 1955 $("#institution-login-button-openathens").removeClass("show"); 1956 $("#institution-login-button-proxy").removeClass("show"); 1957 $("#institution-login-button-ip").removeClass("show"); 1958 1959 $("#institution-login-button-openathens .button-login").attr("href", ""); 1960 $("#institution-login-button-proxy .button-login").attr("href", ""); 1961 } 1962 1963 1964 } 1965 1966 /************************************************/ 1967 1968 1969 /************ Access Codes *************************/ 1970 1971 async RedeemAccessCode(accessCodeElement, accessCodeButton, accessCodeResultElement) { 1972 let obj = this; 1973 const accessCode = $("#" + accessCodeElement).val()?.trim(); 1974 1975 if (!accessCode) { 1976 $("#" + accessCodeResultElement) 1977 .text("Invalid access code") 1978 .addClass("error") 1979 .removeClass("success"); 1980 return; 1981 } 1982 1983 const $btn = $("#" + accessCodeButton); 1984 1985 // Prevent double-clicks 1986 if ($btn.data("busy")) return; 1987 $btn.data("busy", true).prop("disabled", true); 1988 1989 // Add spinner only if not already present 1990 if ($btn.find("i.fa-spinner").length === 0) { 1991 $btn.prepend('<i class="fa-solid fa-spinner fa-spin me-2" aria-hidden="true"></i>'); 1992 } 1993 1994 $("#" + accessCodeResultElement) 1995 .text("") 1996 .removeClass("error") 1997 .removeClass("success"); 1998 1999 try { 2000 2001 await new Promise(r => setTimeout(r, 1000)); 2002 2003 const data = await $.ajax({
2004 url: "/Page/RedeemAccessCode", 2005 method: "POST", 2006 data: { regOrgId: obj.RegisteredOrganizationId, applicationId: obj.ApplicationId, loginProfileId: obj.LoginProfileId, accessCode: accessCode } 2007 }); 2008 2009 if (data && data?.success) { 2010 $("#" + accessCodeResultElement) 2011 .text(data?.message || "Access code redeemed successfully") 2012 .addClass("success") 2013 .removeClass("error"); 2014 } else { 2015 $("#" + accessCodeResultElement) 2016 .text(data?.message || "There was an issue redeeming the access code") 2017 .addClass("error") 2018 .removeClass("success"); 2019 } 2020 } catch (err) { 2021 $("#" + accessCodeResultElement) 2022 .text("There was an issue redeeming the access code") 2023 .addClass("error") 2024 .removeClass("success"); 2025 } finally { 2026 $btn.find("i.fa-spinner").remove(); 2027 $btn.data("busy", false).prop("disabled", false); 2028 } 2029 } 2030 2031 /************************************************/ 2032 2033 2034 /************ Email Form *************************/ 2035 2036 SendForm(formName, emailAddress, subject, intro, formContainer, successFunction) { 2037 let obj = this; 2038 2039 var emailBody = intro + "<br/><br/>"; 2040 2041 try { 2042 2043 // Find all form-field elements 2044 $('#' + formContainer).find('.form-field').each(function () { 2045 // Check for text inputs 2046 var textInput = $(this).find('input[type="text"]'); 2047 if (textInput.length > 0) { 2048 var fieldName = textInput.attr('name'); 2049 var fieldValue = textInput.val(); 2050 emailBody += fieldName + ": " + fieldValue + "<br/>"; 2051 } 2052 2053 // Check for hidden inputs 2054 var textInput = $(this).find('input[type="hidden"]'); 2055 if (textInput.length > 0) { 2056 var fieldName = textInput.attr('name'); 2057 var fieldValue = textInput.val(); 2058 emailBody += fieldName + ": " + fieldValue + "<br/>"; 2059 } 2060 2061 // Check for checkbox groups 2062 var groupName = $(this).attr('data-group'); 2063 if (groupName) { 2064 var selectedValues = []; 2065 2066 // Find all checked checkboxes within this group 2067 $(this).find('input[type="checkbox"]:checked').each(function () { 2068 selectedValues.push($(this).val()); 2069 }); 2070 2071 if (selectedValues.length > 0) { 2072 emailBody += groupName + ": " + selectedValues.join(', ') + "<br/>"; 2073 } 2074 } 2075 }); 2076 2077 $.post("/Page/SendFormByEmail", { emailAddress: emailAddress, subject: subject, body: emailBody }, function (data) { 2078 if (data) { 2079 var toastSuccess = formName + ' sent'; 2080 obj.Toast(toastSuccess, 'success', 2000); 2081 $('#' + formContainer + 'SentWrapper').show(); 2082 $('#' + formContainer + 'Wrapper').hide(); 2083 if (successFunction && successFunction.length) { 2084 let functionObj = window[successFunction]; 2085 functionObj(); 2086 } 2087 } 2088 else { 2089 var toastError = 'There was an issue sending ' + formName; 2090 obj.Toast(toastError, 'error', 2000); 2091 } 2092 2093 return; 2094 }); 2095 2096 } 2097 catch (err) { 2098 var toastError = 'There was an issue sending ' + formName; 2099 obj.Toast(toastError, 'error', 2000); 2100 } 2101 2102 2103 } 2104 2105 /************************************************/ 2106 2107 2108 2109 /************ Search Within Transcripts *************************/ 2110 /* Typically called by LoadData function in cadmoregallery.js */ 2111 /*Primary url field is the clickthrough field if it is present on the media object whose transcript is being search. This is to power click throughs.*/ 2112 SearchWithinTranscripts(safeId, searchTerm, primaryUrlField) { 2113 let obj = this; 2114 2115 try { 2116 if (searchTerm.trim().length === 0 || searchTerm.trim() === "*") { 2117 return; 2118 } 2119 2120 var $searchWithinTranscriptContainers = new Array(); 2121 2122 if (safeId.length) { 2123 //Find all search-within-transcript-containers within a given widget 2124 $searchWithinTranscriptContainers = $(".search-within-transcript-container_" + safeId + "[data-processed=false]"); 2125 } 2126 2127 //Get template 2128 var $templateTranscriptSearchWithin = $("#templateTranscriptSearchWithin_" + safeId); 2129 2130 //Got any containers to process - and do we have a search result template? 2131 if ($searchWithinTranscriptContainers.length > 0 && $templateTranscriptSearchWithin && $templateTranscriptSearchWithin.length > 0) { 2132 2133 //Got some search-within-transcript-containers, so process them 2134 $searchWithinTranscriptContainers.each(function () { 2135 var $container = $(this); 2136 2137 //Find a results container 2138 var $resultsContainer = $container.find(".search-within-transcript-results"); 2139 2140 //Clear results container 2141 if ($resultsContainer && $resultsContainer.length > 0) { 2142 $resultsContainer.empty(); 2143 } 2144 2145 //Only do a search if we have a searchTerm 2146 if ($resultsContainer && $resultsContainer.length > 0 && searchTerm.length) { 2147 //Get MediaObjectId & navToUrl 2148 var mediaObjectId = $container.attr('data-mediaObjectId'); 2149 var origNavToUrl = $container.attr('data-navToUrl'); 2150 var primaryUrlField = $container.attr('data-primaryUrlField'); 2151 var take = $container.attr('data-transcriptSearchResultsTake'); 2152 2153 //Only continue if we have an ObjectId & navToUrl 2154 if (mediaObjectId.length > 0 && origNavToUrl.length) { 2155 2156 $.post("/Page/SearchWithinTranscript", { objectId: mediaObjectId, searchTerm: searchTerm, take: take, applicationId: obj.ApplicationId }, function (data) { 2157 if (data && data.resultsModel) { 2158 var result = data.resultsModel; 2159 2160 //Mark container as processed - to stop any repeat checks 2161 $container.attr('data-processed', 'true'); 2162 2163 var template = $templateTranscriptSearchWithin.html(); 2164 2165 //Add transcript results to container 2166 for (let i = 0; i < result.transcriptHits.length; i++) { 2167 try { 2168 var item = result.transcriptHits[i]; 2169 2170 if (item.timeStamp.length && item.line.length) { 2171 var navToUrl = origNavToUrl + "?cue=" + item.timeStamp + ":000"; 2172 2173 //var $itemMarkup = obj.BuildTranscriptSearchWithResultItemMarkup(navToUrl, item.timeStamp, item.line, $templateTranscriptSearchWithin); 2174 var $itemMarkup = template.replaceAll("{{NavToUrl}}", navToUrl) 2175 .replaceAll("{{Timestamp}}", item.timeStamp + ":000") 2176 .replaceAll("{{Line}}", item.line) 2177 .replaceAll("{{ObjectId}}", mediaObjectId) 2178 .replaceAll("{{PrimaryUrlField}}", primaryUrlField) 2179 ; 2180 2181 if ($itemMarkup && $itemMarkup.length > 0) { 2182 $resultsContainer.append($itemMarkup); 2183 } 2184 } 2185 } 2186 catch (err) { } 2187 } 2188 } 2189 }); 2190 } 2191 } 2192 }); 2193 } 2194 } 2195 catch (err) { } 2196 2197 } 2198 2199 2200 SearchWithinTranscriptsAndSegments(safeId, searchTerm) { 2201 let obj = this; 2202 2203 try { 2204 if (searchTerm.trim().length === 0 || searchTerm.trim() === "*") { 2205 return; 2206 } 2207 2208 var $searchWithinTranscriptContainers = new Array(); 2209 2210 if (safeId.length) { 2211 //Find all search-within-transcript-containers within a given widget 2212 $searchWithinTranscriptContainers = $(".search-within-transcript-container_" + safeId + "[data-processed=false]"); 2213 } 2214 2215 //Get template 2216 var $templateTranscriptSearchWithin = $("#templateTranscriptSearchWithin_" + safeId); 2217 2218 //Got any containers to process - and do we have a search result template? 2219 if ($searchWithinTranscriptContainers.length > 0 && $templateTranscriptSearchWithin && $templateTranscriptSearchWithin.length > 0) { 2220 2221 //Got some search-within-transcript-containers, so process them 2222 $searchWithinTranscriptContainers.each(function () { 2223 var $container = $(this); 2224 2225 //Find a results container 2226 var $resultsContainer = $container.find(".search-within-transcript-results"); 2227 2228 //Clear results container 2229 if ($resultsContainer && $resultsContainer.length > 0) { 2230 $resultsContainer.empty(); 2231 } 2232 2233 //Only do a search if we have a searchTerm 2234 if ($resultsContainer && $resultsContainer.length > 0 && searchTerm.length) { 2235 //Get MediaObjectId & navToUrl 2236 var mediaObjectId = $container.attr('data-mediaObjectId'); 2237 var origNavToUrl = $container.attr('data-navToUrl'); 2238 var take = $container.attr('data-transcriptSearchResultsTake'); 2239 2240 //Only continue if we have an ObjectId & navToUrl 2241 if (mediaObjectId.length > 0 && origNavToUrl.length) { 2242 2243 $.post("/Page/SearchWithinTranscript", { objectId: mediaObjectId, searchTerm: searchTerm, take: take, applicationId: obj.ApplicationId }, function (data) { 2244 if (data && data.resultsModel) { 2245 var result = data.resultsModel; 2246 2247 result.transcriptHits = result.transcriptHits || []; 2248 result.segmentHits = result.segmentHits || []; 2249 2250 //Mark container as processed - to stop any repeat checks 2251 $container.attr('data-processed', 'true'); 2252 2253 var template = $templateTranscriptSearchWithin.html(); 2254 2255 var hits = []; 2256 2257 // Transcript hits 2258 if (result.transcriptHits && result.transcriptHits.length > 0) { 2259 for (let i = 0; i < result.transcriptHits.length; i++) { 2260 hits.push({ 2261 hitTypeLabel: "Transcript", 2262 hitTypeClass: "transcript", 2263 item: result.transcriptHits[i] 2264 }); 2265 } 2266 } 2267 2268 // Segment hits 2269 if (result.segmentHits && result.segmentHits.length > 0) { 2270 for (let i = 0; i < result.segmentHits.length; i++) { 2271 hits.push({ 2272 hitTypeLabel: "Segment", 2273 hitTypeClass: "segment", 2274 item: result.segmentHits[i] 2275 }); 2276 } 2277 } 2278 2279 // Optional: sort by timestamp if both types use the same timestamp format 2280 hits.sort(function (a, b) { 2281 var aTime = a.item.timeStamp || a.item.timestamp || ""; 2282 var bTime = b.item.timeStamp || b.item.timestamp || ""; 2283 return aTime.localeCompare(bTime); 2284 }); 2285
2286 // Add combined results to container 2287 for (let i = 0; i < hits.length; i++) { 2288 try { 2289 var hit = hits[i]; 2290 var item = hit.item; 2291 2292 var timeStamp = item.timeStamp || ""; 2293 var line = item.line || item.segmentTitle || ""; 2294 2295 if (timeStamp.length && line.length) { 2296 var cueTimeStamp = timeStamp; 2297 2298 if (hit.hitTypeClass === "transcript") { 2299 cueTimeStamp = timeStamp + ":000"; 2300 } 2301 2302 var navToUrl = origNavToUrl + "?cue=" + cueTimeStamp; 2303 2304 var $itemMarkup = template 2305 .replaceAll("{{NavToUrl}}", navToUrl) 2306 .replaceAll("{{Timestamp}}", timeStamp) 2307 .replaceAll("{{Line}}", line) 2308 .replaceAll("{{HitTypeLabel}}", hit.hitTypeLabel) 2309 .replaceAll("{{HitTypeClass}}", hit.hitTypeClass); 2310 2311 if ($itemMarkup && $itemMarkup.length > 0) { 2312 $resultsContainer.append($itemMarkup); 2313 } 2314 } 2315 } 2316 catch (err) { } 2317 } 2318 } 2319 }); 2320 } 2321 } 2322 }); 2323 } 2324 } 2325 catch (err) { } 2326 2327 } 2328 2329 /************************************************/ 2330 2331 2332 /************ Share *************************/ 2333 /* Opens a MediaSite-specific modal (see ProjectR:Film for example) */ 2334 2335 ShowShareModal(objectName) { 2336 let obj = this; 2337 //var thisUrl = window.location.href; //obj.Url 2338 var thisUrl = obj.Url 2339 2340 var truncatedName = obj.TruncateString(objectName, 30, true); 2341 2342 //Title 2343 $("#shareModal .shareTitle .objectName").html(truncatedName); 2344 2345 //Share Items 2346 var facebookURL = "https://facebook.com/sharer/sharer.php?u=" + thisUrl; 2347 $("#shareModal #facebook-share-tab-button").attr("href", facebookURL); 2348 2349 var twitterURL = "https://twitter.com/intent/tweet/?text=" + objectName + "&url=" + thisUrl; 2350 $("#shareModal #twitter-share-tab-button").attr("href", twitterURL); 2351 2352 var linkedInURL = "https://www.linkedin.com/shareArticle?mini=true&title=" + objectName + "&url=" + thisUrl; 2353 $("#shareModal #linkedin-share-tab-button").attr("href", linkedInURL); 2354 2355 var emailUrl = "mailto:?subject=" + objectName + "&body=" + thisUrl; 2356 $("#shareModal #email-share-tab-button").attr("href", emailUrl); 2357 2358 //Open Modal 2359 $("#shareModal").fadeIn(); 2360 2361 } 2362 2363 /************************************************/ 2364 2365 /************ Saved Search *************************/ 2366 /* see NISO Video (widget: Search Gallery) for example */ 2367 2368 SavedSearches_UpdateList() { 2369 let obj = this; 2370 try { 2371 2372 //Clear any containers 2373 $(".savedSearch-dropdown-container").empty(); 2374 2375 $.post("/Page/SearchesGetByEntityIdAndApplicationId", {}, function (data) { 2376 $('.savedSearch-dropdown-container').each(function (x, item) { 2377 if (data) { 2378 var isOnSearchPage = ($(item).attr("data-onsearchpage") == "true");
2378 //eg in Header will be "false"", on search filter etc will be "true" 2379 var searchpagename = $(item).attr("data-searchpagename"); //name of search page, for redirect purposes 2380 var showEdit = ($(item).attr("data-showedit") == "true"); //display edit/delete 2381 var showDescription = ($(item).attr("data-showdescription") == "true"); //display description 2382 2383 //Loop thru results, adding to UI 2384 for (let i = 0; i < data.length; i++) { 2385 try { 2386 var editElement = ""; 2387 if (showEdit) { 2388 editElement = "<i class='fas fa-edit savedSearches-listitem-edit' onclick='event.stopPropagation();savedSearch_Edit(`" + data[i].id + "`);'></i>"; 2389 } 2390 2391 var description = ""; 2392 if (showDescription && data[i].description && data[i].description.length) { 2393 description = "<div class='savedSearches-listitem-description'>" + obj.escapeHTML(data[i].description) + "</div>"; 2394 } 2395 2396 2397 var $itemMarkup = ""; 2398 if (isOnSearchPage) { 2399 $itemMarkup = "<div class='savedSearches-listitem'><span class='savedSearches-listitem-name' onclick='savedSearch_Run(`" + data[i].id + "`);'>" + data[i].name + "</span>" + editElement + description + "</div>"; 2400 } 2401 else { 2402 $itemMarkup = "<div class='savedSearches-listitem'><a class=' savedSearches-listitem-name' href='/" + searchpagename + "?searchId=" + data[i].id + "'>" + data[i].name + "</a>" + editElement + description + "</div>"; 2403 } 2404 $(item).append($itemMarkup); 2405 } 2406 catch (err) { } 2407 } 2408 } 2409 2410 if (isOnSearchPage) { 2411 var $addNewMarkup = "<div class='savedSearches-addnewitem' onclick='savedSearch_New();'>add new</div>"; 2412 $(item).append($addNewMarkup); 2413 } 2414 }); 2415 2416 }); 2417 2418 return; 2419 2420 } 2421 catch (err) { 2422 var toastError = 'There was an issue retrieving saved searches'; 2423 _pageManager.Toast(toastError, 'error', 2000); 2424 } 2425 2426 return; 2427 2428 } 2429 2430 SearchSave(searchModel, successFunction) { 2431 let obj = this; 2432 try { 2433 2434 $.post("/Page/SearchSave", { model: searchModel }, function (data) { 2435 if (data) { 2436 var toastSuccess = 'Search saved'; 2437 obj.Toast(toastSuccess, 'success', 2000); 2438 if (successFunction && successFunction.length) { 2439 let functionObj = window[successFunction]; 2440 functionObj(); 2441 } 2442 } 2443 else { 2444 var toastError = 'There was an issue saving this search '; 2445 obj.Toast(toastError, 'error', 2000); 2446 } 2447 2448 return; 2449 }); 2450 2451 } 2452 catch (err) { 2453 var toastError = 'There was an issue saving this search'; 2454 obj.Toast(toastError, 'error', 2000); 2455 } 2456 } 2457 2458 SearchDelete(searchId, successFunction) { 2459 let obj = this; 2460 try { 2461 2462 $.post("/Page/SearchDelete", { searchId: searchId }, function (data) { 2463 if (data) { 2464 var toastSuccess = 'Search deleted'; 2465 obj.Toast(toastSuccess, 'success', 2000); 2466 if (successFunction && successFunction.length) { 2467 let functionObj = window[successFunction]; 2468 functionObj(); 2469 } 2470 } 2471 else { 2472 var toastError = 'There was an issue deleting this search '; 2473 obj.Toast(toastError, 'error', 2000); 2474 } 2475 2476 return; 2477 }); 2478 2479 } 2480 catch (err) { 2481 var toastError = 'There was an issue deleting this search'; 2482 obj.Toast(toastError, 'error', 2000); 2483 } 2484 } 2485 2486 /************************************************/ 2487 2488 2489 /************ Clips *************************/ 2490 /* see NISO Video (widget: Dashboard - Clips - Carousel) for example */ 2491 2492 LoadCadmoreEntityClips(entityId, clipContainer, template, onCompleteFunction) { 2493 //objectId is the object being added to a clip (if required - otherwise pass null) 2494 //onCompleteFunction - name of js function (eg on widget) that needs to run on completion - eg a carousel splide mount 2495 let obj = this; 2496 var clipCount = 0; 2497 2498 try { 2499 if (entityId) { 2500 //Get Entity's Lists 2501 $.post("/Page/ClipsGetByEntityIdAndApplicationId", { entityId: entityId, applicationId: obj.ApplicationId }, function (data) { 2502 if (data) { 2503 $(clipContainer).empty(); 2504 2505 if (data.length > 0) { 2506 clipCount = data.length; 2507 var templateMarkup = $(template).html(); 2508 2509 //Loop thru results, adding to UI 2510 for (let i = 0; i < data.length; i++) { 2511 try { 2512 var $itemMarkup = obj.BuildCadmoreClipItemMarkup(data[i], templateMarkup); 2513 $(clipContainer).append($itemMarkup); 2514 } 2515 catch (err) { } 2516 } 2517 } 2518 2519 //onCompleteFunction 2520 if (onCompleteFunction && onCompleteFunction.length) { 2521 window[onCompleteFunction](clipCount); 2522 } 2523 2524 return data; 2525 } 2526 2527 //onCompleteFunction 2528 if (onCompleteFunction && onCompleteFunction.length) { 2529 window[onCompleteFunction](clipCount); 2530 } 2531 }); 2532 2533 } 2534 } 2535 catch (err) { } 2536 } 2537 2538 BuildCadmoreClipItemMarkup(item, template) { 2539 var obj = this; 2540 //if (item.imageUrl == '') { item.imageUrl = 'https://placehold.co/960x540/46647A/fff?text=Image not available'; } 2541 2542 var $itemMarkup = template.replaceAll('{{ClipId}}', item.id) 2543 .replaceAll('{{EntityId}}', item.entityId) 2544 .replaceAll('{{ApplicationId}}', item.applicationId) 2545 .replaceAll('{{Name}}', item.name ?? "") 2546 .replaceAll('{{NameNoQuotes}}', obj.RemoveQuotes(item.name) ?? "") //Used when passing as a JS parameter, so quotes don't break syntax 2547 .replaceAll('{{NameFriendly}}', obj.UrlFriendlyText(item.name ?? "")) //Used for url, eg to List page 2548 .replaceAll('{{NavToUrl}}', item.fullUrl ?? "") 2549 .replaceAll('{{ImageUrl}}', item.imageUrl ?? "") 2550 .replaceAll('{{ObjectTitle}}', item.objectTitle ?? ""); 2551 2552 //Remove any redundant placeholders, eg {{anything}} 2553 var placeholderPattern = /\{\{.*?\}\}/g; 2554 $itemMarkup = $itemMarkup.replace(placeholderPattern, ""); 2555 2556 return $itemMarkup; 2557 } 2558 2559 /************************************************/ 2560 2561 2562 /************ Get ObjectListItem *************************/ 2563 2564 RedirectToObjectFullPath(objectId, urlPrefix) { 2565 //By default 'urlPrefix' will be empty, but to nav to another site, urlPrefix would be the host 2566 let obj = this; 2567 try { 2568 2569 $.post("/Page/GetObjectListItem", { objectId: objectId, applicationId: obj.ApplicationId }, function (data) { 2570 if (data) { 2571 var url = urlPrefix + data.navToUrl; 2572 window.location = url; 2573 } 2574 else { 2575 window.location = '/PageNotFound-UnableToRedirect'; 2576 } 2577 }); 2578 2579 } 2580 catch (err) { 2581 window.location = '/PageNotFound-UnableToRedirect'; 2582 } 2583 2584 } 2585 2586 /************************************************/ 2587 2588 2589 escapeHTML(str) { 2590 return str 2591 .replace(/&/g, '&') 2592 .replace(/</g, '<') 2593 .replace(/>/g, '>') 2594 .replace(/"/g, '"') 2595 .replace(/'/g, '''); 2596 } 2597 2598 /************* Entity Application Properties Interface *********/ 2599 //This is for saving a specific property type. For custom, need something else, but currently have no customs. 2600 //Property Type Int is the literaly int from the app property enum you want to save. 2601 //If boolean, val shoudl be True or False. (This was left open in case we ever need string values.) 2602 SaveEntityApplicationProperty(propertyTypeInt, val) { 2603 //Make a simple call to add or set. 2604 let obj = this; 2605 var fdata = new FormData(); 2606 fdata.append("type", propertyTypeInt); 2607 fdata.append("value", val); 2608 $.ajax({
2609 url: '/Page/AddOrSetEntityApplicationProperty', 2610 async: true, 2611 type: "POST", 2612 data: fdata, 2613 processData: false, 2614 contentType: false, 2615 success: function (data) { 2616 //Nothing to report. Data will true if it thinks it worked, false if not. 2617 //We have to refresh login context because this is changing a property for the underyling user. 2618 obj.RefreshLoginContext(); 2619 return data; 2620 }, 2621 error: function (x, y, z) { 2622 console.log(x.responseText); 2623 } 2624 }); 2625 } 2626 2627 ///For use with simpel bool variables on entity application properties to toggle them. 2628 EntityApplicationPropertyBoolUpdater(startingVal, identifier) { 2629 var found = false; 2630 let obj = this; 2631 for (var i = 0; i < obj.eaPropToggles.length; i++) { 2632 var propToggle = obj.eaPropToggles[i]; 2633 2634 if (propToggle.identifier == identifier) { 2635 found = true; 2636 if (propToggle.val == "True") { 2637 return "False"; 2638 } 2639 else { 2640 return "True"; 2641 } 2642 } 2643 } 2644 2645 if (!found) { 2646 var flipVal = startingVal; 2647 if (flipVal == "True") { 2648 flipVal = "False"; 2649 } 2650 else { 2651 flipVal = "True"; 2652 } 2653 obj.eaPropToggles.push({ 2654 identifier: identifier, 2655 val: flipVal 2656 }); 2657 2658 return flipVal; 2659 } 2660 } 2661 2662 2663 /************* RunScriptIfExists *********/ 2664 //This can be called from anywhere (eg window.addEventListener below), and will attempt to ru na script if found on current page if it exists 2665 //Typical usecase includes 'PlayerEventOnStart' triggered via PlayerWeb eventListener which caould then enable a 'Mark as Complete' button 2666 //Known uses; 2667 // PlayerEventOnStart, PlayerEventOnEnd, PlayerEventOnJoinEvent, PlayerEventOnLoaded 2668 RunScriptIfExists(functionName, data) { 2669 try { 2670 if (data) { 2671 window[functionName](data); 2672 } 2673 else { 2674 window[functionName](); 2675 } 2676 } 2677 catch (err) { } 2678 }; 2679 2680 ///This is for sending feedback on a specific prompt id. 2681 AI_RAG_Feedback(feedback, promptId) { 2682 let obj = this; 2683 var fdata = new FormData(); 2684 fdata.append("feedback", feedback); 2685 fdata.append("promptId", promptId); 2686 $('#RAG_feedback_prompt_box').hide(); 2687 $('#RAG_feedback_response').show(); 2688 $.ajax({
2689 url: '/Page/RAG_Feedback', 2690 async: true, 2691 type: "POST", 2692 data: fdata, 2693 processData: false, 2694 contentType: false, 2695 success: function () { 2696 }, 2697 error: function (x, y, z) { 2698 console.log('Error on RAG feedback.'); 2699 console.log(x.responseText); 2700 } 2701 }); 2702 } 2703 2704 AI_RAG(prompt, replacement_area, spinner, resetHistory = true) { 2705 2706 let obj = this; 2707 var fdata = new FormData(); 2708 fdata.append("prompt", prompt); 2709 fdata.append("AnonUserId", obj.AnonUserId); 2710 fdata.append("resetHistory", resetHistory); 2711 $(`#${replacement_area}`).empty(); 2712 2713 //Prevent prompt overrun. 2714 if (prompt.length > 500) { 2715 $(`#${replacement_area}`).html(`<div class="prompt_box"><strong>You asked</strong> - "${prompt}"</div><p>Unfortunately, our AI assistant can only respond to queries that are 500 characters or less. Please try again with a shorter question.</p>`); 2716 return; 2717 } 2718 $(`#${spinner}`).show(); 2719 2720 $.ajax({
2721 url: '/Page/RAG', 2722 async: true, 2723 type: "POST", 2724 data: fdata, 2725 processData: false, 2726 contentType: false, 2727 success: function (data) { 2728 $(`#${spinner}`).hide(); 2729 $(`#${replacement_area}`).html(data); 2730 2731 $('#ask_more_box').data('spinner', spinner); 2732 $('#ask_more_box').data('replacement', replacement_area); 2733 }, 2734 error: function (x, y, z) { 2735 if (x.status === 429) { 2736 // Handle rate limit specifically 2737 $(`#${spinner}`).hide(); 2738 $(`#${replacement_area}`).html(`<div class="prompt_box"><strong>You asked</strong> - "${prompt}"</div><p>Unfortunately, our AI assistant is a little busy right now and can't respond. Only 10 questions per minute are allowed per logged in user, or per IP address if you are not logged in. This is to prevent abuse of the AI assistant. Sorry for the inconvenience. Try again in a few moments.</p>`); 2739 2740 } 2741 console.log(x.responseText); 2742 } 2743 }); 2744 } 2745} 2746 2747/**************** PlayerWeb EventListener **************************************/ 2748 2749window.addEventListener('message', function (e) { 2750 try { 2751 2752 if (e.origin !== "https://play.cadmore.media" && e.origin != "https://samoyed-player-dev.azurewebsites.net" && e.origin !== "https://localhost:16301") { 2753 //bad domain. 2754 return; 2755 } 2756 2757 var obj = JSON.parse(e.data); 2758 2759 2760 if (obj.method === "playStart") { 2761 2762 //Post a record request. 2763 _pageManager.RecordRequest(); 2764 _pageManager.RunScriptIfExists('PlayerEventOnStart'); 2765 } 2766 else if (obj.method === "playEnd") { 2767 _pageManager.RunScriptIfExists('PlayerEventOnEnd'); 2768 } 2769 else if (obj.method === "event_joined") { 2770 _pageManager.RunScriptIfExists('PlayerEventOnJoinEvent'); 2771 } 2772 else if (obj.method === "loaded") { 2773 _pageManager.RunScriptIfExists('PlayerEventOnLoaded', obj); 2774 } 2775 } 2776 catch (ex) { } 2777}); 2778 2779
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.