# helper functions

[🔰 CSS](../../css) ⟩ [Layout](../../css/layout) ⟩ [System](../../css/layout/system) ⟩

{% tabs %}
{% tab title="0.2.0" %}
```javascript
// 0.1.7 + arr.last()
// 0.1.8 + toString()
// 0.1.9 + isEqualSets()
// 0.2.0 - toString(): + Map, Set

/* ------- ⭐️ debug ------- */

// ⭐️ log()
export function log(x){ console.log(x) }

/* ------- ⭐️ Object ------- */

// ⭐️ toString(obj)
export function toString(obj){

    // Set
    if (String(obj) === '[object Set]') 
        return `{${[...obj].map(x=>toString(x)).join(', ')}}`;
    
    // Map
    if (String(obj) === '[object Map]') 
        return `{${[...obj].map(p => `${p[0]}→${toString(p[1])}`).join(', ')}}`;
    
    // function: return its code
    if (typeof obj === 'function') return obj.toString();
    
    // NaN
    if (obj !== null && obj !== undefined && obj.toString() === 'NaN') return 'NaN';
    
    // convert to string and check if it's an object
    let str = JSON.stringify(obj);
    let isObject = (/^\{.*\}$/g).test(str);   // { ... }
    
    // not an object, return str anyway
    if (!isObject) return str;
    
    // IS an object, try to get its methods as well.
    let result = [];
    for (let key in obj) {
        result.push(`${key}: ${toString(obj[key])}`);
    }
    return `{${result.join(', ')}}`;
}

// ---------- Set Equality ----------

// isEqualSets(a,b)
export function isEqualSets(a, b){
    if (a.size !== b.size) return false;
    return Array.from(a).every(elem => b.has(elem));
}

// setA.equal(setB)
// Set.prototype.equal = function(setB){
//     return isEqualSets(this, setB);
// }

/* ------- ⭐️ Array ------- */

// ⭐️ escapeHTML()
export function lastOfArray(arr){
    return arr[this.length - 1];
}

// ⭐️ arr.last()
// Array.prototype.last = function(){
//     return this[this.length - 1];
// }


/* ------- ⭐️ String ------- */

// ⭐️ escapeHTML()
export function escapeHTML(str){
    return new Option(str).innerHTML;
}

// ⭐️ mapString(str, mapf)
export function mapString(str, mapf){    
    return Array.prototype.map.call(str, mapf);
};

// ⭐️ StringfromCharCodes(65, 70) -> 'ABCDEF'
export function StringfromCharCodes(from, to) {
    let array = [];
    for (let i = from; i <= to; i++) array.push(i);
    return array.map((code) => String.fromCharCode(code)).join('');
}

/* ------- ⭐️ DOM: selectors ------- */

// ⭐️ $(): select first
export function $(selector, parent = document){
  return parent.querySelector(selector);
}

// ⭐️ $(): select all
export function $all(selector, parent = document){
  return parent.querySelectorAll(selector);
}

/* ------- ⭐️ random ------- */

// ⭐️ randomInt(min, max): min ... max (included)
export function randomInt(min, max) {
  return Math.floor(Math.random()*(max-min+1)) + min;
}

// ⭐️ randomElement(arr)
export function randomElement(arr){
    return arr[randomInt(0, arr.length - 1)];
}

// ⭐️ randomChar(str)
// ⭐️ Note: this may not work well with Unicode❗️
export function randomChar(str) {
    return str[randomInt(0, str.length - 1)];
}

// ⭐️ randomColor()
//   randomColor(0.5): random color with alpha = 0.5
export function randomColor(alpha) {
    const [r,g,b] = [
        randomInt(0,255), 
        randomInt(0,255), 
        randomInt(0,255)
    ];
    return `rgb(${r} ${g} ${b}${alpha >=0 ? ` / ${alpha}` : ''})`;
}

/**
 * ⭐️ shuffle(arr)
 * - arr is shuffled in place
 * @param arr array to shuffle
 */
export function shuffle(arr) {

    var n = arr.length;

    for (let i = n - 1; i > 0; i--) {
        let j = randomInt(0, i);
        [arr[i], arr[j]] = [arr[j], arr[i]];
    }

    return arr;
}

/**
 * ⭐️ shuffleString()
 *    shuffle string
 * @param str [string] string to shuffle
 */
export function shuffleString(str){
    return shuffle(str.split('')).join('');
}

/* ------- ⭐️ Element ------- */

/**
 * ⭐️ attr(elem, attrName[, value])
 * - get: `attr(elem, 'padding')`
 * - set: `attr(elem, 'padding', '8px')`
 */
export function attr(elem, attrName, value=undefined){
    if (value) { elem.setAttribute(attrName, value) };
    return elem.getAttribute(attrName) || undefined;
};

// ⭐️ tag(name[, {textContent, attributes}])
//    tag('div')
//    tag('div', {
//        textContent = 'hello',
//        attributes = {'--color': 'red'}
//    }
export function tag(name, {
    textContent,
    attributes = {}
}={}){

    // create element
    let elem = document.createElement(name);

    // set text content if necessary
    if(textContent) elem.textContent = textContent;
    
    // set attributes
    for (const [key, value] of Object.entries(attributes)) {
        elem.setAttribute(key, value);
    }
    
    // return element
    return elem;
}

// ⭐️ appendTagTo(node, tag)
export function appendTagTo(node, tagName, {
    textContent,
    attributes = {}
}={}){
    let elem = tag(tagName, {textContent, attributes});
    node.appendChild(elem);
    return elem;
};
```
{% endtab %}

{% tab title="history" %}
```
0.1.3 + randomChar(), randomElement()
0.1.4 + mapString(), StringfromCharCodes()
0.1.5 + shuffle(), shuffleString()
0.1.7 + arr.last()
0.1.8 + toString()
0.1.9 + isEqualSets()
0.2.0 - toString(): + Map, Set
```
{% endtab %}

{% tab title="⬇️ 應用" %}
* [VStack](../../css/layout/system/vstack), [HStack](../../css/layout/system/hstack), [Box](../../css/layout/system/box)
{% endtab %}

{% tab title="⬆️ 需要" %}
* [tag()](../../browser/dom/custom/tag), [elem.appendTag()](../../browser/dom/type/node/+ext/.appendtag)
* [$(), $all()](../../browser/dom/querying-elements/dom)
* [toString()](../../js/val/obj/convert/valuetostring)
{% endtab %}

{% tab title="💾 程式" %}
* CodeSandbox ⟩&#x20;
  * [0.1.3](https://codesandbox.io/s/helpers-0-1-3-upb9t?file=/layout/helpers.mjs) + randomChar(), randomElement()
  * [0.1.8](https://codesandbox.io/s/compare-math-trunc-bi2gh?file=/src/helpers.mjs) + arr.last(), toString()
{% endtab %}
{% endtabs %}
