At the bottom of a word game there are 4 buttons, which open jQuery UI dialogs with certain words from the game dictionary:
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>
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
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>