Reputation: 23018
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>
Upvotes: 0
Views: 202
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
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
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:
filterWords
also triggers the openHASHED
available.this.element
$(this)
as I show in the RAW function with $(this).find('.words')
element log of the htmlwordcount: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