1var RankPoll = function (element, format, callbacks, config) { 2 var formats = { 3 UNANSWERED: 0, 4 ANSWERED: 1 5 }; 6 config = config || {}; 7 if (this.UIRendered) { 8 return; 9 } 10 this.callbacks = callbacks || { vote: function () {} }; 11 this.Format = format > 0 ? format : formats.UNANSWERED; 12 this.$element = element; 13 this.PollId = element.data("pollid"); 14 this.Options = window["rankOptions" + this.PollId]; 15 this.pollUI = config.pollUI || {}; 16 this.pollData = this.pollUI.data || {}; 17 switch (this.Format) { 18 case formats.UNANSWERED: 19 this.RenderUnansweredUI(); 20 break; 21 case formats.ANSWERED: 22 this.RenderAnsweredUI(); 23 break; 24 } 25}; 26RankPoll.prototype.RenderAnsweredUI = function () { 27 var that = this; 28 if (!this.$element) { 29 return; 30 } 31 32 that.$element.empty(); 33 window.rankResults = window.rankResults || {}; 34 that.rankResults = window.rankResults[this.PollId] || {}; 35 36 var userResponses = that.rankResults.userResponses; 37 var resultsData = that.rankResults.resultsData; 38 var resultRows = []; 39 $.each(userResponses, function (idx, pollResponse) { 40 var pollOptionStat = $.grep(resultsData, function (r, i) { 41 return r.pollOption.id == pollResponse.optionId; 42 })[0]; 43 var $row = $createResultRow(pollOptionStat, pollResponse); 44 that.$element.append($row); 45 }); 46 $.each(resultsData, function (idx, pollOptionStat) { 47 var alreadyAdded = $.grep(userResponses, function (pr, i) { 48 return pr.optionId == pollOptionStat.pollOption.id; 49 }); 50 if (alreadyAdded.length) { 51 return; 52 } 53 54 var $row = $createResultRow(pollOptionStat, null); 55 that.$element.append($row); 56 }); 57 this.appendAnnouncer(); 58 this.UIRendered = true; 59 60 function $createResultRow(pollOptionStat, pollResponse) { 61 var $answerOuter = $('<div class="option-result"></div>'); 62 $answerOuter.data("option-id", pollOptionStat.pollOption.id); 63 $answerOuter.data("average-rank", pollOptionStat.pollOption.id); 64 if (pollResponse != null) { 65 $answerOuter.addClass("selected"); 66 $answerOuter.data("user-rank", pollResponse.rank); 67 } 68 var $optionInner = $('<div class="option-result__inner-bar"></div>'); 69 $answerOuter.append($optionInner); 70 var $optionName = $('<div class="option-result__name option-name"></div>'); 71 $optionName.html(pollOptionStat.pollOption.name); 72 $optionInner.append($optionName); 73 74 if (pollOptionStat.totalUniqueVoters > 0) { 75 var $optionPercentText = $( 76 '<div class="option-result__percent-text"></div>' 77 ); 78 $optionPercentText.attr( 79 "title", 80 pollOptionStat.optionVoteCount + 81 " participants included this option as a priority" 82 ); 83 $optionPercentText.text(Math.round(pollOptionStat.optionPercent) + "%"); 84 $optionPercentText.tooltip(); 85 $optionInner.append($optionPercentText); 86 87 var $answerInnerBG = $( 88 '<div class="option-result__inner-background"></div>' 89 ); 90 $answerInnerBG.width(pollOptionStat.optionPercent + "%"); 91 $optionInner.append($answerInnerBG); 92 } 93 94 if (pollOptionStat.averageRank > 0) { 95 var $rankDisplay = $( 96 '<span class="option-avg-rank" title="Average Rank" tooltip-append-to-body="true" data-placement="left"></span>' 97 ); 98 $rankDisplay 99 .text("Avg. Rank: " + pollOptionStat.averageRank.toFixed(2)) 100 .data("avg-rank", pollOptionStat.averageRank) 101 .attr( 102 "title", 103 "Average Rank of " + 104 pollOptionStat.averageRank.toFixed(2) + 105 " (1 being top priority)" 106 ); 107 $answerOuter.append($rankDisplay); 108 $rankDisplay.tooltip(); 109 } else if (pollResponse) { 110 var ordinalRank = getOrdinalRankFromInt(pollResponse.rank); 111 var $rankDisplay = $( 112 '<span class="option-avg-rank" tooltip-append-to-body="true" data-placement="left"></span>' 113 ); 114 $rankDisplay 115 .text(ordinalRank) 116 .attr("title", "You ranked this " + ordinalRank); 117 $answerOuter.append($rankDisplay); 118 $rankDisplay.tooltip(); 119 $rankDisplay.width(50); 120 } 121 return $answerOuter; 122 } 123}; 124 125var rankInstructionsAnnounced = {}; 126var RANK_INSTRUCTIONS = 127 "Ranking question. Press Enter on an option to add it to your priorities. " + 128 "On a ranked option, press Enter to remove it, or the up and down arrow keys to change its position."; 129 130RankPoll.prototype.RenderUnansweredUI = function () { 131 var that = this; 132 133 if (that.pollData.extendedOptions.requireAllOptions) { 134 that.SubmitDiv = rankInstructionElement( 135 that.PollId, 136 "Please rank all the options" 137 ); 138 } else { 139 that.SubmitDiv = $(confirmButtonHTML(that.PollId)); 140 that.SubmitDiv.on("click", function () { 141 that.vote(); 142 });
143 } 144 145 this.PriorityDiv = $( 146 '<div class="ranking-priorities" data-pollid="' + this.PollId + '"></div>' 147 ); 148 this.PlaceHolder = $( 149 '<div class="rank-placeholder" data-pollid="' + this.PollId + '"></div>' 150 ); 151 this.FeedMeFirst = $( 152 '<div class="rank-feed-first rank-feed">NEXT PRIORITY</div>' 153 ); 154 this.FeedMeNext = $( 155 '<div class="rank-feed-next rank-feed">NEXT PRIORITY</div>' 156 ); 157 158 this.HolderDiv = $( 159 '<div class="ranking-holder" data-pollid="' + this.PollId + '"></div>' 160 ); 161 this.$element.append( 162 $('<div class="sr-only"></div>') 163 .attr("id", "rank-instructions-" + this.PollId) 164 .text(RANK_INSTRUCTIONS) 165 ); 166 this.appendAnnouncer(); 167 this.$element.on("focusin.rankInstructions", function () { 168 that.$element.off("focusin.rankInstructions"); 169 if (rankInstructionsAnnounced[that.PollId]) { 170 return; 171 } 172 rankInstructionsAnnounced[that.PollId] = true; 173 if ( 174 that.pollUI && 175 typeof that.pollUI._announceToScreenReader === "function" 176 ) { 177 that.pollUI._announceToScreenReader(RANK_INSTRUCTIONS); 178 } 179 }); 180 181 this.PriorityDiv.append(this.PlaceHolder); 182 this.PlaceHolder.append(this.FeedMeFirst); 183 this.$element.append(this.PriorityDiv); 184 this.$element.append(this.HolderDiv); 185 186 this.AssignedCt = function () { 187 return this.PriorityDiv.find(".rank-button").length; 188 }; 189 this.UnAssignedCt = function () { 190 return this.HolderDiv.find(".rank-button").length; 191 }; 192 193 this.PriorityDiv.on("keydown", function (e) { 194 if (!$(e.target).hasClass("rank-button")) { 195 return; 196 } 197 var $rankButton = $(e.target); 198 switch (e.which) { 199 case 13: // Enter 200 case 32: // Space â remove from priorities 201 e.preventDefault(); 202 var rankOpt = $rankButton.data("RankOption"); 203 if (rankOpt) { 204 rankOpt.assignRank({ isClick: true }); 205 } 206 break; 207 case 38: // up arrow key 208 if ($rankButton.prev(".rank-button").length) { 209 $rankButton.prev(".rank-button").before($rankButton); 210 that.updateRankings(); 211 $rankButton.focus(); 212 } 213 break; 214 case 40: // bottom arrow key 215 if ($rankButton.next(".rank-button").length) { 216 $rankButton.next(".rank-button").after($rankButton); 217 that.updateRankings(); 218 $rankButton.focus(); 219 } 220 break; 221 } 222 }); 223 224 this.HolderDiv.on("keydown", function (e) { 225 if (!$(e.target).hasClass("rank-button")) { 226 return; 227 } 228 var $rankButton = $(e.target); 229 switch (e.which) { 230 case 13: // Enter 231 case 32: // Space â add to priorities 232 e.preventDefault(); 233 var rankOpt = $rankButton.data("RankOption"); 234 if (rankOpt) { 235 rankOpt.assignRank({ isClick: false }); 236 } 237 break; 238 case 38: // up arrow â navigate between unranked options 239 e.preventDefault(); 240 if ($rankButton.prev(".rank-button").length) { 241 $rankButton.prev(".rank-button").focus(); 242 } 243 break; 244 case 40: // down arrow â navigate between unranked options 245 e.preventDefault(); 246 if ($rankButton.next(".rank-button").length) { 247 $rankButton.next(".rank-button").focus(); 248 } 249 break; 250 } 251 }); 252 253 that.rankOptions = {}; 254 $.each(this.Options, function (i, item) { 255 var newOption = new RankOption( 256 $( 257 '<div class="rank-button" data-poll-option-type="' + 258 item.PollOptionTypeEnum + 259 '" role="button" tabindex="0"></div>' 260 ), 261 item 262 ); 263 newOption.RankPoll = that; 264 that.rankOptions[item.Id] = newOption; 265 touchInit(newOption.$element); 266 that.HolderDiv.append(newOption.$element); 267 }); 268 269 this.updateElement(); 270 this.initSort(); 271 this.UIRendered = true; 272}; 273 274RankPoll.prototype.initSort = function () { 275 var that = this; 276 this.$element 277 .find( 278 ".ranking-priorities[data-pollid=" + 279 that.PollId +
280 "],.ranking-holder[data-pollid=" + 281 that.PollId + 282 "]" 283 ) 284 .sortable({ 285 connectWith: 286 ".ranking-holder[data-pollid=" + 287 that.PollId + 288 "],.ranking-priorities[data-pollid=" + 289 that.PollId + 290 "]", 291 start: function (event, ui) { 292 $(ui.item).addClass("noclick"); 293 }, 294 delay: 200, 295 update: function (event, ui) { 296 that.updateRankings(); 297 }, 298 // placeholder: 'ui-state-highlight', 299 over: function (event, ui) { 300 /* 301 * var cl = ui.item.attr('class'); 302 * $('.ui-state-highlight').addClass(cl); 303 */ 304 }, 305 cancel: 306 ".ranking-submit, .ranking-instruction, .rank-feed, .rank-placeholder", 307 scroll: false 308 }); 309 $().disableSelection && 310 that.$element 311 .find(".ranking-priorities,.ranking-holder") 312 .disableSelection(); 313}; 314 315RankPoll.prototype.updateRankings = function () { 316 var that = this; 317 var rankedElementCount = 0; 318 319 that.$element.find(".rank-button").each(function (i, item) { 320 var rankOpt = $(this).data("RankOption"); 321 if (rankOpt == null) { 322 var pollOptionObj = { 323 optionId: $(item).find(".rank-name").data("option-id"), 324 pollId: that.PollId 325 }; 326 rankOpt = new RankOption($(item), pollOptionObj); 327 } 328 rankOpt.Rank = i + 1; 329 $(this).find(".rank-number").text(rankOpt.OrdinalRank()); 330 if ($(this).parent().hasClass("ranking-priorities")) { 331 if ( 332 that.pollData.optionLimit > 0 && 333 rankedElementCount >= that.pollData.optionLimit 334 ) { 335 // We've already reached our limit, put this one back in the holder 336 $(this).removeClass("assigned"); 337 that.HolderDiv.append($(this)); 338 } 339 $(this).addClass("assigned"); 340 rankedElementCount++; 341 $(this).attr( 342 "aria-label", 343 rankOpt.OrdinalRank() + ": " + rankOpt.LabelText() 344 ); 345 } else { 346 $(this).removeClass("assigned"); 347 $(this).attr("aria-label", rankOpt.LabelText() + ", not ranked"); 348 } 349 }); 350 351 if ( 352 that.pollData.extendedOptions.requireAllOptions && 353 that.pollData.optionLimit > 0 354 ) { 355 if (that.pollData.optionLimit === that.AssignedCt()) { 356 that.SubmitDiv.detach(); 357 that.SubmitDiv = $(confirmButtonHTML(that.PollId)); 358 that.SubmitDiv.on("click", function () { 359 that.vote(); 360 }); 361 } else if (that.pollData.optionLimit > that.AssignedCt()) { 362 that.SubmitDiv.detach(); 363 that.SubmitDiv = rankInstructionElement( 364 that.PollId, 365 "Please rank " + that.pollData.optionLimit + " options" 366 ); 367 } 368 } else if (that.pollData.extendedOptions.requireAllOptions) { 369 if (that.UnAssignedCt() === 1) { 370 // rank the final option automatically 371 var lastOption = that.HolderDiv.find(".rank-button"); 372 lastOption.click(); 373 374 that.SubmitDiv.detach(); 375 that.SubmitDiv = $(confirmButtonHTML(that.PollId)); 376 that.SubmitDiv.on("click", function () { 377 that.vote(); 378 }); 379 } 380 } 381 382 if (that.AssignedCt() == 0) { 383 that.PriorityDiv.append(that.PlaceHolder); 384 that.FeedMeNext.detach(); 385 that.SubmitDiv.detach(); 386 that.PriorityDiv.removeClass("rank-full"); 387 that.PlaceHolder.append(that.FeedMeFirst); 388 } else if ( 389 that.pollData.optionLimit > 0 && 390 that.AssignedCt() >= that.pollData.optionLimit 391 ) { 392 that.PlaceHolder.detach(); 393 that.PriorityDiv.addClass("rank-full"); 394 that.HolderDiv.prepend(that.SubmitDiv); 395 } else if (that.UnAssignedCt() > 0) { 396 that.PriorityDiv.append(that.PlaceHolder); 397 that.FeedMeFirst.detach(); 398 that.PlaceHolder.append(that.FeedMeNext); 399 that.PriorityDiv.removeClass("rank-full"); 400 that.HolderDiv.prepend(that.SubmitDiv); 401 } else { 402 that.PlaceHolder.detach(); 403 that.PriorityDiv.addClass("rank-full"); 404 that.HolderDiv.prepend(that.SubmitDiv); 405 } 406 407 that.updateSelectedOptionArrayFromUI(); 408}; 409 410RankPoll.prototype.updateElement = function () { 411 this.$element.data("RankPoll", this); 412}; 413RankPoll.prototype.appendAnnouncer = function () {
414 this.$announcer = $('<div class="sr-only rank-announce"></div>') 415 .attr("id", "rank-announce-" + this.PollId) 416 .attr("role", "status") 417 .attr("aria-live", "polite"); 418 this.$element.append(this.$announcer); 419}; 420RankPoll.prototype.announce = function (message) { 421 var that = this; 422 if (!that.$announcer || !that.$announcer.length) { 423 return; 424 } 425 if (that.announceClearTimeout) { 426 clearTimeout(that.announceClearTimeout); 427 that.announceClearTimeout = null; 428 } 429 that.$announcer.text(message); 430 that.announceClearTimeout = setTimeout(function () { 431 that.announceClearTimeout = null; 432 that.$announcer.text(""); 433 }, 5000); 434}; 435RankPoll.prototype.vote = function () { 436 var that = this; 437 var pollId = this.PollId; 438 this.updateSelectedOptionArrayFromUI(); 439 440 this.announce("Your ranking selections have been saved"); 441 442 this.callbacks.vote && this.callbacks.vote(that.selectedOptionArray); 443}; 444RankPoll.prototype.updateSelectedOptionArrayFromUI = function () { 445 var that = this; 446 that.selectedOptionArray = []; 447 this.$element.find(".rank-button.assigned").each(function (i, item) { 448 var rankOpt = $(this).data("RankOption"); 449 that.selectedOptionArray.push(rankOpt.OptionId); 450 }); 451 that.pollUI._userResponseCallback(); 452}; 453 454var RankOption = function ($element, pollOption) { 455 var that = this; 456 this.$element = $element; 457 this.OptionId = pollOption.Id; 458 this.PollId = pollOption.PollId; 459 this.Name = pollOption.Name; 460 this.RankPoll = null; 461 this.Rank = 0; 462 this.PollOptionTypeEnum = pollOption.PollOptionTypeEnum; 463 var $existingName = this.$element.find(".rank-name"); 464 if ($existingName.length) { 465 this.NumberDiv = this.$element 466 .find(".rank-number") 467 .attr("aria-hidden", "true"); 468 this.NameDiv = $existingName.attr("aria-hidden", "true"); 469 this.Name = this.Name || $existingName.text().trim(); 470 } else { 471 this.NumberDiv = $('<div class="rank-number" aria-hidden="true"></div>'); 472 this.$element.append(this.NumberDiv); 473 this.NameDiv = $( 474 '<div class="rank-name option-name" aria-hidden="true" data-option-id="' + 475 pollOption.Id + 476 '">' + 477 '<i class="fa-solid fa-grip-vertical" aria-hidden="true"></i>' + 478 pollOption.Name + 479 "</div>" 480 ); 481 this.$element.append(this.NameDiv); 482 } 483 484 if (!this.$element.attr("aria-label")) { 485 this.$element.attr("aria-label", this.LabelText() + ", not ranked"); 486 } 487 488 $element.on("click", function () { 489 if ($element.hasClass("noclick")) { 490 $element.removeClass("noclick"); 491 return; 492 } 493 if (that.PollOptionTypeEnum === 1 && !$element.hasClass("assigned")) { 494 otherPollOptionUI($element); 495 return; 496 } 497 that.assignRank({ isClick: true }); 498 that.updateElement(); 499 }); 500 this.updateElement(); 501}; 502RankOption.prototype.updateElement = function () { 503 this.$element.data("RankOption", this); 504}; 505RankOption.prototype.LabelText = function () { 506 var text = this.NameDiv ? this.NameDiv.text().trim() : ""; 507 return text || this.Name || ""; 508}; 509RankOption.prototype.assignRank = function (config) { 510 config = config || {}; 511 var e = jQuery.Event("keyup"); 512 e.which = 40; // it is down 513 this.$element.trigger(e); 514 515 var $focusTarget = null; 516 517 if (this.$element.hasClass("assigned") && config.isClick) { 518 // user activated an existing priority â move back to holder 519 // focus the next sibling priority, falling back to previous, then the moved element itself 520 var $nextFocus = this.$element.next(".rank-button"); 521 if (!$nextFocus.length) { 522 $nextFocus = this.$element.prev(".rank-button"); 523 } 524 $focusTarget = $nextFocus.length ? $nextFocus : this.$element; 525 526 this.RankPoll.HolderDiv.append(this.$element); 527 this.$element.removeClass("assigned"); 528 this.RankPoll.announce( 529 "Removed " + 530 this.$element.find(".rank-name").text() + 531 " from priorities" 532 ); 533 } else if ( 534 this.RankPoll.pollData.optionLimit > 0 && // already selected max priorities 535 this.RankPoll.$element.find(".rank-button.assigned").length >= 536 this.RankPoll.pollData.optionLimit 537 ) { 538 var $oldPriority = this.RankPoll.$element 539 .find(".rank-button.assigned") 540 .last(); 541 this.RankPoll.HolderDiv.prepend($oldPriority); 542 $oldPriority.removeClass("assigned"); 543 this.$element.addClass("assigned"); 544 this.RankPoll.PriorityDiv.append(this.$element); 545 this.RankPoll.announce( 546 "Alert removed " + 547 $oldPriority.find(".rank-name").text() + 548 " as priority. Maximum " + 549 this.RankPoll.pollData.optionLimit + 550 " priorities" 551 ); 552 $focusTarget = this.$element; 553 } else { 554 this.$element.addClass("assigned"); 555 this.RankPoll.PriorityDiv.append(this.$element); 556 this.RankPoll.announce( 557 "Added " + this.$element.find(".rank-name").text() + " as priority" 558 ); 559 $focusTarget = this.$element; 560 } 561 this.updateElement(); 562 this.RankPoll.updateRankings(); 563 if ($focusTarget) {
564 $focusTarget.focus(); 565 } 566}; 567RankOption.prototype.OrdinalRank = function () { 568 return getOrdinalRankFromInt(this.Rank); 569}; 570 571function confirmButtonHTML(pollId = 0, buttonText = "Save selection") { 572 return ` 573 <button type="button" class="ranking-submit pi-btn-primary" data-pollid="${pollId}" aria-live="polite"> 574 ${buttonText} 575 </button>`; 576} 577 578// Fills the submit slot while a "rank every option" question is still incomplete. It is an 579// instruction, not a control: it used to be a div dressed as a dark button with role="button" 580// and tabindex="0" but no handler, so screen reader and keyboard users hit a button that did 581// nothing (AB#10620). Deliberately not .ranking-submit, which polls-bootstrap3-shim.css styles 582// as a button, and not a live region: updateRankings re-inserts it after every rank and the 583// announcer already voices each change. The real Save button replaces it once every option 584// is ranked. 585function rankInstructionElement(pollId, instructionText) { 586 return $( 587 '<div class="ranking-instruction" data-pollid="' + pollId + '"></div>' 588 ).text(instructionText); 589} 590 591function getOrdinalRankFromInt(i) { 592 var j = i % 10, 593 k = i % 100; 594 if (j == 1 && k != 11) { 595 return i + "st"; 596 } 597 if (j == 2 && k != 12) { 598 return i + "nd"; 599 } 600 if (j == 3 && k != 13) { 601 return i + "rd"; 602 } 603 return i + "th"; 604} 605 606var startingPos = []; 607var touchClick = true; 608function touchHandler(event) { 609 var touch = event.changedTouches[0]; 610 611 var simulatedEvent = document.createEvent("MouseEvent"); 612 613 simulatedEvent.initMouseEvent( 614 { 615 touchstart: "mousedown", 616 touchmove: "mousemove", 617 touchend: "mouseup" 618 }[event.type], 619 true, 620 true, 621 window, 622 1, 623 touch.screenX, 624 touch.screenY, 625 touch.clientX, 626 touch.clientY, 627 false, 628 false, 629 false, 630 false, 631 0, 632 null 633 ); 634 if (event.type == "mousedown") { 635 touchClick = true; 636 setTimeout(function () { 637 touchClick = false; 638 }, 100); 639 } 640 if (event.type == "mouseup") { 641 if (touchClick) { 642 var clickEvent = document.createEvent("MouseEvent"); 643 clickEvent.initMouseEvent( 644 "click", 645 true, 646 true, 647 window, 648 1, 649 touch.screenX, 650 touch.screenY, 651 touch.clientX, 652 touch.clientY, 653 false, 654 false, 655 false, 656 false, 657 0, 658 null 659 ); 660 touch.target.dispatchEvent(clickEvent); 661 } 662 } 663 if (event.type == "touchmove" && window.isDragging) { 664 event.preventDefault(); 665 } 666 touch.target.dispatchEvent(simulatedEvent); 667} 668window.isDragging = false; 669function touchInit(element) { 670 if (window.mobilecheck()) { 671 element[0].addEventListener("touchstart", touchHandler, true); 672 element[0].addEventListener("touchmove", touchHandler, true); 673 element[0].addEventListener("touchend", touchHandler, true); 674 element[0].addEventListener("touchcancel", touchHandler, true); 675 676 element 677 .mousedown(function (evt) { 678 window.isDragging = false; 679 startingPos = [evt.pageX, evt.pageY]; 680 }) 681 .mousemove(function (evt) { 682 if (!(evt.pageX === startingPos[0] && evt.pageY === startingPos[1])) { 683 window.isDragging = true; 684 } 685 }) 686 .mouseup(function () { 687 window.isDragging = false; 688 startingPos = []; 689 }); 690 } 691} 692 693window.mobilecheck = function () { 694 var check = false; 695 (function (a) { 696 if ( 697 /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test( 698 a 699 ) || 700 /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|
700sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test( 701 a.substr(0, 4) 702 ) 703 ) { 704 check = true; 705 } 706 })(navigator.userAgent || navigator.vendor || window.opera); 707 return check; 708}; 709 710function otherPollOptionUI($pollOptionElement) { 711 var otherInputField = $pollOptionElement.find("input.poll-option-other"); 712 var blurToggle = false; 713 if (otherInputField.length === 0) { 714 var otherLabel = $pollOptionElement.find(".option-name").text(); 715 $pollOptionElement.find(".option-name").html(""); 716 717 var buttonText = "Add Other"; 718 var inputOtherHTML = ""; 719 inputOtherHTML += 720 '<div class="input-group option-other-form">' + 721 '<input type="text" class="form-control poll-option-other" aria-label="Enter your other response">' + 722 '<span class="input-group-btn">' + 723 '<button id="" class="btn btn-success btn-s-lg m-b-xxs poll-option-other-submit">' + 724 buttonText + 725 "</button></span>" + 726 "</div>"; 727 728 $pollOptionElement.data("suspendedRankRole", { 729 role: $pollOptionElement.attr("role"), 730 ariaLabel: $pollOptionElement.attr("aria-label"), 731 tabindex: $pollOptionElement.attr("tabindex") 732 }); 733 $pollOptionElement 734 .removeAttr("role") 735 .removeAttr("aria-label") 736 .attr("tabindex", "-1"); 737 $pollOptionElement.find(".option-name").removeAttr("aria-hidden"); 738 $pollOptionElement.find(".option-name").append(inputOtherHTML); 739 $pollOptionElement.find(".option-name input.poll-option-other").focus(); 740 $pollOptionElement.addClass("contains-other-form"); 741 742 /** 743 * hide the form when input is no longer focused 744 * reinsert original 'other' label 745 */ 746 $pollOptionElement.find("input.poll-option-other").on("blur", function () { 747 $pollOptionElement.find(".option-other-form").hide(); 748 $pollOptionElement.find(".option-name").text(otherLabel); 749 $pollOptionElement.find(".option-name").attr("aria-hidden", "true"); 750 var savedRankRole = $pollOptionElement.data("suspendedRankRole"); 751 if (savedRankRole) { 752 if (savedRankRole.role) { 753 $pollOptionElement.attr("role", savedRankRole.role); 754 } 755 if (savedRankRole.ariaLabel) { 756 $pollOptionElement.attr("aria-label", savedRankRole.ariaLabel); 757 } 758 if (typeof savedRankRole.tabindex !== "undefined") { 759 $pollOptionElement.attr("tabindex", savedRankRole.tabindex); 760 } else { 761 $pollOptionElement.removeAttr("tabindex"); 762 } 763 $pollOptionElement.removeData("suspendedRankRole"); 764 } 765 blurToggle = true; 766 }); 767 } else if (blurToggle) { 768 //This action fires when someone is toggling the "Add other" input to show 769 //previously we cleared the input, but we need to preserve the text to prevent user pain 770 $pollOptionElement.find(".option-other-form").show(); 771 blurToggle = false; 772 } 773}
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.