Saturday, May 29, 2021

TypeScript Object is possibly undefined

   .
   .
   .
   
   newHzl[hanzi].pinyin = newHzl[hanzi].pinyin.filter(eachPinyin => pinyinEnglish[eachPinyin]);
} // for loop   
That yields this error:
error: TS2532 [ERROR]: Object is possibly 'undefined'.
        newHzl[hanzi].pinyin = newHzl[hanzi].pinyin.filter(
Got a similar error code on following code, and removed the error by checking if a variable has nothing, if nothing then skip(continue)
for (const [hanzi, { pinyinEnglish }] of Object.entries(hzl)) {
    if (!pinyinEnglish) {
        continue;
    }
    
    for (const pinyin of Object.keys(pinyinEnglish)) {
        if (pinyinEnglish[pinyin].length === 0) {
            delete pinyinEnglish[pinyin];
        }
    }
    
    .
    .
    .    
I tried to do the same solution on the post's first code, but it still yields a compile error of Object is possibly 'undefined'
	.
    .
    .
    
    if (!newHzl[hanzi].pinyin) {
        continue;
    }

    newHzl[hanzi].pinyin = newHzl[hanzi].pinyin.filter(eachPinyin => pinyinEnglish[eachPinyin]);
} // for loop    
The workaround is to introduce a variable so the compiler will not have a hard time inferring the code's control flow:
    .
    .
    .

    const hanziPinyin = newHzl[hanzi].pinyin;
    if (!hanziPinyin) {
        continue;
    }

    newHzl[hanzi].pinyin = hanziPinyin.filter(eachPinyin => pinyinEnglish[eachPinyin]);
} // for loop   
The problem went away

Tuesday, May 18, 2021

JavaScript splitKeep

Helper function for split keep. Reference: https://medium.com/@shemar.gordon32/how-to-split-and-keep-the-delimiter-s-d433fb697c65
String.prototype.splitKeep = function (tokens) {
    const escaped = escapeRegExp(tokens);
    return this.split(new RegExp(`(?=[${escaped}])|(?<=[${escaped}])`, 'g'));
};


// Not built-in yet https://github.com/tc39/proposal-regex-escaping

// Use a good one for the meantime https://stackoverflow.com/questions/3115150/how-to-escape-regular-expression-special-characters-using-javascript
function escapeRegExp(text) {
    return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
}
Add this when using TypeScript:
declare global {
    interface String {
        splitKeep(tokens: string): string[];
    }
}
November 14 For browsers that the regex component don't have lookbehind capability yet, use match method:
String.prototype.splitKeep = function (tokens) {
    const tokensEscaped = tokens
        .split('')
        .map((s) => escapeRegExp(s))
        .join('|');

    const wordMatch = `[^${escapeRegExp(tokens)}]+`;
    return this.match(new RegExp(tokensEscaped + '|' + wordMatch, 'g'));
};

Saturday, May 15, 2021

Handling both single click and double click in JavaScript

Live code: https://jsfiddle.net/91zoa2qj/1/

HTML and CSS:
<div id="something">Click or double click me</div>
<hr/>
<div id="good">Click or double click me</div>


<style>
#something, #good {
  background-color: lemonchiffon;
}
</style>

JS:
addGlobalEventListener(
    'click',
    '#something',
    debounceSingleClickOnly(sayHello)
);

addGlobalEventListener(
    'dblclick',
    '#something',
    sayWorld
);

addGlobalEventListener(
    'click',
    '#good',
    debounceSingleClickOnly(sayHello)
);

addGlobalEventListener(
    'dblclick',
    '#good',
    sayWorld
);


    
let counter = 0;
function sayHello({target: {id}}) {
    ++counter;
	console.log(`${counter}. clicked ${id}`);
}


function sayWorld({target: {id}}) {
    ++counter;
	console.log(`${counter}. double-clicked ${id}`);
}


function addGlobalEventListener(type, selector, callback) {
    document.addEventListener(type, (e) => {
        if (e.target.matches(selector)) {
            callback(e);
        }
    });
}


function debounce(func, wait, immediate) {
    let timeout;
    return function () {
        const context = this,
            args = arguments;
        const later = function () {
            timeout = null;
            if (!immediate) func.apply(context, args);
        };
        const callNow = immediate && !timeout;
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
        if (callNow) func.apply(context, args);
    };
}

function debounceSingleClickOnly(func, timeout = 500) {
    function eventHandler(event) {
        const { detail } = event;
        if (detail > 1) {
            return;
        }

        func.apply(this, arguments);
    }

    return debounce(eventHandler, timeout);
}