javascriptjqueryjquery-uiclosuresjquery-ui-dialog

$(this) not set in the jQuery UI dialog "open" option


At the bottom of a word game there are 4 buttons, which open jQuery UI dialogs with certain words from the game dictionary:

game screenshot

I am trying to simplify the game code by creating the following function:

// select word using the filterFunc and then concat them all to a string
function filterWords(filterFunc) {
    // the words already filtered and assigned to the dialog's innerHTML
    if ($(this).html().length > 1000) {
        return (ev, ui) => {};
    }

    // filter the keys of HASHED dictionary by calling the filterFunc on each
    const filtered = Object.keys(HASHED)
        .filter(word => filterFunc(word))
        .reduce((result, word) => {
            return result + 
                '<p><span class="tile">' + word + '</span> ' + HASHED[word] + '</p>'
        }, '');

    // return the closure expected by the dialog's "open" option
    return (ev, ui) => {
        $(this).html(filtered);
        const title = $(this).dialog('option', 'title');
        console.log(title + ': ' + filtered.length + ' chars');
    };
}

My hope is that jQuery UI dialog "open" option expects a function (ev, ui) {} and that is what I am trying to give it by my new function:

const twoDlg = $('#twoDlg').dialog({
    modal: true,
    appendTo: '#fullDiv',
    autoOpen: false,
    open: filterWords(word => word.length == 2),
    buttons: {
        'Close': function() {
            $(this).dialog('close');
        }
    }
});

Here another dialog:

const rare2Dlg = $('#rare2Dlg').dialog({
    modal: true,
    appendTo: '#fullDiv',
    autoOpen: false,
    open: filterWords(word => word.indexOf('X') >= 0),
    buttons: {
        'Close': function() {
            $(this).dialog('close');
        }
    }
});

Unfortunately, now I get the error message:

jquery.js:4095  Uncaught TypeError: Cannot read properties of undefined (reading 'length')
    at filterWords (test?player=abcde:833:594)
    at HTMLDocument.<anonymous> (test?player=abcde:835:139)
    at mightThrow (jquery.js:3802:29)
    at process (jquery.js:3870:12)

Which indicates to me that $(this).html() is not valid in my closure.

Is there a way to get it working?

UPDATE:

I have prepared a jsFiddle with 3 dialogs and 3 buttons to open. The method filterWordsWorks() works, but has too much repeated code. The commented out method open: filterWordsBroken(word => word.length == 2), fails.

And below the same demo code, inlined into Stackoverflow:

'use strict';

const HASHED = {
  "one": "Word description 1",
  "two": "Word description 2",
  "three": "Word description 3",
  "four": "Word description 4",
  "five": "Word description 5",
  "six": "Word description 6",
  "seven": "Word description 7"
};

// select word using the filterFunc and then concat them all to a string
function filterWordsBroken(filterFunc) {
  // the words already filtered and assigned to the dialog's innerHTML
  if ($(this).html().length > 1000) {
    return (ev, ui) => {};
  }

  // filter the keys of HASHED dictionary by calling the filterFunc on each
  const filtered = Object.keys(HASHED)
    .filter(word => filterFunc(word))
    .reduce((result, word) => {
      return result +
        '<p>' + word + ': ' + HASHED[word] + '</p>'
    }, '');

  // return the closure expected by the dialog's "open" option
  return (ev, ui) => {
    $(this).html(filtered);
    const title = $(this).dialog('option', 'title');
    console.log(title + ': ' + filtered.length + ' chars');
  };
}

// select word using the filterFunc and then concat them all to a string
function filterWordsWorks(filterFunc) {
  return Object.keys(HASHED)
    .filter(word => filterFunc(word))
    .reduce((result, word) => {
      return result +
        '<p>' + word + ': ' + HASHED[word] + '</p>'
    }, '');
}

jQuery(document).ready(function($) {
  const twoDlg = $('#twoDlg').dialog({
    modal: true,
    autoOpen: false,
    //open: filterWordsBroken(word => word.length == 2),
    open: function(ev, ui) {
      // prevent this code from running twice
      if ($(this).html().length < 1000) {
        const filtered = filterWordsWorks(word => word.length == 2);
        $(this).html(filtered);
        const title = $(this).dialog('option', 'title');
        console.log(title + ': ' + filtered.length);
      }
    }
  });
  const threeDlg = $('#threeDlg').dialog({
    modal: true,
    autoOpen: false,
    //open: filterWordsBroken(word => word.length == 3),
    open: function(ev, ui) {
      // prevent this code from running twice
      if ($(this).html().length < 1000) {
        const filtered = filterWordsWorks(word => word.length == 3);
        $(this).html(filtered);
        const title = $(this).dialog('option', 'title');
        console.log(title + ': ' + filtered.length);
      }
    }
  });
  const fourDlg = $('#fourDlg').dialog({
    modal: true,
    autoOpen: false,
    //open: filterWordsBroken(word => word.length == 3),
    open: function(ev, ui) {
      // prevent this code from running twice
      if ($(this).html().length < 1000) {
        const filtered = filterWordsWorks(word => word.length == 4);
        $(this).html(filtered);
        const title = $(this).dialog('option', 'title');
        console.log(title + ': ' + filtered.length);
      }
    }
  });

  $('#twoBtn').button().click(function(ev) {
    ev.preventDefault();
    twoDlg.dialog('open');
  });

  $('#threeBtn').button().click(function(ev) {
    ev.preventDefault();
    threeDlg.dialog('open');
  });

  $('#fourBtn').button().click(function(ev) {
    ev.preventDefault();
    fourDlg.dialog('open');
  });
});
<link type="text/css" rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-ui@1/dist/themes/redmond/jquery-ui.min.css">
<script src="https://cdn.jsdelivr.net/npm/jquery@3/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery-ui@1/dist/jquery-ui.min.js"></script>

<!-- beware: the twoDlg will be empty -->
<DIV ID="twoDlg" TITLE="2 letters"></DIV>
<DIV ID="threeDlg" TITLE="3 letters"></DIV>
<DIV ID="fourDlg" TITLE="4 letters"></DIV>

<BUTTON ID="twoBtn">2 letters</BUTTON>
<BUTTON ID="threeBtn">3 letters</BUTTON>
<BUTTON ID="fourBtn">4 letters</BUTTON>


Solution

  • Use as much of this as you wish.

    This is NOT a direct answer to the question but more an x/y example of what your stated goal is: less code

    Since you indicated your desire was less code here is a simpler example.

    You COULD create a dialog from ONE dialog - added a "5" letter example for that but it could be all of them.

    'use strict';
    
    const HASHED = {
      "one": "Word description 1",
      "two": "Word description 2",
      "three": "Word description 3",
      "four": "Word description 4",
      "five": "Word description 5",
      "six": "Word description 6",
      "seven": "Word description 7"
    };
    jQuery(function($) {
      function filterFunc(word, len) {
        //console.log(word, len, word.length == len);
        return word.length == len;
      }
      $('.my-letter-dialog')
        .on("dialogopen", function(event, ui) {
          let elWLen = $(this).data('wordlength');
          let wordLen = !!elWLen ? elWLen : $(this).dialog("option", "wordLength");
          console.log(wordLen);
          if ($(this).html().length > 1000) {
            return (ev, ui) => {};
          }
          const filtered = Object.keys(HASHED)
            .filter(word => filterFunc(word, wordLen))
            .reduce((result, word) => {
              return `${result}<p>${word}:${HASHED[word]}</p>`
            }, '');
          //console.log('filtered:', filtered)
          $(this).html(filtered);
          const title = $(this).dialog('option', 'title');
          const newTitle = `${title}:${filtered.length} chars`;
          $(this).dialog('option', 'title', newTitle);
        })
        .dialog({
          modal: true,
          autoOpen: false,
          wordLength: 3,
          filterFunc: filterFunc
        });
    
      $('.my-letter-button').button()
        .on("click", function(ev) {
          const targ = $(this).data('targetselector');
          $(targ).dialog('open');
        });
    });
    <link type="text/css" rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-ui@1/dist/themes/redmond/jquery-ui.min.css">
    <script src="https://cdn.jsdelivr.net/npm/jquery@3/dist/jquery.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/jquery-ui@1/dist/jquery-ui.min.js"></script>
    
    <!-- beware: the twoDlg will be empty -->
    <div class="my-letter-dialog" id="twoDlg" data-wordlength="2" title="2 letters"></div>
    <div class="my-letter-dialog" id="threeDlg" title="3 letters"></div>
    <div class="my-letter-dialog" id="fourDlg" data-wordlength="4" title="4 letters"></div>
    
    <button class="my-letter-button" type="button" id="twoBtn" data-targetselector="#twoDlg">2 letters</button>
    <button class="my-letter-button" type="button" id="threeBtn" data-targetselector="#threeDlg">3 letters</button>
    <button class="my-letter-button" type="button" id="fourBtn" data-targetselector="#fourDlg">4 letters</button>