2020년 12월 21일 월요일

HTML 팁

 1. <input>요소에 대한 "자동 완성" 기능을 제공

<input list="animals" name="animal" id="animal">
<datalist id="animals">
<option value="Cat">
<option value="Dog">
<option value="Chicken">
<option value="Cow">
<option value="Pig">
</datalist>

2. color picker
<label for="favcolor">Select your favorite color:</label>
<input type="color" id="favcolor" name="favcolor">

3. progress bar
<label for="course">Course completion:</label>
<progress id="course" value="67" max="100"></progress> 67%

4. meter tag
<label for="disk_g">Disk usage G:</label>
<meter id="disk_g" value="2" min="0" max="10">2 out of 10</meter><br><label for="disk_h">Disk usage H:</label>
<meter id="disk_h" value="0.7">70%</meter>
progress tag는 동적 meter tag는 정적

2020년 12월 15일 화요일

유용한 javascript 문자관련 method들

 endsWith (mozilla.org)

어떤 문자열에서 특정 문자열로 끝나는지를 확인할 수 있으며, 그 결과를 true 혹은 false로 반환한다. 

var str = 'To be, or not to be, that is the question.';

console.log(str.endsWith('question.')); // true
console.log(str.endsWith('to be'));     // false
console.log(str.endsWith('to be', 19)); // true

includes (mozilla.org)

하나의 문자열이 다른 문자열에 포함되어 있는지를 판별하고, 결과를 true 또는 false 로 반환합니다.

var str = 'To be, or not to be, that is the question.';

console.log(str.includes('To be'));       // true
console.log(str.includes('question'));    // true
console.log(str.includes('nonexistent')); // false
console.log(str.includes('To be', 1));    // false
console.log(str.includes('TO BE'));       // false

indexOf (mozilla.org)

호출한 String 객체에서 주어진 값과 일치하는 첫 번째 인덱스를 반환합니다. 일치하는 값이 없으면 -1을 반환합니다. 

const paragraph = 'The quick brown fox jumps over the lazy dog. If the dog barked, was it really lazy?';

const searchTerm = 'dog';
const indexOfFirst = paragraph.indexOf(searchTerm);

console.log(`The index of the first "${searchTerm}" from the beginning is ${indexOfFirst}`);
// expected output: "The index of the first "dog" from the beginning is 40"

console.log(`The index of the 2nd "${searchTerm}" is ${paragraph.indexOf(searchTerm, (indexOfFirst + 1))}`);
// expected output: "The index of the 2nd "dog" is 52"
'Blue Whale'.indexOf('Blue');     // returns  0
'Blue Whale'.indexOf('Blute');    // returns -1
'Blue Whale'.indexOf('Whale', 0); // returns  5
'Blue Whale'.indexOf('Whale', 5); // returns  5
'Blue Whale'.indexOf('Whale', 7); // returns -1
'Blue Whale'.indexOf('');         // returns  0
'Blue Whale'.indexOf('', 9);      // returns  9
'Blue Whale'.indexOf('', 10);     // returns 10
'Blue Whale'.indexOf('', 11);     // returns 10

lastIndexOf (mozilla.org)

주어진 값과 일치하는 부분을 fromIndex로부터 역순으로 탐색하여, 최초로 마주치는 인덱스를 반환합니다. 일치하는 부분을 찾을 수 없으면 -1을 반환합니다.

const paragraph = 'The quick brown fox jumps over the lazy dog. If the dog barked, was it really lazy?';

const searchTerm = 'dog';

console.log(`The index of the first "${searchTerm}" from the end is ${paragraph.lastIndexOf(searchTerm)}`);
// expected output: "The index of the first "dog" from the end is 52"
'canal'.lastIndexOf('a');     //  3 반환
'canal'.lastIndexOf('a', 2);  //  1 반환
'canal'.lastIndexOf('a', 0);  // -1 반환
'canal'.lastIndexOf('x');     // -1 반환
'canal'.lastIndexOf('c', -5); //  0 반환
'canal'.lastIndexOf('c', 0);  //  0 반환
'canal'.lastIndexOf('');      //  5 반환
'canal'.lastIndexOf('', 2);   //  2 반환

match (mozilla.org)

문자열이 정규식과 매치되는 부분을 검색합니다.

var str = 'For more information, see Chapter 3.4.5.1';
var re = /see (chapter \d+(\.\d)*)/i;
var found = str.match(re);

console.log(found);

// logs [ 'see Chapter 3.4.5.1',
//        'Chapter 3.4.5.1',
//        '.1',
//        index: 22,
//        input: 'For more information, see Chapter 3.4.5.1' ]

// 'see Chapter 3.4.5.1'는 완전한 매치 상태임.
// 'Chapter 3.4.5.1'는 '(chapter \d+(\.\d)*)' 부분에 의해 발견된 것임.
// '.1' 는 '(\.\d)'를 통해 매치된 마지막 값임.
// 'index' 요소가 (22)라는 것은 0에서부터 셀 때 22번째 위치부터 완전 매치된 문자열이 나타남을 의미함.
// 'input' 요소는 입력된 원래 문자열을 나타냄.

matchAll (mozilla.org) ES2020

returns an iterator of all results matching a string against a regular expression, including capturing groups.

const regexp = /t(e)(st(\d?))/g;
const str = 'test1test2';

const array = [...str.matchAll(regexp)];

console.log(array[0]);
// expected output: Array ["test1", "e", "st1", "1"]

console.log(array[1]);
// expected output: Array ["test2", "e", "st2", "2"]
const regexp = RegExp('foo[a-z]*','g');
const str = 'table football, foosball';
let match;

while ((match = regexp.exec(str)) !== null) {
  console.log(`Found ${match[0]} start=${match.index} end=${regexp.lastIndex}.`);
  // expected output: "Found football start=6 end=14."
  // expected output: "Found foosball start=16 end=24."
}

padEnd (mozilla.org)

현재 문자열에 다른 문자열을 채워, 주어진 길이를 만족하는 새로운 문자열을 반환합니다. 채워넣기는 대상 문자열의 끝(우측)부터 적용됩니다.

const str1 = 'Breaded Mushrooms';

console.log(str1.padEnd(25, '.'));
// expected output: "Breaded Mushrooms........"

const str2 = '200';

console.log(str2.padEnd(5));
// expected output: "200  "
'abc'.padEnd(10);          // "abc       "
'abc'.padEnd(10, "foo");   // "abcfoofoof"
'abc'.padEnd(6, "123456"); // "abc123"
'abc'.padEnd(1);           // "abc"

replace (mozilla.org)

"cat, cat, cat, bird".replace("cat", (i) => i + "dog"); // returns "catdog, cat, cat, bird"
replaceAll (mozilla.org)
"cat, cat, cat, bird".replaceAll("cat", (i) => i + "dog"); // returns "catdog, catdog, catdog, bird"
search (mozilla.org)
"cat, dog, cat".search("dog"); // returns 5
// With a regex
"cat, dog, cat".search(/dog/g); // returns 5
slice (mozilla.org)
"This is a string I want to slice".slice(27); // returns 'slice'
"This is a string I want to slice".slice(27, 28); // returns 's'
// And we can work backwards with negative values such as
"This is a string I want to slice".slice(-5); // returns "slice"
"This is a string I want to slice".slice(-5, -1); // returns "slic"
startsWith (mozilla.org)
"Hello".startsWith("h"); // true
"Hello".startsWith("e"); // false
// With optional starting index
"Hello".startsWith("e", 1); // true

2020년 12월 7일 월요일

JavaScript Tricks

 

1. Convert string to number

the_string = "123";
console.log(+the_string);
// 123

the_string = "hello";
console.log(+the_string);
// NaN

2. Convert a number to a string

var converted_number = 5 + "";
console.log(converted_number);
// 5
console.log(typeof converted_number);
// string

3. Extract Unique Values

var entries = [1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 8, 4, 2, 1]
var unique_entries = [...new Set(entries)];
console.log(unique_entries);
// [1, 2, 3, 4, 5, 6, 7, 8]

4. Flatten a multidimensional array

var entries = [1, [2, 5], [6, 7], 9];
var flat_entries = [].concat(...entries);
// [1, 2, 5, 6, 7, 9]

5. Replace All

var example = "potato potato";
console.log(example.replace(/pot/, "tom")); 
// "tomato potato"
console.log(example.replace(/pot/g, "tom")); 
// "tomato tomato"

6. Short Circuit Conditionals

if (available) {
addToCart();
}
available && addToCart()

7. Use the length to resize and empty an array

var entries = [1, 2, 3, 4, 5, 6, 7];  
console.log(entries.length);
// 7
entries.length = 4;
console.log(entries.length);
// 4
console.log(entries);
// [1, 2, 3, 4]
var entries = [1, 2, 3, 4, 5, 6, 7]; 
console.log(entries.length);
// 7
entries.length = 0;
console.log(entries.length);
// 0
console.log(entries);
// []

8. Shuffle elements from array

var my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(my_list.sort(function() {
return Math.random() - 0.5
}));
// [4, 8, 2, 9, 1, 3, 6, 5, 7]

9. Dynamic Property Names

const dynamic = 'flavour';
var item = {
name: 'Coke',
[dynamic]: 'Cherry'
}
console.log(item);
// { name: "Coke", flavour: "Cherry" }

10. Object values

JavaScript has also introduced the Object.values that returns a single dimension array of the object values.

Have a look at the example below:

const person = {
firstName : "Mehdi",
lastName : "Aoussiad",
age : 19,
eyeColor : "black"
};
console.log(Object.values(person));
// Prints ["Mehdi","Aoussiad",19,"black"]

11. Object entries

The Object.entries are similar to Object.values, but returns a multi-dimension array of the object values and properties(keys).

Here is an example:

const person = {
firstName : "Mehdi",
lastName : "Aoussiad",
age : 19,
eyeColor : "black"
};
console.log(Object.entries(person));
// Prints: [["firstName", "Mehdi"],["lastName", "Aoussiad"],["age", 19],["eyeColor", "black"]]

12. Get the last element of an array
let numbersArr = [4, 8, 9, 34, 100];numbersArr[numbersArr.length - 1]; //return 100// 혹은numbersArr.slice(-1)[0];

13. Random number in a specific range



As you know, we use the method Math.random() to return a random number between 0 and 1.
// Random number between 0 and 4.
Math.floor(Math.random() * 5);
// Random number between 0 and 49.
Math.floor(Math.random() * 50);
// Random number between 0 and 309.
Math.floor(Math.random() * 310);


14. Flattening multi-dimensional array
let arr = [5, [1, 2], [4, 8]];arr.flat(); //returns [5, 1, 2, 4, 8]let twoLevelArr = [4, ["John", 7, [5, 9]]]twoLevelArr.flat(2); //returns [4, "John", 7, 5, 9]


15. Check for multiple conditions

let name = "John";//Bad way.
if(name === "John" || name === "Ben" || name === "Chris"){
console.log("included")
}
//Better way.
if(["John", "Ben", "Chris"].includes(name)){
console.log("included")
}


16. Extract unique values


const languages = ['JavaScript', 'Python', 'Python', 'JavaScript', 'HTML', 'Python'];const uniqueLanguages = [...new Set(languages)];
console.log(uniqueLanguages);
//prints: ["JavaScript", "Python", "HTML"]

17. Run an event only once


If you have an event that you want to only run once, you can use the option once as a third parameter for addEventListener() .

Here is the example:

document.body.addEventListener('click', () => {
console.log('Run only once');
}, { once: true });

As you can see, you just have to set the option to true and the event will only run once.

18. Sum all numbers in an array


let numbers = [6, 9 , 90, 120, 55];numbers.reduce((a, b)=> a + b, 0); //returns 280

19. Sum numbers inside an array of objects

const users  = [
{name: "John", age: 25},
{name: "Chris", age: 20},
{name: "James", age: 31},
]

users.reduce(function(a, b){
return {age: a.age + b.age}
}).age;

20. The keyword “in”


Use the keyword in in JavaScript to check if properties are defined inside an object or if elements are included inside an array for example.
const employee = {
name: "Chris",
age: 25
}
"name" in employee; //returns true.
"age" in employee; //returns true.
"experience" in employee; //retuens false.



21. From number to an array of digits

const toArray = num => [...`${num}`].map(elem=> parseInt(elem))
// 위 구문의 다른 방법// const toArray = num => String(num).split("").map(el=> +el);toArray(1234); //returns [1, 2, 3, 4]
toArray(758999); //returns [7, 5, 8, 9, 9, 9]

Null, undefined and empty checks shorthand

JavaScript multiple conditions shorthand

JavaScript loops shorthand

JavaScript implicit return shorthand

JavaScript spread operator shorthand

JavaScript arrow function shorthand

JavaScript template literals shorthand

JavaScript multi-line string shorthand

JavaScript Array.find shorthand


2020년 8월 16일 일요일

Useful NodeJs Libraries

 1. Express

Fast, unopinionated, minimalist web framework for node.

2. Socket.io

Socket.IO enables real-time bidirectional event-based communication

3. Body-parser

Node.js body parsing middleware.

Parse incoming request bodies in a middleware before your handlers, available under the req.body property.

4. Cors

CORS is a node.js package for providing a Connect/Express middleware that can be used to enable CORS with various options.

5. Passport

Passport is Express-compatible authentication middleware for Node.js.

Passport’s sole purpose is to authenticate requests, which it does through an extensible set of plugins known as strategies.

6. Multer

Multer is a node.js middleware for handling multipart/form-data, which is primarily used for uploading files.

NOTE: Multer will not process any form which is not multipart (multipart/form-data).

7. Axios

Promise based HTTP client for the browser and node.js

8. Morgan

HTTP request logger middleware for node.js

9. Http-errors

Create HTTP errors for Express, Koa, Connect, etc. with ease.

10. Dotenv

Dotenv is a zero-dependency module that loads environment variables from a .env file into process.env. Storing configuration in the environment separate from code

11. Faker

generate massive amounts of fake data in the browser and node.js

12. Nodemailer

Send e-mails from Node.js — easy as cake! 🍰✉️

13 Sequelize

Sequelize is a promise-based Node.js ORM for Postgres, MySQL, MariaDB, SQLite and Microsoft SQL Server. It features solid transaction support, relations, eager and lazy loading, read replication and more.

14 Mongoose

Mongoose is a MongoDB object modeling tool designed to work in an asynchronous environment. Mongoose supports both promises and callbacks.

15 Jest

Jest is a JavaScript testing framework developed and maintained regularly by FACEBOOK.

16. Moment

A lightweight JavaScript date library for parsing, validating, manipulating, and formatting dates.

17. lodash

Lodash makes JavaScript easier by taking the hassle out of working with arrays, numbers, objects, strings, etc.Lodash’s modular methods are great for:

Iterating arrays, objects, & strings

Manipulating & testing values

Creating composite functions

18. chalk

Terminal string styling done right

Chalk comes with an easy to use composable API where you just chain and nest the styles you want.

19. validator

A library of string validators and sanitizers.

20. Cheerio

Cheerio parses markup like HTML and provides an API for traversing/manipulating the resulting data structure.

21. JSDoc

An API documentation generator for JavaScript.

22. Helmet

Helmet helps you secure your Express apps by setting various HTTP headers.

23. Crypto-js

JavaScript library of crypto standards.

2020년 7월 23일 목요일

MySQL Pagination Don't use OFFSET. instead remember where you "left off".

http://mysql.rjweb.org/doc.php/pagination

 

The Desire

You have a Web site with news articles, or a blog, or some other thing with a list of things that might be too long for a single page. So, you decide to break it into chunks of, say, 10 items and provide a [Next] button to go the next "page".

You discover OFFSET and LIMIT in MySQL (or MariaDB) and decide that is the obvious way to do it.
    SELECT  *
        FROM  items
        WHERE  messy_filtering
        ORDER BY  date DESC
        OFFSET  $M  LIMIT $N

Note that the problem requirement needs a [Next] link on each page so that the user can 'page' through the data. He does not really need "GoTo Page ___". Jump to the [First] or [Last] page may be useful.

The Problem


All is well -- until you have 50,000 items in a list. And someone tries to walk through all 5000 pages. That 'someone' could be a search engine crawler.

Where's the problem? Performance. Your web page is doing SELECT ... OFFSET 49990 LIMIT 10 (or the equivalent LIMIT 49990,10). MySQL has to find all 50,000 rows, step over the first 49,990, then deliver the 10 for that distant page.

If it is a crawler ('spider') that read all the pages, then it actually touched about 125,000,000 items to read all 5,000 pages.

Reading the entire table, just to get a distant page, can be so much I/O that it can cause timeouts on the web page. Or it can interfere with other activity, causing other things to be slow.

Other Bugs


In addition to a performance problem, ...
    ⚈  If an item is inserted or deleted between the time you look at one page and the next, you could miss an item, or see an item duplicated.
    ⚈  The pages are not easily bookmarked or sent to someone else because the contents shift over time.
    ⚈  The WHERE clause and the ORDER BY may even make it so that all 50,000 items have to be read, just to find the 10 items for page 1!

What to Do?


Hardware? No, that's just a bandaid. The data will continue to grow and even the new hardware won't handle it.

Better INDEX? No. You must get away from reading the entire table to get the 5000th page.

Build another table saying where the pages start? Get real! That would be a maintenance nightmare, and expensive.

Bottom line: Don't use OFFSET; instead remember where you "left off".
First page (latest 10 items):
    SELECT ... WHERE ... ORDER BY id DESC LIMIT 10
Next page (second 10):
    SELECT ... WHERE ... AND id < $left_off  ORDER BY id DESC  LIMIT 10
With INDEX(id), this suddenly becomes very efficient.

Implementation -- Getting Rid of OFFSET


You are probably doing this now: ORDER BY datetime DESC LIMIT 49990,10 You probably have some unique id on the table. This can probably be used for "left off".

Currently, the [Next] button probably has a url something like ?topic=xyz&page=4999 The 'topic' (or 'tag' or 'provider' or 'user' or etc) says which set of items are being displayed. The product of page*limit gives the OFFSET. (The "limit=10" might be in the url, or might be hard-coded; this choice is not relevant to this discussion. I will omit it from the url for brevity.)

The new variant would be ?topic=xyz&id=12345. (Note: the 12345 is not computable from 4999.) By using INDEX(topic, id) you can efficiently say
    WHERE topic = 'xyz'
      AND id >= 1234
    ORDER BY id
    LIMIT 10
That will hit only 10 rows. This is a huge improvement for later pages. Now for more details.

Implementation -- "Left Off"


What if there are exactly 10 rows left when you display the current page? It would make the UI nice if you grayed out the [Next] button, wouldn't it? (Or you could suppress the button all together.)

How to do that? Instead of LIMIT 10, use LIMIT 11. That will give you the 10 items needed for the current page, plus an indication of whether there is another page. And the id for that page.

So, take the 11th id for the [Next] button: <a href=?topic=xyz&id=$id11>Next</a>

If you have a composite key for "left off", then it is a bit more complex. See compound leftoff

Implementation -- Links Beyond [Next]


Let's extend the 11 trick to also find the next 5 pages and build links for them.

Plan A is to say LIMIT 51. If you are on page 12, that would give you links for pages 13 (using 11th id) through pages 17 (51st).

Plan B is to do two queries, one to get the 10 items for the current page, the other to get the next 41 ids (LIMIT 10, 41) for the next 5 pages.

Which Plan to pick? It depends on many things, so benchmark.

A Reasonable set of Links


Reaching forward and backward by 5 pages is not too much work. It would take two separate queries to find the ids in both directions. Also, having links that take you to the First and Last pages would be easy to do. No id is needed; they can be something like
    <a href=?topic=xyz&id=FIRST>First</a>
    <a href=?topic=xyz&id=LAST>Last</a>
The client would recognize those, then generate a SELECT with something like
    WHERE topic = 'xyz'
    ORDER BY id ASC -- ASC for First; DESC for Last
    LIMIT 10
The Last items would be delivered in reverse order. Either deal with that in the client, or make the SELECT more complex:
    ( SELECT ...
        WHERE topic = 'xyz'
        ORDER BY id DESC
        LIMIT 10
    ) ORDER BY id ASC

Let's say you are on page 12 of lots of pages. It could show these links:
    [First] ... [7] [8] [9] [10] [11] 12 [13] [14] [15] [16] [17] ... [Last]
where the ellipsis is really used. Some end cases:
Page one of three:
    First [2] [3]
Page one of many:
    First [2] [3] [4] [5] ... [Last]
Page two of many:
    [First] 2 [3] [4] [5] ... [Last]
If you jump to the Last page, you don't know what page number it is.
So, the best you can do is perhaps:
    [First] ... [Prev] Last

Why it Works


The goal is to touch only the relevant rows, not all the rows leading up to the desired rows. This is nicely achieved, except for building links to the "next 5 pages". That may (or may not) be efficiently resolved by the simple SELECT id, discussed above. The reason that may not be efficient deals with the WHERE clause.

Let's discuss the optimimal and suboptimal indexes.

For this discussion, I am assuming
    ⚈  The datetime field might have duplicates -- this can cause troubles
    ⚈  The id field is unique
    ⚈  The id field is close enough to datetime-ordered to be used instead of datetime.

Very efficient -- it does all the work in INDEX(topic, id):
    WHERE topic = 'xyz'
      AND id >= 876
    ORDER BY id ASC
    LIMIT 10,41
That will hit 51 consecutive index entries, 0 data rows.

Inefficient -- it must reach into the data to get deleted:
    WHERE topic = 'xyz'
      AND id >= 876
      AND is_deleted = 0
    ORDER BY id ASC
    LIMIT 10,41
That will hit at least 51 consecutive index entries, plus at least 51 randomly located data rows.

Efficient -- back to the previous degree of efficiency via INDEX(topic, is_deleted, id):
    WHERE topic = 'xyz'
      AND id >= 876
      AND is_deleted = 0
    ORDER BY id ASC
    LIMIT 10,41
Note how all the = parts of the WHERE come first; then comes both the >= and ORDER BY, both on id. This means that the INDEX can be used for all the WHERE, plus the ORDER BY.

"Items 11-20 out of 12345"


You lose the "out of" except when the count is small. Instead, say something like
    Items 11-20 out of Many

Alternatively... Only a few searches will have too many items to count. Keep another table with the search criteria and a count. This count can be computed daily (or hourly) by some background script. When discovering that the topic is a busy one, look it up in the table to get
    Items 11-20 out of about 49,000
The background script would round off the count.

The quick way to get an estimated number of rows for an InnoDB table is
    SELECT  table_rows
        FROM  information_schema.TABLES
        WHERE  TABLE_SCHEMA = 'database_name'
          AND  TABLE_NAME = 'table_name'
However, it does not allow for the WHERE clause that you probably have.

The SQL_CALC_FOUND_ROWS query modifier and accompanying FOUND_ROWS() function are now deprecated and will be removed in a future MySQL version. ... COUNT(*) is subject to certain optimizations. SQL_CALC_FOUND_ROWS causes some optimizations to be disabled. -- From the MySQL 8.0.17 Changelog

Complex WHERE, or JOIN


If the search criteria cannot be confined to an INDEX in a single table, this technique is doomed. I have another paper that discusses "Lists", which solves that (which extra development work), and even improves on what is discussed here.

How Much Faster?


This depends on
    ⚈  How many rows (total)
    ⚈  Whether the WHERE clause prevented the efficient use of the ORDER BY
    ⚈  Whether the data is bigger than the cache.
This last one kicks in when building one page requires reading more data from disk can be cached. At that point, the problem goes from being CPU-bound to being I/O-bound. This is likely to suddenly slow down the loading of a pages by a factor of 10.

What is Lost


    ⚈  Cannot "jump to Page N", for an arbitrary N. Why would you want to do that?
    ⚈  Walking backward from the end does not know the page numbers.
    ⚈  The code is more complex.

Pagination and UNION


If you must use OFFSET, but the query involves UNION, this is the pattern to use:
( SELECT ... ORDER BY x  LIMIT 10 )
UNION
( SELECT ... ORDER BY x  LIMIT 10 )
ORDER BY x  LIMIT 10
Since you cannot predict which SELECT(s) will the "10" will come from, you need to get 10 from each, then further whittle down the list, repeating both the ORDER BY and LIMIT.

For the 4th page of 10 items, this pattern is needed:
( SELECT ... ORDER BY x  LIMIT 40 )
UNION
( SELECT ... ORDER BY x  LIMIT 40 )
ORDER BY x  LIMIT 30, 10
That is, collect 4 page's worth in each SELECT, then do the OFFSET in the UNION.