diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 343f8d4..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.codio b/.codio deleted file mode 100644 index 8b13789..0000000 --- a/.codio +++ /dev/null @@ -1 +0,0 @@ - diff --git a/.eslintrc b/.eslintrc new file mode 100755 index 0000000..f80e258 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,18 @@ +{ + "env": { + "browser": 1 + }, + + "globals": { + "angular": 1 + }, + + "rules": { + "indent": ["error", "tab"], + "quotes": ["error", "single"], + "semi": ["warn", "never"], + "camelcase": ["warn", "always"], + "comma-spacing": ["warn", {"before": false, "after": true}], + "no-mixed-spaces-and-tabs": ["error", "smart-tabs"] + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index 1f51df9..dbcb0e4 --- a/.gitignore +++ b/.gitignore @@ -17,8 +17,8 @@ dist/ downloads/ eggs/ .eggs/ -lib/ -lib64/ +#lib/ +#lib64/ parts/ sdist/ var/ @@ -36,6 +36,13 @@ var/ pip-log.txt pip-delete-this-directory.txt +# NodeJS +node_modules/ + +# CasperJS Testing +basic-math-1.png +basic-math-2.png + # Unit test / coverage reports htmlcov/ .tox/ @@ -72,4 +79,17 @@ Thumbs.db *.mp4 # codio files -*.codio \ No newline at end of file +*.codio +*.c9 + +# lab12 example from book 'jump start bootstrap' +Chapter_4/ + +# lab15 virtual environment +venv/ + +# lab19 +.idea/ + +# bootstrap sample +Chapter_4.zip \ No newline at end of file diff --git a/.settings b/.settings deleted file mode 100644 index bfa0523..0000000 --- a/.settings +++ /dev/null @@ -1,57 +0,0 @@ -[editor] -; enter your editor preferences here... - -[ide] -; enter your ide preferences here... - -[code-beautifier] -; enter your code-beautifier preferences here... - -[view-javascript-code] -; enter your view-javascript-code preferences here... - -[git] -; enter your git preferences here... - -[terminal] -; enter your terminal preferences here... - -[preview] -; enter your preview preferences here... - -[emmet] -; enter your emmet preferences here... - -[search] -; enter your search preferences here... - -[guides] -; enter your guides preferences here... - -[settings] -; enter your settings preferences here... - -[account] -; enter your account preferences here... - -[project] -; enter your project preferences here... - -[codio-bower] -; enter your codio-bower preferences here... - -[install-software] -; enter your install-software preferences here... - -[deployment] -; enter your deployment preferences here... - -[container] -; enter your container preferences here... - -[sync-structure] -; enter your sync-structure preferences here... - -[Codio Lexikon] -; enter your Codio Lexikon preferences here... - diff --git a/00 What is the Web/README.md b/00 What is the Web/README.md new file mode 100755 index 0000000..9926ac2 --- /dev/null +++ b/00 What is the Web/README.md @@ -0,0 +1,18 @@ +# What is the Web? + +## Task Description + +Organise yourselves into groups of 3. + +Answer the following questions: + +1. What is the Internet? +2. What is the World Wide Web? +3. Describe the role(s) of a Web Developer within a business. +4. What lies in the future for the WWW? + +## Results + +You will be given 1 hr to research and discuss these questions. Groups will then randomly be called on to present their answer. + +Each question will only be answered once, but comments will be called on from the other groups. diff --git a/01 Codeanywhere IDE/.md_images/adding_codeanywhere_container.png b/01 Codeanywhere IDE/.md_images/adding_codeanywhere_container.png new file mode 100755 index 0000000..d880dc3 Binary files /dev/null and b/01 Codeanywhere IDE/.md_images/adding_codeanywhere_container.png differ diff --git a/01 Codeanywhere IDE/.md_images/adding_codeanywhere_project.png b/01 Codeanywhere IDE/.md_images/adding_codeanywhere_project.png new file mode 100755 index 0000000..3bd37f3 Binary files /dev/null and b/01 Codeanywhere IDE/.md_images/adding_codeanywhere_project.png differ diff --git a/01 Codeanywhere IDE/.md_images/adding_github_repo.png b/01 Codeanywhere IDE/.md_images/adding_github_repo.png new file mode 100755 index 0000000..ee6a8c8 Binary files /dev/null and b/01 Codeanywhere IDE/.md_images/adding_github_repo.png differ diff --git a/01 Codeanywhere IDE/.md_images/adding_new_repo.png b/01 Codeanywhere IDE/.md_images/adding_new_repo.png new file mode 100755 index 0000000..c53b7c6 Binary files /dev/null and b/01 Codeanywhere IDE/.md_images/adding_new_repo.png differ diff --git a/01 Codeanywhere IDE/.md_images/changing_profile.png b/01 Codeanywhere IDE/.md_images/changing_profile.png new file mode 100755 index 0000000..72c0974 Binary files /dev/null and b/01 Codeanywhere IDE/.md_images/changing_profile.png differ diff --git a/01 Codeanywhere IDE/.md_images/codeanywhere_container_settings.png b/01 Codeanywhere IDE/.md_images/codeanywhere_container_settings.png new file mode 100755 index 0000000..e4ad591 Binary files /dev/null and b/01 Codeanywhere IDE/.md_images/codeanywhere_container_settings.png differ diff --git a/01 Codeanywhere IDE/.md_images/codeanywhere_ide.png b/01 Codeanywhere IDE/.md_images/codeanywhere_ide.png new file mode 100755 index 0000000..4da7137 Binary files /dev/null and b/01 Codeanywhere IDE/.md_images/codeanywhere_ide.png differ diff --git a/01 Codeanywhere IDE/.md_images/codeanywhere_open_ssh.png b/01 Codeanywhere IDE/.md_images/codeanywhere_open_ssh.png new file mode 100755 index 0000000..b72f596 Binary files /dev/null and b/01 Codeanywhere IDE/.md_images/codeanywhere_open_ssh.png differ diff --git a/01 Codeanywhere IDE/.md_images/codeanywhere_preview_html.png b/01 Codeanywhere IDE/.md_images/codeanywhere_preview_html.png new file mode 100755 index 0000000..5760c6f Binary files /dev/null and b/01 Codeanywhere IDE/.md_images/codeanywhere_preview_html.png differ diff --git a/01 Codeanywhere IDE/.md_images/folder.png b/01 Codeanywhere IDE/.md_images/folder.png new file mode 100755 index 0000000..a6f8475 Binary files /dev/null and b/01 Codeanywhere IDE/.md_images/folder.png differ diff --git a/01 Codeanywhere IDE/.md_images/github_clone_lab.png b/01 Codeanywhere IDE/.md_images/github_clone_lab.png new file mode 100755 index 0000000..f7a9ccf Binary files /dev/null and b/01 Codeanywhere IDE/.md_images/github_clone_lab.png differ diff --git a/01 Codeanywhere IDE/.md_images/github_new_repo_full.png b/01 Codeanywhere IDE/.md_images/github_new_repo_full.png new file mode 100755 index 0000000..2461d2b Binary files /dev/null and b/01 Codeanywhere IDE/.md_images/github_new_repo_full.png differ diff --git a/01 Codeanywhere IDE/.md_images/github_switch_org.png b/01 Codeanywhere IDE/.md_images/github_switch_org.png new file mode 100755 index 0000000..08f347a Binary files /dev/null and b/01 Codeanywhere IDE/.md_images/github_switch_org.png differ diff --git a/01 Codeanywhere IDE/.md_images/new_repo_url.png b/01 Codeanywhere IDE/.md_images/new_repo_url.png new file mode 100755 index 0000000..5ca2e9c Binary files /dev/null and b/01 Codeanywhere IDE/.md_images/new_repo_url.png differ diff --git a/01 Codeanywhere IDE/.md_images/preview.png b/01 Codeanywhere IDE/.md_images/preview.png new file mode 100755 index 0000000..8706376 Binary files /dev/null and b/01 Codeanywhere IDE/.md_images/preview.png differ diff --git a/01 Codeanywhere IDE/.md_images/preview_html.png b/01 Codeanywhere IDE/.md_images/preview_html.png new file mode 100755 index 0000000..8798373 Binary files /dev/null and b/01 Codeanywhere IDE/.md_images/preview_html.png differ diff --git a/01 Codeanywhere IDE/.md_images/profile_page.png b/01 Codeanywhere IDE/.md_images/profile_page.png new file mode 100755 index 0000000..a48c079 Binary files /dev/null and b/01 Codeanywhere IDE/.md_images/profile_page.png differ diff --git a/01 Codeanywhere IDE/.md_images/running.png b/01 Codeanywhere IDE/.md_images/running.png new file mode 100755 index 0000000..a697d98 Binary files /dev/null and b/01 Codeanywhere IDE/.md_images/running.png differ diff --git a/01 Codeanywhere IDE/.md_images/vbox.png b/01 Codeanywhere IDE/.md_images/vbox.png new file mode 100755 index 0000000..50d1a42 Binary files /dev/null and b/01 Codeanywhere IDE/.md_images/vbox.png differ diff --git a/01 Codeanywhere IDE/.md_images/viewing_org.png b/01 Codeanywhere IDE/.md_images/viewing_org.png new file mode 100755 index 0000000..8d637d0 Binary files /dev/null and b/01 Codeanywhere IDE/.md_images/viewing_org.png differ diff --git a/01 Codeanywhere IDE/README.md b/01 Codeanywhere IDE/README.md new file mode 100755 index 0000000..9ca863a --- /dev/null +++ b/01 Codeanywhere IDE/README.md @@ -0,0 +1,386 @@ + +# The Codeanywhere IDE + +Nowadays everything is on the cloud. This includes your integrated development environment (IDE). In this module, we'll use [Codeanywhere](https://codeanywhere.com) as our platform to learn web development. Compared with traditional IDEs such as [Aptana Studio](http://www.aptana.com/) cloud-based tools such as C9 offers the following advantages, among many others: + +* Minimized maintenance efforts. +* Better access your code, virtually from everywhere. +* Easier to share and collaborate. + +> As a programmer we don't normally share photos like most others do, we share code instead. This is because, as said by [Linus Torvalds](https://en.wikipedia.org/wiki/Linus_Torvalds), 'talk is cheap, show me the code'. + +But the drawback is obvious -- you need to be connected. So my advice is KEEP BACKING UP! + +## Setting up Codeanywhere + +### Create (and delete) a workspace + +Visit https://codeanywhere.com and create an account if you haven't got one. After signing in you should see something similar to this. The difference will be that you won't already have a project. + +![](.md_images/adding_codeanywhere_project.png) + +This is called the Dashboard, and it's where different workspaces (projects) are being managed. Click on the plus sign in the big grey square to create a new _project_ and name it `CU`. Now you can click on the **Open Project** link where you will be asked to add a **container**. Scroll down and select the _HTML container running on Ubuntu_ and name it `205CDE` as shown in the screenshot below. + +![](.md_images/adding_codeanywhere_container.png) + +As soon as this has been completed, the container will open and you will see the IDE. Our first task is to open the **SSH Terminal** by right-clicking on the **205CDE** container and choosing the first option from the context menu. This will allow us to run bash commands directly in Codeanywhere. + +![](.md_images/codeanywhere_open_ssh.png) + +### Cloning the Lab Materials + +You will be working on the lab exercises using the Codeanywhere IDE so the next step is to locate the materials on the University GitHub server and clone these into your IDE. + +Start by logging into https://github.coventry.ac.uk using your University username and password. Once you are logged in, access your profile screen using the menu option shown below. + +![](.md_images/changing_profile.png) + +On the profile screen, upload a _head and shoulders_ photo of yourself. + +Check the exact **name** and **email address** associated with your account, this will come from the University's active directory system. make a note of this for later. + +![](.md_images/profile_page.png) + +Now you will need to switch to the 205CDE organisation as shown. + +![](.md_images/github_switch_org.png) + +To see the repositories in the organisation you need to click on the View organisation button in the top-right of the screen as shown. + +![](.md_images/viewing_org.png) + +As you can see there is a repository called `TEACHING-MATERIALS` which contains all the lab materials you will need in this module. Click on this repository title to see the contents. + +![](.md_images/github_clone_lab.png) + +Notice that the GitHub _remote repository_ has a URL which we will use to _clone_ (copy) it into our Codeanywhere IDE. Clicking on the green **Clone or Download** button and the clipboard icon will copy the url into the computer clipboard. + +We will use the SSH Terminal in Codeanywhere to clone these materials into our IDE by running the git command shown below. **Note the period (full-stop) at the end** which will clone the repository into the _current directory_ instead of creating a new directory under it. +``` +$ git clone -o upstream https://github.coventry.ac.uk/205CDE-1617JANMAY/TEACHING-MATERIALS.git . +``` +After a few minutes the cloning will be complete. You will need to refresh the file tree in Codeanywhere by right-clicking on the `205CDE` container name and choosing _refresh_ from the context menu. + +![](.md_images/codeanywhere_ide.png) + +### Updating the Git Profile + +Remember earlier where we identified the name and email address assigned to our University GitHub profile. Now we need to update the Git profile on Codeanywhere to match these settings. In the SSH Terminal enter the following commands, substituting your own name and email address as they appear in your GitHub profile. First we check the current values and change them only if needed. +``` +git config user.name +git config user.name "Mark Tyers" +git config user.name +git config user.email +git config user.email "aa7401@coventry.ac.uk" +git config user.email +``` + +### Adding a New Remote Repository + +As we work through the lab exercises, making changes to our code we will need to ensure this is backed up. The _TEACHING-MATERIALS_ repository is not writable (for obvious reasons) so we will need to add a new remote. Make sure you are in the 205CDE organisation on GitHub then click on the green **New repository** button. + +![](.md_images/adding_github_repo.png) + +We can now create our own _private_ repository called `USERNAME-worksheets` as shown, replacing USERNAME with your _university username_. Click on the **Create repository** button when done. + +![](.md_images/adding_new_repo.png) + +Let's use the _Quick Setup_ option by clicking on the small clipboard icon to copy our repository URL to the clipboard. + +![](.md_images/new_repo_url.png) + +Now we can add this as a second remote called `origin` by entering the following in the SSH Terminal in Codeanywhere, substituting the URL in the clipboard where shown. Once this is completed we can list the remotes we have added and finally push all the commits to our new remote. +``` +$ git remote add origin +$ git remote -v +$ git push origin --all +``` +If we refresh the GitHub webpage for our new repository we should see an exact copy of the original one. + +![](.md_images/github_new_repo_full.png) + +### Cacheing Git Credentials + +You have probably noticed that to push to your remote repository you needed to enter your username and password. For a one-off this is not an issue but with our current settings this will have to be entered every time you push which can get annoying. To make things easier you can set up a _credential cache_ which will cache your username and password. The timeout is the number of seconds to cache the credentials for. In this example they will be cached for an hour. +``` +git config --global credential.helper 'cache --timeout=3600' +``` + +### Previewing a Web Page + +Since the IDE is cloud-based we can use the built-in Apache web server to view our web pages through an online URL. Let's have a go at this. Start by navigating to the Codeanywhere directory, right-clicking on the `index.html` file and choosing **Preview** from the context menu. + +![](.md_images/preview_html.png) + +This will open the web page in a new browser tab. Notice the URL. What happens when you click on the button? + +![](.md_images/preview.png) + +## Basic Linux Commands + +Behind the scene, Codeanywhere is based on Linux technology and specifically it's Ubuntu 14.04 at the time of writing (Jan 2017). This can be verified if you issue the following command in the terminal window: + +```sh +$ lsb_release -a +No LSB modules are available. +Distributor ID: Ubuntu +Description: Ubuntu 14.04 LTS +Release: 14.04 +Codename: trusty +``` +Basically, what happens when you create a Workspace in Codeanywhere the system creates a virtual machine for you using Ubuntu 14.04. You need to know some basic command line operations if you work with Codeanywhere or web technologies in general. + +> Other popular Linux OSs include Fedora and openSUSE. For a comprehensive list, read [Put the fun back into computing from Distrowatch.com](http://distrowatch.com). + +### Navigate the filesystem + +The Console window (also called terminal or shell) is a way we interact with the virtual machine that hosts our workspace. And probably it's the most effective way. + +> They say that [Jeff Dean](https://en.wikipedia.org/wiki/Jeff_Dean_(computer_scientist)) uses just two keys '0' and '1' to talk to the console, which is probably not true. [Read more from Quora.com](https://www.quora.com/What-are-all-the-Jeff-Dean-facts). +> + +Use the following command to see where you are in the system (pwd = print working directory) + +```sh +$ pwd +/home/cabox/workspace +``` +To see what files we have here + +```sh +$ ls +00 What is the Web 09 JS modules +01 C9 IDE 10 Document Object Model +01 Codeanywhere IDE 11 JS testing +02 Git 12 Bootstrap +03 Introduction to HTML5 13_14 Database +04 HTML5 forms 15_16 Python +05 Introduction to CSS3 17_18_Flask_templates +06 Advanced CSS3 19_20_Flask_SQLite +07 JS syntax README.md +08 JS function and objects +``` + +The `ls` command can have different options in the form `ls -a -l` or `ls -al`. Here option `-a` tells the system to show hidden files/folders, and `-l` is the flag for the long listing format. In the outputs, the single dot `.` represent the current directory and the double dot `..` represents the parent directory. Names that begin with a period `.` are hidden files/folders, which don’t normally show up. + +```sh +$ ls -al +total 100 +drwxrwxr-x 21 cabox cabox 4096 Jan 15 11:03 . +drwxr-xr-x 11 cabox cabox 4096 Jan 15 10:38 .. +-rw-r--r-- 1 cabox cabox 350 Jan 15 10:21 .eslintrc +drwxr-xr-x 8 cabox cabox 4096 Jan 15 12:41 .git +-rw-r--r-- 1 cabox cabox 1132 Jan 15 10:21 .gitignore +drwxr-xr-x 2 cabox cabox 4096 Jan 15 10:21 00 What is the Web +drwxr-xr-x 4 cabox cabox 4096 Jan 15 10:21 01 C9 IDE +drwxr-xr-x 3 cabox cabox 4096 Jan 15 12:50 01 Codeanywhere IDE +drwxr-xr-x 4 cabox cabox 4096 Jan 15 10:21 02 Git +drwxr-xr-x 4 cabox cabox 4096 Jan 15 10:21 03 Introduction to HTML5 +drwxr-xr-x 4 cabox cabox 4096 Jan 15 10:21 04 HTML5 forms +drwxr-xr-x 4 cabox cabox 4096 Jan 15 10:21 05 Introduction to CSS3 +drwxr-xr-x 4 cabox cabox 4096 Jan 15 10:21 06 Advanced CSS3 +drwxr-xr-x 4 cabox cabox 4096 Jan 15 10:21 07 JS syntax +drwxr-xr-x 3 cabox cabox 4096 Jan 15 10:21 08 JS function and objects +drwxr-xr-x 3 cabox cabox 4096 Jan 15 10:21 09 JS modules +drwxr-xr-x 3 cabox cabox 4096 Jan 15 10:21 10 Document Object Model +drwxr-xr-x 4 cabox cabox 4096 Jan 15 10:21 11 JS testing +drwxr-xr-x 4 cabox cabox 4096 Jan 15 10:21 12 Bootstrap +drwxr-xr-x 4 cabox cabox 4096 Jan 15 10:21 13_14 Database +drwxr-xr-x 4 cabox cabox 4096 Jan 15 10:21 15_16 Python +drwxr-xr-x 4 cabox cabox 4096 Jan 15 10:21 17_18_Flask_templates +drwxr-xr-x 4 cabox cabox 4096 Jan 15 10:21 19_20_Flask_SQLite +-rw-r--r-- 1 cabox cabox 5508 Jan 15 10:21 README.md +``` + +Use the following command to navigate to the `01 Codeanywhere IDE` folder and see what's in there. Notice that all spaces need to be _escaped_ by preceeding them with a forward-slash (\) character. + +```sh +$ cd 01\ Codeanywhere\ IDE/ +$ ls -a -l +total 36 +drwxr-xr-x 3 cabox cabox 4096 Jan 15 12:50 . +drwxrwxr-x 21 cabox cabox 4096 Jan 15 11:03 .. +drwxr-xr-x 2 cabox cabox 4096 Jan 15 12:56 .md_images +-rw-r--r-- 1 cabox cabox 16932 Jan 15 13:02 README.md +-rw-r--r-- 1 cabox cabox 1272 Jan 15 12:50 index.html +``` + +> By default, the command prompt changes if you change the current directory. + +Use the following command to navigate to the root of the file system + +```sh +$ cd / +$ pwd +/ +``` + +Now we're at the root of the file system. If you issue `ls` command again you'll see different folders in the system + +```sh + $ ls +bin dev fastboot lib media opt root sbin sys usr +boot etc home lib64 mnt proc run srv tmp var +``` +> The system arranges so that the display goes vertical first and then horizontal. + +Things are getting interesting from here. All these folders are for different purposes. Here list a few important ones, for a complete list [read a blog article written by Peter Upfold](https://peter.upfold.org.uk/blog/2006/07/18/a-guide-to-files-and-folders-on-linux/) + +* bin is for binaries, in other words, programmes, executables. +* etc is for configuration files e.g. Apache configuration. +* home is where your personal stuff goes e.g. workspace. +* var is for application data such as SQL database. + +Since we have Apache server installed, if we do the following we'll see how different parts of a single package are separated into different folders + +```sh +$ dpkg -L apache2 +/. +/usr +/usr/share +...... +/etc/apache2/sites-available/default-ssl.conf +/etc/init.d +/etc/init.d/apache2 +/etc/ufw +/etc/ufw/applications.d +...... +/var/log/apache2 +/var/www +/var/www/html +/var/cache +...... +``` + +### Handle files and permissions + +Regarding file operations you need to know how to create, copy, rename, and delete. The Linux commands corresponding to these are `touch`, `cp`, `mv`, and `rm`. + +Copy the Apache server configuration file to your local directory + +```sh +$ cd ~/workspace/ +$ cp /etc/apache2/apache2.conf apache2.conf +``` +Now you'll see that you have a new file named apache2.conf in your workspace. You can now double click to edit in the IDE. + +The following commands will create a new file and then delete it. + +```sh +$ cp apache2.conf apache2.conf_new +$ ls +apache2.conf apache2.conf_new +$ rm apache2.conf_new +$ ls +apache2.conf +``` + +Now issue the following commands and see the differences in the outputs + +```sh +$ ls -l /etc/apache2/apache2.conf + -rw-r--r-- 1 root root 7115 Jan 7 2014 /etc/apache2/apache2.conf +$ ls -l apache2.conf +-rw-r--r-- 1 ubuntu ubuntu 7115 Jan 14 00:18 apache2.conf +$ id +uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),27(sudo),1001(rvm) +``` + +Unlike Windows, files and folders in Linux have owners and permissions. (Windows do too, but not in an obvious way). You can see even though we copied the file from a system folder (and everything should be exactly the same), the owner has been changed. + +For both files, the permissions are `-rw-r--r--`. This means that for the three positions in the order of the owner, group, others, the permissions are read/write `rw`, read only `r` and read only `r`. But the owner has been changed from root to ubuntu. + +The command `id` tells us that the current user (*you*, that is) has been assigned a username `cabox`. + +Run the following commands to change some permissions of the file + +```sh + $ chmod go-r apache2.conf + $ ls -l apache2.conf + -rw------- 1 cabox cabox 7115 Jan 14 00:18 apache2.conf + $ chmod u+x apache2.conf + $ ls -l apache2.conf + -rwx------ 1 cabox cabox 7115 Jan 14 00:18 apache2.conf* +``` + +Now that the read `r` permission has been removed from group `g` and others `o`, and execution has been added to the owner. + +> A shortcut to the example above is to use the number system `chmod 700 apache2.conf`. [Read more explanation and examples on Linux file permissions from Linux.com](https://www.linux.com/learn/tutorials/309527-understanding-linux-file-permissions). + +## What if Codeanywhere is down? + +OK, this is unlikely. But if you are a worrier (like myself) you always want a plan B. Then this is what you need to follow. Note that this will allow you to edit the code you have committed and pushed to your remote. Any code not pushed won't be available until Codeanywhere is back up and running. + +### Work Locally + +If you plan on working locally you will need to have a modern code editor installed. My personal choice is Microsoft's Visual Studio Code which is a free download for Linux, Mac and Windows computers. + +Launch Visual Studio Code and open the Integrated Terminal from the View menu. + +Navigate to your documents directory then run the git clone command on your project on the GitHub server. This will give you a local copy of your repository code to work on. + +Now you're ready to start editing. + +## Advanced: set up local web servers + +Now you can edit and preview HTML files locally. But you'll need to setup a web server to view dynamic contents. You don't need it right now, but you'll need it later during the module. + +Unfortunately, the university policy doesn't allow you to install any software on ITS machines yourself, so you'll have to do it on your own machines. But it's important you install and configure a web server yourself. + +Follow instructions below to install a web server yourself: + +1. Install Ubuntu: + + Depending on if you use the Chromebook regularly (comfortable) or not, you have two options: + * Option 1: Open your Chromebook, follow instructions given on [How-To Geek: How to Install Ubuntu Linux on Your Chromebook with Crouton](http://www.howtogeek.com/162120/how-to-install-ubuntu-linux-on-your-chromebook-with-crouton/) to install Ubuntu on a Chromebook. You might also want to follow this guide [Things to do after installing Ubuntu on a Chromebook using Crouton](http://www.webupd8.org/2013/12/things-to-do-after-installing-ubuntu-on.html) + * Option 2: Open your Windows machine, install [VirtualBox](https://www.virtualbox.org) and then install [Ubuntu](http://www.ubuntu.com) within it. A step by step instruction can be found on [wikiHow](http://www.wikihow.com/Install-Ubuntu-on-VirtualBox). + +2. Boot into Ubuntu to install LAMP server. In the example below I use Ubuntu installed on a VirtualBox running on a Mac. But if it's Chromebook or Windows it's essentially the same. + + ```sh + sudo apt-get update + sudo apt-get install lamp-server^ + ``` + + ![](.md_images/vbox.png) + + > [The official Ubuntu documentation on installing LAMP](https://help.ubuntu.com/community/ApacheMySQLPHP) + +3. Now you can put your lab1 stuff in this server and view it in a browser. + + * Download your lab1 material and extract within Ubuntu. For example, mine is extracted at `/home/yang/Downloads/workspace` + + ![](.md_images/folder.png) + + * Open terminal, create a symbolic link to `/var/www/HTML`. A symbolic link basically is a pointer that stays inside `/var/www/HTML` but pointing to the actual location of our lab material. + + ```sh + sudo ln -s /home/yang/Downloads/workspace/ /var/www/html + ``` + + * Restart Apache server. This step may not be necessary, just a precaution. + + ```sh + sudo /etc/init.d/apache2 restart + ``` + + * You can now open a browser and view the file on your local server + + ![](.md_images/running.png) + +4. You have seen previously files installed by in Apache package using `dpkg -L apache2`. Now let's have a little look to understand how to configure Apache. Open the apache2.conf file in C9 or Ubuntu. Have a read through, especially the following block. This refers to the folder we used before. For explanations of these options, refer to the [official Apache documentation](http://httpd.apache.org/docs/2.4/mod/core.html). + + > [Official Ubuntu Apache2 Web Server guide](https://help.ubuntu.com/lts/serverguide/httpd.html) + + ```xml + + Options Indexes FollowSymLinks + AllowOverride None + Require all granted + + ``` + + +> There are quite a few LAMPP or WAMP packages available, some are listed here on [WiKi](https://en.wikipedia.org/wiki/List_of_Apache%E2%80%93MySQL%E2%80%93PHP_packages). Among these my favorites are [XAMPP](https://www.apachefriends.org/index.html) on Windows and [MAMP](https://www.mamp.info/en/) on Mac. + + diff --git a/01 Codeanywhere IDE/index.html b/01 Codeanywhere IDE/index.html new file mode 100755 index 0000000..e500fca --- /dev/null +++ b/01 Codeanywhere IDE/index.html @@ -0,0 +1,62 @@ + + + + + + +Hello... + + + + +
+

?

+ +
+ + + + diff --git a/02 Git/.md_images/fill.png b/02 Git/.md_images/fill.png new file mode 100755 index 0000000..703d4bf Binary files /dev/null and b/02 Git/.md_images/fill.png differ diff --git a/02 Git/.md_images/full.png b/02 Git/.md_images/full.png new file mode 100755 index 0000000..f245567 Binary files /dev/null and b/02 Git/.md_images/full.png differ diff --git a/02 Git/.md_images/github.png b/02 Git/.md_images/github.png new file mode 100755 index 0000000..7db89b5 Binary files /dev/null and b/02 Git/.md_images/github.png differ diff --git a/02 Git/.md_images/source.png b/02 Git/.md_images/source.png new file mode 100755 index 0000000..7e05883 Binary files /dev/null and b/02 Git/.md_images/source.png differ diff --git a/02 Git/.md_images/url.png b/02 Git/.md_images/url.png new file mode 100755 index 0000000..71f3273 Binary files /dev/null and b/02 Git/.md_images/url.png differ diff --git a/02 Git/README.md b/02 Git/README.md new file mode 100755 index 0000000..fa3ca87 --- /dev/null +++ b/02 Git/README.md @@ -0,0 +1,392 @@ +# Git + +Git is a widely-used version control system (VCS), and probably the most popular one. You need Git mainly for three reasons: + +1. The source code of your website forms part of your final assessment. This needs to be submitted through GitHub, which is a website based on Git. +2. Git allows easy backup (and restore) your source code. +3. Git allows easy sharing and collaborating. + +You'll see details of these as you go along. + +> If you are old enough you've probably heard of [Google Code Search](http://web.archive.org/web/20101112131244/http://www.google.com//codesearch), which is a tool that allows programmers to search for source code. Partially due to the increased popularity of Git-base code hosting websites such as Github, this tool has now been shut down. ([http://code.google.com](http://code.google.com) is not the original site anymore.) + +## Work with Git locally + +In Codeanywhere, create a new connection based on HTML template and name it lab2 (you will have to delete the one your created earlier). In terminal type in the following command and press enter. This shows you Git has been installed + +```sh + $ git + usage: git + ...... + The most commonly used git commands are: + add Add file contents to the index + bisect Find by binary search the change that introduced a bug + ...... +``` + +> If you work on your own computers, make sure you download and install [Git](https://git-scm.com/downloads) first. + +Here we are presented with a summary of the available Git commands. + +### Initialise the repository + +In terminal issue the following command to initialise a Git repository. + +```sh + $ git init + Initialized empty Git repository in /home/<>/.git/ +``` + +A Git repository contains a hidden directory called .git which contains all the tracking data. We need to check that there is a repository. Obviously, we can't see it in the file tree (its hidden) so we need to run a command to list all the files, including the hidden ones (`ls` lists the files whilst the `ls -a` flag includes the hidden ones. + +```sh + $ ls -a + . .. .git +``` + +The first file (.) represents the current directory, the second (..) the parent directory and the last (.git) is the Git repository. So in this case, we can see that there is now a repository. + +Now issue the following command to create a file called README.md + +```sh + $ touch README.md + $ ls -a + . .. .git README.md +``` + +### Set user information + +The first time you use the repository you should check that the name and email details are correct. These are used to indicate who has been committing code. Start by displaying the current contents of the git config file: + +``` + $ git config --list + user.name=Jianhua Yang + user.email=(you all know it) + ...... +``` + +You are looking for a single entry for user.name and one for user.email. So if yours are like mine, you're fine. + +There may be additional keys and values stored. If there are multiple names and/or emails you can replace all instances using the following two commands (substituting your own name and email) + +```sh + $ git config --global --replace-all user.email "johndoe@gmail.com" + $ git config --global --replace-all user.name "John Doe" +``` + +> Ever wonder why it's John again, read this official [Getting Started First Time Git Setup](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup). + +### Stage files + +At any stage, you can use the `status` command to check the status of the project files. Try running this now: + +```sh + $ git status +``` + +You will see a message that there are untracked files. These are listed in red. Before committing files, they need to be placed in the staging area using the `add` command + +> You only need to type the first few letters of the filename, press the tab key to auto-complete. + +```sh + $ git add . +``` + +Here `.` means the current directory. + +So how can we check the file has been staged? Remember that status command? + +```sh + $ git status +``` + +You will see that the files have been added to the staging area to create a snapshot. + +### Commit files + +Once we have added one or more files to the staging area and created a snapshot we can commit the changes to our local repository. Each snapshot is accompanied by a message to explain what changes have been made. Before making the commit, it can be useful to see what changes you will be committing. + +```sh + $ git diff --cached + diff --git a/README.md b/README.md + new file mode 100755 + index 0000000..064ecaa + --- /dev/null + +++ b/README.md + @@ -0,0 +1,26 @@ + ...... +``` + +You'll be automatically redirected to a command line text editor. Type letter 'q' at the command prompt and hit enter to get out of it. Use your mouse to scroll up and down in the terminal window. Take a moment to make sense of the results. The three + characters next to the filename indicate that three lines have been changed. Then all the modified lines are listed. A + character at the start of the line indicates the line has been added, a - character indicates the line has been deleted. So we can see that since we haven't committed, all lines are new. + +Now let's commit our changes. Remember that you need to include a descriptive message with every commit. + +```sh + $ git commit -m '1st commit' +``` + +This commits the changes in your staged files to the repository and removes all the files from the staged area. We can check this using the status command: + +```sh + $ git status +``` + +You get two important pieces of information: + +1. nothing to commit (there are no changes to stage) +2. working directory clean (there are no staged files) + +Let's take a look at the commit history. This lists all the commits you have made to the Git repository. + +```sh + $ git log +``` + +### A second commit + +Return to Codeanywhere and open README.md in the editor. Insert today's date and save + +```sh + 30 Jan 2017 +``` + +> You don't have to add the date, this is just to make some changes to the file. + + +Run the status command `git status` to show the current status of the repository. You should see that the README.md file is shown as modified and that no files have been staged. + +Time to stage the README.md file `git add README.md`. If you check the status again you should see the file listed as modified and staged ready for committing. Commit the staged file and add a message '2nd commit' `git commit -m '2nd commit'`. + +Check the status to make sure there are no uncommitted changes and that the working directory is clean. Finally view you commit history where you should see both of the commits you have made. + +### Compare files + +Git creates a unique hash for each commit made to the repository. These are the long alpha-numeric strings displayed in the git log output. There is also a shorter 7 character version which can be shown when you add the abbrev-commit long flag like this: + +```sh + $ git log --abbrev-commit +``` + +We can use these to reference their associated commits when using the diff command which compares different commits of the same file: + +```sh + $ git diff 5a3b6f4..a07144a README.md +``` + +You should see all the changes made to the file between the two specified commits. '+' indicates lines added whilst '-' lines indicate lines deleted, similar to what you've seen before + +### Rename files + +Renaming files is easy, right? Well, perhaps not. Let's see what happens when we rename our file using the standard approach. Right-click on the file in the file tree and choose Rename. Call the file README2.md then run the status command: + +```sh + $ git status +``` + +Notice that git assumes we have deleted the old file (with all its commits) and created a new file. If we stage and commit at this stage we will lose all our commits because git thinks the file is a completely new one! + +To fix this we need to rename the file using a special git command. + +Start by renaming the file back to the original name and running the status command to check there are no changes to the stage. Next, we run the `git mv` command: + +```sh + $ git mv README.md README2.md +``` + +Now run the status command which indicates the filename has been changed. + +```sh + $ git status + On branch master + Changes to be committed: + (use "git reset HEAD ..." to unstage) + + renamed: README.md -> README2.md +``` + +Go ahead, stage and commit this file which will retain its commit history. Use the commit message 'Renamed README file'. + +``` + $ git commit -am 'Renamed README file' +``` + +The `-a` flag ['tell the command to automatically stage files that have been modified and deleted, but new files you have not told Git about are not affected'](https://git-scm.com/docs/git-commit). + +### Unstage files + +Sometimes you may accidentally stage a file and wish to remove it from the staging area. Let's create a new file called notes.md using command `touch notes.md` in the terminal. Edit the file to add some text (anything you want). Modify the README2.md file as well. + +Now add both files to the staging area using `git add .`. If we use status we can see one new and one modified file. + +```sh + modified: README2.md + new file: notes.md +``` + +Perhaps we only wanted to commit the notes file. Let's unstage the README2 file before committing. + +```sh + $ git reset HEAD README2.md +``` + +If you check the status you should see that only the notes file is staged. Commit this file with the message 'Added notes file'. If you check the status you should see that there are still uncommitted changes. + +We have decided we don't want the changes we made to the README file. To undo these changes we checkout the last version we committed to the repository + +```sh + $ git checkout README2.md +``` + +If we examine the file we should see that the changes we made have gone. If we check the repository status we should see that there are no uncommitted changes. + +### Revert to the previous commit + +Let's pretend that we want to 'undo' all the changes we made since the second commit (`git log --abbrev-commit` shows `af2309b` for my 2nd commit). In other words, we want to change our working code to how it was when we made an earlier commit. + +One important feature of Git is that we keep track of all changes through our commits and we can never delete any commits. So how do we revert our code whilst keeping a record of the commits we have made since that point? + +In Git, all commits are made to a branch. By default, this branch is called 'master' but we can have any number of named branches, each with its own commits rather like stations on a rail network. Git uses a special flag called the `` to identify where to make commits. + +To revert to an old commit we need to take the following steps: + +1. move/rename the current branch to reflect its new status. +2. checkout the good commit +3. create a new branch at that point called `master` and move the `` flag to that branch + +```sh + $ git branch -m badcode + $ git checkout af2309b + $ git checkout -b master +``` + +You should see that the notes file has gone and the README file only contains the early edits. Let's make a change to the README file, stage and commit this as 'Final changes to README'. + +```sh + $ git commit -am 'Final changes to README' +``` + +Now we can display the commits and which branch they were applied to: + +```sh + $ git log --graph --all --decorate +``` + +You should see a detailed log with a visual representation of the branches and commits. + +For a version displaying the short commit codes try: + +```sh + $ git log --graph --abbrev-commit --decorate --date=relative --all +``` + +## Work with GitHub remotely + +### GitHub repository + +> The instructions below are for [https://github.com](https://github.com). This is not to be confused with [https://github.coventry.ac.uk](https://github.coventry.ac.uk) + +GitHub is a source code hosting website based on Git. If you haven't got one, create an account and sign in. Click on the plus sign on the top right corner and select 'New repository'. Name it lab2 and click 'Create repository'. + +![](.md_images/github.png) + +Once created, you'll see something similar to below + +![](.md_images/url.png) + +Take a note of the URL of the repository, you'll need it for later. For example, mine is `https://github.com/jianhuayang/lab2.git` + +> Make sure it's https not SSH. + +### Add the remote repository + +To add a new remote, use the `git remote add` command on the terminal, in the directory your repository is stored at. + +The `git remote add` command takes two arguments: + +1. A remote name, for example, `origin` +2. A remote URL, for example, `https://github.com/jianhuayang/lab2.git` + +For example: + +```sh + $ git remote add origin https://github.com/jianhuayang/lab2.git + $ git remote -v + origin https://github.com/jianhuayang/lab2.git (fetch) + origin https://github.com/jianhuayang/lab2.git (push) +``` + +Here the remote is called origin, which is the default name for remotes. Then finally, you want to push all your commits to your own repository located in new url. + +```sh + $ git push origin master +``` + +Enter the GitHub credentials as and when prompted. If you then refresh you GitHub repository you'll see the files are there now + +![](.md_images/fill.png) + +> You can have more than one remotes, or you can remove/reset remotes. Some examples can be found on [the official GitHub guide on manageing remotes](https://help.github.com/categories/managing-remotes/). + +### Synchronize local with remote + +What we have done in the previous step was to `push` our commits onto the remote GitHub server. The opposite of this operation is to `pull` (download) any commits that are on the server but not in your local repository. There're mainly two reasons for this: + +1. You have been editing (and committing) code using two different editors/computers +2. You are not the only person working on the code + +First of all, let's make sure the remote is up to date + +```sh + $ git push origin master + ...... + Everything up-to-date +``` + +Make some changes to README.md, and in the `git status` outputs you'll see + +```sh + modified: README.md +``` + +Use the following commands to ignore local changes and restore everything to the latest commit on the remote GitHub repository + +```sh + $ git reset --hard HEAD + $ git pull origin master +``` + +> There are some nice discussions on StackOverflow about [how to force Git to overwrite local files on pull](http://stackoverflow.com/questions/1125968/force-git-to-overwrite-local-files-on-pull) + +## What if Github is down? + +OK, you got the idea -- there is nothing that stops you from learning (and submitting your coursework)! + +GitHub will never be down! But sometimes it can happen that you cannot visit it. If that happens, use alternatives such as [GitLab](https://gitlab.com) or [Bitbucket](https://bitbucket.org/) + +As an alternative to web-based hosting tools, you can use offline software as well. In the following example, I show you how to clone the GitHub repository using the SourceTree software. + +1. Download and install [SourceTree](https://www.sourcetreeapp.com/) if it's your own computer. On all EC machines, the software has been installed for you. +2. Select New Repository ==> Clone from URL +3. Enter you GitHub repository URL details and click Clone. + + ![](.md_images/source.png) + +4. You should see now the project has been backed up to your local folder. And that you can manage Git through the GUI. In fact this Codeanywhere ==> GitHub ==> SourceTre is a better approach to backup your project rather than downloading directly from Codeanywhere. + + ![](.md_images/full.png) + + + + + + + + + + + + + + + + diff --git a/02 Git/workspace/README.md b/02 Git/workspace/README.md new file mode 100755 index 0000000..cce7090 --- /dev/null +++ b/02 Git/workspace/README.md @@ -0,0 +1,28 @@ + ,-----.,--. ,--. ,---. ,--.,------. ,------. + ' .--./| | ,---. ,--.,--. ,-| || o \ | || .-. \ | .---' + | | | || .-. || || |' .-. |`..' | | || | \ :| `--, + ' '--'\| |' '-' '' '' '\ `-' | .' / | || '--' /| `---. + `-----'`--' `---' `----' `---' `--' `--'`-------' `------' + ----------------------------------------------------------------- + + +Hi there! Welcome to Cloud9 IDE! + +To get you started, we included a small hello world application. + +1) Open the hello-world.html file + +2) Click on the Preview button to open a live preview pane + +3) Make some changes to the file, save, watch the preview, and have fun! + +Happy coding! +The Cloud9 IDE team + + +## Support & Documentation + +Visit http://docs.c9.io for documentation, or http://support.c9.io for support. +To watch some training videos, visit http://www.youtube.com/user/c9ide + +20 Jan 2016 diff --git a/02 Git/workspace/hello-world.html b/02 Git/workspace/hello-world.html new file mode 100755 index 0000000..e500fca --- /dev/null +++ b/02 Git/workspace/hello-world.html @@ -0,0 +1,62 @@ + + + + + + +Hello... + + + + +
+

?

+ +
+ + + + diff --git a/labs/03 HTML5/img/computer.png b/03 Introduction to HTML5/.md_images/computer.png old mode 100644 new mode 100755 similarity index 100% rename from labs/03 HTML5/img/computer.png rename to 03 Introduction to HTML5/.md_images/computer.png diff --git a/03 Introduction to HTML5/.md_images/eow.png b/03 Introduction to HTML5/.md_images/eow.png new file mode 100755 index 0000000..39db776 Binary files /dev/null and b/03 Introduction to HTML5/.md_images/eow.png differ diff --git a/03 Introduction to HTML5/.md_images/hello.png b/03 Introduction to HTML5/.md_images/hello.png new file mode 100755 index 0000000..eb079d0 Binary files /dev/null and b/03 Introduction to HTML5/.md_images/hello.png differ diff --git a/03 Introduction to HTML5/.md_images/img_sem_elements.gif b/03 Introduction to HTML5/.md_images/img_sem_elements.gif new file mode 100755 index 0000000..f56b534 Binary files /dev/null and b/03 Introduction to HTML5/.md_images/img_sem_elements.gif differ diff --git a/03 Introduction to HTML5/.md_images/upload.png b/03 Introduction to HTML5/.md_images/upload.png new file mode 100755 index 0000000..207cb88 Binary files /dev/null and b/03 Introduction to HTML5/.md_images/upload.png differ diff --git a/03 Introduction to HTML5/README.md b/03 Introduction to HTML5/README.md new file mode 100755 index 0000000..752f563 --- /dev/null +++ b/03 Introduction to HTML5/README.md @@ -0,0 +1,353 @@ +# HTML5 + +The web technology has revolutionized everything, including itself. HTML5 (Hypertext Markup Language) became official in 2014, following the previous release of version 4.01. There are many big changes introduced in this new release, the most important being HTML is now a ['living standard'](https://html.spec.whatwg.org/multipage/). This means that the standard will update itself, there will never be another version of HTML. + +[![](.md_images/eow.png)](http://www.evolutionoftheweb.com/) + +HTML5 is the most fundamental language in web programming. In this lab, you learn the basics of HTML5. In subsequent labs, several new technologies will be introduced to facilitate styling and dynamic content, but HTML5 remains the starting and center point of web programming techniques. + +> [A journey of a thousand miles begins with a single step, Lao Tzu](http://www.bbc.co.uk/worldservice/learningenglish/movingwords/shortlist/laotzu.shtml) + +In this lab you'll do the following tasks, it takes approximately 2 hours. + +1. Use basic HTML5 markup on a web page and verify the page's correctness. +2. Use tables in a web page. +3. Include audio and video and use semantic markup. + +> When we reference to HTML in this and upcoming labs, we always mean HTML5. + +## Basic HTML5 markup + +HTML is the language for describing the structure and content of a web page. In this task, you learn to create an HTML5 document with basic markup. + +### Hello HTML + +Create a new file and name it *computers80.html*. The file will automatically open in the editor. Next, paste the following into this file, and press `ctrl` + `s` to save it. + +```html + + + + Document + + + + + +``` + +The above HTML5 document contains the definition of an empty web page. First, pay attention to the overall structure of the web page defined in the document. There is a correspondence between a file and a web page. Each static web page corresponds to one HTML5 file. + +The page consists of elements, separated by a start tag and end tag. For example, the root element `html` starts with a start tag `` and ends with an end tag ``. + +> HTML elements are written with a start tag, with an end tag, with the content in between. See definition here on [w3school](http://www.w3schools.com/html/html_elements.asp) and discussions on [StackOverFlow](http://stackoverflow.com/questions/8937384/what-is-the-difference-between-tags-and-elements-html) + +Elements are nested: for instance, the `body` element is inside the `html` element. + +> Use Edit ==> Beautify to properly format your code so that parent-children-relation becomes obvious to see. + +The elements follow the HTML5 syntax. The syntax dictates, for example, that: + +* Each HTML5 page contains an `html` root element. All the markup for a web page goes inside this element. +* Inside the `html` element, there are `head` and `body` elements in this order. In head element, there is background information for showing the web page, usually consumed by browsers. In the example file, there is one piece of information, the title of the page, indicated in `title` element. +* The content is within `body` element. + +### Write content and structure + +First, add language attribute to the start tag of `html`. So + +```html + +``` + +becomes + +```html + +``` + +This step is not really compulsory. But according to the [W3C recommendation](https://www.w3.org/International/questions/qa-html-language-declarations), you should always use a language attribute. + +Now, fill the title element with the text you want to appear at the top of the browser window (or as a label in a browser tab). It is also a good practice to enter the `meta` element used for character encoding: + +```html + + 1980's home computers + + +``` + +In the above: + +* `meta` is an empty element with an attribute. It is an exception to the start tag / end tag rule. We return to these later. +* UTF-8 is a means of encoding characters in disk or memory. Instead of UTF-8, we could have ASCII or ANSI, among many others. But UTF-8 (Unicode) covers almost all of the characters and symbols in the world. + +> If you're confused about UTF-8/ANSI etc, you're not alone. Read discussions on [StackOverflow](http://stackoverflow.com/questions/700187/unicode-utf-ascii-ansi-format-differences) and [the Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets](http://www.joelonsoftware.com/articles/Unicode.html). + +Once the `head` element is ready, add some markup inside the `body` element of the web page: + +```html + +
+

1980's Home Computers

+

In early 1980's, home computers became mainstream. For the first time, computers could be purchased by an average family household.

+ +

Clever uses

+

The main usage for a home computer was, of course, games. As cool games may not be a valid reason for purchasing an expensive gadget, a wide variety of potential uses were invented to satisfy those family members who might otherwise be sceptical.

+ +

Legendary computers

+

The best known computer of the eighties was Commodore 64, but there are other computers that gained popularity.

+ +

Key concepts

+

The hardware charateristics of a home computer could be defined by its graphics and sound capabilities, CPU, and amount of memory.

+
+ +``` + +Open the file in a browser by right-clicking computers80.html then select Preview. The page is displayed using the browser's built-in style settings. We can later change how the page looks using CSS (Cascaded Style Sheets). + +![](.md_images/hello.png) + +> If you don't want to type yourself to fill the space, instead of copy/paste, you can use [Lorem ipsum](https://en.wikipedia.org/wiki/Lorem_ipsum). + +Later, when you amend your code, just hit **Refresh** icon in the built-in browser to show the updated content. + +### Paragraphs and headers + +All visual HTML elements have a display type, the most common of this are **block** and **inline**. In your web page, we have used a few of HTML5 block elements. + +The direct subelements of the `body` element in the example are block elements. This means that each of these elements uses a rectangular area of the resulting web page. By default, block elements are displayed in a browser in the same order as they appear in the HTML document. That is, the first header element `h1` is displayed before the consequent paragraph `p`, etc. + +Some block elements can be nested. However, it is not legal to nest headers and paragraphs between each other. + +Note those header elements represent the level of the header. Thus, all `h1` elements are the top-level headers (usually one per page), `h2` elements are second-level headers etc. + +### Validate the code + +Everyone writing HTML5 pages introduces errors into the code by mistake. These can be simple typos or errors in the element structure. + +It is a good idea to check the page against syntax error using an automated tool called validator. For this purpose, open new browser window and go to [W3C Markup Validation Service](https://validator.w3.org/). + +Select **Validate by direct input** tag and copy-paste your code into the text area. Once you click the **Check** button, you get feedback about your markup: + +- If the page is error-free, a green bar appears. The page should be correctly viewable by any browser that follows the HTML5 syntax. +- If the page contains errors, a red bar appears. Browsers may still be able to show its contents, but not necessarily in the intended way. You should correct the errors in the markup and revalidate. + +The list of errors provided by the validator can be very long. This happens especially if one error causes the validator to loose the trail of parsing, causing a chain reaction of errors. It is often easiest to correct the first error, and then revalidate. + +Add some errors into your HTML5 code and see how the validator responds. + +You should validate frequently. Validation often helps you to spot problems, as you write your code. You should always validate all pages of your site before publishing. + +### Lists + +Now, add a list on your web page. Insert the following lines of code after the paragraph describing clever uses for home computers: + +```html +

Some reasons often mentioned were:

+ + +``` + +View the list in the browser. + +The `ul` element describes an unordered list. It contains three list items (denoted by `li` elements). + +See what happens, when you change the list type to ordered list (`ol`). + +Next, try a definition list. Add the following piece of code at the very end of the document, right before the `` end tag: + +```html +

There are two types of memory:

+ +
+
RAM
+
Random access memory. The memory usable by programs. The amount was typically in the ballpark of 20 to 64 kilobytes. However, the Basic interpreter by default consumed a part of this.
+ +
ROM
+
Read-only memory. This was normally smaller in size than RAM and roughly corresponded to the hardware implementation of the operating system.
+
+``` + +The definitions list contains two elements for each item: a `dt` for the term and a `dd` for definition. + +### Images + +In HTML5, images are put inside a `figure` element. The `figure` element normally contains one image, possibly with a caption, but technically it can serve as a container for multiple images. + +In your workspace, create a new folder called 'images'. Add an image of a computer into this folder by using the File ==> Upload Local Files menu. + +> Organize your folders/projects properly using sub-folders such as images/assets/js/css. Read [Reddit discussions on folder structure best practice](https://www.reddit.com/r/webdev/comments/3afdb8/website_directory_structure_bestpractices/). For file names, follow common practice using all lower case letters and hyphen such as `contact-details.html`. See [some more explanations](http://www.dummies.com/how-to/content/how-to-name-html-files.html). + +> Don't use `index.html` for pages other than your homepage, because browsers detect your homepage by looking for a file called index.html by default. + +![](.md_images/upload.png) + +You can use the image below if you wish. + +![](.md_images/computer.png) + +Now, Add the following lines of code in an appropriate place, directly inside the `body` element: + +```html +
+ A computer with a monitor +
Photo: Piotr Siedlecki, public domain via http://www.publicdomainpictures.net.
+
+``` + +There are a couple of notable things about the `img` element: + +1. It is a void element. A void element doesn't have any content, and can be closed immediately, like this ``. For convenience, it can also be closed like this ``. The extra `/` character, in the end, is not essential. However, `` must **NOT** be closed like this ``, which is a syntax error. + + > There are other void elements besides `img`. One of the most widely used is `hr` for creating a horizontal rule and written as `
`. Void elements are not to be confused with self-closing tags. For void elements it's **illegal** to write `` or `
`, but for self-closing tags such as `
  • ` or `` you can safely ignore closing tags `
  • ` or ``. + + > Click [here](https://www.w3.org/TR/html-markup/syntax.html) for a comprehensive list of void elements, [here](http://stackoverflow.com/questions/3558119/are-self-closing-tags-valid-in-html5) and [here](http://stackoverflow.com/questions/5641997/is-it-necessary-to-write-head-body-and-html-tags) for discussions on StackOverflow. [To close or not to close tags](http://blog.teamtreehouse.com/to-close-or-not-to-close-tags-in-html5), that is the question. + + +2. For an image to be displayed, it requires additional information in the form of attribute/value pairs. The attribute/value pairs above, are given within the start tag in the form of `attribute="value"`. Multiple definitions are separated by space. Two of attribute/value pairs are mandatory for images: + +| Attribute | Purpose | +|-----------|---------------------------------------------------------------------------------------------------------------------------------------------| +| src | For locating the image file. The file path is normally given as relative path, starting from the directory where the HTML file is located. | +| alt | For displaying an alternative text if the image cannot be displayed, or if the user prefers using a screen reader for accessibility purposes. | + +### Links + +Now, add a link to a Wikipedia article to your web page, in a suitable location directly inside the `body` element: + +```html +

    Read the Wikipedia article for Retrocomputing.

    +``` + +If you preview the web page and click the link, you'll be directed to the wiki page you specified earlier. + +A link, defined by `a` element contains the URL of the linked web page as its `href` attribute. The link above contains an absolute path to a document on the external server. The absolute path begins will full protocol identifier and domain name. + +Unlike block elements (such as `h1` or `p`), links are inline elements: they always need a block element as a container and they are considered as a running part of the content of the parent block element. + +A relative link is intended for links within the same domain. The parsing of the file path starts from the default directory, which is the directory where the containing HTML document is located. For instance: + +- `"document.html"` points to a document of that name in the default directory. +- `"info/document.html"` points to a document in `info` subdirectory of the default directory. +- `"../document.html"` points to a document in the parent directory of the default directory. (Note that, for security reasons, web servers prohibit the traversal of server's directory structure outside the dedicated document root folder.) + +Add a link or two to suitable places in **computers80.html**. + +### Test your understanding + +1. Create a Commodore 64 web page to the same website. To do this, create a file called **commodore64.html** and include the following content, properly marked in HTML. Finally, link the main page (computers80.html) to your new page by adding a link from the correct list item. + + ```html + Commodore 64 + History + Commodore 64, abbreviated C64, was introduced in 1982 and gained worldwide popularity. It is still the most sold computer in the world. + (image of C64, download a copy to local site from http://commons.wikimedia. org, include copyright notice as a caption) + + Current status + There's still a wide community of C64 enthusiasts. There are numerous C64 emulators, + such as CCS64 (link to http://www.ccs64.com/) and Frodo (link to http:// frodo.cebix.net/). + + Gaming device + Best games + Commodore 64 was notorious for its magnificent games. Some of the best include: + - Jet Set Willy + - contained 60 levels + - originally a ZX Spectrum game + - Last Ninja 2 + - Ultima 5 + Joysticks + Several joysticks were manufactured for Commodore 64, such as Quickshot II. + + Back to Main page (link) + + ``` + +2. Validate your newly-created web page and correct any potential errors. + +## Tables + +The second task focuses on HTML tables. + +Open the page **comparison.html** that contains a comparison table of the three computers. + +Verify that you understand the elements used in marking up the table: +- `table` to indicate the entire table. +- `caption` for specifying the header of the table. By default, it appears above the table centered. +- `thead` and `tbody` are semantic elements to show the header and body parts of the tables (there´s also `tfoot` for the footer). These are semantical elements, and, while not necessary, can help figuring out the structure of the table. In addition, `tfoot` could be used for a footer row. +- `tr` for a table row. You can add as many of these elements into the table as necessary. Inside each `tr`, put a necessary number of `th`/`td` elements (see below) +- `th` for a header cell. Inside the `tr` element corresponding to the header row (usually the topmost row), each column heading is given using this element. +- `td` for a table data cell, i.e. regular table cell. + +> A summary of all HTML table tags can be found on [w3schools.com](http://www.w3schools.com/html/html_tables.asp). + +### Test your understanding + +1. Modify the table structure to contain a column for Spectravideo home computer. Its data is: Spectravideo SV-328, Zilog Z80A CPU, 64kB RAM, 32kB ROM. +2. Generate two additional rows where each row should have a multi-column cell (use `colspan` attribute): + + - Main usage: home computing (for all computers, a single cell should span over four columns) + - Killer game: Jet Set Willy (for Commodore 64 and ZX Spectrum, in a merged cell covering two columns), Jelly Monsters (for VIC-20), and Armoured Assault for Spectravideo. + +## Multimedia and semantic elements + +Two benefits of using HTML5 are the easier inclusion of multimedia content and better semantic markup possibilities for the content. + +### Audio and video + +The inclusion of audio and video files is easy. In the lab's `assets` folder, there is a sample public domain audio clip. Download to your computer and then upload to your own `assets` folder (create it first). + +The audio file can then be included in a web page by adding the following HTML5 code: + +```html + +``` + +The incorporation of videos is similar to audio elements, except for that the corresponding tag is `video`. For large videos, the file size limit can, however, hinder the use of anything but small video clips. + +> For YouTube videos, however, the format is different. See [w3school page](http://www.w3schools.com/html/tryit.asp?filename=tryhtml_youtubeiframe) for an example + +### Semantic elements + +Semantic elements allow improved markup of semantic content. They facilitate defining which elements form logical entities (articles, section, headers etc.) in the content. + +The inclusion of semantic elements in HTML5 makes the code more manageable, as they often diminish the need for generic `div` elements, which have decreased the readability of code. + +Furthermore, the use of semantic elements makes documents more accessible to screen readers etc. + +The summary of some key semantic elements to describe the contents of the web page is below, based on the [W3C documentation](http://www.w3.org/TR/html5/sections.html). + +| Element | Intended content | Example | +|---------|------------------------------------------------------------|--------------------------| +| article | complete, independent composition | a blog post | +| section | part of the document that could be enlisted in the outline | a news item | +| nav | navigation functionality | navigation bar | +| header | introductory content | welcome texts | +| footer | "smallprint" content, usually in the end. | contact data, disclaimer | + + +![](.md_images/img_sem_elements.gif) + +There are more semantic elements to clarify the content, such as `thead`, `tbody` and `tfoot` elements inside the tables. + +The use of semantic elements is encouraged, even though they are not yet in widespread use. + +### Test your understanding + +1. Generate a personal web page of a fictitious student Jane Doe, or John Doe (or your own, if you prefer), that contains, at least, the information listed below. Use semantic elements when applicable. + + - personal data (first names, family name, date of birth) + - image + - short introduction + - hobbies listed, links to further information provided + - courses passed (name, date, grade), in tabular format + - contact data + +2. Validate the web page you created and correct any potential errors. diff --git a/labs/03 HTML5/audio/tune.mp3 b/03 Introduction to HTML5/workspace/assets/tune.mp3 old mode 100644 new mode 100755 similarity index 100% rename from labs/03 HTML5/audio/tune.mp3 rename to 03 Introduction to HTML5/workspace/assets/tune.mp3 diff --git a/03 Introduction to HTML5/workspace/commodore64.html b/03 Introduction to HTML5/workspace/commodore64.html new file mode 100755 index 0000000..7efb89b --- /dev/null +++ b/03 Introduction to HTML5/workspace/commodore64.html @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/03 Introduction to HTML5/workspace/comparison.html b/03 Introduction to HTML5/workspace/comparison.html new file mode 100755 index 0000000..42b0dda --- /dev/null +++ b/03 Introduction to HTML5/workspace/comparison.html @@ -0,0 +1,48 @@ + + + + + Comparison of three computers + + + + +

    A comparison of technical data of three popular 80's home computers:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Technical comparison
    PropertyCommodore 64ZX SpectrumCommodore VIC-20
    CPUMOS Technology 6510Zilog Z80MOS Technology 6502
    RAM64 kilobytes16 or 64 kilobytes5 kilobytes
    ROM20 kilobytes16 kilobytes16 kilobytes
    + +
    + + + + \ No newline at end of file diff --git a/03 Introduction to HTML5/workspace/computers80.html b/03 Introduction to HTML5/workspace/computers80.html new file mode 100755 index 0000000..c1fd327 --- /dev/null +++ b/03 Introduction to HTML5/workspace/computers80.html @@ -0,0 +1,53 @@ + + + + + 1980's home computers + + + + +
    + A computer with a monitor +
    Photo: Piotr Siedlecki, public domain via http://www.publicdomainpictures.net.
    +
    +
    +

    1980's Home Computers

    + +

    In early 1980's, home computers became mainstream. For the first time, computers could be purchased by an average family household.

    + +

    Clever uses

    +

    The main usage for a home computer was, of course, games. As cool games may not be a valid reason for purchasing an expensive gadget, a wide variety of potential uses were invented to satisfy those family members who might otherwise be sceptical.

    +

    Some reasons often mentioned were:

    + + +

    Legendary computers

    +

    The best known computer of the eighties was Commodore 64, but there are other computers that gained popularity.

    + +

    Read the Wikipedia article for Retrocomputing.

    + +

    Key concepts

    +

    The hardware charateristics of a home computer could be defined by its graphics and sound capabilities, CPU, and amount of memory.

    + +

    There are two types of memory:

    + +
    +
    RAM
    +
    Random access memory. The memory usable by programs. The amount was typically in the ballpark of 20 to 64 kilobytes. However, the Basic interpreter by default consumed a part of this.
    + +
    ROM
    +
    Read-only memory. This was normally smaller in size than RAM and roughly corresponded to the hardware implementation of the operating system.
    +
    + + + +
    + + + \ No newline at end of file diff --git a/labs/04 Introduction to CSS3/img/computer.png b/03 Introduction to HTML5/workspace/images/computer.png old mode 100644 new mode 100755 similarity index 100% rename from labs/04 Introduction to CSS3/img/computer.png rename to 03 Introduction to HTML5/workspace/images/computer.png diff --git a/labs/06 HTML5 forms/img/error_fin.png b/04 HTML5 forms/.md_images/error_fin.png old mode 100644 new mode 100755 similarity index 100% rename from labs/06 HTML5 forms/img/error_fin.png rename to 04 HTML5 forms/.md_images/error_fin.png diff --git a/labs/06 HTML5 forms/img/error_html5.png b/04 HTML5 forms/.md_images/error_html5.png old mode 100644 new mode 100755 similarity index 100% rename from labs/06 HTML5 forms/img/error_html5.png rename to 04 HTML5 forms/.md_images/error_html5.png diff --git a/labs/06 HTML5 forms/img/fieldset.png b/04 HTML5 forms/.md_images/fieldset.png old mode 100644 new mode 100755 similarity index 100% rename from labs/06 HTML5 forms/img/fieldset.png rename to 04 HTML5 forms/.md_images/fieldset.png diff --git a/labs/06 HTML5 forms/img/input_hint.png b/04 HTML5 forms/.md_images/input_hint.png old mode 100644 new mode 100755 similarity index 100% rename from labs/06 HTML5 forms/img/input_hint.png rename to 04 HTML5 forms/.md_images/input_hint.png diff --git a/04 HTML5 forms/.md_images/pickers.png b/04 HTML5 forms/.md_images/pickers.png new file mode 100755 index 0000000..e446fab Binary files /dev/null and b/04 HTML5 forms/.md_images/pickers.png differ diff --git a/04 HTML5 forms/README.md b/04 HTML5 forms/README.md new file mode 100755 index 0000000..18dd475 --- /dev/null +++ b/04 HTML5 forms/README.md @@ -0,0 +1,263 @@ +# HTML5 forms + +In this lab, you learn how to create a form which is a collection of controls that accept information from the user and sends the information to a web server. + +The information is sent to the server as a query string. We will learn more on interacting with the user, later on, using Javascript and Flask. + +In this lab you do the following tasks, it takes approximately 2 hours: + +- Create an HTML5 form. +- Use HTML5 for controlling the data types. + +## Introduce HTML5 forms + +### A very basic form + +First, create a create a file called **sample-form.html** (in terminal issue the following: touch sample-form.html) + +In the newly created HTML file, add the following lines of code: + +```html +
    +
    + Let's search Google: + + +
    +
    +``` + +Notice that as an action it refers to [Google's search engine](http://www.google.com/search). It gives a URL where the user's input data is sent. The `
    ` tag defines a division or a section in an HTML document. `` tags are for many, but not all, input fields, as we'll see later. `` is another example of a void element. + +Right click sample-form.html and select Preview. Now when you write something into the box and click submit, your text is sent to Google's search engine. + +Notice how you see parameters passed in a URL. Depending on where you're (i.e. locale), if you're at Helsinki, you'll see something like + +```html +https://www.google.fi/search?q=YOUR_QUERY_GOES_HERE +``` +or if you're at Coventry you'll see + +```html +https://www.google.co.uk/search?q=YOUR_QUERY_GOES_HERE +``` + +This means that we are using `GET` method to send out data to the server. The method is not explicitly written in the form tag, therefore, the default (= `GET`) is used. + +Everything that the user writes in the form is shown in the URL. In our case, Google needs only one parameter which is called `q`. The general syntax for URL query string is + +``` +URL?name1=value1&name2=value2&name3=value3..... +``` + +In most cases we don't want this, the usual way is to use `POST` method (which will be discussed later). Confidential data is recommended to be sent by `POST` methods. + +When discussing REST APIs, we will have even more methods like INSERT and DELETE. + +HTML form is just a set of things, which are called controls. They are elements of different types like text fields, checkboxes, radio buttons, submit buttons, and more. The `` element is the most important form element, it has no content and is self-closing (i.e. void element). + +In our form, we have 2 input elements. The first one is for text input and we named it `q` because it happens to be the Google search string. + +> The convention is to use camelCase for things such as name/id, which is the same as in JavaScript. However, in this case, it has to be `q` as this is what Google expects. + +It is important to have the name attribute on each input control and name should be unique. This is how browser knows how to send control's data to the server. You'll find out more about this later in the module. + +Second input element's type is `submit` which generates a submit button for the user. This one sends the form information to the URL mentioned in the action. + +### Test your understanding + +1. Create a file called **form-skel.html**. You can download from current workspace folder above and then upload to your workspace. Or you can create a new file in your workspace and copy/paste contents into it. +2. Insert the required input elements in the file and check that you have them all. You can use text input type for all but submit button. + +## Control inputs + +### Type attributes + +HTML defines a number of input types that can be used in forms. The commonly used ones are: + +- Plain text: `` +- Radio buttons: `` +- Check boxes: `` +- Password fields: `` +- Email addresses: `` + +The above are supported by most browsers. There are some new input types also: + +- URL addresses: `` +- Numeric data: `` +- Date pickers: `` + +The main reason for introducing these new input types is for mobile devices with limited screen sizes where special keyboards/input methods can be used. But note that not every browser supports the new types. + +> Input types, not supported by old web browsers, will behave as input type text. See [here](http://www.w3schools.com/html/html_form_input_types.asp). + + +![](.md_images/pickers.png) + +### Hidden parameters + +Sometimes we need a way to submit some additional parameter to the server. This can be done by using a `hidden` input parameter. +A hidden parameter has no onscreen appearance, but it will be sent to the server. + +```html +
    +...... + + .... +
    +``` + +### Drop-down lists + +The `select` element defines a drop-down list, and the `option` element is used to define a list item. + +```html + +``` + +Every `option` element should have a unique value, just like in checkboxes and radio buttons. + +### Reset button + +Earlier `Reset` buttons were common, they are used to reset a form's data to its initial value. + +```html +
    +...... + + .... +
    +``` + +The reason for this was that on web page designers wanted to follow usability heuristics of user control and freedom by allowing users an "emergency exit" out of any situation they may have entered. + +Unfortunately `Reset` buttons almost never help users but often hurt them. It is better to give users other ways to accomplish undoing. + +For example in the case of text fields or checkboxes, the user can always erase the entry and revert to the original state. + +Radio buttons and checkboxes and pull-down menus are trickier. A classic design mistake on the Web is to have radio buttons that initially do not have a selection. Often, there is no way for the user to select a "nothing" option, once he or she has selected one of the choices. Also, selectable menus should include an explicitly selectable menu entry in pull-down menus for the default choice. + +There is one exception where `Reset` buttons can be used. This is when + +- the same user does repeat form-filling and +- the data to be entered differs significantly from one use of the form to the next + +In other cases, it is easier for the user to edit the old data than to erase it and start from scratch. + +### Test your understanding + +1. Open file **form-skel.html**. Change your input types for the input elements to appropriate values. +2. Try to put incorrect data in your form and check the browser's support for types. +3. Implement a 'reset' button to reset the form-data to its initial values. + +## Make it easier for the user + +We have a couple of ways to help the user with forms. + +### Connect label with control + +`label` elements are used to connect texts and controls that are used together in forms. For example radio buttons and check boxes often come with preceding texts that describe the choice. However, if the user clicks the text nothing happens. That's because the browser doesn't know the connection between the text and the neighboring control. They must be wrapped up with label element. + +```html +
    +I have a bike
    +I have a car +
    +``` + +You have to click on the box. But if you have + +```html +
    +
    + +
    +``` +It is enough to click on text too. + +Note the above can also be written as the following, which is also valid but needs more typing + +```html +
    +
    + +
    +``` + +> Read [StackOverflow discussions on different ways of associating labels and inputs](http://stackoverflow.com/questions/774054/should-i-put-input-tag-inside-label-tag). + +### Use grouping and hints + +In many user interfaces, you can see how different elements on the screen are grouped together in order to make the input easier for the user. In HTML, you can use `fieldset` element for this. + +```html +
    +
    +Credit Card +
    + +
    +
    +``` +What you get is + +![Example of a fieldset and legend](.md_images/fieldset.png) + +In user interfaces it also a common practice to give hints on the kind of data is expected. In HTML, this can be achieved by `value` or `placeholder` attribute on the controls. The `placeholder` attribute's text disappears once the control is clicked in or gains focus and the `value` attribute's text stays in place when a control has focus unless a user manually deletes it. + +![Example of a value and placeholder](.md_images/input_hint.png) + +### Test your understanding + +Open file **form-skel.html**: + +2. At the moment, you have to click exactly the right spot on your checkboxes etc. Change this by using labels. +3. Rearrange your form with legends and fieldsets in order to make it easier for the user to understand. +4. Give input hints to the user whenever possible. + +## Form validation + +Form validation is traditionally done using JavaScript. But HTML5 introduces some new ways of doing it, which makes validation a lot easier. + +### 'required' attribute + +Now return to the very first example in this lab + +```html +
    +
    + Let's search Google: + + +
    +
    +``` + +If we add a `required` attribute to the query input, it will become ``. In this case, the query field is required. In other words, if we don't type in anything in this field, we'll have an error message when trying to click submit. + +![Example of an error message](.md_images/error_html5.png). + +Behind the scene, the browser tries to verify user's input. There are some other input attributes that can serve for validation purposes. For example, `min` and `max` attributes for numerical input types such as `number` or `month`; `size` and `maxlength` for limiting the number of characters entered. + +### 'pattern' attribute based on regular expression + +In addition to using different input types, we can also use patterns a.k.a.regular expressions. Using patterns, we can validate user inputs even more precisely. + +The pattern is an encoded sequence of characters that define a pattern of text characters. Remember that client-side validation is not reliable as the only means of validation, it is useful to make the user interface more pleasant. + +For example, a Finnish social security number (similar to UK National Insurance number) is often 999999-999X (for those who were born in the 20th century). The first 6 digits are the birth date: day, month and year's last two numbers. After "-" there are 3 digits and the last character can be either digit or letter from A to Y. If we want to make a pattern of this, it would be + +```html + +``` +\d means a digit, \\- means the "-", [chars] means a set of characters. + +### Test your understanding + +* Open file **form-skel.html**. Validate the user's data in Student id, email address and score by using patterns. + + + diff --git a/labs/06 HTML5 forms/form_skel.html b/04 HTML5 forms/workspace/form-skel.html old mode 100644 new mode 100755 similarity index 100% rename from labs/06 HTML5 forms/form_skel.html rename to 04 HTML5 forms/workspace/form-skel.html diff --git a/04 HTML5 forms/workspace/sample-form.html b/04 HTML5 forms/workspace/sample-form.html new file mode 100755 index 0000000..31c1924 --- /dev/null +++ b/04 HTML5 forms/workspace/sample-form.html @@ -0,0 +1,18 @@ + + + + + + + + +
    +
    + Let's search Google: + + +
    +
    + + + \ No newline at end of file diff --git a/labs/04 Introduction to CSS3/img/comparison.png b/05 Introduction to CSS3/.md_images/comparison.png old mode 100644 new mode 100755 similarity index 100% rename from labs/04 Introduction to CSS3/img/comparison.png rename to 05 Introduction to CSS3/.md_images/comparison.png diff --git a/05 Introduction to CSS3/.md_images/ex.png b/05 Introduction to CSS3/.md_images/ex.png new file mode 100755 index 0000000..34ecde5 Binary files /dev/null and b/05 Introduction to CSS3/.md_images/ex.png differ diff --git a/05 Introduction to CSS3/.md_images/expand.png b/05 Introduction to CSS3/.md_images/expand.png new file mode 100755 index 0000000..e27e69a Binary files /dev/null and b/05 Introduction to CSS3/.md_images/expand.png differ diff --git a/05 Introduction to CSS3/.md_images/ul.png b/05 Introduction to CSS3/.md_images/ul.png new file mode 100755 index 0000000..25098d6 Binary files /dev/null and b/05 Introduction to CSS3/.md_images/ul.png differ diff --git a/labs/04 Introduction to CSS3/worksheet.md b/05 Introduction to CSS3/README.md old mode 100644 new mode 100755 similarity index 62% rename from labs/04 Introduction to CSS3/worksheet.md rename to 05 Introduction to CSS3/README.md index 4e3009d..9662c80 --- a/labs/04 Introduction to CSS3/worksheet.md +++ b/05 Introduction to CSS3/README.md @@ -1,38 +1,55 @@ # Introduction to CSS3 -In this lab you learn to use CSS3 for defining the visual outlook of a web site. +The purpose of HTML is to define the structure and contents of a website. It is not intended to contain any representational markups. CSS (Cascaded Style Sheets) is designed for that purpose. -As you know by now, the purpose of HTML is to define the structure and content of a web site. It is not intended to contain any representational markup. CSS (Cascaded Style Sheets) is designed for that purpose. +> CSS is amazingly powerful. As an example, click to visit [CSS Zen Garden](http://www.csszengarden.com). Different CSS rules are applied to a single HTML file by clicking on the navigation links to the right of the screen. There's a book for it as well on [Amazon](http://www.amazon.co.uk/ZEN-CSS-Design-Visual-Enlightenment/dp/0321303474). -This lab focuses on basic repsetentation (colors, fonts, etc.). The layout of the elements in a web page will be covered in the next lab. +In this lab, you learn to use CSS3 for defining the visual outlook of a website. This lab focuses on basic representation (colors, fonts, etc.). The layout of the elements on a web page will be covered in the next lab. -From now on, we use CSS as a synonym for CSS3, which is the newest version of the styling language. - -## Task list - -In the lab you do the following tasks: +In the lab, you do the following tasks, and it takes approximately 2 hours. 1. Perform basic formatting (colors, fonts, etc.) of your web pages. 2. Use CSS classes and identifiers to pick the correct elements -3. Fine-tune your selection by picking nested elements and using pseudoelements. +3. Fine-tune your selection by picking nested elements and using pseudo-elements. + +> We use CSS as a synonym for CSS3, which is the newest version of the styling language. + +## Emmet -This lab takes approximately 2 hours. +Now you've (hopefully) learned how to write proper/valid HTML codes, it's the time to show some tricks. +> [Laziness is the mother of invention](http://innovators.vassar.edu/innovator.html?id=8). -## 1 Style sheet basics +[Emmet](http://emmet.io) is a plugin for code editors etc that saves your time from typing full HTML/CSS tags. Emmet is enabled by default in most code editors. -The purpose of a style sheet is to collect all information on how the pages in a web site should be visually rendered, into one place. +In your workspace create an empty file called **hello-world.html** (without contents). Type in the exclamation mark and immediately hit the tab key. What happened? -Ideally, all web pages in a site should be linked into one style sheet (one CSS file) that contains all visual formatting rules for the entire web site. +![](.md_images/ex.png). + +What you see is that the editor expands the single character into a full HTML template. Behind the scene, it's Emmet that does the job. + +![](.md_images/expand.png). + +There are quite a few other tricks you can do as well, for example, type `ul>li*8` and hit tab immediately, it will expand into full list element. + + ![](.md_images/ul.png). + +Some more example usages can be found on [Goodbye, Zen Coding. Hello, Emmet! By Zeno Rocha](https://www.smashingmagazine.com/2013/03/goodbye-zen-coding-hello-emmet/). + +## CSS basics + +The purpose of a style sheet is to collect all information on how the pages in a website should be visually rendered, into one place. + +Ideally, all web pages in a site should be linked to one style sheet (one CSS file) that contains all visual formatting rules for the entire website. This approach makes a clear separation between content and representation. The approach makes it easy to get a uniform outlook for the entire site. Moreover, one can easily build alternative views that may be strikingly different visually yet they share the same content. +> Maybe you've heard of the [model-view-controller](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) pattern. In terms of web development, the model is HTML, the view is CSS, and the controller is JS files. Have a look at MVC patterns in my other module [300CEM Android development](https://github.com/covcom/300CEM/tree/master/Week_04_Simple_Views_and_Layouts). -### Creating and linking a style sheet +### Create and link a style sheet -Create a new file in Codio and name it **simple.css**. +Create a folder called `css` in your workspace. Then create a new file and name it **simple.css**. Save this file into the `css` folder. Insert the following into simple.css file: -In the resulting CSS file, replace the placeholder content with the following: ```css body { font-family: Arial, Verdana, sans-serif; @@ -45,6 +62,7 @@ h1, h2 { ``` Examine the CSS file and verify that you understand its contents. Pay attention to the following points: + - The file contains two rules. Each rule begins with the list of elements it has an effect on. The first rule is applied to the `body` element whilst the second rule is applied to `h1` and `h2` header elements. - Followed by the element names (called selectors), there is a block starting with `{` and ending with `}`. This block contains a list of properties. - The first rule (applicable to the `body` element) contains one property for setting the font. @@ -52,13 +70,13 @@ Examine the CSS file and verify that you understand its contents. Pay attention - The rules are automatically in effect in the nested subelements, so the font setting for `body` element applies to all elements in the page, including headers, lists, and paragraphs. - The fonts are given as a list of alternatives. The browser uses the first font in the list that it is capable of. The list should always end in a generic font such as `serif` or `sans-serif`. -Even though the CSS file is present, it has no effect as it is not currently linked to any HTML document. +Even though the CSS file is present, it has no effect as it is not currently linked to any HTML document. The next step is to link it to a file called **csstest.html** in your workspace. To create this file you can simply create an empty file called csstest.html, and make it identical to our example by copying the contents over. The sample file can be found from the workspace folder above. -The next step is to link it to the file **csstest.html** in the lab folder. First, open the file and see how it looks like without the CSS file. The browser now uses its default stylesheet for displaying the page. +First, preview the file and see how it looks like without the CSS file. The browser now uses its default stylesheet for displaying the page. Then, insert the following line of HTML code in the `head` element of the HTML file: -``` +```html ``` @@ -68,21 +86,25 @@ The link can be added before or after the `title` element, as the order of eleme Having added the link, reload the web page and see how the rules are applied to the page. -Next, play with the CSS file. Add new rules and properties to the file and see how they affect the web page. Note that Codio helps you in writing the contents of a CSS file by proposing property names and values as you type. +Next, play with the CSS file. Add new rules and properties to the file and see how they affect the web page. Note that the IDE helps you in writing the contents of a CSS file by proposing property names and values as you type. -You can find a complete list of CSS property names and possible values (among other information) at the [W3C Cascading Stylesheets documentation](http://www.w3.org/TR/CSS/). +> You can find a complete list of CSS property names and possible values (among other information) at the [W3C Cascading Stylesheets documentation](http://www.w3.org/TR/CSS/). ### Colours In the example above, the colours of certain elements were set. -This can be done in two ways: +This can be done in three ways: - using colour words ("red", "blue", etc.) - using RGB colour values. +- using HEX values. -In RGB colour system, each colour is encoded as a combination of three bytes (valued 0 to 255), corresponding to red, green, and blue channels. For instance, bright red is (255,0,0) and royal blue is encoded (65,105,225). +In RGB colour system, each colour is encoded as a combination of three bytes (valued 0 to 255), corresponding to red, green, and blue channels. For instance, bright red is (255,0,0) and royal blue is encoded (65,105,225). + +RGB values can also be specified using hexadecimal color values in the form: #RRGGBB, where RR (red), GG (green) and BB (blue) are hexadecimal values between 00 and FF (same as decimal 0-255). Thus, valid ways to set the `h1` colour would be: + ```css h1 { color: blue; @@ -97,7 +119,15 @@ h1 { } ``` -In CSS3, RGB colours can be supplemented by an additional alpha channel, which sets the opacity value (0.0 meaning fully transparent and 1.0 fully opaque). +or + +```css +h1 { + color: #4169E1; +} +``` + +In CSS3, RGB colours can be supplemented by an additional alpha channel, which sets the opacity value (0.0 means fully transparent and 1.0 fully opaque). To add some opacity to headers, the aforementioned rule would be: ```css @@ -106,7 +136,6 @@ h1 { } ``` - ### Font sizes and measurements Add the following lines of code to **simple.css** to change the font sizes: @@ -123,8 +152,7 @@ ul, ol { This sets a large font size for the first-level headers and a smaller than normal size for unordered and ordered lists. -In the previous example, you probably noticed that some properties (such as `font-size`) require you to set sizes . CSS provides alternative ways to specify sizes: - +In the previous example, you probably noticed that some properties (such as `font-size`) require you to set sizes. CSS provides alternative ways to specify sizes: | Unit | Type | Meaning | |------|----------|----------------------------------------------------------| @@ -145,27 +173,23 @@ Validation makes sure that your CSS file complies with the CSS syntax. It may al Go to the validation page, and validate the file you created. If no errors are found, introduce some on purpose and see how the validator reports them. - ### Test your understanding -You need the aforementioned file `simple.css`. - -1. Using the CSS documentation, make the following amendments to the CSS file: - a. Add a border around each list and change the color of list items to dark grey. - b. Indent the paragraphs and lists. - c. Make figure captions smaller and use bold face. - d. Set a background color for the page. - +You need the aforementioned file `simple.css`. Using the CSS documentation, make the following amendments to the CSS file: +* Add a border to each list and change the color of list items to dark grey. +* Indent the paragraphs and lists. +* Make figure captions smaller and use bold face. +* Set a background color for the page. -## 2 Classes and identifiers +## Classes and identifiers So far, the rules in the CSS file have been applied to all elements matching one of the selectors. For example, all paragraphs have automatically had the same looks. It is possible to create a separate outlook for some instances of a given element by using classes or selectors. + - Classes can be applied to multiple instances of an element. These instances can also be a mixture of various element types, such a paragraphs (`p`) and first-level headers (`h1`). - Identifiers are unique to each instance. - ### Classes An instance of an element is assigned to a class by entering a class attribute in the corresponding start tag in the HTML file. @@ -173,19 +197,21 @@ An instance of an element is assigned to a class by entering a class attribute i Enter the following line of markup into **csstest.html**: ```html - + ``` -In the CSS file, assign a rule for paragraphs belonging to copyright\_notice class: -``` -p.copyright_notice { +In the CSS file, assign a rule for paragraphs belonging to `copyright-notice` class: + +```css +p.copyright-notice { font-size: 0.8em; } ``` -The rule is now applied to only those paragraphs that belong to `copyright_notice` class. There can be many such paragraphs, each having a similar `class` attribute. Verify this in browser. +The rule is now applied to only those paragraphs that belong to `copyright-notice` class. There can be many such paragraphs, each having a similar `class` attribute. Verify this in the browser. + +Alternatively, you could create a rule for all elements in `copyright-notice` class, regardless of whether they are paragraphs, list items, or something else: -Alternatively, you could create a rule for all elements in `copyright_notice` class, regardless of whether they are paragraphs, list items, or something else: ```css .copyright_notice { font-size: 0.8em; @@ -193,6 +219,7 @@ Alternatively, you could create a rule for all elements in `copyright_notice` cl ``` An HTML element can belong to multiple classes simultanously: + ```html

    The tour starts at 10 am.

    ``` @@ -204,10 +231,13 @@ The paragraph element above belongs to two classes: `info` and `urgent`. As seen before, multiple elements can belong to the same class. However, unique identifiers are a better option to generate rules that are applied only once. This is marked in HTML by the identifier attribute: + ```html

    This is different from all other paragraphs

    ``` + The CSS rule looks as follows: + ```css #special { font-size: 0.8em; @@ -223,7 +253,6 @@ Add the paragraph and the corresponding rule in your files and verify the outcom Alternatively, you could use `p#special`, but - since there cannot be another special element that is not a paragraph - this would result in the same outcome. - Now, open file **selectors.html** and the associated style sheet **selectors.css** to play with classes and identifiers. In the file, there are five paragraphs (`p`) and one header (`h1`). Verify that you understand the following: @@ -234,10 +263,10 @@ In the file, there are five paragraphs (`p`) and one header (`h1`). Verify that - The header belongs to `alert` class like two of the paragraphs. It is affected by a rule that is targeted to all elements of `alert` class (`.alert`). In addition, all rules for `h1` would be applied but there is none. - The rule for `body` element is applied to all elements as they are nested elements inside the body element. -If a property of an element would be defined in multiple CSS rules, the rule whose selector has highest specificity wins. For instance, the properties defined in a class-specific rule override the attributes defined in the general rule for the element. - +If a property of an element would be defined in multiple CSS rules, the rule whose selector has the highest specificity wins. For instance, the properties defined in a class-specific rule override the attributes defined in the general rule for the element. ### Test your understanding + 1. Modify the **selectors.html** and **selectors.css** files so that the first, third, and fifth paragraph appear underlined (in addition to the existing formatting). You should achieve this with a single CSS rule. 2. In **csstest.html** file, add a paragraph about Basic programming to the bottom of the page (Basic was a common language for programming 80's home computers). Below the paragraph, add a sample program as below: @@ -248,16 +277,19 @@ If a property of an element would be defined in multiple CSS rules, the rule who ``` Find the proper markup for doing this. Include the necessary CSS rule(s) to get a pleasant outcome. Validate your work. -3. Open the file **comparison.html** containing the comparison table of three computers. Create a new CSS file and link it to the HTML file. Generate the contents of the CSS file to make the HTML table resemble the image **comparison.png** in the lab's **img** folder as accurately as possible. Validate. -![(Alternatively, you see the image embedded if you open this assignment sheet in a separate browser window).](img/comparison.png) +3. Open the file **comparison.html** containing the comparison table of three computers. Create a new CSS file and link it to the HTML file. Generate the contents of the CSS file to make the HTML table resemble the image **comparison.png** in the lab's **images** folder as accurately as possible. Validate. + + ![(Alternatively, you see the image embedded if you open this assignment sheet in a separate browser window).](.md_images/comparison.png) + + > Some examples of CSS table rules can be found on [w3schools.com](http://www.w3schools.com/css/css_table.asp). -## 3 Fine-tuning the rules +## Fine-tune the rules In this part, we look at various ways to modify the CSS rules. The selections can be applied to elements in a specific context such as inside a specific element. Furthermore, pseudoelements allow selection of only the first row or character inside the content, for example. Finally, we look at the options for placing CSS. -### Selecting the elements +### Select the elements So far, we have used elements, classes and identifiers to pick the applicable rules. @@ -269,7 +301,7 @@ Write the following rules to **targetstyles.css**: ```css ol > li { - color: green; + color: green; } ul li ul { @@ -281,18 +313,32 @@ ul li ul li ul { } ``` -As the HTML file contains several list elements, nested and unnested, we can't to make the correct elements as targets of the rules using a single element as a selector. However, CSS provides a straightforward way to achieve our goal. +As the HTML file contains several list elements, nested and unnested, we can't make the correct elements as targets of the rules using a single element as a selector. However, CSS provides a straightforward way to achieve our goal. - In the first rule, selector `ol > li` matches only those `li` elements that are direct children of the unordered list. - In the second rule, those unordered lists are selected that appear in list items of a higher-level unordered list. Note that this rule applies to both the second and the third levels of the nested list in the example. - The third rule applies only to the third level (and potential deeper levels) in nested unordered lists. +### Use CSS to change the look of forms +A useful selector for styling forms is CSS attribute selector which applies to an element with a particular attribute set to a value. For example +```css +input[type="text"] { + color : blue; +} +input[type="submit"] { + font-weight : bold; +} +``` -### Nested elements and pseudoelements +Of course, you can use also the normal selectors for elements like `form`, `legend`, `fieldset`, etc. Unfortunately support for styling form controls is inconsistent across browsers. -Pseudoelements allow formatting only certain parts of an element. Using pseudoelements, it is for instance possible to pick the first letter or line of a paragraph. +Therefore, you should always check that the results are what is expected. + +### Nested elements and pseudo-elements + +Pseudoelements allow formatting only certain parts of an element. Using pseudoelements, it is, for instance, possible to pick the first letter or line of a paragraph. Add the following line into **simple.css** (that is linked to **csstest.html**): @@ -306,7 +352,7 @@ p::first-line { } ``` -Reload the page in browser and see the changes. +Reload the page in the browser and see the changes. In pseudoelement notation, a double colon (`::`) appears after the element name. This precedes one of the following: `after`, `before`, `first-letter`, `first-line`, or `selection`. @@ -319,6 +365,7 @@ In all the examples above, the CSS rules have been located in a single CSS file. Technically, there are other options for placing CSS rules, even though they are considered bad practice. The first alternative is to make CSS inline: + ```html

    Hello!

    ``` @@ -334,17 +381,9 @@ The second alternative is to generate a page-specific internal stylesheet inside As these approaches obscure the separation of content and presentation, their use is discouraged. It may, however, be useful to recognize them, as they are sometimes used. - ### Test your understanding -You need the aforementioned file `simple.css`. - -1. CSS pseudoelements `::before` and `::after` allow inserting content right before or after an element for stylistic purposes. These are normally used in conjunction with `content` property that specifies the content to be included. - a. Add a rule that adds the text `Note!` before each paragraph in **csstest.html**. - b. Add a rule that prints the image of a tick mark after each paragraph in **csstest.html**. A public domain tick mark image (Author: Kosta Kostov, via http://www.publicdomainpictures.net/) can be found in the lab's `img` folder. - - - - - +You need the aforementioned file `simple.css`. CSS pseudo-elements `::before` and `::after` allow inserting content right before or after an element for stylistic purposes. These are normally used in conjunction with `content` property that specifies the content to be included. +* Add a rule that adds the text `Note!` before each paragraph in **csstest.html**. +* Add a rule that prints the image of a tick mark after each paragraph in **csstest.html**. A public domain tick mark image (Author: Kosta Kostov, via http://www.publicdomainpictures.net/) can be found in the lab's `images` folder. diff --git a/05 Introduction to CSS3/workspace/comparison.html b/05 Introduction to CSS3/workspace/comparison.html new file mode 100755 index 0000000..42b0dda --- /dev/null +++ b/05 Introduction to CSS3/workspace/comparison.html @@ -0,0 +1,48 @@ + + + + + Comparison of three computers + + + + +

    A comparison of technical data of three popular 80's home computers:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Technical comparison
    PropertyCommodore 64ZX SpectrumCommodore VIC-20
    CPUMOS Technology 6510Zilog Z80MOS Technology 6502
    RAM64 kilobytes16 or 64 kilobytes5 kilobytes
    ROM20 kilobytes16 kilobytes16 kilobytes
    + +
    + + + + \ No newline at end of file diff --git a/labs/04 Introduction to CSS3/css/selectors.css b/05 Introduction to CSS3/workspace/css/selectors.css old mode 100644 new mode 100755 similarity index 100% rename from labs/04 Introduction to CSS3/css/selectors.css rename to 05 Introduction to CSS3/workspace/css/selectors.css diff --git a/05 Introduction to CSS3/workspace/css/simple.css b/05 Introduction to CSS3/workspace/css/simple.css new file mode 100755 index 0000000..061fbf3 --- /dev/null +++ b/05 Introduction to CSS3/workspace/css/simple.css @@ -0,0 +1,9 @@ +body { + font-family: Arial, Verdana, sans-serif; +} + +h1, +h2 { + color: blue; + font-weight: bold; +} \ No newline at end of file diff --git a/labs/04 Introduction to CSS3/css/targetstyles.css b/05 Introduction to CSS3/workspace/css/targetstyles.css old mode 100644 new mode 100755 similarity index 100% rename from labs/04 Introduction to CSS3/css/targetstyles.css rename to 05 Introduction to CSS3/workspace/css/targetstyles.css diff --git a/labs/04 Introduction to CSS3/csstest.html b/05 Introduction to CSS3/workspace/csstest.html old mode 100644 new mode 100755 similarity index 97% rename from labs/04 Introduction to CSS3/csstest.html rename to 05 Introduction to CSS3/workspace/csstest.html index b46c8d3..0791d1a --- a/labs/04 Introduction to CSS3/csstest.html +++ b/05 Introduction to CSS3/workspace/csstest.html @@ -11,7 +11,7 @@

    1980's Home Computers

    In early 1980's, home computers became mainstream. For the first time, computers could be purchased by an average family household.

    - A computer with a monitor + A computer with a monitor
    Photo: Piotr Siedlecki, public domain via http://www.publicdomainpictures.net.

    Novel uses

    diff --git a/05 Introduction to CSS3/workspace/hello-world.html b/05 Introduction to CSS3/workspace/hello-world.html new file mode 100755 index 0000000..32dc15b --- /dev/null +++ b/05 Introduction to CSS3/workspace/hello-world.html @@ -0,0 +1,24 @@ + + + + + Document + + + + + + + + + + +
    + + \ No newline at end of file diff --git a/05 Introduction to CSS3/workspace/images/comparison.png b/05 Introduction to CSS3/workspace/images/comparison.png new file mode 100755 index 0000000..86b62f1 Binary files /dev/null and b/05 Introduction to CSS3/workspace/images/comparison.png differ diff --git a/05 Introduction to CSS3/workspace/images/computer.png b/05 Introduction to CSS3/workspace/images/computer.png new file mode 100755 index 0000000..b71ac38 Binary files /dev/null and b/05 Introduction to CSS3/workspace/images/computer.png differ diff --git a/labs/04 Introduction to CSS3/img/tickmark.png b/05 Introduction to CSS3/workspace/images/tickmark.png old mode 100644 new mode 100755 similarity index 100% rename from labs/04 Introduction to CSS3/img/tickmark.png rename to 05 Introduction to CSS3/workspace/images/tickmark.png diff --git a/labs/04 Introduction to CSS3/selectors.html b/05 Introduction to CSS3/workspace/selectors.html old mode 100644 new mode 100755 similarity index 100% rename from labs/04 Introduction to CSS3/selectors.html rename to 05 Introduction to CSS3/workspace/selectors.html diff --git a/labs/04 Introduction to CSS3/targets.html b/05 Introduction to CSS3/workspace/targets.html old mode 100644 new mode 100755 similarity index 100% rename from labs/04 Introduction to CSS3/targets.html rename to 05 Introduction to CSS3/workspace/targets.html diff --git a/06 Advanced CSS3/.md_images/box-model.gif b/06 Advanced CSS3/.md_images/box-model.gif new file mode 100755 index 0000000..11eaf28 Binary files /dev/null and b/06 Advanced CSS3/.md_images/box-model.gif differ diff --git a/labs/05 Responsive Layout with CSS3/img/box_assignment.png b/06 Advanced CSS3/.md_images/box_assignment.png old mode 100644 new mode 100755 similarity index 100% rename from labs/05 Responsive Layout with CSS3/img/box_assignment.png rename to 06 Advanced CSS3/.md_images/box_assignment.png diff --git a/06 Advanced CSS3/.md_images/ins.png b/06 Advanced CSS3/.md_images/ins.png new file mode 100755 index 0000000..759a5ff Binary files /dev/null and b/06 Advanced CSS3/.md_images/ins.png differ diff --git a/labs/05 Responsive Layout with CSS3/img/menu.png b/06 Advanced CSS3/.md_images/menu.png old mode 100644 new mode 100755 similarity index 100% rename from labs/05 Responsive Layout with CSS3/img/menu.png rename to 06 Advanced CSS3/.md_images/menu.png diff --git a/06 Advanced CSS3/.md_images/tools.png b/06 Advanced CSS3/.md_images/tools.png new file mode 100755 index 0000000..9c6dd91 Binary files /dev/null and b/06 Advanced CSS3/.md_images/tools.png differ diff --git a/06 Advanced CSS3/README.md b/06 Advanced CSS3/README.md new file mode 100755 index 0000000..a38dcbb --- /dev/null +++ b/06 Advanced CSS3/README.md @@ -0,0 +1,233 @@ +# Advanced CSS3 + +In this lab, you learn to create a responsive layout for a web page or a website. + +You first learn the basics of positioning elements to create a unified layout. The positioning is done using CSS, based on the proper markup of elements using HTML. In addition to the positioning of the elements, you learn to design the navigation and create a multi-level menu for your website. + +In this lab you do the following tasks, it takes approximately 2 hours. + +- Use CSS box model to define the appearance of your web pages. +- Position elements on a web page. +- Implement navigation functionality with CSS. + +## CSS box model + +First, open the file **boxmodel.html** that illustrates the CSS box model which is vital for understanding how to set the dimensions, margins, and paddings of elements in a web page. Check the contents of the file. You notice that it contains four paragraph elements, each labeled to belong to a different class. + +Create a file called **boxstyles.css** and link it to the HTML file. + +In the newly-created CSS file, add the following lines of code: + +```css +p.one { + border: 1px solid black; + padding: 3em; + margin: 1em; + margin-bottom: 0em; +} +``` + +This sets the visual appearance of the first text paragraph that belongs to class `one`. Reload the page and see how the paragraph is affected. + +To truly understand the lines of code above, it is important to get an idea of the CSS box model. + +The box model is applicable to block elements, i.e. elements that hold a rectangular space on the screen. Recall those common HTML elements such as headers, paragraphs, and lists are block elements. + +For each element, you can set various CSS properties, such as: + +- `border`: the thickness and looks of the border outlining the element +- `margin`: the amount of free space outside the border +- `padding`: the amount of free space inside the border + + +![](.md_images/box-model.gif) + + +Notice that borders are normally invisible for most elements (such as regular paragraphs). In spite of this, you can use margin and padding settings in the normal way. + +By default, the settings above apply to all four directions. In the example above, the margin setting was overridden for the bottom direction. + +Now, add rules for the remaining paragraph classes (two, three, four) to generate the following appearance for the remaining paragraphs: + +- class `two`: same border as for class `one`, no padding, 5em margins except for the top where no should be no margin. +- class `three`: a 3-pixel wide solid black border only on the left edge of the paragraph, 1em padding except for the top where no padding is applied, 2em top margin, 8em bottom margin, no left and right margins. +- class `four`: a 5-pixels wide dashed red margin with rounded corners (1em radius). + +Verify that all four paragraphs are displayed exactly as intended. + +### Developer tools + +In Google Chrome browser, there are a built-in Developer Tools for viewing the layout of an individual element. It is an important tool for finding and correcting layout problems. Click options menu to the right of the menu bar, then More Tools ==> Developer Tools to view it. + +![](.md_images/tools.png) + +Alternatively, you can simply right-click on an element and select **Inspect**. Try it for each differently-formatted paragraph on your page. + +![](.md_images/ins.png) + +### Test your understanding + +The file **box_assignment.png** in the lab's `images` folder contains an image of intended appearance for a web page. Using CSS box model, construct an HTML page and the associated CSS file that reproduce the appearance of the image. + +> In the lab's **images** folder, you also find the public domain image **berries.png** (author Scott Bauer, via http://commons.wikimedia.org) needed in the assignment. + +![(Alternatively, you see the image embedded if you open this assignment sheet in a separate browser window).](.md_images/box_assignment.png) + +## Position of elements + +So far, we have modified the way how individual elements look and how much space should be left inside or outside the margin. + +Next, you learn how to modify the position, width, and height of a block element. As a result, you will be able to generate exactly the desired layout for your web page. + +There are three positioning schemes in CSS: + +1. Normal flow of positioning +2. Floating positioning +3. Absolute positioning + +So far, we have used normal positioning where elements are stacked on top of each other in the order they appear in the HTML file. + +Next, let's take a look at the alternative positioning schemes. + +### Float positioning + +A floating element is an element whose location in the web page is treated in a special way, so it is removed from the normal flow of positioning. The remaining elements on the page are rendered so that they wrap around the floating element. + +Floating positioning is common for figures. A floating image can be positioned in the desired location on the page, and any surrounding text paragraphs and other elements can be set to surround the image nicely. + +Open the file **floating.html** and view the HTML file as well as the linked CSS file **floatstyles.css**, located in the **css** directory. + +The HTML file contains one image tag. First, add an identifier for the figure holding the image, as it is going to be treated in a different way compared to potential other figures in the document: + +```html +
    +Onions +
    Onions are delicious.
    +
    +``` + +Next, open the linked CSS file **floatstyles.css** and add the following rule: +```css +figure#floating { + float: right; +} +``` + +Reopen the page in the browser. The figure is now positioned to the right, together with the caption. The text wraps around the figure. + +Next, we make a floating fact box that contains multiple elements. Add the following files in the HTML file **floating.html** right after the main heading, before any paragraphs: + + +```html +
    +

    Did you know?

    +
      +
    • The latin name for onion plant is Allium Cepa.
    • +
    • Onions have been used in cooking for thousands of years.
    • +
    • 89% of onion's content is water.
    • +
    +
    + +``` + +This time, the floating entity consists of two elements: a paragraph (`p`) and an unordered list (`ul`). Thus, we need a special container element that contains both the paragraph and the list. + +There are two alternatives for the container: either a generic `div` element or a more semantic `section` element. As the header and the list in the fact box clearly form a semantic entity, it is advisable to use the latter. + +Now, add the rule for the `section` element whose identifier is `factbox`: + +```css +section#factbox { + float: left; + border: 1px solid black; + margin: 2em; + padding: 1em; +} +``` + +Reload the page to see the effect of the modification. The fact box should appear bordered on the left side of the content, text wrapping around it. + +Test, how the floating elements behave as you resize the window. + +### Absolute positioning + +The third positioning scheme is absolute positioning. In this scheme, an element's absolute position is given, usually in pixels or percentage. + +Next, let's create navigation links for the onion page. Initially, we set the links to be located in accordance with the default flow of the elements. Then, we use CSS to reposition the links into the top left edge of the screen. + +Add the navigation information (using the HTML5 `nav` element) into the bottom of **floating.html** file, inside the body element: + +```html + +``` + +Note that the files that the links point to do not exist. We could add them later. + +Reload the page and see how the links are positioned by default. + +To move the navigation items to the left edge, we use absolute positioning. Add the following rule for the nav element to the CSS file: +```css +nav { + position: absolute; + top: 120px; + left: 40px; + width: 100px; +} +``` + +This sets the absolute position of the element to be exactly 120 pixels from the top of the page and exactly 40 pixels from the left edge of the page. Furthermore, the width of the element is set to 100 pixels. + +Note that the elements positioned using absolute positioning may appear on top of other elements, essentially garbling the output. To avoid this, we move the left edge of the page contents to 200px, to facilitate the 100px wide navigation element, starting at 40px. Modify the rule for `body` element to contain the new `margin-left` property: + +```css +body { + font-family: Arial, Verdana, sans-serif; + margin-left: 200px; +} +``` + +### Test your understanding + +1. Open file **columns.html**. Generate a style sheet **columnstyles.css** where the paragraphs are displayed in: + * two columns + * three columns + + > Search for CSS properties containing the word `column`. Currently, there are browser restrictions and inconsistencies in displaying multi-column content, but the workarounds are well documented. Try to take these into account in your CSS. + +2. In Lab 3 (HTML5), you created a home page of fictional Jane or John Doe, or yourself (If you didn't do that lab, quickly generate a home page). Build a CSS file for the home page that displays the information in a nicely formatted fashion. Position the image as a floating element on either edge of the page. + + +## Navigation + +The next task is to build a navigation menu bar to the website using CSS. Depending on the number of menu items, the menu bar can contain multi-level menus. + +A menu bar is be defined as an HTML unordered list. Each menu in a menu bar is a list item. + +The menus can contain submenus. A submenu is coded as a list that is within the parent menu's list item. + +Open file **menu.html** as well as the linked CSS file in the **css** directory called **menustyles.css**. + +Try the functionality of the two-level menu by hovering the mouse pointer over the **Item 1**, **Item 2**, and **Item 3** texts. The last two have submenus. + +Examine, how the hierarchical menu was created using plain CSS. Note that the links are just dummies; they don't lead to any web pages. In the example, the barebones menu bar contains only minimum formatting that is necessary for correct layout. + +Important things to note: + +- List bullets are removed by setting `list-style: none`. +- Second-level list items are set, by default, hidden: `visibility:hidden`. +- Whenever the mouse pointer hovers over the first-level list item, the second level list item is set visible. The selector `nav ul li:hover > li` points to those `li` items that are descendants of `nav`, `ul`, and (one that a mouse hovers over) `li` elements. + +### Test your understanding + +You need the aforementioned file `menustyles.css`. + +1. Add a fourth menu with a few menu items to **menu.html**. + +2. Use CSS to format the menu bar in the way that is shown in file **menu.png** in the lab's **images** folder. + ![(Alternatively, you see the image embedded if you open this assignment sheet in a separate browser window).](.md_images/menu.png) +3. Make one of the second-level menu items to contain a third-level menu. Make necessary amendments to the CSS file. diff --git a/labs/05 Responsive Layout with CSS3/boxmodel.html b/06 Advanced CSS3/workspace/boxmodel.html old mode 100644 new mode 100755 similarity index 100% rename from labs/05 Responsive Layout with CSS3/boxmodel.html rename to 06 Advanced CSS3/workspace/boxmodel.html diff --git a/labs/05 Responsive Layout with CSS3/columns.html b/06 Advanced CSS3/workspace/columns.html old mode 100644 new mode 100755 similarity index 100% rename from labs/05 Responsive Layout with CSS3/columns.html rename to 06 Advanced CSS3/workspace/columns.html diff --git a/labs/05 Responsive Layout with CSS3/css/floatstyles.css b/06 Advanced CSS3/workspace/css/floatstyles.css old mode 100644 new mode 100755 similarity index 100% rename from labs/05 Responsive Layout with CSS3/css/floatstyles.css rename to 06 Advanced CSS3/workspace/css/floatstyles.css diff --git a/labs/05 Responsive Layout with CSS3/css/menustyles.css b/06 Advanced CSS3/workspace/css/menustyles.css old mode 100644 new mode 100755 similarity index 100% rename from labs/05 Responsive Layout with CSS3/css/menustyles.css rename to 06 Advanced CSS3/workspace/css/menustyles.css diff --git a/labs/05 Responsive Layout with CSS3/floating.html b/06 Advanced CSS3/workspace/floating.html old mode 100644 new mode 100755 similarity index 98% rename from labs/05 Responsive Layout with CSS3/floating.html rename to 06 Advanced CSS3/workspace/floating.html index 9a3a719..d45bda9 --- a/labs/05 Responsive Layout with CSS3/floating.html +++ b/06 Advanced CSS3/workspace/floating.html @@ -14,7 +14,7 @@

    Onions

    Onions in cooking

    -Onions +Onions
    Image: Petr Kratochvil, public domain via www.publicdomainpictures.net.
    diff --git a/labs/05 Responsive Layout with CSS3/img/berries.png b/06 Advanced CSS3/workspace/images/berries.png old mode 100644 new mode 100755 similarity index 100% rename from labs/05 Responsive Layout with CSS3/img/berries.png rename to 06 Advanced CSS3/workspace/images/berries.png diff --git a/06 Advanced CSS3/workspace/images/box_assignment.png b/06 Advanced CSS3/workspace/images/box_assignment.png new file mode 100755 index 0000000..4b0a022 Binary files /dev/null and b/06 Advanced CSS3/workspace/images/box_assignment.png differ diff --git a/06 Advanced CSS3/workspace/images/menu.png b/06 Advanced CSS3/workspace/images/menu.png new file mode 100755 index 0000000..bc57010 Binary files /dev/null and b/06 Advanced CSS3/workspace/images/menu.png differ diff --git a/labs/05 Responsive Layout with CSS3/img/onions.png b/06 Advanced CSS3/workspace/images/onions.png old mode 100644 new mode 100755 similarity index 100% rename from labs/05 Responsive Layout with CSS3/img/onions.png rename to 06 Advanced CSS3/workspace/images/onions.png diff --git a/labs/05 Responsive Layout with CSS3/menu.html b/06 Advanced CSS3/workspace/menu.html old mode 100644 new mode 100755 similarity index 100% rename from labs/05 Responsive Layout with CSS3/menu.html rename to 06 Advanced CSS3/workspace/menu.html diff --git a/07 JS syntax/.md_images/debug.png b/07 JS syntax/.md_images/debug.png new file mode 100755 index 0000000..a48a13e Binary files /dev/null and b/07 JS syntax/.md_images/debug.png differ diff --git a/07 JS syntax/.md_images/error.png b/07 JS syntax/.md_images/error.png new file mode 100755 index 0000000..31c6dec Binary files /dev/null and b/07 JS syntax/.md_images/error.png differ diff --git a/07 JS syntax/.md_images/inspect.png b/07 JS syntax/.md_images/inspect.png new file mode 100755 index 0000000..bce3537 Binary files /dev/null and b/07 JS syntax/.md_images/inspect.png differ diff --git a/07 JS syntax/.md_images/lab7.png b/07 JS syntax/.md_images/lab7.png new file mode 100755 index 0000000..966c528 Binary files /dev/null and b/07 JS syntax/.md_images/lab7.png differ diff --git a/07 JS syntax/.md_images/notes.png b/07 JS syntax/.md_images/notes.png new file mode 100755 index 0000000..64783e3 Binary files /dev/null and b/07 JS syntax/.md_images/notes.png differ diff --git a/07 JS syntax/.md_images/preview.png b/07 JS syntax/.md_images/preview.png new file mode 100755 index 0000000..2667b72 Binary files /dev/null and b/07 JS syntax/.md_images/preview.png differ diff --git a/07 JS syntax/README.md b/07 JS syntax/README.md new file mode 100755 index 0000000..4251858 --- /dev/null +++ b/07 JS syntax/README.md @@ -0,0 +1,143 @@ +# JavaScript syntax + +From this week, we start doing JavaScript, which is the language for the modern web development, and also the language of the future. + +You need to learn JS for the following reasons (since I've been asked several times in the past): + +1. You want to use JS to add interactivity to your website, which greatly enhances user experience. +2. JS is an essential component of Bootstrap, which is a popular front-end framework introduced later in the module. +3. You need JS for your 3rd-year modules such as [304CEM: Web API Development](http://resourcelists.coventry.ac.uk/modules/304cem.html), that teaches [full stack JavaScript development](https://www.smashingmagazine.com/2013/11/introduction-to-full-stack-javascript/). + +To help you get acquainted with the syntax used in JavaScript, use a tutorial such as [Mozilla developer network](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Introduction) or [W3Schools JavaScript tutorial](http://www.w3schools.com/js/). You will learn that several JavaScript constructs, such as the control structures are very similar to Java. + +> For this and following labs, it's necessary to clone the module repository in your workspace. Just follow instructions given in the first lab. + +> Some of the JS exercises were based on tutorials written by [Colin Stephen](https://github.com/c0lin). + +## Edit and test JS code + +In the Workspace tool window navigate to lab7 ==> workspace, the folder structure looks like this: + +![](.md_images/lab7.png) + +Double-click to open the file **basic_math.html**, and right-click on the same file to preview. + +![](.md_images/preview.png) + +In the same manner, open **shopping.js** located in the `js` folder. + +Now do the following: + +* Try out the application by entering different values and clicking on the Calculate link. +* Read through comments in the JS file, and try to understand the code. You can now make changes to the code. + +### Test your understanding + +1. The values read from the text boxes are stored as *strings*. Modify the code to store the quantity to an *integer* and the price, tax, and discount as *floats* (hint, check the crib sheet). +2. Modify the JS code to double the discount applied when the quantity ordered is greater than 100 items +3. Modify the HTML form to include a *shipping* field, and include this value in the total calculation in `shopping.js`. + +You'll need to press `ctrl` + `s` to save changes after modification. To view the modified version simply refresh the web browser tab running your web page. + +## Chrome developer tools + +You've (hopefully) seen Chrome developer tools in the previous lab, where you inspected CSS rules applied to HTML elements. In the example that follows you will be using the Chrome Developer Tools to see outputs from JS code. + +### Console output + +Load up **conditionals_booleans.html** and run it in a new browser tab. +Enter some valid inputs into the form and submit it. + +"Nothing" happens. . . Actually, the result is being logged to the console which is a part of the Chrome Developer Tools integrated into the browser. + +![](.md_images/inspect.png) + +1. Right click within the preview browser window and select Inspect to open the Developer Tools panel. This can be docked to the right or bottom of the main window (bottom preferable). +2. Click on the console tab and try submitting the form again (you can ignore errors at this point) to see the output. +3. Go back to **contact.js** to see where this output comes from + +### Test your understanding + +1. Load up **conditionals_booleans.html** and modify the HTML form to include a "Full Name" field +2. Add validation in contact.js to ensure that the name entered has, at least, two parts separated by space, and is between 3 and 100 characters in length + +## Language analysis + +### JSLint code checking + +Writing clean, readable, and error-free high-quality code is essential to maintainability. Therefore most languages now have automated syntax and style "checkers" called linters that code editors and IDEs can use to alert potential problems to programmers immediately when they are writing their code. + +One such linter for JavaScript is called [JSLint](http://www.jslint.com/), and it is built into most online IDEs and enabled by default. + +> [JSLint](http://www.jslint.com/) provides checks based on direct input, similar to HTML/CSS validation. + +Load up **notes.js** and you'll see immediately there are some highlights in the gutter. If you move your mouse over you'll see some hints on the error. + +![](.md_images/error.png) + +There are three types of identifiers: + +1. Informational: these are non-critical, non-dangerous updates about your code +2. Warnings: these are potentially incorrect pieces of code +3. Errors: these are incorrect lines of code that will almost certainly throw a runtime error when you try to run/compile your code + + +Preview **notes.html** and input some texts to prove the code is fully functional. + +![](.md_images/notes.png) + +Next, open **notes.js** and make sure you understand how it works. + +1. Click on the icons in the gutter to read warning messages +2. Try to make changes to correctly format the code + +Note that many of the highlighted issues are about things like indentation, and consistency of style, as well as syntax. That is because linters take readable code very seriously, and so should you! You should aim to have no JSLint warnings when you write your JavaScript or other programming code. + +### Strict mode + +JavaScript’s strict mode allows you to use a safer, restricted version of the language. It makes changes to the semantics to help you avoid many common errors. For example, if you don’t use the `var` keyword when declaring variables these become globally scoped. With strict mode enabled these get flagged up as errors. + +To enable strict mode, you simply add a line of code. + +`"use strict";` + +This can be added to the top of your script if you want it to apply to everything, alternatively, it can be implemented only in certain blocks of code by adding it there instead. + +Click [here](http://www.w3schools.com/js/js_strict.asp) for some more examples on `use strict`' + +### Test your understanding + +Once you have corrected for any JSLint errors found by the code linter (**notes.js**), copy/paste the code into [JSLint](http://www.jslint.com/) and run the analysis. Google the error messages that pop up, and make changes as appropriate. + +## Basic debugging + +For a much more detailed description read [Google’s debugging JavaScript guide](https://developer.chrome.com/devtools/docs/javascript-debugging). + +![](https://developer.chrome.com/devtools/docs/javascript-debugging/javascript-debugging-overview.jpg) + +Sometimes your attempts to change the code will introduce bugs. A bug is usually signalled by some kind of error message, usually RED in colour, appearing on the console. If you find a bug has appeared in your code do the following to help track it down: + +1. Note the line number that caused the problem, if any is mentioned in the console output (sometimes it is not) +2. Load the 'Sources' tab in the developer tools area and select the JS file +3. Click in the margin next to any line number to add a code break (add it near but before the suspect line); you can add several if you wish +4. Now refresh the page and proceed as normal until the code execution stops at the line break you created +5. On the information tab on the right-hand side, you will see details about the call stack and currently scoped variables to help you pinpoint the state of your program +6. Use the "Step over / into / out of" buttons to execute your JS line by line and keep an eye on the stack and scope to catch anything unexpected + +Let's get familiar with the debugger: + +1. Make sure you're viewing your webpage using the live preview in a new tab. This is important. +1. Click on the Sources tab and open the notes.js file +2. Click in the margin to add a breakpoint to the first line of the addItem() function. +3. Refresh the browser, type in a new item and click the button. The execution will pause at the breakpoint without running line 5. +4. Click on the Step into next function call button (down arrow with dot). This will run line 5 and move execution to line 6. Click this button to execute the code line by line. +5. Observe the variables changing in the right-hand panel. + +![](.md_images/debug.png) + +### Test your understanding + +Use the debugger to help fix a problem with our program: + +It is currently possible to add duplicate items. Instead of adding duplicated the quantity should increase by one. + diff --git a/07 JS syntax/workspace/basic_math.html b/07 JS syntax/workspace/basic_math.html new file mode 100755 index 0000000..235f295 --- /dev/null +++ b/07 JS syntax/workspace/basic_math.html @@ -0,0 +1,46 @@ + + + + + + Bulk Order Calculator + + + + + + +
    +
    +

    Calculate a bulk book order.

    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + +
    +
    +
    + + + + \ No newline at end of file diff --git a/labs/07 JavaScript Tools/conditionals_booleans.html b/07 JS syntax/workspace/conditionals_booleans.html old mode 100644 new mode 100755 similarity index 55% rename from labs/07 JavaScript Tools/conditionals_booleans.html rename to 07 JS syntax/workspace/conditionals_booleans.html index 0c25517..5b87a45 --- a/labs/07 JavaScript Tools/conditionals_booleans.html +++ b/07 JS syntax/workspace/conditionals_booleans.html @@ -1,5 +1,6 @@ + Membership Form @@ -8,15 +9,24 @@ +
    -
    Contact -
    -
    +
    + Contact +
    + + +
    +
    + + +
    + \ No newline at end of file diff --git a/labs/07 JavaScript Tools/css/styles.css b/07 JS syntax/workspace/css/styles.css old mode 100644 new mode 100755 similarity index 94% rename from labs/07 JavaScript Tools/css/styles.css rename to 07 JS syntax/workspace/css/styles.css index 8e97505..446f224 --- a/labs/07 JavaScript Tools/css/styles.css +++ b/07 JS syntax/workspace/css/styles.css @@ -1,157 +1,157 @@ -// From: http://www.assemblesoft.com/examples/form/simpleform.html -* {margin:0; padding:0;} - -html { - font: 90%/1.3 arial,sans-serif; - padding:1em; - background:#B9C2CC; -} -form { - background:#fff; - padding:1em; - border:1px solid #eee; -} - -fieldset div { - margin:0.3em 0; - clear:both; -} - -form { - margin:1em; - width:15em; -} - -label { - float:none; - width:12em; - display:block; - clear:both; -} - -legend { - color:#0b77b7; - font-size:1.4em; -} -legend span { - width:10em; - text-align:right; -} -input { - padding:0.15em; - width:10em; - border:1px solid #ddd; - background:#fafafa; - font:bold 1em arial, sans-serif; - -moz-border-radius:0.4em; - -khtml-border-radius:0.4em; - display:block; - float:left; -} -input:hover, input:focus { - border-color:#c5c5c5; - background:#f6f6f6; -} -fieldset { - border:1px solid #ddd; - padding:0 0.5em 0.5em; -} -.date input { - background-image:url(../gfx/calendar-small.gif); - background-repeat:no-repeat; - background-position:100% 50%; -} - -.date fieldset label { - float:none; - display:block; - text-align:left; - width:auto; -} -.date fieldset div { - float:left; - clear:none; - margin-right:0.2em; -} -.radio, .date { - position:relative; -} -.radio fieldset, .date fieldset { - border:none; - width:auto; - padding:1px 0 0 11em; -} -.radio legend, .date legend { - font-size:1em; - color:#000; -} -.radio legend span, .date legend span { - position:absolute; - left:0; - top:0.3em; - width:10em; - display:block; -} -.radio label, .radio input { - vertical-align:middle; - display:inline; - float:none; - width:auto; - background:none; - border:none; -} -.radio div { - float:left; - white-space:nowrap; - clear:none; -} - -.email { - width:14em; -} - -input.default { - color:#bbb; -} - -#submit { - margin:1em; - width:69px; - height:26px; - overflow:hidden; - border:0; - display:block; - cursor:pointer !important; cursor:hand; -} -#submit:hover { - background-position:0 -26px; -} - -textarea { - padding:0.15em; - width: 200px; - height: 50px; - display: block; -} - -.error { - color: ##FF1400; -} -/* -:invalid { - color:red; -} -*/ -/* -input[type=checkbox], input[type=radio] { visibility: hidden; width:0; height:0; padding:0; margin:0; } -input[type=checkbox] + label, input[type=radio] + label { padding-left:18px; } -input[type=checkbox] + label{ background: url(../gfx/check_radio.png) 0 0 no-repeat; } -input[type=checkbox]:focus + label{ background-position: 0 -16px; } -input[type=checkbox] + label:hover{ background-position: 0 -32px; } -input[type=checkbox]:checked + label{ background-position: 0 -48px; } - -input[type=radio] + label{ background: url(../gfx/check_radio.png) 0 -64px no-repeat; } -input[type=radio]:focus + label{ background-position: 0 -80px; } -input[type=radio] + label:hover{ background-position: 0 -96px; } -input[type=radio]:checked + label{ background-position: 0 -112px; } +// From: http://www.assemblesoft.com/examples/form/simpleform.html +* {margin:0; padding:0;} + +html { + font: 90%/1.3 arial,sans-serif; + padding:1em; + background:#B9C2CC; +} +form { + background:#fff; + padding:1em; + border:1px solid #eee; +} + +fieldset div { + margin:0.3em 0; + clear:both; +} + +form { + margin:1em; + width:15em; +} + +label { + float:none; + width:12em; + display:block; + clear:both; +} + +legend { + color:#0b77b7; + font-size:1.4em; +} +legend span { + width:10em; + text-align:right; +} +input { + padding:0.15em; + width:10em; + border:1px solid #ddd; + background:#fafafa; + font:bold 1em arial, sans-serif; + -moz-border-radius:0.4em; + -khtml-border-radius:0.4em; + display:block; + float:left; +} +input:hover, input:focus { + border-color:#c5c5c5; + background:#f6f6f6; +} +fieldset { + border:1px solid #ddd; + padding:0 0.5em 0.5em; +} +.date input { + background-image:url(../gfx/calendar-small.gif); + background-repeat:no-repeat; + background-position:100% 50%; +} + +.date fieldset label { + float:none; + display:block; + text-align:left; + width:auto; +} +.date fieldset div { + float:left; + clear:none; + margin-right:0.2em; +} +.radio, .date { + position:relative; +} +.radio fieldset, .date fieldset { + border:none; + width:auto; + padding:1px 0 0 11em; +} +.radio legend, .date legend { + font-size:1em; + color:#000; +} +.radio legend span, .date legend span { + position:absolute; + left:0; + top:0.3em; + width:10em; + display:block; +} +.radio label, .radio input { + vertical-align:middle; + display:inline; + float:none; + width:auto; + background:none; + border:none; +} +.radio div { + float:left; + white-space:nowrap; + clear:none; +} + +.email { + width:14em; +} + +input.default { + color:#bbb; +} + +#submit { + margin:1em; + width:69px; + height:26px; + overflow:hidden; + border:0; + display:block; + cursor:pointer !important; cursor:hand; +} +#submit:hover { + background-position:0 -26px; +} + +textarea { + padding:0.15em; + width: 200px; + height: 50px; + display: block; +} + +.error { + color: ##FF1400; +} +/* +:invalid { + color:red; +} +*/ +/* +input[type=checkbox], input[type=radio] { visibility: hidden; width:0; height:0; padding:0; margin:0; } +input[type=checkbox] + label, input[type=radio] + label { padding-left:18px; } +input[type=checkbox] + label{ background: url(../gfx/check_radio.png) 0 0 no-repeat; } +input[type=checkbox]:focus + label{ background-position: 0 -16px; } +input[type=checkbox] + label:hover{ background-position: 0 -32px; } +input[type=checkbox]:checked + label{ background-position: 0 -48px; } + +input[type=radio] + label{ background: url(../gfx/check_radio.png) 0 -64px no-repeat; } +input[type=radio]:focus + label{ background-position: 0 -80px; } +input[type=radio] + label:hover{ background-position: 0 -96px; } +input[type=radio]:checked + label{ background-position: 0 -112px; } */ \ No newline at end of file diff --git a/labs/07 JavaScript Tools/js/contact.js b/07 JS syntax/workspace/js/contact.js old mode 100644 new mode 100755 similarity index 86% rename from labs/07 JavaScript Tools/js/contact.js rename to 07 JS syntax/workspace/js/contact.js index 22ca200..2220519 --- a/labs/07 JavaScript Tools/js/contact.js +++ b/07 JS syntax/workspace/js/contact.js @@ -8,25 +8,23 @@ function process() { // Variable to represent validity: var okay = true; - + // Get form references: var email = document.getElementById('email'); var comments = document.getElementById('comments'); // Validate the email address: - if (!email || !email.value - || (email.value.length < 6) - || (email.value.indexOf('@') == -1)) { + if (!email || !email.value || (email.value.length < 6) || (email.value.indexOf('@') == -1)) { okay = false; alert('Please enter a valid email address!'); } // Validate the comments: var maxLength = 100; - if (!comments || !comments.value - || (comments.value.indexOf('<') != -1) ) { + if (!comments || !comments.value || (comments.value.indexOf('<') != -1)) { okay = false; alert('Please enter your comments, without any HTML!'); - } else if (comments.value.length > maxLength) { + } + else if (comments.value.length > maxLength) { okay = false; var originalText = comments.value; // Find the last space before the limit: @@ -38,7 +36,7 @@ function process() { alert('Comment was trimmed under ' + maxLength + ' characters'); } - + // Normally you would "return okay;" here to submit/block the form submission // return okay; @@ -46,7 +44,8 @@ function process() { var message; if (okay) { message = "Form submitted"; - } else { + } + else { message = "Form not submitted"; } console.log(message); @@ -56,7 +55,7 @@ function process() { // Prevent submission for the purposes of this example: return false; - + } // End of process() function. // Initial setup: diff --git a/labs/07 JavaScript Tools/js/membership.js b/07 JS syntax/workspace/js/membership.js old mode 100644 new mode 100755 similarity index 100% rename from labs/07 JavaScript Tools/js/membership.js rename to 07 JS syntax/workspace/js/membership.js diff --git a/labs/07 JavaScript Tools/js/notes.js b/07 JS syntax/workspace/js/notes.js old mode 100644 new mode 100755 similarity index 100% rename from labs/07 JavaScript Tools/js/notes.js rename to 07 JS syntax/workspace/js/notes.js diff --git a/07 JS syntax/workspace/js/shopping.js b/07 JS syntax/workspace/js/shopping.js new file mode 100755 index 0000000..99d6e86 --- /dev/null +++ b/07 JS syntax/workspace/js/shopping.js @@ -0,0 +1,59 @@ +// shopping.js +// This script calculates an order total. + +// Function called when the form is submitted. +// Function performs the calculation and returns false. +function calculate() { + 'use strict' + + // For storing the order total: + var total; + + // Get references to the form values: + var quantity = document.getElementById('quantity').value; + var price = document.getElementById('price').value; + var tax = document.getElementById('tax').value; + var discount = document.getElementById('discount').value; + + // Add validation here later! + + // Calculate the initial total: + total = quantity * price; + console.log('total before tax: ' + total); + + // Make the tax rate easier to use: + tax = tax / 100; + tax = tax + 1; + + // Factor in the tax: + total = total * tax; + console.log("total after tax: " + total); + + // Factor in the discount: + total = total - discount; + console.log("total after discount: " + total); + + // Format the total to two decimal places: + total = total.toFixed(2); + + // Display the total: + document.getElementById('total').value = total; + + // Return false to prevent submission: + return false; + +} // End of calculate() function. + +// Function called when the window has been loaded. +// Function needs to add an event listener to the form. +function init() { + 'use strict'; + + // Add an event listener to the form: + var theForm = document.getElementById('theForm'); + theForm.onsubmit = calculate; + +} // End of init() function. + +// Assign an event listener to the window's load event: +window.onload = init; \ No newline at end of file diff --git a/labs/07 JavaScript Tools/notes.html b/07 JS syntax/workspace/notes.html old mode 100644 new mode 100755 similarity index 100% rename from labs/07 JavaScript Tools/notes.html rename to 07 JS syntax/workspace/notes.html diff --git a/labs/07 JavaScript Tools/switch_case.html b/07 JS syntax/workspace/switch_case.html old mode 100644 new mode 100755 similarity index 100% rename from labs/07 JavaScript Tools/switch_case.html rename to 07 JS syntax/workspace/switch_case.html diff --git a/08 JS function and objects/README.md b/08 JS function and objects/README.md new file mode 100755 index 0000000..a06ddc2 --- /dev/null +++ b/08 JS function and objects/README.md @@ -0,0 +1,68 @@ +# Function and objects + +This lab introduces the two key data types in JS: objects and functions. Get used to their basic syntax and common use cases. + +Aim to complete these in roughly 30-60 minutes of lab time. + +1. Write a simple function to remove duplicates from a task list. +2. Nest objects to define the internal sub-components of a car and engine. +3. Use an inner "helper function" to calculate fuel consumption. + +## Array manipulation + +In this task, you will write a simple function to detect and remove duplicates from a task list. + +1. Open the lab08 workspace containing the lab code and open the file **simple_function.html**. +2. Preview this file in a browser tab and test to see it's working. +3. Open the associated JS file **tasks.js** and check you understand how it works. + +### Test your understanding + +You need the aforementioned files **js/tasks.js** and **simple_function.html**. + +1. Add a button to the HTML page labelled "Remove duplicates". +2. When clicked, this button should trigger a new function removeDuplicates() against the tasks list. +3. The removal function should parse the list and remove any identical copies of previous items (leaving at most one). + +## Nested objects + +In this task, you will extend an existing JS object by nesting further object(s). + +1. Open the **function_returns_object.html** file. +2. Run the page in Chrome and open the developer tools JS console. + + > All output from the JS appears on the console. + +3. Refresh the browser to see alternative outputs. +4. Review the JS in the HTML file and ensure you understand how it works. + +### Test your understanding + +1. Add a `makeEngine()` function that constructs and returns random engine objects. Engines might be characterised by: + + - Cylinders + - CC Volume + - Fuel type (diesel, petrol, etc.) + - Max torque + +2. Use the new function inside the `makeCar()` function, to make an engine and add it to the `car` object returned by `makeCar()`. +3. Ensure you include the engine details in the console log to show that it works + +## Nested functions + +In this task, you will modify an existing method of an object and "factor out" some of its code into a separate internal "helper function" which does an intermediary calculation. + +1. Open the `object_methods.html` file. +2. Run the page in Chrome and then open the developer tools JS console. + + > all output from the JS appears on the console. + +3. Review the JS in the HTML file and ensure you understand how it works. + +### Test your understanding + +1. Add an empty internal updateFuel() function within the drive method of the fiat object. +2. Add code to the new helper function which updates the fuel by decrementing the fiat’s fuel by 1. +3. In the original drive() method, instead of the assignment this.fuel = this.fuel - 1, invoke your new function. +4. Refresh the page and ensure that the console output is exactly the same as before. +5. Also, test it by adding an appropriate code break in the "Sources" tab of your file in Chrome developer tools, and watch the value of fiat.fuel change as you step through the calls to the fiat.drive() method. diff --git a/labs/08 JavaScript Language/css/form.css b/08 JS function and objects/workspace/css/form.css old mode 100644 new mode 100755 similarity index 93% rename from labs/08 JavaScript Language/css/form.css rename to 08 JS function and objects/workspace/css/form.css index d7ed5b2..cd59a33 --- a/labs/08 JavaScript Language/css/form.css +++ b/08 JS function and objects/workspace/css/form.css @@ -1,92 +1,92 @@ -/* -dark blue: 212B40 -gray-blue: 547B97 -gray-green-blue: BADCDD -light-green: C2E078 -*/ -/* -dark blue: 1B1D26 -dark green: 425955 -gray-green: 778C7A -off-white: F1F2D8 -tan: BFBD9F -*/ - -body{ -font-family:"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif; -font-size:12px; -color: #1b1d26; -background-color: #f1f2d8; -} -p, h1, form, button{border:0; margin:0; padding:0;} -.spacer{clear:both; height:1px;} -/* ----------- My Form ----------- */ -form{ -margin:0 auto; -width:400px; -padding:14px; -background-color: #ffffff; -border:solid 2px #425955; -} - -/* ----------- stylized ----------- */ - h1 { -font-size:14px; -font-weight:bold; -margin-bottom:8px; -} - p{ -font-size:11px; -color:#666666; -margin-bottom:20px; -border-bottom:solid 1px #BFBD9F; -padding-bottom:10px; -} - label{ -display:block; -font-weight:bold; -text-align:right; -width:140px; -float:left; -} - .small{ -color:#666666; -display:block; -font-size:11px; -font-weight:normal; -text-align:right; -width:140px; -} - select{ -float:left; -font-size:12px; -padding:4px 2px; -border:solid 1px #BFBD9F; -width:200px; -margin:2px 0 20px 10px; -} - input{ -float:left; -font-size:12px; -padding:4px 2px; -border:solid 1px #BFBD9F; -width:200px; -margin:2px 0 20px 10px; -} -#submit{ -clear:both; -margin-left:150px; -width:125px; -height:31px; -background:#F1F2D8; -text-align:center; -line-height:20px; -color:#000000; -font-size:12px; -font-weight:bold; -} -#output { - clear:both; - margin-bottom: 10px; - color: blue; -} +/* +dark blue: 212B40 +gray-blue: 547B97 +gray-green-blue: BADCDD +light-green: C2E078 +*/ +/* +dark blue: 1B1D26 +dark green: 425955 +gray-green: 778C7A +off-white: F1F2D8 +tan: BFBD9F +*/ + +body{ +font-family:"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif; +font-size:12px; +color: #1b1d26; +background-color: #f1f2d8; +} +p, h1, form, button{border:0; margin:0; padding:0;} +.spacer{clear:both; height:1px;} +/* ----------- My Form ----------- */ +form{ +margin:0 auto; +width:400px; +padding:14px; +background-color: #ffffff; +border:solid 2px #425955; +} + +/* ----------- stylized ----------- */ + h1 { +font-size:14px; +font-weight:bold; +margin-bottom:8px; +} + p{ +font-size:11px; +color:#666666; +margin-bottom:20px; +border-bottom:solid 1px #BFBD9F; +padding-bottom:10px; +} + label{ +display:block; +font-weight:bold; +text-align:right; +width:140px; +float:left; +} + .small{ +color:#666666; +display:block; +font-size:11px; +font-weight:normal; +text-align:right; +width:140px; +} + select{ +float:left; +font-size:12px; +padding:4px 2px; +border:solid 1px #BFBD9F; +width:200px; +margin:2px 0 20px 10px; +} + input{ +float:left; +font-size:12px; +padding:4px 2px; +border:solid 1px #BFBD9F; +width:200px; +margin:2px 0 20px 10px; +} +#submit{ +clear:both; +margin-left:150px; +width:125px; +height:31px; +background:#F1F2D8; +text-align:center; +line-height:20px; +color:#000000; +font-size:12px; +font-weight:bold; +} +#output { + clear:both; + margin-bottom: 10px; + color: blue; +} diff --git a/08 JS function and objects/workspace/function_returns_object.html b/08 JS function and objects/workspace/function_returns_object.html new file mode 100755 index 0000000..dfe5d40 --- /dev/null +++ b/08 JS function and objects/workspace/function_returns_object.html @@ -0,0 +1,45 @@ + + + + + + Object-o-matic + + + + + + diff --git a/08 JS function and objects/workspace/function_using_object.html b/08 JS function and objects/workspace/function_using_object.html new file mode 100755 index 0000000..2e1e3a4 --- /dev/null +++ b/08 JS function and objects/workspace/function_using_object.html @@ -0,0 +1,42 @@ + + + + + + Employee Form + + + + + + +
    +
    + Add an Employee +
    + + +
    +
    + + +
    +
    + + +
    + +
    +
    +
    + + + + \ No newline at end of file diff --git a/labs/08 JavaScript Language/hidden_parameters.html b/08 JS function and objects/workspace/hidden_parameters.html old mode 100644 new mode 100755 similarity index 97% rename from labs/08 JavaScript Language/hidden_parameters.html rename to 08 JS function and objects/workspace/hidden_parameters.html index 27d09ee..2b9e559 --- a/labs/08 JavaScript Language/hidden_parameters.html +++ b/08 JS function and objects/workspace/hidden_parameters.html @@ -1,10 +1,13 @@ + Hidden Parameters + + \ No newline at end of file diff --git a/labs/08 JavaScript Language/js/employee.js b/08 JS function and objects/workspace/js/employee.js old mode 100644 new mode 100755 similarity index 96% rename from labs/08 JavaScript Language/js/employee.js rename to 08 JS function and objects/workspace/js/employee.js index 1a58fa5..53e5c74 --- a/labs/08 JavaScript Language/js/employee.js +++ b/08 JS function and objects/workspace/js/employee.js @@ -1,45 +1,45 @@ -// employee.js -// This script creates an object using form data. - -// Function called when the form is submitted. -// Function creates a new object. -function process() { - 'use strict'; - - // Get form references: - var firstName = document.getElementById('firstName').value; - var lastName = document.getElementById('lastName').value; - var department = document.getElementById('department').value; - - // Reference to where the output goes: - var output = document.getElementById('output'); - - // Create a new object: - var employee = { - firstName: firstName, - lastName: lastName, - department: department, - hireDate: new Date() - }; // Don't forget the semicolon! - - console.log(employee); - - // Create the ouptut as HTML: - var message = '

    Employee Added

    Name: ' + employee.lastName + ', ' + employee.firstName + '
    '; - message += 'Department: ' + employee.department + '
    '; - message += 'Hire Date: ' + employee.hireDate.toDateString(); - - // Display the employee object: - output.innerHTML = message; - - // Return false: - return false; - -} // End of process() function. - -// Initial setup: -function init() { - 'use strict'; - document.getElementById('theForm').onsubmit = process; -} // End of init() function. -window.onload = init; +// employee.js +// This script creates an object using form data. + +// Function called when the form is submitted. +// Function creates a new object. +function process() { + 'use strict'; + + // Get form references: + var firstName = document.getElementById('firstName').value; + var lastName = document.getElementById('lastName').value; + var department = document.getElementById('department').value; + + // Reference to where the output goes: + var output = document.getElementById('output'); + + // Create a new object: + var employee = { + firstName: firstName, + lastName: lastName, + department: department, + hireDate: new Date() + }; // Don't forget the semicolon! + + console.log(employee); + + // Create the ouptut as HTML: + var message = '

    Employee Added

    Name: ' + employee.lastName + ', ' + employee.firstName + '
    '; + message += 'Department: ' + employee.department + '
    '; + message += 'Hire Date: ' + employee.hireDate.toDateString(); + + // Display the employee object: + output.innerHTML = message; + + // Return false: + return false; + +} // End of process() function. + +// Initial setup: +function init() { + 'use strict'; + document.getElementById('theForm').onsubmit = process; +} // End of init() function. +window.onload = init; diff --git a/labs/08 JavaScript Language/js/hidden_parameters.js b/08 JS function and objects/workspace/js/hidden_parameters.js old mode 100644 new mode 100755 similarity index 100% rename from labs/08 JavaScript Language/js/hidden_parameters.js rename to 08 JS function and objects/workspace/js/hidden_parameters.js diff --git a/labs/08 JavaScript Language/js/tasks.js b/08 JS function and objects/workspace/js/tasks.js old mode 100644 new mode 100755 similarity index 95% rename from labs/08 JavaScript Language/js/tasks.js rename to 08 JS function and objects/workspace/js/tasks.js index 586105d..0652dea --- a/labs/08 JavaScript Language/js/tasks.js +++ b/08 JS function and objects/workspace/js/tasks.js @@ -2,7 +2,7 @@ // This script manages a to-do list. // Need a global variable: -var tasks = []; +var tasks = []; // Function called when the form is submitted. // Function adds a task to the global array. @@ -14,15 +14,15 @@ function addTask() { // Reference to where the output goes: var output = document.getElementById('output'); - + // For the output: var message = ''; if (task.value) { - + // Add the item to the array: tasks.push(task.value); - + // Update the page: message = '

    To-Do

      '; for (var i = 0, count = tasks.length; i < count; i++) { @@ -30,12 +30,12 @@ function addTask() { } message += '
    '; output.innerHTML = message; - + } // End of task.value IF. // Return false to prevent submission: return false; - + } // End of addTask() function. // Initial setup: diff --git a/08 JS function and objects/workspace/object_methods.html b/08 JS function and objects/workspace/object_methods.html new file mode 100755 index 0000000..681b367 --- /dev/null +++ b/08 JS function and objects/workspace/object_methods.html @@ -0,0 +1,68 @@ + + + + + + Car with fuel + + + + + + + diff --git a/08 JS function and objects/workspace/simple_function.html b/08 JS function and objects/workspace/simple_function.html new file mode 100755 index 0000000..5da6dec --- /dev/null +++ b/08 JS function and objects/workspace/simple_function.html @@ -0,0 +1,29 @@ + + + + + + To-Do List + + + + + + +
    +
    + Enter an Item To Be Done +
    + + +
    + +
    +
    +
    + + + + diff --git a/09 JS modules/README.md b/09 JS modules/README.md new file mode 100755 index 0000000..e0dc68c --- /dev/null +++ b/09 JS modules/README.md @@ -0,0 +1,127 @@ +# JavaScript modules + +This lab covers objects and functions again, but with more advanced uses. These code patterns are what brings out the full power of JS and are essential for understanding JS code libraries that you might use for bigger projects. + +Below is an overall of files available in the current lab, and the associated level of skills required. In particular, the todo app in the `advanced_obj_fun` directory contains some example code that uses many of the key techniques you need to learn. Also, the application uses other files than those listed. However, by studying the four files listed below you will see how to implement the model part of _Model_ _View Controller_. + +* Basics + - first_class_functions.html (*not in excercises, take a look when you finish everything) + - function_closure.html + - js_classes.html +* Intermediate + - prototype_inheritance.html (*not in excercises) + - extending_js_types.html +* Advanced + - advanced_obj_fun/ + * js/ + - model.js + - store.js + - app.js + * index.html + +Aim to complete these tasks in roughly 60-80 minutes of lab time. + +1. Write a closure to maintain the internal state of a counter. +2. Define a constructor function and modify its prototype. +3. Create a new JS module. (For the adventurous!) + +## Use closure to maintain internal state + +Closure is the JS way to define a little environment that goes together with function. By 'environment' I meant variables etc that only this specific function can have access to. This is the JS way of doing encapsulation in a way similar to other languages such as Java. + +> "Closures are functions that refer to independent (free) variables. In other words, the function defined in the closure 'remembers' the environment in which it was created", from [Introduction to closures on Mozilla Developer Network (MDN)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures). See also discussions about [JavaScript Closures for Beginners On StackOverflow](http://stackoverflow.com/questions/111102/how-do-javascript-closures-work?rq=1). + +This is a bit difficult to explain without an example. Let's look at an example now: + +- Load the `function_closure.html` file and take a look at the functionality. +- Observe that the `button.onclick` event handler is a function that uses the count variable +- Observe also that this function does not specify any value for count +- Instead, count inside the handler function is given its value by the enclosing function context + +Since the `window.onload` handler function encloses the `button.onclick` one, the latter has an "environment" of local variables that it can work with. The `onclick` function is, therefore, able to keep track of the state of a higher-level variable, namely `count`. + +This technique adds a lot of power to the language. For example, as we see here, the state can be maintained in "private" scoped variables, even after the enclosing function has returned. + +### Test your understanding + +1. Add three buttons to the function_closure.html page: '-', '0', '+'. + > You can think of these as the playback speed controls for a media player + +2. Add a `
    ` with ID `playback-speed` to the page as well +3. Using the existing onload handler as an example, write another `window.onload` handler: + - This one should have an internal state representing the current playback speed + - Clicking on one of the '+' or '-' buttons should increment or decrement the current speed as appropriate + - Clicking on the '0' button should reset the current speed to 0 + - The current speed should be displayed in the `
    ` you added +4. If you have time, use a single helper function to update the `
    ` rather than update it in each separate click handler. + +## Construct objects and modify their prototype + +Object Oriented JavaScript + +1. Load the js_classes.html file and view it in a browser +2. Open the console window to view its output +3. Note the creation of three "coffee objects" in the JS code, using the new keyword and a constructor function called Coffee +4. Now open extending_js_types.html to see the use of JavaScript's inheritance mechanism: the prototype +5. Open the page in the web browser and note the console output. +6. Note that the prototype object of the built-in String JS data type has allowed us to "extend" the functionality of strings by adding a new string method called palindrome. + +Prototype inheritance applies to any JS data type/object, including objects created as instances of constructor functions like Coffee(). The prototype of an object can be modified or overridden by JS code at runtime. Any other object sharing this prototype is also modified in the same way. + +> Note in js_classes.html there are two implementations of the Coffee class: one based on ES5 standard, the other based on the latest ES6. Bear in mind this two are equivalent -- the `class` keyword introduced in ES6 is a ['syntactical sugar'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes), it's still function behind the scene. + +> [Some examples of ES6 classes on Sitepoint](http://www.sitepoint.com/understanding-ecmascript-6-class-inheritance/). + +### Test your understanding + +1. First, add a Tea(type, ounces) class to the js_classes.html JavaScript. + - Add properties for type and ounces + - Add methods: getSteepTime() and order() , and use appropriate code within them. +2. Now create some new Tea instance objects and invoke the order() method to check that it works. +3. Once it is working, extend the Tea and Coffee prototypes by giving both a new method sip() + - Each invocation of sip() should simply log "Hmm, tasty" to the console + +## Create a JavaScript module (N.B.: advanced) + +This is more advanced but begins to show the power of JS as a fully fledged language for designing and building large-scale applications. In particular, function closures and immediate invocation allows us to define self-contained modules that do not pollute the global namespace. This is how libraries such as, for example, jQuery and YUI, are put together. + +> See [a blog page on JS Module Pattern written by Ben Cherry](http://www.adequatelygood.com/JavaScript-Module-Pattern-In-Depth.html) along with [Immediately-Invoked function expressions written by Ben Alman](http://benalman.com/news/2010/11/immediately-invoked-function-expression/) for a good discussion of what follows. + +1. Open index.html in the advanced_obj_fun folder and preview it. +2. Play with the app, it is a simple task list. However, it is implemented as a proper application, using modules. +3. Load the following files in the editor: + + ``` + advanced_obj_fun/ + js/ + model.js + store.js + app.js + index.html + ``` + Together these JS modules implement the "model" part of MVC (Model View Controller). + +4. Look at the JS files and note some of their similarities: + a. Each one defines an "immediately invoked function" + b. Each one contains a constructor function at the top + c. store.js and model.js both extend their constructor prototypes + d. store.js and model.js both export their constructor to a global namespace at the end + +These things are all typical of the "module pattern" in JS. + +> The example used here is modified based on 'vanilla' flavor of the [TodoMVC](http://todomvc.com/). Click here for some help on [What (function (window, document, undefined) {})(window, document); really means written by Todd](https://toddmotto.com/what-function-window-document-undefined-iife-really-means/). + +### Test your understanding + +* Write a short module that exports a utility object called "$" to the global namespace. +* This object should reimplement the JS `document.getElementById()` selector method using its own method `$.id()`. + +In other words, any page that includes your new module JS file should be able to call `$.id("myButton")` and this will return a reference to the DOM element with ID "myButton" (if any). + +By doing this, you should start to see how libraries such as jQuery add to or simplify the standard functionality of core JavaScript! + +## YouTube video on closure/module + +The following is a very good tutorial on closure. + +* [Modular Javascript #3 - Revealing Module Pattern](https://www.youtube.com/watch?v=pOfwp6VlnlM&index=3&list=PLoYCgNOIyGABs-wDaaxChu82q_xQgUb4f) \ No newline at end of file diff --git a/labs/11 JavaScript Modules/1000buttons.html b/09 JS modules/workspace/1000buttons.html old mode 100644 new mode 100755 similarity index 100% rename from labs/11 JavaScript Modules/1000buttons.html rename to 09 JS modules/workspace/1000buttons.html diff --git a/labs/11 JavaScript Modules/advanced_obj_fun/css/base.css b/09 JS modules/workspace/advanced_obj_fun/css/base.css old mode 100644 new mode 100755 similarity index 100% rename from labs/11 JavaScript Modules/advanced_obj_fun/css/base.css rename to 09 JS modules/workspace/advanced_obj_fun/css/base.css diff --git a/labs/11 JavaScript Modules/advanced_obj_fun/data/bg.png b/09 JS modules/workspace/advanced_obj_fun/data/bg.png old mode 100644 new mode 100755 similarity index 100% rename from labs/11 JavaScript Modules/advanced_obj_fun/data/bg.png rename to 09 JS modules/workspace/advanced_obj_fun/data/bg.png diff --git a/labs/11 JavaScript Modules/advanced_obj_fun/index.html b/09 JS modules/workspace/advanced_obj_fun/index.html old mode 100644 new mode 100755 similarity index 98% rename from labs/11 JavaScript Modules/advanced_obj_fun/index.html rename to 09 JS modules/workspace/advanced_obj_fun/index.html index 534eb1f..f8ac2d9 --- a/labs/11 JavaScript Modules/advanced_obj_fun/index.html +++ b/09 JS modules/workspace/advanced_obj_fun/index.html @@ -2,7 +2,7 @@ - TodoApp - 305cde + TodoApp - 205cde diff --git a/labs/11 JavaScript Modules/advanced_obj_fun/js/app.js b/09 JS modules/workspace/advanced_obj_fun/js/app.js old mode 100644 new mode 100755 similarity index 96% rename from labs/11 JavaScript Modules/advanced_obj_fun/js/app.js rename to 09 JS modules/workspace/advanced_obj_fun/js/app.js index f2ca523..c37e2e6 --- a/labs/11 JavaScript Modules/advanced_obj_fun/js/app.js +++ b/09 JS modules/workspace/advanced_obj_fun/js/app.js @@ -1,25 +1,25 @@ -/*global app, $on */ -(function () { - 'use strict'; - - /** - * Sets up a brand new Todo list. - * - * @param {string} name The name of your new to do list. - */ - function Todo(name) { - this.storage = new app.Store(name); - this.model = new app.Model(this.storage); - this.template = new app.Template(); - this.view = new app.View(this.template); - this.controller = new app.Controller(this.model, this.view); - } - - var todo = new Todo('todos-vanillajs'); - - function setView() { - todo.controller.setView(document.location.hash); - } - $on(window, 'load', setView); - $on(window, 'hashchange', setView); -})(); +/*global app, $on */ +(function () { + 'use strict'; + + /** + * Sets up a brand new Todo list. + * + * @param {string} name The name of your new to do list. + */ + function Todo(name) { + this.storage = new app.Store(name); + this.model = new app.Model(this.storage); + this.template = new app.Template(); + this.view = new app.View(this.template); + this.controller = new app.Controller(this.model, this.view); + } + + var todo = new Todo('todos-vanillajs'); + + function setView() { + todo.controller.setView(document.location.hash); + } + $on(window, 'load', setView); + $on(window, 'hashchange', setView); +})(); diff --git a/labs/11 JavaScript Modules/advanced_obj_fun/js/base.js b/09 JS modules/workspace/advanced_obj_fun/js/base.js old mode 100644 new mode 100755 similarity index 96% rename from labs/11 JavaScript Modules/advanced_obj_fun/js/base.js rename to 09 JS modules/workspace/advanced_obj_fun/js/base.js index 1ec9259..099da60 --- a/labs/11 JavaScript Modules/advanced_obj_fun/js/base.js +++ b/09 JS modules/workspace/advanced_obj_fun/js/base.js @@ -1,209 +1,209 @@ -(function () { - 'use strict'; - - // Underscore's Template Module - // Courtesy of underscorejs.org - var _ = (function (_) { - _.defaults = function (object) { - if (!object) { - return object; - } - for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { - var iterable = arguments[argsIndex]; - if (iterable) { - for (var key in iterable) { - if (object[key] == null) { - object[key] = iterable[key]; - } - } - } - } - return object; - } - - // By default, Underscore uses ERB-style template delimiters, change the - // following template settings to use alternative delimiters. - _.templateSettings = { - evaluate : /<%([\s\S]+?)%>/g, - interpolate : /<%=([\s\S]+?)%>/g, - escape : /<%-([\s\S]+?)%>/g - }; - - // When customizing `templateSettings`, if you don't want to define an - // interpolation, evaluation or escaping regex, we need one that is - // guaranteed not to match. - var noMatch = /(.)^/; - - // Certain characters need to be escaped so that they can be put into a - // string literal. - var escapes = { - "'": "'", - '\\': '\\', - '\r': 'r', - '\n': 'n', - '\t': 't', - '\u2028': 'u2028', - '\u2029': 'u2029' - }; - - var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; - - // JavaScript micro-templating, similar to John Resig's implementation. - // Underscore templating handles arbitrary delimiters, preserves whitespace, - // and correctly escapes quotes within interpolated code. - _.template = function(text, data, settings) { - var render; - settings = _.defaults({}, settings, _.templateSettings); - - // Combine delimiters into one regular expression via alternation. - var matcher = new RegExp([ - (settings.escape || noMatch).source, - (settings.interpolate || noMatch).source, - (settings.evaluate || noMatch).source - ].join('|') + '|$', 'g'); - - // Compile the template source, escaping string literals appropriately. - var index = 0; - var source = "__p+='"; - text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { - source += text.slice(index, offset) - .replace(escaper, function(match) { return '\\' + escapes[match]; }); - - if (escape) { - source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; - } - if (interpolate) { - source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; - } - if (evaluate) { - source += "';\n" + evaluate + "\n__p+='"; - } - index = offset + match.length; - return match; - }); - source += "';\n"; - - // If a variable is not specified, place data values in local scope. - if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; - - source = "var __t,__p='',__j=Array.prototype.join," + - "print=function(){__p+=__j.call(arguments,'');};\n" + - source + "return __p;\n"; - - try { - render = new Function(settings.variable || 'obj', '_', source); - } catch (e) { - e.source = source; - throw e; - } - - if (data) return render(data, _); - var template = function(data) { - return render.call(this, data, _); - }; - - // Provide the compiled function source as a convenience for precompilation. - template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; - - return template; - }; - - return _; - })({}); - - if (location.hostname === 'todomvc.com') { - window._gaq = [['_setAccount','UA-31081062-1'],['_trackPageview']];(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src='//www.google-analytics.com/ga.js';s.parentNode.insertBefore(g,s)}(document,'script')); - } - - function redirect() { - if (location.hostname === 'tastejs.github.io') { - location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); - } - } - - function findRoot() { - var base; - - [/labs/, /\w*-examples/].forEach(function (href) { - var match = location.href.match(href); - - if (!base && match) { - base = location.href.indexOf(match); - } - }); - - return location.href.substr(0, base); - } - - function getFile(file, callback) { - if (!location.host) { - return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); - } - - var xhr = new XMLHttpRequest(); - - xhr.open('GET', findRoot() + file, true); - xhr.send(); - - xhr.onload = function () { - if (xhr.status === 200 && callback) { - callback(xhr.responseText); - } - }; - } - - function Learn(learnJSON, config) { - if (!(this instanceof Learn)) { - return new Learn(learnJSON, config); - } - - var template, framework; - - if (typeof learnJSON !== 'object') { - try { - learnJSON = JSON.parse(learnJSON); - } catch (e) { - return; - } - } - - if (config) { - template = config.template; - framework = config.framework; - } - - if (!template && learnJSON.templates) { - template = learnJSON.templates.todomvc; - } - - if (!framework && document.querySelector('[data-framework]')) { - framework = document.querySelector('[data-framework]').getAttribute('data-framework'); - } - - - if (template && learnJSON[framework]) { - this.frameworkJSON = learnJSON[framework]; - this.template = template; - - this.append(); - } - } - - Learn.prototype.append = function () { - var aside = document.createElement('aside'); - aside.innerHTML = _.template(this.template, this.frameworkJSON); - aside.className = 'learn'; - - // Localize demo links - var demoLinks = aside.querySelectorAll('.demo-link'); - Array.prototype.forEach.call(demoLinks, function (demoLink) { - demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); - }); - - document.body.className = (document.body.className + ' learn-bar').trim(); - document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); - }; - - redirect(); - getFile('learn.json', Learn); -})(); +(function () { + 'use strict'; + + // Underscore's Template Module + // Courtesy of underscorejs.org + var _ = (function (_) { + _.defaults = function (object) { + if (!object) { + return object; + } + for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { + var iterable = arguments[argsIndex]; + if (iterable) { + for (var key in iterable) { + if (object[key] == null) { + object[key] = iterable[key]; + } + } + } + } + return object; + } + + // By default, Underscore uses ERB-style template delimiters, change the + // following template settings to use alternative delimiters. + _.templateSettings = { + evaluate : /<%([\s\S]+?)%>/g, + interpolate : /<%=([\s\S]+?)%>/g, + escape : /<%-([\s\S]+?)%>/g + }; + + // When customizing `templateSettings`, if you don't want to define an + // interpolation, evaluation or escaping regex, we need one that is + // guaranteed not to match. + var noMatch = /(.)^/; + + // Certain characters need to be escaped so that they can be put into a + // string literal. + var escapes = { + "'": "'", + '\\': '\\', + '\r': 'r', + '\n': 'n', + '\t': 't', + '\u2028': 'u2028', + '\u2029': 'u2029' + }; + + var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; + + // JavaScript micro-templating, similar to John Resig's implementation. + // Underscore templating handles arbitrary delimiters, preserves whitespace, + // and correctly escapes quotes within interpolated code. + _.template = function(text, data, settings) { + var render; + settings = _.defaults({}, settings, _.templateSettings); + + // Combine delimiters into one regular expression via alternation. + var matcher = new RegExp([ + (settings.escape || noMatch).source, + (settings.interpolate || noMatch).source, + (settings.evaluate || noMatch).source + ].join('|') + '|$', 'g'); + + // Compile the template source, escaping string literals appropriately. + var index = 0; + var source = "__p+='"; + text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { + source += text.slice(index, offset) + .replace(escaper, function(match) { return '\\' + escapes[match]; }); + + if (escape) { + source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; + } + if (interpolate) { + source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; + } + if (evaluate) { + source += "';\n" + evaluate + "\n__p+='"; + } + index = offset + match.length; + return match; + }); + source += "';\n"; + + // If a variable is not specified, place data values in local scope. + if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; + + source = "var __t,__p='',__j=Array.prototype.join," + + "print=function(){__p+=__j.call(arguments,'');};\n" + + source + "return __p;\n"; + + try { + render = new Function(settings.variable || 'obj', '_', source); + } catch (e) { + e.source = source; + throw e; + } + + if (data) return render(data, _); + var template = function(data) { + return render.call(this, data, _); + }; + + // Provide the compiled function source as a convenience for precompilation. + template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; + + return template; + }; + + return _; + })({}); + + if (location.hostname === 'todomvc.com') { + window._gaq = [['_setAccount','UA-31081062-1'],['_trackPageview']];(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src='//www.google-analytics.com/ga.js';s.parentNode.insertBefore(g,s)}(document,'script')); + } + + function redirect() { + if (location.hostname === 'tastejs.github.io') { + location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); + } + } + + function findRoot() { + var base; + + [/labs/, /\w*-examples/].forEach(function (href) { + var match = location.href.match(href); + + if (!base && match) { + base = location.href.indexOf(match); + } + }); + + return location.href.substr(0, base); + } + + function getFile(file, callback) { + if (!location.host) { + return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); + } + + var xhr = new XMLHttpRequest(); + + xhr.open('GET', findRoot() + file, true); + xhr.send(); + + xhr.onload = function () { + if (xhr.status === 200 && callback) { + callback(xhr.responseText); + } + }; + } + + function Learn(learnJSON, config) { + if (!(this instanceof Learn)) { + return new Learn(learnJSON, config); + } + + var template, framework; + + if (typeof learnJSON !== 'object') { + try { + learnJSON = JSON.parse(learnJSON); + } catch (e) { + return; + } + } + + if (config) { + template = config.template; + framework = config.framework; + } + + if (!template && learnJSON.templates) { + template = learnJSON.templates.todomvc; + } + + if (!framework && document.querySelector('[data-framework]')) { + framework = document.querySelector('[data-framework]').getAttribute('data-framework'); + } + + + if (template && learnJSON[framework]) { + this.frameworkJSON = learnJSON[framework]; + this.template = template; + + this.append(); + } + } + + Learn.prototype.append = function () { + var aside = document.createElement('aside'); + aside.innerHTML = _.template(this.template, this.frameworkJSON); + aside.className = 'learn'; + + // Localize demo links + var demoLinks = aside.querySelectorAll('.demo-link'); + Array.prototype.forEach.call(demoLinks, function (demoLink) { + demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); + }); + + document.body.className = (document.body.className + ' learn-bar').trim(); + document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); + }; + + redirect(); + getFile('learn.json', Learn); +})(); diff --git a/labs/11 JavaScript Modules/advanced_obj_fun/js/controller.js b/09 JS modules/workspace/advanced_obj_fun/js/controller.js old mode 100644 new mode 100755 similarity index 96% rename from labs/11 JavaScript Modules/advanced_obj_fun/js/controller.js rename to 09 JS modules/workspace/advanced_obj_fun/js/controller.js index 4f02797..341e91d --- a/labs/11 JavaScript Modules/advanced_obj_fun/js/controller.js +++ b/09 JS modules/workspace/advanced_obj_fun/js/controller.js @@ -1,268 +1,268 @@ -(function (window) { - 'use strict'; - - /** - * Takes a model and view and acts as the controller between them - * - * @constructor - * @param {object} model The model instance - * @param {object} view The view instance - */ - function Controller(model, view) { - var that = this; - that.model = model; - that.view = view; - - that.view.bind('newTodo', function (title) { - that.addItem(title); - }); - - that.view.bind('itemEdit', function (item) { - that.editItem(item.id); - }); - - that.view.bind('itemEditDone', function (item) { - that.editItemSave(item.id, item.title); - }); - - that.view.bind('itemEditCancel', function (item) { - that.editItemCancel(item.id); - }); - - that.view.bind('itemRemove', function (item) { - that.removeItem(item.id); - }); - - that.view.bind('itemToggle', function (item) { - that.toggleComplete(item.id, item.completed); - }); - - that.view.bind('removeCompleted', function () { - that.removeCompletedItems(); - }); - - that.view.bind('toggleAll', function (status) { - that.toggleAll(status.completed); - }); - } - - /** - * Loads and initialises the view - * - * @param {string} '' | 'active' | 'completed' - */ - Controller.prototype.setView = function (locationHash) { - var route = locationHash.split('/')[1]; - var page = route || ''; - this._updateFilterState(page); - }; - - /** - * An event to fire on load. Will get all items and display them in the - * todo-list - */ - Controller.prototype.showAll = function () { - var that = this; - that.model.read(function (data) { - that.view.render('showEntries', data); - }); - }; - - /** - * Renders all active tasks - */ - Controller.prototype.showActive = function () { - var that = this; - that.model.read({ completed: false }, function (data) { - that.view.render('showEntries', data); - }); - }; - - /** - * Renders all completed tasks - */ - Controller.prototype.showCompleted = function () { - var that = this; - that.model.read({ completed: true }, function (data) { - that.view.render('showEntries', data); - }); - }; - - /** - * An event to fire whenever you want to add an item. Simply pass in the event - * object and it'll handle the DOM insertion and saving of the new item. - */ - Controller.prototype.addItem = function (title) { - var that = this; - - if (title.trim() === '') { - return; - } - - that.model.create(title, function () { - that.view.render('clearNewTodo'); - that._filter(true); - }); - }; - - /* - * Triggers the item editing mode. - */ - Controller.prototype.editItem = function (id) { - var that = this; - that.model.read(id, function (data) { - that.view.render('editItem', {id: id, title: data[0].title}); - }); - }; - - /* - * Finishes the item editing mode successfully. - */ - Controller.prototype.editItemSave = function (id, title) { - var that = this; - if (title.trim()) { - that.model.update(id, {title: title}, function () { - that.view.render('editItemDone', {id: id, title: title}); - }); - } else { - that.removeItem(id); - } - }; - - /* - * Cancels the item editing mode. - */ - Controller.prototype.editItemCancel = function (id) { - var that = this; - that.model.read(id, function (data) { - that.view.render('editItemDone', {id: id, title: data[0].title}); - }); - }; - - /** - * By giving it an ID it'll find the DOM element matching that ID, - * remove it from the DOM and also remove it from storage. - * - * @param {number} id The ID of the item to remove from the DOM and - * storage - */ - Controller.prototype.removeItem = function (id) { - var that = this; - that.model.remove(id, function () { - that.view.render('removeItem', id); - }); - - that._filter(); - }; - - /** - * Will remove all completed items from the DOM and storage. - */ - Controller.prototype.removeCompletedItems = function () { - var that = this; - that.model.read({ completed: true }, function (data) { - data.forEach(function (item) { - that.removeItem(item.id); - }); - }); - - that._filter(); - }; - - /** - * Give it an ID of a model and a checkbox and it will update the item - * in storage based on the checkbox's state. - * - * @param {number} id The ID of the element to complete or uncomplete - * @param {object} checkbox The checkbox to check the state of complete - * or not - * @param {boolean|undefined} silent Prevent re-filtering the todo items - */ - Controller.prototype.toggleComplete = function (id, completed, silent) { - var that = this; - that.model.update(id, { completed: completed }, function () { - that.view.render('elementComplete', { - id: id, - completed: completed - }); - }); - - if (!silent) { - that._filter(); - } - }; - - /** - * Will toggle ALL checkboxe's on/off state and completeness of models. - * Just pass in the event object. - */ - Controller.prototype.toggleAll = function (completed) { - var that = this; - that.model.read({ completed: !completed }, function (data) { - data.forEach(function (item) { - that.toggleComplete(item.id, completed, true); - }); - }); - - that._filter(); - }; - - /** - * Updates the pieces of the page which change depending on the remaining - * number of todos. - */ - Controller.prototype._updateCount = function () { - var that = this; - that.model.getCount(function (todos) { - that.view.render('updateElementCount', todos.active); - that.view.render('clearCompletedButton', { - completed: todos.completed, - visible: todos.completed > 0 - }); - - that.view.render('toggleAll', {checked: todos.completed === todos.total}); - that.view.render('contentBlockVisibility', {visible: todos.total > 0}); - }); - }; - - /** - * Re-filters the todo items, based on the active route. - * @param {boolean|undefined} force forces a re-painting of todo items. - */ - Controller.prototype._filter = function (force) { - var activeRoute = this._activeRoute.charAt(0).toUpperCase() + this._activeRoute.substr(1); - - // Update the elements on the page, which change with each completed todo - this._updateCount(); - - // If the last active route isn't "All", or we're switching routes, we - // re-create the todo item elements, calling: - // this.show[All|Active|Completed](); - if (force || this._lastActiveRoute !== 'All' || this._lastActiveRoute !== activeRoute) { - this['show' + activeRoute](); - } - - this._lastActiveRoute = activeRoute; - }; - - /** - * Simply updates the filter nav's selected states - */ - Controller.prototype._updateFilterState = function (currentPage) { - // Store a reference to the active route, allowing us to re-filter todo - // items as they are marked complete or incomplete. - this._activeRoute = currentPage; - - if (currentPage === '') { - this._activeRoute = 'All'; - } - - this._filter(); - - this.view.render('setFilter', currentPage); - }; - - // Export to window - window.app = window.app || {}; - window.app.Controller = Controller; -})(window); +(function (window) { + 'use strict'; + + /** + * Takes a model and view and acts as the controller between them + * + * @constructor + * @param {object} model The model instance + * @param {object} view The view instance + */ + function Controller(model, view) { + var that = this; + that.model = model; + that.view = view; + + that.view.bind('newTodo', function (title) { + that.addItem(title); + }); + + that.view.bind('itemEdit', function (item) { + that.editItem(item.id); + }); + + that.view.bind('itemEditDone', function (item) { + that.editItemSave(item.id, item.title); + }); + + that.view.bind('itemEditCancel', function (item) { + that.editItemCancel(item.id); + }); + + that.view.bind('itemRemove', function (item) { + that.removeItem(item.id); + }); + + that.view.bind('itemToggle', function (item) { + that.toggleComplete(item.id, item.completed); + }); + + that.view.bind('removeCompleted', function () { + that.removeCompletedItems(); + }); + + that.view.bind('toggleAll', function (status) { + that.toggleAll(status.completed); + }); + } + + /** + * Loads and initialises the view + * + * @param {string} '' | 'active' | 'completed' + */ + Controller.prototype.setView = function (locationHash) { + var route = locationHash.split('/')[1]; + var page = route || ''; + this._updateFilterState(page); + }; + + /** + * An event to fire on load. Will get all items and display them in the + * todo-list + */ + Controller.prototype.showAll = function () { + var that = this; + that.model.read(function (data) { + that.view.render('showEntries', data); + }); + }; + + /** + * Renders all active tasks + */ + Controller.prototype.showActive = function () { + var that = this; + that.model.read({ completed: false }, function (data) { + that.view.render('showEntries', data); + }); + }; + + /** + * Renders all completed tasks + */ + Controller.prototype.showCompleted = function () { + var that = this; + that.model.read({ completed: true }, function (data) { + that.view.render('showEntries', data); + }); + }; + + /** + * An event to fire whenever you want to add an item. Simply pass in the event + * object and it'll handle the DOM insertion and saving of the new item. + */ + Controller.prototype.addItem = function (title) { + var that = this; + + if (title.trim() === '') { + return; + } + + that.model.create(title, function () { + that.view.render('clearNewTodo'); + that._filter(true); + }); + }; + + /* + * Triggers the item editing mode. + */ + Controller.prototype.editItem = function (id) { + var that = this; + that.model.read(id, function (data) { + that.view.render('editItem', {id: id, title: data[0].title}); + }); + }; + + /* + * Finishes the item editing mode successfully. + */ + Controller.prototype.editItemSave = function (id, title) { + var that = this; + if (title.trim()) { + that.model.update(id, {title: title}, function () { + that.view.render('editItemDone', {id: id, title: title}); + }); + } else { + that.removeItem(id); + } + }; + + /* + * Cancels the item editing mode. + */ + Controller.prototype.editItemCancel = function (id) { + var that = this; + that.model.read(id, function (data) { + that.view.render('editItemDone', {id: id, title: data[0].title}); + }); + }; + + /** + * By giving it an ID it'll find the DOM element matching that ID, + * remove it from the DOM and also remove it from storage. + * + * @param {number} id The ID of the item to remove from the DOM and + * storage + */ + Controller.prototype.removeItem = function (id) { + var that = this; + that.model.remove(id, function () { + that.view.render('removeItem', id); + }); + + that._filter(); + }; + + /** + * Will remove all completed items from the DOM and storage. + */ + Controller.prototype.removeCompletedItems = function () { + var that = this; + that.model.read({ completed: true }, function (data) { + data.forEach(function (item) { + that.removeItem(item.id); + }); + }); + + that._filter(); + }; + + /** + * Give it an ID of a model and a checkbox and it will update the item + * in storage based on the checkbox's state. + * + * @param {number} id The ID of the element to complete or uncomplete + * @param {object} checkbox The checkbox to check the state of complete + * or not + * @param {boolean|undefined} silent Prevent re-filtering the todo items + */ + Controller.prototype.toggleComplete = function (id, completed, silent) { + var that = this; + that.model.update(id, { completed: completed }, function () { + that.view.render('elementComplete', { + id: id, + completed: completed + }); + }); + + if (!silent) { + that._filter(); + } + }; + + /** + * Will toggle ALL checkboxe's on/off state and completeness of models. + * Just pass in the event object. + */ + Controller.prototype.toggleAll = function (completed) { + var that = this; + that.model.read({ completed: !completed }, function (data) { + data.forEach(function (item) { + that.toggleComplete(item.id, completed, true); + }); + }); + + that._filter(); + }; + + /** + * Updates the pieces of the page which change depending on the remaining + * number of todos. + */ + Controller.prototype._updateCount = function () { + var that = this; + that.model.getCount(function (todos) { + that.view.render('updateElementCount', todos.active); + that.view.render('clearCompletedButton', { + completed: todos.completed, + visible: todos.completed > 0 + }); + + that.view.render('toggleAll', {checked: todos.completed === todos.total}); + that.view.render('contentBlockVisibility', {visible: todos.total > 0}); + }); + }; + + /** + * Re-filters the todo items, based on the active route. + * @param {boolean|undefined} force forces a re-painting of todo items. + */ + Controller.prototype._filter = function (force) { + var activeRoute = this._activeRoute.charAt(0).toUpperCase() + this._activeRoute.substr(1); + + // Update the elements on the page, which change with each completed todo + this._updateCount(); + + // If the last active route isn't "All", or we're switching routes, we + // re-create the todo item elements, calling: + // this.show[All|Active|Completed](); + if (force || this._lastActiveRoute !== 'All' || this._lastActiveRoute !== activeRoute) { + this['show' + activeRoute](); + } + + this._lastActiveRoute = activeRoute; + }; + + /** + * Simply updates the filter nav's selected states + */ + Controller.prototype._updateFilterState = function (currentPage) { + // Store a reference to the active route, allowing us to re-filter todo + // items as they are marked complete or incomplete. + this._activeRoute = currentPage; + + if (currentPage === '') { + this._activeRoute = 'All'; + } + + this._filter(); + + this.view.render('setFilter', currentPage); + }; + + // Export to window + window.app = window.app || {}; + window.app.Controller = Controller; +})(window); diff --git a/labs/11 JavaScript Modules/advanced_obj_fun/js/helpers.js b/09 JS modules/workspace/advanced_obj_fun/js/helpers.js old mode 100644 new mode 100755 similarity index 96% rename from labs/11 JavaScript Modules/advanced_obj_fun/js/helpers.js rename to 09 JS modules/workspace/advanced_obj_fun/js/helpers.js index 6d68326..180c777 --- a/labs/11 JavaScript Modules/advanced_obj_fun/js/helpers.js +++ b/09 JS modules/workspace/advanced_obj_fun/js/helpers.js @@ -1,64 +1,64 @@ -/*global NodeList */ -(function (window) { - 'use strict'; - - // Get element(s) by CSS selector: - window.qs = function (selector, scope) { - return (scope || document).querySelector(selector); - }; - window.qsa = function (selector, scope) { - return (scope || document).querySelectorAll(selector); - }; - - // addEventListener wrapper: - window.$on = function (target, type, callback, useCapture) { - target.addEventListener(type, callback, !!useCapture); - }; - - // Register events on elements that may or may not exist yet: - // $live('div a', 'click', function (event) {}); - window.$live = (function () { - var eventRegistry = {}; - - function dispatchEvent(event) { - var targetElement = event.target; - - eventRegistry[event.type].forEach(function (entry) { - var potentialElements = window.qsa(entry.selector); - var hasMatch = Array.prototype.indexOf.call(potentialElements, targetElement) >= 0; - - if (hasMatch) { - entry.handler.call(targetElement, event); - } - }); - } - - return function (selector, event, handler) { - if (!eventRegistry[event]) { - eventRegistry[event] = []; - window.$on(document.documentElement, event, dispatchEvent, true); - } - - eventRegistry[event].push({ - selector: selector, - handler: handler - }); - }; - }()); - - // Find the element's parent with the given tag name: - // $parent(qs('a'), 'div'); - window.$parent = function (element, tagName) { - if (!element.parentNode) { - return; - } - if (element.parentNode.tagName.toLowerCase() === tagName.toLowerCase()) { - return element.parentNode; - } - return window.$parent(element.parentNode, tagName); - }; - - // Allow for looping on nodes by chaining: - // qsa('.foo').forEach(function () {}) - NodeList.prototype.forEach = Array.prototype.forEach; -})(window); +/*global NodeList */ +(function (window) { + 'use strict'; + + // Get element(s) by CSS selector: + window.qs = function (selector, scope) { + return (scope || document).querySelector(selector); + }; + window.qsa = function (selector, scope) { + return (scope || document).querySelectorAll(selector); + }; + + // addEventListener wrapper: + window.$on = function (target, type, callback, useCapture) { + target.addEventListener(type, callback, !!useCapture); + }; + + // Register events on elements that may or may not exist yet: + // $live('div a', 'click', function (event) {}); + window.$live = (function () { + var eventRegistry = {}; + + function dispatchEvent(event) { + var targetElement = event.target; + + eventRegistry[event.type].forEach(function (entry) { + var potentialElements = window.qsa(entry.selector); + var hasMatch = Array.prototype.indexOf.call(potentialElements, targetElement) >= 0; + + if (hasMatch) { + entry.handler.call(targetElement, event); + } + }); + } + + return function (selector, event, handler) { + if (!eventRegistry[event]) { + eventRegistry[event] = []; + window.$on(document.documentElement, event, dispatchEvent, true); + } + + eventRegistry[event].push({ + selector: selector, + handler: handler + }); + }; + }()); + + // Find the element's parent with the given tag name: + // $parent(qs('a'), 'div'); + window.$parent = function (element, tagName) { + if (!element.parentNode) { + return; + } + if (element.parentNode.tagName.toLowerCase() === tagName.toLowerCase()) { + return element.parentNode; + } + return window.$parent(element.parentNode, tagName); + }; + + // Allow for looping on nodes by chaining: + // qsa('.foo').forEach(function () {}) + NodeList.prototype.forEach = Array.prototype.forEach; +})(window); diff --git a/labs/11 JavaScript Modules/advanced_obj_fun/js/model.js b/09 JS modules/workspace/advanced_obj_fun/js/model.js old mode 100644 new mode 100755 similarity index 96% rename from labs/11 JavaScript Modules/advanced_obj_fun/js/model.js rename to 09 JS modules/workspace/advanced_obj_fun/js/model.js index fc90e67..9da766a --- a/labs/11 JavaScript Modules/advanced_obj_fun/js/model.js +++ b/09 JS modules/workspace/advanced_obj_fun/js/model.js @@ -1,120 +1,120 @@ -(function (window) { - 'use strict'; - - /** - * Creates a new Model instance and hooks up the storage. - * - * @constructor - * @param {object} storage A reference to the client side storage class - */ - function Model(storage) { - this.storage = storage; - } - - /** - * Creates a new todo model - * - * @param {string} [title] The title of the task - * @param {function} [callback] The callback to fire after the model is created - */ - Model.prototype.create = function (title, callback) { - title = title || ''; - callback = callback || function () {}; - - var newItem = { - title: title.trim(), - completed: false - }; - - this.storage.save(newItem, callback); - }; - - /** - * Finds and returns a model in storage. If no query is given it'll simply - * return everything. If you pass in a string or number it'll look that up as - * the ID of the model to find. Lastly, you can pass it an object to match - * against. - * - * @param {string|number|object} [query] A query to match models against - * @param {function} [callback] The callback to fire after the model is found - * - * @example - * model.read(1, func); // Will find the model with an ID of 1 - * model.read('1'); // Same as above - * //Below will find a model with foo equalling bar and hello equalling world. - * model.read({ foo: 'bar', hello: 'world' }); - */ - Model.prototype.read = function (query, callback) { - var queryType = typeof query; - callback = callback || function () {}; - - if (queryType === 'function') { - callback = query; - return this.storage.findAll(callback); - } else if (queryType === 'string' || queryType === 'number') { - query = parseInt(query, 10); - this.storage.find({ id: query }, callback); - } else { - this.storage.find(query, callback); - } - }; - - /** - * Updates a model by giving it an ID, data to update, and a callback to fire when - * the update is complete. - * - * @param {number} id The id of the model to update - * @param {object} data The properties to update and their new value - * @param {function} callback The callback to fire when the update is complete. - */ - Model.prototype.update = function (id, data, callback) { - this.storage.save(data, callback, id); - }; - - /** - * Removes a model from storage - * - * @param {number} id The ID of the model to remove - * @param {function} callback The callback to fire when the removal is complete. - */ - Model.prototype.remove = function (id, callback) { - this.storage.remove(id, callback); - }; - - /** - * WARNING: Will remove ALL data from storage. - * - * @param {function} callback The callback to fire when the storage is wiped. - */ - Model.prototype.removeAll = function (callback) { - this.storage.drop(callback); - }; - - /** - * Returns a count of all todos - */ - Model.prototype.getCount = function (callback) { - var todos = { - active: 0, - completed: 0, - total: 0 - }; - - this.storage.findAll(function (data) { - data.forEach(function (todo) { - if (todo.completed) { - todos.completed++; - } else { - todos.active++; - } - - todos.total++; - }); - callback(todos); - }); - }; - - // Export to window - window.app = window.app || {}; - window.app.Model = Model; -})(window); +(function (window) { + 'use strict'; + + /** + * Creates a new Model instance and hooks up the storage. + * + * @constructor + * @param {object} storage A reference to the client side storage class + */ + function Model(storage) { + this.storage = storage; + } + + /** + * Creates a new todo model + * + * @param {string} [title] The title of the task + * @param {function} [callback] The callback to fire after the model is created + */ + Model.prototype.create = function (title, callback) { + title = title || ''; + callback = callback || function () {}; + + var newItem = { + title: title.trim(), + completed: false + }; + + this.storage.save(newItem, callback); + }; + + /** + * Finds and returns a model in storage. If no query is given it'll simply + * return everything. If you pass in a string or number it'll look that up as + * the ID of the model to find. Lastly, you can pass it an object to match + * against. + * + * @param {string|number|object} [query] A query to match models against + * @param {function} [callback] The callback to fire after the model is found + * + * @example + * model.read(1, func); // Will find the model with an ID of 1 + * model.read('1'); // Same as above + * //Below will find a model with foo equalling bar and hello equalling world. + * model.read({ foo: 'bar', hello: 'world' }); + */ + Model.prototype.read = function (query, callback) { + var queryType = typeof query; + callback = callback || function () {}; + + if (queryType === 'function') { + callback = query; + return this.storage.findAll(callback); + } else if (queryType === 'string' || queryType === 'number') { + query = parseInt(query, 10); + this.storage.find({ id: query }, callback); + } else { + this.storage.find(query, callback); + } + }; + + /** + * Updates a model by giving it an ID, data to update, and a callback to fire when + * the update is complete. + * + * @param {number} id The id of the model to update + * @param {object} data The properties to update and their new value + * @param {function} callback The callback to fire when the update is complete. + */ + Model.prototype.update = function (id, data, callback) { + this.storage.save(data, callback, id); + }; + + /** + * Removes a model from storage + * + * @param {number} id The ID of the model to remove + * @param {function} callback The callback to fire when the removal is complete. + */ + Model.prototype.remove = function (id, callback) { + this.storage.remove(id, callback); + }; + + /** + * WARNING: Will remove ALL data from storage. + * + * @param {function} callback The callback to fire when the storage is wiped. + */ + Model.prototype.removeAll = function (callback) { + this.storage.drop(callback); + }; + + /** + * Returns a count of all todos + */ + Model.prototype.getCount = function (callback) { + var todos = { + active: 0, + completed: 0, + total: 0 + }; + + this.storage.findAll(function (data) { + data.forEach(function (todo) { + if (todo.completed) { + todos.completed++; + } else { + todos.active++; + } + + todos.total++; + }); + callback(todos); + }); + }; + + // Export to window + window.app = window.app || {}; + window.app.Model = Model; +})(window); diff --git a/labs/11 JavaScript Modules/advanced_obj_fun/js/store.js b/09 JS modules/workspace/advanced_obj_fun/js/store.js old mode 100644 new mode 100755 similarity index 96% rename from labs/11 JavaScript Modules/advanced_obj_fun/js/store.js rename to 09 JS modules/workspace/advanced_obj_fun/js/store.js index 3cf01c4..5e40544 --- a/labs/11 JavaScript Modules/advanced_obj_fun/js/store.js +++ b/09 JS modules/workspace/advanced_obj_fun/js/store.js @@ -1,140 +1,140 @@ -/*jshint eqeqeq:false */ -(function (window) { - 'use strict'; - - /** - * Creates a new client side storage object and will create an empty - * collection if no collection already exists. - * - * @param {string} name The name of our DB we want to use - * @param {function} callback Our fake DB uses callbacks because in - * real life you probably would be making AJAX calls - */ - function Store(name, callback) { - callback = callback || function () {}; - - this._dbName = name; - - if (!localStorage[name]) { - var data = { - todos: [] - }; - - localStorage[name] = JSON.stringify(data); - } - - callback.call(this, JSON.parse(localStorage[name])); - } - - /** - * Finds items based on a query given as a JS object - * - * @param {object} query The query to match against (i.e. {foo: 'bar'}) - * @param {function} callback The callback to fire when the query has - * completed running - * - * @example - * db.find({foo: 'bar', hello: 'world'}, function (data) { - * // data will return any items that have foo: bar and - * // hello: world in their properties - * }); - */ - Store.prototype.find = function (query, callback) { - if (!callback) { - return; - } - - var todos = JSON.parse(localStorage[this._dbName]).todos; - - callback.call(this, todos.filter(function (todo) { - for (var q in query) { - if (query[q] !== todo[q]) { - return false; - } - } - return true; - })); - }; - - /** - * Will retrieve all data from the collection - * - * @param {function} callback The callback to fire upon retrieving data - */ - Store.prototype.findAll = function (callback) { - callback = callback || function () {}; - callback.call(this, JSON.parse(localStorage[this._dbName]).todos); - }; - - /** - * Will save the given data to the DB. If no item exists it will create a new - * item, otherwise it'll simply update an existing item's properties - * - * @param {object} updateData The data to save back into the DB - * @param {function} callback The callback to fire after saving - * @param {number} id An optional param to enter an ID of an item to update - */ - Store.prototype.save = function (updateData, callback, id) { - var data = JSON.parse(localStorage[this._dbName]); - var todos = data.todos; - - callback = callback || function () {}; - - // If an ID was actually given, find the item and update each property - if (id) { - for (var i = 0; i < todos.length; i++) { - if (todos[i].id === id) { - for (var key in updateData) { - todos[i][key] = updateData[key]; - } - break; - } - } - - localStorage[this._dbName] = JSON.stringify(data); - callback.call(this, JSON.parse(localStorage[this._dbName]).todos); - } else { - // Generate an ID - updateData.id = new Date().getTime(); - - todos.push(updateData); - localStorage[this._dbName] = JSON.stringify(data); - callback.call(this, [updateData]); - } - }; - - /** - * Will remove an item from the Store based on its ID - * - * @param {number} id The ID of the item you want to remove - * @param {function} callback The callback to fire after saving - */ - Store.prototype.remove = function (id, callback) { - var data = JSON.parse(localStorage[this._dbName]); - var todos = data.todos; - - for (var i = 0; i < todos.length; i++) { - if (todos[i].id == id) { - todos.splice(i, 1); - break; - } - } - - localStorage[this._dbName] = JSON.stringify(data); - callback.call(this, JSON.parse(localStorage[this._dbName]).todos); - }; - - /** - * Will drop all storage and start fresh - * - * @param {function} callback The callback to fire after dropping the data - */ - Store.prototype.drop = function (callback) { - localStorage[this._dbName] = JSON.stringify({todos: []}); - callback.call(this, JSON.parse(localStorage[this._dbName]).todos); - }; - - // Export to window - window.app = window.app || {}; - window.app.Store = Store; -})(window); +/*jshint eqeqeq:false */ +(function (window) { + 'use strict'; + + /** + * Creates a new client side storage object and will create an empty + * collection if no collection already exists. + * + * @param {string} name The name of our DB we want to use + * @param {function} callback Our fake DB uses callbacks because in + * real life you probably would be making AJAX calls + */ + function Store(name, callback) { + callback = callback || function () {}; + + this._dbName = name; + + if (!localStorage[name]) { + var data = { + todos: [] + }; + + localStorage[name] = JSON.stringify(data); + } + + callback.call(this, JSON.parse(localStorage[name])); + } + + /** + * Finds items based on a query given as a JS object + * + * @param {object} query The query to match against (i.e. {foo: 'bar'}) + * @param {function} callback The callback to fire when the query has + * completed running + * + * @example + * db.find({foo: 'bar', hello: 'world'}, function (data) { + * // data will return any items that have foo: bar and + * // hello: world in their properties + * }); + */ + Store.prototype.find = function (query, callback) { + if (!callback) { + return; + } + + var todos = JSON.parse(localStorage[this._dbName]).todos; + + callback.call(this, todos.filter(function (todo) { + for (var q in query) { + if (query[q] !== todo[q]) { + return false; + } + } + return true; + })); + }; + + /** + * Will retrieve all data from the collection + * + * @param {function} callback The callback to fire upon retrieving data + */ + Store.prototype.findAll = function (callback) { + callback = callback || function () {}; + callback.call(this, JSON.parse(localStorage[this._dbName]).todos); + }; + + /** + * Will save the given data to the DB. If no item exists it will create a new + * item, otherwise it'll simply update an existing item's properties + * + * @param {object} updateData The data to save back into the DB + * @param {function} callback The callback to fire after saving + * @param {number} id An optional param to enter an ID of an item to update + */ + Store.prototype.save = function (updateData, callback, id) { + var data = JSON.parse(localStorage[this._dbName]); + var todos = data.todos; + + callback = callback || function () {}; + + // If an ID was actually given, find the item and update each property + if (id) { + for (var i = 0; i < todos.length; i++) { + if (todos[i].id === id) { + for (var key in updateData) { + todos[i][key] = updateData[key]; + } + break; + } + } + + localStorage[this._dbName] = JSON.stringify(data); + callback.call(this, JSON.parse(localStorage[this._dbName]).todos); + } else { + // Generate an ID + updateData.id = new Date().getTime(); + + todos.push(updateData); + localStorage[this._dbName] = JSON.stringify(data); + callback.call(this, [updateData]); + } + }; + + /** + * Will remove an item from the Store based on its ID + * + * @param {number} id The ID of the item you want to remove + * @param {function} callback The callback to fire after saving + */ + Store.prototype.remove = function (id, callback) { + var data = JSON.parse(localStorage[this._dbName]); + var todos = data.todos; + + for (var i = 0; i < todos.length; i++) { + if (todos[i].id == id) { + todos.splice(i, 1); + break; + } + } + + localStorage[this._dbName] = JSON.stringify(data); + callback.call(this, JSON.parse(localStorage[this._dbName]).todos); + }; + + /** + * Will drop all storage and start fresh + * + * @param {function} callback The callback to fire after dropping the data + */ + Store.prototype.drop = function (callback) { + localStorage[this._dbName] = JSON.stringify({todos: []}); + callback.call(this, JSON.parse(localStorage[this._dbName]).todos); + }; + + // Export to window + window.app = window.app || {}; + window.app.Store = Store; +})(window); diff --git a/labs/11 JavaScript Modules/advanced_obj_fun/js/template.js b/09 JS modules/workspace/advanced_obj_fun/js/template.js old mode 100644 new mode 100755 similarity index 96% rename from labs/11 JavaScript Modules/advanced_obj_fun/js/template.js rename to 09 JS modules/workspace/advanced_obj_fun/js/template.js index 49477b1..9eb72ec --- a/labs/11 JavaScript Modules/advanced_obj_fun/js/template.js +++ b/09 JS modules/workspace/advanced_obj_fun/js/template.js @@ -1,92 +1,92 @@ -/*jshint laxbreak:true */ -(function (window) { - 'use strict'; - - /** - * Sets up defaults for all the Template methods such as a default template - * - * @constructor - */ - function Template() { - this.defaultTemplate - = '
  • ' - + '
    ' - + '' - + '' - + '' - + '
    ' - + '
  • '; - } - - /** - * Creates an
  • HTML string and returns it for placement in your app. - * - * NOTE: In real life you should be using a templating engine such as Mustache - * or Handlebars, however, this is a vanilla JS example. - * - * @param {object} data The object containing keys you want to find in the - * template to replace. - * @returns {string} HTML String of an
  • element - * - * @example - * view.show({ - * id: 1, - * title: "Hello World", - * completed: 0, - * }); - */ - Template.prototype.show = function (data) { - var i, l; - var view = ''; - - for (i = 0, l = data.length; i < l; i++) { - var template = this.defaultTemplate; - var completed = ''; - var checked = ''; - - if (data[i].completed) { - completed = 'completed'; - checked = 'checked'; - } - - template = template.replace('{{id}}', data[i].id); - template = template.replace('{{title}}', data[i].title); - template = template.replace('{{completed}}', completed); - template = template.replace('{{checked}}', checked); - - view = view + template; - } - - return view; - }; - - /** - * Displays a counter of how many to dos are left to complete - * - * @param {number} activeTodos The number of active todos. - * @returns {string} String containing the count - */ - Template.prototype.itemCounter = function (activeTodos) { - var plural = activeTodos === 1 ? '' : 's'; - - return '' + activeTodos + ' item' + plural + ' left'; - }; - - /** - * Updates the text within the "Clear completed" button - * - * @param {[type]} completedTodos The number of completed todos. - * @returns {string} String containing the count - */ - Template.prototype.clearCompletedButton = function (completedTodos) { - if (completedTodos > 0) { - return 'Clear completed (' + completedTodos + ')'; - } else { - return ''; - } - }; - - // Export to window - window.app = window.app || {}; - window.app.Template = Template; -})(window); +/*jshint laxbreak:true */ +(function (window) { + 'use strict'; + + /** + * Sets up defaults for all the Template methods such as a default template + * + * @constructor + */ + function Template() { + this.defaultTemplate + = '
  • ' + + '
    ' + + '' + + '' + + '' + + '
    ' + + '
  • '; + } + + /** + * Creates an
  • HTML string and returns it for placement in your app. + * + * NOTE: In real life you should be using a templating engine such as Mustache + * or Handlebars, however, this is a vanilla JS example. + * + * @param {object} data The object containing keys you want to find in the + * template to replace. + * @returns {string} HTML String of an
  • element + * + * @example + * view.show({ + * id: 1, + * title: "Hello World", + * completed: 0, + * }); + */ + Template.prototype.show = function (data) { + var i, l; + var view = ''; + + for (i = 0, l = data.length; i < l; i++) { + var template = this.defaultTemplate; + var completed = ''; + var checked = ''; + + if (data[i].completed) { + completed = 'completed'; + checked = 'checked'; + } + + template = template.replace('{{id}}', data[i].id); + template = template.replace('{{title}}', data[i].title); + template = template.replace('{{completed}}', completed); + template = template.replace('{{checked}}', checked); + + view = view + template; + } + + return view; + }; + + /** + * Displays a counter of how many to dos are left to complete + * + * @param {number} activeTodos The number of active todos. + * @returns {string} String containing the count + */ + Template.prototype.itemCounter = function (activeTodos) { + var plural = activeTodos === 1 ? '' : 's'; + + return '' + activeTodos + ' item' + plural + ' left'; + }; + + /** + * Updates the text within the "Clear completed" button + * + * @param {[type]} completedTodos The number of completed todos. + * @returns {string} String containing the count + */ + Template.prototype.clearCompletedButton = function (completedTodos) { + if (completedTodos > 0) { + return 'Clear completed (' + completedTodos + ')'; + } else { + return ''; + } + }; + + // Export to window + window.app = window.app || {}; + window.app.Template = Template; +})(window); diff --git a/labs/11 JavaScript Modules/advanced_obj_fun/js/view.js b/09 JS modules/workspace/advanced_obj_fun/js/view.js old mode 100644 new mode 100755 similarity index 96% rename from labs/11 JavaScript Modules/advanced_obj_fun/js/view.js rename to 09 JS modules/workspace/advanced_obj_fun/js/view.js index 60113c3..bbec14b --- a/labs/11 JavaScript Modules/advanced_obj_fun/js/view.js +++ b/09 JS modules/workspace/advanced_obj_fun/js/view.js @@ -1,219 +1,219 @@ -/*global qs, qsa, $on, $parent, $live */ - -(function (window) { - 'use strict'; - - /** - * View that abstracts away the browser's DOM completely. - * It has two simple entry points: - * - * - bind(eventName, handler) - * Takes a todo application event and registers the handler - * - render(command, parameterObject) - * Renders the given command with the options - */ - function View(template) { - this.template = template; - - this.ENTER_KEY = 13; - this.ESCAPE_KEY = 27; - - this.$todoList = qs('#todo-list'); - this.$todoItemCounter = qs('#todo-count'); - this.$clearCompleted = qs('#clear-completed'); - this.$main = qs('#main'); - this.$footer = qs('#footer'); - this.$toggleAll = qs('#toggle-all'); - this.$newTodo = qs('#new-todo'); - } - - View.prototype._removeItem = function (id) { - var elem = qs('[data-id="' + id + '"]'); - - if (elem) { - this.$todoList.removeChild(elem); - } - }; - - View.prototype._clearCompletedButton = function (completedCount, visible) { - this.$clearCompleted.innerHTML = this.template.clearCompletedButton(completedCount); - this.$clearCompleted.style.display = visible ? 'block' : 'none'; - }; - - View.prototype._setFilter = function (currentPage) { - qs('#filters .selected').className = ''; - qs('#filters [href="#/' + currentPage + '"]').className = 'selected'; - }; - - View.prototype._elementComplete = function (id, completed) { - var listItem = qs('[data-id="' + id + '"]'); - - if (!listItem) { - return; - } - - listItem.className = completed ? 'completed' : ''; - - // In case it was toggled from an event and not by clicking the checkbox - qs('input', listItem).checked = completed; - }; - - View.prototype._editItem = function (id, title) { - var listItem = qs('[data-id="' + id + '"]'); - - if (!listItem) { - return; - } - - listItem.className = listItem.className + ' editing'; - - var input = document.createElement('input'); - input.className = 'edit'; - - listItem.appendChild(input); - input.focus(); - input.value = title; - }; - - View.prototype._editItemDone = function (id, title) { - var listItem = qs('[data-id="' + id + '"]'); - - if (!listItem) { - return; - } - - var input = qs('input.edit', listItem); - listItem.removeChild(input); - - listItem.className = listItem.className.replace('editing', ''); - - qsa('label', listItem).forEach(function (label) { - label.textContent = title; - }); - }; - - View.prototype.render = function (viewCmd, parameter) { - var that = this; - var viewCommands = { - showEntries: function () { - that.$todoList.innerHTML = that.template.show(parameter); - }, - removeItem: function () { - that._removeItem(parameter); - }, - updateElementCount: function () { - that.$todoItemCounter.innerHTML = that.template.itemCounter(parameter); - }, - clearCompletedButton: function () { - that._clearCompletedButton(parameter.completed, parameter.visible); - }, - contentBlockVisibility: function () { - that.$main.style.display = that.$footer.style.display = parameter.visible ? 'block' : 'none'; - }, - toggleAll: function () { - that.$toggleAll.checked = parameter.checked; - }, - setFilter: function () { - that._setFilter(parameter); - }, - clearNewTodo: function () { - that.$newTodo.value = ''; - }, - elementComplete: function () { - that._elementComplete(parameter.id, parameter.completed); - }, - editItem: function () { - that._editItem(parameter.id, parameter.title); - }, - editItemDone: function () { - that._editItemDone(parameter.id, parameter.title); - } - }; - - viewCommands[viewCmd](); - }; - - View.prototype._itemId = function (element) { - var li = $parent(element, 'li'); - return parseInt(li.dataset.id, 10); - }; - - View.prototype._bindItemEditDone = function (handler) { - var that = this; - $live('#todo-list li .edit', 'blur', function () { - if (!this.dataset.iscanceled) { - handler({ - id: that._itemId(this), - title: this.value - }); - } - }); - - $live('#todo-list li .edit', 'keypress', function (event) { - if (event.keyCode === that.ENTER_KEY) { - // Remove the cursor from the input when you hit enter just like if it - // were a real form - this.blur(); - } - }); - }; - - View.prototype._bindItemEditCancel = function (handler) { - var that = this; - $live('#todo-list li .edit', 'keyup', function (event) { - if (event.keyCode === that.ESCAPE_KEY) { - this.dataset.iscanceled = true; - this.blur(); - - handler({id: that._itemId(this)}); - } - }); - }; - - View.prototype.bind = function (event, handler) { - var that = this; - if (event === 'newTodo') { - $on(that.$newTodo, 'change', function () { - handler(that.$newTodo.value); - }); - - } else if (event === 'removeCompleted') { - $on(that.$clearCompleted, 'click', function () { - handler(); - }); - - } else if (event === 'toggleAll') { - $on(that.$toggleAll, 'click', function () { - handler({completed: this.checked}); - }); - - } else if (event === 'itemEdit') { - $live('#todo-list li label', 'dblclick', function () { - handler({id: that._itemId(this)}); - }); - - } else if (event === 'itemRemove') { - $live('#todo-list .destroy', 'click', function () { - handler({id: that._itemId(this)}); - }); - - } else if (event === 'itemToggle') { - $live('#todo-list .toggle', 'click', function () { - handler({ - id: that._itemId(this), - completed: this.checked - }); - }); - - } else if (event === 'itemEditDone') { - that._bindItemEditDone(handler); - - } else if (event === 'itemEditCancel') { - that._bindItemEditCancel(handler); - } - }; - - // Export to window - window.app = window.app || {}; - window.app.View = View; -}(window)); +/*global qs, qsa, $on, $parent, $live */ + +(function (window) { + 'use strict'; + + /** + * View that abstracts away the browser's DOM completely. + * It has two simple entry points: + * + * - bind(eventName, handler) + * Takes a todo application event and registers the handler + * - render(command, parameterObject) + * Renders the given command with the options + */ + function View(template) { + this.template = template; + + this.ENTER_KEY = 13; + this.ESCAPE_KEY = 27; + + this.$todoList = qs('#todo-list'); + this.$todoItemCounter = qs('#todo-count'); + this.$clearCompleted = qs('#clear-completed'); + this.$main = qs('#main'); + this.$footer = qs('#footer'); + this.$toggleAll = qs('#toggle-all'); + this.$newTodo = qs('#new-todo'); + } + + View.prototype._removeItem = function (id) { + var elem = qs('[data-id="' + id + '"]'); + + if (elem) { + this.$todoList.removeChild(elem); + } + }; + + View.prototype._clearCompletedButton = function (completedCount, visible) { + this.$clearCompleted.innerHTML = this.template.clearCompletedButton(completedCount); + this.$clearCompleted.style.display = visible ? 'block' : 'none'; + }; + + View.prototype._setFilter = function (currentPage) { + qs('#filters .selected').className = ''; + qs('#filters [href="#/' + currentPage + '"]').className = 'selected'; + }; + + View.prototype._elementComplete = function (id, completed) { + var listItem = qs('[data-id="' + id + '"]'); + + if (!listItem) { + return; + } + + listItem.className = completed ? 'completed' : ''; + + // In case it was toggled from an event and not by clicking the checkbox + qs('input', listItem).checked = completed; + }; + + View.prototype._editItem = function (id, title) { + var listItem = qs('[data-id="' + id + '"]'); + + if (!listItem) { + return; + } + + listItem.className = listItem.className + ' editing'; + + var input = document.createElement('input'); + input.className = 'edit'; + + listItem.appendChild(input); + input.focus(); + input.value = title; + }; + + View.prototype._editItemDone = function (id, title) { + var listItem = qs('[data-id="' + id + '"]'); + + if (!listItem) { + return; + } + + var input = qs('input.edit', listItem); + listItem.removeChild(input); + + listItem.className = listItem.className.replace('editing', ''); + + qsa('label', listItem).forEach(function (label) { + label.textContent = title; + }); + }; + + View.prototype.render = function (viewCmd, parameter) { + var that = this; + var viewCommands = { + showEntries: function () { + that.$todoList.innerHTML = that.template.show(parameter); + }, + removeItem: function () { + that._removeItem(parameter); + }, + updateElementCount: function () { + that.$todoItemCounter.innerHTML = that.template.itemCounter(parameter); + }, + clearCompletedButton: function () { + that._clearCompletedButton(parameter.completed, parameter.visible); + }, + contentBlockVisibility: function () { + that.$main.style.display = that.$footer.style.display = parameter.visible ? 'block' : 'none'; + }, + toggleAll: function () { + that.$toggleAll.checked = parameter.checked; + }, + setFilter: function () { + that._setFilter(parameter); + }, + clearNewTodo: function () { + that.$newTodo.value = ''; + }, + elementComplete: function () { + that._elementComplete(parameter.id, parameter.completed); + }, + editItem: function () { + that._editItem(parameter.id, parameter.title); + }, + editItemDone: function () { + that._editItemDone(parameter.id, parameter.title); + } + }; + + viewCommands[viewCmd](); + }; + + View.prototype._itemId = function (element) { + var li = $parent(element, 'li'); + return parseInt(li.dataset.id, 10); + }; + + View.prototype._bindItemEditDone = function (handler) { + var that = this; + $live('#todo-list li .edit', 'blur', function () { + if (!this.dataset.iscanceled) { + handler({ + id: that._itemId(this), + title: this.value + }); + } + }); + + $live('#todo-list li .edit', 'keypress', function (event) { + if (event.keyCode === that.ENTER_KEY) { + // Remove the cursor from the input when you hit enter just like if it + // were a real form + this.blur(); + } + }); + }; + + View.prototype._bindItemEditCancel = function (handler) { + var that = this; + $live('#todo-list li .edit', 'keyup', function (event) { + if (event.keyCode === that.ESCAPE_KEY) { + this.dataset.iscanceled = true; + this.blur(); + + handler({id: that._itemId(this)}); + } + }); + }; + + View.prototype.bind = function (event, handler) { + var that = this; + if (event === 'newTodo') { + $on(that.$newTodo, 'change', function () { + handler(that.$newTodo.value); + }); + + } else if (event === 'removeCompleted') { + $on(that.$clearCompleted, 'click', function () { + handler(); + }); + + } else if (event === 'toggleAll') { + $on(that.$toggleAll, 'click', function () { + handler({completed: this.checked}); + }); + + } else if (event === 'itemEdit') { + $live('#todo-list li label', 'dblclick', function () { + handler({id: that._itemId(this)}); + }); + + } else if (event === 'itemRemove') { + $live('#todo-list .destroy', 'click', function () { + handler({id: that._itemId(this)}); + }); + + } else if (event === 'itemToggle') { + $live('#todo-list .toggle', 'click', function () { + handler({ + id: that._itemId(this), + completed: this.checked + }); + }); + + } else if (event === 'itemEditDone') { + that._bindItemEditDone(handler); + + } else if (event === 'itemEditCancel') { + that._bindItemEditCancel(handler); + } + }; + + // Export to window + window.app = window.app || {}; + window.app.View = View; +}(window)); diff --git a/09 JS modules/workspace/closure.js b/09 JS modules/workspace/closure.js new file mode 100755 index 0000000..438cec4 --- /dev/null +++ b/09 JS modules/workspace/closure.js @@ -0,0 +1,18 @@ +function celebrityName (firstName) { + + var nameIntro = "This celebrity is "; + + function lastName (theLastName) { + return nameIntro + firstName + " " + theLastName; + } + + //return lastName; + + return function(theLastName) { + return nameIntro + firstName + " " + theLastName; + } +} + +var mjName = celebrityName("Michael"); + +mjName ("Jackson"); \ No newline at end of file diff --git a/09 JS modules/workspace/extending_js_types.html b/09 JS modules/workspace/extending_js_types.html new file mode 100755 index 0000000..4c7c3de --- /dev/null +++ b/09 JS modules/workspace/extending_js_types.html @@ -0,0 +1,52 @@ + + + + + + Palindrome + + + + + + + diff --git a/labs/11 JavaScript Modules/first_class_functions.html b/09 JS modules/workspace/first_class_functions.html old mode 100644 new mode 100755 similarity index 100% rename from labs/11 JavaScript Modules/first_class_functions.html rename to 09 JS modules/workspace/first_class_functions.html diff --git a/09 JS modules/workspace/function_closure.html b/09 JS modules/workspace/function_closure.html new file mode 100755 index 0000000..d89890f --- /dev/null +++ b/09 JS modules/workspace/function_closure.html @@ -0,0 +1,58 @@ +ctype html> + + + + + Click me! + + + + + + +
    + + + diff --git a/09 JS modules/workspace/js_classes.html b/09 JS modules/workspace/js_classes.html new file mode 100755 index 0000000..393a433 --- /dev/null +++ b/09 JS modules/workspace/js_classes.html @@ -0,0 +1,94 @@ + + + + + + Coffee Constructor + + + + + + + \ No newline at end of file diff --git a/labs/11 JavaScript Modules/prototype_inheritance.html b/09 JS modules/workspace/prototype_inheritance.html old mode 100644 new mode 100755 similarity index 100% rename from labs/11 JavaScript Modules/prototype_inheritance.html rename to 09 JS modules/workspace/prototype_inheritance.html diff --git a/10 Document Object Model/README.md b/10 Document Object Model/README.md new file mode 100755 index 0000000..a917492 --- /dev/null +++ b/10 Document Object Model/README.md @@ -0,0 +1,163 @@ +# The Document Object Model (DOM) + +DOM manipulation by JS allows you to 'read', 'write', 'update', and 'delete' actual elements of the web page itself. In other words, you can use JS to modify the HTML presented to the user dynamically. + +## DOM elements + +The DOM is essentially a description of the elements on a web page along with their attributes, properties, and accessible methods. To see JS interacting with the DOM, follow the next steps. + +> Do these after a quick scan of the [Introduction to the DOM from Mozilla Developer Network (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction). + +1. Load `map.html` and preview the functionality. +2. Observe that the `showCoords()` function sets the innerHTML property of a DOM element. +3. Also observe that the `addtext()` function dynamically adds a new element to the DOM ( a "text node"). + * Similarly you can add other HTML tags such as `` or `

    ` etc., or update their attributes + +> [Discussions about innerHTML vs appendChild(txtNode) on StatckOverflow](http://stackoverflow.com/questions/2305654/innerhtml-vs-appendchildtxtnode). + +### Test your understanding + +1. Use the `createElement` method of the document object to add a new list `

  • -
    -

    Edit a Note

    - -
    - - -

    -
    - - - - \ No newline at end of file diff --git a/labs/10 Single-Page Applications/worksheet.md b/labs/10 Single-Page Applications/worksheet.md deleted file mode 100644 index 0f896ca..0000000 --- a/labs/10 Single-Page Applications/worksheet.md +++ /dev/null @@ -1,82 +0,0 @@ -# Single-Page Applications - -This week you will be modifying the content of the web browser through manipulating the Document Object Model (DOM). Once you have mastered this you will apply the skills you have learned in modify a Single-Page Application (SPA). - -## Task List - -Aim to complete these in roughly 30-60 minutes of lab time. - -1. Use a range of event handlers to access data from a simple form and display it in html tags. - -2. Take a complex JavaScript object containing arrays and objects and display the data in the web browser. - -3. Take an existing SPA and make improvements to it. - -## Resources - -One of the most useful resources is the Mozilla JS Tutorial. You should read this to become familiar with the best features of the language. - -## 1 Event Handlers - -In this task you will write code to respond to a series of events. The code will access data contained within a form and display it in HTML tags. - -1. Open the Codio project containing the lab code (see the previous lab) and open the file simple_dom.html from /labs/10 Single-Page Applications/ -2. Use the Current File (static) button to test out what the page does. Make sure you open the Chrome Developer Tools at the Console tab. -3. Open the associated JS file js/simple_dom.js and check you understand how it works. - -Experiment by entering text into the three textboxes and clicking on the button. Check you fully understand how the interactions work. - -Identify how the script can locate both the textboxes and the heading and paragraph tags. - -Notice the two different ways the functions are called by the event handler on line 5 and line 6. Can you understand the different syntax used? - -There is one bug in the script, can you identify what this is? - -### Check Your Understanding - -Now you have a good idea of how the JavaScript code works, try the following: - -1. There is currently an event handler called onkeypress attached to the email field. Substitute the following events and verify they do what you expect: - a. onkeydown - b. onkeyup - c. onmouseover - d. onmouseout - e. onchange - f. onclick - -2. The onclick event handler currently calls a named function. Using the code on line 6 as a guide, replace this with an anonymous inline function. -3. Modify the page so that the name, email and password are displayed in the heading and paragraph tags when the button is clicked. -4. Modify the page so that the headings and paragraphs are updated as the user types. Remove the button and its event handler. - -## 2 DOM Lists - -In this second task you will learn how to visualise lists of data by manipulating the DOM. - -1. Open the file building_lists.html and its companion script file building_lists.js and take a few moments to understand how the program works. -2. Open the page in the Chrome browser, open the console in the Chrome Developer Tools then refresh the browser. -3. Open the data/books.json file and see if you can relate the contents to the data displayed in the console. - -### Test Your Understanding - -Now you understand how the program works, try the following activities: - -1. Lets start with a simple task. Change the list to bullet-points and display the book titles and year of publication. -2. Now replace the bulleted list with a table that displays each field in a different column -3. Can you add column-headings? -4. Finally add an event handler so when you click on a row, the book title is displayed at the top of the page. - -## 3 Single-Page Application - -In this final activity you will be applying the skills you acquired in the previous two activities to make improvements to a simple single-page application. - -1. Open the file lecture_notes.html and its associated JavaScript file. -2. Load the page into a chrome tab and open the Console window. -3. Click on each of the two links at the top of the page and see what happens... - -### Test Your Knowledge - -Its now time to implement the missing functionality: - -1. The links should allow you to toggle between the add and edit content. At the moment this functionality is missing. See if you can fix this (the code on lines 7-9 should give you a clue). -2. Next we need to add new note objects to the notes array when the add button is clicked. The basic function has already been written. You need to complete it. Make sure you clear the title and note fields once the note has been added. Call the loadList function to load the list items into the notes list. -3. Finally, as the user changes the title or note you need to update the appropriate object in the array. diff --git a/labs/11 JavaScript Modules/.DS_Store b/labs/11 JavaScript Modules/.DS_Store deleted file mode 100644 index 2ab6840..0000000 Binary files a/labs/11 JavaScript Modules/.DS_Store and /dev/null differ diff --git a/labs/11 JavaScript Modules/README.md b/labs/11 JavaScript Modules/README.md deleted file mode 100644 index ce515a6..0000000 --- a/labs/11 JavaScript Modules/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# JavaScript Objects and Functions 2 - -## About - -This week covers objects and functions again, but with more advanced uses. These code patterns are what brings out the full power of JS and are essential for understanding JS code libraries that you might use for bigger projects. - -In particular, the todo app in the `advanced_obj_fun` directory contains some example code that uses many of the key techniques you need to learn. - -## Priority of Labs - -* Basics - - first_class_functions.html - - function_closure.html - - js_classes.html -* Intermediate - - prototype_inheritance.html - - extending_js_types.html -* Advanced - - advanced_obj_fun/ - * js/ - - model.js - - store.js - - app.js - * index.html - -For the advanced lab code, the application uses other files than those listed. However, by studying the four files listed above you will see how to implement the model part of __Model__ _View Controller_. diff --git a/labs/11 JavaScript Modules/extending_js_types.html b/labs/11 JavaScript Modules/extending_js_types.html deleted file mode 100644 index 84fa7c6..0000000 --- a/labs/11 JavaScript Modules/extending_js_types.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - -Palindrome - - - - - - diff --git a/labs/11 JavaScript Modules/function_closure.html b/labs/11 JavaScript Modules/function_closure.html deleted file mode 100644 index cd9b745..0000000 --- a/labs/11 JavaScript Modules/function_closure.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - -Click me! - - - - - -
    - - - diff --git a/labs/11 JavaScript Modules/js_classes.html b/labs/11 JavaScript Modules/js_classes.html deleted file mode 100644 index db52b2a..0000000 --- a/labs/11 JavaScript Modules/js_classes.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - -Coffee Constructor - - - - - - diff --git a/labs/11 JavaScript Modules/worksheet.md b/labs/11 JavaScript Modules/worksheet.md deleted file mode 100644 index e25e156..0000000 --- a/labs/11 JavaScript Modules/worksheet.md +++ /dev/null @@ -1,94 +0,0 @@ -# JavaScript Modules - -This week covers more advanced uses of objects and functions in JS. Objects and functions are extremely powerful. This week explores some of the more advanced ways you can use both of them. - -## Task List - -Aim to complete these in roughly 60-80 minutes of lab time. - -1. Write a closure to maintain the internal state of a counter. -2. Define a constructor function and modify its prototype. -3. Create a new JS module. (For the adventurous!) - -## 1. Use closure to maintain internal state - -MDN guide to JS closures. - -- Load the `function_closure.html` file and take a look at the functionality. -- Observe that the `button.onclick` event handler is a function that uses the count variable -- Observe also that this function does not specify any value for count -- Instead, count inside the handler function is given its value by the enclosing function context - -Since the `window.onload` handler function encloses the `button.onclick` one, the latter has an “environment” of local variables that it can work with. The `onclick` function is therefore able to keep track of the state of a higher-level variable, namely `count`. - -This technique adds a lot of power to the language. For example, as we see here, state can be maintained in “private” scoped variables, even after the enclosing function has returned. - -### Test your understanding - -1. Add three buttons to the function_closure.html page: ‘-’, ‘0’, ‘+’. - - You can think of these as the playback speed controls for a media player -2. Add a `
    ` with ID `playback-speed` to the page as well -3. Using the existing onload handler as an example, write another window.onload handler: - - This one should have an internal state representing the current playback speed - - Clicking on one of the ‘+’ or ‘-’ buttons should increment or decrement the current speed as appropriate - - Clicking on the ‘0’ button should reset the current speed to 0 - - The current speed should be displayed in the `
    ` you added -4. If you have time, use a single helper function to update the `
    ` rather than update it in each separate click handler. - -## 2. Use functions to construct objects and modify their prototype - -Object Oriented JavaScript - -1. Load the js_classes.html file and view it in a browser -2. Open the console window to view its output -3. Note the creation of three “coffee objects” in the JS code, using the new keyword and a constructor function called Coffee -4. Now open extending_js_types.html to see the use of JavaScript’s inheritance mechanism: the prototype -5. Open the page in the web browser and note the console output. -6. Note that the prototype object of the built-in String JS data type has allowed us to “extend” the functionality of strings by adding a new string method called palindrome . - -Prototype inheritance applies to any JS data type / object, including objects created as instances of constructor functions like Coffee() . The prototype of an object can be modified or overridden by JS code at runtime. Any other object sharing this prototype is also modified in the same way. - -### Test your understanding - -1. First, add a Tea(type, ounces) constructor function to the js_classes.html JavaScript. - - Add properties for type and ounces - - Add methods: getSteepTime() and order() , and use appropriate code within them -2. Now create some new Tea instance objects and invoke the order() method to check that it works -3. Once it is working, extend the Tea and Coffee prototypes by giving both a new method sip() - - Each invocation of sip() should simply log "Hmm, tasty" to the console - -## 3. Create a JavaScript module -See JS module pattern along with Immediately-Invoked function expressions for a good discussion of what follows. - -This is more advanced but begins to show the power of JS as a fully fledged language for designing and building large-scale applications. In particular, function closures and immediate invocation allows us to define self-contained modules that do not pollute the global namespace. This is how libraries such as, for example, jQuery and YUI, are put together. - -1. Load the advanced_obj_fun folder in Brackets and preview index.html. -2. Play with the app, it is a simple task list. However, it is implemented as a proper application, using modules. -3. Load the following files in the editor: -``` -advanced_obj_fun/ - js/ - model.js - store.js - app.js -index.html -``` -Together these JS modules implement the “model” part of MVC (Model View Controller). - -4. Look at the JS files and note some of their similarities: - a. Each one defines an “immediately invoked function” - b. Each one contains a constructor function at the top - c. store.js and model.js both extend their constructor prototypes - d. store.js and model.js both export their constructor to a global namespace at the end - -These things are all typical of the “module pattern” in JS. For more on it, see the website linked to above. - -### Test your understanding - -Write a short module that exports a utility object called “$” to the global namespace. - -This object should reimplement the JS document.getElementById() selector method using its own method $.id(). - -In other words, any page that includes your new module JS file should be able to call $.id("myButton") and this will return a reference to the DOM element with ID “myButton” (if any). - -By doing this you should start to see how libraries such as jQuery add to or simplify the standard functionality of core JavaScript! diff --git a/labs/12 Unit Testing/.DS_Store b/labs/12 Unit Testing/.DS_Store deleted file mode 100644 index b204089..0000000 Binary files a/labs/12 Unit Testing/.DS_Store and /dev/null differ diff --git a/labs/12 Unit Testing/Testing_withPHPUnit.md b/labs/12 Unit Testing/Testing_withPHPUnit.md deleted file mode 100644 index 2640f6d..0000000 --- a/labs/12 Unit Testing/Testing_withPHPUnit.md +++ /dev/null @@ -1,169 +0,0 @@ -# Testing PHP with PHPUnit - -PHPUnit is quite similar to the JUnit library you have used for testing Java code. Download PHP Archive (PHAR) that contains everything you need in order to use PHPUnit. Simply download it, make it executable, and put it into your $PATH. - -Open Tools > Terminal and give the following commands: -``` -$ wget https://phar.phpunit.de/phpunit.phar - -$ chmod +x phpunit.phar - -$ sudo mv phpunit.phar /usr/local/bin/phpunit - -$ phpunit --version -``` - -PHPUnit 4.8.0 by Sebastian Bergmann and contributors. -You can also immediately use the PHAR after you have downloaded it, of course: -``` -$ wget https://phar.phpunit.de/phpunit.phar - -$ php phpunit.phar --version -PHPUnit 4.8.0 by Sebastian Bergmann and contributors -``` - -## Testing classes - -The next code snippet is a simple class called Money, which has one attribute called amount. (src/Money.php) -```php - -amount = $amount; - } - public function getAmount() - { - return $this->amount; - } - public function negate() - { - return new Money(-1 * $this->amount); - } - - // ... -} -``` - -The corresponding test class is called MoneyTest. (tests/MoneyTest.php) - -```php - -negate(); - - // Assert - $this->assertEquals(-1, $b->getAmount()); - } - // ... -} - -``` - -## Running tests - -``` -$ phpunit --bootstrap src/autoload.php tests/MoneyTest -PHPUnit 4.8.0 by Sebastian Bergmann and contributors. - -.................... - -Time: 121 ms, Memory: 4.50Mb - -OK (20 tests, 39 assertions) -``` - -Let’s have a look at what the three parts of above invocation mean: - -• phpunit invokes the PHPUnit command-line test runner. We assume that you have downloaded phpunit.phar (see above) and put it into your $PATH as phpunit. - -• --bootstrap src/autoload.php instructs the PHPUnit command-line test runner to include src/autoload.php before the test execution. Such a bootstrap script is commonly used to set up autoloading for the classes that are to be tested. - -• tests/MoneyTest instructs the PHPUnit command-line test runner to execute the tests of the MoneyTest class that is declared in tests/MoneyTest.php. - -Using tests instead of tests/MoneyTest would instruct the PHPUnit command-line test runner to execute all tests found declared in *Test.php sourcecode files in the tests directory. - -You can have in the src folder a simple script in the autoload.php file, which will be used to find class definitions. The contents of it can be e.g. - -```php - -``` -If you don't have this kind of script, you can list the source files needed for testing instead. - -## Testing procedural code - -Let’s assume we have a PHP function which we want to test. It’s located in a PHP file named function.php. - -``` php - - - -``` - -We want to test that function - -``` -require_once 'includes/functions.php'; -class EasterTest extends PHPUnit_Framework_TestCase { - public function testEasterOk() { - $easter2011 = easter(2011); - $this->assertEquals($easter2011, gmmktime(0,0,0,4,24,2011)); - } - - public function testEasterFalse() { - $easter2011 = easter(2011); - $this->assertNotEquals($easter2011, gmmktime(0,0,0,12,24,2011)); - } -} -``` - -First there is a link to the script containing the easter-function (here: functions.php). -Then we generate a new class (“EasterTest”), which extends PHPUnit. In this class there are two basic tests: - -A positive test (24.4.2011, is this easter sunday?) - -A negative test (24.12.2011, is this easter sunday?) - -``` -$ phpunit EasterTest.php -PHPUnit 4.8.0 by Sebastian Bergmann and contributors. - -.................... - -Time: 121 ms, Memory: 4.50Mb - -not OK (2 tests, 2 assertions) -``` - - -Of course we get not OK, because we have not yet implemented the function. - - -Documentation on PHPUnit see https://phpunit.de/manual/current/en/index.html - -### Using PHPUnit with Codeigniter - -See e.g. https://github.com/kenjis/ci-phpunit-test diff --git a/labs/12 Unit Testing/css/vendor/qunit-1.17.1.css b/labs/12 Unit Testing/css/vendor/qunit-1.17.1.css deleted file mode 100644 index 0eb0b01..0000000 --- a/labs/12 Unit Testing/css/vendor/qunit-1.17.1.css +++ /dev/null @@ -1,280 +0,0 @@ -/*! - * QUnit 1.17.1 - * http://qunitjs.com/ - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2015-01-20T19:39Z - */ - -/** Font Family and Sizes */ - -#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { - font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; -} - -#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } -#qunit-tests { font-size: smaller; } - - -/** Resets */ - -#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { - margin: 0; - padding: 0; -} - - -/** Header */ - -#qunit-header { - padding: 0.5em 0 0.5em 1em; - - color: #8699A4; - background-color: #0D3349; - - font-size: 1.5em; - line-height: 1em; - font-weight: 400; - - border-radius: 5px 5px 0 0; -} - -#qunit-header a { - text-decoration: none; - color: #C2CCD1; -} - -#qunit-header a:hover, -#qunit-header a:focus { - color: #FFF; -} - -#qunit-testrunner-toolbar label { - display: inline-block; - padding: 0 0.5em 0 0.1em; -} - -#qunit-banner { - height: 5px; -} - -#qunit-testrunner-toolbar { - padding: 0.5em 1em 0.5em 1em; - color: #5E740B; - background-color: #EEE; - overflow: hidden; -} - -#qunit-userAgent { - padding: 0.5em 1em 0.5em 1em; - background-color: #2B81AF; - color: #FFF; - text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; -} - -#qunit-modulefilter-container { - float: right; - padding: 0.2em; -} - -.qunit-url-config { - display: inline-block; - padding: 0.1em; -} - -.qunit-filter { - display: block; - float: right; - margin-left: 1em; -} - -/** Tests: Pass/Fail */ - -#qunit-tests { - list-style-position: inside; -} - -#qunit-tests li { - padding: 0.4em 1em 0.4em 1em; - border-bottom: 1px solid #FFF; - list-style-position: inside; -} - -#qunit-tests > li { - display: none; -} - -#qunit-tests li.running, -#qunit-tests li.pass, -#qunit-tests li.fail, -#qunit-tests li.skipped { - display: list-item; -} - -#qunit-tests.hidepass li.running, -#qunit-tests.hidepass li.pass { - display: none; -} - -#qunit-tests li strong { - cursor: pointer; -} - -#qunit-tests li.skipped strong { - cursor: default; -} - -#qunit-tests li a { - padding: 0.5em; - color: #C2CCD1; - text-decoration: none; -} -#qunit-tests li a:hover, -#qunit-tests li a:focus { - color: #000; -} - -#qunit-tests li .runtime { - float: right; - font-size: smaller; -} - -.qunit-assert-list { - margin-top: 0.5em; - padding: 0.5em; - - background-color: #FFF; - - border-radius: 5px; -} - -.qunit-collapsed { - display: none; -} - -#qunit-tests table { - border-collapse: collapse; - margin-top: 0.2em; -} - -#qunit-tests th { - text-align: right; - vertical-align: top; - padding: 0 0.5em 0 0; -} - -#qunit-tests td { - vertical-align: top; -} - -#qunit-tests pre { - margin: 0; - white-space: pre-wrap; - word-wrap: break-word; -} - -#qunit-tests del { - background-color: #E0F2BE; - color: #374E0C; - text-decoration: none; -} - -#qunit-tests ins { - background-color: #FFCACA; - color: #500; - text-decoration: none; -} - -/*** Test Counts */ - -#qunit-tests b.counts { color: #000; } -#qunit-tests b.passed { color: #5E740B; } -#qunit-tests b.failed { color: #710909; } - -#qunit-tests li li { - padding: 5px; - background-color: #FFF; - border-bottom: none; - list-style-position: inside; -} - -/*** Passing Styles */ - -#qunit-tests li li.pass { - color: #3C510C; - background-color: #FFF; - border-left: 10px solid #C6E746; -} - -#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } -#qunit-tests .pass .test-name { color: #366097; } - -#qunit-tests .pass .test-actual, -#qunit-tests .pass .test-expected { color: #999; } - -#qunit-banner.qunit-pass { background-color: #C6E746; } - -/*** Failing Styles */ - -#qunit-tests li li.fail { - color: #710909; - background-color: #FFF; - border-left: 10px solid #EE5757; - white-space: pre; -} - -#qunit-tests > li:last-child { - border-radius: 0 0 5px 5px; -} - -#qunit-tests .fail { color: #000; background-color: #EE5757; } -#qunit-tests .fail .test-name, -#qunit-tests .fail .module-name { color: #000; } - -#qunit-tests .fail .test-actual { color: #EE5757; } -#qunit-tests .fail .test-expected { color: #008000; } - -#qunit-banner.qunit-fail { background-color: #EE5757; } - -/*** Skipped tests */ - -#qunit-tests .skipped { - background-color: #EBECE9; -} - -#qunit-tests .qunit-skipped-label { - background-color: #F4FF77; - display: inline-block; - font-style: normal; - color: #366097; - line-height: 1.8em; - padding: 0 0.5em; - margin: -0.4em 0.4em -0.4em 0; -} - -/** Result */ - -#qunit-testresult { - padding: 0.5em 1em 0.5em 1em; - - color: #2B81AF; - background-color: #D2E0E6; - - border-bottom: 1px solid #FFF; -} -#qunit-testresult .module-name { - font-weight: 700; -} - -/** Fixture */ - -#qunit-fixture { - position: absolute; - top: -10000px; - left: -10000px; - width: 1000px; - height: 1000px; -} diff --git a/labs/12 Unit Testing/js/simple_tests.js b/labs/12 Unit Testing/js/simple_tests.js deleted file mode 100644 index 59e2941..0000000 --- a/labs/12 Unit Testing/js/simple_tests.js +++ /dev/null @@ -1,11 +0,0 @@ -var myModule = (function () { - return { - hello: function hello() { - return 'hello world'; - } - }; -}()); - -test('Module Pattern', function() { - equal(myModule.hello(), 'hello world', 'Module works!'); -}); \ No newline at end of file diff --git a/labs/12 Unit Testing/js/vendor/qunit-1.17.1.js b/labs/12 Unit Testing/js/vendor/qunit-1.17.1.js deleted file mode 100644 index 006ca47..0000000 --- a/labs/12 Unit Testing/js/vendor/qunit-1.17.1.js +++ /dev/null @@ -1,2875 +0,0 @@ -/*! - * QUnit 1.17.1 - * http://qunitjs.com/ - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2015-01-20T19:39Z - */ - -(function( window ) { - -var QUnit, - config, - onErrorFnPrev, - loggingCallbacks = {}, - fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ), - toString = Object.prototype.toString, - hasOwn = Object.prototype.hasOwnProperty, - // Keep a local reference to Date (GH-283) - Date = window.Date, - now = Date.now || function() { - return new Date().getTime(); - }, - globalStartCalled = false, - runStarted = false, - setTimeout = window.setTimeout, - clearTimeout = window.clearTimeout, - defined = { - document: window.document !== undefined, - setTimeout: window.setTimeout !== undefined, - sessionStorage: (function() { - var x = "qunit-test-string"; - try { - sessionStorage.setItem( x, x ); - sessionStorage.removeItem( x ); - return true; - } catch ( e ) { - return false; - } - }()) - }, - /** - * Provides a normalized error string, correcting an issue - * with IE 7 (and prior) where Error.prototype.toString is - * not properly implemented - * - * Based on http://es5.github.com/#x15.11.4.4 - * - * @param {String|Error} error - * @return {String} error message - */ - errorString = function( error ) { - var name, message, - errorString = error.toString(); - if ( errorString.substring( 0, 7 ) === "[object" ) { - name = error.name ? error.name.toString() : "Error"; - message = error.message ? error.message.toString() : ""; - if ( name && message ) { - return name + ": " + message; - } else if ( name ) { - return name; - } else if ( message ) { - return message; - } else { - return "Error"; - } - } else { - return errorString; - } - }, - /** - * Makes a clone of an object using only Array or Object as base, - * and copies over the own enumerable properties. - * - * @param {Object} obj - * @return {Object} New object with only the own properties (recursively). - */ - objectValues = function( obj ) { - var key, val, - vals = QUnit.is( "array", obj ) ? [] : {}; - for ( key in obj ) { - if ( hasOwn.call( obj, key ) ) { - val = obj[ key ]; - vals[ key ] = val === Object( val ) ? objectValues( val ) : val; - } - } - return vals; - }; - -QUnit = {}; - -/** - * Config object: Maintain internal state - * Later exposed as QUnit.config - * `config` initialized at top of scope - */ -config = { - // The queue of tests to run - queue: [], - - // block until document ready - blocking: true, - - // by default, run previously failed tests first - // very useful in combination with "Hide passed tests" checked - reorder: true, - - // by default, modify document.title when suite is done - altertitle: true, - - // by default, scroll to top of the page when suite is done - scrolltop: true, - - // when enabled, all tests must call expect() - requireExpects: false, - - // add checkboxes that are persisted in the query-string - // when enabled, the id is set to `true` as a `QUnit.config` property - urlConfig: [ - { - id: "hidepassed", - label: "Hide passed tests", - tooltip: "Only show tests and assertions that fail. Stored as query-strings." - }, - { - id: "noglobals", - label: "Check for Globals", - tooltip: "Enabling this will test if any test introduces new properties on the " + - "`window` object. Stored as query-strings." - }, - { - id: "notrycatch", - label: "No try-catch", - tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging " + - "exceptions in IE reasonable. Stored as query-strings." - } - ], - - // Set of all modules. - modules: [], - - // The first unnamed module - currentModule: { - name: "", - tests: [] - }, - - callbacks: {} -}; - -// Push a loose unnamed module to the modules collection -config.modules.push( config.currentModule ); - -// Initialize more QUnit.config and QUnit.urlParams -(function() { - var i, current, - location = window.location || { search: "", protocol: "file:" }, - params = location.search.slice( 1 ).split( "&" ), - length = params.length, - urlParams = {}; - - if ( params[ 0 ] ) { - for ( i = 0; i < length; i++ ) { - current = params[ i ].split( "=" ); - current[ 0 ] = decodeURIComponent( current[ 0 ] ); - - // allow just a key to turn on a flag, e.g., test.html?noglobals - current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; - if ( urlParams[ current[ 0 ] ] ) { - urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] ); - } else { - urlParams[ current[ 0 ] ] = current[ 1 ]; - } - } - } - - if ( urlParams.filter === true ) { - delete urlParams.filter; - } - - QUnit.urlParams = urlParams; - - // String search anywhere in moduleName+testName - config.filter = urlParams.filter; - - config.testId = []; - if ( urlParams.testId ) { - - // Ensure that urlParams.testId is an array - urlParams.testId = [].concat( urlParams.testId ); - for ( i = 0; i < urlParams.testId.length; i++ ) { - config.testId.push( urlParams.testId[ i ] ); - } - } - - // Figure out if we're running the tests from a server or not - QUnit.isLocal = location.protocol === "file:"; -}()); - -// Root QUnit object. -// `QUnit` initialized at top of scope -extend( QUnit, { - - // call on start of module test to prepend name to all tests - module: function( name, testEnvironment ) { - var currentModule = { - name: name, - testEnvironment: testEnvironment, - tests: [] - }; - - // DEPRECATED: handles setup/teardown functions, - // beforeEach and afterEach should be used instead - if ( testEnvironment && testEnvironment.setup ) { - testEnvironment.beforeEach = testEnvironment.setup; - delete testEnvironment.setup; - } - if ( testEnvironment && testEnvironment.teardown ) { - testEnvironment.afterEach = testEnvironment.teardown; - delete testEnvironment.teardown; - } - - config.modules.push( currentModule ); - config.currentModule = currentModule; - }, - - // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0. - asyncTest: function( testName, expected, callback ) { - if ( arguments.length === 2 ) { - callback = expected; - expected = null; - } - - QUnit.test( testName, expected, callback, true ); - }, - - test: function( testName, expected, callback, async ) { - var test; - - if ( arguments.length === 2 ) { - callback = expected; - expected = null; - } - - test = new Test({ - testName: testName, - expected: expected, - async: async, - callback: callback - }); - - test.queue(); - }, - - skip: function( testName ) { - var test = new Test({ - testName: testName, - skip: true - }); - - test.queue(); - }, - - // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0. - // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior. - start: function( count ) { - var globalStartAlreadyCalled = globalStartCalled; - - if ( !config.current ) { - globalStartCalled = true; - - if ( runStarted ) { - throw new Error( "Called start() outside of a test context while already started" ); - } else if ( globalStartAlreadyCalled || count > 1 ) { - throw new Error( "Called start() outside of a test context too many times" ); - } else if ( config.autostart ) { - throw new Error( "Called start() outside of a test context when " + - "QUnit.config.autostart was true" ); - } else if ( !config.pageLoaded ) { - - // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it - config.autostart = true; - return; - } - } else { - - // If a test is running, adjust its semaphore - config.current.semaphore -= count || 1; - - // Don't start until equal number of stop-calls - if ( config.current.semaphore > 0 ) { - return; - } - - // throw an Error if start is called more often than stop - if ( config.current.semaphore < 0 ) { - config.current.semaphore = 0; - - QUnit.pushFailure( - "Called start() while already started (test's semaphore was 0 already)", - sourceFromStacktrace( 2 ) - ); - return; - } - } - - resumeProcessing(); - }, - - // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0. - stop: function( count ) { - - // If there isn't a test running, don't allow QUnit.stop() to be called - if ( !config.current ) { - throw new Error( "Called stop() outside of a test context" ); - } - - // If a test is running, adjust its semaphore - config.current.semaphore += count || 1; - - pauseProcessing(); - }, - - config: config, - - // Safe object type checking - is: function( type, obj ) { - return QUnit.objectType( obj ) === type; - }, - - objectType: function( obj ) { - if ( typeof obj === "undefined" ) { - return "undefined"; - } - - // Consider: typeof null === object - if ( obj === null ) { - return "null"; - } - - var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ), - type = match && match[ 1 ] || ""; - - switch ( type ) { - case "Number": - if ( isNaN( obj ) ) { - return "nan"; - } - return "number"; - case "String": - case "Boolean": - case "Array": - case "Date": - case "RegExp": - case "Function": - return type.toLowerCase(); - } - if ( typeof obj === "object" ) { - return "object"; - } - return undefined; - }, - - extend: extend, - - load: function() { - config.pageLoaded = true; - - // Initialize the configuration options - extend( config, { - stats: { all: 0, bad: 0 }, - moduleStats: { all: 0, bad: 0 }, - started: 0, - updateRate: 1000, - autostart: true, - filter: "" - }, true ); - - config.blocking = false; - - if ( config.autostart ) { - resumeProcessing(); - } - } -}); - -// Register logging callbacks -(function() { - var i, l, key, - callbacks = [ "begin", "done", "log", "testStart", "testDone", - "moduleStart", "moduleDone" ]; - - function registerLoggingCallback( key ) { - var loggingCallback = function( callback ) { - if ( QUnit.objectType( callback ) !== "function" ) { - throw new Error( - "QUnit logging methods require a callback function as their first parameters." - ); - } - - config.callbacks[ key ].push( callback ); - }; - - // DEPRECATED: This will be removed on QUnit 2.0.0+ - // Stores the registered functions allowing restoring - // at verifyLoggingCallbacks() if modified - loggingCallbacks[ key ] = loggingCallback; - - return loggingCallback; - } - - for ( i = 0, l = callbacks.length; i < l; i++ ) { - key = callbacks[ i ]; - - // Initialize key collection of logging callback - if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) { - config.callbacks[ key ] = []; - } - - QUnit[ key ] = registerLoggingCallback( key ); - } -})(); - -// `onErrorFnPrev` initialized at top of scope -// Preserve other handlers -onErrorFnPrev = window.onerror; - -// Cover uncaught exceptions -// Returning true will suppress the default browser handler, -// returning false will let it run. -window.onerror = function( error, filePath, linerNr ) { - var ret = false; - if ( onErrorFnPrev ) { - ret = onErrorFnPrev( error, filePath, linerNr ); - } - - // Treat return value as window.onerror itself does, - // Only do our handling if not suppressed. - if ( ret !== true ) { - if ( QUnit.config.current ) { - if ( QUnit.config.current.ignoreGlobalErrors ) { - return true; - } - QUnit.pushFailure( error, filePath + ":" + linerNr ); - } else { - QUnit.test( "global failure", extend(function() { - QUnit.pushFailure( error, filePath + ":" + linerNr ); - }, { validTest: true } ) ); - } - return false; - } - - return ret; -}; - -function done() { - var runtime, passed; - - config.autorun = true; - - // Log the last module results - if ( config.previousModule ) { - runLoggingCallbacks( "moduleDone", { - name: config.previousModule.name, - tests: config.previousModule.tests, - failed: config.moduleStats.bad, - passed: config.moduleStats.all - config.moduleStats.bad, - total: config.moduleStats.all, - runtime: now() - config.moduleStats.started - }); - } - delete config.previousModule; - - runtime = now() - config.started; - passed = config.stats.all - config.stats.bad; - - runLoggingCallbacks( "done", { - failed: config.stats.bad, - passed: passed, - total: config.stats.all, - runtime: runtime - }); -} - -// Doesn't support IE6 to IE9 -// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack -function extractStacktrace( e, offset ) { - offset = offset === undefined ? 4 : offset; - - var stack, include, i; - - if ( e.stacktrace ) { - - // Opera 12.x - return e.stacktrace.split( "\n" )[ offset + 3 ]; - } else if ( e.stack ) { - - // Firefox, Chrome, Safari 6+, IE10+, PhantomJS and Node - stack = e.stack.split( "\n" ); - if ( /^error$/i.test( stack[ 0 ] ) ) { - stack.shift(); - } - if ( fileName ) { - include = []; - for ( i = offset; i < stack.length; i++ ) { - if ( stack[ i ].indexOf( fileName ) !== -1 ) { - break; - } - include.push( stack[ i ] ); - } - if ( include.length ) { - return include.join( "\n" ); - } - } - return stack[ offset ]; - } else if ( e.sourceURL ) { - - // Safari < 6 - // exclude useless self-reference for generated Error objects - if ( /qunit.js$/.test( e.sourceURL ) ) { - return; - } - - // for actual exceptions, this is useful - return e.sourceURL + ":" + e.line; - } -} - -function sourceFromStacktrace( offset ) { - var e = new Error(); - if ( !e.stack ) { - try { - throw e; - } catch ( err ) { - // This should already be true in most browsers - e = err; - } - } - return extractStacktrace( e, offset ); -} - -function synchronize( callback, last ) { - if ( QUnit.objectType( callback ) === "array" ) { - while ( callback.length ) { - synchronize( callback.shift() ); - } - return; - } - config.queue.push( callback ); - - if ( config.autorun && !config.blocking ) { - process( last ); - } -} - -function process( last ) { - function next() { - process( last ); - } - var start = now(); - config.depth = ( config.depth || 0 ) + 1; - - while ( config.queue.length && !config.blocking ) { - if ( !defined.setTimeout || config.updateRate <= 0 || - ( ( now() - start ) < config.updateRate ) ) { - if ( config.current ) { - - // Reset async tracking for each phase of the Test lifecycle - config.current.usedAsync = false; - } - config.queue.shift()(); - } else { - setTimeout( next, 13 ); - break; - } - } - config.depth--; - if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { - done(); - } -} - -function begin() { - var i, l, - modulesLog = []; - - // If the test run hasn't officially begun yet - if ( !config.started ) { - - // Record the time of the test run's beginning - config.started = now(); - - verifyLoggingCallbacks(); - - // Delete the loose unnamed module if unused. - if ( config.modules[ 0 ].name === "" && config.modules[ 0 ].tests.length === 0 ) { - config.modules.shift(); - } - - // Avoid unnecessary information by not logging modules' test environments - for ( i = 0, l = config.modules.length; i < l; i++ ) { - modulesLog.push({ - name: config.modules[ i ].name, - tests: config.modules[ i ].tests - }); - } - - // The test run is officially beginning now - runLoggingCallbacks( "begin", { - totalTests: Test.count, - modules: modulesLog - }); - } - - config.blocking = false; - process( true ); -} - -function resumeProcessing() { - runStarted = true; - - // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.) - if ( defined.setTimeout ) { - setTimeout(function() { - if ( config.current && config.current.semaphore > 0 ) { - return; - } - if ( config.timeout ) { - clearTimeout( config.timeout ); - } - - begin(); - }, 13 ); - } else { - begin(); - } -} - -function pauseProcessing() { - config.blocking = true; - - if ( config.testTimeout && defined.setTimeout ) { - clearTimeout( config.timeout ); - config.timeout = setTimeout(function() { - if ( config.current ) { - config.current.semaphore = 0; - QUnit.pushFailure( "Test timed out", sourceFromStacktrace( 2 ) ); - } else { - throw new Error( "Test timed out" ); - } - resumeProcessing(); - }, config.testTimeout ); - } -} - -function saveGlobal() { - config.pollution = []; - - if ( config.noglobals ) { - for ( var key in window ) { - if ( hasOwn.call( window, key ) ) { - // in Opera sometimes DOM element ids show up here, ignore them - if ( /^qunit-test-output/.test( key ) ) { - continue; - } - config.pollution.push( key ); - } - } - } -} - -function checkPollution() { - var newGlobals, - deletedGlobals, - old = config.pollution; - - saveGlobal(); - - newGlobals = diff( config.pollution, old ); - if ( newGlobals.length > 0 ) { - QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) ); - } - - deletedGlobals = diff( old, config.pollution ); - if ( deletedGlobals.length > 0 ) { - QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) ); - } -} - -// returns a new Array with the elements that are in a but not in b -function diff( a, b ) { - var i, j, - result = a.slice(); - - for ( i = 0; i < result.length; i++ ) { - for ( j = 0; j < b.length; j++ ) { - if ( result[ i ] === b[ j ] ) { - result.splice( i, 1 ); - i--; - break; - } - } - } - return result; -} - -function extend( a, b, undefOnly ) { - for ( var prop in b ) { - if ( hasOwn.call( b, prop ) ) { - - // Avoid "Member not found" error in IE8 caused by messing with window.constructor - if ( !( prop === "constructor" && a === window ) ) { - if ( b[ prop ] === undefined ) { - delete a[ prop ]; - } else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) { - a[ prop ] = b[ prop ]; - } - } - } - } - - return a; -} - -function runLoggingCallbacks( key, args ) { - var i, l, callbacks; - - callbacks = config.callbacks[ key ]; - for ( i = 0, l = callbacks.length; i < l; i++ ) { - callbacks[ i ]( args ); - } -} - -// DEPRECATED: This will be removed on 2.0.0+ -// This function verifies if the loggingCallbacks were modified by the user -// If so, it will restore it, assign the given callback and print a console warning -function verifyLoggingCallbacks() { - var loggingCallback, userCallback; - - for ( loggingCallback in loggingCallbacks ) { - if ( QUnit[ loggingCallback ] !== loggingCallbacks[ loggingCallback ] ) { - - userCallback = QUnit[ loggingCallback ]; - - // Restore the callback function - QUnit[ loggingCallback ] = loggingCallbacks[ loggingCallback ]; - - // Assign the deprecated given callback - QUnit[ loggingCallback ]( userCallback ); - - if ( window.console && window.console.warn ) { - window.console.warn( - "QUnit." + loggingCallback + " was replaced with a new value.\n" + - "Please, check out the documentation on how to apply logging callbacks.\n" + - "Reference: http://api.qunitjs.com/category/callbacks/" - ); - } - } - } -} - -// from jquery.js -function inArray( elem, array ) { - if ( array.indexOf ) { - return array.indexOf( elem ); - } - - for ( var i = 0, length = array.length; i < length; i++ ) { - if ( array[ i ] === elem ) { - return i; - } - } - - return -1; -} - -function Test( settings ) { - var i, l; - - ++Test.count; - - extend( this, settings ); - this.assertions = []; - this.semaphore = 0; - this.usedAsync = false; - this.module = config.currentModule; - this.stack = sourceFromStacktrace( 3 ); - - // Register unique strings - for ( i = 0, l = this.module.tests; i < l.length; i++ ) { - if ( this.module.tests[ i ].name === this.testName ) { - this.testName += " "; - } - } - - this.testId = generateHash( this.module.name, this.testName ); - - this.module.tests.push({ - name: this.testName, - testId: this.testId - }); - - if ( settings.skip ) { - - // Skipped tests will fully ignore any sent callback - this.callback = function() {}; - this.async = false; - this.expected = 0; - } else { - this.assert = new Assert( this ); - } -} - -Test.count = 0; - -Test.prototype = { - before: function() { - if ( - - // Emit moduleStart when we're switching from one module to another - this.module !== config.previousModule || - - // They could be equal (both undefined) but if the previousModule property doesn't - // yet exist it means this is the first test in a suite that isn't wrapped in a - // module, in which case we'll just emit a moduleStart event for 'undefined'. - // Without this, reporters can get testStart before moduleStart which is a problem. - !hasOwn.call( config, "previousModule" ) - ) { - if ( hasOwn.call( config, "previousModule" ) ) { - runLoggingCallbacks( "moduleDone", { - name: config.previousModule.name, - tests: config.previousModule.tests, - failed: config.moduleStats.bad, - passed: config.moduleStats.all - config.moduleStats.bad, - total: config.moduleStats.all, - runtime: now() - config.moduleStats.started - }); - } - config.previousModule = this.module; - config.moduleStats = { all: 0, bad: 0, started: now() }; - runLoggingCallbacks( "moduleStart", { - name: this.module.name, - tests: this.module.tests - }); - } - - config.current = this; - - this.testEnvironment = extend( {}, this.module.testEnvironment ); - delete this.testEnvironment.beforeEach; - delete this.testEnvironment.afterEach; - - this.started = now(); - runLoggingCallbacks( "testStart", { - name: this.testName, - module: this.module.name, - testId: this.testId - }); - - if ( !config.pollution ) { - saveGlobal(); - } - }, - - run: function() { - var promise; - - config.current = this; - - if ( this.async ) { - QUnit.stop(); - } - - this.callbackStarted = now(); - - if ( config.notrycatch ) { - promise = this.callback.call( this.testEnvironment, this.assert ); - this.resolvePromise( promise ); - return; - } - - try { - promise = this.callback.call( this.testEnvironment, this.assert ); - this.resolvePromise( promise ); - } catch ( e ) { - this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " + - this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) ); - - // else next test will carry the responsibility - saveGlobal(); - - // Restart the tests if they're blocking - if ( config.blocking ) { - QUnit.start(); - } - } - }, - - after: function() { - checkPollution(); - }, - - queueHook: function( hook, hookName ) { - var promise, - test = this; - return function runHook() { - config.current = test; - if ( config.notrycatch ) { - promise = hook.call( test.testEnvironment, test.assert ); - test.resolvePromise( promise, hookName ); - return; - } - try { - promise = hook.call( test.testEnvironment, test.assert ); - test.resolvePromise( promise, hookName ); - } catch ( error ) { - test.pushFailure( hookName + " failed on " + test.testName + ": " + - ( error.message || error ), extractStacktrace( error, 0 ) ); - } - }; - }, - - // Currently only used for module level hooks, can be used to add global level ones - hooks: function( handler ) { - var hooks = []; - - // Hooks are ignored on skipped tests - if ( this.skip ) { - return hooks; - } - - if ( this.module.testEnvironment && - QUnit.objectType( this.module.testEnvironment[ handler ] ) === "function" ) { - hooks.push( this.queueHook( this.module.testEnvironment[ handler ], handler ) ); - } - - return hooks; - }, - - finish: function() { - config.current = this; - if ( config.requireExpects && this.expected === null ) { - this.pushFailure( "Expected number of assertions to be defined, but expect() was " + - "not called.", this.stack ); - } else if ( this.expected !== null && this.expected !== this.assertions.length ) { - this.pushFailure( "Expected " + this.expected + " assertions, but " + - this.assertions.length + " were run", this.stack ); - } else if ( this.expected === null && !this.assertions.length ) { - this.pushFailure( "Expected at least one assertion, but none were run - call " + - "expect(0) to accept zero assertions.", this.stack ); - } - - var i, - bad = 0; - - this.runtime = now() - this.started; - config.stats.all += this.assertions.length; - config.moduleStats.all += this.assertions.length; - - for ( i = 0; i < this.assertions.length; i++ ) { - if ( !this.assertions[ i ].result ) { - bad++; - config.stats.bad++; - config.moduleStats.bad++; - } - } - - runLoggingCallbacks( "testDone", { - name: this.testName, - module: this.module.name, - skipped: !!this.skip, - failed: bad, - passed: this.assertions.length - bad, - total: this.assertions.length, - runtime: this.runtime, - - // HTML Reporter use - assertions: this.assertions, - testId: this.testId, - - // DEPRECATED: this property will be removed in 2.0.0, use runtime instead - duration: this.runtime - }); - - // QUnit.reset() is deprecated and will be replaced for a new - // fixture reset function on QUnit 2.0/2.1. - // It's still called here for backwards compatibility handling - QUnit.reset(); - - config.current = undefined; - }, - - queue: function() { - var bad, - test = this; - - if ( !this.valid() ) { - return; - } - - function run() { - - // each of these can by async - synchronize([ - function() { - test.before(); - }, - - test.hooks( "beforeEach" ), - - function() { - test.run(); - }, - - test.hooks( "afterEach" ).reverse(), - - function() { - test.after(); - }, - function() { - test.finish(); - } - ]); - } - - // `bad` initialized at top of scope - // defer when previous test run passed, if storage is available - bad = QUnit.config.reorder && defined.sessionStorage && - +sessionStorage.getItem( "qunit-test-" + this.module.name + "-" + this.testName ); - - if ( bad ) { - run(); - } else { - synchronize( run, true ); - } - }, - - push: function( result, actual, expected, message ) { - var source, - details = { - module: this.module.name, - name: this.testName, - result: result, - message: message, - actual: actual, - expected: expected, - testId: this.testId, - runtime: now() - this.started - }; - - if ( !result ) { - source = sourceFromStacktrace(); - - if ( source ) { - details.source = source; - } - } - - runLoggingCallbacks( "log", details ); - - this.assertions.push({ - result: !!result, - message: message - }); - }, - - pushFailure: function( message, source, actual ) { - if ( !this instanceof Test ) { - throw new Error( "pushFailure() assertion outside test context, was " + - sourceFromStacktrace( 2 ) ); - } - - var details = { - module: this.module.name, - name: this.testName, - result: false, - message: message || "error", - actual: actual || null, - testId: this.testId, - runtime: now() - this.started - }; - - if ( source ) { - details.source = source; - } - - runLoggingCallbacks( "log", details ); - - this.assertions.push({ - result: false, - message: message - }); - }, - - resolvePromise: function( promise, phase ) { - var then, message, - test = this; - if ( promise != null ) { - then = promise.then; - if ( QUnit.objectType( then ) === "function" ) { - QUnit.stop(); - then.call( - promise, - QUnit.start, - function( error ) { - message = "Promise rejected " + - ( !phase ? "during" : phase.replace( /Each$/, "" ) ) + - " " + test.testName + ": " + ( error.message || error ); - test.pushFailure( message, extractStacktrace( error, 0 ) ); - - // else next test will carry the responsibility - saveGlobal(); - - // Unblock - QUnit.start(); - } - ); - } - } - }, - - valid: function() { - var include, - filter = config.filter, - module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(), - fullName = ( this.module.name + ": " + this.testName ).toLowerCase(); - - // Internally-generated tests are always valid - if ( this.callback && this.callback.validTest ) { - return true; - } - - if ( config.testId.length > 0 && inArray( this.testId, config.testId ) < 0 ) { - return false; - } - - if ( module && ( !this.module.name || this.module.name.toLowerCase() !== module ) ) { - return false; - } - - if ( !filter ) { - return true; - } - - include = filter.charAt( 0 ) !== "!"; - if ( !include ) { - filter = filter.toLowerCase().slice( 1 ); - } - - // If the filter matches, we need to honour include - if ( fullName.indexOf( filter ) !== -1 ) { - return include; - } - - // Otherwise, do the opposite - return !include; - } - -}; - -// Resets the test setup. Useful for tests that modify the DOM. -/* -DEPRECATED: Use multiple tests instead of resetting inside a test. -Use testStart or testDone for custom cleanup. -This method will throw an error in 2.0, and will be removed in 2.1 -*/ -QUnit.reset = function() { - - // Return on non-browser environments - // This is necessary to not break on node tests - if ( typeof window === "undefined" ) { - return; - } - - var fixture = defined.document && document.getElementById && - document.getElementById( "qunit-fixture" ); - - if ( fixture ) { - fixture.innerHTML = config.fixture; - } -}; - -QUnit.pushFailure = function() { - if ( !QUnit.config.current ) { - throw new Error( "pushFailure() assertion outside test context, in " + - sourceFromStacktrace( 2 ) ); - } - - // Gets current test obj - var currentTest = QUnit.config.current; - - return currentTest.pushFailure.apply( currentTest, arguments ); -}; - -// Based on Java's String.hashCode, a simple but not -// rigorously collision resistant hashing function -function generateHash( module, testName ) { - var hex, - i = 0, - hash = 0, - str = module + "\x1C" + testName, - len = str.length; - - for ( ; i < len; i++ ) { - hash = ( ( hash << 5 ) - hash ) + str.charCodeAt( i ); - hash |= 0; - } - - // Convert the possibly negative integer hash code into an 8 character hex string, which isn't - // strictly necessary but increases user understanding that the id is a SHA-like hash - hex = ( 0x100000000 + hash ).toString( 16 ); - if ( hex.length < 8 ) { - hex = "0000000" + hex; - } - - return hex.slice( -8 ); -} - -function Assert( testContext ) { - this.test = testContext; -} - -// Assert helpers -QUnit.assert = Assert.prototype = { - - // Specify the number of expected assertions to guarantee that failed test - // (no assertions are run at all) don't slip through. - expect: function( asserts ) { - if ( arguments.length === 1 ) { - this.test.expected = asserts; - } else { - return this.test.expected; - } - }, - - // Increment this Test's semaphore counter, then return a single-use function that - // decrements that counter a maximum of once. - async: function() { - var test = this.test, - popped = false; - - test.semaphore += 1; - test.usedAsync = true; - pauseProcessing(); - - return function done() { - if ( !popped ) { - test.semaphore -= 1; - popped = true; - resumeProcessing(); - } else { - test.pushFailure( "Called the callback returned from `assert.async` more than once", - sourceFromStacktrace( 2 ) ); - } - }; - }, - - // Exports test.push() to the user API - push: function( /* result, actual, expected, message */ ) { - var assert = this, - currentTest = ( assert instanceof Assert && assert.test ) || QUnit.config.current; - - // Backwards compatibility fix. - // Allows the direct use of global exported assertions and QUnit.assert.* - // Although, it's use is not recommended as it can leak assertions - // to other tests from async tests, because we only get a reference to the current test, - // not exactly the test where assertion were intended to be called. - if ( !currentTest ) { - throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) ); - } - - if ( currentTest.usedAsync === true && currentTest.semaphore === 0 ) { - currentTest.pushFailure( "Assertion after the final `assert.async` was resolved", - sourceFromStacktrace( 2 ) ); - - // Allow this assertion to continue running anyway... - } - - if ( !( assert instanceof Assert ) ) { - assert = currentTest.assert; - } - return assert.test.push.apply( assert.test, arguments ); - }, - - /** - * Asserts rough true-ish result. - * @name ok - * @function - * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); - */ - ok: function( result, message ) { - message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " + - QUnit.dump.parse( result ) ); - this.push( !!result, result, true, message ); - }, - - /** - * Assert that the first two arguments are equal, with an optional message. - * Prints out both actual and expected values. - * @name equal - * @function - * @example equal( format( "{0} bytes.", 2), "2 bytes.", "replaces {0} with next argument" ); - */ - equal: function( actual, expected, message ) { - /*jshint eqeqeq:false */ - this.push( expected == actual, actual, expected, message ); - }, - - /** - * @name notEqual - * @function - */ - notEqual: function( actual, expected, message ) { - /*jshint eqeqeq:false */ - this.push( expected != actual, actual, expected, message ); - }, - - /** - * @name propEqual - * @function - */ - propEqual: function( actual, expected, message ) { - actual = objectValues( actual ); - expected = objectValues( expected ); - this.push( QUnit.equiv( actual, expected ), actual, expected, message ); - }, - - /** - * @name notPropEqual - * @function - */ - notPropEqual: function( actual, expected, message ) { - actual = objectValues( actual ); - expected = objectValues( expected ); - this.push( !QUnit.equiv( actual, expected ), actual, expected, message ); - }, - - /** - * @name deepEqual - * @function - */ - deepEqual: function( actual, expected, message ) { - this.push( QUnit.equiv( actual, expected ), actual, expected, message ); - }, - - /** - * @name notDeepEqual - * @function - */ - notDeepEqual: function( actual, expected, message ) { - this.push( !QUnit.equiv( actual, expected ), actual, expected, message ); - }, - - /** - * @name strictEqual - * @function - */ - strictEqual: function( actual, expected, message ) { - this.push( expected === actual, actual, expected, message ); - }, - - /** - * @name notStrictEqual - * @function - */ - notStrictEqual: function( actual, expected, message ) { - this.push( expected !== actual, actual, expected, message ); - }, - - "throws": function( block, expected, message ) { - var actual, expectedType, - expectedOutput = expected, - ok = false; - - // 'expected' is optional unless doing string comparison - if ( message == null && typeof expected === "string" ) { - message = expected; - expected = null; - } - - this.test.ignoreGlobalErrors = true; - try { - block.call( this.test.testEnvironment ); - } catch (e) { - actual = e; - } - this.test.ignoreGlobalErrors = false; - - if ( actual ) { - expectedType = QUnit.objectType( expected ); - - // we don't want to validate thrown error - if ( !expected ) { - ok = true; - expectedOutput = null; - - // expected is a regexp - } else if ( expectedType === "regexp" ) { - ok = expected.test( errorString( actual ) ); - - // expected is a string - } else if ( expectedType === "string" ) { - ok = expected === errorString( actual ); - - // expected is a constructor, maybe an Error constructor - } else if ( expectedType === "function" && actual instanceof expected ) { - ok = true; - - // expected is an Error object - } else if ( expectedType === "object" ) { - ok = actual instanceof expected.constructor && - actual.name === expected.name && - actual.message === expected.message; - - // expected is a validation function which returns true if validation passed - } else if ( expectedType === "function" && expected.call( {}, actual ) === true ) { - expectedOutput = null; - ok = true; - } - - this.push( ok, actual, expectedOutput, message ); - } else { - this.test.pushFailure( message, null, "No exception was thrown." ); - } - } -}; - -// Provide an alternative to assert.throws(), for enviroments that consider throws a reserved word -// Known to us are: Closure Compiler, Narwhal -(function() { - /*jshint sub:true */ - Assert.prototype.raises = Assert.prototype[ "throws" ]; -}()); - -// Test for equality any JavaScript type. -// Author: Philippe Rathé -QUnit.equiv = (function() { - - // Call the o related callback with the given arguments. - function bindCallbacks( o, callbacks, args ) { - var prop = QUnit.objectType( o ); - if ( prop ) { - if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) { - return callbacks[ prop ].apply( callbacks, args ); - } else { - return callbacks[ prop ]; // or undefined - } - } - } - - // the real equiv function - var innerEquiv, - - // stack to decide between skip/abort functions - callers = [], - - // stack to avoiding loops from circular referencing - parents = [], - parentsB = [], - - getProto = Object.getPrototypeOf || function( obj ) { - /* jshint camelcase: false, proto: true */ - return obj.__proto__; - }, - callbacks = (function() { - - // for string, boolean, number and null - function useStrictEquality( b, a ) { - - /*jshint eqeqeq:false */ - if ( b instanceof a.constructor || a instanceof b.constructor ) { - - // to catch short annotation VS 'new' annotation of a - // declaration - // e.g. var i = 1; - // var j = new Number(1); - return a == b; - } else { - return a === b; - } - } - - return { - "string": useStrictEquality, - "boolean": useStrictEquality, - "number": useStrictEquality, - "null": useStrictEquality, - "undefined": useStrictEquality, - - "nan": function( b ) { - return isNaN( b ); - }, - - "date": function( b, a ) { - return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf(); - }, - - "regexp": function( b, a ) { - return QUnit.objectType( b ) === "regexp" && - - // the regex itself - a.source === b.source && - - // and its modifiers - a.global === b.global && - - // (gmi) ... - a.ignoreCase === b.ignoreCase && - a.multiline === b.multiline && - a.sticky === b.sticky; - }, - - // - skip when the property is a method of an instance (OOP) - // - abort otherwise, - // initial === would have catch identical references anyway - "function": function() { - var caller = callers[ callers.length - 1 ]; - return caller !== Object && typeof caller !== "undefined"; - }, - - "array": function( b, a ) { - var i, j, len, loop, aCircular, bCircular; - - // b could be an object literal here - if ( QUnit.objectType( b ) !== "array" ) { - return false; - } - - len = a.length; - if ( len !== b.length ) { - // safe and faster - return false; - } - - // track reference to avoid circular references - parents.push( a ); - parentsB.push( b ); - for ( i = 0; i < len; i++ ) { - loop = false; - for ( j = 0; j < parents.length; j++ ) { - aCircular = parents[ j ] === a[ i ]; - bCircular = parentsB[ j ] === b[ i ]; - if ( aCircular || bCircular ) { - if ( a[ i ] === b[ i ] || aCircular && bCircular ) { - loop = true; - } else { - parents.pop(); - parentsB.pop(); - return false; - } - } - } - if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) { - parents.pop(); - parentsB.pop(); - return false; - } - } - parents.pop(); - parentsB.pop(); - return true; - }, - - "object": function( b, a ) { - - /*jshint forin:false */ - var i, j, loop, aCircular, bCircular, - // Default to true - eq = true, - aProperties = [], - bProperties = []; - - // comparing constructors is more strict than using - // instanceof - if ( a.constructor !== b.constructor ) { - - // Allow objects with no prototype to be equivalent to - // objects with Object as their constructor. - if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) || - ( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) { - return false; - } - } - - // stack constructor before traversing properties - callers.push( a.constructor ); - - // track reference to avoid circular references - parents.push( a ); - parentsB.push( b ); - - // be strict: don't ensure hasOwnProperty and go deep - for ( i in a ) { - loop = false; - for ( j = 0; j < parents.length; j++ ) { - aCircular = parents[ j ] === a[ i ]; - bCircular = parentsB[ j ] === b[ i ]; - if ( aCircular || bCircular ) { - if ( a[ i ] === b[ i ] || aCircular && bCircular ) { - loop = true; - } else { - eq = false; - break; - } - } - } - aProperties.push( i ); - if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) { - eq = false; - break; - } - } - - parents.pop(); - parentsB.pop(); - callers.pop(); // unstack, we are done - - for ( i in b ) { - bProperties.push( i ); // collect b's properties - } - - // Ensures identical properties name - return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); - } - }; - }()); - - innerEquiv = function() { // can take multiple arguments - var args = [].slice.apply( arguments ); - if ( args.length < 2 ) { - return true; // end transition - } - - return ( (function( a, b ) { - if ( a === b ) { - return true; // catch the most you can - } else if ( a === null || b === null || typeof a === "undefined" || - typeof b === "undefined" || - QUnit.objectType( a ) !== QUnit.objectType( b ) ) { - - // don't lose time with error prone cases - return false; - } else { - return bindCallbacks( a, callbacks, [ b, a ] ); - } - - // apply transition with (1..n) arguments - }( args[ 0 ], args[ 1 ] ) ) && - innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) ); - }; - - return innerEquiv; -}()); - -// Based on jsDump by Ariel Flesler -// http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html -QUnit.dump = (function() { - function quote( str ) { - return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\""; - } - function literal( o ) { - return o + ""; - } - function join( pre, arr, post ) { - var s = dump.separator(), - base = dump.indent(), - inner = dump.indent( 1 ); - if ( arr.join ) { - arr = arr.join( "," + s + inner ); - } - if ( !arr ) { - return pre + post; - } - return [ pre, inner + arr, base + post ].join( s ); - } - function array( arr, stack ) { - var i = arr.length, - ret = new Array( i ); - - if ( dump.maxDepth && dump.depth > dump.maxDepth ) { - return "[object Array]"; - } - - this.up(); - while ( i-- ) { - ret[ i ] = this.parse( arr[ i ], undefined, stack ); - } - this.down(); - return join( "[", ret, "]" ); - } - - var reName = /^function (\w+)/, - dump = { - - // objType is used mostly internally, you can fix a (custom) type in advance - parse: function( obj, objType, stack ) { - stack = stack || []; - var res, parser, parserType, - inStack = inArray( obj, stack ); - - if ( inStack !== -1 ) { - return "recursion(" + ( inStack - stack.length ) + ")"; - } - - objType = objType || this.typeOf( obj ); - parser = this.parsers[ objType ]; - parserType = typeof parser; - - if ( parserType === "function" ) { - stack.push( obj ); - res = parser.call( this, obj, stack ); - stack.pop(); - return res; - } - return ( parserType === "string" ) ? parser : this.parsers.error; - }, - typeOf: function( obj ) { - var type; - if ( obj === null ) { - type = "null"; - } else if ( typeof obj === "undefined" ) { - type = "undefined"; - } else if ( QUnit.is( "regexp", obj ) ) { - type = "regexp"; - } else if ( QUnit.is( "date", obj ) ) { - type = "date"; - } else if ( QUnit.is( "function", obj ) ) { - type = "function"; - } else if ( obj.setInterval !== undefined && - obj.document !== undefined && - obj.nodeType === undefined ) { - type = "window"; - } else if ( obj.nodeType === 9 ) { - type = "document"; - } else if ( obj.nodeType ) { - type = "node"; - } else if ( - - // native arrays - toString.call( obj ) === "[object Array]" || - - // NodeList objects - ( typeof obj.length === "number" && obj.item !== undefined && - ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null && - obj[ 0 ] === undefined ) ) ) - ) { - type = "array"; - } else if ( obj.constructor === Error.prototype.constructor ) { - type = "error"; - } else { - type = typeof obj; - } - return type; - }, - separator: function() { - return this.multiline ? this.HTML ? "
    " : "\n" : this.HTML ? " " : " "; - }, - // extra can be a number, shortcut for increasing-calling-decreasing - indent: function( extra ) { - if ( !this.multiline ) { - return ""; - } - var chr = this.indentChar; - if ( this.HTML ) { - chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); - } - return new Array( this.depth + ( extra || 0 ) ).join( chr ); - }, - up: function( a ) { - this.depth += a || 1; - }, - down: function( a ) { - this.depth -= a || 1; - }, - setParser: function( name, parser ) { - this.parsers[ name ] = parser; - }, - // The next 3 are exposed so you can use them - quote: quote, - literal: literal, - join: join, - // - depth: 1, - maxDepth: 5, - - // This is the list of parsers, to modify them, use dump.setParser - parsers: { - window: "[Window]", - document: "[Document]", - error: function( error ) { - return "Error(\"" + error.message + "\")"; - }, - unknown: "[Unknown]", - "null": "null", - "undefined": "undefined", - "function": function( fn ) { - var ret = "function", - - // functions never have name in IE - name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ]; - - if ( name ) { - ret += " " + name; - } - ret += "( "; - - ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" ); - return join( ret, dump.parse( fn, "functionCode" ), "}" ); - }, - array: array, - nodelist: array, - "arguments": array, - object: function( map, stack ) { - var keys, key, val, i, nonEnumerableProperties, - ret = []; - - if ( dump.maxDepth && dump.depth > dump.maxDepth ) { - return "[object Object]"; - } - - dump.up(); - keys = []; - for ( key in map ) { - keys.push( key ); - } - - // Some properties are not always enumerable on Error objects. - nonEnumerableProperties = [ "message", "name" ]; - for ( i in nonEnumerableProperties ) { - key = nonEnumerableProperties[ i ]; - if ( key in map && !( key in keys ) ) { - keys.push( key ); - } - } - keys.sort(); - for ( i = 0; i < keys.length; i++ ) { - key = keys[ i ]; - val = map[ key ]; - ret.push( dump.parse( key, "key" ) + ": " + - dump.parse( val, undefined, stack ) ); - } - dump.down(); - return join( "{", ret, "}" ); - }, - node: function( node ) { - var len, i, val, - open = dump.HTML ? "<" : "<", - close = dump.HTML ? ">" : ">", - tag = node.nodeName.toLowerCase(), - ret = open + tag, - attrs = node.attributes; - - if ( attrs ) { - for ( i = 0, len = attrs.length; i < len; i++ ) { - val = attrs[ i ].nodeValue; - - // IE6 includes all attributes in .attributes, even ones not explicitly - // set. Those have values like undefined, null, 0, false, "" or - // "inherit". - if ( val && val !== "inherit" ) { - ret += " " + attrs[ i ].nodeName + "=" + - dump.parse( val, "attribute" ); - } - } - } - ret += close; - - // Show content of TextNode or CDATASection - if ( node.nodeType === 3 || node.nodeType === 4 ) { - ret += node.nodeValue; - } - - return ret + open + "/" + tag + close; - }, - - // function calls it internally, it's the arguments part of the function - functionArgs: function( fn ) { - var args, - l = fn.length; - - if ( !l ) { - return ""; - } - - args = new Array( l ); - while ( l-- ) { - - // 97 is 'a' - args[ l ] = String.fromCharCode( 97 + l ); - } - return " " + args.join( ", " ) + " "; - }, - // object calls it internally, the key part of an item in a map - key: quote, - // function calls it internally, it's the content of the function - functionCode: "[code]", - // node calls it internally, it's an html attribute value - attribute: quote, - string: quote, - date: quote, - regexp: literal, - number: literal, - "boolean": literal - }, - // if true, entities are escaped ( <, >, \t, space and \n ) - HTML: false, - // indentation unit - indentChar: " ", - // if true, items in a collection, are separated by a \n, else just a space. - multiline: true - }; - - return dump; -}()); - -// back compat -QUnit.jsDump = QUnit.dump; - -// For browser, export only select globals -if ( typeof window !== "undefined" ) { - - // Deprecated - // Extend assert methods to QUnit and Global scope through Backwards compatibility - (function() { - var i, - assertions = Assert.prototype; - - function applyCurrent( current ) { - return function() { - var assert = new Assert( QUnit.config.current ); - current.apply( assert, arguments ); - }; - } - - for ( i in assertions ) { - QUnit[ i ] = applyCurrent( assertions[ i ] ); - } - })(); - - (function() { - var i, l, - keys = [ - "test", - "module", - "expect", - "asyncTest", - "start", - "stop", - "ok", - "equal", - "notEqual", - "propEqual", - "notPropEqual", - "deepEqual", - "notDeepEqual", - "strictEqual", - "notStrictEqual", - "throws" - ]; - - for ( i = 0, l = keys.length; i < l; i++ ) { - window[ keys[ i ] ] = QUnit[ keys[ i ] ]; - } - })(); - - window.QUnit = QUnit; -} - -// For nodejs -if ( typeof module !== "undefined" && module && module.exports ) { - module.exports = QUnit; - - // For consistency with CommonJS environments' exports - module.exports.QUnit = QUnit; -} - -// For CommonJS with exports, but without module.exports, like Rhino -if ( typeof exports !== "undefined" && exports ) { - exports.QUnit = QUnit; -} - -// Get a reference to the global object, like window in browsers -}( (function() { - return this; -})() )); - -/*istanbul ignore next */ -// jscs:disable maximumLineLength -/* - * Javascript Diff Algorithm - * By John Resig (http://ejohn.org/) - * Modified by Chu Alan "sprite" - * - * Released under the MIT license. - * - * More Info: - * http://ejohn.org/projects/javascript-diff-algorithm/ - * - * Usage: QUnit.diff(expected, actual) - * - * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick brown fox jumped jumps over" - */ -QUnit.diff = (function() { - var hasOwn = Object.prototype.hasOwnProperty; - - /*jshint eqeqeq:false, eqnull:true */ - function diff( o, n ) { - var i, - ns = {}, - os = {}; - - for ( i = 0; i < n.length; i++ ) { - if ( !hasOwn.call( ns, n[ i ] ) ) { - ns[ n[ i ] ] = { - rows: [], - o: null - }; - } - ns[ n[ i ] ].rows.push( i ); - } - - for ( i = 0; i < o.length; i++ ) { - if ( !hasOwn.call( os, o[ i ] ) ) { - os[ o[ i ] ] = { - rows: [], - n: null - }; - } - os[ o[ i ] ].rows.push( i ); - } - - for ( i in ns ) { - if ( hasOwn.call( ns, i ) ) { - if ( ns[ i ].rows.length === 1 && hasOwn.call( os, i ) && os[ i ].rows.length === 1 ) { - n[ ns[ i ].rows[ 0 ] ] = { - text: n[ ns[ i ].rows[ 0 ] ], - row: os[ i ].rows[ 0 ] - }; - o[ os[ i ].rows[ 0 ] ] = { - text: o[ os[ i ].rows[ 0 ] ], - row: ns[ i ].rows[ 0 ] - }; - } - } - } - - for ( i = 0; i < n.length - 1; i++ ) { - if ( n[ i ].text != null && n[ i + 1 ].text == null && n[ i ].row + 1 < o.length && o[ n[ i ].row + 1 ].text == null && - n[ i + 1 ] == o[ n[ i ].row + 1 ] ) { - - n[ i + 1 ] = { - text: n[ i + 1 ], - row: n[ i ].row + 1 - }; - o[ n[ i ].row + 1 ] = { - text: o[ n[ i ].row + 1 ], - row: i + 1 - }; - } - } - - for ( i = n.length - 1; i > 0; i-- ) { - if ( n[ i ].text != null && n[ i - 1 ].text == null && n[ i ].row > 0 && o[ n[ i ].row - 1 ].text == null && - n[ i - 1 ] == o[ n[ i ].row - 1 ] ) { - - n[ i - 1 ] = { - text: n[ i - 1 ], - row: n[ i ].row - 1 - }; - o[ n[ i ].row - 1 ] = { - text: o[ n[ i ].row - 1 ], - row: i - 1 - }; - } - } - - return { - o: o, - n: n - }; - } - - return function( o, n ) { - o = o.replace( /\s+$/, "" ); - n = n.replace( /\s+$/, "" ); - - var i, pre, - str = "", - out = diff( o === "" ? [] : o.split( /\s+/ ), n === "" ? [] : n.split( /\s+/ ) ), - oSpace = o.match( /\s+/g ), - nSpace = n.match( /\s+/g ); - - if ( oSpace == null ) { - oSpace = [ " " ]; - } else { - oSpace.push( " " ); - } - - if ( nSpace == null ) { - nSpace = [ " " ]; - } else { - nSpace.push( " " ); - } - - if ( out.n.length === 0 ) { - for ( i = 0; i < out.o.length; i++ ) { - str += "" + out.o[ i ] + oSpace[ i ] + ""; - } - } else { - if ( out.n[ 0 ].text == null ) { - for ( n = 0; n < out.o.length && out.o[ n ].text == null; n++ ) { - str += "" + out.o[ n ] + oSpace[ n ] + ""; - } - } - - for ( i = 0; i < out.n.length; i++ ) { - if ( out.n[ i ].text == null ) { - str += "" + out.n[ i ] + nSpace[ i ] + ""; - } else { - - // `pre` initialized at top of scope - pre = ""; - - for ( n = out.n[ i ].row + 1; n < out.o.length && out.o[ n ].text == null; n++ ) { - pre += "" + out.o[ n ] + oSpace[ n ] + ""; - } - str += " " + out.n[ i ].text + nSpace[ i ] + pre; - } - } - } - - return str; - }; -}()); -// jscs:enable - -(function() { - -// Deprecated QUnit.init - Ref #530 -// Re-initialize the configuration options -QUnit.init = function() { - var tests, banner, result, qunit, - config = QUnit.config; - - config.stats = { all: 0, bad: 0 }; - config.moduleStats = { all: 0, bad: 0 }; - config.started = 0; - config.updateRate = 1000; - config.blocking = false; - config.autostart = true; - config.autorun = false; - config.filter = ""; - config.queue = []; - - // Return on non-browser environments - // This is necessary to not break on node tests - if ( typeof window === "undefined" ) { - return; - } - - qunit = id( "qunit" ); - if ( qunit ) { - qunit.innerHTML = - "

    " + escapeText( document.title ) + "

    " + - "

    " + - "
    " + - "

    " + - "
      "; - } - - tests = id( "qunit-tests" ); - banner = id( "qunit-banner" ); - result = id( "qunit-testresult" ); - - if ( tests ) { - tests.innerHTML = ""; - } - - if ( banner ) { - banner.className = ""; - } - - if ( result ) { - result.parentNode.removeChild( result ); - } - - if ( tests ) { - result = document.createElement( "p" ); - result.id = "qunit-testresult"; - result.className = "result"; - tests.parentNode.insertBefore( result, tests ); - result.innerHTML = "Running...
       "; - } -}; - -// Don't load the HTML Reporter on non-Browser environments -if ( typeof window === "undefined" ) { - return; -} - -var config = QUnit.config, - hasOwn = Object.prototype.hasOwnProperty, - defined = { - document: window.document !== undefined, - sessionStorage: (function() { - var x = "qunit-test-string"; - try { - sessionStorage.setItem( x, x ); - sessionStorage.removeItem( x ); - return true; - } catch ( e ) { - return false; - } - }()) - }, - modulesList = []; - -/** -* Escape text for attribute or text content. -*/ -function escapeText( s ) { - if ( !s ) { - return ""; - } - s = s + ""; - - // Both single quotes and double quotes (for attributes) - return s.replace( /['"<>&]/g, function( s ) { - switch ( s ) { - case "'": - return "'"; - case "\"": - return """; - case "<": - return "<"; - case ">": - return ">"; - case "&": - return "&"; - } - }); -} - -/** - * @param {HTMLElement} elem - * @param {string} type - * @param {Function} fn - */ -function addEvent( elem, type, fn ) { - if ( elem.addEventListener ) { - - // Standards-based browsers - elem.addEventListener( type, fn, false ); - } else if ( elem.attachEvent ) { - - // support: IE <9 - elem.attachEvent( "on" + type, fn ); - } -} - -/** - * @param {Array|NodeList} elems - * @param {string} type - * @param {Function} fn - */ -function addEvents( elems, type, fn ) { - var i = elems.length; - while ( i-- ) { - addEvent( elems[ i ], type, fn ); - } -} - -function hasClass( elem, name ) { - return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0; -} - -function addClass( elem, name ) { - if ( !hasClass( elem, name ) ) { - elem.className += ( elem.className ? " " : "" ) + name; - } -} - -function toggleClass( elem, name ) { - if ( hasClass( elem, name ) ) { - removeClass( elem, name ); - } else { - addClass( elem, name ); - } -} - -function removeClass( elem, name ) { - var set = " " + elem.className + " "; - - // Class name may appear multiple times - while ( set.indexOf( " " + name + " " ) >= 0 ) { - set = set.replace( " " + name + " ", " " ); - } - - // trim for prettiness - elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" ); -} - -function id( name ) { - return defined.document && document.getElementById && document.getElementById( name ); -} - -function getUrlConfigHtml() { - var i, j, val, - escaped, escapedTooltip, - selection = false, - len = config.urlConfig.length, - urlConfigHtml = ""; - - for ( i = 0; i < len; i++ ) { - val = config.urlConfig[ i ]; - if ( typeof val === "string" ) { - val = { - id: val, - label: val - }; - } - - escaped = escapeText( val.id ); - escapedTooltip = escapeText( val.tooltip ); - - if ( config[ val.id ] === undefined ) { - config[ val.id ] = QUnit.urlParams[ val.id ]; - } - - if ( !val.value || typeof val.value === "string" ) { - urlConfigHtml += ""; - } else { - urlConfigHtml += ""; - } - } - - return urlConfigHtml; -} - -// Handle "click" events on toolbar checkboxes and "change" for select menus. -// Updates the URL with the new state of `config.urlConfig` values. -function toolbarChanged() { - var updatedUrl, value, - field = this, - params = {}; - - // Detect if field is a select menu or a checkbox - if ( "selectedIndex" in field ) { - value = field.options[ field.selectedIndex ].value || undefined; - } else { - value = field.checked ? ( field.defaultValue || true ) : undefined; - } - - params[ field.name ] = value; - updatedUrl = setUrl( params ); - - if ( "hidepassed" === field.name && "replaceState" in window.history ) { - config[ field.name ] = value || false; - if ( value ) { - addClass( id( "qunit-tests" ), "hidepass" ); - } else { - removeClass( id( "qunit-tests" ), "hidepass" ); - } - - // It is not necessary to refresh the whole page - window.history.replaceState( null, "", updatedUrl ); - } else { - window.location = updatedUrl; - } -} - -function setUrl( params ) { - var key, - querystring = "?"; - - params = QUnit.extend( QUnit.extend( {}, QUnit.urlParams ), params ); - - for ( key in params ) { - if ( hasOwn.call( params, key ) ) { - if ( params[ key ] === undefined ) { - continue; - } - querystring += encodeURIComponent( key ); - if ( params[ key ] !== true ) { - querystring += "=" + encodeURIComponent( params[ key ] ); - } - querystring += "&"; - } - } - return location.protocol + "//" + location.host + - location.pathname + querystring.slice( 0, -1 ); -} - -function applyUrlParams() { - var selectBox = id( "qunit-modulefilter" ), - selection = decodeURIComponent( selectBox.options[ selectBox.selectedIndex ].value ), - filter = id( "qunit-filter-input" ).value; - - window.location = setUrl({ - module: ( selection === "" ) ? undefined : selection, - filter: ( filter === "" ) ? undefined : filter, - - // Remove testId filter - testId: undefined - }); -} - -function toolbarUrlConfigContainer() { - var urlConfigContainer = document.createElement( "span" ); - - urlConfigContainer.innerHTML = getUrlConfigHtml(); - addClass( urlConfigContainer, "qunit-url-config" ); - - // For oldIE support: - // * Add handlers to the individual elements instead of the container - // * Use "click" instead of "change" for checkboxes - addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", toolbarChanged ); - addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", toolbarChanged ); - - return urlConfigContainer; -} - -function toolbarLooseFilter() { - var filter = document.createElement( "form" ), - label = document.createElement( "label" ), - input = document.createElement( "input" ), - button = document.createElement( "button" ); - - addClass( filter, "qunit-filter" ); - - label.innerHTML = "Filter: "; - - input.type = "text"; - input.value = config.filter || ""; - input.name = "filter"; - input.id = "qunit-filter-input"; - - button.innerHTML = "Go"; - - label.appendChild( input ); - - filter.appendChild( label ); - filter.appendChild( button ); - addEvent( filter, "submit", function( ev ) { - applyUrlParams(); - - if ( ev && ev.preventDefault ) { - ev.preventDefault(); - } - - return false; - }); - - return filter; -} - -function toolbarModuleFilterHtml() { - var i, - moduleFilterHtml = ""; - - if ( !modulesList.length ) { - return false; - } - - modulesList.sort(function( a, b ) { - return a.localeCompare( b ); - }); - - moduleFilterHtml += "" + - ""; - - return moduleFilterHtml; -} - -function toolbarModuleFilter() { - var toolbar = id( "qunit-testrunner-toolbar" ), - moduleFilter = document.createElement( "span" ), - moduleFilterHtml = toolbarModuleFilterHtml(); - - if ( !toolbar || !moduleFilterHtml ) { - return false; - } - - moduleFilter.setAttribute( "id", "qunit-modulefilter-container" ); - moduleFilter.innerHTML = moduleFilterHtml; - - addEvent( moduleFilter.lastChild, "change", applyUrlParams ); - - toolbar.appendChild( moduleFilter ); -} - -function appendToolbar() { - var toolbar = id( "qunit-testrunner-toolbar" ); - - if ( toolbar ) { - toolbar.appendChild( toolbarUrlConfigContainer() ); - toolbar.appendChild( toolbarLooseFilter() ); - } -} - -function appendHeader() { - var header = id( "qunit-header" ); - - if ( header ) { - header.innerHTML = "" + header.innerHTML + " "; - } -} - -function appendBanner() { - var banner = id( "qunit-banner" ); - - if ( banner ) { - banner.className = ""; - } -} - -function appendTestResults() { - var tests = id( "qunit-tests" ), - result = id( "qunit-testresult" ); - - if ( result ) { - result.parentNode.removeChild( result ); - } - - if ( tests ) { - tests.innerHTML = ""; - result = document.createElement( "p" ); - result.id = "qunit-testresult"; - result.className = "result"; - tests.parentNode.insertBefore( result, tests ); - result.innerHTML = "Running...
       "; - } -} - -function storeFixture() { - var fixture = id( "qunit-fixture" ); - if ( fixture ) { - config.fixture = fixture.innerHTML; - } -} - -function appendUserAgent() { - var userAgent = id( "qunit-userAgent" ); - if ( userAgent ) { - userAgent.innerHTML = ""; - userAgent.appendChild( document.createTextNode( navigator.userAgent ) ); - } -} - -function appendTestsList( modules ) { - var i, l, x, z, test, moduleObj; - - for ( i = 0, l = modules.length; i < l; i++ ) { - moduleObj = modules[ i ]; - - if ( moduleObj.name ) { - modulesList.push( moduleObj.name ); - } - - for ( x = 0, z = moduleObj.tests.length; x < z; x++ ) { - test = moduleObj.tests[ x ]; - - appendTest( test.name, test.testId, moduleObj.name ); - } - } -} - -function appendTest( name, testId, moduleName ) { - var title, rerunTrigger, testBlock, assertList, - tests = id( "qunit-tests" ); - - if ( !tests ) { - return; - } - - title = document.createElement( "strong" ); - title.innerHTML = getNameHtml( name, moduleName ); - - rerunTrigger = document.createElement( "a" ); - rerunTrigger.innerHTML = "Rerun"; - rerunTrigger.href = setUrl({ testId: testId }); - - testBlock = document.createElement( "li" ); - testBlock.appendChild( title ); - testBlock.appendChild( rerunTrigger ); - testBlock.id = "qunit-test-output-" + testId; - - assertList = document.createElement( "ol" ); - assertList.className = "qunit-assert-list"; - - testBlock.appendChild( assertList ); - - tests.appendChild( testBlock ); -} - -// HTML Reporter initialization and load -QUnit.begin(function( details ) { - var qunit = id( "qunit" ); - - // Fixture is the only one necessary to run without the #qunit element - storeFixture(); - - if ( qunit ) { - qunit.innerHTML = - "

      " + escapeText( document.title ) + "

      " + - "

      " + - "
      " + - "

      " + - "
        "; - } - - appendHeader(); - appendBanner(); - appendTestResults(); - appendUserAgent(); - appendToolbar(); - appendTestsList( details.modules ); - toolbarModuleFilter(); - - if ( qunit && config.hidepassed ) { - addClass( qunit.lastChild, "hidepass" ); - } -}); - -QUnit.done(function( details ) { - var i, key, - banner = id( "qunit-banner" ), - tests = id( "qunit-tests" ), - html = [ - "Tests completed in ", - details.runtime, - " milliseconds.
        ", - "", - details.passed, - " assertions of ", - details.total, - " passed, ", - details.failed, - " failed." - ].join( "" ); - - if ( banner ) { - banner.className = details.failed ? "qunit-fail" : "qunit-pass"; - } - - if ( tests ) { - id( "qunit-testresult" ).innerHTML = html; - } - - if ( config.altertitle && defined.document && document.title ) { - - // show ✖ for good, ✔ for bad suite result in title - // use escape sequences in case file gets loaded with non-utf-8-charset - document.title = [ - ( details.failed ? "\u2716" : "\u2714" ), - document.title.replace( /^[\u2714\u2716] /i, "" ) - ].join( " " ); - } - - // clear own sessionStorage items if all tests passed - if ( config.reorder && defined.sessionStorage && details.failed === 0 ) { - for ( i = 0; i < sessionStorage.length; i++ ) { - key = sessionStorage.key( i++ ); - if ( key.indexOf( "qunit-test-" ) === 0 ) { - sessionStorage.removeItem( key ); - } - } - } - - // scroll back to top to show results - if ( config.scrolltop && window.scrollTo ) { - window.scrollTo( 0, 0 ); - } -}); - -function getNameHtml( name, module ) { - var nameHtml = ""; - - if ( module ) { - nameHtml = "" + escapeText( module ) + ": "; - } - - nameHtml += "" + escapeText( name ) + ""; - - return nameHtml; -} - -QUnit.testStart(function( details ) { - var running, testBlock; - - testBlock = id( "qunit-test-output-" + details.testId ); - if ( testBlock ) { - testBlock.className = "running"; - } else { - - // Report later registered tests - appendTest( details.name, details.testId, details.module ); - } - - running = id( "qunit-testresult" ); - if ( running ) { - running.innerHTML = "Running:
        " + getNameHtml( details.name, details.module ); - } - -}); - -QUnit.log(function( details ) { - var assertList, assertLi, - message, expected, actual, - testItem = id( "qunit-test-output-" + details.testId ); - - if ( !testItem ) { - return; - } - - message = escapeText( details.message ) || ( details.result ? "okay" : "failed" ); - message = "" + message + ""; - message += "@ " + details.runtime + " ms"; - - // pushFailure doesn't provide details.expected - // when it calls, it's implicit to also not show expected and diff stuff - // Also, we need to check details.expected existence, as it can exist and be undefined - if ( !details.result && hasOwn.call( details, "expected" ) ) { - expected = escapeText( QUnit.dump.parse( details.expected ) ); - actual = escapeText( QUnit.dump.parse( details.actual ) ); - message += ""; - - if ( actual !== expected ) { - message += "" + - ""; - } - - if ( details.source ) { - message += ""; - } - - message += "
        Expected:
        " +
        -			expected +
        -			"
        Result:
        " +
        -				actual + "
        Diff:
        " +
        -				QUnit.diff( expected, actual ) + "
        Source:
        " +
        -				escapeText( details.source ) + "
        "; - - // this occours when pushFailure is set and we have an extracted stack trace - } else if ( !details.result && details.source ) { - message += "" + - "" + - "
        Source:
        " +
        -			escapeText( details.source ) + "
        "; - } - - assertList = testItem.getElementsByTagName( "ol" )[ 0 ]; - - assertLi = document.createElement( "li" ); - assertLi.className = details.result ? "pass" : "fail"; - assertLi.innerHTML = message; - assertList.appendChild( assertLi ); -}); - -QUnit.testDone(function( details ) { - var testTitle, time, testItem, assertList, - good, bad, testCounts, skipped, - tests = id( "qunit-tests" ); - - if ( !tests ) { - return; - } - - testItem = id( "qunit-test-output-" + details.testId ); - - assertList = testItem.getElementsByTagName( "ol" )[ 0 ]; - - good = details.passed; - bad = details.failed; - - // store result when possible - if ( config.reorder && defined.sessionStorage ) { - if ( bad ) { - sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad ); - } else { - sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name ); - } - } - - if ( bad === 0 ) { - addClass( assertList, "qunit-collapsed" ); - } - - // testItem.firstChild is the test name - testTitle = testItem.firstChild; - - testCounts = bad ? - "" + bad + ", " + "" + good + ", " : - ""; - - testTitle.innerHTML += " (" + testCounts + - details.assertions.length + ")"; - - if ( details.skipped ) { - testItem.className = "skipped"; - skipped = document.createElement( "em" ); - skipped.className = "qunit-skipped-label"; - skipped.innerHTML = "skipped"; - testItem.insertBefore( skipped, testTitle ); - } else { - addEvent( testTitle, "click", function() { - toggleClass( assertList, "qunit-collapsed" ); - }); - - testItem.className = bad ? "fail" : "pass"; - - time = document.createElement( "span" ); - time.className = "runtime"; - time.innerHTML = details.runtime + " ms"; - testItem.insertBefore( time, assertList ); - } -}); - -if ( !defined.document || document.readyState === "complete" ) { - config.pageLoaded = true; - config.autorun = true; -} - -if ( defined.document ) { - addEvent( window, "load", QUnit.load ); -} - -})(); diff --git a/labs/12 Unit Testing/simple_tests.html b/labs/12 Unit Testing/simple_tests.html deleted file mode 100644 index 481929d..0000000 --- a/labs/12 Unit Testing/simple_tests.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - Unit Testing with QUnit - - - -
        -
        - - - - - - diff --git a/labs/12 Unit Testing/worksheet.md b/labs/12 Unit Testing/worksheet.md deleted file mode 100644 index a5cb052..0000000 --- a/labs/12 Unit Testing/worksheet.md +++ /dev/null @@ -1,366 +0,0 @@ -# Unit Testing with Jasmine - -In this worksheet you will be introduced to the concept of Unit Testing. You will install the Jasmine Behaviour-Driven Development framework, configure a test runner and build tests. - -## 1 Installing Jasmine - -To get a link to the current Jasmine release, visit the download page https://github.com/jasmine/jasmine/releases and click on the ‘Latest release’ button then right-click on the zip button and copy the URL to the clipboard. - -Download using `wget` and `unzip` on your Codio server then delete the *original zip file*. Finally delete the contents of two sub-directories `spec` and `src`. - -**NB: ensure you click through on GitHub to view the correct URL for the raw zip file (the `view raw` link. Do not use the link to the zip file appearing in the `jasmine/dist` directory in the repo.** -``` -wget https://github.com/jasmine/jasmine/.../jasmine-standalone-2.1.3.zip -unzip jasmine-standalone-2.1.3.zip -rm jasmine-standalone-2.1.3.zip -rm -rf spec/* -rm -rf src/* -``` - -The src directory is where we will save our JavaScript modules. The spec directory is where we will save our unit tests. -``` -. -├── lib -│ └── jasmine-2.1.3 -├── MIT.LICENSE -├── spec -├── SpecRunner.html -└── src -``` - -## 2 Adding The Files - -Lets add a module file and its corresponding test. Create a file called `notes.js` in the src directory. This will contain our module code. Next create a file called notes.spec.js in the spec directory. This will contain the unit tests. Note the naming convention, the unit test file is the same as the module file with .spec added before the file extension. - -## 2.1 Editing the Spec Runner - -We need to tell Jasmine about our two files. This is done in the file SpecRunner.html in the source file and spec file sections (these are clearly labelled). You will also need to delete the existing entries since we have deleted these files. -``` - - - - - -``` - -Finally load up `SpecRunner.html` in the browser inside codio. You should get a web page indicating that no specs have been found (that’s OK because we have not written any). - -## 3 Defining Our Tests - -Before writing tests take a moment to understand the purpose of the module we will be writing. Specifically we should write down the required functionality and, for each piece of functionality, how we can test for compliance. - -We will be building a module called notes which will store a series of notes together with a unix timestamp indicating when they were added. It will expose the following six public functions. - -**add(note)** - -adds a new note to the module, parameter is the text of the note. if an empty string is passed (or no parameter), nothing should be added. If nothing is added the function should return false, otherwise it returns true. - -**remove(index)** - -deletes the note at the specified index. returns true on success, if the index is missing or is out of bounds or a string it returns false. - -**count()** - -returns the number of notes stored in the module. - -**list()** - -returns an array containing all notes. Each note is an object that contains both the note and the timestamp. - -**find(string)** - -returns an array containing all notes that contain the specified string. Each note is an object that contains both the note and the timestamp. If no matches are found it returns false. Search should not be case sensitive. - -**clear()** - -removes all the notes. - -That all seems pretty clear, so now we can define the steps we need to take to test each of these. Before we carry out each of our tests we need to do a little setup. In our case we should clear out the list and add some new notes. - -#### Before Each Test -- clear the list -- add note 1 (first note) -- add note 2 (second note) -- add note 3 (third note) -- add note 4 (fourth note) -- add note 5 (fifth note) - -#### Add - -- should be able to add a new note -- check that this returns true -- check that count returns 6 -- should ignore blank notes -- check that false is returned -- check that count() returns 5 -- should ignore notes containing only whitespace -- check that false is returned -- check that count() returns 5 -- should require a string parameter -- check that false is returned -- check that count() returns 5 - -#### Remove - -- remove first index -- remove an index out of range -- don’t pass a parameter to remove - -#### Find - -- search for ‘note’ -- search for ‘Note’ -- search for ‘th’ -- search for ‘four’ -- search for ‘six’ -- search for a blank string -- search without passing a parameter - -## 4 Creating a Test Suite - -Now lets start writing our tests. These live in test suites. Normally there is one suite per module you are testing. Each test suite needs to be given a name to describe the module it is testing. -``` -describe('notes module', function () { - -}); -``` - -Now if you refresh the spec runner web page you will see it has detected the test suite and has displayed its name: - -### 4.1 Adding Pending Specs - -The next step is to add all the specs (tests) to the test suite. This will serve as a todo list when we start writing the module. Specs are defined with the global function it which takes a string and an optional function. The string is the title and the function is the spec or test. If we leave out the function the spec is flagged as pending in the test harness. -``` -describe('notes module', function () { - it('should be able to add a new note'); - it('should ignore blank notes'); - it('should ignore notes containing only whitespace'); - it('should require a string parameter'); -}); -``` - -If we run the test harness we can see that the four specs are flagged as pending. Also notice the four asterix characters running along the top of the green bar. This indicated that there are four specs we are not currently running. - -### 4.2 Setup & Teardown - -Each test suite is self-contained and its tests need to operate on a known state to correctly test the module. To help ensure this you can run a block of code before the suite runs any specs and a block of code after all the specs in the suite have completed. You can also run a block of code before each spec and a block of code after each spec. These are provided as four global Jasmine functions which take an anonymous function containing the code to run. - -- beforeAll() -- afterAll() -- beforeEach() -- afterEach() - -In our suite we plan to empty out any notes and pre-populate the module with five notes before we run each spec. -``` -describe('notes module', function () { - beforeEach(function() { - notes.clear(); - notes.add('first note'); - notes.add('second note'); - notes.add('third note'); - notes.add('fourth note'); - notes.add('fifth note'); - }); - it('should be able to add a new note'); - it('should ignore blank notes'); - it('should ignore notes containing only whitespace'); - it('should require a string parameter'); -}); -``` - -From this point onward the entire suite code will be omitted from the examples in this worksheet to save space. We will be working on individual specs. - -### 4.3 Writing the First Spec - -Each one of our tests will become a separate spec. The first one is testing to see if we can add a new note. We can test this by seeing if the count function returns ‘6’. function toBe is an example of a Jasmine matcher. There are lots of others, a full list can be found in the Jasmine documentation. -``` -it("should be able to add a new note", function () { - expect(notes.add('sixth note')).toBe(true); - expect(notes.count()).toBe(6); -}); -``` - -If we refresh the test runner we should see the following. Note that the first asterix is now a red cross. This means the spec is running but has failed. There are two views available, Spec List and Failures. We are looking at the failures view and the first of the two errors indicates the test got a value of undefined but was expecting the result to be true. - -We are getting a failed test! If you think about it this is because we have not written any code in our module. We should therefore start by creating a module and stubbing the public functions. In this example we are writing our module as an immediately invoked function expression (IIFE) that return an object called notes. -``` -var notes = (function() { - return { - add: function(note) {}, - remove: function(index) {}, - count: function() {}, - list: function() {}, - find: function(str) {}, - clear: function() {}; - } -}()); -``` - -### 4.4 Passing the Spec -We are now ready to start writing our module code. Lets look at the test runner. - -This tells us that the value returned is undefined but the spec was expecting ‘6’. To pass this test we need to implement both the add and count functions. Again, here is a first attempt. -``` -var notes = (function() { - var list = []; - - return { - add: function(note) { - var item = {timestamp: Date.now(), text: note}; - list.push(item); - return true; - }, - remove: function(index) {}, - count: function() { - return list.length; - }, - list: function() {}, - find: function(str) {}, - clear: function() {}; - } -}()); -``` - -If we run our test runner it shows that the code we wrote was enough to pass the spec. - -### 4.5 Passing the Second Spec - -In this one we attempt to add an empty string. We expect the add function to return false. We also expect the notes count to be ‘5’ because the note was not added. -``` -it("should ignore blank notes", function () { - expect(notes.add('')).toBe(false); - expect(notes.count()).toBe(5); -}); -``` - -If we refresh the test runner we find that both tests in the spec fail. -``` -Expected true to be false. -Expected 12 to be 5. -``` - -Lets try and fix the second one first. Somehow there were 12 notes instead of the 5 we expected. This looks as if the notes are not being removed between specs. The culprit is the clear() function which has not been implemented. Here is a minimal implementation. -``` -clear: function() { -list.splice(0, list.length); -} -``` - -Running the spec again still shows both tests failed but the second error is different. -``` -Expected true to be false. -Expected 6 to be 5. -``` - -That’s better. Now we can see that the blank note is being added and the function is still returning true instead of false. - -Lets make enough changes to the add function to let it pass the tests. -``` -add: function(note) { - if (note) { - var item = {timestamp: Date.now(), text: note}; - list.push(item); - return true; - } - return false; -} -``` - -The test runner now shows that both tests pass. Notice how it runs all the specs every time. This means you get alerted if you break a test that previously passed. - -## 5 Writing Pending Specs - -Up until now you have written the tests as they were needed however a better workflow might be to write all your module tests up front. The problem with this is that you will get a huge list of errors when you start coding because every test will be run. -The solution is to write the tests but mark them as pending until you are ready to run them. -We can flag up a spec as pending by calling the global Jasmine pending() function. -``` -it('should ignore notes containing only whitespace', function() { - expect(notes.add(' ')).toBe(false); - expect(notes.count()).toBe(5); - pending(); -}); - -it('should require a string parameter', function() { - expect(notes.add()).toBe(false); - expect(notes.count()).toBe(5); - pending(); -}); -``` - -As you can see, these specs are flagged as pending and not run. This means you can design and code all your tests before starting to write your module, removing the pending flags one by one as the earlier tests pass. - -## 6 Nesting Test Suites - -Up until now, all your specs have been in a long list but for a complex module it would make sense to use nesting to provide a clearer structure. So, for example the test runner might look like this. - -This is done by placing related code inside testing sub-suites using the describe function. This makes the test runner easier to follow -``` -describe("notes module", function () { - - beforeEach(function() {}); - - describe('adding a note', function() { - - it('should be able to add a new note', function () { - // tests go here - }); - - it('should ignore blank notes', function () { - // tests go here - }); - - it('should ignore notes containing only whitespace'); - it('should require a string parameter'); - }); - - describe('deleting a note', function() { - it('should be able to delete the first index'); - it('should be able to delete the last index'); - it('should return false if index is out of range'); - it('should return false if the parameter is missing'); - }); -}); -``` - -## 7 Test-Driven Development - -So what is Test-Driven Development? lets start by looking at the definition on Wikipedia. -Test-driven development is a software development process that relies on the repetition of a very short development cycle: first the developer writes an automated test case that defines a desired improvement or new function, then produces the minimum amount of code to pass that test, and finally refactors the new code - -So from this definition we can see three distinct steps: - -1. write an automated test to define an improvement or new function -2. produce enough code to pass the test -3. refactor (clean up) the code without breaking the tests. - -You have already been provided with a description of the functionality required by this module. You have also been given the first set of specs and shown how to pass the first few of these. You now need to complete the following activities: - -1. complete the four specs to test the add functionality -2. devise tests for the remove and find functionality -3. write the specs to test the remove and find functionality -4. tidy up the code without breaking any of the tests. - -Test-Driven Development is an important process to learn. You should ensure you apply this process to all future work in this module. You are also encouraged to use TDD methodologies in your other modules. - -## 8 Advanced Topics - -Once you have mastered the skills covered in this worksheet you should learn some of the more advanced topics: - -- using a range of different matchers -- writing custom matchers -- synchronous support -- spies - -References - -https://github.com/jasmine/jasmine/releases - -http://evanhahn.com/how-do-i-jasmine/ - -http://code.tutsplus.com/tutorials/testing-your-javascript-with-jasmine--net-21229 - -http://jasmine.github.io/2.0/introduction.html - -http://blog.stevensanderson.com/2009/08/24/writing-great-unit-tests-best-and-worst-practises/ - -https://gitlab.com/marktyers/unit-testing (access restricted to teaching staff) diff --git a/labs/13 Offline Storage/worksheet.md b/labs/13 Offline Storage/worksheet.md deleted file mode 100644 index f196fa7..0000000 --- a/labs/13 Offline Storage/worksheet.md +++ /dev/null @@ -1 +0,0 @@ -# Offline Storage diff --git a/labs/14 Introduction to PHP/worksheet.md b/labs/14 Introduction to PHP/worksheet.md deleted file mode 100644 index e1b15a8..0000000 --- a/labs/14 Introduction to PHP/worksheet.md +++ /dev/null @@ -1,135 +0,0 @@ -# Introduction to PHP - -## What is PHP? - -In this worksheet is an introduction to the PHP programming language. - -When implementing a Web-based application some processing is usually done on a client-side (web browser) and/or on a server-side (web server). The server creates a response programmatically since static content is not usually enough. - -This can be done in various ways: using PHP, Java Server Pages (JSP), Ruby, Python etc. PHP programming language is widely used and fairly easy to learn, so we are using PHP in this course. - -PHP is a scripting language meaning it is interpreted; it is translated to machine instructions and executed on the fly, rather than compiled to machine code before actually running a program. - -Traditionally a PHP program creates a dynamic content in the HTML page, for example client information read from the database which is stored in the server. Nowadays there is a trend towards to an API based processing: a PHP programming module is triggered by a REST API call and returns a JSON object as a response. - -## Task List - -In this lab you - -- check, that Codio is able to run PHP programs -- write small PHP code examples - -## 1. Requirements - -PHP needs a server installation, which can be Apache or Microsoft IIS. Also PHP extension for the server is needed. Instructions for installation required by Codio can be found from here: -[Installing Apache and PHP in Codio][apache] -## 2. Basic PHP-program - -Take a look at the basic PHP-program below: -``` - - - Minimal PHP - - -

        - All day long I just write PHP -

        - - - -``` -This file should be named using PHP-extension (not HTML-extension) for example Minimal.php, so that the server understands to execute the PHP-code. -You can embed HTML-code inside PHP-code. You start writing PHP by using You can think these syntax forms as being mode switchers: -"" starts HTML-mode (or at least ends PHP-mode). - -The syntax template for embedded PHP content is the following: -``` -HTML content - - - -HTML content -``` - - - -## 3 Some PHP-spesific features - -### 3.1 Defining a variable - -A variable name in PHP is always preceded by a $ sign. The variable is defined by assigning it a value. The type information is not needed. -The type of a variable is based on the type of an assigned value. The syntax template for variable definition is below: - -``` -$name = expression -``` - -Examples: -```php -$user_name = "james"; -$age = 20; -$under_age = $age - 5; -``` - -Under_scores are forbidden by some programming style guides but let's follow the style of CodeIgniter which says: -"variables should contain only lowercase letters, use underscore separators, and be reasonably named to indicate their purpose and contents. Very short, non-word variables should only be used as iterators in for() loops." - -### 3.2 Loosely Typed Language - -PHP is loosely typed language meaning that you can assign a value of a certain type to a variable which is of a different type and PHP tries to make a conversion. For example the code -```php -Values $number and $f_number

        "; - -?> -``` -will print the result: Values 20.5 and 23.64 - - -### 3.3 Strings - -Strings in PHP are enclosed "-marks (quotation) or '-marks (apostrophe). - -Examples: -```php -$str = "Writing PHP is cool"; -$printed = "Values $number and $f_number"; -$str2 = 'This is also a string'; -``` - -The first and the second are examples of interpreted strings, meaning you can write variable names inside a string and the values of variables will be inserted into a string. -The third example is not interpreted so writing a variable name into this string have no special meaning. - -The + operator is always a numeric operator in PHP. String concatenation is done by using . (dot-operator). - -Printing out variable values using a non-interpreted string looks like this: -print '

        Values '.$number.' and '.$f_number.'

        '; - -Strings in PHP are an array of characters so accessing an individual character is similar to many other languages: writing $str[0] returns "W" from the upper example. -More on strings: [PHP strings][PHPStrings] - -## Test Your Understanding - -1. Write a PHP program which prints a classic "Hello word" greeting on the screen. -2. Write a PHP program which shows an image duplicated so that there are four images in two rows on the web page. -3. Write a PHP program that calculates an area of a circle when the radius is known. You may use a harcoded radius value. -4. Check [for example from here][W3SchoolsPHP], how a PHP script process input fields of a form. Apply this and let the user fill in a radius value to the form and calculate area of a circle based on that value. -4. Define few variables of different types, assign values to them and print them out using an interpreted string. -5. Same as 4, but use a non-interpreted string this time. - - -[apache]: https://codio.com/docs/ide/tutorials/php/ -[PHPStrings]: http://php.net/manual/en/language.types.string.php -[W3SchoolsPHP]: http://www.w3schools.com/php/php_forms.asp \ No newline at end of file diff --git a/labs/15 Modular PHP/ShowForm.php b/labs/15 Modular PHP/ShowForm.php deleted file mode 100644 index 9f223f0..0000000 --- a/labs/15 Modular PHP/ShowForm.php +++ /dev/null @@ -1,11 +0,0 @@ - - - -
        -Name:
        -E-mail:
        - -
        - - - \ No newline at end of file diff --git a/labs/15 Modular PHP/getInput.php b/labs/15 Modular PHP/getInput.php deleted file mode 100644 index 6eeb7d8..0000000 --- a/labs/15 Modular PHP/getInput.php +++ /dev/null @@ -1,8 +0,0 @@ - - - -Reading Input; Your name is:
        -Your email address is: - - - \ No newline at end of file diff --git a/labs/15 Modular PHP/worksheet.md b/labs/15 Modular PHP/worksheet.md deleted file mode 100644 index 4a477ef..0000000 --- a/labs/15 Modular PHP/worksheet.md +++ /dev/null @@ -1,330 +0,0 @@ -# Modular PHP - -in this lab you will use modular features of the PHP language, for example functions and classes. - -## Task List - -in this lab you will do the following tasks: - -- write PHP functions -- use predined functions -- write PHP classes -- create a JSON object as a response to an API call - -### 1 Functions -#### 1.1 Defining a Function - -A function in PHP has a same meaning as in other languages: it is a group of logically connected statements which have a name and which can -be called using that name. A function can have parameters which should be named but types are not declared. A function can return a value but -the type of the returned value should not be written in declaration. - -Syntax template is the following: - -``` -function name($parameterName, ..., $parameterName) { - statements; -} -```` -Example: - -``` - - - Function example - - Number $checked_number is odd.

        "; - else - print "

        Number $checked_number is even.

        "; - ?> - - -``` - -The latter if-statement looks a bit redundant. How would you get rid of it? - -#### 1.2 Parameter Types - -By default parameters are passed by value in PHP, meaning that the actual parameter ($checked_number above) value is copied into a formal parameter ($number) in each function call. -This means that updating parameter value inside a function block does not affect to the value of the actual parameter in calling part of a program. - -A parameter can also be passed by reference which means that both parameters (actual and formal) are references to the same memory address and formal parameter can be thought as being an alias to the actual parameter. In this case a developer can pass information inside a function as well outside of it with parameters. -A reference parameter should have a & mark before the usual $ mark. -See the example below. - -``` - - - Function example - - Number $checked_original_value is odd. (incremented value $checked_number)

        "; - else - print "

        Number $checked_original_value is even. (incremented value $checked_number)

        "; - ?> - - -``` - - -#### 1.2 Scope - -A variable defined outside of a function have a global scope and can be seen throughout the program. A variable which is defined inside a function are local -variables and have a local scope. They can be used only inside a function block they are defined. - -If a function wants to access to a global variable, global keyword is needed in front of the variable name. See the example below. - -``` - - - Function example - - Number $checked_number is odd.

        "; - else - print "

        Number $checked_number is even.

        "; - ?> - - -``` - -### 2 Files - -PHP allows access to resources on the server. One such a resource is the files on the server. PHP has powerful and fairly easy to use file related functions. -Below is a table containing some useful I/O related functions. - - - -| function names | category | -| ------------------------------------------------- |:--------------------------------:| -| file,file_get_contents,file_put_contents | reading/writing entire files | -| basename,file_exists,filesize,fileperms,filemtime | asking for information | -| is_dir,is_readable,is_writable,disk_free_space | asking for information | -|copy,rename,unlink,chmod,chgrp,chown,mkdir,rmdir |manipulating files and directories| -|glob,scandir |reading directories | - -An example below reverses lines of a text file (The example is found from the book Stepp, Miller, Kirst:"Web Programming Step bt Step"). - -```php - -``` -### 3 REST API - a Simple Example - -Here is a simple REST API [example][RESTAPI] found from the web. In the example the first PHP program creates an api implementation and the second is a client which creates api calls. -The API implementation is below: - -```php - "Web Demo", "app_price" => "Free", "app_version" => "2.0"); - break; - case 2: - $app_info = array("app_name" => "Audio Countdown", "app_price" => "Free", "app_version" => "1.1"); - break; - case 3: - $app_info = array("app_name" => "The Tab Key", "app_price" => "Free", "app_version" => "1.2"); - break; - case 4: - $app_info = array("app_name" => "Music Sleep Timer", "app_price" => "Free", "app_version" => "1.9"); - break; - } - - return $app_info; -} - -function get_app_list() -{ - //normally this info would be pulled from a database. - //build JSON array - $app_list = array(array("id" => 1, "name" => "Web Demo"), array("id" => 2, "name" => "Audio Countdown"), array("id" => 3, "name" => "The Tab Key"), array("id" => 4, "name" => "Music Sleep Timer")); - - return $app_list; -} - -$possible_url = array("get_app_list", "get_app"); - -$value = "An error has occurred"; - -if (isset($_GET["action"]) && in_array($_GET["action"], $possible_url)) -{ - switch ($_GET["action"]) - { - case "get_app_list": - $value = get_app_list(); - break; - case "get_app": - if (isset($_GET["id"])) - $value = get_app_by_id($_GET["id"]); - else - $value = "Missing argument"; - break; - } -} - -//return JSON array -exit(json_encode($value)); -?> -``` -The edited client implementation is below: -``` - - - - - - - - - - - - - - -
        App Name:
        Price:
        Version:
        -
        - - Return to the app list - - - - - -``` -These are the API calls in this example: - -- http://drink-office.codio.io:3000/REST_Client.php?action=get_app&id=1 (drink-office.codio.io:3000 is the host name which may vary.) -- http://drink-office.codio.io:3000/REST_Client.php?action=get_app&id=2 -- http://drink-office.codio.io:3000/REST_Client.php?action=get_app&id=3 -- http://drink-office.codio.io:3000/REST_Client.php?action=get_app&id=4 -- http://drink-office.codio.io:3000/REST_Client.php?action=get_app_list - -The API implementation returns a specific JSON object related to the query string id value. The query string variable "get_app_list" is used to create a menu if there is a need to run client more than once. -Notice how a PHP program obtain access to the HTTP query ids and to the query id values. - - - -### 4 Object Oriented PHP - -PHP can be used as an object oriented language i.e. a developer can create classes and objects and use other object oriented features. -The syntax template for PHP class is the following: - -```php -class name { - private $name; # member attribute (class data in each object) - - public function __contruct(name) { # constructor - statements; - } - - public function name(parameters) { # member method - statements; - } - -} -``` - -When accessing class members inside class member functions an implicit reference $this should be used. - -Further reading: [PHP home page][PHP] - -[PHP]: http://php.net/manual/en/ -[RESTAPI]: http://blog.ijasoneverett.com/2013/02/rest-api-a-simple-php-tutorial/ - -### Test Your Understanding - -It is good to be able reed some data when doing these exercises. There are two small php-scrits (Showform.php, getInput.php) included, which can be useful. Feel free to use them if you like. - -1. Write a PHP function which gets user name and age as input and prints them out. -2. Write a PHP function which reverses characters of the input string and prints the result out. So input string "John" is printed out as "nhoJ". -3. Write a PHP function that prints out prime numbers from 2 to some maximum, the maximum is given as input. -4. Write a PHP function which randomly chooses (and prints) one line from the list of words found from here: http://www.webstepbook.com/words.txt. -5. Write a PHP program that calculates an area of a circle when the radius is known. Make this version based on the REST API (radius is read from an HTTP query string and the result is returned as a JSON object). -6. Write a traffic card program using PHP class which makes it possible to “travel” in public transportation (in busses, trams, etc.). Define a TrafficCard class which has at least the following features: - a. a card initialization (who’s the owner, assign zero to a balance) - b. loading a value to the card (euros/pounds) - c. traveling using a card - d. two different charges: 2.80 euros/pounds in city central and 4 euros/pounds in areas outside of a city. - - Consider what operations a card must include. Implement the TrafficCard class and test that it functions as should (for example a balance can never be negative). - diff --git a/labs/16 RESTful API/img/data_city.png b/labs/16 RESTful API/img/data_city.png deleted file mode 100644 index 7c727fa..0000000 Binary files a/labs/16 RESTful API/img/data_city.png and /dev/null differ diff --git a/labs/16 RESTful API/img/datanew_city.png b/labs/16 RESTful API/img/datanew_city.png deleted file mode 100644 index 1af3e13..0000000 Binary files a/labs/16 RESTful API/img/datanew_city.png and /dev/null differ diff --git a/labs/16 RESTful API/img/delete_city.png b/labs/16 RESTful API/img/delete_city.png deleted file mode 100644 index dd5c1e4..0000000 Binary files a/labs/16 RESTful API/img/delete_city.png and /dev/null differ diff --git a/labs/16 RESTful API/img/get_city.png b/labs/16 RESTful API/img/get_city.png deleted file mode 100644 index 13c0996..0000000 Binary files a/labs/16 RESTful API/img/get_city.png and /dev/null differ diff --git a/labs/16 RESTful API/img/hist_city.png b/labs/16 RESTful API/img/hist_city.png deleted file mode 100644 index 9387e4d..0000000 Binary files a/labs/16 RESTful API/img/hist_city.png and /dev/null differ diff --git a/labs/16 RESTful API/img/newfile.png b/labs/16 RESTful API/img/newfile.png deleted file mode 100644 index a6a5891..0000000 Binary files a/labs/16 RESTful API/img/newfile.png and /dev/null differ diff --git a/labs/16 RESTful API/img/post_city.png b/labs/16 RESTful API/img/post_city.png deleted file mode 100644 index 313b799..0000000 Binary files a/labs/16 RESTful API/img/post_city.png and /dev/null differ diff --git a/labs/16 RESTful API/img/put_city.png b/labs/16 RESTful API/img/put_city.png deleted file mode 100644 index 1b4ac8a..0000000 Binary files a/labs/16 RESTful API/img/put_city.png and /dev/null differ diff --git a/labs/16 RESTful API/php/index.php b/labs/16 RESTful API/php/index.php deleted file mode 100644 index 42befd1..0000000 --- a/labs/16 RESTful API/php/index.php +++ /dev/null @@ -1,101 +0,0 @@ - - diff --git a/labs/16 RESTful API/worksheet.md b/labs/16 RESTful API/worksheet.md deleted file mode 100644 index 62c15c6..0000000 --- a/labs/16 RESTful API/worksheet.md +++ /dev/null @@ -1,320 +0,0 @@ -# RESTful APIs - -In this lab you learn the basics of RESTful APIs. REST has become one of the most important technologies for Web applications. -Every major development language now includes frameworks for building RESTful Web services. -Also CodeIgniter has also nowadays a RESTful server implementation. We will discuss this later on section 17. - -REST stands for Representational State Transfer, which is an architectural style used to build Web services that are lightweight, maintainable, and scalable. - - -## Task list - -In the lab you do the following tasks: - -1. Use existing published REST APIs -2. Write a mash-up combining existing REST API's results -3. Write your own REST API. - -This lab takes at least 2 hours. - - -## 1 REST architectural style - -### Application as a resource provider - -We can think of every system as a system using resources. These resources can be pictures, video files, -Web pages, business information, or anything that can be represented in a computer-based system. -The purpose of a service is to provide access to these resources. - -A RESTful service is stateless and does not maintain the application state for any client. -A request cannot be dependent on a past request and a service treats each request independently. -HTTP is itself a stateless protocol by design. - - -### Requiring resources - -REST requires each resource to have at least one URI. -A RESTful service uses a directory hierarchy like human readable URIs to address its resources. -The job of a URI is to identify a resource or a collection of resources. -The actual operation is determined by an HTTP verb. The URI should not say anything about the operation or action. -This enables us to call the same URI with different HTTP verbs to perform different operations. - -Suppose we have a database of persons and we wish to expose it to the outer world through a service. -A resource person can be addressed like this: - -http://MyService/Persons/1 - -### HTTP methods used - -You have seen GET and POST methods used, but a RESTful style is to use all HTTP methods, and to use of them in -a consistent way. - -Method | Operation performed on server | Quality -| --------|---------|-------| -GET | Read a resource. | Safe -PUT | Insert a new resource or update if the resource already exists. | Idempotent -POST | Insert a new resource. Also can be used to update an existing resource. | N/A -DELETE | Delete a resource . | Idempotent -OPTIONS | List the allowed operations on a resource. | Safe -HEAD | Return only the response headers and no response body. | Safe - -A Safe operation is an operation that does not have any effect on the original value of the resource. - -An Idempotent operation is an operation that gives the same result no matter how many times you perform it. - -An example of an HTTP request to put a new character in the Persons database. -``` -POST http://MyService/Person/ -Host: MyService -Content-Type: application/json -Accept: application/json -{ - "id": 1, - "Name": "Harry Potter", - "Email": "harry.potter@hogwarts.magic", - "From": Book -} -``` - -## 2 Using REST API - -API stands for Application Programming Interface - it means that we give access to public methods of an application outside of the program itself. -To allow this action to take place, the application has to publish an API that specifically allows for -foreign applications to make calls to its data and return data to the user from inside of the external application. - -We have created a simple API in FireBase, which is a cloud platform for building web applications. The URL to the API is - -https://cityofhelsinki-erja.firebaseio.com - -The API is a copy of issue reporting API, which is used by applications for sending service requests to the City of Helsinki. At the moment, the real issue API is mostly used for sending service requests about broken parts of city infrastructure like street signs, potholes, etc. -To do our experiments we are using a Chrome browser plugin called Postman. If it’s not installed on your computer, do it now. -The resources we have are shown in FireBase dashboard: - -![Resources available in the API](img/data_city.png). - -The requests we have are the ones which are shown in Postman history. - -![Postman history](img/hist_city.png). - -### Making queries with Postman - -By choosing a GET method at the left-hand side and requesting for the first issue in the URL, we have - - -![Postman get](img/get_city.png). - -Check your understanding by asking -- for a description of an issue and -- for all issues having “Katujen kunto ja liikenne” ask for their group - -The PUT method is used to add a named resource. we will have to give the item we want to add in the request. By choosing PUT at the left hand side, giving URL and adding the json data to the request body as raw data in JSON format, we will have - -![Postman put](img/put_city.png). - -In our data store we have the new item number 10 - -![Resources available in the API](img/datanew_city.png). - -The POST method is used to add a new item to a collection. These are represented as arrays in our data and the keywords is such an array. By choosing POST at the left hand side, giving URL and adding the string to the request body as raw data in Json or Text format, we will have - -![Postman post](img/post_city.png). - -Finally we have DELETE method which is used to remove resources and collections. By choosing POST at the left hand side and giving URL and the issue, we will have - -![Postman delete](img/delete_city.png). - -### Using public API - - -Try the next two requests in Postman. -One for Google Maps API for getting coordinates for a location, another for Instagram to pull images from Instagram API using the coordinates. -Instagram like some other APIs need authentication. For public API's you may want to have a look at - -https://www.avoindata.fi/data/fi/dataset?res_format=json&_res_format_limit=0 - -The requests are e.g. - -https://maps.googleapis.com/maps/api/geocode/json?address=helsinki, fi - -and - -https://api.instagram.com/v1/media/search?lat=60.1788737%lng=24.9570322 - - -Next take a look at the following code, which is a small application called mashup, which refers to a web application, that uses content from more than one source to create a single -new service displayed in a single graphical interface. Nowadays there are also Mashup composition tools that are usually simple enough to be used by end-users. -They generally do not require programming skills and rather support visual wiring of GUI widgets, services and components together. -We are using these two APIs to program a mashup with PHP. First the queries we are using are the ones we have in the above figures. -Notice that this is not a REST API itself, it is just a demonstration how to construct HTTP requests in PHP. - - -But because we are making an application, we will allow the user to tell a location name, and make our program to find the coordinates, and use them to find out photos from Instagram. -```html - - - - - geogram - - -
        - - -
        -
        - $image){ - echo '
        '; - } - } - ?> - - -``` -We will put everything in a single page, the above is our form with a little PHP code. In front of this we put our PHP code to make up the URIs. -As you can see the file is called geogram.php. - -```php - -``` - -As you can see, the request to Instagram needs a client-id. If you want the code to work, you need to authenticate the application in Instagram. By registering the application you get the client-id, which you can use. - -### Test your understanding - -1. Change the mash-up application to use some other existing public REST APIs. Remember first to test your queries with Postman. - - -### Making your own REST API - -Next, we design a simple RESTful API and set it up as a service. As the implementation of a full-scale -RESTful API usually requires permanent storage at server side (a database), the functionality of the example API consists of mock implementations -that merely demonstrate that each call has been handled by a correct PHP function. -Nevertheless, it will later be straightforward to further develop the API into a full, working application. - -First, we choose the API end point to be: - -https://museum-austria.codio.io:9500/staffapi/ - -Here, staffapi is the name of the designed API. The domain part in the URI is given by Codio. In your project that runs in a different Codio Box, -the domain will be different. - -The URI shown before will be a prefix for all API calls. - -Next, we design the resources that our API handles. The API should handle individual persons and provide a list of persons available, so the corresponding resources could be: -1. **persons**, a collection of persons -2. **person**, a single person - -The choice of resources will be reflected in the URIs used to call the API. - -The following API methods will be implemented: - -Method | Resource | Purpose | Example ---------|---------|-------|-------| -POST | person | add a person| POST .../staffapi/person?id=13&firstname="Jane"&lastname="Doe" -GET | persons | get a list of persons | GET .../staffapi/persons -GET | person | get a person | GET .../staffapi/person/13 -DELETE | person | delete a person | DELETE .../staffapi/13 - -The list can be expanded for new functionality. - -The URIs above do not correspond to the directory hierarchy and actual files in the server. The idea of resources represented by URIs is to redirect a call to each URI to -a handler file (here **index.php**, created later). The handler file can retrieve the original URI parts to decide the original request of the caller. - -Next, you should consider which error messages you give in the situations when accessing the resources fails. In our example, -we will return HTTP error code **405 (Method not allowed)** in case of an illegal API call. - -As it is easier to setup the API server in a new Codio project, create a new Codio project at this step. **From now on, until the end of the worksheet, do not continue to work in the original Codio project.** - -The next step is to map each URI to a PHP function that handles the corresponding action. - - -In the new Codio project, the first step is to add a file called **.htaccess** into the root directory of the project's workspace (in the root of **~/workspace** directory). - -You can simply right-click the project's master folder icon in the project's Codio workspace and select New File. -![(You see the image of file creation if you open this assignment sheet outside Codio, in a separate browser window.)](img/newfile.png) - -The **.htaccess** file is used by Apache web server, and it may contain instructions for the web server to redirect URI requests. -Add the following contents to the newly created **.htaccess** file: - -``` -Options -MultiViews -RewriteEngine On -RewriteCond %{REQUEST_FILENAME} !-f -RewriteRule ^ index.php [QSA,L] -``` - -The file tells to map all URIs to a file called index.php. Thus, all API calls are now directed to a single handler file **index.php** -that will be constructed in such a way that it contains a handler function for each operation. (Technically, -there could be separate handler files for various URIs, but in this example all API calls are handled by the same handler script. Modularity is achieved by assigning separate functions for different operations.) - -This URI redirecting is the reason why it is not advisable to work in the original Codio project, as the lines above would remap all URIs to **index.php** even if they were completely unrelated to this worksheet. - -Now, create a handler file called **index.php** in the root of the project workspace. Copy the contents of the file **index.php** in course module's **php** folder into the -newly-created **index.php** file. - -To get the HTTPS server address, hit "Box URL SSL" in Codio's menu row. This should open a blank browser tab with the server uri. The domain -name in the URI varies, but it should be something like: - -https://museum-austria.codio.io:9500. - -This is a prefix in the API calls. - -To make an api call for listing persons, for example, you can now make a GET call to - -https://museum-austria.codio.io:9500/staffapi/persons. - -Again, the domain name must be replaced with the correct one. - -Open Postman and make API calls to try all operations mentions in the list of operations. Each mock handler function contains an echo statement that verifies -that the correct function has been called. - -Study the contents of the file and make sure you understand how it works. - -The three functions in the beginning, **getResource()**, **getParameters()** and **getMethod()**, are URI parsing functions that return the information embedded in the URI in a consumable format. -For instance, suppose a POST request [https://museum-austria.codio.io:9500/staffapi/person?id=13&firstname="Jane"&lastname="Doe"](https://museum-austria.codio.io:9500/staffapi/person?id=13&firstname="Jane"&lastname="Doe") has been obtained by server. -- For this sample URI, function **getResource()** would produce a numerical array of URI parts, where element 0 contains the value 'staffapi', and element 1 contains the value 'person'. -- The second function, **getParameters()**, produces an associative array, where element 'firstname' has value "Jane", and element 'lastname' has a value of "Doe". -- Finally, function **getMethod()** returns a string containing the HTTP method, i.e. 'POST'. - -The URI parser functions can be left as they are. The interesting part is in the very end, where the different handler functions are called. In the example, GET requests with URI parts staffapi/person are directed to a function called **getPerson()** that gets the person's id as a parameter. The function **getPerson()**, is just a mockup implementation. -Thus, adding new functionality to the API is simple: - -1. write corresponding handler functions. -2. expand the if statement in the end to call the newly-written handler functions. - - -### Test your understanding - -1. Add a mock DELETE operation that deletes the entire collection of persons. Verify that the API call works as intended. - -2. Generate a minimalistic RESTful API that gives you a lucky number and a lucky word. Both should be delivered within a single JSON. Use random number generator to produce the output. - -3. Generate a HTML client that contains a button for making a call for your API. The client should also display the results. - diff --git a/labs/17 Model-View-Controller/img/500px-MVC-Process.png b/labs/17 Model-View-Controller/img/500px-MVC-Process.png deleted file mode 100644 index be53e63..0000000 Binary files a/labs/17 Model-View-Controller/img/500px-MVC-Process.png and /dev/null differ diff --git a/labs/17 Model-View-Controller/img/codeigniter.jpg b/labs/17 Model-View-Controller/img/codeigniter.jpg deleted file mode 100644 index dea1925..0000000 Binary files a/labs/17 Model-View-Controller/img/codeigniter.jpg and /dev/null differ diff --git a/labs/17 Model-View-Controller/img/codeigniter.png b/labs/17 Model-View-Controller/img/codeigniter.png deleted file mode 100644 index 73503ac..0000000 Binary files a/labs/17 Model-View-Controller/img/codeigniter.png and /dev/null differ diff --git a/labs/17 Model-View-Controller/worksheet.md b/labs/17 Model-View-Controller/worksheet.md deleted file mode 100644 index 538704a..0000000 --- a/labs/17 Model-View-Controller/worksheet.md +++ /dev/null @@ -1,209 +0,0 @@ -# Model-View-Controller and Frameworks - -"Model-view-controller (MVC) is a software architectural pattern for implementing user interfaces. It divides a given software application into three interconnected parts, so as to separate internal representations of information from the ways that information is presented to or accepted from the user." (Wikipedia) - -## Task list - -In the lab you do the following tasks: - -1. Understand how MVC-modelling works -1. Install a CodeIgniter Framework -1. Go through CodeIgniter tutorial - -This lab takes approximately 3 hours. - -## Basics - -MVC-model has three components: (Wikipedia) - -1. **A controller** can send commands to the model to update the model's state (e.g., editing a document). It can also send commands to its associated view to change the view's presentation of the model (e.g., by scrolling through a document). -2. **A model** stores data that is retrieved by the controller and displayed in the view. Whenever there is a change to the data it is updated by the controller. -3. **A view** requests information from the model that it uses to generate an output representation to the user. - -![MVC-model](http://upload.wikimedia.org/wikipedia/commons/f/fd/MVC-Process.png "MVC") - -As the original idea of MVC was to use it implementing user interfaces, nowadays it is commonly used with WWW-applications. Several commercial and noncommercial web application frameworks have been created that enforce the pattern. These frameworks vary in their interpretations, mainly in the way that the MVC responsibilities are divided between the client and server. - -## First example: a simple chat - -In this example we make a simple chat program. First we desing a Model: (name it Model.php) - -``` - file)) { - return file($this->file); - } else { - return array(); - } - } - - public function add_message($message) { - $message = date("H:i:s: ") . $message; - file_put_contents($this->file, "{$message}\n", FILE_APPEND); - } - } - ?> -``` - -Remember: before you can use this code, you should make a new empty file named *messages.txt* and change its access: - -``` -$ touch messages.txt -$ chmod o+rw messages.txt -``` - -After that the web server could read and write to this file. - -Model defines two functions: *messages()*, wich returns previous messages and *add_message()*, which writes a new message to chat. So you can read and write messages with it. - -Next we make *View.php*, which is an user interface for chat: -``` - - - - Chat - - -

        Chat

        - messages)) { - echo "There is no messages in chat..."; - } else { - foreach ($this->messages as $message) { - echo htmlspecialchars($message) . "
        "; - } - } - ?> -

        New message

        -
        - - -
        - - -``` - -Next one is the Controller (*Controller.php*): -``` - model = new Model(); - } - - public function list_it() { - $this->messages = $this->model->messages(); - include("View.php"); - } - - public function send() { - $this->model->add_message($_POST["message"]); - header("Location: Chat.php?action=list_it"); - } - } - ?> -``` -Notice the first function *__construct()*. It is the constructor, which creates a new object. You might need sometimes also a destrunctor (*__destruct()*), which deletes an object. - -And last: put all together: (*Chat.php*) -``` - $action(); - ?> -``` -Some links for MVC: - -[Wikipedia](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) -[MVC Architecture](https://developer.chrome.com/apps/app_frameworks) -[MVC for Noobs](http://code.tutsplus.com/tutorials/mvc-for-noobs--net-10488) - -# Web frameworks - -A web framework is a software which is designed for development of web software, such as - -- web applications -- dynamic websites -- web resources -- web services - -Also these are supporting code reusing. - -## How to select right or best framework? - -There are a lot of different frameworks but not one answer, which is the best choise. First of all, it is depending which programming language you are using. After this, you can choose your framework. - -Here is one list from Wikipedia, which kind of frameworks exists: - -[Comparison of web application frameworks](http://en.wikipedia.org/wiki/Comparison_of_web_application_frameworks) - -As you can see, there are a lot of them... - -And also some links for doing right choises: - -[Choosing a Web Framework/Language Combo for the Next Decade](http://jacquesmattheij.com/choosing-web-framework-language-combo) -[15 Most Important Considerations when Choosing a Web Development Framework](http://code.tutsplus.com/tutorials/15-most-important-considerations-when-choosing-a-web-development-framework--net-8035) -[10 criteria for choosing the correct framework](http://symfony.com/ten-criteria) -[Choosing A Web Framework](http://www.clearlytech.com/2013/12/01/choosing-web-framework/) -[How to choose a web framework and be surprised](http://www.slideshare.net/jmarranz/how-to-choose-a-web-framework-and-be-surprised) - -# CodeIgniter - -CodeIgniter is an open source framework, which uses PHP language. It is commonly used and has following options: - -- **It follows MVC design pattern:** Current days, Model View Controller(MVC) is the most popular design pattern in software development. -- **Performance matters:** It obtains better scores than other php based application frameworks like Cakephp, Zend etc in terms of loading speed / performance. -- **Easy integration support:** It gives a very easy way to integrate third-party tools as library/plugins. -- **Good documentation:** For example, it has a very good tutorial in it. -- **Easy istallation:** Just copy file structure, make a couple small configurations and start using it. - -## Installing CodeIgniter to Codio - -First, make a new project to Codio, open a terminal and install a codeigniter (get it & unzip). Note: before downloading, check the newest version! -``` -$ wget https://github.com/bcit-ci/CodeIgniter/archive/3.0.3.zip -$ unzip 3.0.3.zip -``` -When done, you will see this kind of directory structure on your project: - -![directory](http://users.metropolia.fi/~kuivi/codeigniter/codeigniter.png "directory structure") - -As you see, also documentation is included. - -We need also another programs to use: Some database and web server. So we install Apache Web Server, PHP5, PHP5 plugin for Apache and a MySQL relational database: (and of course to start these services) -``` -$ parts install php5 php5-apache2 mysql -$ parts start apache2 mysql -``` -Now we are ready for using Codeigniter. - -## Tutorial - -CodeIgniter has a very good tutorial. With it you will find out what is MVC:s idea and how CodeIgniter works. - -## Test your understanding - -1. Test how chat example works -1. Add a nickname-field to your chat so that you can show in messages list who wrote message. Could you do this so, that you have to give your nickname only once? -1. Go through GodeIgniters tutorial -1. Use CodeIgniter to make your chat. -1. For more experienced users: By default CodeIgniters URL:s are something like that: *example.com/index.php/news/article/my_article* Find out how to remove *index.php* from the visible URL. Tip: you maybe have to install something more to your Apache server... - - - - - diff --git a/labs/18 Codeigniter and RESTful API/worksheet.md b/labs/18 Codeigniter and RESTful API/worksheet.md deleted file mode 100644 index 331dcf3..0000000 --- a/labs/18 Codeigniter and RESTful API/worksheet.md +++ /dev/null @@ -1,66 +0,0 @@ -# Codeigniter and RESTful API - -In this lab you learn how to use RESTful APIs with Codeigniter. To do this, you will install some extra parts to Codeigniter. - -## Task list - -In the lab you do the following tasks: - -1. Install REST parts to Codeigniter -1. Go through tutorial -1. Do a small application with Codeigniter and REST API - -## What we need? - -For making RESTful APIs with Codeigniter, easiest way is to install Codeiginter REST Server. It's "*A fully RESTful server implementation for CodeIgniter using one library, one config file and one controller.*" - -You can download it from - -https://github.com/chriskacerguis/codeigniter-restserver - -You can download full package (.zip) or just these four files: - -``` -application/libraries/Format.php -application/libraries/REST_Controller.php -application/config/rest.php -application/language/english/rest_controller_lang.php - -``` -Copy those files to your applications directories and after that you can make your own RESTful API:s with CI. - -## How to use Rest Server? - -In Codeigniter, when creating a class, you'll do this: - -``` -company; - - // select collection - $collection = $db->persons; - - // retrieve all items in collection - $cursor = $collection->find(); - - if($cursor->hasNext()) - { - echo json_encode(iterator_to_array($cursor)); - } - - // close connection - $connection->close(); - } -} - -?> diff --git a/labs/20 Document Databases/worksheet.md b/labs/20 Document Databases/worksheet.md deleted file mode 100644 index 5b6c678..0000000 --- a/labs/20 Document Databases/worksheet.md +++ /dev/null @@ -1,132 +0,0 @@ -# Document Databases - -In this lab you learn to connect your web application to a NoSQL database management system. - -NoSQL referes to a family of database management systems that neither use relational model nor SQL for representing data and queries. The main driving force for NoSQL is scalability: -NoSQL databases are good at handling very large quantities of semistructured or unstructured data, usually in a distributed fashion. - -Document databases are a subcategory of NoSQL databases. They are designed for storing key/value pairs where the value field also has utilizable structure, usually in a form of a JSON or XML object. - -MongoDB that we use in this lab is one of the most commonly used document database management systems. - -## Task list - -In this lab you do the following tasks: - -1. Install MongoDB in Codio -2. Learn MongoDB data model. -3. Make queries as well as data modification operations via MongoDB interpreter. -4. Use MongoDB from your own PHP scripts. - -## 1 Installation - -Start the lab by installing MongoDB server in Codio. To do this, open a new terminal window and type: - -``` -parts install mongodb -``` - -Then, following the on-screen instructions, start MongoDB server: - -``` -parts start mongodb -``` - -Now, MongoDB server is listening to a dedicated port (by default 27017), waiting for clients to connect. Later, PHP REST handler scriptss are going to be such clients. For now, we start with command-line usage, where the MongoDB command interpreter acts as a client. - -## 2 Data model - -Before starting to use MongoDB, let us get acquainted with its data model. - -The data model in MongoDB consists of documents, collections and databases: -- Entities to be stored are called **documents**. MongoDB documents are stored in BSON format, which is a binary representation of JSON. Consequently, MongoDB is ideal for storing JSON objects. The transfer to BSON format (and vice versa) is done automatically. -- A **collection** is a set of documents. -- A **database** contains related collections. - -Contrasting these to relational database terms, a document roughly corresponds to a row whereas a collection is a MongoDB equivalent for a table. The concept of database is similar in both data models. - -## 3 Command line usage - -Type `mongo` to start MongoDB command line tool. The command line tool acts as a MongoDB client and can be used to initiate queries and other operations to a MongoDB database. - -Command line usage is a good approach for learning MongoDB's features and functionality. Slightly later, you will submit your queries from a PHP program instead. - -In MongoDB client, type `help` to get the list of available commands. - -Create a new database called company by entering: -``` -use company -``` - -Then add a new person into a new collection in the newly-created database. Notice the JSON format for the person's data: -``` -db.persons.insert({"id": 1, "first name": "Mary", "last name": "Smith", "salary": 5000}). -``` -Add the following persons into the collection: -- 2, Ahmed Bakir, 5000, -- 3 Viivi Virtanen, 7200, -- 4, John Smith, 2900. - -Now write a query to fetch all persons: -``` -db.persons.find() -``` - -Apply a formatting function for more readable output: -``` -db.persons.find().pretty() -``` - -Notice the object id given by MongoDB to each document. - -Now, add a selection criterion in the query and pick just those persons whose last name is Smith: -``` -db.persons.find({"last name" : "Smith"}).pretty() -``` - -Next, get used to the documents and tutorials: -- http://www.tutorialspoint.com/mongodb/ -- http://docs.mongodb.org/manual/reference/ - -### Test your understanding -Use MongoDB reference manual as well as other sources, and perform the following operations. Verify the outcome of each operation. - -1. Update the salary of person number 2 to 6000 euros. -2. Add everybody's salary by 5% (with a single operation). -3. Drop person number 4. - -## 4 Programmatic use - -The next step is to use MongoDB programmatically from a PHP program. As you may guess by now, the correspondence between REST API handlers and database operations is often straightforward: for example, a handler for HTTP POST operation usually generates an **insert()** call to MongoDB. - -From the previous lab, you should have a REST API enabled Codeigniter project. If you don't, create one at this step. Make sure that Apache web server is running. - -Now, install PHP drivers for MongoDB in a terminal window: -``` -parts install php5-mongo -``` -After the driver installation, restart Apache server; otherwise the drivers may not be visible to the web server: -``` -parts restart apache2 -``` - -In the lab's **php** folder, there´s a file named **Persons.php**. This is a REST API handler for GET operation to resource **.../Persons/list** that should work together with the MongoDB database that you generated a while ago using command line. Recall at this step that the database **company** contains one collection named **persons** that should have a few persons inserted in it. The handler file simply returns as JSON that contains all the persons in the collection. - -Study the contents of **Persons.php**. As you see, it is fairly straightforward to perform MongoDB operations from inside PHP code. The database driver provides a collection of functions for communication with the database. - -Copy **Persons.php** to **controllers** folder in your Codeigniter/REST project. - -If your project is in working condition, you can simply send the GET request from Postman or browser to the following URI: -``` -https://chrome-coral.codio.io:9500/staff/index.php/Persons/list/ -``` -In the URI, replace **chrome-coral** with your own Codio box name, and, instead of **staff**, write the name of your API. It appears as the name of the folder right under the project folder in the hierarchy (see image below). - -![(You see the image embedded here if you open this assignment sheet in a separate browser window).](img/ci_files.png) - -As a reply to your request, you should get a JSON containing the persons. Examine the structure of the received JSON. - -### Test your understanding - -1. Create another REST API handler for your project. The handler should insert a new person into the MongoDB database. The data for the new person can be sent as URI parameters or as a JSON in the request's body (choose either). -