PageSourceSearch

https://deldotinteract.org/Scripts/RankPoll.js

js deldotinteract.org collected 2026-09-29 21:53:48 UTC 26,771 bytes, 773 lines download raw bytes

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.