skip to Main Content

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>

2

Answers


  1. This is pretty rough but here I created a UI widget method called filterWords and added that to the ui.dialog then showed how to call it with a button.

    Alternately I added a method that gets called on the dialog open.

    Notes:

    • dialog filterWords also triggers the open
    • commented out a good bit of your code for this demo as I did not have HASHED available.
    • added an open handler if you wish to go that direction
    • note how the widget code uses this.element
    • widget doc: https://jqueryui.com/widget/
    • note how the dialog is available as $(this) as I show in the RAW function with $(this).find('.words') element log of the html
    • I added some additional code to show how your word count can be an option wordcount:2,

    There are probably other ways to make this more efficient but here I just show a lot of options available here.

    $.widget("ui.dialog", $.ui.dialog, {
      filterWords: function(event) {
        let count = this.options.wordcount;
        console.log(count);
        console.log('we are here!');
        //console.log(this.element);
        let myWords = this.element.find('.words');
        console.log('words:', myWords.length, myWords.html());
        $(this).trigger('open');
    
        /*
               // the words already filtered and assigned to the dialog's innerHTML
        if ($(this).find('.words').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');
        };
         */
      }
    });
    
    
    function filterWordsRaw(event, ui) {
      console.log('in RAW form');
      console.log($(this).find('.words').html());
      // the words already filtered and assigned to the dialog's innerHTML
      if ($(this).html().length > 1000) {
        return (ev, ui) => {};
      }
      /* commented out as I do not have HASHED defined 
        // 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');
        };
        */
    }
    
    /* just to show we did this */
    function handleOpen(event) {
      console.log('opened');
      console.log(event.target === this);
    }
    
    $(".selector").dialog({
      modal: true,
      appendTo: '#fullDiv',
      autoOpen: false,
      open: handleOpen,
      wordcount: 2,
      buttons: {
        'Close': function() {
          $(this).dialog('close');
        }
      }
    });
    
    $(".selector").on("dialogopen", filterWordsRaw);
    
    $('.go-do-it').on('click', function(event) {
      console.log('hi');
      $(".selector").dialog("filterWords");
    });
    <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
    <script src="https://code.jquery.com/ui/1.13.2/jquery-ui.min.js"></script>
    <link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/redmond/jquery-ui.css">
    <div class="container">
      <button type="button" class="go-do-it">Click</button>
    </div>
    
    <div class="selector" title="Dialog Title">
      <span class="words">one fish two fish red fish blue fish</span>
    </div>
    <div id="fullDiv"></div>
    Login or Signup to reply.
  2. 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.

    • Uses some data attributes in the buttons and dialog

      • Note how dialog 3 has none and uses the code default wordLength: 3,
    • Use a class for all the buttons to target them in code

    • Use a class for all the dialogs to target them in code

    • Data attribute on the button to say which dialog it points to.

    • Used some literals as for example (with back quote on them)

      `${myvar}text more text${anothervar} fun`;
      
    • Removed the preventDefault from the button click and instead used the type="button" on the elements

    • Chained the $('.my-letter-dialog') .on("dialogopen" and the .dialog( – order matters as .dialog( does not return jQuery but the event handler does.

    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>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search