An introduction to programming
Covers basic concepts and guides you through three projects, using web technologies and Rust.
Welcome to my introduction to programming! This is targeted to those who have no previous experience; everybody can follow along!
If you find any errors or sections in need of improvement, mail me. Also, if you find this introduction helpful, consider sharing.
First I’ll cover how you can follow along in this guide. Then, I’ll cover the basics and answer what a programming language is.
Jump to…
Follow along
Trying it out for yourself is the best way to learn programming.
An overwhelming number of tools exist, so it’s tough to choose one broad enough (and good enough) to get started with and learn. I recommend VS Code (or rather Codium, a completely free (as in freedom) variant) as it supports virtually all languages and has excellent features.
See this section for info on why it’s important to not use Microsoft’s “official” download.
Press the Releases
button on the middle right and download the file for your
operating system.
Code online
If you want to educate a broader audience and found this article, consider using my VS Code setup for online development. All users get their own VS Code in the browser with all the features of the desktop version.
To set it up requires you to have technical know-how and comfort with the shell.
The basics
First we’ll cover the basics. This will give you the knowledge needed to build a simple counter which increments (adds) when you press a button.
Note on running the examples: The following examples can be ran by right-clicking on this web page, then
Inspect
. You have now opened theDeveloper tools
. Find the console tab in the top. There, you can paste the code you find here.
For some reason (probably lazyness, it’s hard to write capitals) programmers usually use all lowercase names. Try to keep this in mind.
Functions
A function takes data (anything; text, numbers, lists, other data structures), changes it and also possibly it’s environment (anything you see the program doing; changing files on your computer, showing info to you), and returning something (which can be nothing).
Example:
// We define a function called `multiply`, which takes two arguments; `x` and `y`.
function multiply(x, y) {
return x * y
}
// Read "let eight be multiply two with four
let eight = multiply(2, 4)
// `eight` is 8!
We often use parentheses ()
to call (run) them. The text between the
parentheses is the input to the function. You can have multiple inputs,
separated by ,
. The type of data and count of arguments have to match in the
function definition and where it’s called.
In the example above, 2
is x
and 4
is y
in the function.
These arguments are sensitive to order; if a function takes two inputs, you have to write them in the right order.
Variables
Variables store data. See eight
above, a variable holding the number 8
.
Comments
It’s crucial to write reminders and reasons for the code you write. This is
called comments. In these examples, anything after //
is a comment; excluded
from the logic of the code.
Accessing properties and methods
The data variables store can have properties and methods. Properties are data part of the “bigger object”. Methods are functions (which use parentheses to take data or arguments) which use the data of the “bigger object”, both viewing it and changing it. You can think about it as the method taking a reference to the “bigger object”.
These both are accessed by a dot .
let person = {
name: "Icelk",
}
console.log(person.name)
This creates a person
with a name Icelk
. We log (write the text from the
function input to the console
) the name
property of person
. console
is
a object with several methods and properties, one of them being log
, which
takes text (called a string
), in this example Icelk
.
Control flow
It’s good to do different things depending on input; otherwise all computer programs would do the exact same thing every time. It would mean no input, no new websites, no new features. That’s not good!
The main concept of control flow is if else. Take this example:
// Math.random() returns a number between 0 and 1
let randomValue = Math.random()
if (randomValue > 0.8) {
console.log("We are very lucky! 🥠🍀")
} else if (randomValue >= 0.5) {
console.log("50-50")
} else {
console.log("We had bad luck 😔")
}
The same { }
brackets are used for separating code in control flow as in
functions.
Why the
( )
parentheses are used around the predicate (logic resulting intrue
orfalse
), nobody knows. We’ll cover this later, but in the meantime, just add them.
Example: Saying hello[]
function sayHello(names) {
// Lists have a method called `forEach`,
// which runs the code in the { } for every element in the list.
names.forEach(function (name) {
// The `${name}` becomes the value of the variable named `name` when it's ran.
console.log(`Hello ${name}!`)
})
}
let friends = ["Arnold", "Carl", "Bob"]
sayHello(friends)
Here, we define a function called sayHello
, which takes one argument
(input), a array (list) of names.
It calls the method forEach
on names
. We then define a “anonymous”
function which will be ran for each element in the list, with the element as the
first and only argument.
We then console.log
the name.
Notice the ` (backticks) around Hello
. This enables us to use ${name} to show
the name variable in the text.
This code, if run, prints
Hello Arnold!
Hello Carl!
Hello Bob!
Sharing code
It would be impossible to build everything from the ground up every time we developers want to make a new product. We therefore reuse most of the code our product uses, and only write a fraction ourselves.
This is enabled by a concept called open source, that you share your work under a permissive license (anyone can use the code for anything).
Building apps on the web is a relatively easy thing to do; a lot of code goes in to making it easy to program and fast for the user.
See this video for a overview of the layers of code used to run your website.
Similarities; what is a “programming language”?
You have probably heard the term “programming language” before.
Like languages, programming languages all strive after the same goal (communication or software) but achieve it with different quirks, style, grammatical rules (called syntax in programming), and vocabularies (in a programming language, there exists several keywords, reserved words used)
These keywords include the
if else
we’ve used,function
, andlet
to define a variable.
Syntax
The formal “rules” of a programming language is called the syntax. It’s where
and when the parentheses ( )
, { }
, and [ ]
should be placed, where commas
and dots should be, how the keywords are used (function
, if else
).
This differers between languages, though must languages are “C-like”; they follow syntactic rules inherited from the “first” programming language, C.
Built-ins
To enable basic functionality (e.g. reading files, showing text in the console, networking), each programming language provides some “built-in” functions, objects, and methods.
These naturally differ between languages.
This and syntax is virtually the only differences between programming languages.
Style
To make code appear nicer, we programmers build formatters to automatically set right indentation, break apparat long lines, move parentheses, and much much more.
There also exits naming conventions; how to capitalise words. In the language
you’ve seen examples in so far, the norm is to name everything like this
icelkFavouritePet
, with capitalised words after the first. In Rust, VS Code
(if you’ve installed the Rust
extension) tells you to change the name, and
provides a button which does it for you.
Note how this is handled in Rust later on.
Variables can’t have spaces in them, else the computer would have a very hard time understanding what you wrote, which results in these odd naming conventions.
Some languages, such as Python, are sensitive to indentation. Where the examples we’ve seen have
{ }
, Python only reads the indentation level.
Example: Saying hello[] in multiple languages
To show the differences, I’ll write the example from above in Rust too.
JavaScript (the previous example)
function sayHello(names) {
names.forEach(function (name) {
console.log(`Hello ${name}!`)
})
}
let friends = ["Arnold", "Carl", "Bob"]
sayHello(friends)
Rust
fn say_hello(names: &[&str]) {
for name in names {
println!("Hello {}!", name);
}
}
fn main() {
let friends = ["Arnold", "Carl", "Bob"];
say_hello(&friends);
}
We have a few things to note.
First, function
becomes fn
.
Second, we add ;
semicolons to the end of declarations of variables and
function calls.
Third, we use the &
and sign to pass a reference. This tells Rust the
say_hello
function only needs to read the names, not change them.
Fourth, the code which calls say_hello
is in another function called main
.
This function is called when you execute (run) the program, while JavaScript
runs all code not in functions.
Fifth, the say_hello
function has a lowercase n with a underscore between the
words. This is the norm in Rust.
Sixth, the say_hello
function takes names: &[&str]
. In a faster language
such as Rust, this is needed to avoid complications in the program. It tells the
computer to take a reference of an array (list) [ ]
of &str
, string
references (&
). This, as I told before, tells the program to only take a
readable reference; we won’t change the names.
Some say the sixth point increase robustness and readability, as the types of the data is explicit.
Seventh, the code to loop each element in the array names
is different. This
is built in to Rust (which is a newer language), but a method is needed in
JavaScript.
Eighth, and last, after the function println
(print line), a exclamation mark
!
is placed before it’s parentheses. This is because it’s a macro. You don’t
need to know this, so skip this if you’re not curious. A macro generates more
code; the println!("Hello {}!", name)
expands to more code before it’s run.
This enables special arguments to be used. The {}
becomes the value of name
.
This wouldn’t be possible using standard functions, as they have to take a
defined number of arguments.
Project: Let’s learn how to count!
It’s high time to practical learning. You are going to build a button which increments (adds) a number shown on the screen.
HTML (HyperText Markup Language) is the syntax of how to tell the browser to display certain elements.
Spaces and newlines do (mostly, don’t worry!) not matter. Keep this in mind; you can split sentences over multiple lines in your code to make it easier to read without effecting the product shown in the browser.
You can compare HTML, JavaScript, and CSS (styling to make the website look pretty) to writing a document. CSS being the style, HTML the position of paragraphs and grouping of text, and JavaScript the animations and other complex behaviour. JavaScript can do everything, including changing the HTML, handling payments, and executing game logic.
Boilerplate
To set up the document, you can type the !
exclamation mark in VS Code and hit
tab, ↹
.
Now, tab through the selected values (which leaves them by their defaults) but change the title; it’s the text in the tab on the top of your browser.
As you see, every tag which contains text or other elements have a corresponding closing tag. This is important.
The <head>
section contains metadata (data or information not shown in the
website but used by the browser and search engine to compose the page). We will
not touch it for now.
The <body>
section contains all elements. In HTML (the markup language you’re
going to write your website in) a element is a tag, such as <button>
which can
have attributes, <button id="my-button">
, and often a text or other elements
with a closing tag, in this case </button>
.
div, h1, p, and button
To start with HTML, it’s good to know 4 tag types.
<div>
is a container; it’s invisible but contains other elements. Each div
takes up the whole vertical space; you can’t have two divs beside each other.
<h1>
stands for “heading 1”, the first heading type. There exists 6, with 1
being the largest.
<p>
stands for “paragraph”. Well, it’s a paragraph…
<button>
can trigger JavaScript code when it’s pressed and appears as a button
to the user.
Divs, headings, and paragraphs share a important property. When the browser lays out the text and content, these all take up 100% of the horizontal space. If you changed the background of a heading, only the height it takes up would be the set color, but the whole horizontal space would become said colour.
This is unlike the button, which by default (yes, this can be changed, both for buttons and all the above) is inline; when placing two after each other they appear on the same “line”.
Remember, spaces and newlines do not matter in HTML.
Let’s add some elements
After typing !
(and saving the document as index.html
), your document should
look something like this
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My website!</title>
</head>
<body></body>
</html>
To reduce the clutter, you can remove the first two <meta>
tags. They are
mostly redundant.
The name="viewport"
meta tag makes the website look larger on smaller displays
(mobile devices).
If you have access to a phone and if your code is stored online, try changing the value of
initial-scale
, after we’ve added some content, and see how it changes the scale of the elements.
Now, lets add a <h1>
(do this by typing h1
and pressing tab in VS Code) tag
and a <button>
(same as before, type button
and press tab) under it. Type a
0
between the opening and closing tags of the heading and Add to counter
as
the buttons text. Your HTML should now look like this
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My website!</title>
</head>
<body>
<h1>0</h1>
<button>Add to counter</button>
</body>
</html>
If you do not have colours in your editor, make sure it’s saved a file ending in
.html
. The file extension (the text after the last period) defines how the syntax highlighting (colours. Syntax from how the code is structured and highlighting from the definition of highlighting; to make it clear) should be applied.
If you access the file, you should see a large 0
and a button which does
nothing. Let’s fix it!
Store references globally
To interact with the HTML, we need to store references to the elements. To get a reference, the elements need to have id’s.
Set the heading to have the id heading
and the button to have the id button
.
These id’s only need to be specified in the opening tag.
Now, the elements you added should look like this.
<h1 id="heading">0</h1>
<button id="button">Add to counter</button>
After the button and heading, add a <script>
tag. In it, you can write
JavaScript (the programming language the examples in this article are written
in) which is executed as it’s read in the document.
Spaces and newlines mostly do not matter in JavaScript. We use them to make it easier to read.
Notice the
"
. They mean the text inside is a string. A id is a string. If you put the closing parentheses)
inside the""
, JavaScript thinks it’s part of the id.
In the script tag, put this
let heading = document.getElementById("heading")
let button = document.getElementById("button")
Now, we’ve acquired references to JavaScript objects with methods and properties which we can interact with.
Events
We want to execute some code when the user clicks the button. In JavaScript, this is handled with events; we attach a function which is ran every time the event we are listening to happens.
Add this after the references (the let ... = ...
code you’ve just added).
button.addEventListener("click", function () {
// The user clicked the button.
// Increment the heading's text.
heading.innerText = heading.innerText + 1
})
The heading has a property called innerText
which contains the text of the
heading.
Now, open your file in a browser. See what happends when you press the button.
Your file should look like this.
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My website!</title>
</head>
<body>
<h1 id="heading">0</h1>
<button id="button">Add to counter</button>
<script>
let heading = document.getElementById("heading")
let button = document.getElementById("button")
button.addEventListener("click", function () {
// The user clicked the button.
// Increment the heading's text.
heading.innerText = heading.innerText + 1
})
</script>
</body>
</html>
Oh no! * 1
The number isn’t incremented, a 1
is just added to the end of the text.
To increment the number, we first need to convert it to a number, then increment it, and then set that number as the text (JavaScript handles the conversion from a number to a string (text)).
To convert text to a number, we can multiply the text with one. This is a mathematical operation only available on numbers; JavaScript has to convert the text to a number.
Because multiplying a number with 1 does nothing, this won’t change the number.
Change the line setting the text to this
heading.innerText = heading.innerText * 1 + 1
Now it should work!
To make this more flexible, we can move the code to a separate function.
Add this beneath the end of the addEventListener
call.
function addToHeader(amount) {
heading.innerText = heading.innerText * 1 + amount
}
and call this function from the event listener instead of changing the value manually;
button.addEventListener("click", function () {
addToHeader(1)
})
Now the code should look like this
let heading = document.getElementById("heading")
let button = document.getElementById("button")
button.addEventListener("click", function () {
// The user clicked the button.
// Increment the heading's text.
addToHeader(1)
})
function addToHeader(amount) {
heading.innerText = heading.innerText * 1 + amount
}
Try changing the input
amount
in the call toaddToHeader
to be any other number; the counter will increment with that amount every time you press the button.
Optional: Double the number every time instead
You need to change the addToHeader
function.
Tips: Multiply by amount and remove the
+ amount
. Now pass2
to theaddToHeader
function.
Optional: Styling
If you want to make the website look pretty, you can add CSS. This is defined in
the <head>
of the document.
Paste this code after the closing </title>
tag.
Remember, it can be on the next line, spaces and newlines do not matter in HTML.
<style>
/* Notice that the comments need a closing part too!
This means they can span several lines. */
:root {
/* Set the height to 100% so the body can take up all the space (else centrering won't work) */
height: 100%;
}
body {
/* See comment above */
height: 100%;
/* This is a advanced layouting option */
display: flex;
/* Set the layout to be in columns (we want one column, the button under the heading), not rows which is the default */
flex-direction: column;
/* These two center the content vertically and horizontally */
align-items: center;
justify-content: center;
/* Self-explanatory */
background-color: black;
/* Sets the text colour to `wheat` */
color: wheat;
}
button {
/* Set the background colour and text colour */
background-color: wheat;
color: black;
}
</style>
You can change the colours to hex colours (a colour on computers are represented as three values for each chanel in RGB (red, green, blue). Hex colours is a format to store this in a compact 6-character long string).
Search for “colour picker” and change the
colour. Then copy the value at the bottom with a #
prefixed.
You can then paste the hex colour in the places where a colour is applicable
with a #
prefixed. #3f3f3f
is a grey. #6699dd
is a light blue colour not
unlike the one used on icelk.dev.
Summary
So far, you’ve learned all the basics needed to build complex web apps.
Next, we are going to learn a bit about how the web works, the client-server and request-response model, and Git.
In a few minutes, you’re going to start building a to-do list.
What is the web?
Not so long ago (before I went head-first into the backbone of the web with my project Kvarn), I thought of the web as a static thing. Websites are always available. I didn’t think further.
When you open a website (for example this very page), a request is sent from your browser to the server hosting the website. That request is encoded to protect your data, sent through countless servers which relay your data (also called the Internet. Don’t know if you’ve heard of it ¯\_(ツ)_/¯).
At the other end a web server, such as Kvarn (which is handling your requests to this site), handles the request and creates a response, which it sends back to you, with the same procedure as when you sent the request. Your browser then interprets this and eventually shows a website to you. This all usually takes under a second.
To recap, my code takes the info your browsers code transmitted on your request and sent a response back to you. It’s all code!
When the content is showed to you on your browser, three main languages are used; HTML for the markup text (what is showed), JavaScript for interactive changes, and CSS for styling, layouting, colour, font, and all other appearance-related things.
Git it?
A prerequisite to Git (which you’ll learn more about soon) is the terminal. Assuming you’re using VS Code, you can access the integrated terminal (there also exists separate programs to access it) by hovering over the top edge of the bottom bar (you should see a arrow pointing up), pressing and holding, and dragging it up.
This is a very powerful tool and should be handled with care. Never Paste in any code you do not completely understand. It can destroy the entire system. Git is completely safe.
You are always inside a directory (folder on you computer). The directory you
are in is called the current working directory (CWD). You can test it by
typing pwd
(print working directory) and hitting enter. Most actions are
relative to it, including Git.
To interact with the system, we execute programs by their name and give them arguments, and hit enter.
To see the files in the CWD, type
ls
and hit enter. Tryls -lA
for more info. Here,ls
is the program and-la
a argument to get a list with all items. You should see the same files as the explorer in VS Code.
To change directory, use the cd
program. To navigate to a folder named web
in the CWD, type cd web
. To go to the parent directory (up one folder), use
cd ..
. ..
is always the parent directory.
Say you are in the previously mentioned
web
directory, executingcd ../web
would do nothing; you first go to the parent, then back down to the directory you are already in.
If a program takes a long time to finish, you can press and hold Ctrl
and
press c
to terminate a program. This can be used to abruptly stop the program.
To start of, watch this great video from the YouTube channel Fireship. His channel has tons of great content, for all levels of skill.
Feel free to pause and try out what he’s talking about.
You can also play around in VS Code’s Git integration. It’s located in the left Activity Bar and looks like three dots connected by two lines.
Here are some of the Git commands mentioned.
The
$
at the start means it should be executed by a user, not a administrator (called superuser or root in programming) and should not be included in the command you execute.
To initiate a new Git repo, use
$ git init
To stage changes in files for a commit
$ git add <file or . (a dot) for all files in the current working directory>
To commit them to Git use
$ git commit -m "Message"
I recommend making frequent commits; if you’ve changed something and your code works, commit it. Begin doing this in VS Code, as it’s simpler.
Project: What to do? A to-do list!
Since the basics were quite comprehensive, you don’t really need to learn anything more to make a to-do list.
I’ll short JavaScript to JS in this chapter. This is widely used.
I’ll list a few built-ins in JS you’ll need here. Try to do some of this on your own!
document.getElementById(id)
gets the element withid
<input type="text">
shows a small text input field.<input>
s can have aplaceholder
attribute to show a default text when the user hasn’t written anything yet.<hr>
shows a vertical bar, to separate sections.document.createElement(tagName)
creates a element with the format<tagName></tagName>
. To create a paragraph, usedocument.createElement("p")
. You have to declare this as a variable and later append it to another element. See below. To create a header and set it’s test:let header = document.createElement("h1")
and on the next lineheader.innerText = "Fun stuff"
.parentElement.appendChild(childElement)
adds the elementchildElement
just before the closing tag ofparentElement
. To add the previously createdheader
(see above) to the end of the document (specifically the end of<body>
), add thisdocument.body.appendChild(heading)
Structure
We need to define the structure of the document. Begin with the boilerplate by
typing !
and pressing tab in a new document.
Try to make a structure which suits this. We need a container (<div>
) for the
items, another for the inputs to create a new item, and another one for the
completed items.
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>To-do list</title>
</head>
<body>
<div id="list">
<div id="to-do-items">
<!-- Items -->
<!-- Template item: -->
<div class="item">
<input type="text" placeholder="Item" />
<input type="checkbox" />
<!-- a fun character -->
<span>✘</span>
</div>
</div>
<div id="new-item">
<input
type="text"
id="new-item-name"
placeholder="Add new item..." />
</div>
<hr />
<div id="done-items">
<!-- Items which are checked -->
</div>
</div>
</body>
</html>
Modifying HTML
To add items to our list, we’ll need to add HTML from JS.
This can be achieved by using the document.createElement
and
element.appendChild
methods. See the built-ins
list in the start of
this chapter.
To create a new element visible to the user, we have to:
- Create the element in JS
- Optionally change it’s parameters, such as
innerText
,id
, etc. - Add it to a already existing element, such as the document body (which can be
accessed with
document.body
)
Connect JavaScript
To start changing the HTML, we need references to the elements we are going to use.
We are going to declare variables which contain references to the to-do-items
container, the new-item-name
text input, and the done-items
container.
Add a <script>
just before the end of the <body>
tag. In said script tag,
declare a variable (in the example below, I used todoItems
) set to a reference
to the element with id to-do-items
. Do the same for all other elements
mentioned above.
Your document should now look something like this.
...
<body>
...
<script>
let todoItems = document.getElementById("to-do-items")
let newItemName = document.getElementById("new-item-name")
let doneItems = document.getElementById("done-items")
</script>
</body>
Generate content
Let’s try to add a new item to the to-do-items
container.
Keep in mind we are working inside the script tag after the
list
container, before the closing</body>
tag.
Declare a function which takes a argument named value
. In the function, add
this.
// Create the item element
let item = document.createElement("div")
// Add a class to it. Classes are groups of elements which you can apply styles to.
item.classList.add("item")
let name = document.createElement("input")
// Set the input type to text
name.type = "text"
// Placeholder when the field is empty
name.placeholder = "Item"
// Set it's value to the argument of the function
name.value = value
let completed = document.createElement("input")
// The type of this input is a checkbox
completed.type = "checkbox"
// Span is a container, like div, but for inline elements; it won't be a separate line.
let remove = document.createElement("span")
// Set it's text
remove.innerText = "✘"
remove.classList.add("remove")
// This is to make it tabbable; so it's a stop when you press tab on the website. Important for assistive technologies.
remove.setAttribute("tabindex", "0")
// Append all the elements in order to the item div
item.appendChild(name)
item.appendChild(completed)
item.appendChild(remove)
// Append the div to the todoItems container
todoItems.appendChild(item)
// Return a reference to input.
return name
This function returns a refernce to the input element of the new item.
After the function, call it. I assume it’s name is addItem.
// This variable declaration isn't needed. You can remove the `let newItem = ` if you aren't going to use it.
let newItem = addItem("Tell my friends about the great resource icelk.dev!")
// Focus on the element
newItem.focus()
This should now have added a new item!
Remove the template item from your HTML; it does not have any functionality that we added in the JS with the event listeners, we never added them to the template item.
The inside of your script tag should now look like this, but perhaps with some comments.
let todoItems = document.getElementById("to-do-items")
let newItemName = document.getElementById("new-item-name")
let doneItems = document.getElementById("done-items")
function addItem(value) {
let item = document.createElement("div")
item.classList.add("item")
let name = document.createElement("input")
name.type = "text"
name.placeholder = "Item"
name.value = value
let completed = document.createElement("input")
completed.type = "checkbox"
let remove = document.createElement("span")
remove.innerText = "✘"
remove.classList.add("remove")
remove.setAttribute("tabindex", "0")
item.appendChild(name)
item.appendChild(completed)
item.appendChild(remove)
todoItems.appendChild(item)
return name
}
let newItem = addItem("Tell my friends about the great resource icelk.dev!")
newItem.focus()
Tying it all together
The logic of the to-do list can be split into multiple funcitons (as in I have a funciton, not JS functions).
- When the user starts typing a new item (in the
new-item-name
input), make a new item and refocus the user input to the new item’s input field. - Make the remove buttons work.
- Move item when it’s checked or unchecked.
- Disable editing of items in the
done-items
container. - Most importantly; add some sweet visuals!
Things we will not do in this tutorial:
- Drag and drop to change order
- Sub-items
To add a new item when the user starts writing in the input, we’ll have to
listen for the input
event.
Add this outside the function, in the same identation level (spaces left of the code).
Here we use the
() => { code... }
syntax instead offunction () { code... }
. It’s essentially the same, but the first is cleaner. You can add arguments in the parentheses()
in both.
// Listen on the `input` event (which is called every time the user entered something)
newItemName.addEventListener("input", () => {
// Add a new item with the name of the input field that changed
let newItem = addItem(newItemName.value)
// Set the `newItemName` value to be nothing. We "moved" the text to another input.
newItemName.value = ""
// Focus the new input; the user will get moved of the `newItemName` immediately.
newItem.focus()
})
To make the remove button work, lets add this after the
let remove = document.createElement("span")
.
remove.addEventListener("click", () => {
item.remove()
})
We’ll also add some style to make the cursor (mouse pointer on the screen)
signal the <span>
is clickable.
Add a <style>
tag at the end of the head. This should be inside the style tag.
.remove {
cursor: pointer;
}
This sets the cursor type of all elements with the class remove
(which we
added when we created the remove element).
Next, we’ll move the item element from the to-do-items
container to the
done-items
container.
Add this after the completed
element creation. The order doesn’t really
matter, but it looks better when we change things on one element close to other
changes.
completed.addEventListener("change", () => {
// This `.checked` property only exists on checkbox input types.
let done = completed.checked
// Reomve the item element from it's parent element.
item.parentElement.removeChild(item)
// If the item is done (the checkbox is checked), we
if (done) {
// Add it to the doneItems contaner
doneItems.appendChild(item)
} else {
// Else, add it to the todoItems container.
todoItems.appendChild(item)
}
})
Now, let’s remove the ability to remove or change the item once it’s done.
Add the following code to the end of the last event listener we added.
name.disabled = done
remove.classList.toggle("disabled")
This disables the input (name.disabled
) and adds the class disabled
to the
remove element.
We’ll need to check if the remove class is present on the remove
element
before we remove it; else, it’ll always be removed.
Replace the event listener of the remove
element with this. Note that we only
added a if
surrounding the removal.
if (!remove.classList.contains("disabled")) {
item.remove()
}
To signal to the user that the disabled remove button is disabled, and not just
ignore the input, let’s add this CSS in the <style>
tag you added before.
Make sure to add it as a new rule (the blocks with the thing to act on and the style to apply).
.remove.disabled {
/* Makes the remove button less opaque */
opacity: 0.5;
/* Makes the cursor be a "not-allowed" style */
cursor: not-allowed;
}
Next, let’s add some nice visuals. Here, the explanations are entirely in the code, as comments. This is how you would do it in real life.
We’ve added the attribute (key-value pairs inside the starting tag, after the
tag name; <tagName attribute="value">
) id
to some elements. An id identifies
an element (shocking… i know.) Ids can be used in three main ways.
- First is to get a reference to the element, as we’ve done several times in JS.
- The second thing we can do is to apply styles to a specific object. This
should only be used when you only need to target one element, ever, with the
styles. You can target this by using this syntax:
#id { styles... }
. - Third, you can make the browser go down to where they are located on the
page. Let’s say we have heading with id
resources
onhttps://icelk.dev/
. If we want to share that part of the site, we can give our friends the linkhttps://icelk.dev/#resources
; a hash#
followed by the id added to the URL.
This code should be copy and pasted to the end of the style tag.
.item {
/* Increase the size of the item. Font-size is the general size of a element; if the text increases in size, everything else does. */
/* An em is 100% of the current font size, so 1.3em = 130%. A rem is the root em, the size of the default font size.
It's good to specify margins and paddings with rem if you don't want spacing to increase with font size. */
font-size: 1.3em;
/* Padding is space between the content inside the item and the border where the background color ends. */
padding: 0.2rem;
/* This is the space between the borders of the items */
margin: 0 0.5rem;
/* This fixes a lot of issues. This and the next line centers the elements in the item vertically */
display: flex;
align-items: center;
}
/* We can use the > to specify children. See this web page for more details about the so called selectors. */
/* https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors */
.item > input[type="text"] {
/* Sets inputs of type text to try to occupy 100% width. */
/* This doesn't take the whole screen because of the #list rule below. */
/* It also doesn't take the whole width of the item because of the `display: flex;` above. It's useful! */
width: 100%;
}
.item > input[type="checkbox"] {
/* The checkbox doesn't like the `font-size` (it doesn't change anything), so we'll set the height and width manually. */
/* It's 1.5 and not 1.3 because of the padding of the text input, which doesn't apply here. I know, confusing. It's easiest to just play around. */
height: 1.5em;
width: 1.5em;
/* This disabes automatically shrinking due to the parent (.item) being of type flex. */
flex-shrink: 0;
}
.item > input {
/* Make sure the font-size is inherited from the parent .item element. This is needed as browsers force a font size on input elements. We revert that here. */
font-size: inherit;
padding: 0.1rem;
}
.item > .remove {
/* Makes the font size of the remove button 120% of it's parent. Now, it's 1.3 * 1.2 size, because the parent (.item) has a 1.3em font size. */
font-size: 1.2em;
}
.item > * {
/* Add a margin to all .item's children */
margin: 0 0.2rem;
}
body {
/* Center the lists on the body with flex and justify-content */
/* Experiment with justify-content and align-items to see which works. I don't know, so trying my way forward is helpful. */
/* You can also search on `css flex justify-content` and click on the `developer.mozilla.org` search result. There you can find all the info. */
display: flex;
/* We want it to be a column of items. */
flex-direction: column;
align-items: center;
/* Set the colours */
background-color: black;
color: white;
}
/* This specifies the element with id `list` */
#list {
/* Make the list's width whatever is the maximum of 100% and 10rem */
width: max(10rem, 50%);
/* Add some padding */
padding: 0.75rem;
}
My whole document now looks like this. Yours doesn’t need to exactly match, this is only here as a guide to compare to if something went wrong.
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>To-do list</title>
<style>
.remove {
cursor: pointer;
}
.remove.disabled {
opacity: 0.5;
cursor: not-allowed;
}
.item {
font-size: 1.3em;
padding: 0.2rem;
margin: 0 0.5rem;
display: flex;
align-items: center;
}
.item > input[type="text"] {
width: 100%;
}
.item > input[type="checkbox"] {
height: 1.5em;
width: 1.5em;
flex-shrink: 0;
}
.item > input {
font-size: inherit;
padding: 0.1rem;
}
.item > span {
font-size: 1.2em;
}
.item > * {
margin: 0 0.2rem;
}
body {
display: flex;
flex-direction: column;
align-items: center;
background-color: black;
color: white;
}
#list {
width: max(10rem, 50%);
padding: 0.75rem;
}
</style>
</head>
<body>
<div id="list">
<div id="to-do-items">
<!-- Items -->
</div>
<div id="new-item" class="item">
<input
type="text"
id="new-item-name"
placeholder="Add new item..." />
</div>
<hr />
<div id="done-items">
<!-- Items which are checked -->
</div>
<script>
let todoItems = document.getElementById("to-do-items")
let newItemName = document.getElementById("new-item-name")
let doneItems = document.getElementById("done-items")
newItemName.addEventListener("input", () => {
let newItem = addItem(newItemName.value)
newItemName.value = ""
newItem.focus()
})
function addItem(value) {
let item = document.createElement("div")
item.classList.add("item")
let name = document.createElement("input")
name.type = "text"
name.placeholder = "Item"
name.value = value
let completed = document.createElement("input")
completed.type = "checkbox"
completed.addEventListener("change", () => {
// This `.checked` property only exists on checkbox input types.
let done = completed.checked
item.parentElement.removeChild(item)
if (done) {
doneItems.appendChild(item)
} else {
todoItems.appendChild(item)
}
name.disabled = done
remove.classList.toggle("disabled")
})
let remove = document.createElement("span")
remove.innerText = "✘"
remove.classList.add("remove")
remove.setAttribute("tabindex", "0")
remove.addEventListener("click", () => {
if (!remove.classList.contains("disabled")) {
item.remove()
}
})
item.appendChild(name)
item.appendChild(completed)
item.appendChild(remove)
todoItems.appendChild(item)
return name
}
</script>
</div>
</body>
</html>
It should now work. Congratulations!
Iterative improvement: S#¡t, I reloaded. Let’s save our lists!
If you reload, the list isn’t saved!
Let’s fix this by saving the list locally (on the users computer). You’ve maybe
heard of cookies (specially if you live in the EU with GDPR…) which is a way
for the web server to store info on the client. We want to store data on the
client from JS. Here, localStorage
is a great choice.
The localStorage
API (application programmable interface, what we’ve called
built-ins, but API can be code from others too) consists of getting and settings
names to values where both are strings.
See this MDN page about the feature.
Now we know how to save the data, but how do we get it?
Getting the data
We have all the items in a container, so we can iterate (for each … do …) over the children of the container and extract the values.
An alternative is to make a list of all items and add a reference to the input to the list when we create a new item. This is faster (as in fractions of milliseconds faster) but more error-prone; if the item is deleted without being removed from the list, we’ll try to get data from a non-existent input. Also, removing items from lists take some time (but this is naturally done when we moved the item, but by the browser).
Today, we’ll go with the first option.
Make a function called saveList
with no arguments, at the end of the script
tag. It should look like the following.
function saveList() {
let todoChildren = todoItems.children
let doneChildren = doneItems.children
// Here, we create a object called data with two properties, a list named todo and another list named done.
let data = {
todo: [],
done: [],
}
// A function within a function to add all values of the inputs of `children` to the `list`
function addNameToList(list, children) {
// This is a loop. Type `for` and press tab to make the loop appear.
// Here, we can't use `children.forEach` because of some weird JS standards.
// let i = 0 declares i to be 0
// the loop will continue as long as `i < children.length`
// (this can be tricky if you remove items from the children list in the loop, then the child count is lowered.)
// `i++` increases i by one at the end of each iteration (each time the code inside is ran).
for (let i = 0; i < children.length; i++) {
// Get a refernce to the child.
// `children` is a array-like object, which means we can access the item at position i with this syntax.
let child = children[i]
// Gets the first child, which is the input
let input = child.firstElementChild
let name = input.value
// `array.push` adds to the end of the list
list.push(name)
}
}
addNameToList(data.todo, todoChildren)
addNameToList(data.done, doneChildren)
// The `data` object now contains all the names.
// To store it as a string, we're going to have to serialize it.
// This can easily be achieved by using `JSON.stringify()`
let string = JSON.stringify(data)
localStorage.setItem("savedList", string)
// Add the contents of `string` after `Saved `. Note the space, we have to specify it manually.
alert("Saved " + string)
return data
}
If you run this (by calling the function after the function), you should see a pop-up containing the data saved.
Remove the alert
line (and it’s comment, if you copy-pasted).
Reading the saved items
For the saved data to have any meaning, we’re going to read it and add items.
See if you can do this with the todo
items. To get a JS object from the
text we saved, use JSON.parse
.
If you want to try this with the
done
items too, remember how we moved the items when pressing thecompleted
checkbox. The same code should be useful here.
For further help with this, consider adding the done items with the
addItem
funciton, then moving each (make sure to move the parent of the returned element; the function returns a reference to the input, we want to remove the item container).
Let’s view my solution.
function loadList() {
let data = JSON.parse(localStorage.getItem("savedList"))
data.todo.forEach((todoItem) => {
addItem(todoItem)
})
data.done.forEach((doneItem) => {
let input = addItem(doneItem)
let itemContainer = input.parentElement
// This moves the item from the `todoItems` container to `doneItems`.
itemContainer.parentElement.removeChild(itemContainer)
doneItems.appendChild(itemContainer)
// This disabled input
input.disabled = true
// When we get a item from a array, the first item has the position `0`.
// Therefore, the checkbox, which is the second item, has a index of 1.
// This makes the checkbox checked.
itemContainer.children[1].checked = true
// Here, we are accessing the third item, with a index of `2`
// This disabled the remove button
itemContainer.children[2].classList.add("disabled")
})
}
loadList()
Note the loadList()
call at the end. This loads the list when the document is
loaded.
To save the list every time we change something, let’s add this code to the item creation function.
// After the creation of `name`
name.addEventListener("input", () => {
saveList()
})
// After the event listener attached to (listening to events from) `completed`
completed.addEventListener("change", () => {
saveList()
})
// After the event listener attached to `remove`
// Note the event here is `click`, not `change` since this isn't a input, just a element.
remove.addEventListener("click", () => {
saveList()
})
And just like that, your todo-list should not be saved. Try writing something and reload. Everything should be there!
This is how my <script>
tag looks now, here for your reference if something
goes wrong.
<script>
let todoItems = document.getElementById("to-do-items")
let newItemName = document.getElementById("new-item-name")
let doneItems = document.getElementById("done-items")
newItemName.addEventListener("input", () => {
let newItem = addItem(newItemName.value)
newItemName.value = ""
newItem.focus()
})
function addItem(value) {
let item = document.createElement("div")
item.classList.add("item")
let name = document.createElement("input")
name.type = "text"
name.placeholder = "Item"
name.value = value
name.addEventListener("input", () => {
saveList()
})
let completed = document.createElement("input")
completed.type = "checkbox"
completed.addEventListener("change", () => {
let done = completed.checked
item.parentElement.removeChild(item)
if (done) {
doneItems.appendChild(item)
} else {
todoItems.appendChild(item)
}
name.disabled = done
remove.classList.toggle("disabled")
})
completed.addEventListener("change", () =>