Home JavaScript_자바스크림트로 웹 페이지 제어하기
Post
Cancel

JavaScript_자바스크림트로 웹 페이지 제어하기

자바스크립트 시작하기

HTML과 자바스크립트 연결하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- HTML 파일 내부 연결 -->

<!DOCTYPE html>
<html lang="ko">
	<head>
		<meta charset="UTF-8">
		<title>javascript</title>
		<link rel="css" href="./css.css">
	</head>
	<body>
		<h1 id="title">Java Script</h1>
<!-- <script> 태그 안의 코드는 아이디값이 #title인 요소를 title 변수에 입시로 저장하라는 의미 -->
		<script>
		const title = document.querySelector("#title");
		console.log(title);
// title 안에는 h1 요소가 담기고 console.log로 title을 출력
		</script>
	</body>
</html>

HTML 내부 연결

  • 자바스크립트 문법은 마지막에 세미콜론으로 명령문이 끝났다는 표시를 해야함
  • console 구문은 웹 브라우저의 개발자 도구에서 확인해야함
  • 웹 브라우저는 HTML 파일을 입력한 순서대로 코드를 읽기때문에 자바스크립트 구문을 <body>영역보다 먼저 실행한다면 생성되지 않은 HTML 요소를 제어할 수 없으므로 문제 발생
1
2
3
4
// 외부 자바스크립트 연결

const title = document.querySelector("#title");
console.log(title);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- 외부 자바스크립트 연결 -->

<!DOCTYPE html>
<html lang="ko">
	<head>
		<meta charset="UTF-8">
		<title>javascript</title>
		<link rel="css" href="./css.css">
<!-- HTML과 다른 폴더에 있다면 '폴더명/스크립트 파일명' 기재 -->
		<script defer src="java script.js"></script>
	</head>
	<body>
		<h1 id="title">Java Script</h1>
	</body>
</html>

HTML 외부 연결

  • defer 지정 : 웹 브라우저의 자바스크립트 해석기가 <body>를 해석하면서 동시에 외부 자바스크립트 파일을 가져오며, 웹 브라우저 <body>영역의 내용이 모두 출력되면 외부 스크립트 파일 실행

자바스크립트로 HTML요소 선택하기

document.querySelector() - 요소 선택하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="ko">
	<head>
		<meta charset="UTF-8">
		<title>document.querySelector</title>
		<link rel="css" href="./css.css">
		<script defer src="java script.js"></script>
	</head>
	<body>
		<section id= "wrap">
			<article class="box1">TEXT1</article>
			<article class="box2">TEXT2</article>
			<article class="box3">TEXT3</article>
		</section>
	</body>
</html>
1
2
// <body>에서 아이디가 wrap인 요소 찾기
document.querySeletor("#wrap");
  • 찾은 요소를 임시로 저장하는 변수에 넣어야 필요시 효율적으로 재사용할 수 있으므로 변수를 넣어 수정
1
2
3
4
5
// const.변수이름 = 요소를 찾는 자바스크립트 구문
const frame = document.querySelector("#wrap");
console.log(frame);
// 아이디가 wrap인 요소를 찾아 그 결괏값을 frame이라는 변수에 저장
// 이 값을 다시 console.log()를 이용해 콘솔창 출력 : TEXT1~3모두 출력됨
1
2
3
4
const frame = document.querySelector("#wrap .box1");
console.log(box1);

// console창에는 box1 구문만 뜨지만 결과창에는 <body>안 내용이 모두 출력됨

= 기호

  • 자바스크립트에서 = 기호는 오른쪽의 구문을 먼저 실행해서 왼쪽에 대입하는 연산자
  • = 을 기준으로 오른쪽에서 연산한 결괏값을 왼쪽에 넣는 것을 의미

변수

  • 자주 쓰는 데이터를 값으로 저장하여 빠르고 간편하게 사용할 수 있도록 저장하는 공간
  • const : 값이 절대 변경되면 안되는 데이터를 저장할 때 사용
  • let : 값이 계속 변할 수 있는 데이터를 저장할 때 사용

document.querySelectorAll() - 요소를 모두 선택하기

1
2
3
4
5
const items = document.querySelectorAll("#wrap article");
console.log(items);

// NodeList 라는 특별한 묶음의 결괏값이 나오게됨
// Nodelist는 반복문을 이용해 요소를 하나씩 선택해야함
1
2
3
4
5
6
7
8
const items = document.querySelectorAll("#wrap article");

for(let item of items) {
	console.log(item);
}

// 변수 item은 반복문을 실행하면서 계속하여 변경된 값을 저장할 것이므로 예약어 let 사용
// itemts에 담긴 article 요소의 개수만큼 반복하며 찾은 대상을 item에 저장하고, 코드 블록({})안의 구문을 반복하면서 item값 출력
1
2
3
4
5
6
// for of문의 기본 형식
// 여러 개의 요소를 편하게 반복하며 코드를 실행 할 수 있음

for (let 반복하는 요소가 담길 변수 of 반복시킬 그룹) {
	반복 실행할 구문
}

image

1
2
3
4
5
6
7
8
9
10
11
12
const items = document.querySelectorAll("#wrap article");

for(let i=0; i<item.length; i++) {
	console.log(item[i]);
}

// let i=0 : 반복하기 위한 초기 숫섯값 지정(프로그래밍 언어에서는 시작 순서가 0)
// i < items.length : 변수에 담긴 그룹 요소의 개수를 자동으로 계산하여 해당 개수보다 작을 때까지 반복
// i++ : 1씩 증가
// console문의 items[i] : items 그룹에서 i번째의 요소 탐색

// console.log(items[0]~console.log(items[3])구문을 반복하면서 items그룹 안에 있는 모든 요소 탐색
1
2
3
4
5
6
// for문의 기본 형식
// 반복되는 요소의 순섯값을 코드 내부에서 활용하기에 효과적

for (let 반복하는 순서가 담길 변수; 반복할 횟수의 최댓값; 증감 연산자) {
	반복 실행할 구문
}

image

for문

  • 변수 i의 초깃값인 0부터 시작해 전체 그룹의 개수보다 작으면 코드 블록 안의 구문을 실행하고, i값을 1씩 증가시킴
  • 증가된 i값을 다시 전체 그룹의 개수와 비교해 작으면 코드 실행 반복, i값이 전체 그룹의 개수와 같거나 크면 반복 종료

부모, 자식, 형제 요소 선택하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="ko">
	<head>
		<meta charset="UTF-8">
		<title>document.querySelector</title>
		<link rel="css" href="./css.css">
		<script defer src="java script.js"></script>
	</head>
	<body>
		<ul class="list">
			<li class="item1">item1</li>
			<li class="item2">item2</li>
			<li class="item3">item3</li>
			<li class="item4">item4</li>
		</ul>
	</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 자식 요소 선택하기

const list = document.querySelector(".list");
const items = list.children;

console.log(items);
console.log(items[0]);
console.log(items[1]);
console.log(items[2]);
console.log(items[3]);

// list 변수에 .list 요소 저장
// 자식 요소인 .children을 선택하여 items 변수에 저장(li 자식 요소 4개가 그룹으로 묶임)
// console.log()문으로 요소를 하나씩 선택하여 보여줌
1
2
3
4
5
6
7
// 부모 요소 선택하기

const itme2 = document.querySelector(".item2");
console.log(item2.parentElement);

// 클래스가 item2인 자식 요소를 변수에 저장
// 해당 요소를 기준으로 .parentElement 구문을 이용해서 console.log()문으로 출력하면 부모 요소인 ul 요소 선택
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!-- 제일 가까운 상위 부모 요소 선택하기 -->

<!DOCTYPE html>
<html lang="ko">
	<head>
		<meta charset="UTF-8">
		<title>document.querySelector</title>
		<link rel="css" href="./css.css">
		<script defer src="java script.js"></script>
	</head>
	<body>
		<main>
			<section>
				<article>
					<ul>
						<li>list</li>
					</ul>
				</article>
			</section>
		</main>
	</body>
</html>
1
2
3
4
5
6
7
8
// 제일 가까운 상위 부모 요소 선택하기
// li 요소 기준 최상위 부모 요소인 main 탐색

const li = document.querySelector("li");
console.log(li.closest("main"))

// li 요소를 변수에 저장
// console.log()문으로 li.closest("main")의 결괏값 출력 : li의 부모 요소 중 제일 가까운 main 요소 탐색
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- 형제 요소 선택하기 -->

<!DOCTYPE html>
<html lang="ko">
	<head>
		<meta charset="UTF-8">
		<title>document.querySelector</title>
		<link rel="css" href="./css.css">
		<script defer src="java script.js"></script>
	</head>
	<body>
		<ul class="list">
			<li class="item1">item1</li>
			<li class="item2">item2</li>
			<li class="item3">item3</li>
			<li class="item4">item4</li>
		</ul>
	</body>
</html>
1
2
3
4
5
6
7
8
9
// 형제 요소 선택하기

const item3 = document.querySelector(".item3");
console.log(item3.previousElementSibling);
console.log(item3.nextElementSibling);

// 변수에 item3의 3번째 li저장
// previousElementSibling과 nextElementSibling을 사용
// 3번째 리스트 기준 각각 이전 형제 요소와 다음 형제 요소 선택

자바스크립트로 스타일 제어하기

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="ko">
	<head>
		<meta charset="UTF-8">
		<title>style</title>
		<link rel="css" href="./css.css">
		<script defer src="java script.js"></script>
	</head>
	<body>
		<article id="box"></article>
	</body>
</html>
1
2
3
4
5
6
7
8
9
10
// #box 요소를 찾아 변수 box에 넣음
const box = document.querySelector("#box");

// 변수에 담겨 있는 DOM 모델을 불러온 뒤 .style을 공통으로 붙이고 변경하고 싶은 속성명 입력
box.style.width = "10%";
box.style.height = "300px"
// 단어가 2개가 하이픈(-)으로 연결된 속성은 2번째 시작하는 단어의 첫 글자를 대문자로 바꿔 입력(예약어로 존재하기 때문)
box.style.backgroundColor = "hotpink";
box.style.border = "none";
box.style.transform = "rotate(10deg)";

자바스크립트 스타일

  • 자바스크립트 이용 시 이미 CSS로 지정된 HTML 요소의 스타일을 다시 변경할 수 있음(DOM에 내장된 style 속성값을 변경하여 기존 CSS 스타일에 덮어쓰는 작업)

DOM

  • 브라우저가 인식할 수 있는 특수한 형태의 문서 객체 모델
  • HTML의 기본 정보 + 요소 스타일의 정봇값(style 속성) = 특정 정보의 패키지 묶음

예약어

  • 프로그래밍 언어에서 특수한 기능을 하는 문자나 단어
  • 자바스크립트에서 하이픈은 산술 연산 예약어이기에 속성명에 쓸 수 없음

자바스크립트로 이벤트 연결하기

  • 자바스크립트를 적용한 웹 페이지는 출력을 완료한 후에도 사용자 행동에 따라 웹 요소를 동적으로 변경할 수 있음
    • 사용자 행동은 자바스크립트의 여러 가지 이벤트로 나타낼 수 있게 됨

클릭 이벤트 연결하기

1
2
3
// addEventListener()문

요소명.addEventListener("이벤트명", (전달될 ) => {실행할 구문});

addEventListener() 문

  • 첫번째 괄호 : 등록할 이벤트명
  • 두번째 괄호 : 리스너(이벤트가 발생할 때 응답해서 실행할 동작)
1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="ko">
	<head>
		<meta charset="UTF-8">
		<title>click</title>
		<link rel="css" href="./css.css">
		<script defer src="java script.js"></script>
	</head>
	<body>
		<a hrdf = "https://1027-r.github.io/">click</a>
	</body>
</html>
1
2
3
4
5
6
7
8
9
10
// 변수 link 생성 후 querySelector() 문으로 a 요소 찾아 저장
// link에 담겨 있는 a요소에 .addEventListner()문 연결해 클릭 이벤트 지정
const link = documnet.querySelector("a");

link.addEventListener("click", ()=>{
	console.log("링크 클릭");
});

// link 변수에 저장된 a 요소 클릭 시 콘솔 창에 '링크 클릭'이라는 텍스트 출력
// 하지만 웹 브라우저에서 링크를 클릭하면 콘솔 창에 텍스트를 출력하기도 전에 네이버 페이지로 이동
1
2
3
4
5
6
7
8
9
10
11
12
// 링크 클릭 시 기본 기능인 링크 이동을 막고, 콘솔 창에 텍스트가 나타나도록 수정한 코드

const link = documnet.querySelector("a");

link.addEventListener("click", (e)=>{
	e.preventDefault();
	// 링크 이동기능을 막고 console.log() 실행
	console.log("링크 클릭");
});

// 이벤트가 발생하면 자동으로 이벤트 객체라는 특별한 값이 화살표 함수로 자동 전달
// 추가로 넣은 e값이 바로 이벤트 객체

매개변수 e

  • 이벤트 객체를 전달받는 매개변수
  • 매개변수 이름은 e 대신 마음대로 붙여도 됨(생략 가능)
  • 이벤트 핸들러 함수에 들어가는 매개변수 e는 이벤트 리스너에 등록됐을 때 자동으로 이벤트 객체가 담기게 됨

e.preventDefault() 문

  • 이벤트의 기본 기능을 실행하지 말라는 명령어

호버 이벤트 연결하기

  • 특정 영역에 마우스 포인터를 올리거나 내릴 때 동작
1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="ko">
	<head>
		<meta charset="UTF-8">
		<title>hover</title>
		<link rel="css" href="./css.css">
		<script defer src="java script.js"></script>
	</head>
	<body>
		<div id = "box"></div>
	</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
const box = documnet.querySelector("#box");

box.addEventListener("mouseenter", ()=>{
	box.style.backgroundColor = "hotpink";
});
// mouseenter 실행 시 box 배경색이 hotpink로 변경


box.addEventListener("mouseleave", ()=>{
	box.style.backgroundColor = "black";
});
// mouseleave 실행 시 배경색 black

반복되는 요소에 이벤트 한꺼번에 연결하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="ko">
	<head>
		<meta charset="UTF-8">
		<title>event</title>
		<link rel="css" href="./css.css">
		<script defer src="java script.js"></script>
	</head>
	<body>
		<ul class = "list">
			<li><a href="#">item1</a></li>
			<li><a href="#">item2</a></li>
			<li><a href="#">item3</a></li>
			<li><a href="#">item4</a></li>
		</ul>
	</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 리스트를 그룹으로 만들어 변수 list에 저장
const list = document.querySelectorAll(".list li");

// for of문을 이용해 list 그룹을 반복하면서 이벤트 연결
for(let el of list){
	el.addEventListener("click", e=>{
		e.preventDefault();
		console.log(e.currentTarget.innerText);
	})
}

// 변수 el에 저장되고 있는 반복 요소를 클릭할 때마다 해당 요소를 e.currentTarget.으로 선택 후 .innerText 구문 연결
// .innerText구문은 선택한 요소의 텍스트를 불러옴
// 버튼 클릭 시 콘솔 탭에 각각 item1~4의 텍스트 내용 출력

선택자.innerText = “변경할 텍스트”;

  • 텍스트 내용 변경 시 변경한 텍스트 내용을 대입 연산자로 지정

e를 감싸는 괄호

  • 전달되는 값이 하나뿐일 경우 괄호 생략 가능

클릭 이벤트가 발생할 때 숫자를 증가, 감소하기

  • 텍스트를 클릭할 때마다 계속해서 숫자를 증가 혹은 감소 시키는 기능 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="ko">
	<head>
		<meta charset="UTF-8">
		<title>click event</title>
		<link rel="css" href="./css.css">
		<script defer src="java script.js"></script>
	</head>
	<body>
		<a href="#" class = "btnPlus">plus</a>
		<a href="#" class = "btnMinus">minus</a>
	</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 각각의 버튼을 변수 btnPlus, btnMinus에 담아둠
const btnPlus = document.querySelector(".btnPlus");
const btnMinus = document.querySelector(".btnMinus");
let num = 0; //제어할 숫잣값 0으로 초기화, 클릭할때 마다 변화 예정이므로 let으로 선언

// btnPlus를 클릭할 때마다
btnPlus.addEventListener("click", e => {
	e.preventDefault();
	// num값을 1씩 증가
	num++; // ex_값을 2씩 증가로 변경시 num+=2
	console.log(num);
});

// btnMinus를 클릭할 때마다
btnMinus.addEventListner("click", e => {
	e.preventDefault();
	// num값을 1씩 감소
	num--; // ex_값을 2씩 감소로 변경시 num-=2
	console.log(num);
})

문자 안에 변수 삽입하기

  • 변수의 값을 그대로 유지하면서 문자 안에 삽입하는 방법 : 문자 안에 변수나 연산식 삽입 시 해당 변수안에 저장되어 있는 값이 실행되는 것이 아니라 변수가 강제로 문자로 변환되어 결과값이 출력됨
1
2
const myName = "김철수";
console.log(`내 이름은 ${myName}입니다`); // 문자를 백틱(``)으로 감싸줌

클릭하면 좌우로 회전하는 박스 만들기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="ko">
	<head>
		<meta charset="UTF-8">
		<title>box</title>
		<link rel="css" href="./css.css">
		<script defer src="java script.js"></script>
	</head>
	<body>
		<a href="#" class = "btnLeft">왼쪽으로 회전</a>
		<a href="#" class = "btnRight">오른쪽으로 회전</a>
		<div id="box"></div> <!-- 제어할 박스 -->
	</body>
</html>
1
2
3
4
5
6
7
8
9
@charset "utf-8";

#box {
	width: 300px;
	height: 300px;
	margin: 50px;
	background: black;
	transition: 0.5s;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const btnLeft = document.querySelector(".btnLeft");
const btnRight = document.querySelector(".btnRight");
const box = document.querySelector("#box");
const deg = 45; // 회전할 각도의 값 저장
let num = 0; // 증가시킬 값 0으로 초기화

// btnLeft 클릭할 때마다
btnLeft.addEventListener("click", e => {
	e.preventDefault();
	// num값 1씩 감소
	num--;
	// 45도 각도에 감소된 num값을 deg값과 곱하여 rotate 구문에 삽입
	box.style.transform = 'rotate(${num * deg}deg)';
});

// btnRight 클릭할 때마다
btnRight.addEventListener("click", e => {
	e.prenetDefault();
	// num값을 1씩 증가
	num++;
	// 45도 각도에 증가된 num값을 deg값과 곱하여 rotate 구문에 삽입
	box.style.transform = 'rotate(${num * deg}deg)';
});

// btnLeft, btnRight 변수에 각 텍스트 저장하고 클릭 이벤트 연결
// 텍스트 링크를 클릭하면 0으로 초기화한 num 변숫값을 1씩 증가 or 1씩 감소시켜 해당 값을 다시 deg값과 곱함
// rotate() 속성값에서 ${ num * deg } 연산식 이용시 box의 회전값을 좌우로 45도씩 변경 가능
// css에 transition을 0.5s로 지정했기 때문에 텍스트 링크를 클릭할 때마다 박스가 좌우로 45도씩 0.5초만에 회전하게 됨

자바스크립트의 산술 연산자

  • * : 곱하기
  • / : 나누기
  • % : 나머지 값 구하기

자바스크립트로 클래스 제어하기

  • CSS 파일에서 특정 클래스 이름에 따라 스타일 설정 후 자바스크립트에서는 클래스 이름만 추가 및 제거하는 방법으로 스타일 변경
    • why? : 특정 HTML 요소의 스타일을 자바스크립트로 직접 변경하게되면 CSS 파일의 스타일 구문을 바꾸지 않고 HTML에 인라인 스타일을 직접 적용하므로 CSS파일에 등록한 우선순위가 무시됨
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="ko">
	<head>
		<meta charset="UTF-8">
		<title>class</title>
		<link rel="css" href="./css.css">
		<script defer src="java script.js"></script>
	</head>
	<body>
		<section id = "wrap">
			<article></article>
		</section>
	</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@charset "utf-8";

/* 프레임 #wrap */
#wrap {
	width: 500px;
	height: 500px;
	border : 1px solid #000;
	padding : 100px;
	box-sizing : border-box;
	margin: 100px auto;
}
#wrap article {
	width : 100%;
	height : 100%;
	background : black;
	transition : 1s;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
// #wrap 영역을 클릭하면 해당 요소의 자식인 article의 배경색이 white로 변경

const wrap = document.querySelector("#wrap");
// documet.querySelector()문으로 #wrap 요소를 찾아 변수 wrap에 저장
const box = wrap.qeurySelector("article");
// article에는 이미 wrap이라는 변수에 부모 요소가 저장되었으므로 변수 wrap을 불러와서 다시 querySelector로 자식 요소인 article을 찾아 변수 box에 저장

// addEventListener()문을 활용하여 wrap을 클릭하면 자식 요소인 box의 배경색을 white로 변경
wrap.addEventListener("click", () => {
	box.style.backgroindColcor = "white";
});

// article에 인라인으로 스타일이 강제 지정
자바스크립트로 변경한 CSS는 태그에 스타일 속성이 직접 삽입되게됨
태그에 인라인 형태로 삽입된 스타일 구문은 우선순위가 높아 기존 CSS파일에 적용된 스타일 우선순위에 문제가 발생할 수도 있음
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 /* 해당 요소에 스타일 속성값을 직접 변경하지 않고 클래스명을 추가해서 배경색 제어하기
  */
@charset "utf-8";

#wrap {
	width: 500px;
	height: 500px;
	border : 1px solid #000;
	padding : 100px;
	box-sizing : border-box;
	margin: 100px auto;
}
#wrap.on article { /* 클래스 on 추가 */
	width : 100%;
	height : 100%;
	background : black;
	transition : 1s;
}
1
2
3
4
5
6
7
8
9
10
11
// 해당 요소에 스타일 속성값을 직접 변경하지 않고 클래스명을 추가해서 배경색 제어하기

const wrap = document.querySelector("#wrap");
const box = wrap.qeurySelector("article");

wrap.addEventListener("click", () => {
	wrap.classList.add = ("on");
	// wrap 요소의 클래스 리스트에 on 클래스 추가
});

// 부모 요소인 section#wrap에만 클래스 on이 추가되어 CSS 파일에 미리 설정한 배경색 적용
1
2
3
4
5
6
7
8
9
10
11
// section#wrap 영역을 다시 클릭하면 원래 색상으로 돌아올 수 있도록 추가해 준 클래스 on 제거
// 이벤트가 발생했을 때 특정 조건을 설정해서 이 조건에 맞을 때에만 클래스를 제거해야하므로 조건문 활용
// 기존 #wrap 요소 클릭 시 클래스 on이 있으면 true, 없으면 false값을 받음

const wrap = document.querySelector("#wrap");
const box = wrap.qeurySelector("article");

wrap.addEventListener("click", () => {
	let isOn = wrap.classList.contains("on");
	console.log(isOn);
});
This post is licensed under CC BY 4.0 by the author.