Thursday, April 29, 2021

Toggling boolean without using global variable

<div id='_form'>
    Form Inputs
</div>

<div id='_preview' style='display: none'>
    Preview
</div>

<button id='_toggle'>
   Toggle
</button>

<script>
window._toggle.onclick = () => previewFormInputs();
// can also do the following instead of the above
/* window._toggle.onclick = previewFormInputs; */

function setElementVisibility(element, visible) {
    element.style.display = visible ? 'inherit' : 'none';
}

let _previewVisible = false;
function previewFormInputs() {
    _previewVisible = !_previewVisible;
    setElementVisibility(window._form, !_previewVisible);
    setElementVisibility(window._preview, _previewVisible);
}
</script>
Live: https://jsfiddle.net/sewa4jh1/1/
The code above can be improved by combining both the action and the state _previewVisible
<div id='_form'>
    Form Inputs
</div>
 
<div id='_preview' style='display: none'>
    Preview
</div>
 
<button id='_toggle'>
   Toggle
</button>
 
<script>
window._toggle.onclick = () => previewFormInputs();
// can also do the following instead of the above
// _stats.onclick = previewFormInputs;
 
function setElementVisibility(element, visible) {
    element.style.display = visible ? 'inherit' : 'none';
}
 
const previewFormInputs = (function () {
    let previewVisible = false;
     
    return function () {
        previewVisible = !previewVisible;
        setElementVisibility(window._form, !previewVisible);
        setElementVisibility(window._preview, previewVisible);
    }   
})();
</script>
Live: https://jsfiddle.net/sewa4jh1/3/

Sunday, April 25, 2021

Do not be driven nuts by multiple nots


Multiple nots. Complex
    firstname != 'Juan' || lastname != 'Cruz'
English-speak: 
    If your firstname is not Juan or your lastname is not Cruz, therefore you are not Juan Cruz

Don't use the above, it has multiple nots. Use this:

Single not, simple. Translates well to English:
    !(firstname == 'Juan' && lastname == 'Cruz')
English-speak (reading from inside out): 
    If you are Juan Cruz, then don't.
English-speak (reading from left to right): 
    If you are not Juan Cruz, then do.
Postgres tuple test:
    (firstname, lastname) != ('Juan', 'Cruz')
Languages without tuple:
    firstname + ' ' + lastname != 'Juan Cruz'

**Schema (PostgreSQL v13)**

    create table person(firstname text, lastname text);
    
    insert into person(firstname, lastname) values
    ('Juan', 'Cruz'),
    ('Juan', 'Buen'),
    ('Michael', 'Cruz'),
    ('Michael', 'Buen');
    

---

**Query #1**

    select
        firstname, lastname,
        
        firstname != 'Juan' or lastname != 'Cruz' as test1,
        not (firstname = 'Juan' and lastname = 'Cruz') as test2,
        
        (firstname, lastname) != ('Juan', 'Cruz') as test3,
        (firstname || ' ' || lastname) != 'Juan Cruz' as test4
        
    from 
    	person;

| firstname | lastname | test1 | test2 | test3 | test4 |
| --------- | -------- | ----- | ----- | ----- | ----- |
| Juan      | Cruz     | false | false | false | false |
| Juan      | Buen     | true  | true  | true  | true  |
| Michael   | Cruz     | true  | true  | true  | true  |
| Michael   | Buen     | true  | true  | true  | true  |

---

[View on DB Fiddle](https://www.db-fiddle.com/f/nxbuszjT4zgmdj4pGaZeuQ/0)


-------------

Multiple nots. Complex
    firstname != 'Juan' && lastname != 'Cruz'

Single not, simple. Translates well to English:
    !(firstname == 'Juan' || lastname == 'Cruz')
English-speak: 
    If your firstname is Juan or lastname is Cruz, then don't.


**Schema (PostgreSQL v13)**

    create table person(firstname text, lastname text);
    
    insert into person(firstname, lastname) values
    ('Juan', 'Cruz'),
    ('Juan', 'Buen'),
    ('Michael', 'Cruz'),
    ('Michael', 'Buen');
    

---

**Query #1**

    select
        firstname, lastname,
        
        firstname != 'Juan' and lastname != 'Cruz' as test1,
        not (firstname = 'Juan' or lastname = 'Cruz') as test2
        
    from 
    	person;

| firstname | lastname | test1 | test2 |
| --------- | -------- | ----- | ----- |
| Juan      | Cruz     | false | false |
| Juan      | Buen     | false | false |
| Michael   | Cruz     | false | false |
| Michael   | Buen     | true  | true  |

---

[View on DB Fiddle](https://www.db-fiddle.com/f/nxbuszjT4zgmdj4pGaZeuQ/1)


Thursday, April 22, 2021

Intl.Segmenter

It would be awesome if Intl.Segmenter is perfect. For the meantime, retokenize the words that should not be together
function tokenizeZH(text) {
    const segmenter = new Intl.Segmenter('zh', { granularity: 'word' });
    const segments = segmenter.segment(text);

    const words = [];
    for (const { segment /* , index, isWordLike */ } of segments) {
        words.push(segment);
    }

    return words;
}

console.log(tokenizeZH('我不是太清楚'));

Live: https://jsfiddle.net/rgqen1zc/
Output:
["我不是", "太", "清楚"]

我不是 should be 我 不是

Monday, April 19, 2021

Unchecked runtime.lastError: The message port closed before a response was received.

Even if the listener's async code returns true to indicate it is running an async code, the code will still have the error "Unchecked runtime.lastError: The message port closed before a response was received" if the async code is wrongly placed.
The following code produces the error mentioned due to misplaced async
chrome.runtime.onMessage.addListener(async (message /* , sender, sendResponse */) => {
    if (message.action === UPDATE_PAGE) {
        await applyStyleFromSettings();
    }
    // https://stackoverflow.com/questions/53024819/chrome-extension-sendresponse-not-waiting-for-async-function    
    return true;
});
Returning true is not enough, to fully fix the problem, we must place the async code on the code body itself, not on the callback's declaration. Remove the async declaration from the listener, move it to the code's body instead
chrome.runtime.onMessage.addListener((message /*, sender, sendResponse */) => {
    (async () => {
        if (message.action === UPDATE_PAGE) {
            await applyStyleFromSettings();
        }
    })();
    // https://stackoverflow.com/questions/53024819/chrome-extension-sendresponse-not-waiting-for-async-function            
    return true;
});

Saturday, April 17, 2021

TypeScript, array map can't be type-checked

Given this interface:
	interface IHskLevel {
       hanzi: string;
       hsk: number;
    }
This code does not produce compile error (it should):
function* generateHskLevel(): Iterable<IHskLevel> {
    yield* json.map(
        ({ hanzi, pinyin, HSK: hsk }) => ({ hanzi, pinyin, hsk })
    );
}
This produces compile error (it should):
function* generateHskLevel(): Iterable<IHskLevel> {
    for (const { hanzi, pinyin, HSK: hsk } of json) {
        yield { hanzi, pinyin, hsk };
    }
}
Error:
error: TS2322 [ERROR]: Type '{ hanzi: string; pinyin: string; hsk: number; }' is not assignable to type 'IHskLevel'.
  Object literal may only specify known properties, and 'pinyin' does not exist in type 'IHskLevel'.
            pinyin,

Wednesday, April 14, 2021

Uncaught (in promise) The message port closed before a response was received

// eslint-disable-next-line no-undef
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
    if (request.dataNeeded === HANZI) {
        (async () => {                                       
            const hzl = await loadHanziListFile();                    
            sendResponse({data: hzl});
        })();
    
        // Need to return true if we are using async code in addListener.
        // Without this line..
        return true;
        // ..we will receive the error: 
        // Uncaught (in promise) The message port closed before a response was received
    }
});

Sunday, April 11, 2021

Service worker registration failed. Cannot read property 'onClicked' of undefined

Given this background.js:
// eslint-disable-next-line no-undef
chrome.action.onClicked.addListener((tab) => {
    console.log("working");

    // eslint-disable-next-line no-undef
    chrome.tabs.sendMessage(
        // tabs[0].id,
        tab.id,
        { action: "CHANGE_COLOR" },
        // eslint-disable-next-line no-unused-vars
        function (response) {}
    );

});

I'm using manifest version 3:
{
    "name": "Chinese word separator",
    "description": "Put spaces between words",
    "version": "1.0",
    "manifest_version": 3,    
    "background" : {
        "service_worker": "background.js"
    },
    "content_scripts": [
        {
            "matches": ["<all_urls>"],
            "css": [],
            "js": ["contentScript.js"]
        }
    ]  
}


And I received this error:

To fix the error, add the action property even if it is empty:

{
    "name": "Chinese word separator",
    "description": "Put spaces between words",
    "version": "1.0",
    "manifest_version": 3,    
    "action": {},
    "background" : {
        "service_worker": "background.js"
    },
    "content_scripts": [
        {
            "matches": ["<all_urls>"],
            "css": [],
            "js": ["contentScript.js"]
        }
    ]  
}
It is advisable to wrap the background.js via a wrapper so you'll get better error message, so if there's no action property in manifest.json..
{
    "name": "Chinese word separator",
    "description": "Put spaces between words",
    "version": "1.0",
    "manifest_version": 3,    
    "background" : {
        "service_worker": "background-wrapper.js"
    },
    "content_scripts": [
        {
            "matches": ["<all_urls>"],
            "css": [],
            "js": ["contentScript.js"]
        }
    ]  
}
..and you wrap the background via wrapper (e.g., background-wrapper.js)..
try {
    // eslint-disable-next-line no-undef
    importScripts("./background.js");
} catch (e) {
    console.error(e);
}
..you will receive a more descriptive error instead:
background-wrapper.js:5 TypeError: Cannot read property 'onClicked' of undefined
    at background.js:1
    at background-wrapper.js:3
(anonymous) @ background-wrapper.js:5
To fix the error, add action on manifest.json