Alexander Farber
Alexander Farber

Reputation: 23018

$(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>

Upvotes: 0

Views: 202

Answers (2)

Mark Schultheiss
Mark Schultheiss

Reputation: 34217

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>

Upvotes: 1

Mark Schultheiss
Mark Schultheiss

Reputation: 34217

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>

Upvotes: 1

Related Questions