Sleazy Fork is available in English.

Chaturbate Easy Tipping

Adds a new tipping popup and modifies the existing one

  1. // ==UserScript==
  2. // @name Chaturbate Easy Tipping
  3. // @namespace madTipper
  4. // @version 0.2
  5. // @author omgmikey
  6. // @match https://chaturbate.com/*
  7. // @require https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js
  8. // @require https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js
  9. // @require https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // @license MIT
  13. // @run-at document-idle
  14. // @description Adds a new tipping popup and modifies the existing one
  15. // ==/UserScript==
  16. /* globals jQuery, $, initialRoomDossier */
  17. /* eslint-disable dot-notation, no-multi-spaces */
  18.  
  19. var ID_PREFIX = '#madTipper'
  20. var CLASS_PREFIX = '.madTipper'
  21. var CLASS_INPUT = CLASS_PREFIX + '_input';
  22.  
  23. var HTML_IDS = {
  24. 'BUTTON': 'button',
  25. 'BUTTON_BG': 'button_bg',
  26. 'POPUP': 'popup',
  27. 'AMOUNT': 'amount',
  28. 'COUNT': 'count',
  29. 'INTERVAL': 'interval',
  30. 'VARIANCE_LOWER': 'variance_lower',
  31. 'VARIANCE_UPPER': 'variance_upper',
  32.  
  33. 'START': 'start',
  34. 'STOP': 'stop',
  35. 'TOTAL': 'total',
  36. 'ETA': 'eta'
  37. }
  38.  
  39. for (var key in HTML_IDS) {
  40. HTML_IDS[key] = ID_PREFIX + '_' + HTML_IDS[key];
  41. }
  42.  
  43. var defaultSendTipButton = null;
  44. var buttonAttach = null;
  45. var popupAttach = null;
  46.  
  47. var tipsLeft = 0;
  48. var tipFunctionTimeout = null;
  49. var juration = loadJuration();
  50.  
  51. waitForKeyElements("#sendTipButton", initialize, true);
  52.  
  53. function initialize() {
  54. defaultSendTipButton = $('#sendTipButton');
  55. buttonAttach = defaultSendTipButton.parent();
  56. popupAttach = $('#SplitModeTipCallout').parent();
  57.  
  58. createTipperButton();
  59. createTipperPopup();
  60. injectCSS();
  61. improveDefaultTipPopup();
  62.  
  63. loadPreviousSettings();
  64. initializeButtonCallbacks();
  65. updateTipperButton();
  66. };
  67.  
  68. function createTipperButton() {
  69.  
  70. buttonAttach.append('<div id="madTipper_button_bg" class="sendTipButton"><a href="#" id="madTipper_button"></a></div>');
  71. }
  72.  
  73. function updateTipperButton() {
  74. if (tipsLeft == 0) {
  75. $(HTML_IDS['BUTTON_BG']).html('MAD TIPPER').css({'width': '93px', 'max-width': '93px'});
  76. }
  77. else {
  78. $(HTML_IDS['BUTTON_BG']).html('MAD TIPPER (' + tipsLeft + ')').css({'width': '120px', 'max-width': '120px'});
  79. }
  80. }
  81.  
  82. function createTipperPopup() {
  83.  
  84. popupAttach.append(
  85. '<div class="overlay_popup" id="madTipper_popup">' +
  86. '<table width="100%" border="0" cellspacing="0" cellpadding="0">' +
  87. '<tbody>' +
  88. '<tr>' +
  89. '<td class="formborder">' +
  90. '<div class="title">Mad Tipper</div>' +
  91. '<div class="body">' +
  92. '<form>' +
  93. '<label>Amount per tip:</label><br >' +
  94. '<input type="text" id="madTipper_amount" class="madTipper_input">' +
  95. '<br />' +
  96.  
  97. '<label>Number of tips:</label><br >' +
  98. '<input type="text" id="madTipper_count" class="madTipper_input">' +
  99. '<br /><hr />' +
  100.  
  101. '<label>Interval:</label><br >' +
  102. '<input type="text" id="madTipper_interval" class="madTipper_input">' +
  103. '<br />' +
  104.  
  105. '<label>Interval variance lower (optional):</label><br >' +
  106. '<input type="text" id="madTipper_variance_lower" class="madTipper_input">' +
  107. '<br />' +
  108.  
  109. '<label>Interval variance upper (optional):</label><br >' +
  110. '<input type="text" id="madTipper_variance_upper" class="madTipper_input">' +
  111. '<br /><hr />' +
  112.  
  113. 'Total tip: ' + '<a id="madTipper_total"></a>' +
  114. '<br />' +
  115. 'Estimated duration: ' + '<a id="madTipper_eta"></a>' +
  116. '</form>' +
  117. '<hr />' +
  118. '<button id="madTipper_start">Start</button>' +
  119. '<button id="madTipper_stop" disabled="disabled">Stop</button>' +
  120. '</div>' +
  121. '</td>' +
  122. '</tr>' +
  123. '</tbody>' +
  124. '</table>' +
  125. '</div>'
  126. );
  127. }
  128.  
  129. function injectCSS() {
  130.  
  131. $(HTML_IDS['BUTTON_BG']).attr('style', defaultSendTipButton.attr('style'));
  132.  
  133. $(HTML_IDS['START']).attr('style', defaultSendTipButton.attr('style'));
  134. $(HTML_IDS['STOP']).attr('style', defaultSendTipButton.attr('style'));
  135.  
  136. $(HTML_IDS['START']).addClass("sendTipButton");
  137.  
  138. $(HTML_IDS['START']).prop('disabled', false);
  139. $(HTML_IDS['STOP']).prop('disabled', true);
  140.  
  141. $(HTML_IDS['POPUP']).attr('style', $('#SplitModeTipCallout').attr('style'));
  142. $(HTML_IDS['POPUP']).css('left', '200px');
  143.  
  144. $(CLASS_INPUT).css({
  145. 'width': 'auto',
  146. 'margin-bottom': '10px'
  147. });
  148.  
  149. $(HTML_IDS['POPUP']).draggable();
  150.  
  151. $(HTML_IDS['POPUP'] + ' .formborder').css({
  152. 'height': '420px'
  153. });
  154. }
  155.  
  156. function improveDefaultTipPopup() {
  157.  
  158. $('#SplitModeTipCallout').draggable();
  159.  
  160. $('#SplitModeTipCallout .tipMessageInput').parent()
  161. .append('<input type="checkbox" id="tip_keepopen"></input>')
  162. .append('<label for="tip_keepopen">Keep this window open after tipping</label>');
  163.  
  164. var tipPopup = $('#SplitModeTipCallout');
  165. var keepOpenCheckbox = $('#tip_keepopen');
  166. var popupIsForcedOpen = false;
  167.  
  168. var tipPopupForm = window.$('#SplitModeTipCallout form');
  169. tipPopupForm.submit(onFormSubmit);
  170.  
  171. $('body').click(function(ev) {
  172.  
  173. if ($('#SplitModeTipCallout .sendTip button[type="submit"]').is(ev.target)) {
  174. popupIsForcedOpen = false;
  175. return;
  176. }
  177.  
  178. if (!popupIsForcedOpen || tipPopup.has(ev.target).length) {
  179. return;
  180. }
  181.  
  182. if (tipPopup.is(':visible')) {
  183. tipPopup.hide();
  184. }
  185.  
  186. popupIsForcedOpen = false;
  187. });
  188.  
  189. function onFormSubmit() {
  190.  
  191. if (!keepOpenCheckbox.is(':checked')) {
  192. return;
  193. }
  194.  
  195. if (!tipPopup.is(':visible')) {
  196. tipPopup.show();
  197. popupIsForcedOpen = true;
  198.  
  199. keepPopupOpen();
  200. }
  201. }
  202.  
  203. function keepPopupOpen() {
  204. if (!tipPopup.is(':visible')) {
  205. defaultSendTipButton.trigger('click');
  206. tipPopup.show();
  207. return;
  208. }
  209.  
  210. setTimeout(() => {
  211. keepPopupOpen();
  212. }, 100);
  213. }
  214. }
  215.  
  216. function startTipping() {
  217.  
  218. var err = verifyTipperFields();
  219.  
  220. if (err) {
  221. alert(err);
  222. stopTipping();
  223. return;
  224. }
  225.  
  226. saveCurrentSettings();
  227.  
  228. $(HTML_IDS['START']).prop('disabled', true);
  229. $(HTML_IDS['STOP']).prop('disabled', false);
  230. $(CLASS_INPUT).prop('disabled', true);
  231.  
  232. tipsLeft = getTipCount();
  233.  
  234. /* we really want to send the first one immediately */
  235. sendTip();
  236.  
  237. if (tipsLeft > 0) {
  238. chainQueueTips();
  239. }
  240. }
  241.  
  242. function verifyTipperFields() {
  243.  
  244. function isInt(value) {
  245. var regex = /^[0-9]+$/;
  246. return regex.test(String(value));
  247. }
  248.  
  249. function isDuration(value) {
  250. try {
  251. juration.parse(value);
  252. return true;
  253. }
  254. catch(ex) {
  255. return false;
  256. }
  257. }
  258.  
  259. function isDurationOrEmpty(value) {
  260. return value === '' || isDuration(value);
  261. }
  262.  
  263. if (!isInt(getTipAmountRaw()) || getTipAmount() <= 0) {
  264. return 'Tip amount field should be a positive integer.';
  265. }
  266.  
  267. if (!isInt(getTipCountRaw()) || getTipCount() <= 0) {
  268. return 'Tip count field should be a positive integer.';
  269. }
  270.  
  271. if (!isDuration(getTipInterval())) {
  272. return 'Tip interval should contain a duration. E.g.: "2.5s", "1", "2min"';
  273. }
  274.  
  275. if (!isDurationOrEmpty(getVarianceLowerRaw()) || !isDurationOrEmpty(getVarianceUpperRaw())) {
  276. return 'Variance fields should contain durations, or be left blank. E.g.: "", "2.5s"';
  277. }
  278. }
  279.  
  280. function getSleepInterval() {
  281.  
  282. var interval = juration.parse(getTipInterval());
  283. var lower_bound = interval - getVarianceLower();
  284. var upper_bound = interval + getVarianceUpper();
  285.  
  286. return getRandomNumber(lower_bound, upper_bound) * 1000;
  287. }
  288.  
  289. function getRandomNumber(min, max) {
  290.  
  291. return Math.random() * (max - min) + min;
  292. }
  293.  
  294. function chainQueueTips() {
  295.  
  296. var sleepTime = getSleepInterval();
  297.  
  298. tipFunctionTimeout = setTimeout(function() {
  299. sendTip(chainQueueTips);
  300. }, sleepTime);
  301. }
  302.  
  303. function sendTip(queueNextTipFn) {
  304.  
  305. var roomInfo = JSON.parse(initialRoomDossier);
  306. var queryUrl = '/tipping/send_tip/' + roomInfo.broadcaster_username + '/';
  307.  
  308. var params = {
  309. 'csrfmiddlewaretoken': $.cookie('csrftoken'),
  310. 'tip_amount': getTipAmount(),
  311. 'tip_room_type': roomInfo.room_status,
  312. 'tip_type': 'public',
  313. 'from_username': roomInfo.viewer_username,
  314. 'source': 'theater',
  315. 'video_mode': 'split',
  316. 'message': '',
  317. };
  318.  
  319. var formData = new FormData();
  320.  
  321. for (const [k, v] of Object.entries(params)) {
  322. formData.append(k , v);
  323. }
  324.  
  325. $.ajax({
  326. url: queryUrl,
  327. data: formData,
  328. method: 'POST',
  329. type: 'POST',
  330. cache: false,
  331. contentType: false,
  332. processData: false,
  333. success: function(response) {
  334. if (response.error) {
  335. alert(response.error);
  336. stopTipping();
  337. }
  338. }
  339. });
  340.  
  341. updateTipsLeft();
  342.  
  343. if (tipsLeft === 0) {
  344. stopTipping();
  345. }
  346. else if (queueNextTipFn) {
  347. queueNextTipFn();
  348. }
  349. }
  350.  
  351. function updateTipsLeft() {
  352.  
  353. tipsLeft--;
  354. updateTipperButton();
  355. }
  356.  
  357. function stopTipping() {
  358.  
  359. clearTimeout(tipFunctionTimeout);
  360. tipFunctionTimeout = null;
  361.  
  362. tipsLeft = 0;
  363. updateTipperButton();
  364.  
  365. $(HTML_IDS['STOP']).prop('disabled', true);
  366. $(HTML_IDS['START']).prop('disabled', false);
  367. $(CLASS_INPUT).prop('disabled', false);
  368. }
  369.  
  370. function initializeButtonCallbacks() {
  371.  
  372. var popup = $(HTML_IDS['POPUP']);
  373. var button = $(HTML_IDS['BUTTON_BG']);
  374.  
  375. button.click(function(ev) {
  376. if (popup.is(':visible')) {
  377. popup.hide();
  378. }
  379. else {
  380. $(HTML_IDS['POPUP']).attr('style', $('#SplitModeTipCallout').attr('style'));
  381. popup.show();
  382. }
  383. });
  384.  
  385. popup.click(function(ev) {
  386. ev.stopPropagation();
  387. });
  388.  
  389. $(HTML_IDS['START']).click(function() {
  390. startTipping();
  391. $(HTML_IDS['POPUP']).hide();
  392. });
  393.  
  394. $(HTML_IDS['STOP']).click(function() {
  395. stopTipping();
  396. });
  397.  
  398. $('body').click(function(ev) {
  399. if (ev.target.id != button.prop('id')) {
  400. $(HTML_IDS['POPUP']).hide();
  401. }
  402. });
  403.  
  404. $(CLASS_INPUT).change(function() {
  405. calculateAndSetTotalTip();
  406. calculateAndSetETA();
  407. });
  408. }
  409.  
  410. function calculateAndSetTotalTip() {
  411.  
  412. var value = $(HTML_IDS['AMOUNT']).val() * $(HTML_IDS['COUNT']).val();
  413. $(HTML_IDS['TOTAL']).html(value + ' tokens');
  414. }
  415.  
  416. function calculateAndSetETA() {
  417. var interval = juration.parse($(HTML_IDS['INTERVAL']).val());
  418.  
  419. /* we're not counting the first tip */
  420. var count = getTipCount() - 1;
  421.  
  422. var variance_lower = getVarianceLower();
  423. var variance_upper = getVarianceUpper();
  424.  
  425. var eta = (interval + variance_upper - variance_lower) * count;
  426. $(HTML_IDS['ETA']).html(juration.stringify(eta, {'format': 'long', 'units': 2}));
  427. }
  428.  
  429. function getTipAmount() {
  430.  
  431. return parseInt(getTipAmountRaw());
  432. }
  433.  
  434. function getTipAmountRaw() {
  435.  
  436. return $(HTML_IDS['AMOUNT']).val();
  437. }
  438.  
  439. function getTipInterval() {
  440.  
  441. return $(HTML_IDS['INTERVAL']).val();
  442. }
  443.  
  444. function getTipCount() {
  445.  
  446. return parseInt(getTipCountRaw());
  447. }
  448.  
  449. function getTipCountRaw() {
  450.  
  451. return $(HTML_IDS['COUNT']).val();
  452. }
  453.  
  454. function getVarianceLower() {
  455.  
  456. return parseVariance(getVarianceLowerRaw());
  457. }
  458.  
  459. function getVarianceLowerRaw() {
  460.  
  461. return $(HTML_IDS['VARIANCE_LOWER']).val();
  462. }
  463.  
  464.  
  465. function getVarianceUpper() {
  466.  
  467. return parseVariance(getVarianceUpperRaw());
  468. }
  469.  
  470. function getVarianceUpperRaw() {
  471.  
  472. return $(HTML_IDS['VARIANCE_UPPER']).val();
  473. }
  474.  
  475. function parseVariance(variance) {
  476.  
  477. if (variance == '0') {
  478. variance = 0;
  479. }
  480.  
  481. variance = variance || 0;
  482.  
  483. if (variance != 0) {
  484. variance = juration.parse(variance);
  485. }
  486.  
  487. return variance;
  488. }
  489.  
  490. function saveCurrentSettings() {
  491.  
  492. GM_setValue('amount', getTipAmount());
  493. GM_setValue('interval', getTipInterval());
  494. GM_setValue('count', getTipCount());
  495. GM_setValue('variance_lower', getVarianceLower());
  496. GM_setValue('variance_upper', getVarianceUpper());
  497. }
  498.  
  499. function loadPreviousSettings() {
  500.  
  501. var amount = GM_getValue('amount', 1);
  502. $(HTML_IDS['AMOUNT']).val(amount);
  503.  
  504. var count = GM_getValue('count', 10);
  505. $(HTML_IDS['COUNT']).val(count);
  506.  
  507. var interval = GM_getValue('interval', '1s');
  508. $(HTML_IDS['INTERVAL']).val(interval);
  509.  
  510. var variance_lower = GM_getValue('variance_lower', '');
  511. $(HTML_IDS['VARIANCE_LOWER']).val(variance_lower);
  512.  
  513. var variance_upper = GM_getValue('variance_upper', '');
  514. $(HTML_IDS['VARIANCE_UPPER']).val(variance_upper);
  515.  
  516. calculateAndSetTotalTip();
  517. calculateAndSetETA();
  518. }
  519.  
  520.  
  521. // Script ends here
  522. // Libs included because they're not on a popular cdn
  523.  
  524.  
  525. /*
  526. * juration - a natural language duration parser
  527. * https://github.com/domchristie/juration
  528. *
  529. * Copyright 2011, Dom Christie
  530. * Licenced under the MIT licence
  531. *
  532. */
  533.  
  534. function loadJuration() {
  535.  
  536. var UNITS = {
  537. seconds: {
  538. patterns: ['second', 'sec', 's'],
  539. value: 1,
  540. formats: {
  541. 'chrono': '',
  542. 'micro': 's',
  543. 'short': 'sec',
  544. 'long': 'second'
  545. }
  546. },
  547. minutes: {
  548. patterns: ['minute', 'min', 'm(?!s)'],
  549. value: 60,
  550. formats: {
  551. 'chrono': ':',
  552. 'micro': 'm',
  553. 'short': 'min',
  554. 'long': 'minute'
  555. }
  556. },
  557. hours: {
  558. patterns: ['hour', 'hr', 'h'],
  559. value: 3600,
  560. formats: {
  561. 'chrono': ':',
  562. 'micro': 'h',
  563. 'short': 'hr',
  564. 'long': 'hour'
  565. }
  566. },
  567. days: {
  568. patterns: ['day', 'dy', 'd'],
  569. value: 86400,
  570. formats: {
  571. 'chrono': ':',
  572. 'micro': 'd',
  573. 'short': 'day',
  574. 'long': 'day'
  575. }
  576. },
  577. weeks: {
  578. patterns: ['week', 'wk', 'w'],
  579. value: 604800,
  580. formats: {
  581. 'chrono': ':',
  582. 'micro': 'w',
  583. 'short': 'wk',
  584. 'long': 'week'
  585. }
  586. },
  587. months: {
  588. patterns: ['month', 'mon', 'mo', 'mth'],
  589. value: 2628000,
  590. formats: {
  591. 'chrono': ':',
  592. 'micro': 'm',
  593. 'short': 'mth',
  594. 'long': 'month'
  595. }
  596. },
  597. years: {
  598. patterns: ['year', 'yr', 'y'],
  599. value: 31536000,
  600. formats: {
  601. 'chrono': ':',
  602. 'micro': 'y',
  603. 'short': 'yr',
  604. 'long': 'year'
  605. }
  606. }
  607. };
  608.  
  609. var stringify = function(seconds, options) {
  610.  
  611. if(!_isNumeric(seconds)) {
  612. throw "juration.stringify(): Unable to stringify a non-numeric value";
  613. }
  614.  
  615. if((typeof options === 'object' && options.format !== undefined) && (options.format !== 'micro' && options.format !== 'short' && options.format !== 'long' && options.format !== 'chrono')) {
  616. throw "juration.stringify(): format cannot be '" + options.format + "', and must be either 'micro', 'short', or 'long'";
  617. }
  618.  
  619. var defaults = {
  620. format: 'short',
  621. units: undefined
  622. };
  623.  
  624. var opts = _extend(defaults, options);
  625.  
  626. var units = ['years', 'months', 'days', 'hours', 'minutes', 'seconds'], values = [];
  627. var remaining = seconds;
  628. var activeUnits = 0;
  629. for(var i = 0, len = units.length;
  630. i < len && (opts.units == undefined || activeUnits < opts.units);
  631. i++) {
  632. var unit = UNITS[units[i]];
  633. values[i] = Math.floor(remaining / unit.value);
  634. if (values[i] > 0 || activeUnits > 0)
  635. activeUnits++;
  636.  
  637. if(opts.format === 'micro' || opts.format === 'chrono') {
  638. values[i] += unit.formats[opts.format];
  639. }
  640. else {
  641. values[i] += ' ' + _pluralize(values[i], unit.formats[opts.format]);
  642. }
  643. remaining = remaining % unit.value;
  644. }
  645. var output = '';
  646. for(i = 0, len = values.length; i < len; i++) {
  647. if(values[i].charAt(0) !== "0" && opts.format != 'chrono') {
  648. output += values[i] + ' ';
  649. }
  650. else if (opts.format == 'chrono') {
  651. output += _padLeft(values[i]+'', '0', i==values.length-1 ? 2 : 3);
  652. }
  653. }
  654. return output.replace(/\s+$/, '').replace(/^(00:)+/g, '').replace(/^0/, '');
  655. };
  656.  
  657. var parse = function(string) {
  658.  
  659. // returns calculated values separated by spaces
  660. for(var unit in UNITS) {
  661. for(var i = 0, mLen = UNITS[unit].patterns.length; i < mLen; i++) {
  662. var regex = new RegExp("((?:\\d+\\.\\d+)|\\d+)\\s?(" + UNITS[unit].patterns[i] + "s?(?=\\s|\\d|\\b))", 'gi');
  663. string = string.replace(regex, function(str, p1, p2) {
  664. return " " + (p1 * UNITS[unit].value).toString() + " ";
  665. });
  666. }
  667. }
  668.  
  669. var sum = 0,
  670. numbers = string
  671. .replace(/(?!\.)\W+/g, ' ') // replaces non-word chars (excluding '.') with whitespace
  672. .replace(/^\s+|\s+$|(?:and|plus|with)\s?/g, '') // trim L/R whitespace, replace known join words with ''
  673. .split(' ');
  674.  
  675. for(var j = 0, nLen = numbers.length; j < nLen; j++) {
  676. if(numbers[j] && isFinite(numbers[j])) {
  677. sum += parseFloat(numbers[j]);
  678. } else if(!numbers[j]) {
  679. throw "juration.parse(): Unable to parse: a falsey value";
  680. } else {
  681. // throw an exception if it's not a valid word/unit
  682. throw "juration.parse(): Unable to parse: " + numbers[j].replace(/^\d+/g, '');
  683. }
  684. }
  685. return sum;
  686. };
  687.  
  688. // _padLeft('5', '0', 2); // 05
  689. var _padLeft = function(s, c, n) {
  690. if (! s || ! c || s.length >= n) {
  691. return s;
  692. }
  693.  
  694. var max = (n - s.length)/c.length;
  695. for (var i = 0; i < max; i++) {
  696. s = c + s;
  697. }
  698.  
  699. return s;
  700. };
  701.  
  702. var _pluralize = function(count, singular) {
  703. return count == 1 ? singular : singular + "s";
  704. };
  705.  
  706. var _isNumeric = function(n) {
  707. return !isNaN(parseFloat(n)) && isFinite(n);
  708. };
  709.  
  710. var _extend = function(obj, extObj) {
  711. for (var i in extObj) {
  712. if(extObj[i] !== undefined) {
  713. obj[i] = extObj[i];
  714. }
  715. }
  716. return obj;
  717. };
  718.  
  719. var juration = {
  720. parse: parse,
  721. stringify: stringify,
  722. humanize: stringify
  723. };
  724.  
  725. return juration;
  726. };
  727.  
  728. /*--- waitForKeyElements(): A utility function, for Greasemonkey scripts,
  729. that detects and handles AJAXed content.
  730.  
  731. Usage example:
  732.  
  733. waitForKeyElements (
  734. "div.comments"
  735. , commentCallbackFunction
  736. );
  737.  
  738. //--- Page-specific function to do what we want when the node is found.
  739. function commentCallbackFunction (jNode) {
  740. jNode.text ("This comment changed by waitForKeyElements().");
  741. }
  742.  
  743. IMPORTANT: This function requires your script to have loaded jQuery.
  744. */
  745. function waitForKeyElements (
  746. selectorTxt, /* Required: The jQuery selector string that
  747. specifies the desired element(s).
  748. */
  749. actionFunction, /* Required: The code to run when elements are
  750. found. It is passed a jNode to the matched
  751. element.
  752. */
  753. bWaitOnce, /* Optional: If false, will continue to scan for
  754. new elements even after the first match is
  755. found.
  756. */
  757. iframeSelector /* Optional: If set, identifies the iframe to
  758. search.
  759. */
  760. ) {
  761. var targetNodes, btargetsFound;
  762.  
  763. if (typeof iframeSelector == "undefined")
  764. targetNodes = $(selectorTxt);
  765. else
  766. targetNodes = $(iframeSelector).contents ()
  767. .find (selectorTxt);
  768.  
  769. if (targetNodes && targetNodes.length > 0) {
  770. btargetsFound = true;
  771. /*--- Found target node(s). Go through each and act if they
  772. are new.
  773. */
  774. targetNodes.each ( function () {
  775. var jThis = $(this);
  776. var alreadyFound = jThis.data ('alreadyFound') || false;
  777.  
  778. if (!alreadyFound) {
  779. //--- Call the payload function.
  780. var cancelFound = actionFunction (jThis);
  781. if (cancelFound)
  782. btargetsFound = false;
  783. else
  784. jThis.data ('alreadyFound', true);
  785. }
  786. } );
  787. }
  788. else {
  789. btargetsFound = false;
  790. }
  791.  
  792. //--- Get the timer-control variable for this selector.
  793. var controlObj = waitForKeyElements.controlObj || {};
  794. var controlKey = selectorTxt.replace (/[^\w]/g, "_");
  795. var timeControl = controlObj [controlKey];
  796.  
  797. //--- Now set or clear the timer as appropriate.
  798. if (btargetsFound && bWaitOnce && timeControl) {
  799. //--- The only condition where we need to clear the timer.
  800. clearInterval (timeControl);
  801. delete controlObj [controlKey]
  802. }
  803. else {
  804. //--- Set a timer, if needed.
  805. if ( ! timeControl) {
  806. timeControl = setInterval ( function () {
  807. waitForKeyElements ( selectorTxt,
  808. actionFunction,
  809. bWaitOnce,
  810. iframeSelector
  811. );
  812. },
  813. 300
  814. );
  815. controlObj [controlKey] = timeControl;
  816. }
  817. }
  818. waitForKeyElements.controlObj = controlObj;
  819. }