├── .gitignore ├── img ├── celebration-wwe-yes.gif ├── markdown-html-comparison.png ├── markdown-methods-lesson01.png ├── udacity-google-complete.png └── udacity-google-scholarship.png ├── es6 ├── img │ ├── udacity-google-06-0201.png │ ├── udacity-google-09-0202-ie-error.png │ ├── udacity-google-09-1401-summary.png │ ├── udacity-google-09-0201-safari-error.png │ ├── udacity-google-09-1101-babel-es6-to-es5.png │ ├── udacity-google-09-1201-npm-dependencies.png │ ├── udacity-google-09-1102-es6-code-in-project.png │ └── udacity-google-09-1103-es6-preset-in-project.png ├── es6-4-polyfills-transpiling.md └── es6-1-syntax.md ├── offline-web-apps ├── img │ ├── udacity-google-04-0101.png │ ├── udacity-google-04-0501.png │ ├── udacity-google-04-0901.png │ ├── udacity-google-05-0101.png │ └── udacity-google-05-0101-trophy.png ├── offline-1-benefits.md ├── offline-3-indexeddb.md └── offline-2-sw.md ├── udacity-google-01.md ├── LICENSE.md ├── udacity-google-00-apply.md ├── README.md ├── udacity-google-10.md ├── udacity-google-story.md └── markdown-guide.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | misc 3 | *.code-workspace 4 | *.sublime-workspace 5 | *.sublime-project -------------------------------------------------------------------------------- /img/celebration-wwe-yes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/br3ndonland/udacity-google/HEAD/img/celebration-wwe-yes.gif -------------------------------------------------------------------------------- /img/markdown-html-comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/br3ndonland/udacity-google/HEAD/img/markdown-html-comparison.png -------------------------------------------------------------------------------- /img/markdown-methods-lesson01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/br3ndonland/udacity-google/HEAD/img/markdown-methods-lesson01.png -------------------------------------------------------------------------------- /img/udacity-google-complete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/br3ndonland/udacity-google/HEAD/img/udacity-google-complete.png -------------------------------------------------------------------------------- /es6/img/udacity-google-06-0201.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/br3ndonland/udacity-google/HEAD/es6/img/udacity-google-06-0201.png -------------------------------------------------------------------------------- /img/udacity-google-scholarship.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/br3ndonland/udacity-google/HEAD/img/udacity-google-scholarship.png -------------------------------------------------------------------------------- /es6/img/udacity-google-09-0202-ie-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/br3ndonland/udacity-google/HEAD/es6/img/udacity-google-09-0202-ie-error.png -------------------------------------------------------------------------------- /es6/img/udacity-google-09-1401-summary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/br3ndonland/udacity-google/HEAD/es6/img/udacity-google-09-1401-summary.png -------------------------------------------------------------------------------- /es6/img/udacity-google-09-0201-safari-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/br3ndonland/udacity-google/HEAD/es6/img/udacity-google-09-0201-safari-error.png -------------------------------------------------------------------------------- /offline-web-apps/img/udacity-google-04-0101.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/br3ndonland/udacity-google/HEAD/offline-web-apps/img/udacity-google-04-0101.png -------------------------------------------------------------------------------- /offline-web-apps/img/udacity-google-04-0501.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/br3ndonland/udacity-google/HEAD/offline-web-apps/img/udacity-google-04-0501.png -------------------------------------------------------------------------------- /offline-web-apps/img/udacity-google-04-0901.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/br3ndonland/udacity-google/HEAD/offline-web-apps/img/udacity-google-04-0901.png -------------------------------------------------------------------------------- /offline-web-apps/img/udacity-google-05-0101.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/br3ndonland/udacity-google/HEAD/offline-web-apps/img/udacity-google-05-0101.png -------------------------------------------------------------------------------- /es6/img/udacity-google-09-1101-babel-es6-to-es5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/br3ndonland/udacity-google/HEAD/es6/img/udacity-google-09-1101-babel-es6-to-es5.png -------------------------------------------------------------------------------- /es6/img/udacity-google-09-1201-npm-dependencies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/br3ndonland/udacity-google/HEAD/es6/img/udacity-google-09-1201-npm-dependencies.png -------------------------------------------------------------------------------- /es6/img/udacity-google-09-1102-es6-code-in-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/br3ndonland/udacity-google/HEAD/es6/img/udacity-google-09-1102-es6-code-in-project.png -------------------------------------------------------------------------------- /offline-web-apps/img/udacity-google-05-0101-trophy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/br3ndonland/udacity-google/HEAD/offline-web-apps/img/udacity-google-05-0101-trophy.png -------------------------------------------------------------------------------- /es6/img/udacity-google-09-1103-es6-preset-in-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/br3ndonland/udacity-google/HEAD/es6/img/udacity-google-09-1103-es6-preset-in-project.png -------------------------------------------------------------------------------- /udacity-google-01.md: -------------------------------------------------------------------------------- 1 | # Lesson 01. Course intro 2 | 3 | Udacity Grow with Google Scholarship challenge course 4 | 5 | Intermediate Web Developer track 6 | 7 | Brendon Smith 8 | 9 | br3ndonland 10 | 11 | Chaitra Ramanathan and Nick Blumenthal, Udacity scholarship program managers 12 | 13 | - There were >100K applicants. 14 | - They selected 50K "passionate, motivated, and resilient scholars" for all four tracks in the program. The intermediate web developer section has 10K people. 15 | - Three months to complete the challenge course (ends on April 11, 2018). 16 | - Six month follow-up [Mobile Web Specialist Nanodegree program](https://www.udacity.com/course/mobile-web-specialist-nanodegree--nd024) awarded based on "progress and performance in this initial three month challenge course, as well as your contributions to the student community." Participation is important. Use forum and Slack. 17 | 18 | [next lesson](udacity-google-02.md) 19 | 20 | [(Back to top)](#top) 21 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Brendon Smith 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /offline-web-apps/offline-1-benefits.md: -------------------------------------------------------------------------------- 1 | # The benefits of offline first 2 | 3 | 4 | Udacity logo 5 | 6 | 7 | [Offline Web Applications by Google course](https://www.udacity.com/course/offline-web-applications--ud899) lesson 1/3 8 | 9 | Udacity Google Mobile Web Specialist Nanodegree program part 2 lesson 16 10 | 11 | Udacity Grow with Google Scholarship challenge course lesson 02 12 | 13 | Brendon Smith 14 | 15 | br3ndonland 16 | 17 | ## Table of Contents 18 | 19 | - [Lesson](#lesson) 20 | - [1.01. Intro](#101-intro) 21 | - [1.02. The Problem](#102-the-problem) 22 | - [1.03. The Benefits of Offline First](#103-the-benefits-of-offline-first) 23 | - [1.04. Quiz: What Can Slow Us Down](#104-quiz-what-can-slow-us-down) 24 | - [1.05. Quiz: What Does Online First Look Like](#105-quiz-what-does-online-first-look-like) 25 | - [1.06. Quiz: What Are Ways To Be Offline First](#106-quiz-what-are-ways-to-be-offline-first) 26 | - [1.07. Introducing the Demo App](#107-introducing-the-demo-app) 27 | - [1.08. Quiz: Installing the Demo App](#108-quiz-installing-the-demo-app) 28 | - [1.09. Quiz: Running the Demo App](#109-quiz-running-the-demo-app) 29 | - [1.10. Exploring the Demo Apps Code](#110-exploring-the-demo-apps-code) 30 | - [1.11. Quiz: Changing Connection Types](#111-quiz-changing-connection-types) 31 | - [1.12. Quiz: Testing Lie Fi Mode](#112-quiz-testing-lie-fi-mode) 32 | - [1.13. Introducing Service Worker](#113-introducing-service-worker) 33 | 34 | ## Lesson 35 | 36 | ### 1.01. Intro 37 | 38 | ### 1.02. The Problem 39 | 40 | ### 1.03. The Benefits of Offline First 41 | 42 | ### 1.04. Quiz: What Can Slow Us Down 43 | 44 | ### 1.05. Quiz: What Does Online First Look Like 45 | 46 | ### 1.06. Quiz: What Are Ways To Be Offline First 47 | 48 | ### 1.07. Introducing the Demo App 49 | 50 | ### 1.08. Quiz: Installing the Demo App 51 | 52 | - Node installed: 53 | 54 | ```shell 55 | $ node --version 56 | v8.7.0 57 | ``` 58 | 59 | - Clone and run app 60 | 61 | ```shell 62 | $ cd 63 | $ git clone https://github.com/jakearchibald/wittr 64 | $ npm install 65 | $ npm update 66 | $ npm run serve 67 | ``` 68 | 69 | - Navigate to http://localhost:8888/ for the app and http://localhost:8889/ for the settings. 70 | - I was able to successfully get the app running. 71 | - **The wittr app repo is ~230 MB and contains thousands of files. I noticed the files syncing through my Dropbox, and moved the repo out of Dropbox.** 72 | 73 | ### 1.09. Quiz: Running the Demo App 74 | 75 | ### 1.10. Exploring the Demo Apps Code 76 | 77 | - wittr makes an HTTP request via the browser's HTTP cache. 78 | - If no match in HTTP cache, continues on to internet server. 79 | - wittr also requests some JavaScript. This opens a **web socket**, a persistent connection that continually streams newer posts from the server. 80 | 81 | ### 1.11. Quiz: Changing Connection Types 82 | 83 | ### 1.12. Quiz: Testing Lie Fi Mode 84 | 85 | ### 1.13. Introducing Service Worker 86 | 87 | [Next lesson](offline-2-sw.md) 88 | 89 | [(Back to top)](#top) 90 | -------------------------------------------------------------------------------- /udacity-google-00-apply.md: -------------------------------------------------------------------------------- 1 | # Udacity Grow with Google scholarship application 2 | 3 | Brendon Smith 4 | 5 | br3ndonland 6 | 7 | ## Background 8 | 9 | I found out about the Grow with Google scholarship via Udacity's [Facebook](https://www.facebook.com/Udacity/posts/1250067568431912) and [Medium](https://medium.com/udacity/grow-with-google-50-000-new-scholarships-available-now-1aa0513430b6) posts, while on a bus to NYC for the [New York Coffee Festival](https://www.newyorkcoffeefestival.com/) on October 14, 2017. 10 | 11 | ## Application 12 | 13 | I applied through the [Udacity website](https://admissions.udacity.com/apply/grow-with-google-scholarships). 14 | 15 | ### Parts 16 | 17 | There were three parts to the application: 18 | 19 | 1. Background Information 20 | 2. Pre-requisite Knowledge 21 | 3. Your Goals 22 | 23 | ### Responses 24 | 25 | > **What is your primary purpose in taking this program?** 26 | 27 | I selected "Help move from academia to industry", but "Start a new career in this field" would also fit. 28 | 29 | > **What do you hope to accomplish through this program?** 30 | > 31 | > _Please comment on relevant personal and professional goals. Include information about professional achievements (e.g., projects) that demonstrate how you have worked towards your goal. Please answer using complete sentences and no more than 150 words._ 32 | 33 | In the short term, I plan to build responsive and powerful web apps. I have already begun building my skills in the Udacity Full Stack Web Developer Nanodegree program. I am proficient with Python, HTML5, CSS3, Markdown, Git, and GitHub, and am starting to learn JavaScript and build websites. I created a [portfolio website](https://br3ndonland.github.io/udacity/). The website is fully responsive, built with Bootstrap and Jekyll, and hosted with GitHub Pages. 34 | 35 | In the long term, I will use the skills I build with Udacity and Google to make a positive impact on science. While science is central to our existence as modern intelligent humans, my time as a scientist has made me realize that scientists lack the hardware and software we need to do our work effectively. After building skills in this program, I will be able to deliver improved technology tools to make science more efficient, reproducible, and sustainable. 36 | 37 | > Why do you deserve a scholarship? 38 | > 39 | > _Please include a detailed response between 100-250 words._ 40 | 41 | **I have a clear purpose.** 42 | 43 | During my two years in a large molecular biology lab at Harvard, I became frustrated by the lack of technology tools to do my work efficiently. I saw that we are facing a reproducibility crisis in science, in which the discoveries that we publish are not correct or relevant to humans. I also saw that many scientists are struggling to sustain work/life balance. 44 | 45 | We will never effectively address these issues without better technology tools to perform and document our work. After realizing this, I left my job at Harvard to dedicate myself to web development. My purpose is to build science technology tools for a more efficient, reproducible and sustainable research culture. 46 | 47 | **Web development engages the two major motivating outcomes in my life, focused personal growth and positive impact.** 48 | 49 | I am most happy when I am improving myself. Web developers use a large set of tools and skills. I have enjoyed building these skills so far, and appreciate how quickly I can make progress. My vision for improving science leads my personal growth to be focused and purposeful. 50 | 51 | Web development will enable me to have a positive impact on the world by building technology tools for science. Science is of central importance in our society, but scientists do not have the software or hardware to work effectively. I will be able to deliver improved technology tools that will allow science to become more efficient, reproducible, and sustainable. 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | Udacity Grow with Google scholarship award 4 | 5 | Udacity Grow with Google Scholarship challenge course 6 | 7 | Intermediate Web Developer track 8 | 9 | Brendon Smith 10 | 11 | [br3ndonland](https://github.com/br3ndonland) 12 | 13 | [![license](https://img.shields.io/badge/license-MIT-blue.svg?longCache=true&style=for-the-badge)](https://choosealicense.com/) 14 | 15 | ## Background 16 | 17 | This repository contains notes and resources from the [Udacity Grow with Google](https://www.udacity.com/grow-with-google) Scholarship challenge course, in the Intermediate Web Developer track. 18 | 19 | The [Grow with Google scholarship program](https://www.udacity.com/grow-with-google) is an initiative designed to help people make career changes into coding. I was accepted to the intermediate web developer track to learn techniques for building progressive web apps. There was a three month challenge round, after which the top participants move on to a more advanced Mobile Web Specialist program. 20 | 21 | Here's how it went down: 22 | 23 | - Found out about the Grow with Google scholarship via Udacity's [Facebook](https://www.facebook.com/Udacity/posts/1250067568431912) and [Medium](https://medium.com/udacity/grow-with-google-50-000-new-scholarships-available-now-1aa0513430b6) posts, while on a bus to NYC for the [New York Coffee Festival](https://www.newyorkcoffeefestival.com/) on October 14, 2017. 24 | - [Applied](https://github.com/br3ndonland/udacity-google/blob/master/udacity-google-00-apply.md) in December 2017. 25 | - Won the scholarship in January 2018! 26 | 27 | Social media message: 28 | 29 | > I won a web development scholarship from Udacity and Google! I was accepted to the intermediate web developer track. There is a three month challenge round. If I do well, I can win another scholarship to a more advanced Mobile Web Specialist program. 30 | > 31 | > #GoogleUdacityScholars #GrowWithGoogle 32 | 33 | - Started the challenge course materials February 21, 2018. 34 | - Completed the challenge course materials on March 7, 2018 (working on Udacity basically full time). The course taught us how to build progressive web apps and use the new features in JavaScript ES6. 35 | - Attended five local meetups. 36 | - Started building a collaborative open-source transportation app, [MBTAccess](https://github.com/growwithgooglema/mbtaccess), in April 2018. 37 | - **Ranked in the top 10% of 10,000 students in the intermediate web developer track.** Ranking was based on completing all course materials, as well as participation in the Slack workspace, discussion forum, and meetups. 38 | - **Won a full scholarship to the [Google Mobile Web Specialist Nanodegree program](https://www.udacity.com/course/mobile-web-specialist-nanodegree--nd024).** See my [udacity-google-mws](https://github.com/br3ndonland/udacity-google-mws) repo for materials from the Nanodegree program. 39 | 40 | ## Course materials 41 | 42 | Materials in this repository: 43 | 44 | - [es6](es6): JavaScript ES6 course 45 | - [es6-1-syntax](es6/es6-1-syntax.md): JavaScript ES6 Syntax 46 | - [es6-2-functions](es6/es6-2-functions.md): JavaScript ES6 Functions 47 | - [es6-3-built-ins](es6/es6-3-built-ins.md): JavaScript ES6 Built-Ins (Symbols, Sets, Proxies, Maps, Generators) 48 | - [es6-4-polyfills-transpiling](es6/es6-4-polyfills-transpiling.md): JavaScript ES6 Transpiling and Polyfills 49 | - [offline-web-apps](offline-web-apps): Offline web apps course 50 | - [offline-1-benefits](offline-web-apps/offline-1-benefits.md): Intro to offline-first web apps 51 | - [offline-2-sw](offline-web-apps/offline-2-sw.md): Overview of Service Worker for offline-first web apps 52 | - [offline-3-indexeddb](offline-web-apps/offline-3-indexeddb.md): IndexedDB and Caching for offline-first web apps 53 | - [markdown-guide.md](markdown-guide.md): Guide to writing with Markdown 54 | - [udacity-google-00-apply.md](udacity-google-00-apply.md): Program application 55 | - [udacity-google-01.md](udacity-google-01.md): Course intro 56 | - [udacity-google-10.md](udacity-google-10.md): Program completion and feedback 57 | - [udacity-google-story.md](udacity-google-story.md): Developer story detailing my experience in the program 58 | -------------------------------------------------------------------------------- /udacity-google-10.md: -------------------------------------------------------------------------------- 1 | # Lesson 10. Challenge Course Wrap-Up 2 | 3 | Udacity Grow with Google Scholarship challenge course 4 | 5 | Intermediate Web Developer track 6 | 7 | Brendon Smith 8 | 9 | br3ndonland 10 | 11 | ## Table of Contents 12 | 13 | - [Table of Contents](#table-of-contents) 14 | - [I did it](#i-did-it) 15 | - [Great work](#great-work) 16 | - [Program feedback](#program-feedback) 17 | - [Program suggestions](#program-suggestions) 18 | 19 | ## I did it 20 | 21 | I did it! I got through all the material for the challenge course! I will find out the results mid-April. 22 | 23 | Screenshot showing course completion 24 | 25 | GIF of WWE wrestler fist pumping 26 | 27 | ## Great work 28 | 29 | Message from Udacity: 30 | 31 | > Congrats on completing the Mobile Web Specialist challenge course! 32 | > 33 | > It's been an awesome journey and we hope you've enjoyed the program thus far. 34 | > 35 | > The fun doesn't stop here. We will be evaluating your course progress and engagement in the student community to determine the recipients of Nanodegree scholarships for the second phase of this program. 36 | > 37 | > Stay tuned for the announcement of round two scholarship recipients on April 17! 38 | 39 | ## Program feedback 40 | 41 | I was really pumped when I got this scholarship. And even more pumped when I started the lessons. 42 | 43 | **I enjoyed the introduction to progressive web apps (PWAs)**. I think about PWAs every time I look at my phone. I regularly prune my app collection to remove anything I don't use, but my Google Pixel XL still currently has >150 apps. That seems like too many. I think we are over-dependent on native apps. Native apps have their place, but have reduced the openness of the web and created clutter on our mobile devices. The future seems like it's going to be in blurring the line between websites and apps. I'm excited to be surfing the next wave of PWAs! 44 | 45 | The **timing** was challenging. When the Grow with Google challenge course began, I had been working through the Udacity Full Stack Web Developer Nanodegree program (FSND). I was a a little bit stressed about the time limit on the challenge course, and about how to budget my time between the two programs. However, with the help of my Udacity FSND mentor, I came up with an effective approach. I worked up to a good pause point in the FSND, then worked through the Grow with Google materials, finishing over a month ahead of the deadline, and then got back to the FSND. 46 | 47 | Another challenging aspect for me was the **participation**. Social media can be distracting. It took me a little while to warm up to the forums and Slack, but I was glad I did. Two positive outcomes of the social networking were **meetups** and **pomodoros**. I got to meet up with some fellow Udacious humans in Boston. I also learned about the pomodoro technique. It's like interval training for working, and really helped me get through the course materials efficiently. My participation not only helped me, but the other students as well. I took detailed notes on the course materials and shared them on [GitHub](https://github.com/br3ndonland/udacity-google). Other students appreciated the organized, thorough notes. I also put together a [guide to writing with Markdown](markdown-guide.md) to help other students maximize their documentation. 48 | 49 | Thanks Udacity and Google! 50 | 51 | ## Program suggestions 52 | 53 | - **Present the ES6 lessons first.** This would especially help those of us new to JavaScript. I heard some students did the ES6 syntax lessons first and found it helpful. 54 | - **Update the wittr code for ES6.** This would make the codebase for the course more cohesive, allowing us to apply what we learn in the ES6 syntax lessons. It is helpful to include Babel transpiling for browser compatibility, but for the students it would be more helpful to always be looking at ES6 throughout the course. 55 | - **Explain the wittr stack more clearly.** There was a lot of code running under the hood. The instructor Jake did a good job explaining how the app connects to the internet (caching, sockets, etc), but it would have been great to have a more detailed walk-through of the code used to actually build the app. 56 | - It would also be great to have a **project** as a more technical assessment of our performance, but I understand that it would be difficult to evaluate 10K projects. 57 | 58 | I shared this in the Udacity discussion forum, and many people agreed. 59 | 60 | [(Back to TOC)](#table-of-contents) 61 | -------------------------------------------------------------------------------- /udacity-google-story.md: -------------------------------------------------------------------------------- 1 | # Student stories challenge 2 | 3 | ## Table of Contents 4 | 5 | - [Table of Contents](#table-of-contents) 6 | - [Personal story](#personal-story) 7 | - [1. Circumstances that brought you to Udacity](#1-circumstances-that-brought-you-to-udacity) 8 | - [2. Reasons for applying](#2-reasons-for-applying) 9 | - [3. Ways in which this Scholarship Challenge helped so far in your life](#3-ways-in-which-this-scholarship-challenge-helped-so-far-in-your-life) 10 | - [Tips for fitting study time/project time into your daily schedule](#tips-for-fitting-study-timeproject-time-into-your-daily-schedule) 11 | - [Result](#result) 12 | 13 | ## Personal story 14 | 15 | ### 1. Circumstances that brought you to Udacity 16 | 17 | During my two years in a large molecular biology lab at Harvard, I became frustrated by the lack of technology tools to do my work efficiently. I saw that we are facing a reproducibility crisis in science, in which the discoveries that we publish are not correct or relevant to humans. I also saw that many scientists are struggling to sustain work/life balance. 18 | 19 | We will never effectively address these issues without better technology tools to perform and document our work. After realizing this, I left my job at Harvard to dedicate myself to web development. My purpose is to build science technology tools for a more efficient, reproducible and sustainable research culture. 20 | 21 | ### 2. Reasons for applying 22 | 23 | I wanted to expand my training into progressive web apps (PWAs). I think about PWAs every time I look at my phone. I regularly prune my app collection to remove anything I don't use, but my Google Pixel XL still currently has >150 apps. That seems like too many. I think we are over-dependent on native apps. Native apps have their place, but have reduced the openness of the web and created clutter on our mobile devices. The future seems like it's going to be in blurring the line between websites and apps. I'm excited to be surfing the next wave of PWAs! 24 | 25 | ### 3. Ways in which this Scholarship Challenge helped so far in your life 26 | 27 | I was really pumped when I got this scholarship. And even more pumped when I started the lessons. I enjoyed the introduction to progressive web apps (PWAs) in the lessons, and found that the program improved my life! 28 | 29 | I discovered that **web development engages the two major motivating outcomes in my life, focused personal growth and positive impact.** 30 | 31 | I am most happy when I am improving myself. Web developers use a large set of tools and skills. I have enjoyed building these skills so far, and appreciate how quickly I can make progress. My vision for improving science leads my personal growth to be focused and purposeful. 32 | 33 | Web development will enable me to have a positive impact on the world by building technology tools for science. Science is of central importance in our society, but scientists do not have the software or hardware to work effectively. I will be able to deliver improved technology tools that will allow science to become more efficient, reproducible, and sustainable. 34 | 35 | **The participation was challenging for me, but ended up really enriching my experience.** Social media can be distracting. It took me a little while to warm up to the forums and Slack, but I was glad I did. Two positive outcomes of the social networking were **meetups** and **pomodoros**. I got to meet up with some fellow Udacious humans in Boston. I also learned about the pomodoro technique. It's like interval training for working, and really helped me get through the course materials efficiently. My participation not only helped me, but the other students as well. I took detailed notes on the course materials and shared them on [GitHub](https://github.com/br3ndonland/udacity-google). Other students appreciated the organized, thorough notes. I also put together a guide to writing with Markdown to help other students maximize their documentation. 36 | 37 | Thanks Udacity and Google! 38 | 39 | ## Tips for fitting study time/project time into your daily schedule 40 | 41 | Pomodoro intervals were really helpful for watching lessons. I found that each lesson took about one 25 minute pomodoro. After the 25 minutes, I would reflect on how well I used my time, and how I want to spend the next 25 minutes. Pomodoros helped me stay efficient and focused. 42 | 43 | ## Result 44 | 45 | One story winner was selected from each track of the program. 46 | 47 | Slack announcement: 48 | 49 | > _WAY TO GO, SCHOLARS!_ You exceeded our 1,000 story goal by submitting 1,193 of your amazing Student Stories to us! :man_dancing::dancer: 50 | > 51 | > And now, we'd like to introduce the five Mobile Web finalists: 52 | > 53 | > HUGE congrats to @jSick, @neuralnet, @derick_gross, @twip, and @Serenity :clap: 54 | > 55 | > Read their personal story and vote for your favorite in the doc below. You can only pick one! All votes need to be in by _tomorrow March 21st at 11:59 pm PT._ 56 | > 57 | > Thank you to everyone who submitted a story! It was so much fun getting to know you better. 58 | > 59 | > Mobile Web Finalist Stories [here](https://docs.google.com/presentation/d/1XItqm2vEREGrqLCBh1FMgG0fbhBJtFEMArvJYAW1kvQ/edit?usp=sharing) 60 | -------------------------------------------------------------------------------- /es6/es6-4-polyfills-transpiling.md: -------------------------------------------------------------------------------- 1 | # JavaScript Polyfills and Transpiling 2 | 3 | 4 | Udacity logo 5 | 6 | 7 | [ES6 - JavaScript Improved course](https://www.udacity.com/course/es6-javascript-improved--ud356) lesson 4/4 8 | 9 | Udacity Google Mobile Web Specialist Nanodegree program part 3 lesson 07 10 | 11 | Udacity Grow with Google Scholarship challenge course lesson 09 12 | 13 | Brendon Smith 14 | 15 | [br3ndonland](https://github.com/br3ndonland) 16 | 17 | ## Table of Contents 18 | 19 | - [Intro](#intro) 20 | - [4.01. The Web is Growing Up](#401-the-web-is-growing-up) 21 | - [4.02. Old and New Browsers](#402-old-and-new-browsers) 22 | - [4.03. ES6 Specification](#403-es6-specification) 23 | - [4.04. Supported Features](#404-supported-features) 24 | - [4.05. The Web is Eternal](#405-the-web-is-eternal) 25 | - [Polyfills](#polyfills) 26 | - [4.06. Polyfills](#406-polyfills) 27 | - [4.07. Using Polyfills](#407-using-polyfills) 28 | - [4.08. Polyfill Walkthrough](#408-polyfill-walkthrough) 29 | - [4.09. Other Uses for Polyfills](#409-other-uses-for-polyfills) 30 | - [Transpiling](#transpiling) 31 | - [4.10. Transpiling](#410-transpiling) 32 | - [4.11. Using Babel](#411-using-babel) 33 | - [4.12. Transpiling Walkthrough](#412-transpiling-walkthrough) 34 | - [4.13. Transpiling Recap](#413-transpiling-recap) 35 | - [4.14. Course Summary](#414-course-summary) 36 | - [Feedback on Lesson 9 (JavaScript ES6 lesson 4/4)](#feedback-on-lesson-9-javascript-es6-lesson-44) 37 | 38 | ## Intro 39 | 40 | **This lesson will show us how to write ES6 code, even when browsers haven't caught up to the latest features yet.** 41 | 42 | **ES6 is also referred to as ES2015.** 43 | 44 | ### 4.01. The Web is Growing Up 45 | 46 | ### 4.02. Old and New Browsers 47 | 48 | #### Code doesn't work in old browsers 49 | 50 | The code from the course throws errors in old browsers like Safari 9 and IE 11 51 | 52 | ES6 Safari 9 error 53 | 54 | ES6 IE 11 error 55 | 56 | > It makes sense that code doesn't work in older browsers that were developed prior to the release of ES6, but there are some browsers that have been released after ES6 that don't support the new JavaScript syntax and functionality yet. 57 | > 58 | > Most of us don't think much about the browser and all it can do...until it doesn't work! But really, browser makers have a tough time. Think about HTML, CSS, and JavaScript - these languages are fluid and are always improving. Browser makers have to keep up with all of these changes. 59 | 60 | #### But how do they know about these changes 61 | 62 | > They learn (or actually build) the language specifications! 63 | > 64 | > Just like the [World Wide Web Consortium (W3C)](https://www.w3.org/) is the standards body for things like HTML, CSS, and SVG, [Ecma International](https://www.ecma-international.org/) is an industry association that develops and oversees standards like JavaScript and JSON. You can find the specifications for ES6 [here](http://www.ecma-international.org/ecma-262/6.0/index.html). 65 | 66 | #### Further Info 67 | 68 | > Ecma International is an important industry community and definitely worth checking out in more detail: 69 | > 70 | > - [https://en.wikipedia.org/wiki/Ecma_International](https://en.wikipedia.org/wiki/Ecma_International) 71 | > - [http://www.ecma-international.org/memento/index.html](http://www.ecma-international.org/memento/index.html) 72 | > 73 | > **NOTE:** The code we've been looking at in this course is not supported by older browsers. Older browsers that were developed prior to the release of ES6 were developed to support the version of JavaScript at the time (which was ES5.1). If you try running any ES6 code in an older browser, it won't work. 74 | 75 | ### 4.03. ES6 Specification 76 | 77 | #### Intro to ES6 Specification 78 | 79 | > The specification (commonly shortened to "spec") for ES6 can be found [here](http://www.ecma-international.org/ecma-262/6.0/index.html). The spec lists the set of rules and guidelines on _how_ the language is supposed to function. It doesn't give specific details on how browser makers are supposed to achieve functionality, but it does provide step-by-step instructions on how the language is supposed to work. While making this course, we repeatedly referred to this official spec. 80 | > 81 | > Ok, so honestly, it can be a little difficult to decipher some of the cryptic wording of the spec. But when you have a question about ES6, we recommend checking out info on the topic like that provided by the [Mozilla Developer Network](https://developer.mozilla.org/) and then also reviewing what the spec actually says. 82 | 83 | #### Quiz Question 84 | 85 | Check out the [ES6 Specification](http://www.ecma-international.org/ecma-262/6.0/index.html). Which section in the spec covers arrow functions? 86 | 87 | - section 6 88 | - section 10.3.2 89 | - section 14.2 90 | - section 18.3.29 91 | 92 |
93 | Solution 94 | 95 | section 14.2 96 | 97 |
98 | 99 | ### 4.04. Supported Features 100 | 101 | #### How Can You Know What Features Browsers Support 102 | 103 | > With new language specifications coming out every year and with browsers updating every other month, it can be quite challenging to know what browser supports which language features. Each browser maker (except for Safari) has a website that tracks its development status. Checkout the platform feature updates for each browser: 104 | > 105 | > - Google Chrome - [https://www.chromestatus.com/features#ES6](https://www.chromestatus.com/features#ES6) 106 | > - Microsoft Edge - [https://developer.microsoft.com/en-us/microsoft-edge/platform/status/?q=ES6](https://developer.microsoft.com/en-us/microsoft-edge/platform/status/?q=ES6) 107 | > - Mozilla Firefox - [https://platform-status.mozilla.org/](https://platform-status.mozilla.org/) 108 | > 109 | > **NOTE:** Safari doesn't have its own platform status website. Under the hood, though, Safari is powered by the open source browser engine, Webkit. The status for Webkit features can be found [here](https://webkit.org/status/). 110 | > 111 | > This can be a lot of information to track down. If you prefer a birdseye view of all the feature support for all JavaScript code, check out 112 | > 113 | > - [https://caniuse.com/#search=arrow](https://caniuse.com/#search=arrow) 114 | > 115 | > You can also use the ECMAScript Compatibility Table built by [@kangax](https://twitter.com/kangax): 116 | > 117 | > - [http://kangax.github.io/compat-table/es6/](http://kangax.github.io/compat-table/es6/) 118 | 119 | #### 4.04 Quiz Question 120 | 121 | Looking at the ECMAScript Compatibility Table, what kind of information does the first _colored_ column display? 122 | 123 | - The list of up-to-date browsers that support ES6. 124 | - The list of all ES6 features. 125 | - The status of all ES6 features supported by your current browser. 126 | - Links to each browser platform's status for the specific ES6 feature. 127 | 128 |
129 | Solution 130 | 131 | The status of all ES6 features supported by your current browser. 132 | 133 | > The very first column lists all of the ES6 features. The second column in the table is the first one that's colored and displays the support of each ES6 feature in your the current browser. 134 | 135 |
136 | 137 | ### 4.05. The Web is Eternal 138 | 139 | As developers, we need to always be learning and adapting as the web does. 140 | 141 | ## Polyfills 142 | 143 | ### 4.06. Polyfills 144 | 145 | Richard and James use the analogy of filling a hole in the wall with spackling (UK brand name is Polyfilla). 146 | 147 | In JavaScript, a **polyfill** is: 148 | 149 | > a JavaScript file that patches a hole by replicating some native feature that's missing. 150 | 151 | ### 4.07. Using Polyfills 152 | 153 | #### What is a polyfill 154 | 155 | > A polyfill, or polyfiller, is a piece of code (or plugin) that provides the technology that you, the developer, expect the browser to provide natively. 156 | 157 | Coined by Remy Sharp - [https://remysharp.com/2010/10/08/what-is-a-polyfill](https://remysharp.com/2010/10/08/what-is-a-polyfill) 158 | 159 | > We, as developers, should be able to develop with the HTML5 APIs, and scripts can create the methods and objects that should exist. Developing in this future-proof way means as users upgrade, your code doesn't have to change but users will move to the better, native experience cleanly. From the HTML5 Boilerplate team on polyfills - [https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills](https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills) 160 | 161 | [Further info](https://en.wikipedia.org/wiki/Polyfill) 162 | 163 | #### An example polyfill 164 | 165 | The code below is a polyfill for the new ES6 String method, startsWith(): 166 | 167 | ```js 168 | if (!String.prototype.startsWith) { 169 | String.prototype.startsWith = function(searchString, position) { 170 | position = position || 0 171 | return this.substr(position, searchString.length) === searchString 172 | } 173 | } 174 | ``` 175 | 176 | As you can see, a polyfill is just regular JavaScript. 177 | 178 | This code is a simple polyfill (check it out on MDN), but there's also a significantly more robust one, [here](https://github.com/mathiasbynens/String.prototype.startsWith/blob/master/startswith.js) 179 | 180 | #### 4.07 Quiz Question 181 | 182 | Why does the `startsWith()` polyfill begin with the following line?: 183 | 184 | ```js 185 | if (!String.prototype.startsWith) 186 | ``` 187 | 188 | - Without it, the script would throw an error. 189 | - It checks to make sure the `String.prototype` exists. 190 | - It avoids overwriting the native `startsWith` method. 191 | 192 |
193 | Solution 194 | 195 | It avoids overwriting the native `startsWith` method. 196 | 197 | I thought it would just check for `startsWith`, but didn't realize that overwriting would be a concern. 198 | 199 | > Remember that a polyfill is used to patch missing functionality. If the browser supports ES6 and has the native startsWith method, then there's no reason to polyfill it. If this check didn't exist, then this polyfill would overwrite the native implementation. 200 | 201 |
202 | 203 | ### 4.08. Polyfill Walkthrough 204 | 205 | > Remember that a polyfill is used to fill a hole in a browser that doesn't yet support the native feature. 206 | > 207 | > This polyfill starts with a check to see if the native `startsWith` method actually exists. If it does exist then we don't want to override the native version with this one. If it doesn't exist then the browser will then run the code following. 208 | > 209 | > ```js 210 | > if (!String.prototype.startsWith) { 211 | > String.prototype.startsWith = function(searchString, position) { 212 | > position = position || 0 213 | > return this.substr(position, searchString.length) === searchString 214 | > } 215 | > } 216 | > 217 | > ;/- Sample usage */ 218 | > "Udacity".startsWith("Udac") // returns `true` 219 | > "Udacity".startsWith("Udac", 2) // returns `false` 220 | > "Udacity".startsWith("ES6") // returns `false` 221 | > ``` 222 | > 223 | > This adds a new method to String's prototype object. The function defaults to the position indicated by this second argument that's passed in or it'll be the first character of the string. 224 | > 225 | > Then it returns `true` or `false` if the string that's passed in is the same as the string that we're looking at. 226 | 227 | ### 4.09. Other Uses for Polyfills 228 | 229 | #### Polyfills aren't only for patching missing JavaScript features 230 | 231 | > JavaScript is the language used to create a polyfill, but a polyfill doesn't just patch up missing JavaScript features! There are polyfills for all sorts of browser features: 232 | > 233 | > - SVG 234 | > - Canvas 235 | > - Web Storage (local storage / session storage) 236 | > - Video 237 | > - HTML5 elements 238 | > - Accessibility 239 | > - Web Sockets 240 | > - and many more! 241 | > 242 | > For a more-complete list of polyfills, check out [this link](https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills) 243 | 244 | ## Transpiling 245 | 246 | ### 4.10. Transpiling 247 | 248 | - **Compiling:** source code -> machine code. Reduces abstraction. 249 | - **Transpiling:** source code -> another source code at the same level of abstraction. 250 | 251 | Quiz 252 | 253 | To convert Java to JavaScript, would you use a compiler or a transpiler? 254 | 255 |
256 | Solution 257 | 258 | First try 259 | 260 | Transpiler 261 | 262 | > Since both the Java source code and the JavaScript target code are of the same level of abstraction (they're both human-readable), a transpiler would be used. 263 | 264 |
265 | 266 | ### 4.11. Using Babel 267 | 268 | #### Intro to Babel 269 | 270 | > The most popular JavaScript transpiler is called [Babel](https://babeljs.io/). 271 | > 272 | > Babel's original name was slightly more descriptive - 6to5. This was because, originally, Babel converted ES6 code to ES5 code. Now, Babel does a lot more. It'll convert ES6 to ES5, JSX to JavaScript, and Flow to JavaScript. 273 | > 274 | > Before we look at transpiling code on our computer, let's do a quick test by transpiling some ES6 code into ES5 code directly on the Babel website. Check out [Babel's REPL](http://babeljs.io/repl/#?babili=false&evaluate=true&lineWrap=false&presets=es2015)(Read-Eval-Print Loop) and paste the following code into the section on the left: 275 | 276 | ```js 277 | class Student { 278 | constructor(name, major) { 279 | this.name = name 280 | this.major = major 281 | } 282 | 283 | displayInfo() { 284 | console.log(`${this.name} is a ${this.major} student.`) 285 | } 286 | } 287 | 288 | const richard = new Student("Richard", "Music") 289 | const james = new Student("James", "Electrical Engineering") 290 | ``` 291 | 292 | Babel REPL 293 | 294 | #### Transpiling project in repo 295 | 296 | > If you check in the [repo for this project](https://github.com/udacity/course-es6/tree/master/lesson-4/walk-through-transpiling), inside the Lesson 4 directory is a little project that's all set up for transpiling ES6 code to ES5 code. There's an "ES6" directory that contains the ES6 code we'll be transpiling (using Babel) to ES5 code that will be able to run in every browser. 297 | > 298 | > The way Babel transforms code from one language to another is through plugins. There are plugins that transform ES6 arrow functions to regular ES5 functions (the [ES2015 arrow function plugin](http://babeljs.io/docs/plugins/transform-es2015-arrow-functions/)). There are plugins that transform ES6 template literals to regular string concatenation (the [ES2015 template literals transform](http://babeljs.io/docs/plugins/transform-es2015-template-literals/)). For a full list, check out [all of Babel's plugins](http://babeljs.io/docs/plugins/). 299 | 300 | JavaScript ES6 code in project 301 | 302 | > Now, you're busy and you don't want to have to sift through a big long list of plugins to see which ones you need to convert your code from ES6 to ES5. So instead of having to use a bunch of individual plugins, Babel has **presets** which are groups of plugins bundled together. So instead of worrying about which plugins you need to install, we'll just use the [ES2015 preset](http://babeljs.io/docs/plugins/preset-es2015/) that is a collection of all the plugins we'll need to convert all of our ES6 code to ES5. 303 | > 304 | > You can see that the project has a `.babelrc` file. This is where you'd put all of the plugins and/or presets that the project will use. Since we want to convert all ES6 code, we've set it up so that it has the ES2015 preset. 305 | 306 | ```json 307 | { 308 | "presets": ["es2015"] 309 | } 310 | ``` 311 | 312 | JavaScript ES6 preset in project 313 | 314 | > **WARNING:** Babel uses both **Node** and **NPM** to distribute its plugins. So before you can install anything, make sure you have both of these tools installed: 315 | > 316 | > - install [Node](https://nodejs.org/) (which will automatically install NPM) 317 | 318 | ### 4.12. Transpiling Walkthrough 319 | 320 | **Note that Richard has selected the MIT license for his materials. I have correspondingly also selected the MIT license for my course repo.** 321 | 322 | > The project's \*package.json- file lists all of the NPM packages that this project depends on. 323 | > 324 | > This project depends on 325 | > 326 | > - `babel-cli` 327 | > - `babel-preset-es2015` 328 | > 329 | > The babel 2015 preset is a collection of all es6 plugins. So these are the plugins that will be downloaded and installed. 330 | > 331 | > Once they're installed we need to tell the Babel CLI which plugins it should use to do the transpiling. The CLI will check the \*.babelrc- file for which plugins and presets to use. 332 | > 333 | > So the _package.json- file lists what should be installed and the _.babelrc- file tells babel which plugins to use when it does its transpiling. 334 | > 335 | > Now that babel knows to use this preset we need to tell it to actually transpile the code. To do that we've added a build script that will tell babel to take the files in the ES6 directory, transpile them using the es2015 preset, and then put the transformed code in the ES5 directory. 336 | 337 | NPM dependencies of project 338 | 339 | ### 4.13. Transpiling Recap 340 | 341 | > NOTE: As of the creation of this course (circa Winter 2016), most of ES6 is supported by the current set of browsers. But that's "most", not "all", unfortunately. And that's also referring to "current" browsers. There are plenty of older browsers that do not support many, if any, of the new ES6 additions. However, it is safe to say that pretty much every browser supports the previous version of the language (ES5.1). 342 | > 343 | > It's important to stay on top of all the changes JavaScript is going through. The best way to do that is to start making use of the new features that are added. The problem is that not all browsers support these new features. 344 | > 345 | > So, to have your cake and eat it too, you can write in ES6 and then use a transpiler to convert it to ES5 code. This lets you transform your project's code base to the newest version of the language while still letting it run everywhere. Then, once all of the browsers your app has to run on fully support ES6 code, you can stop transpiling your code and just serve the straight ES6 code, directly! 346 | 347 | ### 4.14. Course Summary 348 | 349 | Course Summary 350 | 351 | ## Feedback on Lesson 9 (JavaScript ES6 lesson 4/4) 352 | 353 | Informative and helpful lesson. 354 | 355 | [Previous lesson](es6-3-built-ins.md) 356 | 357 | [(Back to top)](#top) 358 | -------------------------------------------------------------------------------- /offline-web-apps/offline-3-indexeddb.md: -------------------------------------------------------------------------------- 1 | # IndexedDB and Caching 2 | 3 | 4 | Udacity logo 5 | 6 | 7 | [Offline Web Applications by Google course](https://www.udacity.com/course/offline-web-applications--ud899) lesson 3/3 8 | 9 | Udacity Google Mobile Web Specialist Nanodegree program part 3 lesson 08 10 | 11 | Udacity Grow with Google Scholarship challenge course lesson 04 12 | 13 | Brendon Smith 14 | 15 | br3ndonland 16 | 17 | ## Table of Contents 18 | 19 | - [Intro](#intro) 20 | - [3.01. Introducing the IDB Promised Library](#301-introducing-the-idb-promised-library) 21 | - [3.02. Getting Started with IDB](#302-getting-started-with-idb) 22 | - [3.03. Quiz: Getting Started with IDB](#303-quiz-getting-started-with-idb) 23 | - [3.04. Quiz: More IDB](#304-quiz-more-idb) 24 | - [Caching](#caching) 25 | - [3.05. Using the IDB Cache and Display Entries](#305-using-the-idb-cache-and-display-entries) 26 | - [3.06. Quiz: Using IDB Cache](#306-quiz-using-idb-cache) 27 | - [3.07. Quiz: Using IDB 2](#307-quiz-using-idb-2) 28 | - [3.08. Quiz: Cleaning IDB](#308-quiz-cleaning-idb) 29 | - [Cache photos and avatars](#cache-photos-and-avatars) 30 | - [3.09. Cache Photos](#309-cache-photos) 31 | - [3.10. Quiz: Cache Photos Quiz](#310-quiz-cache-photos-quiz) 32 | - [3.11. Cleaning Photo Cache](#311-cleaning-photo-cache) 33 | - [3.12. Quiz: Cleaning Photo Cache Quiz](#312-quiz-cleaning-photo-cache-quiz) 34 | - [3.13. Quiz: Caching Avatars](#313-quiz-caching-avatars) 35 | - [3.14. Outro](#314-outro) 36 | - [Lesson feedback](#lesson-feedback) 37 | 38 | ## Intro 39 | 40 | ### 3.01. Introducing the IDB Promised Library 41 | 42 | - This will be a crash course in IndexedDB 43 | - The wittr app will benefit from a **database**. It will allow us to add and remove posts as needed. 44 | - **IndexedDB** allows us to create a **database** for the app. You will generally have one database per app, with multiple **object stores**. 45 | - The database object stores can contain JavaScript objects, strings, numbers, dates, or arrays. 46 | - Changes are made to the database with **transactions**. Transactions are atomic-if one part fails, none of the changes are applied. 47 | - **Indexes** (indices?) organize data by properties. 48 | 49 | Organizing IndexeDB with indexes 50 | 51 | - Why does IndexedDB have a bad reputation? 52 | - The API is "a little... horrid" and it can create spaghetti code. 53 | - It has its own event-based promise system (it predates promises) that can be confusing. 54 | - Jake believes in teaching the web platform rather than libraries, but in this case, we will use **[IndexedDB Promised (idb)](https://github.com/jakearchibald/idb)**, which Jake created. It mirrors the IndexedDB API and uses promises instead of events. 55 | 56 | ### 3.02. Getting Started with IDB 57 | 58 | - Navigate to [http://localhost:8888/idb-test](http://localhost:8888/idb-test) which is currently just a blank page with a script tag to import `idb`. 59 | - Jake walked through some of the code in `idb`. 60 | 61 | #### Create database 62 | 63 | - `createObjectStore`: Jake referred to [MDN](https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/createObjectStore). Jake creates an object store named 'keyval'. 64 | - `IDBObjectStore.put()`: [MDN](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/put) 65 | 66 | ```javascript 67 | import idb from "idb" 68 | 69 | var dbPromise = idb.open("test-db", 1, function(upgradeDb) { 70 | var keyValStore = upgradeDb.createObjectStore("keyval") 71 | keyValStore.put("world", "hello") 72 | }) 73 | ``` 74 | 75 | #### Read from database 76 | 77 | ```javascript 78 | dbPromise 79 | .then(function(db) { 80 | var tx = db.transaction("keyval") 81 | var keyValStore = tx.objectStore("keyval") 82 | return keyValStore.get("hello") 83 | }) 84 | .then(function(val) { 85 | console.log('The value of "hello" is:', val) 86 | }) 87 | ``` 88 | 89 | #### Add another value to the object store 90 | 91 | ```javascript 92 | dbPromise 93 | .then(function(db) { 94 | var tx = db.transaction("keyval", "readwrite") 95 | var keyValStore = tx.objectStore("keyval") 96 | keyValStore.put("bar", "foo") 97 | return tx.complete 98 | }) 99 | .then(function() { 100 | console.log("Added foo:bar to keyval") 101 | }) 102 | .catch(function(error) { 103 | console.error("Transaction failed:", error) 104 | }) 105 | ``` 106 | 107 | ### 3.03. Quiz: Getting Started with IDB 108 | 109 | Git 110 | 111 | - Checkout to new branch: 112 | 113 | ```shell 114 | $ git reset --hard 115 | $ git checkout origin/page-skeleton 116 | ``` 117 | 118 |
119 | Solution 120 | 121 | Based on the code [above](#add-another-value-to-the-object-store): 122 | 123 | Code in _public/js/idb-test/index.js_ 124 | 125 | ```javascript 126 | dbPromise 127 | .then(function(db) { 128 | var tx = db.transaction("keyval", "readwrite") 129 | var keyValStore = tx.objectStore("keyval") 130 | keyValStore.put("sloth", "favoriteAnimal") 131 | return tx.complete 132 | }) 133 | .then(function() { 134 | console.log("Added favoriteAnimal:sloth to keyval") 135 | }) 136 | .catch(function(error) { 137 | console.error("Transaction failed:", error) 138 | }) 139 | ``` 140 | 141 | - Refresh the app at [http://localhost:8888/](http://localhost:8888/) 142 | - Test on settings page at [http://localhost:8889/](http://localhost:8889/) with "idb-animal". 143 | 144 |
145 | 146 | "If you're still alive at this point, you're doing really well." 147 | 148 | #### Diving deeper into the API 149 | 150 | The code above was not very practical, because you would have to change and re-run it every time you added an object. What if we wanted to add a bunch of values? 151 | 152 | ```javascript 153 | // upgrade database 154 | var dbPromise = idb.open("test-db", 2, function(upgradeDb) { 155 | switch (upgradeDb.oldVersion) { 156 | case 0: 157 | var keyValStore = upgradeDb.createObjectStore("keyval") 158 | keyValStore.put("world", "hello") 159 | case 1: 160 | upgradeDb.createObjectStore("people", { 161 | keyPath: "name" 162 | }) 163 | } 164 | }) 165 | 166 | // add data to object store 167 | // each person is a javascript object 168 | dbPromise 169 | .then(function(db) { 170 | var tx = db.transaction("people", "readwrite") 171 | var peopleStore = tx.objectStore("people") 172 | 173 | peopleStore.put({ 174 | name: "Sam Munoz", 175 | age: 25, 176 | favoriteAnimal: "dog" 177 | }) 178 | 179 | peopleStore.put({ 180 | name: "Susan Keller", 181 | age: 34, 182 | favoriteAnimal: "cat" 183 | }) 184 | 185 | peopleStore.put({ 186 | name: "Lillie Wolfe", 187 | age: 28, 188 | favoriteAnimal: "dog" 189 | }) 190 | 191 | peopleStore.put({ 192 | name: "Marc Stone", 193 | age: 39, 194 | favoriteAnimal: "cat" 195 | }) 196 | 197 | return tx.complete 198 | }) 199 | .then(function() { 200 | console.log("People added!") 201 | }) 202 | ``` 203 | 204 | ### 3.04. Quiz: More IDB 205 | 206 | Task: Create an index that sorts people by age. 207 | 208 | Git 209 | 210 | - Checkout to new branch: 211 | 212 | ```shell 213 | $ git reset --hard 214 | $ git checkout origin/task-idb-people 215 | ``` 216 | 217 |
218 | Solution 219 | 220 | Code in _public/js/idb-test/index.js_ 221 | 222 | ```javascript 223 | // upgrade database 224 | var dbPromise = idb.open("test-db", 4, function(upgradeDb) { 225 | switch (upgradeDb.oldVersion) { 226 | case 0: 227 | var keyValStore = upgradeDb.createObjectStore("keyval") 228 | keyValStore.put("world", "hello") 229 | case 1: 230 | upgradeDb.createObjectStore("people", { keyPath: "name" }) 231 | case 2: 232 | var peopleStore = upgradeDb.transaction.objectStore("people") 233 | peopleStore.createIndex("animal", "favoriteAnimal") 234 | case 3: 235 | peopleStore = upgradeDb.transaction.objectStore("people") 236 | peopleStore.createIndex("age", "age") 237 | } 238 | }) 239 | 240 | // sort the people objects 241 | dbPromise 242 | .then(function(db) { 243 | var tx = db.transaction("people") 244 | var peopleStore = tx.objectStore("people") 245 | var ageIndex = peopleStore.index("age") 246 | 247 | return ageIndex.getAll() 248 | }) 249 | .then(function(people) { 250 | console.log("People by age:", people) 251 | }) 252 | ``` 253 | 254 | - Test on settings page at [http://localhost:8889/](http://localhost:8889/) with "idb-age". 255 | - We can cycle through the people objects one at a time using **cursors**. I have used cursor objects in Python before (see my [SQL database analysis project](https://github.com/br3ndonland/udacity-fsnd03-p01-logs)). 256 | 257 | ```javascript 258 | dbPromise 259 | .then(function(db) { 260 | var tx = db.transaction("people") 261 | var peopleStore = tx.objectStore("people") 262 | var ageIndex = peopleStore.index("age") 263 | 264 | return ageIndex.openCursor() 265 | }) 266 | .then(function(cursor) { 267 | if (!cursor) return 268 | console.log("Cursored at:", cursor.value.name) 269 | return cursor.continue() 270 | }) 271 | ``` 272 | 273 | - We can store the cursor cycle as a function, a "neat trick": 274 | 275 | ```javascript 276 | dbPromise 277 | .then(function(db) { 278 | var tx = db.transaction("people") 279 | var peopleStore = tx.objectStore("people") 280 | var ageIndex = peopleStore.index("age") 281 | 282 | return ageIndex.openCursor() 283 | }) 284 | .then(function logPerson(cursor) { 285 | if (!cursor) return 286 | console.log("Cursored at:", cursor.value.name) 287 | // cursor.update(newValue) 288 | // cursor.delete() 289 | return cursor.continue().then(logPerson) 290 | }) 291 | .then(function() { 292 | console.log("Done cursoring") 293 | }) 294 | ``` 295 | 296 | - To restore the repo to this point in the future: 297 | 298 | ```shell 299 | $ git reset --hard 300 | $ git checkout origin/idb-cursoring 301 | ``` 302 | 303 |
304 | 305 | [(Back to TOC)](#table-of-contents) 306 | 307 | ## Caching 308 | 309 | ### 3.05. Using the IDB Cache and Display Entries 310 | 311 | Next, we will create a database for wittr posts. The posts will still arrive via a web socket,but can be served offline as well from `idb`. 312 | 313 | Using the IDB Cache and Display Entries 314 | 315 | - _public/js/main/IndexController.js_ 316 | - The `IndexController._openSocket` method is called to open the Web Socket. 317 | - `IndexController._openSocket` listens for the message event, and passes data to the `IndexController._onSocketMessage` method. 318 | - `IndexController._onSocketMessage` parses JSON data and passes it to `IndexController._postsView.addPosts`. 319 | 320 | ### 3.06. Quiz: Using IDB Cache 321 | 322 | Git 323 | 324 | - Checkout to new branch: 325 | 326 | ```shell 327 | $ git reset --hard 328 | $ git checkout origin/task-idb-store 329 | ``` 330 | 331 | Task 332 | 333 | > Your task is to return a Promise for a database called 'wittr' that has an object store called 'wittrs' that uses 'id' as its key and has an index called called 'by-date', which is sorted by the 'time' property. 334 | > 335 | > Once you've done that, you'll need to add messages to the database. Down in the IndexController.\_onSocketMessage method, the database has been fetched. Your task is to add each of the messages to the Wittr store. Note that we're not using the entries in the database yet - we'll do that in the next chapter. 336 | 337 | If the database gets messed up, run `indexedDB.deleteDatabase('wittr')` in DevTools to reset. 338 | 339 |
340 | Solution 341 | 342 | Code in _public/js/main/IndexController.js_ 343 | 344 | ```javascript 345 | // Open the database 346 | function openDatabase() { 347 | // If the browser doesn't support service worker, 348 | // we don't care about having a database 349 | if (!navigator.serviceWorker) { 350 | return Promise.resolve() 351 | } 352 | 353 | // return a promise for a database called 'wittr' 354 | // that contains one objectStore: 'wittrs' 355 | // that uses 'id' as its key 356 | // and has an index called 'by-date', which is sorted 357 | // by the 'time' property 358 | return idb.open("wittr", 1, function(upgradeDb) { 359 | var store = upgradeDb.createObjectStore("wittrs", { 360 | keyPath: "id" 361 | }) 362 | 363 | store.createIndex("by-date", "time") 364 | }) 365 | } 366 | 367 | // called when the web socket sends message data 368 | IndexController.prototype._onSocketMessage = function(data) { 369 | var messages = JSON.parse(data) 370 | 371 | this._dbPromise.then(function(db) { 372 | if (!db) return 373 | 374 | // loop through messages and add to store 375 | var tx = db.transaction("wittrs", "readwrite") 376 | var store = tx.objectStore("wittrs") 377 | 378 | messages.forEach(function(message) { 379 | store.put(message) 380 | }) 381 | 382 | return tx.complete 383 | }) 384 | 385 | this._postsView.addPosts(messages) 386 | } 387 | ``` 388 | 389 | - Refresh the app at [http://localhost:8888/](http://localhost:8888/) 390 | - Test on settings page at [http://localhost:8889/](http://localhost:8889/) with "idb-store". 391 | 392 |
393 | 394 | ### 3.07. Quiz: Using IDB 2 395 | 396 | We now have wittr posts in the database, but we need to serve posts from IDB wittrs before opening the web socket. 397 | 398 | Git 399 | 400 | - Checkout to new branch: 401 | 402 | ```shell 403 | $ git reset --hard 404 | $ git checkout origin/task-show-stored 405 | ``` 406 | 407 |
408 | Solution 409 | 410 | Code in _public/js/main/IndexController.js_ 411 | 412 | ```javascript 413 | IndexController.prototype._showCachedMessages = function() { 414 | var indexController = this 415 | 416 | return this._dbPromise.then(function(db) { 417 | // if we're already showing posts, eg shift-refresh 418 | // or the very first load, there's no point fetching 419 | // posts from IDB 420 | if (!db || indexController._postsView.showingPosts()) return 421 | 422 | // TODO: get all of the wittr message objects from indexeddb, 423 | // then pass them to: 424 | // indexController._postsView.addPosts(messages) 425 | // in order of date, starting with the latest. 426 | // Remember to return a promise that does all this, 427 | // so the websocket isn't opened until you're done! 428 | var index = db 429 | .transaction("wittrs") 430 | .objectStore("wittrs") 431 | .index("by-date") 432 | 433 | return index.getAll().then(function(messages) { 434 | indexController._postsView.addPosts(messages.reverse()) 435 | }) 436 | }) 437 | } 438 | ``` 439 | 440 | - Refresh the app at [http://localhost:8888/](http://localhost:8888/) 441 | - Test on settings page at [http://localhost:8889/](http://localhost:8889/) with "serve-skeleton". 442 | 443 |
444 | 445 | ### 3.08. Quiz: Cleaning IDB 446 | 447 | So far, so good, but we have only been adding posts to the database. We can't just keep adding posts indefinitely. 448 | 449 | In this task, we will modify the database so it only has 30 wittr items at a time. 450 | 451 | Git 452 | 453 | - Checkout to new branch: 454 | 455 | ```shell 456 | $ git reset --hard 457 | $ git checkout origin/task-clean-db 458 | ``` 459 | 460 |
461 | Solution 462 | 463 | _public/js/main/IndexController.js_, `IndexController._onSocketMessage` method 464 | 465 | ```javascript 466 | IndexController.prototype._onSocketMessage = function(data) { 467 | var messages = JSON.parse(data) 468 | 469 | this._dbPromise.then(function(db) { 470 | if (!db) return 471 | 472 | var store = db.transaction("wittrs", "readwrite").objectStore("wittrs") 473 | // create a variable to index the posts by date 474 | var index = store.index("by-date") 475 | 476 | messages.forEach(function(message) { 477 | store.put(message) 478 | }) 479 | 480 | // keep the newest 30 entries in 'wittrs', delete the rest. 481 | // 482 | // Hint: you can use .openCursor(null, 'prev') to open a cursor 483 | // that goes through an index/store backwards. 484 | return index 485 | .openCursor(null, "prev") 486 | .then(function(cursor) { 487 | return cursor.advance(30) 488 | }) 489 | .then(function deletePost(cursor) { 490 | // if the entry is undefined, stop 491 | if (!cursor) return 492 | cursor.delete() 493 | // otherwise continue looping through and deleting posts 494 | return cursor.continue().then(deletePost) 495 | }) 496 | }) 497 | 498 | this._postsView.addPosts(messages) 499 | } 500 | ``` 501 | 502 | - Refresh the app at [http://localhost:8888/](http://localhost:8888/) 503 | - Test on settings page at [http://localhost:8889/](http://localhost:8889/) with "idb-clean". 504 | 505 |
506 | 507 | #### Goals 508 | 509 | - ~~Unobtrusive app updates~~ 510 | - ~~Get the user onto the latest version~~ 511 | - ~~Continually update cache of posts~~ 512 | - Cache photos 513 | - Cache avatars 514 | 515 | #### App performance changes 516 | 517 | - Perfect: Not much change, but "perfect doesn't really exist." 518 | - Slow: Content renders much more quickly. 519 | - Lie-Fi: Users actually get content, instead of a blank screen. 520 | - Offline: Users still get content. 521 | - Images are still slow or broken, so we will fix those next. 522 | 523 | [(Back to TOC)](#table-of-contents) 524 | 525 | ## Cache photos and avatars 526 | 527 | ### 3.09. Cache Photos 528 | 529 | - We want to cache photos as they appear. 530 | - If we retrieve images from the cache API, it's more memory efficient and renders faster: 531 | 532 | Caching and serving photos from service worker 533 | 534 | - Images will be stored in a separate cache from the other static content. This allows the photos to live on between different versions of the app. 535 | - We will be working with a responsive image. It has different sizes based on the viewport width. 536 | - Using responses multiple times: `response.json();` cannot be re-read with `response.blob();`. Once the data are read in as json, it disappears from memory. This applies to `event.respondWith(response);` as well. This is a problem for our photos. We want to open a cache, fetch from the network, and send the response both to the cache and the browser. To fix this, we clone the response with `response.clone()`. A clone goes to the cache, and the original response gets sent to the page. 537 | 538 | ### 3.10. Quiz: Cache Photos Quiz 539 | 540 | Git 541 | 542 | - Checkout to new branch: 543 | 544 | ```shell 545 | $ git reset --hard 546 | $ git checkout origin/task-cache-photos 547 | ``` 548 | 549 | Code in _public/js/sw/index.js_ (the service worker script) 550 | 551 |
552 | Solution 553 | 554 | ```javascript 555 | function servePhoto(request) { 556 | // Photo urls look like: 557 | // /photos/9-8028-7527734776-e1d2bda28e-800px.jpg 558 | // But storageUrl has the -800px.jpg bit missing. 559 | // Use this url to store & match the image in the cache. 560 | // This means you only store one copy of each photo. 561 | var storageUrl = request.url.replace(/-\d+px\.jpg$/, "") 562 | 563 | // return images from the "wittr-content-imgs" cache 564 | // if they're in there. Otherwise, fetch the images from 565 | // the network, put them into the cache, and send it back 566 | // to the browser. 567 | // 568 | // HINT: cache.put supports a plain url as the first parameter 569 | return caches.open(contentImgsCache).then(function(cache) { 570 | return cache.match(storageUrl).then(function(response) { 571 | return ( 572 | response || 573 | fetch(request).then(function(networkResponse) { 574 | cache.put(storageUrl, networkResponse.clone()) 575 | return networkResponse 576 | }) 577 | ) 578 | }) 579 | }) 580 | } 581 | ``` 582 | 583 | - Refresh the app at [http://localhost:8888/](http://localhost:8888/) 584 | - Test on settings page at [http://localhost:8889/](http://localhost:8889/) with "cache-photos". 585 | 586 |
587 | 588 | ### 3.11. Cleaning Photo Cache 589 | 590 | As we saw in [4.08. Quiz: Cleaning IDB](#408-quiz-cleaning-idb), we eventually need to clear out the cache. We can't keep adding to it infinitely. 591 | 592 | If we want to remove specific entries from the cache, we can use `cache.delete`, passing in the URL or the request of the thing we want to delete: 593 | 594 | ```javascript 595 | cache.delete(request) 596 | ``` 597 | 598 | There's also a `cache.keys` method that returns a Promise providing all the requests for entries in the cache: 599 | 600 | ```javascript 601 | cache.keys().then(function(requests) { 602 | // ... 603 | }) 604 | ``` 605 | 606 | Next, we will clean the image cache. 607 | 608 | ### 3.12. Quiz: Cleaning Photo Cache Quiz 609 | 610 | Git 611 | 612 | - Checkout to new branch: 613 | 614 | ```shell 615 | $ git reset --hard 616 | $ git checkout origin/task-clean-photos 617 | ``` 618 | 619 | Implement the `IndexController._cleanImageCache` method in _public/js/main/IndexController.js_. 620 | 621 |
622 | Solution 623 | 624 | ```javascript 625 | IndexController.prototype._cleanImageCache = function() { 626 | return this._dbPromise.then(function(db) { 627 | if (!db) return 628 | 629 | // open the 'wittr' object store, get all the messages, 630 | // gather all the photo urls. 631 | // 632 | // Open the 'wittr-content-imgs' cache, and delete any entry 633 | // that you no longer need. 634 | // 635 | // create an array of images to keep 636 | var imagesNeeded = [] 637 | 638 | var tx = db.transaction("wittrs") 639 | // get object store and messages 640 | return tx 641 | .objectStore("wittrs") 642 | .getAll() 643 | .then(function(messages) { 644 | messages.forEach(function(message) { 645 | if (message.photo) { 646 | imagesNeeded.push(message.photo) 647 | } 648 | }) 649 | 650 | // open image cache and get stored image requests 651 | return caches.open("wittr-content-imgs") 652 | }) 653 | .then(function(cache) { 654 | return cache.keys().then(function(requests) { 655 | requests.forEach(function(request) { 656 | var url = new URL(request.url) 657 | 658 | if (!imagesNeeded.includes(url.pathname)) { 659 | cache.delete(request) 660 | } 661 | }) 662 | }) 663 | }) 664 | }) 665 | } 666 | ``` 667 | 668 | - Refresh the app at [http://localhost:8888/](http://localhost:8888/) 669 | - Test on settings page at [http://localhost:8889/](http://localhost:8889/) with "cache-clean". 670 | 671 |
672 | 673 | #### Goals after 3.12 674 | 675 | - ~~Unobtrusive app updates~~ 676 | - ~~Get the user onto the latest version~~ 677 | - ~~Continually update cache of posts~~ 678 | - ~~Cache photos~~ 679 | - Cache avatars 680 | 681 | ### 3.13. Quiz: Caching Avatars 682 | 683 | Final task! Caching avatars. Avatars are also responsive images, but they vary by density rather than width. The URL pattern is, correspondingly, a little different than for other images: 684 | 685 | ```html 686 | 692 | ``` 693 | 694 | Git 695 | 696 | - Checkout to new branch: 697 | 698 | ```shell 699 | $ git reset --hard 700 | $ git checkout origin/task-cache-avatars 701 | ``` 702 | 703 | Call and implement the `serveAvatar` function in _public/js/sw/index.js_ (the service worker script) 704 | 705 |
706 | Solution 707 | 708 | _Call the `serveAvatar` function from within the `fetch` event handler:_ 709 | 710 | ```javascript 711 | // respond to avatar urls by responding with 712 | // the return value of serveAvatar(event.request) 713 | if (requestUrl.pathname.startsWith("/avatars/")) { 714 | event.respondWith(serveAvatar(event.request)) 715 | return 716 | } 717 | ``` 718 | 719 | _Implement the `serveAvatar` function:_ 720 | 721 | ```javascript 722 | function serveAvatar(request) { 723 | // Avatar urls look like: 724 | // avatars/sam-2x.jpg 725 | // But storageUrl has the -2x.jpg bit missing. 726 | // Use this url to store & match the image in the cache. 727 | // This means you only store one copy of each avatar. 728 | var storageUrl = request.url.replace(/-\dx\.jpg$/, "") 729 | 730 | // return images from the "wittr-content-imgs" cache 731 | // if they're in there. But afterwards, go to the network 732 | // to update the entry in the cache. 733 | // 734 | // Note that this is slightly different from servePhoto! 735 | return caches.open(contentImgsCache).then(function(cache) { 736 | return cache.match(storageUrl).then(function(response) { 737 | var fetchPromise = fetch(request).then(function(networkResponse) { 738 | cache.put(storageUrl, networkResponse.clone()) 739 | return networkResponse 740 | }) 741 | 742 | return response || fetchPromise 743 | }) 744 | }) 745 | } 746 | ``` 747 | 748 | - Refresh the app at [http://localhost:8888/](http://localhost:8888/) 749 | - Test on settings page at [http://localhost:8889/](http://localhost:8889/) with "cache-avatars". 750 | 751 |
752 | 753 | ### 3.14. Outro 754 | 755 | #### Goals complete 756 | 757 | - ~~Unobtrusive app updates~~ 758 | - ~~Get the user onto the latest version~~ 759 | - ~~Continually update cache of posts~~ 760 | - ~~Cache photos~~ 761 | - ~~Cache avatars~~ 762 | 763 | **We're now completely offline first!** 764 | 765 | #### App performance changes complete 766 | 767 | - Perfect: Not much change, but "perfect doesn't really exist." 768 | - Slow: Content renders instantly. 769 | - Lie-Fi: Users get content instantly, instead of a blank screen. 770 | - Offline: Users get content, and a non-disruptive custom error, instead of error page. 771 | 772 | Offline web apps completion 773 | 774 | ## Lesson feedback 775 | 776 | Again, a very informative and helpful lesson. It would be nice to use Udacity's browser-based quiz system instead of all the git resets, but I understand that it would be difficult to demo the app without a local clone of the git repo. 777 | 778 | [Previous lesson](offline-2-sw.md) 779 | 780 | [(Back to top)](#top) 781 | -------------------------------------------------------------------------------- /markdown-guide.md: -------------------------------------------------------------------------------- 1 | # Markdown guide 2 | 3 | Brendon Smith ([br3ndonland](https://github.com/br3ndonland)) 4 | 5 | ## Table of Contents 6 | 7 | - [Markdown syntax](#markdown-syntax) 8 | - [Syntactic suggestions](#syntactic-suggestions) 9 | - [General Markdown resources](#general-markdown-resources) 10 | - [Markdown apps](#markdown-apps) 11 | - [Text editors](#text-editors) 12 | - [IDEs](#ides) 13 | - [Note apps](#note-apps) 14 | - [In-browser editors](#in-browser-editors) 15 | - [Social apps](#social-apps) 16 | 17 | **Markdown is a simplified HTML syntax.** It has most of the functionality of HTML while being much easier to read, and is very widely used (for example, READMEs on GitHub). 18 | 19 | Here's a comparison of the same code written in Markdown and HTML (using Sublime Text): 20 | 21 | Markdown and HTML comparison 22 | 23 | ## Markdown syntax 24 | 25 | ### Syntactic suggestions 26 | 27 | Suggestions for standardized Markdown formatting have been provided by [markdownlint](https://github.com/DavidAnson/markdownlint) and [Markdown Style Guide](http://www.cirosantilli.com/markdown-style-guide/). Here are a few personal pointers: 28 | 29 | #### File extensions 30 | 31 | - Several different extensions can be used, including .md, .mdown, and .markdown. 32 | - **I prefer to use .md for brevity and consistency.** 33 | 34 | #### Headers 35 | 36 | - Create headers with `#`. Each `#` increases header level (`##` is outline level two), up to six levels. 37 | - **For organization, I reserve H1 (`#`) for the title of the file at the top. Major headers begin with H2 (`##`).** 38 | - I use headers to create a **Table of Contents (TOC)** at the beginning of the file. 39 | - **I add `## Table of Contents` before the TOC for navigation.** I also include ``, which tells the VSCode Markdown All In One extension not to put the `## Table of Contents` header itself into the TOC. 40 | - **I include [(Back to top)](#top) links after each section for easy navigation back to the top of the page.** Simply write `[(Back to top)](#top)`. 41 | - I add and auto-update TOCs in vscode with the [Markdown All in One](https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one) extension. 42 | - [Markdown All in One](https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one), JupyterLab and RStudio provide inline TOC displays ([see below](#jupyterlab)). 43 | - Prior to vscode, I was adding and updating TOCs with [DocToc](https://github.com/thlorenz/doctoc) from the command line. 44 | 45 | #### Text 46 | 47 | - **Bold text:** use **double star at beginning and end of text to bold.** 48 | - _Italics:_ use _underscores or single stars with no space before and after._ 49 | - **I prefer to indent Markdown text with two spaces.** Four spaces can be read by some systems as code blocks. 50 | 51 | #### Lists 52 | 53 | - **Lists should be preceded by a blank line.** 54 | - Single `*`, `-`, or `+` at beginning of line, followed by tab or space. 55 | - Indent for next outline level 56 | - Like this 57 | 58 | 1. Ordered lists 59 | 2. Like this 60 | - And you can add in unordered lists within ordered lists like this. 61 | - Adding an unordered list within an ordered list requires two levels of indentation. 62 | 63 | #### Code 64 | 65 | You can include `inline code inside single backticks` 66 | 67 | ```text 68 | Fenced code blocks inside triple backticks 69 | ``` 70 | 71 | - Code blocks can be indented to match your lists 72 | 73 | ```text 74 | like this 75 | ``` 76 | 77 | - In GitHub-Flavored Markdown, you can specify the language next to the first set of triple backticks for syntax highlighting. Each language has a full name (like `python`), and an abbreviation (like `py`). The full list of supported languages can be found in [GitHub's Linguist repo](https://github.com/github/linguist), which is used to detect languages on GitHub. The _[languages.yml](https://github.com/github/linguist/blob/master/lib/linguist/languages.yml)_ contains a list of the available abbreviations (called "extensions" in the YAML) for each language. 78 | 79 | - Shell: `shell` or `sh` 80 | 81 | ```sh 82 | git status 83 | git commit 84 | ``` 85 | 86 | - JavaScript _fizzbuzz.js_, ES6, formatted with [Prettier](https://prettier.io/): `javascript` or `js` 87 | 88 | ```js 89 | const fizzBuzz = () => { 90 | for (let i = 1; i <= 100; i++) { 91 | let out = "" 92 | if (i % 3 === 0) out += "Fizz" 93 | if (i % 5 === 0) out += "Buzz" 94 | console.log(out || i) 95 | } 96 | } 97 | ``` 98 | 99 | - Python _fizzbuzz.py_, Python 3, formatted with [Black](https://black.readthedocs.io/en/stable/): `python` or `py` 100 | 101 | ```py 102 | def fizzbuzz(): 103 | """Print 1-100 104 | Multiples of 3: Fizz 105 | Multiples of 5: Buzz 106 | Multiples of 3 and 5: FizzBuzz 107 | """ 108 | for i in range(1, 101): 109 | out = "" 110 | if i % 3 == 0: 111 | out += "Fizz" 112 | if i % 5 == 0: 113 | out += "Buzz" 114 | print(out or i) 115 | ``` 116 | 117 | #### Images 118 | 119 | ```text 120 | ![Alt text that appears below the image in the output](/path/to/img.jpg "Optional title that will show up when you hover over the image in the output")` 121 | ``` 122 | 123 | **I still prefer to use HTML image tags, because they allow for more customization.** In particular, it's useful to set width on SVG. 124 | 125 | ```text 126 | Udacity logo 127 | ``` 128 | 129 | Udacity logo 130 | 131 | [(Back to top)](#top) 132 | 133 | ### General Markdown resources 134 | 135 | - [MarkdownGuide](https://www.markdownguide.org/) 136 | - [markdownlint syntax suggestions](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint) 137 | - [Markdown Style Guide](http://www.cirosantilli.com/markdown-style-guide/) 138 | - [GitHub-Flavored Markdown](https://guides.github.com/features/mastering-markdown/) 139 | - [Dillinger](https://dillinger.io/) is a helpful online Markdown editor with live preview. 140 | - [Turndown](https://domchristie.github.io/turndown/) is an HTML to Markdown converter. 141 | - [Udacity README course](https://www.udacity.com/course/writing-readmes--ud777) 142 | 143 | [(Back to top)](#top) 144 | 145 | ## Markdown apps 146 | 147 | ### Text editors 148 | 149 | Most code editors have extensions for Markdown. 150 | 151 | #### Atom 152 | 153 | [Atom](https://atom.io/) has good Markdown support. See the [Flight Manual](https://flight-manual.atom.io/using-atom/sections/writing-in-atom/) for instructions. 154 | 155 | #### Sublime Text 156 | 157 | Here's how to set up Sublime Text for Markdown: 158 | 159 | - Install [Sublime Text](http://www.sublimetext.com/) 160 | - I like the Mariana color scheme and the Adaptive theme. 161 | - Install [Package Control](https://packagecontrol.io/) 162 | - Use Package Control from within Sublime Text to install: 163 | - MarkdownEditing 164 | - Markdown Preview 165 | - MarkdownLivePreview: Has some issues with lack of wrapping in the previews. See [GitHub Issue tracker](https://github.com/math2001/MarkdownLivePreview/issues/34). 166 | 167 | #### Visual Studio Code (vscode) 168 | 169 | **In my opinion, vscode is currently the best editor for working with Markdown.** Here's why: 170 | 171 | - [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) 172 | - Autoformatter for Markdown files 173 | - In addition to formatting Markdown itself, it actually formats source code inside fenced code blocks. Awesome feature. 174 | - [Markdown All in One](https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one) 175 | - TOC auto-generation and update 176 | - Live TOC in explorer panel 177 | - Easier keyboard shortcuts than Sublime Text (with the exception of hyperlink insertion, which I added with a [keybinding](https://github.com/neilsustc/vscode-markdown/issues/20)) 178 | - [markdownlint](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint) 179 | - Lints Markdown files based on style recommendations for standardizing code. 180 | - Built in live preview 181 | 182 | My full vscode configuration is available in [my dotfiles repo](https://github.com/br3ndonland/dotfiles). 183 | 184 | See [Markdown and Visual Studio Code](https://code.visualstudio.com/Docs/languages/markdown) for more info. 185 | 186 | [(Back to top)](#top) 187 | 188 | ### IDEs 189 | 190 | IDE = Integrated Development Environment 191 | 192 | #### JupyterLab 193 | 194 | [JupyterLab](http://jupyterlab.readthedocs.io/en/latest/) is produced by [Project Jupyter](http://jupyter.org/). It is most widely used for scientific computing with Python, but supports many programming languages. It allows you to create "reproducible computational narratives," containing Markdown text interspersed with code chunks that you can run. JupyterLab has [some awesome features](https://blog.jupyter.org/jupyterlab-is-ready-for-users-5a6f039b8906) and was previously Jupyter notebook. 195 | 196 | I would suggest using [JupyterLab](https://jupyterlab.readthedocs.io/en/stable/) within a Pipenv virtual environment. I use the [Homebrew](https://brew.sh) package manager on macOS to install Python 3, the [Pipenv](https://pipenv.readthedocs.io/en/latest/) virtual environment tool, and [Jupyter](https://jupyter.org). Here are some setup instructions: 197 | 198 | - Install Homebrew from the command line as described on the [Homebrew website](https://brew.sh). 199 | - After installing Homebrew, install the necessary Homebrew packages from the command line: 200 | 201 | ```sh 202 | brew install python3 203 | brew install pipenv 204 | brew install jupyter 205 | ``` 206 | 207 | - Once installation of Homebrew and its packages is complete, navigate to the desired directory, or [clone a repository from GitHub](https://help.github.com/articles/cloning-a-repository/). 208 | 209 | ```sh 210 | cd path/to/a/directory 211 | git clone repository 212 | ``` 213 | 214 | - Finally, install the virtual environment with Pipenv, which includes [JupyterLab](https://jupyterlab.readthedocs.io/en/stable/getting_started/installation.html) and the other necessary packages, and launch JupyterLab to run the Jupyter Notebook. 215 | 216 | ```sh 217 | cd repository 218 | pipenv install 219 | pipenv shell 220 | # Install any JupyterLab extensions at this point 221 | (pipenv) $ jupyter labextension install @jupyterlab/toc 222 | # Launch JupyterLab 223 | (pipenv) $ jupyter lab 224 | ``` 225 | 226 | I previously used [Anaconda](https://www.anaconda.com/) to manage my Python and R distributions, and now use Homebrew. I switched because Anaconda is not as flexible or general as Homebrew, not as important for virtual environments now that we have Pipenv, and is a very large installation that is difficult to manage and uninstall. 227 | 228 | For examples of how to use Jupyter Notebook/JupyterLab, you can check out my [Udacity Full Stack Web Developer Nanodegree program repo](https://github.com/br3ndonland/udacity-fsnd). 229 | 230 | #### RStudio 231 | 232 | - [RStudio](https://www.rstudio.com/) is an IDE for the R programming language, used mostly for statistics and data science. 233 | - Like JupyterLab, R Markdown documents contain Markdown text with functional R code chunks. 234 | - I have provided an example of scientific data analysis with R Markdown on [GitHub](https://github.com/br3ndonland/R-proteomics-Nrf1). 235 | 236 | [(Back to top)](#top) 237 | 238 | ### Note apps 239 | 240 | Also see [Notable's comparison table](https://notable.md/static/images/comparison.png). 241 | 242 | #### [Bear](https://bear.app/) 243 | 244 | ##### Bear pros 245 | 246 | - This is one of the best Markdown note apps. 247 | - Supports Markdown. Uses a modified syntax called Polar Bear. 248 | - Tags and subtags (nested tags) 249 | - Untagged notes easily identified 250 | - Themes 251 | - Syntax highlighting 252 | - Supports internal relative links, like `[[Note title]]` 253 | - Evernote migration and import (though not perfect-see cons below) 254 | - Writing tools, like word counts and read time 255 | - Note encryption (see [blog post announcing Bear 1.7](https://blog.bear.app/2019/09/bear-1-7-is-here-with-note-encryption-bear-lock-live-note-links-and-more/)) 256 | - Password and FaceID lock 257 | - Reasonably-priced subscription plan 258 | 259 | ##### Bear cons 260 | 261 | - Apple only (macOS, iOS, iCloud), with [no plans to support Android](https://bear.app/faq/What%20about%20Bear%20for%20web,%20Android,%20or%20Windows/). 262 | - Collaboration features could be better. No shared notebooks. 263 | - Bear's "Markdown compatibility mode" is actually not standard or fully compatible with common tools like [Prettier](https://prettier.io/). The use of tabs for indentation and asterisks for bullets leads Prettier to mis-format Markdown documents exported from Bear. 264 | - Global search is painfully slow, and fires instantly, so it runs a search for each character you type. 265 | - Web clipper needs some work. Doesn't properly capture text on all sites. Search should have a delay, or require the user to hit enter, so it doesn’t immediately try to search while you’re typing. 266 | - Sync disrupts glide typing on iOS. This seems to be particularly problematic in notes containing large numbers of note links. It is very frustrating to constantly be interrupted in the middle of glide typing a word. Sync should wait until the keyboard is closed. 267 | - Can't search within notes on iOS. This is a major limitation that the Bear team has repeatedly overlooked. 268 | - Evernote import doesn't convert Evernote internal note links to Bear note links. Joplin also has this same issue, and it's a major barrier for switching from Evernote. If you would like to switch from Evernote to Bear, you can try running my [el2bl](https://github.com/br3ndonland/el2bl) script on your Evernote exports. 269 | 270 | #### [Day One](https://dayoneapp.com/) 271 | 272 | ##### Day One pros 273 | 274 | - Day One [supports Markdown](http://help.dayoneapp.com/tips-and-tutorials/markdown-guide). 275 | 276 | ##### Day One cons 277 | 278 | - Subscription service 279 | - Google Drive sync requires Google login, so you can't use it if you have [Advanced Protection](https://landing.google.com/advancedprotection/) enabled. 280 | 281 | #### [Dropbox Paper](https://www.dropbox.com/paper) 282 | 283 | ##### Dropbox Paper pros 284 | 285 | - Markdown export 286 | - Collaboration 287 | - Sync 288 | - Embedding works well 289 | 290 | ##### Dropbox Paper cons 291 | 292 | - Paper files don't show up in your regular Dropbox file structure 293 | - No tags 294 | - No themes 295 | - No internal linking 296 | - Only has H1-H2. H3 shows up as bold when downloading Markdown files. 297 | - PDFs embedded in Dropbox Paper documents don’t lead to the actual PDF when clicked, in the mobile apps. 298 | - PDF thumbnails are too large when inserted as single PDFs. When two or more files are inserted, the thumbnails are next to each other and the size is more reasonable. Needs a "view as attachment" option like Evernote. 299 | 300 | #### [iA Writer](https://ia.net/writer) 301 | 302 | iA = information Architects 303 | 304 | ##### iA Writer pros 305 | 306 | - Cross-platform (macOS, iOS, Windows) 307 | - Light/dark themes 308 | - Syntax highlighting: the grammatical syntax highlighting is interesting 309 | - Uses the same `#tag/subtag` nested tagging syntax from Bear 310 | - Currently just a one-time payment model, though that will probably change. 311 | 312 | ##### iA Writer cons 313 | 314 | - Lacks many of the features of Bear 315 | - No import from other apps. When manually importing notes exported from Bear as Markdown, it doesn't recognize the tags. Tags have to be manually typed out for iA Writer to recognize them. 316 | - Note links? 317 | - Tags are only recognized when they are typed in. If a tag is pasted in, or already present (for example, in a Markdown file that has been exported from Bear), the tags are not recognized. 318 | - Multimedia? 319 | - No Linux support 320 | 321 | #### [Inkdrop](https://www.inkdrop.info/) 322 | 323 | ##### Inkdrop pros 324 | 325 | - [Features](https://www.inkdrop.info/features): 326 | - Markdown 327 | - Themes 328 | - Encryption 329 | - Cross-platform. Seems to be like Bear, but cross-platform. 330 | - Check out the developer's [Medium blog](https://blog.inkdrop.info). 331 | 332 | ##### Inkdrop cons 333 | 334 | - Subscription plan 335 | - Android app has poor reviews (~3.3 rating) 336 | 337 | #### [Joplin](https://joplin.cozic.net/) 338 | 339 | ##### Joplin pros 340 | 341 | - Open-source 342 | - Desktop is Electron, mobile is React Native. 343 | - Flexible cloud sync 344 | - Markdown format 345 | - Evernote import 346 | 347 | ##### Joplin cons 348 | 349 | - Evernote to Joplin migration not ideal (see below). 350 | - Documentation on the website is okay, but [contributing guidelines](https://github.com/laurent22/joplin/blob/master/CONTRIBUTING.md) are not well delineated. The code and stack should be clearly explained so people can easily contribute. I should know roughly where I need to go in the codebase to add a feature. 351 | - Laurent Cozic only makes \$60/month on Patreon. Not sustainable. 352 | 353 | ##### Evernote to Joplin migration 354 | 355 | - Tried this on 201809 356 | - Export Evernote notebook to .enex. 357 | - Joplin -> Import -> ENEX 358 | - Images came through 359 | - Formatting generally came through well. 360 | - Updates needed 361 | - Critical 362 | - **Internal note links import as Evernote links, not Joplin links. This is a critical issue for me.** 363 | - Example: 364 | - Joplin internal note link: `:/cddf8681528148b9aad5077198e58a4a` 365 | - Evernote internal note link: `evernote:///view/6168869/s55/426ec3ae-6cad-48c7-aab3-b300845063ef/426ec3ae-6cad-48c7-aab3-b300845063ef/` 366 | - Links sometimes also just lead to the top note result in the notebook, rather than the specific note needed. 367 | - Note URL not yet available (issue #[427](https://github.com/laurent22/joplin/issues/427)). This will be important for clipped news articles. 368 | - Bold text comes through as big section breaks (see issue #[767](https://github.com/laurent22/joplin/issues/767)) 369 | - I had to un-bold all the headers I put into my notes. 370 | - Enhancements 371 | - Evernote code blocks not transferring in as Markdown code blocks. 372 | - Markdown TOC (issue #[478](https://github.com/laurent22/joplin/issues/478)) 373 | - Searching in notes: The search bar only searches notes, but doesn't reveal results within the note. See #[382](https://github.com/laurent22/joplin/issues/382). 374 | 375 | #### [Jottings](http://jottingsapp.com/) 376 | 377 | iOS-only note app with Markdown, tagging, and Dropbox sync. 378 | 379 | #### [Journey](https://2appstudio.com/journey/) 380 | 381 | ##### Journey pros 382 | 383 | - Encrypted 384 | - Cross-platform 385 | - Google Drive sync 386 | - Import from Day One, Evernote, etc 387 | 388 | ##### Journey cons 389 | 390 | - Forces Google login before allowing access to app on Mac. This is a problem because Google Drive sync is not allowed when [Google Advanced Protection Program](https://landing.google.com/advancedprotection/) is enabled. 391 | - Subscription options 392 | - Vague Evernote import capabilities 393 | 394 | #### [Laverna](https://laverna.cc/) 395 | 396 | ##### Laverna pros 397 | 398 | - Encrypted 399 | - Markdown 400 | - Multimedia 401 | - Internal linking 402 | - Make tags with #tag 403 | - Open source 404 | - Built with Electron 405 | 406 | ##### Laverna cons 407 | 408 | - Evernote import? 409 | - No Android app yet 410 | - No dark themes yet 411 | - No Markdown TOC 412 | - Development coming along slowly 413 | 414 | #### [Notable](https://notable.md/) 415 | 416 | Heard about Notable via the [Changelog weekly email #238](https://email.changelog.com/t/t-614770D9C810C3FD2540EF23F30FEDED). 417 | 418 | From the [README](https://github.com/notable/notable/blob/master/README.md): 419 | 420 | > The markdown-based note-taking app that doesn't suck. 421 | > 422 | > I couldn't find a note-taking app that ticked all the boxes I'm interested in: notes are written and rendered in GitHub-flavored Markdown, no WYSIWYG, no proprietary formats, I can run a search & replace across all notes, notes support attachments, the app isn't bloated, the app has a pretty interface, tags are indefinitely nestable and can import Evernote notes (because that's what I was using before). 423 | > 424 | > So I built my own. 425 | 426 | The developer has made extensive comparisons with other note apps. See [Notable's comparison table](https://notable.md/static/images/comparison.png). 427 | 428 | ##### Notable pros 429 | 430 | - Uses same Markdown editor as VSCode 431 | - Has an "open in default editor" button for easy transfer to a text editor 432 | - Handles nested code blocks (Bear doesn't) 433 | 434 | ##### Notable cons 435 | 436 | - No mobile app yet 437 | - [No longer open-source](https://github.com/notable/notable/blob/master/SOURCE_CODE.md). 438 | - No Bear import. The closest thread I could find was [this](https://www.reddit.com/r/Notable/comments/bsg1dn/just_moved_from_bear_nice_surprise/), but there's no migration script. 439 | - Metadata in YAML frontmatter: will require the `#tag/subtag` nested tags from Bear to be moved into an array in the frontmatter. 440 | - No searching within notes yet. Major shortcoming. 441 | - No tag autocompletion 442 | - Not sure if it automatically update note links when note titles change, like Bear does 443 | - Pipes are a problem. For example, I frequently title clipped articles like `[[Periodical | Date: Article title]]`. When I try to use this format in Notable, it treats the text to the left of the pipe as the link title, and the text to the right of the pipe as the note title. 444 | - Doesn't autocomplete lists like Markdown All in One in VSCode 445 | - [According to the developer](https://www.reddit.com/r/Notable/comments/g0vy8v/privacy_policy_why_notable_phones_home_who_else/), Notable collects analytics via [Amplitude](https://amplitude.com/). 446 | 447 | #### [Simplenote](https://simplenote.com/) 448 | 449 | From WordPress 450 | 451 | ##### Simplenote pros 452 | 453 | - Markdown support 454 | 455 | ##### Simplenote cons 456 | 457 | - Evernote import? 458 | - Multimedia? 459 | 460 | #### [Standard Notes](https://standardnotes.org/getting-started) 461 | 462 | ##### Standard Notes pros 463 | 464 | - Simple, dependable text note app 465 | - Note tagging 466 | - Themes like solarized and dark 467 | - Extensions to add features like Markdown 468 | - Encrypted 469 | - Backup to Dropbox and Google Drive 470 | - "Built to last" 471 | 472 | ##### Standard Notes cons 473 | 474 | - User interface is generally not intuitive. 475 | - The Account -> Encryption section says "8/8 notes and tags encrypted." Why would I want to combine the number of notes with the number of tags? 476 | - Very difficult to even figure out how to log in to my account online. Apparently there are separate accounts for the web app and premium? I think it's the [dashboard](https://dashboard.standardnotes.org/). 477 | - Extensions make it way too confusing. 478 | - I have to select different editors? I don't want a different editor for every task, I want to use one editor for all tasks. 479 | - Extensions used to only work on desktop and web (they now work on mobile apparently). 480 | - Attachments 481 | - Can't attach files from mobile devices. 482 | - Not great with multimedia. Tried to drag and drop a movie, and it just displayed the movie instead of my notes. 483 | - [Evernote import](https://standardnotes.org/evernote): 484 | - Formatting, images, and attachments will not be copied over. 485 | - Have to break up .enex into 250 MB segments. 486 | - Import is not intuitive. Importing anything goes through import backup, but most imports are not backups. 487 | - **Doesn't do a good job of Evernote HTML to Markdown conversion.** Loses file attachments and lists, doesn't recognize headers. The options are either retain HTML, or strip all formatting. 488 | - My response to email survey: 489 | > _Have you ever tried Standard Notes Extended?_ 490 | > No. If I became a regular user, I would happily pay for Extended. I'm not regularly using Standard Notes because I'm heavily invested in Evernote. I've been using it for six years, and have a 4 GB database with ~4400 tagged notes. I would love to migrate from Evernote to another service, converting from rich text to Markdown and encrypting my data, while keeping my multimedia attachments. Bear is one example of this type of migration, but I don't use Bear because it's Apple-only. 491 | 492 | #### [Trilium Notes](https://github.com/zadam/trilium) 493 | 494 | Heard about Trilium via the [Changelog weekly email #237](https://email.changelog.com/t/t-2599C05F2D5B961E2540EF23F30FEDED). 495 | 496 | ##### Trilium pros 497 | 498 | - Evernote import: I haven't tried it yet, so I can't comment on import of formatting, internal links, etc. 499 | - Code editing with syntax highlighting 500 | - Also supports mind mapping 501 | - Encryption 502 | 503 | ##### Trilium cons 504 | 505 | - Work in progress 506 | - No mobile app yet 507 | 508 | #### [Turtl](https://turtlapp.com/) 509 | 510 | [Turtl blog on Tumblr](http://turtlapp.tumblr.com/) 511 | 512 | ##### Turtl pros 513 | 514 | - Promising encrypted Evernote alternative 515 | - Evernote import coming in 0.6.5 516 | - Markdown 517 | - Sharing 518 | - Some multimedia support 519 | - Android app 520 | 521 | ##### Turtl cons 522 | 523 | - Still needs more development, and development has been very slow. 524 | - Not sure how dependable this app will be. The developers don't even own a MacBook. 525 | - No dark themes yet 526 | 527 | #### [Typora](https://typora.io/) 528 | 529 | ##### Typora pros 530 | 531 | - File list panel, allowing you to use any cloud service to sync. 532 | - CSS configurable 533 | - Support for images 534 | - Document export 535 | - Footnote feature is cool 536 | 537 | ##### Typora cons 538 | 539 | - Feature-poor 540 | - Not for android 541 | - If it’s not for mobile and not multimedia, what’s the point beyond Sublime Text? Or Dillinger? 542 | 543 | #### [Ulysses](https://ulyssesapp.com/) 544 | 545 | ##### Ulysses pros 546 | 547 | - Nice interface 548 | - Markdown 549 | - Dark themes 550 | - Can also manage journal articles and research 551 | 552 | ##### Ulysses cons 553 | 554 | - Apple only (macOS/iOS) 555 | - Encryption? 556 | - Moved to subscription model 557 | - Maintains article tags as "keywords," a fatal flaw shared by other apps like [Papers](https://www.readcube.com/papers/). See my notes on citation managers in [my cite repo](https://github.com/br3ndonland/cite). 558 | 559 | ### In-browser editors 560 | 561 | - [Dillinger](https://dillinger.io/): In-browser Markdown editor with live preview. 562 | - [GitBook](https://www.gitbook.com/): Online platform for writing documentation. It combines and converts Markdown files into multimedia-enabled notebooks, like book chapters. 563 | - [Gnotes](https://notes.giggy.com/): Write Markdown, save to Dropbox, see in Evernote. One-way only. 564 | - [Markdown Here](https://markdown-here.com/index.html): Allows Markdown formatting for in-browser web apps like Gmail. See [GitHub](https://github.com/adam-p/markdown-here). 565 | - [Marxico](http://marxi.co/): 566 | - In-browser Markdown editor based on Dillinger. 567 | - Works with images. 568 | - Has live TOC. 569 | - Renders well on mobile browsers, but only selection capability available is "select all". 570 | - May not have seamless integration with Evernote. See [discussion on Evernote forums](https://discussion.evernote.com/topic/99861-native-markdown-support/?do=findComment&comment=448394). 571 | - [StackEdit](https://stackedit.io/): In-browser Markdown editor with live preview. Uses PageDown, the engine powering the Markdown capabilities of the Stack Exchange forums. 572 | - Udacity discussion forums: You can use Markdown formatting in your forum posts. 573 | 574 | [(Back to top)](#top) 575 | 576 | ### Social apps 577 | 578 | #### Gitter 579 | 580 | [Gitter](https://gitter.im/) uses Markdown formatting for chats. See their [Markdown basics](https://gitter.zendesk.com/hc/en-us/articles/200176682-Markdown-basics) support article. 581 | 582 | #### ~~Slack~~ 583 | 584 | Slack uses a simplified pseudo-Markdown to format messages. I call it "slackdown." On the [Slack "Format your messages" page](https://get.slack.help/hc/en-us/articles/202288908-Format-your-messages), Slack states that it will not be building in full Markdown capabilities. 585 | 586 | [(Back to top)](#top) 587 | -------------------------------------------------------------------------------- /offline-web-apps/offline-2-sw.md: -------------------------------------------------------------------------------- 1 | # An Overview of Service Worker 2 | 3 | 4 | Udacity logo 5 | 6 | 7 | [Offline Web Applications by Google course](https://www.udacity.com/course/offline-web-applications--ud899) lesson 2/3 8 | 9 | Udacity Google Mobile Web Specialist Nanodegree program part 2 lesson 17 10 | 11 | Udacity Grow with Google Scholarship challenge course lesson 03 12 | 13 | Brendon Smith 14 | 15 | br3ndonland 16 | 17 | ## Table of Contents 18 | 19 | - [Service Worker basics](#service-worker-basics) 20 | - [2.01. An Overview of Service Worker](#201-an-overview-of-service-worker) 21 | - [2.02. Quiz: Scoping Quiz](#202-quiz-scoping-quiz) 22 | - [2.03. Adding a Service Worker To the Project](#203-adding-a-service-worker-to-the-project) 23 | - [2.04. Quiz: Registering a Service Worker](#204-quiz-registering-a-service-worker) 24 | - [2.05. The Service Worker Lifecycle](#205-the-service-worker-lifecycle) 25 | - [2.06. Quiz: Enabling Service Worker Dev Tools](#206-quiz-enabling-service-worker-dev-tools) 26 | - [2.07. Quiz: Service Worker Dev Tools](#207-quiz-service-worker-dev-tools) 27 | - [2.08. Quiz: Service Worker Dev Tools 2](#208-quiz-service-worker-dev-tools-2) 28 | - [2.09. Service Worker Dev Tools Continued](#209-service-worker-dev-tools-continued) 29 | - [Service Worker and requests](#service-worker-and-requests) 30 | - [2.10. Hijacking Requests](#210-hijacking-requests) 31 | - [2.11. Quiz: Hijacking Requests 1 Quiz](#211-quiz-hijacking-requests-1-quiz) 32 | - [2.12. Hijacking Requests 2](#212-hijacking-requests-2) 33 | - [2.13. Quiz: Hijacking Requests 2 Quiz](#213-quiz-hijacking-requests-2-quiz) 34 | - [2.14. Hijacking Requests 3](#214-hijacking-requests-3) 35 | - [2.15. Quiz: Hijacking Requests 3 Quiz](#215-quiz-hijacking-requests-3-quiz) 36 | - [Service Worker and caching](#service-worker-and-caching) 37 | - [2.16. Caching and Serving Assets](#216-caching-and-serving-assets) 38 | - [2.17. Quiz: Install and Cache Quiz](#217-quiz-install-and-cache-quiz) 39 | - [2.18. Quiz: Cache Response Quiz](#218-quiz-cache-response-quiz) 40 | - [2.19. Updating the Static Cache](#219-updating-the-static-cache) 41 | - [2.20. Quiz: Update Your CSS Quiz](#220-quiz-update-your-css-quiz) 42 | - [2.21. Quiz: Update Your CSS 2](#221-quiz-update-your-css-2) 43 | - [2.22. Adding UX to the Update Process](#222-adding-ux-to-the-update-process) 44 | - [2.23. Quiz: Adding UX Quiz](#223-quiz-adding-ux-quiz) 45 | - [2.24. Triggering an Update](#224-triggering-an-update) 46 | - [2.25. Quiz: Triggering an Update Quiz](#225-quiz-triggering-an-update-quiz) 47 | - [2.26. Quiz: Caching the Page Skeleton](#226-quiz-caching-the-page-skeleton) 48 | - [Lesson feedback](#lesson-feedback) 49 | 50 | ## Service Worker basics 51 | 52 | ### 2.01. An Overview of Service Worker 53 | 54 | - **Service worker is a JavaScript file that sits between the browser and network requests.** 55 | - It returns a **promise**. 56 | - `navigator.serviceWorker.register('/sw.js')` 57 | - Jake made a website called [is serviceworker ready?](https://jakearchibald.github.io/isserviceworkerready/), to evaluate browser compatibility with different aspects of Service Worker. 58 | 59 | ### 2.02. Quiz: Scoping Quiz 60 | 61 | Got this on my first try. 62 | 63 |
64 | Solution 65 |

66 | The scope defines the URLs to be handled. ServiceWorker will handle any deeper URL, but not shallower URLs. 67 |

68 |
69 | 70 | ### 2.03. Adding a Service Worker To the Project 71 | 72 | ### 2.04. Quiz: Registering a Service Worker 73 | 74 | - **This was the first coding task of the course.** 75 | - _/js/sw/index.js_ is the ServiceWorker. 76 | - JavaScript doesn't have "private methods," but it's common to call methods starting with an underscore, like `this._openSocket();`, if they will only be called by other methods of this object. 77 | - Service workers are limited to HTTPS, except localhost. 78 | 79 | Git 80 | 81 | - Add the register service worker command. We want the scope to be the whole origin, so the default scope is fine. 82 | - You don't actually need to stop the server to switch branches, but I did. 83 | 84 | ```shell 85 | $ git reset --hard 86 | $ git checkout task-register-sw 87 | ``` 88 | 89 |
90 | Solution 91 | 92 | - Service registration code: 93 | 94 | ```javascript 95 | IndexController.prototype._registerServiceWorker = function() { 96 | // register service worker 97 | if (!navigator.serviceWorker) return 98 | 99 | navigator.serviceWorker 100 | .register("/sw.js") 101 | .then(function() { 102 | console.log("Registration worked!") 103 | }) 104 | .catch(function() { 105 | console.log("Registration failed!") 106 | }) 107 | } 108 | ``` 109 | 110 | - Go to [http://localhost:8889/](http://localhost:8889/) and type `registered`. 111 | - I tried several times, but it didn't work, and dev tools didn't help. 112 | - I basically had the answer, just needed a little more code. 113 | - I also needed to check my code in a different sequence: 114 | - Save the JavaScript file _IndexController.js_ 115 | - Refresh [http://localhost:8888/](http://localhost:8888/) 116 | - Check JavaScript console with cmd+alt+j (note that Firefox developer tools separates the JavaScript console from the other dev tools) 117 | - Now go to [http://localhost:8889/](http://localhost:8889/) and type `registered`. 118 | 119 |
120 | 121 | ### 2.05. The Service Worker Lifecycle 122 | 123 | ### 2.06. Quiz: Enabling Service Worker Dev Tools 124 | 125 | - We will be using Chrome Canary. 126 | 127 | ### 2.07. Quiz: Service Worker Dev Tools 128 | 129 | - The Resources tab is no longer there in Chrome Canary. The resources are just shown directly. 130 | - Typing "sw-waiting" into the test field shows there's a service worker waiting. 131 | 132 | ### 2.08. Quiz: Service Worker Dev Tools 2 133 | 134 | ### 2.09. Service Worker Dev Tools Continued 135 | 136 | - **The Sources panel of dev tools should have a Service Workers tab next to Scope and Watch with the option to "Force update on page load." I don't see it.** I found the [answer](https://discussions.udacity.com/t/solved-force-update-on-page-load-not-appearing-in-chrome-canary/528320/4) in the discussion forum: It's now on the Applications tab. I also tried Firefox developer edition, but couldn't find the equivalent setting. 137 | 138 | [(Back to top)](#top) 139 | 140 | ## Service Worker and requests 141 | 142 | ### 2.10. Hijacking Requests 143 | 144 | - **Finally using the service worker to intercept requests and serve offline content!** 145 | - This part demonstrated **general request hijacking.** 146 | - _sw/index.js_: 147 | 148 | ```javascript 149 | self.addEventListener("fetch", function(event) { 150 | event.respondWith(new Response("Hello, Offline World!")) 151 | }) 152 | ``` 153 | 154 | ### 2.11. Quiz: Hijacking Requests 1 Quiz 155 | 156 | Git 157 | 158 | - We had to checkout to a new branch. 159 | - I was getting an error: 160 | 161 | ```text 162 | $ git reset --hard 163 | HEAD is now at 1aff011 Method to register SW 164 | $ git checkout task-custom-response 165 | error: pathspec 'task-custom-response' did not match any file(s) known to git. 166 | ``` 167 | 168 | I had to create the branch in a separate step: 169 | 170 | ```text 171 | $ git branch task-custom-response 172 | $ git checkout task-custom-response 173 | Switched to branch 'task-custom-response' 174 | ``` 175 | 176 | But then the _sw/index.js_ file was blank. 177 | 178 | I had to go back to master, find the branch on origin, and delete the branch I made: 179 | 180 | ```text 181 | $ git checkout master 182 | Switched to branch 'master' 183 | Your branch is up-to-date with 'origin/master'. 184 | $ git branch 185 | - master 186 | task-custom-response 187 | task-register-sw 188 | $ git branch -d task-custom-response 189 | Deleted branch task-custom-response (was 28d9ac3). 190 | $ git checkout origin/task-custom-response 191 | Note: checking out 'origin/task-custom-response'. 192 | 193 | You are in 'detached HEAD' state. You can look around, make experimental 194 | changes and commit them, and you can discard any commits you make in this 195 | state without impacting any branches by performing another checkout. 196 | 197 | If you want to create a new branch to retain commits you create, you may 198 | do so (now or later) by using -b with the checkout command again. Example: 199 | 200 | git checkout -b 201 | 202 | HEAD is now at 4f2c245... TODOs for custom response task 203 | br3ndonland ((4f2c245...)) wittr 204 | $ 205 | ``` 206 | 207 | This finally showed me the code I was supposed to see in _sw/index.js_: 208 | 209 | ```javascript 210 | self.addEventListener("fetch", function(event) { 211 | // TODO: respond to all requests with an html response 212 | // containing an element with class="a-winner-is-me". 213 | // Ensure the Content-Type of the response is "text/html" 214 | console.log(event.request) 215 | }) 216 | ``` 217 | 218 |
219 | Solution 220 | 221 | - I had the HTML right on the first try, but didn't know how to set the content type so I checked the solution for that. So many curly braces. 222 | 223 | ```javascript 224 | self.addEventListener("fetch", function(event) { 225 | // respond to all requests with an html response 226 | // containing an element with class="a-winner-is-me". 227 | // Ensure the Content-Type of the response is "text/html" 228 | event.respondWith( 229 | new Response('Hello, Offline HTML World!', { 230 | headers: { "Content-Type": "text/html" } 231 | }) 232 | ) 233 | }) 234 | ``` 235 | 236 | - Refresh the app at [http://localhost:8888/](http://localhost:8888/) 237 | - Test on settings page at [http://localhost:8889/](http://localhost:8889/) with "html-response" 238 | - The test verified. As recommended in the video, I disabled the "Force update on reload" option (to keep the JavaScript after reload), switched to offline mode in [http://localhost:8889/](http://localhost:8889/) (not in dev tools), and the text still appears. Service worker is working! 239 | 240 |
241 | 242 | ### 2.12. Hijacking Requests 2 243 | 244 | - Now that we are able to respond to all requests with the same HTML, we will customize the response based on the input. This is **selective response hijacking.** 245 | - We will need to work with HTTP requests. 246 | - **The original JavaScript HTTP request method `XMLHTTPRequest` sucks. The XHR API is over 15 years old. The syntax is strange: It says XML, though it deals with other file types, it says HTTP, although it works with filesystem also, and Request, even though the returned object is a strange mixture of request and response. The event handler puts you in "callback hell."** 247 | - The [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) is an improvement over the [XMLHttpRequest API](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest). 248 | 249 | ```javascript 250 | fetch('/foo').then(function(response)) { 251 | return response.json(); 252 | }).then(function(data) { 253 | console.log(data); 254 | }).catch(function() { 255 | console.log('It failed') 256 | }) 257 | ``` 258 | 259 | ### 2.13. Quiz: Hijacking Requests 2 Quiz 260 | 261 | Git 262 | 263 | - Checkout to the new branch: 264 | 265 | ```text 266 | $ git reset --hard 267 | HEAD is now at 4f2c245 TODOs for custom response task 268 | $ git checkout origin/gif-response 269 | Previous HEAD position was 4f2c245... TODOs for custom response task 270 | HEAD is now at adf8827... Responding with gif 271 | ``` 272 | 273 | - Return a response if the request is for a .jpg. 274 | 275 |
276 | Solution 277 | 278 | - Here was my first attempt, based on what we had learned so far: 279 | 280 | ```javascript 281 | self.addEventListener('fetch', function(event) { 282 | // TODO: only respond to requests with a 283 | // url ending in ".jpg" 284 | if (!.jpg) return; 285 | event.respondWith( 286 | fetch('/imgs/dr-evil.gif') 287 | ); 288 | }); 289 | ``` 290 | 291 | - Refresh the app at [http://localhost:8888/](http://localhost:8888/) 292 | - Test on settings page at [http://localhost:8889/](http://localhost:8889/) 293 | - Jake demonstrated the solution. 294 | 295 | - What other properties does `event.Request()` have? He acknowledged that we didn't have enough information yet, and Google searched "mdn request". 296 | 297 | ```javascript 298 | self.addEventListener("fetch", function(event) { 299 | // TODO: only respond to requests with a 300 | // url ending in ".jpg" 301 | if (event.request.url.endsWith(".jpg")) { 302 | event.respondWith(fetch("/imgs/dr-evil.gif")) 303 | } 304 | }) 305 | ``` 306 | 307 |
308 | 309 | ### 2.14. Hijacking Requests 3 310 | 311 | - New code from video: 312 | 313 | ```javascript 314 | self.addEventListener("fetch", function(event) { 315 | event.respondWith( 316 | fetch(event.request) 317 | .then(function(response) { 318 | if (response.status == 404) { 319 | return new Response("Whoops, not found") 320 | } 321 | return response 322 | }) 323 | .catch(function() { 324 | return new Response("Uh oh, that totally failed!") 325 | }) 326 | ) 327 | }) 328 | ``` 329 | 330 | ### 2.15. Quiz: Hijacking Requests 3 Quiz 331 | 332 | - Respond with Dr. Evil GIF instead of custom text. 333 | 334 | ```javascript 335 | self.addEventListener("fetch", function(event) { 336 | event.respondWith( 337 | fetch(event.request) 338 | .then(function(response) { 339 | if (response.status === 404) { 340 | // TODO: instead, respond with the gif at 341 | // /imgs/dr-evil.gif 342 | // using a network request 343 | return new Response("Whoops, not found") 344 | } 345 | return response 346 | }) 347 | .catch(function() { 348 | return new Response("Uh oh, that totally failed!") 349 | }) 350 | ) 351 | }) 352 | ``` 353 | 354 | Git 355 | 356 | - Checkout to the new branch: 357 | 358 | ```text 359 | $ git reset --hard 360 | HEAD is now at adf8827 Responding with gif 361 | $ git checkout origin/error-handling 362 | Note: checking out 'origin/error-handling'. 363 | 364 | You are in 'detached HEAD' state. You can look around, make experimental 365 | changes and commit them, and you can discard any commits you make in this 366 | state without impacting any branches by performing another checkout. 367 | 368 | If you want to create a new branch to retain commits you create, you may 369 | do so (now or later) by using -b with the checkout command again. Example: 370 | 371 | git checkout -b 372 | 373 | HEAD is now at de59554... Handling 404 and failure 374 | ``` 375 | 376 |
377 | Solution 378 | 379 | - First attempt: 380 | 381 | ```javascript 382 | self.addEventListener("fetch", function(event) { 383 | event.respondWith( 384 | fetch(event.request) 385 | .then(function(response) { 386 | if (response.status === 404) { 387 | event.respondWith(fetch("/imgs/dr-evil.gif")) 388 | return new Response("Whoops, not found") 389 | } 390 | return response 391 | }) 392 | .catch(function() { 393 | return new Response("Uh oh, that totally failed!") 394 | }) 395 | ) 396 | }) 397 | ``` 398 | 399 | - Refresh the app at [http://localhost:8888/](http://localhost:8888/) 400 | - Test on settings page at [http://localhost:8889/](http://localhost:8889/) with "gif-404" 401 | - Solution 402 | 403 | - I just needed to simplify my code a little bit. 404 | - If you return a promise within a promise, it returns the eventual value to the outer promise. 405 | 406 | ```javascript 407 | self.addEventListener("fetch", function(event) { 408 | event.respondWith( 409 | fetch(event.request) 410 | .then(function(response) { 411 | if (response.status === 404) { 412 | // instead, respond with the gif at 413 | // /imgs/dr-evil.gif 414 | // using a network request 415 | return fetch("/imgs/dr-evil.gif") 416 | } 417 | return response 418 | }) 419 | .catch(function() { 420 | return new Response("Uh oh, that totally failed!") 421 | }) 422 | ) 423 | }) 424 | ``` 425 | 426 |
427 | 428 | [(Back to top)](#top) 429 | 430 | ## Service Worker and caching 431 | 432 | ### 2.16. Caching and Serving Assets 433 | 434 | - The cache API gives us the `caches` object on the global namespace. 435 | - Start the cache 436 | 437 | ```javascript 438 | caches.open("my-stuff").then(function(cache) { 439 | //... 440 | }) 441 | ``` 442 | 443 | - Add cache items with `cache.put()` 444 | 445 | ```javascript 446 | cache.put(request, response) 447 | ``` 448 | 449 | - Add cache items with `cache.addAll()`: accepts array of URLs, fetches them, and puts the response pairs into the cache. This operation is **atomic**: if any fail to cache, none are added to the cache. 450 | 451 | ```javascript 452 | cache.addAll(["/foo", "bar"]) 453 | ``` 454 | 455 | - To retrieve items from the cache, use `cache.match`. It will return a promise if the query is found in the cache. 456 | 457 | ```javascript 458 | cache.match(request) 459 | ``` 460 | 461 | - We can control caching when the service worker is installed. 462 | 463 | ### 2.17. Quiz: Install and Cache Quiz 464 | 465 | Git 466 | 467 | - Checkout to the new branch: 468 | 469 | ```shell 470 | $ git reset --hard 471 | $ git checkout task-install 472 | ``` 473 | 474 |
475 | Solution 476 | 477 | - Code 478 | 479 | ```javascript 480 | self.addEventListener("install", function(event) { 481 | event.waitUntil( 482 | caches.open("wittr-static-v1").then(function(cache) { 483 | return cache.addAll([ 484 | "/", 485 | "js/main.js", 486 | "css/main.css", 487 | "imgs/icon.png", 488 | "https://fonts.gstatic.com/s/roboto/v15/2UX7WLTfW3W8TclTUvlFyQ.woff", 489 | "https://fonts.gstatic.com/s/roboto/v15/d-6IYplOFocCacKzxwXSOD8E0i7KZn-EPnyo3HZu7kw.woff" 490 | ]) 491 | }) 492 | ) 493 | }) 494 | 495 | self.addEventListener("fetch", function(event) { 496 | // Leave this blank for now. 497 | // We'll get to this in the next task. 498 | }) 499 | ``` 500 | 501 | - Refresh the app at [http://localhost:8888/](http://localhost:8888/) 502 | - Test on settings page at [http://localhost:8889/](http://localhost:8889/) with "install-cached" 503 | 504 |
505 | 506 | ### 2.18. Quiz: Cache Response Quiz 507 | 508 | Combining `event.respondWith()` and `cache.match()`. 509 | 510 | Git 511 | 512 | - Checkout to the new branch: 513 | 514 | ```shell 515 | $ git reset --hard 516 | $ git checkout origin/task-cache-response 517 | ``` 518 | 519 |
520 | Solution 521 | 522 | - Code (Combined 17. Quiz: Install and Cache Quiz and 18. Quiz: Cache Response Quiz) 523 | 524 | ```javascript 525 | self.addEventListener("install", function(event) { 526 | event.waitUntil( 527 | caches.open("wittr-static-v1").then(function(cache) { 528 | return cache.addAll([ 529 | "/", 530 | "js/main.js", 531 | "css/main.css", 532 | "imgs/icon.png", 533 | "https://fonts.gstatic.com/s/roboto/v15/2UX7WLTfW3W8TclTUvlFyQ.woff", 534 | "https://fonts.gstatic.com/s/roboto/v15/d-6IYplOFocCacKzxwXSOD8E0i7KZn-EPnyo3HZu7kw.woff" 535 | ]) 536 | }) 537 | ) 538 | }) 539 | 540 | self.addEventListener("fetch", function(event) { 541 | // respond with an entry from the cache if there is one. 542 | // if not, fetch from network. 543 | event.respondWith( 544 | caches.match(event.request).then(function(response) { 545 | if (response) return response 546 | return fetch(event.request) 547 | }) 548 | ) 549 | }) 550 | ``` 551 | 552 | - Refresh the app at [http://localhost:8888/](http://localhost:8888/) 553 | - Test on settings page at [http://localhost:8889/](http://localhost:8889/) with "cache-served" 554 | - At this point, we are starting to get a substantial amount of content offline. 555 | - They could have combined the git commits here. We were working with a different function in 18 than in 17, so we didn't really need a git reset. 556 | 557 |
558 | 559 | #### Goals 560 | 561 | - Unobtrusive app updates 562 | - Get the user onto the latest version 563 | - Continually update cache of posts 564 | - Cache photos 565 | - Cache avatars 566 | 567 | ### 2.19. Updating the Static Cache 568 | 569 | - Disabling force update on page load, to more closely simulate the end user experience. 570 | - We are now going to clear the old cache, so that updates show up, even after disabling force update on page load. 571 | - We can use `caches.delete(cacheName)` or `caches.keys();` 572 | - Jake showed us the theme colors in the Sass .scss file at _/public/scss/\_theme.scss_. 573 | 574 | ### 2.20. Quiz: Update Your CSS Quiz 575 | 576 | Git 577 | 578 | - Checkout to new branch: 579 | 580 | ```shell 581 | $ git reset --hard 582 | $ git checkout origin/task-handling-updates 583 | ``` 584 | 585 |
586 | Solution 587 | 588 | - Code: Jake says there's a "simple way" and a "scalable way." 589 | - Simple way: 590 | - Change CSS 591 | - Change name of cache to 'wittr-static-v2' 592 | - Add `caches.delete(wittr-static-v1)` 593 | - Scalable way: 594 | - Can't simply delete the cache name each time. Too messy. 595 | - Instead, maintain a safe list of wanted cache names and remove unwanted ones. 596 | - Store the name of the static cache in a variable (at top of service worker). 597 | ```javascript 598 | var staticCacheName = "wittr-static-v2" 599 | ``` 600 | - In the activate event, get all the cache names that exist. 601 | - Refresh the app at [http://localhost:8888/](http://localhost:8888/) 602 | - Test on settings page at [http://localhost:8889/](http://localhost:8889/) with "new-cache-ready" 603 | - _Jake also recommends auto-generating hashes for filenames, so they can be updated and ensure the latest versions._ 604 | 605 |
606 | 607 | #### Goals after 2.20 608 | 609 | - ~~Unobtrusive app updates~~ 610 | - Get the user onto the latest version 611 | - Continually update cache of posts 612 | - Cache photos 613 | - Cache avatars 614 | 615 | ### 2.21. Quiz: Update Your CSS 2 616 | 617 | - Test on settings page at [http://localhost:8889/](http://localhost:8889/) with "new-cache-used" 618 | 619 | ### 2.22. Adding UX to the Update Process 620 | 621 | - As we have learned, new service workers have to wait until the old one goes away before they are active in the browser. Let's do something better. We will include an update notification for the user. 622 | - As we have seen, when a service worker is registered, it returns a promise. 623 | - The promise "fulfills with a service worker registration object," which can then be used to act on the registration, like `reg.unregister();` and `reg.update();`. 624 | - We will also get three properties: 625 | 626 | ```javascript 627 | reg.installing 628 | reg.waiting 629 | reg.active 630 | ``` 631 | 632 | - We can also operate on the service workers themselves. 633 | 634 | ```javascript 635 | var sw = reg.installing 636 | console.log(sw.state) // ...logs "installing" 637 | // state can also be: 638 | // "installed" 639 | // "activating" 640 | // "activated" 641 | // "redundant" means the service worker has been thrown away 642 | sw.addEventListener("statechange", function() { 643 | // statechange event created when the service worker changes 644 | }) 645 | ``` 646 | 647 | - `navigator.serviceWorker.controller` refers to the service worker controlling the current page. 648 | - We can tell the user about updates whether they are already present, in progress, or will start later. 649 | 650 | ### 2.23. Quiz: Adding UX Quiz 651 | 652 | Git 653 | 654 | - Checkout to new branch: 655 | 656 | ```shell 657 | $ git reset --hard 658 | $ git checkout origin/task-update-notify 659 | ``` 660 | 661 |
662 | Solution 663 | 664 | - Code: For this exercise, we worked with _wittr/public/js/main/IndexController.js_. 665 | 666 | ```javascript 667 | import PostsView from "./views/Posts" 668 | import ToastsView from "./views/Toasts" 669 | import idb from "idb" 670 | 671 | export default function IndexController(container) { 672 | this._container = container 673 | this._postsView = new PostsView(this._container) 674 | this._toastsView = new ToastsView(this._container) 675 | this._lostConnectionToast = null 676 | this._openSocket() 677 | this._registerServiceWorker() 678 | } 679 | 680 | IndexController.prototype._registerServiceWorker = function() { 681 | if (!navigator.serviceWorker) return 682 | 683 | var indexController = this 684 | 685 | navigator.serviceWorker.register("/sw.js").then(function(reg) { 686 | // TODO: if there's no controller, this page wasn't loaded 687 | // via a service worker, so they're looking at the latest version. 688 | // In that case, exit early 689 | if (!navigator.serviceWorker.controller) { 690 | return 691 | } 692 | 693 | // TODO: if there's an updated worker already waiting, call 694 | // indexController._updateReady() 695 | if (reg.waiting) { 696 | indexController._updateReady() 697 | return 698 | } 699 | 700 | // TODO: if there's an updated worker installing, track its 701 | // progress. If it becomes "installed", call 702 | // indexController._updateReady() 703 | if (reg.installing) { 704 | indexController._trackInstalling(reg.installing) 705 | return 706 | } 707 | 708 | // TODO: otherwise, listen for new installing workers arriving. 709 | // If one arrives, track its progress. 710 | // If it becomes "installed", call 711 | // indexController._updateReady() 712 | reg.addEventListener("updatefound", function() { 713 | indexController._trackInstalling(reg.installing) 714 | }) 715 | }) 716 | } 717 | 718 | IndexController.prototype._trackInstalling = function(worker) { 719 | var indexController = this 720 | // if service worker is installed, notify user 721 | worker.addEventListener("statechange", function() { 722 | if (worker.state == "installed") { 723 | indexController._updateReady() 724 | } 725 | }) 726 | } 727 | 728 | IndexController.prototype._updateReady = function() { 729 | var toast = this._toastsView.show("New version available", { 730 | buttons: ["whatever"] 731 | }) 732 | } 733 | 734 | // open a connection to the server for live updates 735 | IndexController.prototype._openSocket = function() { 736 | var indexController = this 737 | var latestPostDate = this._postsView.getLatestPostDate() 738 | 739 | // create a url pointing to /updates with the ws protocol 740 | var socketUrl = new URL("/updates", window.location) 741 | socketUrl.protocol = "ws" 742 | 743 | if (latestPostDate) { 744 | socketUrl.search = "since=" + latestPostDate.valueOf() 745 | } 746 | 747 | // this is a little hack for the settings page's tests, 748 | // it isn't needed for Wittr 749 | socketUrl.search += "&" + location.search.slice(1) 750 | 751 | var ws = new WebSocket(socketUrl.href) 752 | 753 | // add listeners 754 | ws.addEventListener("open", function() { 755 | if (indexController._lostConnectionToast) { 756 | indexController._lostConnectionToast.hide() 757 | } 758 | }) 759 | 760 | ws.addEventListener("message", function(event) { 761 | requestAnimationFrame(function() { 762 | indexController._onSocketMessage(event.data) 763 | }) 764 | }) 765 | 766 | ws.addEventListener("close", function() { 767 | // tell the user 768 | if (!indexController._lostConnectionToast) { 769 | indexController._lostConnectionToast = indexController._toastsView.show( 770 | "Unable to connect. Retrying…" 771 | ) 772 | } 773 | 774 | // try and reconnect in 5 seconds 775 | setTimeout(function() { 776 | indexController._openSocket() 777 | }, 5000) 778 | }) 779 | } 780 | 781 | // called when the web socket sends message data 782 | IndexController.prototype._onSocketMessage = function(data) { 783 | var messages = JSON.parse(data) 784 | this._postsView.addPosts(messages) 785 | } 786 | ``` 787 | 788 | - After completing the IndexController.js code, make a random change to the service worker in _sw/index.js_: 789 | 790 | ```javascript 791 | var staticCacheName = "wittr-static-v2" 792 | // random change to trigger user notification 793 | ``` 794 | 795 | - Refresh the app at [http://localhost:8888/](http://localhost:8888/) 796 | - Test on settings page at [http://localhost:8889/](http://localhost:8889/) with "update-notify" 797 | - Got it working! 798 | 799 |
800 | 801 | ### 2.24. Triggering an Update 802 | 803 | - Code previewed in intro video: 804 | 805 | ```javascript 806 | // have new service worker take over right when refresh is pressed 807 | self.skipWaiting() 808 | 809 | // from a page 810 | reg.installing.postMessage({ foo: "bar" }) 811 | 812 | // in the service worker 813 | self.addEventListener("message", function(event) { 814 | event.data // {foo: 'bar'} 815 | }) 816 | 817 | // when the new service worker has taken over, signal page reload 818 | navigator.serviceWorker.addEventListener("controllerchange", function() { 819 | // navigator.serviceWorker.controller has changed 820 | }) 821 | ``` 822 | 823 | ### 2.25. Quiz: Triggering an Update Quiz 824 | 825 | Git 826 | 827 | - Checkout to new branch: 828 | 829 | ```shell 830 | $ git reset --hard 831 | $ git checkout origin/task-update-reload 832 | ``` 833 | 834 |
835 | Solution 836 | 837 | - Code: 838 | - _wittr/public/js/main/IndexController.js_ 839 | 840 | ```javascript 841 | import PostsView from "./views/Posts" 842 | import ToastsView from "./views/Toasts" 843 | import idb from "idb" 844 | 845 | export default function IndexController(container) { 846 | this._container = container 847 | this._postsView = new PostsView(this._container) 848 | this._toastsView = new ToastsView(this._container) 849 | this._lostConnectionToast = null 850 | this._openSocket() 851 | this._registerServiceWorker() 852 | } 853 | 854 | IndexController.prototype._registerServiceWorker = function() { 855 | if (!navigator.serviceWorker) return 856 | 857 | var indexController = this 858 | 859 | navigator.serviceWorker.register("/sw.js").then(function(reg) { 860 | if (!navigator.serviceWorker.controller) { 861 | return 862 | } 863 | 864 | if (reg.waiting) { 865 | indexController._updateReady(reg.waiting) 866 | return 867 | } 868 | 869 | if (reg.installing) { 870 | indexController._trackInstalling(reg.installing) 871 | return 872 | } 873 | 874 | reg.addEventListener("updatefound", function() { 875 | indexController._trackInstalling(reg.installing) 876 | }) 877 | }) 878 | 879 | // TODO: listen for the controlling service worker changing 880 | // and reload the page 881 | navigator.serviceWorker.addEventListener("controllerchange", function() { 882 | window.location.reload() 883 | }) 884 | } 885 | 886 | IndexController.prototype._trackInstalling = function(worker) { 887 | var indexController = this 888 | worker.addEventListener("statechange", function() { 889 | if (worker.state == "installed") { 890 | indexController._updateReady(worker) 891 | } 892 | }) 893 | } 894 | 895 | IndexController.prototype._updateReady = function(worker) { 896 | var toast = this._toastsView.show("New version available", { 897 | buttons: ["refresh", "dismiss"] 898 | }) 899 | 900 | toast.answer.then(function(answer) { 901 | if (answer != "refresh") return 902 | // TODO: tell the service worker to skipWaiting 903 | worker.postMessage({ action: "skipWaiting" }) 904 | }) 905 | } 906 | 907 | // open a connection to the server for live updates 908 | IndexController.prototype._openSocket = function() { 909 | var indexController = this 910 | var latestPostDate = this._postsView.getLatestPostDate() 911 | 912 | // create a url pointing to /updates with the ws protocol 913 | var socketUrl = new URL("/updates", window.location) 914 | socketUrl.protocol = "ws" 915 | 916 | if (latestPostDate) { 917 | socketUrl.search = "since=" + latestPostDate.valueOf() 918 | } 919 | 920 | // this is a little hack for the settings page's tests, 921 | // it isn't needed for Wittr 922 | socketUrl.search += "&" + location.search.slice(1) 923 | 924 | var ws = new WebSocket(socketUrl.href) 925 | 926 | // add listeners 927 | ws.addEventListener("open", function() { 928 | if (indexController._lostConnectionToast) { 929 | indexController._lostConnectionToast.hide() 930 | } 931 | }) 932 | 933 | ws.addEventListener("message", function(event) { 934 | requestAnimationFrame(function() { 935 | indexController._onSocketMessage(event.data) 936 | }) 937 | }) 938 | 939 | ws.addEventListener("close", function() { 940 | // tell the user 941 | if (!indexController._lostConnectionToast) { 942 | indexController._lostConnectionToast = indexController._toastsView.show( 943 | "Unable to connect. Retrying…" 944 | ) 945 | } 946 | 947 | // try and reconnect in 5 seconds 948 | setTimeout(function() { 949 | indexController._openSocket() 950 | }, 5000) 951 | }) 952 | } 953 | 954 | // called when the web socket sends message data 955 | IndexController.prototype._onSocketMessage = function(data) { 956 | var messages = JSON.parse(data) 957 | this._postsView.addPosts(messages) 958 | } 959 | ``` 960 | 961 | - _sw/index.js_ 962 | 963 | ```javascript 964 | // TODO: listen for the "message" event, and call 965 | // skipWaiting if you get the appropriate message 966 | self.addEventListener("message", function(event) { 967 | if (event.data.action == "skipWaiting") { 968 | self.skipWaiting() 969 | } 970 | }) 971 | ``` 972 | 973 | - Again, after completing the IndexController.js code, make a random change to the service worker in _sw/index.js_: 974 | 975 | ```javascript 976 | var staticCacheName = "wittr-static-v2" 977 | // another random change to trigger user notification 978 | ``` 979 | 980 | - Refresh the app at [http://localhost:8888/](http://localhost:8888/) 981 | - As before, you will see a notification that the app has changed, but now there is an option to refresh the page. Don't need to refresh again, we'll do this in the next step. 982 | - Test on settings page at [http://localhost:8889/](http://localhost:8889/) with "update-reload" 983 | - You then have eight seconds to refresh. 984 | - Jake mentions SVGOMG for SVG compression. 985 | 986 |
987 | 988 | #### Goals after 2.25 989 | 990 | - ~~Unobtrusive app updates~~ 991 | - ~~Get the user onto the latest version~~ 992 | - Continually update cache of posts 993 | - Cache photos 994 | - Cache avatars 995 | 996 | ### 2.26. Quiz: Caching the Page Skeleton 997 | 998 | Git 999 | 1000 | - Checkout to new branch: 1001 | 1002 | ```shell 1003 | $ git reset --hard 1004 | $ git checkout origin/task-page-skeleton 1005 | ``` 1006 | 1007 |
1008 | Solution 1009 | 1010 | - Code: 1011 | - In the service worker _sw/index.js_, cache `/skeleton` instead of the root page, and respond to requests for the root page with `/skeleton` instead: 1012 | 1013 | ```javascript 1014 | var staticCacheName = "wittr-static-v4" 1015 | 1016 | self.addEventListener("install", function(event) { 1017 | // TODO: cache /skeleton rather than the root page 1018 | 1019 | event.waitUntil( 1020 | caches.open(staticCacheName).then(function(cache) { 1021 | return cache.addAll([ 1022 | "/skeleton", 1023 | "js/main.js", 1024 | "css/main.css", 1025 | "imgs/icon.png", 1026 | "https://fonts.gstatic.com/s/roboto/v15/2UX7WLTfW3W8TclTUvlFyQ.woff", 1027 | "https://fonts.gstatic.com/s/roboto/v15/d-6IYplOFocCacKzxwXSOD8E0i7KZn-EPnyo3HZu7kw.woff" 1028 | ]) 1029 | }) 1030 | ) 1031 | }) 1032 | 1033 | self.addEventListener("activate", function(event) { 1034 | event.waitUntil( 1035 | caches.keys().then(function(cacheNames) { 1036 | return Promise.all( 1037 | cacheNames 1038 | .filter(function(cacheName) { 1039 | return ( 1040 | cacheName.startsWith("wittr-") && cacheName != staticCacheName 1041 | ) 1042 | }) 1043 | .map(function(cacheName) { 1044 | return caches.delete(cacheName) 1045 | }) 1046 | ) 1047 | }) 1048 | ) 1049 | }) 1050 | 1051 | self.addEventListener("fetch", function(event) { 1052 | // TODO: respond to requests for the root page with 1053 | // the page skeleton from the cache 1054 | // note that we wrote URL three different ways in the line below 1055 | var requestUrl = new URL(event.request.url) 1056 | 1057 | if (requestUrl.origin === location.origin) { 1058 | if (requestUrl.pathname === "/") { 1059 | event.respondWith(caches.match("/skeleton")) 1060 | return 1061 | } 1062 | } 1063 | 1064 | event.respondWith( 1065 | caches.match(event.request).then(function(response) { 1066 | return response || fetch(event.request) 1067 | }) 1068 | ) 1069 | }) 1070 | 1071 | self.addEventListener("message", function(event) { 1072 | if (event.data.action === "skipWaiting") { 1073 | self.skipWaiting() 1074 | } 1075 | }) 1076 | ``` 1077 | 1078 | - Refresh the app at [http://localhost:8888/](http://localhost:8888/) and you will now need to also click "REFRESH" in the popup. 1079 | - Test on settings page at [http://localhost:8889/](http://localhost:8889/) with "serve-skeleton". 1080 | 1081 |
1082 | 1083 | Next we will use "one of the web's most hated APIs," `IndexedDB`, and "tame" it to use the good aspects "without getting burned." 1084 | 1085 | [(Back to top)](#top) 1086 | 1087 | ## Lesson feedback 1088 | 1089 | Long lesson, but very informative. 1090 | 1091 | I would encourage students to track their code and take notes in a separate Markdown file. It's very easy to lose data with all the git resets. 1092 | 1093 | [Next lesson](offline-3-indexeddb.md) 1094 | 1095 | [Previous lesson](offline-1-benefits.md) 1096 | 1097 | [(Back to top)](#top) 1098 | -------------------------------------------------------------------------------- /es6/es6-1-syntax.md: -------------------------------------------------------------------------------- 1 | # JavaScript ES6 Syntax 2 | 3 | 4 | Udacity logo 5 | 6 | 7 | [ES6 - JavaScript Improved course](https://www.udacity.com/course/es6-javascript-improved--ud356) lesson 1/4 8 | 9 | Udacity Google Mobile Web Specialist Nanodegree program part 3 lesson 04 10 | 11 | Udacity Grow with Google Scholarship challenge course lesson 06 12 | 13 | Brendon Smith 14 | 15 | [br3ndonland](https://github.com/br3ndonland) 16 | 17 | ## Table of Contents 18 | 19 | - [Lesson plan](#lesson-plan) 20 | - [Lesson Part 1: Declaring Variables](#lesson-part-1-declaring-variables) 21 | - [1.01. Harmony, ES6, ES2015](#101-harmony-es6-es2015) 22 | - [1.02. Let and Const](#102-let-and-const) 23 | - [1.03. Quiz: Using Let and Const (1-1)](#103-quiz-using-let-and-const-1-1) 24 | - [1.04. Template Literals](#104-template-literals) 25 | - [1.05. Quiz: Build an HTML Fragment (1-2)](#105-quiz-build-an-html-fragment-1-2) 26 | - [1.06. Destructuring](#106-destructuring) 27 | - [1.07. Quiz: Destructuring Arrays (1-3)](#107-quiz-destructuring-arrays-1-3) 28 | - [1.08. Object Literal Shorthand](#108-object-literal-shorthand) 29 | - [1.09. Lesson 1 Checkup](#109-lesson-1-checkup) 30 | - [Lesson Part 2: Iteration](#lesson-part-2-iteration) 31 | - [1.10. Iteration](#110-iteration) 32 | - [1.11. Family of For Loops](#111-family-of-for-loops) 33 | - [1.12. For...of Loop](#112-forof-loop) 34 | - [1.13. Quiz: Writing a For...of Loop (1-4)](#113-quiz-writing-a-forof-loop-1-4) 35 | - [1.14. Spread... Operator](#114-spread-operator) 36 | - [1.15. ...Rest Parameter](#115-rest-parameter) 37 | - [1.16. Quiz: Using the Rest Parameter (1-5)](#116-quiz-using-the-rest-parameter-1-5) 38 | - [1.17. Lesson 1 Summary](#117-lesson-1-summary) 39 | - [Feedback on JavaScript ES6 lesson 1/4](#feedback-on-javascript-es6-lesson-14) 40 | 41 | ## Lesson plan 42 | 43 | 1. Syntax 44 | 2. Functions 45 | 3. Built-Ins 46 | 4. Developer-Fu 47 | 48 | ## Lesson Part 1: Declaring Variables 49 | 50 | ### 1.01. Harmony, ES6, ES2015 51 | 52 | > Welcome to the course on ES6! We're glad you're here! 👋 53 | > 54 | > This course is all about the new changes brought to the JavaScript programming language. We're expecting that you've worked with JavaScript for a couple of years and have an intermediate level of experience with the language. 55 | > 56 | > If you're new to the JavaScript language or would like a refresher, check out our [Intro to JavaScript course](https://www.udacity.com/course/intro-to-javascript--ud803). 57 | > 58 | > Follow us! 59 | > 60 | > - @parkesrjames 61 | > - @richardkalehoff 62 | 63 | ES6 brings some much-needed modernization to JavaScript. This is the biggest update to JavaScript ever. 64 | 65 | These lessons are part of the free [ES6 - JavaScript Improved course](https://www.udacity.com/course/es6-javascript-improved--ud356). 66 | 67 | The [ES6 spec](https://www.ecma-international.org/ecma-262/6.0/) came out in June 2015. 68 | 69 | ### 1.02. Let and Const 70 | 71 | #### Intro 72 | 73 | `let` and `const` are new keywords added to JavaScript. 74 | 75 | > There are now two new ways to declare variables in JavaScript: let and const. 76 | > 77 | > Up until now, the only way to declare a variable in JavaScript was to use the keyword var. To understand why let and const were added, it’s probably best to look at an example of when using var can get us into trouble. 78 | > 79 | > Take a look at the following code. 80 | > 81 | > **Question 1 of 3** 82 | > 83 | > What do you expect to be the output from running `getClothing(false)`? 84 | 85 | ```javascript 86 | function getClothing(isCold) { 87 | if (isCold) { 88 | var freezing = "Grab a jacket!" 89 | } else { 90 | var hot = "It’s a shorts kind of day." 91 | console.log(freezing) 92 | } 93 | } 94 | ``` 95 | 96 | Answer: `undefined`. Got this on the first try. 97 | 98 | #### Hoisting 99 | 100 | > Hoisting is a result of how JavaScript is interpreted by your browser. Essentially, before any JavaScript code is executed, all variables are "hoisted", which means they're raised to the top of the function scope. So at run-time, the getClothing() function actually looks more like this: 101 | 102 | JavaScript hoisting 103 | 104 | #### let and const 105 | 106 | > Variables declared with `let` and `const` eliminate this specific issue of hoisting because they’re **scoped to the block, not to the function.** Previously, when you used `var`, variables were either scoped globally or locally to an entire function scope. 107 | > 108 | > If a variable is declared using `let` or `const` inside a block of code (denoted by curly braces `{ }`), then the variable is stuck in what is known as the **temporal dead zone** until the variable’s declaration is processed. This behavior prevents variables from being accessed only until after they’ve been declared. 109 | > 110 | > **Question 2 of 3** 111 | > 112 | > What do you expect to be the output from running `getClothing(false)`? 113 | 114 | ```javascript 115 | function getClothing(isCold) { 116 | if (isCold) { 117 | const freezing = "Grab a jacket!" 118 | } else { 119 | const hot = "It’s a shorts kind of day." 120 | console.log(freezing) 121 | } 122 | } 123 | ``` 124 | 125 | Answer: `ReferenceError: freezing is not defined`. 126 | 127 | > Because freezing is not declared inside the else statement, the function scope, or the global scope, a ReferenceError is thrown. 128 | 129 | #### Rules for using let and const 130 | 131 | > `let` and `const` also have some other interesting properties. 132 | > 133 | > - Variables declared with `let` can be reassigned, but can’t be redeclared in the same scope. 134 | > - Variables declared with `const` must be assigned an initial value, but can’t be redeclared in the same scope, and can’t be reassigned. 135 | > 136 | > **Question 3 of 3** 137 | > 138 | > What do you expect to be output from running the following code? 139 | 140 | ```javascript 141 | let instructor = "James" 142 | instructor = "Richard" 143 | console.log(instructor) 144 | ``` 145 | 146 | Answer: Richard. Got it on the first try. 147 | 148 | > This is the correct way to use `let`. Use `let` to declare variables when you plan on changing the value of a variable later in your code. 149 | 150 | #### Use cases 151 | 152 | > The big question is when should you use `let` and `const`? The general rule of thumb is as follows: 153 | > 154 | > - use `let` when you plan to reassign new values to a variable, and 155 | > - use `const` when you don’t plan on reassigning new values to a variable. 156 | > 157 | > Since `const` is the strictest way to declare a variable, we suggest that you always declare variables with `const` because it'll make your code easier to reason about since you know the identifiers won't change throughout the lifetime of your program. If you find that you need to update a variable or change it, then go back and switch it from `const` to `let`. 158 | > 159 | > That’s pretty straightforward, right? But what about `var`? 160 | 161 | #### What about var 162 | 163 | > **Is there any reason to use `var` anymore? Not really.** 164 | > 165 | > There are some arguments that can be made for using `var` in situations where you want to globally define variables, but this is often considered bad practice and should be avoided. From now on, we suggest ditching `var` in place of using `let` and `const`. 166 | 167 | ### 1.03. Quiz: Using Let and Const (1-1) 168 | 169 | Quiz 170 | 171 | Replace `var` with `let` or `const` 172 | 173 | ```javascript 174 | /* 175 | - Programming Quiz: Using Let and Const (1-1) 176 | */ 177 | 178 | var CHARACTER_LIMIT = 255 179 | var posts = [ 180 | "#DeepLearning transforms everything from self-driving cars to language translations. AND it's our new Nanodegree!", 181 | "Within your first week of the VR Developer Nanodegree Program, you'll make your own virtual reality app", 182 | "I just finished @udacity's Front-End Web Developer Nanodegree. Check it out!" 183 | ] 184 | 185 | // prints posts to the console 186 | function displayPosts() { 187 | for (var i = 0; i < posts.length; i++) { 188 | console.log(posts[i].slice(0, CHARACTER_LIMIT)) 189 | } 190 | } 191 | 192 | displayPosts() 193 | ``` 194 | 195 |
196 | Solution 197 | 198 | ```javascript 199 | /* 200 | - Programming Quiz: Using Let and Const (1-1) 201 | */ 202 | 203 | const CHARACTER_LIMIT = 255 204 | const posts = [ 205 | "#DeepLearning transforms everything from self-driving cars to language translations. AND it's our new Nanodegree!", 206 | "Within your first week of the VR Developer Nanodegree Program, you'll make your own virtual reality app", 207 | "I just finished @udacity's Front-End Web Developer Nanodegree. Check it out!" 208 | ] 209 | 210 | // prints posts to the console 211 | function displayPosts() { 212 | for (let i = 0; i < posts.length; i++) { 213 | console.log(posts[i].slice(0, CHARACTER_LIMIT)) 214 | } 215 | } 216 | 217 | displayPosts() 218 | ``` 219 | 220 | > What Went Well 221 | > 222 | > - Your code should have a variable i 223 | > - Your code should have a variable posts 224 | > - Your code should have a variable CHARACTER_LIMIT 225 | > - Your variable i should be declared using let 226 | > - Your variable posts should be declared using const 227 | > - Your variable CHARACTER_LIMIT should be declared using const 228 | > 229 | > Feedback 230 | > 231 | > Your answer passed all our tests! Awesome job! 232 | 233 |
234 | 235 | ### 1.04. Template Literals 236 | 237 | #### Intro to template literals 238 | 239 | > Prior to ES6, the old way to concatenate strings together was by using the string concatenation operator ( + ). 240 | > 241 | > ```javascript 242 | > const student = { 243 | > name: "Richard Kalehoff", 244 | > guardian: "Mr. Kalehoff" 245 | > } 246 | > 247 | > const teacher = { 248 | > name: "Mrs. Wilson", 249 | > room: "N231" 250 | > } 251 | > 252 | > let message = 253 | > student.name + 254 | > " please see " + 255 | > teacher.name + 256 | > " in " + 257 | > teacher.room + 258 | > " to pick up your report card." 259 | > ``` 260 | > 261 | > Returns: 262 | > 263 | > ```text 264 | > Richard Kalehoff please see Mrs. Wilson in N231 to pick up your report card. 265 | > ``` 266 | > 267 | > This works alright, but it gets more complicated when you need to build multi-line strings. 268 | > 269 | > ```javascript 270 | > let note = 271 | > teacher.name + 272 | > ",\n\n" + 273 | > "Please excuse " + 274 | > student.name + 275 | > ".\n" + 276 | > "He is recovering from the flu.\n\n" + 277 | > "Thank you,\n" + 278 | > student.guardian 279 | > ``` 280 | > 281 | > Returns: 282 | > 283 | > ```text 284 | > Mrs. Wilson, 285 | > 286 | > Please excuse Richard Kalehoff. 287 | > He is recovering from the flu. 288 | > 289 | > Thank you, 290 | > Mr. Kalehoff 291 | > ``` 292 | > 293 | > However, that’s changed with the introduction of **template literals** (previously referred to as "template strings" in development releases of ES6). 294 | > 295 | > _NOTE:_ As an alternative to using the string concatenation operator ( + ), you can use the `concat()` method, but both options are rather clunky for simulating true string interpolation. 296 | 297 | #### Template Literals 298 | 299 | > **Template literals** are essentially string literals that include embedded expressions. 300 | > 301 | > Denoted with backticks instead of single quotes or double quotes, template literals can contain placeholders which are represented using backticks. This makes it much easier to build strings. 302 | > 303 | > Here's the previous example using template literals. 304 | > 305 | > ```javascript 306 | > const student = { 307 | > name: "Richard Kalehoff", 308 | > guardian: "Mr. Kalehoff" 309 | > } 310 | > 311 | > const teacher = { 312 | > name: "Mrs. Wilson", 313 | > room: "N231" 314 | > } 315 | > 316 | > let message = `${student.name} please see ${teacher.name} in ${teacher.room} to pick up your report card.` 317 | > console.log(message) 318 | > ``` 319 | > 320 | > Returns: 321 | > 322 | > ```text 323 | > Richard Kalehoff please see Mrs. Wilson in N231 to pick up your report card. 324 | > ``` 325 | > 326 | > By using template literals, you can drop the quotes along with the string concatenation operator. Also, you can reference the object's properties inside expressions. 327 | 328 | Template literals are similar to [Python new style string formatting](https://pyformat.info/), so they made sense to me. 329 | 330 | Here's the code from above in Python for comparison: 331 | 332 | ```python 333 | student = { 334 | "name": "Richard Kalehoff", 335 | "guardian": "Mr. Kalehoff" 336 | } 337 | 338 | teacher = { 339 | "name": "Mrs. Wilson", 340 | "room": "N231" 341 | } 342 | 343 | message = ('{} please see {} in {} to pick up your report card.' 344 | .format(student['name'], teacher['name'], teacher['room'])) 345 | print(message) 346 | 347 | ``` 348 | 349 | The JSON is read by Python as a dictionary. Note that it is no longer necessary to `import json` or parse the JSON with `json.loads(student)`. 350 | 351 | #### Quiz 352 | 353 | > Change the `greeting` string below to use a template literal. Also, feel free to swap in your name for the placeholder. 354 | 355 | ```javascript 356 | /* 357 | - Instructions: Change the `greeting` string to use a template literal. 358 | */ 359 | 360 | const myName = "[NAME]" 361 | const greeting = "Hello, my name is " + myName 362 | console.log(greeting) 363 | ``` 364 | 365 |
366 | Solution 367 | 368 | ```javascript 369 | const myName = "[NAME]" 370 | const greeting = `Hello, my name is ${myName}` 371 | console.log(greeting) 372 | ``` 373 | 374 | > What Went Well 375 | > 376 | > - Your code should have a variable myName 377 | > - Your code should have a variable greeting 378 | > - Your code should have a template literal greeting 379 | > - Your template literal should match the original greeting string 380 | > 381 | > Feedback 382 | > 383 | > Your answer passed all our tests! Awesome job! 384 | 385 |
386 | 387 | > ...but what about the multi-line example from before? 388 | > 389 | > ```javascript 390 | > var note = 391 | > teacher.name + 392 | > ",\n\n" + 393 | > "Please excuse " + 394 | > student.name + 395 | > ".\n" + 396 | > "He is recovering from the flu.\n\n" + 397 | > "Thank you,\n" + 398 | > student.guardian 399 | > ``` 400 | > 401 | > Becomes: 402 | > 403 | > ```javascript 404 | > var note = `${teacher.name}, 405 | > 406 | > Please excuse ${student.name}. 407 | > He is recovering from the flu. 408 | > 409 | > Thank you, 410 | > ${student.guardian}` 411 | > ``` 412 | > 413 | > Here’s where template literals really shine. In the animation above, the quotes and string concatenation operator have been dropped, as well as the newline characters ( \n ). That's because template literals also preserve newlines as part of the string! 414 | > 415 | > TIP: Embedded expressions inside template literals can do more than just reference variables. You can perform operations, call functions and use loops inside embedded expressions! 416 | 417 | ### 1.05. Quiz: Build an HTML Fragment (1-2) 418 | 419 | Quiz 420 | 421 | > Modify the createAnimalTradingCardHTML() function to use a template literal for cardHTML. 422 | 423 | ```javascript 424 | /* 425 | - Programming Quiz: Build an HTML Fragment (1-2) 426 | */ 427 | 428 | const cheetah = { 429 | name: "Cheetah", 430 | scientificName: "Acinonyx jubatus", 431 | lifespan: "10-12 years", 432 | speed: "68-75 mph", 433 | diet: "carnivore", 434 | summary: 435 | "Fastest mammal on land, the cheetah can reach speeds of 60 or perhaps even 70 miles (97 or 113 kilometers) an hour over short distances. It usually chases its prey at only about half that speed, however. After a chase, a cheetah needs half an hour to catch its breath before it can eat.", 436 | fact: 437 | "Cheetahs have “tear marks” that run from the inside corners of their eyes down to the outside edges of their mouth." 438 | } 439 | 440 | // creates an animal trading card 441 | function createAnimalTradingCardHTML(animal) { 442 | const cardHTML = 443 | '
' + 444 | '

' + 445 | animal.name + 446 | "

" + 447 | '' +
 450 |     animal.name +
 451 |     '' + 452 | '
' + 453 | '

' + 454 | animal.fact + 455 | "

" + 456 | '
    ' + 457 | '
  • Scientific Name: ' + 458 | animal.scientificName + 459 | "
  • " + 460 | '
  • Average Lifespan: ' + 461 | animal.lifespan + 462 | "
  • " + 463 | '
  • Average Speed: ' + 464 | animal.speed + 465 | "
  • " + 466 | '
  • Diet: ' + 467 | animal.diet + 468 | "
  • " + 469 | "
" + 470 | '

' + 471 | animal.summary + 472 | "

" + 473 | "
" + 474 | "
" 475 | 476 | return cardHTML 477 | } 478 | 479 | console.log(createAnimalTradingCardHTML(cheetah)) 480 | ``` 481 | 482 |
483 | Solution 484 | 485 | ```javascript 486 | /* 487 | - Programming Quiz: Build an HTML Fragment (1-2) 488 | */ 489 | 490 | const cheetah = { 491 | name: "Cheetah", 492 | scientificName: "Acinonyx jubatus", 493 | lifespan: "10-12 years", 494 | speed: "68-75 mph", 495 | diet: "carnivore", 496 | summary: 497 | "Fastest mammal on land, the cheetah can reach speeds of 60 or perhaps even 70 miles (97 or 113 kilometers) an hour over short distances. It usually chases its prey at only about half that speed, however. After a chase, a cheetah needs half an hour to catch its breath before it can eat.", 498 | fact: 499 | "Cheetahs have “tear marks” that run from the inside corners of their eyes down to the outside edges of their mouth." 500 | } 501 | 502 | // creates an animal trading card 503 | function createAnimalTradingCardHTML(animal) { 504 | const cardHTML = `
505 |

${animal.name}

506 | ${animal.name} 507 |
508 |

${animal.fact}

509 |
    510 |
  • Scientific Name: ${animal.scientificName}
  • 511 |
  • Average Lifespan: ${animal.lifespan}
  • 512 |
  • Average Speed: ${animal.speed}
  • 513 |
  • Diet: ${animal.diet}
  • 514 |
515 |

${animal.summary}

516 |
517 |
` 518 | 519 | return cardHTML 520 | } 521 | 522 | console.log(createAnimalTradingCardHTML(cheetah)) 523 | ``` 524 | 525 | > What Went Well 526 | > 527 | > - Your code should have an object cheetah 528 | > - Your code should have a function createAnimalTradingCardHTML() 529 | > - The createAnimalTradingCardHTML function should have a variable cardHTML 530 | > - The cardHTML variable should be a template literal 531 | > 532 | > Feedback 533 | > 534 | > Your answer passed all our tests! Awesome job! 535 | 536 |
537 | 538 | ### 1.06. Destructuring 539 | 540 | #### Intro to destructuring 541 | 542 | > In ES6, you can extract data from arrays and objects into distinct variables using **destructuring**. 543 | > 544 | > This probably sounds like something you’ve done before, for example, look at the two code snippets below that extract data using pre-ES6 techniques: 545 | > 546 | > ```javascript 547 | > const point = [10, 25, -34] 548 | > 549 | > const x = point[0] 550 | > const y = point[1] 551 | > const z = point[2] 552 | > 553 | > console.log(x, y, z) 554 | > ``` 555 | > 556 | > Prints: `10 25 -34` 557 | > 558 | > The example above shows extracting values from an array. 559 | > 560 | > ```javascript 561 | > const gemstone = { 562 | > type: "quartz", 563 | > color: "rose", 564 | > carat: 21.29 565 | > } 566 | > 567 | > const type = gemstone.type 568 | > const color = gemstone.color 569 | > const carat = gemstone.carat 570 | > 571 | > console.log(type, color, carat) 572 | > ``` 573 | > 574 | > Prints: `quartz rose 21.29` 575 | > 576 | > And this example shows extracting values from an object. 577 | > 578 | > Both are pretty straightforward, however, neither of these examples are actually using destructuring. 579 | > 580 | > So what exactly is **destructuring**? 581 | 582 | #### Destructuring 583 | 584 | > **Destructuring** borrows inspiration from languages like [Perl](https://en.wikipedia.org/wiki/Perl) and [Python](https://en.wikipedia.org/wiki/Python_%28programming_language%29) by allowing you to specify the elements you want to extract from an array or object _on the left side of an assignment_. It sounds a little weird, but you can actually achieve the same result as before, but with much less code; and it's still easy to understand. 585 | > 586 | > Let’s take a look at both examples rewritten using destructuring. 587 | 588 | ##### Destructuring values from an array 589 | 590 | > ```javascript 591 | > const point = [10, 25, -34] 592 | > 593 | > const [x, y, z] = point 594 | > 595 | > console.log(x, y, z) 596 | > ``` 597 | > 598 | > Prints: `10 25 -34` 599 | > 600 | > In this example, the brackets `[ ]` represent the array being destructured and `x`, `y`, and `z` represent the variables where you want to store the values from the array. Notice how you don’t have to specify the indexes for where to extract the values from because the indexes are implied. 601 | > 602 | > TIP: You can also ignore values when destructuring arrays. For example, const [x, , z] = point; ignores the y coordinate and discards it. 603 | > 604 | > **Question 1 of 2** 605 | > 606 | > What do you expect to be the value of `second` after running the following code? 607 | > 608 | > ```javascript 609 | > let positions = ["Gabrielle", "Jarrod", "Kate", "Fernando", "Mike", "Walter"] 610 | > let [first, second, third] = positions 611 | > ``` 612 | 613 | Answer: Jarrod. Got it on the first try. 614 | 615 | > The variables first, second, and third get populated with the first 3 values in the positions array while the remaining values are ignored. 616 | 617 | ##### Destructuring values from an object 618 | 619 | > ```javascript 620 | > const gemstone = { 621 | > type: "quartz", 622 | > color: "rose", 623 | > carat: 21.29 624 | > } 625 | > 626 | > const { type, color, carat } = gemstone 627 | > 628 | > console.log(type, color, carat) 629 | > ``` 630 | > 631 | > Prints: `quartz rose 21.29` 632 | > 633 | > In this example, the curly braces `{ }` represent the object being destructured and `type`, `color`, and `carat` represent the variables where you want to store the properties from the object. Notice how you don’t have to specify the property from where to extract the values. Because `gemstone` has a property named `type`, the value is automatically stored in the `type` variable. Similarly, `gemstone` has a `color` property, so the value of `color` automatically gets stored in the `color` variable. And it's the same with `carat`. 634 | > 635 | > TIP: You can also specify the values you want to select when destructuring an object. For example, `let {color} = gemstone;` will only select the `color` property from the `gemstone` object. 636 | > 637 | > **Question 2 of 2** 638 | > 639 | > What do you expect to be returned from calling `getArea()`? 640 | > 641 | > ```javascript 642 | > const circle = { 643 | > radius: 10, 644 | > color: "orange", 645 | > getArea: function() { 646 | > return Math.PI - this.radius - this.radius 647 | > }, 648 | > getCircumference: function() { 649 | > return 2 - Math.PI - this.radius 650 | > } 651 | > } 652 | > 653 | > let { radius, getArea, getCircumference } = circle 654 | > ``` 655 | 656 | Answer: NaN. Got it on my second try. 657 | 658 | > Correct! Calling `getArea()` will return `NaN`. When you destructure the object and store the `getArea()` method into the `getArea` variable, it no longer has access to `this` in the `circle` object which results in an area that is `NaN`. 659 | 660 | ### 1.07. Quiz: Destructuring Arrays (1-3) 661 | 662 | Use array destructuring to pull out the three colors from the array of things and store them into the variables one, two, and three. 663 | 664 | ```javascript 665 | /* 666 | - Programming Quiz: Destructuring Arrays (1-3) 667 | * 668 | - Use destructuring to initialize the variables `one`, `two`, and `three` 669 | - with the colors from the `things` array. 670 | */ 671 | 672 | const things = [ 673 | "red", 674 | "basketball", 675 | "paperclip", 676 | "green", 677 | "computer", 678 | "earth", 679 | "udacity", 680 | "blue", 681 | "dogs" 682 | ] 683 | 684 | const one = things 685 | const two = "" 686 | const three = "" 687 | 688 | const colors = `List of Colors 689 | 1. ${one} 690 | 2. ${two} 691 | 3. ${three}` 692 | 693 | console.log(colors) 694 | ``` 695 | 696 |
697 | Solution 698 | 699 | First attempt 700 | 701 | Found that the arrays are zero indexed like Python. 702 | 703 | ```javascript 704 | const things = [ 705 | "red", 706 | "basketball", 707 | "paperclip", 708 | "green", 709 | "computer", 710 | "earth", 711 | "udacity", 712 | "blue", 713 | "dogs" 714 | ] 715 | 716 | const one = things[0] 717 | const two = things[3] 718 | const three = things[7] 719 | 720 | const colors = `List of Colors 721 | 1. ${one} 722 | 2. ${two} 723 | 3. ${three}` 724 | 725 | console.log(colors) 726 | ``` 727 | 728 | ```text 729 | List of Colors 730 | 1. red 731 | 2. green 732 | 3. blue 733 | ``` 734 | 735 | > What Went Well 736 | > 737 | > - Your code should have a variable things 738 | > - Your code should set variable one correctly 739 | > - Your code should set variable two correctly 740 | > - Your code should set variable three correctly 741 | > 742 | > What Went Wrong 743 | > 744 | > - Your code doesn't seem to be using array destructuring to access the colors 745 | > 746 | > Feedback 747 | > 748 | > Not everything is correct yet, but you're close! 749 | 750 | Solution 751 | 752 | The solution produces the same answer, but using commas to skip items in the array instead of slicing. It was a little weird that they taught us the slicing method in [6.06. Destructuring](#606-destructuring), and not the comma method, but expected us to use the comma method here. 753 | 754 | ```javascript 755 | const things = [ 756 | "red", 757 | "basketball", 758 | "paperclip", 759 | "green", 760 | "computer", 761 | "earth", 762 | "udacity", 763 | "blue", 764 | "dogs" 765 | ] 766 | 767 | const [one, , , two, , , , three] = things 768 | 769 | const colors = `List of Colors 770 | 1. ${one} 771 | 2. ${two} 772 | 3. ${three}` 773 | 774 | console.log(colors) 775 | ``` 776 | 777 | ```text 778 | List of Colors 779 | 1. red 780 | 2. green 781 | 3. blue 782 | ``` 783 | 784 | > What Went Well 785 | > 786 | > - Your code should have a variable things 787 | > - Your code should set variable one correctly 788 | > - Your code should set variable two correctly 789 | > - Your code should set variable three correctly 790 | > - Your code should use destructuring 791 | > 792 | > Feedback 793 | > 794 | > Your answer passed all our tests! Awesome job! 795 | 796 |
797 | 798 | ### 1.08. Object Literal Shorthand 799 | 800 | #### Intro to object literal shorthand 801 | 802 | > A recurring trend in ES6 is to remove unnecessary repetition in your code. By removing unnecessary repetition, your code becomes easier to read and more concise. This trend continues with the introduction of new shorthand ways for initializing objects and adding methods to objects. 803 | > 804 | > Let’s see what those look like. 805 | 806 | #### Object literal shorthand 807 | 808 | > You’ve probably written code where an object is being initialized using the same property names as the variable names being assigned to them. 809 | > 810 | > But just in case you haven’t, here’s an example. 811 | > 812 | > ```javascript 813 | > let type = "quartz" 814 | > let color = "rose" 815 | > let carat = 21.29 816 | > 817 | > const gemstone = { 818 | > type: type, 819 | > color: color, 820 | > carat: carat 821 | > } 822 | > 823 | > console.log(gemstone) 824 | > ``` 825 | > 826 | > Prints: `Object {type: "quartz", color: "rose", carat: 21.29}` 827 | > 828 | > Do you see the repetition? Doesn't `type: type`, `color: color`, and `carat: carat` seem redundant? 829 | > 830 | > The good news is that you can remove those duplicate variables names from object properties _if_ the properties have the same name as the variables being assigned to them. 831 | > 832 | > Check it out! 833 | > 834 | > ```javascript 835 | > let type = "quartz" 836 | > let color = "rose" 837 | > let carat = 21.29 838 | > 839 | > let gemstone = { type, color, carat } 840 | > ``` 841 | > 842 | > Speaking of shorthand, there’s also a shorthand way to add methods to objects. 843 | > 844 | > To see how that looks, let’s start by adding a `calculateWorth()` method to our `gemstone` object. The `calculateWorth()` method will tell us how much our gemstone costs based on its `type`, `color`, and `carat`. 845 | > 846 | > ```javascript 847 | > let type = "quartz" 848 | > let color = "rose" 849 | > let carat = 21.29 850 | > 851 | > const gemstone = { 852 | > type, 853 | > color, 854 | > carat, 855 | > calculateWorth: function() { 856 | > // will calculate worth of gemstone based on type, color, and carat 857 | > } 858 | > } 859 | > ``` 860 | > 861 | > In this example, an anonymous function is being assigned to the property `calculateWorth`, but is the **function** keyword really needed? In ES6, it’s not! 862 | 863 | #### Shorthand method names 864 | 865 | > Since you only need to reference the gemstone’s `calculateWorth` property in order to call the function, having the function keyword is redundant, so it can be dropped. 866 | > 867 | > ```javascript 868 | > let gemstone = { 869 | > type, 870 | > color, 871 | > carat, 872 | > calculateWorth() { ... } 873 | > }; 874 | > ``` 875 | 876 | ### 1.09. Lesson 1 Checkup 877 | 878 | We're about halfway through the lesson. 879 | 880 | Declaring variables: 881 | 882 | - `let` & `const` 883 | - template literals 884 | - destructuring 885 | - object literal shorthand 886 | 887 | [(Back to top)](#top) 888 | 889 | ## Lesson Part 2: Iteration 890 | 891 | ### 1.10. Iteration 892 | 893 | Iteration will be an important concept for the rest of the lesson. 894 | 895 | James and Richard explained it with a `for` loop. 896 | 897 | - The variable `i` is typically used to iterate because "iterator" starts with an i. 898 | 899 | ```javascript 900 | const years = ["1999", "2001", "2013", "2016"] 901 | 902 | for (let i = 0; i < years.length; i++) { 903 | console.log(years[i]) 904 | } 905 | ``` 906 | 907 | - ES6 features a new **iterable protocol** that allows JavaScript objects to **define their iteration behavior**. 908 | - The new **for... of loop** iterates over iterable objects. 909 | 910 | ### 1.11. Family of For Loops 911 | 912 | #### Intro to loops 913 | 914 | > The **for...of loop** is the most recent addition to the family of for loops in JavaScript. 915 | > 916 | > It combines the strengths of its siblings, the **for loop** and the **for...in loop**, to loop over any type of data that is **iterable** (meaning it follows the [iterable protocol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) which we'll look at in lesson 3). By default, this includes the data types String, Array, Map, and Set—notably absent from this list is the `Object` data type (i.e. `{}`). Objects are not iterable, by default. 917 | > 918 | > Before we look at the for...of loop, let’s first take a quick look at the other for loops to see where they have weaknesses. 919 | 920 | #### The for loop 921 | 922 | > The for loop is obviously the most common type of loop there is, so this should be a quick refresher. 923 | > 924 | > ```javascript 925 | > const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 926 | > 927 | > for (let i = 0; i < digits.length; i++) { 928 | > console.log(digits[i]) 929 | > } 930 | > ``` 931 | > 932 | > Prints: 933 | > 934 | > ```text 935 | > 0 936 | > 1 937 | > 2 938 | > 3 939 | > 4 940 | > 5 941 | > 6 942 | > 7 943 | > 8 944 | > 9 945 | > ``` 946 | > 947 | > Really the biggest downside of a for loop is having to keep track of the **counter** and **exit condition**. 948 | > 949 | > In this example, we’re using the variable `i` as a counter to keep track of the loop and to access values in the array. We’re also using `digits.length` to determine the exit condition for the loop. If you just glance at this code, it can sometimes be confusing exactly what’s happening; especially for beginners. 950 | > 951 | > While for loops certainly have an advantage when looping through arrays, some data is not structured like an array, so a for loop isn’t always an option. 952 | 953 | #### The for...in loop 954 | 955 | > The for...in loop improves upon the weaknesses of the for loop by **eliminating the counting logic and exit condition**. 956 | > 957 | > ```javascript 958 | > const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 959 | > 960 | > for (const index in digits) { 961 | > console.log(digits[index]) 962 | > } 963 | > ``` 964 | > 965 | > Prints: 966 | > 967 | > ```text 968 | > 0 969 | > 1 970 | > 2 971 | > 3 972 | > 4 973 | > 5 974 | > 6 975 | > 7 976 | > 8 977 | > 9 978 | > ``` 979 | > 980 | > But, you still have to deal with the issue of using an **index** to access the values of the array, and that stinks; it almost makes it more confusing than before. 981 | > 982 | > Also, the for...in loop can get you into big trouble when you need to add an extra method to an array (or another object). Because for...in loops loop over all enumerable properties, this means if you add any additional properties to the array's prototype, then those properties will also appear in the loop. 983 | > 984 | > ```javascript 985 | > Array.prototype.decimalfy = function() { 986 | > for (let i = 0; i < this.length; i++) { 987 | > this[i] = this[i].toFixed(2) 988 | > } 989 | > } 990 | > 991 | > const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 992 | > 993 | > for (const index in digits) { 994 | > console.log(digits[index]) 995 | > } 996 | > ``` 997 | > 998 | > Prints: 999 | > 1000 | > ```text 1001 | > 0 1002 | > 1 1003 | > 2 1004 | > 3 1005 | > 4 1006 | > 5 1007 | > 6 1008 | > 7 1009 | > 8 1010 | > 9 1011 | > function() { 1012 | >  for (let i = 0; i < this.length; i++) { 1013 | >   this[i] = this[i].toFixed(2); 1014 | >  } 1015 | > } 1016 | > ``` 1017 | > 1018 | > Gross! This is why for...in loops are discouraged when looping over arrays. 1019 | > 1020 | > NOTE: The **forEach loop** is another type of for loop in JavaScript. However, `forEach()` is actually an array method, so it can only be used exclusively with arrays. There is also no way to stop or break a forEach loop. If you need that type of behavior in your loop, you’ll have to use a basic for loop. 1021 | 1022 | ### 1.12. For...of Loop 1023 | 1024 | #### Intro to for of loops 1025 | 1026 | > Finally, we have the mighty for...of loop. 1027 | 1028 | #### For...of loop 1029 | 1030 | ##### Description 1031 | 1032 | > The **for...of loop** is used to loop over any type of data that is iterable. 1033 | > 1034 | > You write a **for...of loop** almost exactly like you would write a for...in loop, except you swap out `in` with `of` and you can **drop the index**. 1035 | > 1036 | > ```javascript 1037 | > const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 1038 | > 1039 | > for (const digit of digits) { 1040 | > console.log(digit) 1041 | > } 1042 | > ``` 1043 | > 1044 | > Prints: 1045 | > 1046 | > ```text 1047 | > 0 1048 | > 1 1049 | > 2 1050 | > 3 1051 | > 4 1052 | > 5 1053 | > 6 1054 | > 7 1055 | > 8 1056 | > 9 1057 | > ``` 1058 | > 1059 | > **This makes the for...of loop the most concise version of all the for loops.** 1060 | > 1061 | > TIP: It’s good practice to use plural names for objects that are collections of values. That way, when you loop over the collection, you can use the singular version of the name when referencing individual values in the collection. For example, `for (const button of buttons) {...}`. 1062 | 1063 | ##### Comparison of loops 1064 | 1065 | ```javascript 1066 | // comparison of javascript loops 1067 | 1068 | const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 1069 | 1070 | // for loop 1071 | for (let i = 0; i < digits.length; i++) { 1072 | console.log(digits[i]) 1073 | } 1074 | 1075 | // for...in loop 1076 | for (let index in digits) { 1077 | console.log(digits[index]) 1078 | } 1079 | 1080 | // for...of loop 1081 | for (let digits of digit) { 1082 | console.log(digit) 1083 | } 1084 | ``` 1085 | 1086 | Equivalent loop in Python: 1087 | 1088 | ```python 1089 | digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 1090 | for digit in digits: 1091 | print(digit) 1092 | ``` 1093 | 1094 | ##### Additional benefits of for...of loops 1095 | 1096 | > But wait, there’s more! The for...of loop also has some additional benefits that fix the weaknesses of the for and for...in loops. 1097 | > 1098 | > You can **stop or break a for...of loop at any time**. 1099 | > 1100 | > ```javascript 1101 | > const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 1102 | > 1103 | > for (const digit of digits) { 1104 | > if (digit % 2 === 0) { 1105 | > continue 1106 | > } 1107 | > console.log(digit) 1108 | > } 1109 | > ``` 1110 | > 1111 | > Prints: 1112 | > 1113 | > ```text 1114 | > 1 1115 | > 3 1116 | > 5 1117 | > 7 1118 | > 9 1119 | > ``` 1120 | > 1121 | > And you don’t have to worry about adding new properties to objects. **The for...of loop will only loop over the values in the object.** 1122 | > 1123 | > ```javascript 1124 | > Array.prototype.decimalfy = function() { 1125 | > for (i = 0; i < this.length; i++) { 1126 | > this[i] = this[i].toFixed(2) 1127 | > } 1128 | > } 1129 | > 1130 | > const digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 1131 | > 1132 | > for (const digit of digits) { 1133 | > console.log(digit) 1134 | > } 1135 | > ``` 1136 | > 1137 | > Prints: 1138 | > 1139 | > ```text 1140 | > 0 1141 | > 1 1142 | > 2 1143 | > 3 1144 | > 4 1145 | > 5 1146 | > 6 1147 | > 7 1148 | > 8 1149 | > 9 1150 | > ``` 1151 | 1152 | ### 1.13. Quiz: Writing a For...of Loop (1-4) 1153 | 1154 | Write a for...of loop that: 1155 | 1156 | - loops through each day in the days array 1157 | - capitalizes the first letter of the day 1158 | - and prints the day out to the console 1159 | 1160 | Your code should log the following to the console: 1161 | 1162 | ```text 1163 | Sunday 1164 | Monday 1165 | Tuesday 1166 | Wednesday 1167 | Thursday 1168 | Friday 1169 | Saturday 1170 | ``` 1171 | 1172 | ```javascript 1173 | /* 1174 | - Programming Quiz: Writing a For...of Loop (1-4) 1175 | */ 1176 | 1177 | const days = [ 1178 | "sunday", 1179 | "monday", 1180 | "tuesday", 1181 | "wednesday", 1182 | "thursday", 1183 | "friday", 1184 | "saturday" 1185 | ] 1186 | 1187 | // your code goes here 1188 | ``` 1189 | 1190 |
1191 | Solution 1192 | 1193 | First attempt 1194 | 1195 | Got the days to print on my first try: 1196 | 1197 | ```javascript 1198 | const days = [ 1199 | "sunday", 1200 | "monday", 1201 | "tuesday", 1202 | "wednesday", 1203 | "thursday", 1204 | "friday", 1205 | "saturday" 1206 | ] 1207 | 1208 | // your code goes here 1209 | for (const day of days) { 1210 | console.log(day) 1211 | } 1212 | ``` 1213 | 1214 | ```text 1215 | sunday 1216 | monday 1217 | tuesday 1218 | wednesday 1219 | thursday 1220 | friday 1221 | saturday 1222 | ``` 1223 | 1224 | Next, I need to capitalize the first letter of each day. In Python, this would be something like `letter.upper()`. 1225 | 1226 | This didn't work: 1227 | 1228 | ```javascript 1229 | const days = [ 1230 | "sunday", 1231 | "monday", 1232 | "tuesday", 1233 | "wednesday", 1234 | "thursday", 1235 | "friday", 1236 | "saturday" 1237 | ] 1238 | 1239 | // your code goes here 1240 | for (const day of days) { 1241 | day.upper() 1242 | console.log(day) 1243 | } 1244 | ``` 1245 | 1246 | I needed some reformatting: 1247 | 1248 | ```javascript 1249 | const days = [ 1250 | "sunday", 1251 | "monday", 1252 | "tuesday", 1253 | "wednesday", 1254 | "thursday", 1255 | "friday", 1256 | "saturday" 1257 | ] 1258 | 1259 | // your code goes here 1260 | for (let day of days) { 1261 | let word = day[0].toUpperCase() + day.substr(1) 1262 | console.log(word) 1263 | } 1264 | ``` 1265 | 1266 | ```text 1267 | Sunday 1268 | Monday 1269 | Tuesday 1270 | Wednesday 1271 | Thursday 1272 | Friday 1273 | Saturday 1274 | ``` 1275 | 1276 | > What Went Well 1277 | > 1278 | > - Your code should have a variable days 1279 | > - Your variable days should be an array 1280 | > - Your variable days should contain the days of the week 1281 | > - Your for...of loop should loop through the days array 1282 | > - Your for...of loop should print each day capitalized to the console 1283 | > 1284 | > Feedback 1285 | > 1286 | > Your answer passed all our tests! Awesome job! 1287 | 1288 |
1289 | 1290 | ### 1.14. Spread... Operator 1291 | 1292 | #### Intro to spread operator 1293 | 1294 | > Time to switch gears for a moment and check out the spread operator! 1295 | 1296 | #### Spread operator 1297 | 1298 | > The **spread operator**, written with three consecutive dots ( `...` ), is new in ES6 and gives you the ability to expand, or _spread_, [iterable objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators#Iterators) into multiple elements. 1299 | > 1300 | > Let’s take a look at a few examples to see how it works. 1301 | > 1302 | > ```javascript 1303 | > const books = [ 1304 | > "Don Quixote", 1305 | > "The Hobbit", 1306 | > "Alice in Wonderland", 1307 | > "Tale of Two Cities" 1308 | > ] 1309 | > console.log(...books) 1310 | > ``` 1311 | > 1312 | > Prints: 1313 | > 1314 | > ```text 1315 | > Don Quixote The Hobbit Alice in Wonderland Tale of Two Cities 1316 | > ``` 1317 | > 1318 | > ```javascript 1319 | > const primes = new Set([2, 3, 5, 7, 11, 13, 17, 19, 23, 29]) 1320 | > console.log(...primes) 1321 | > ``` 1322 | > 1323 | > Prints: 1324 | > 1325 | > ```text 1326 | > 2 3 5 7 11 13 17 19 23 29 1327 | > ``` 1328 | > 1329 | > If you look at the output from the examples, notice that both the array and set have been expanded into their individual elements. So how is this useful? 1330 | > 1331 | > NOTE: Sets are a new built-in object in ES6 that we’ll cover in more detail in a future lesson. 1332 | 1333 | #### Combining arrays with concat 1334 | 1335 | > One example of when the spread operator can be useful is when combining arrays. 1336 | > 1337 | > If you’ve ever needed to combine multiple arrays, prior to the spread operator, you were forced to use the array’s `concat()` method. 1338 | > 1339 | > ```javascript 1340 | > const fruits = ["apples", "bananas", "pears"] 1341 | > const vegetables = ["corn", "potatoes", "carrots"] 1342 | > const produce = fruits.concat(vegetables) 1343 | > console.log(produce) 1344 | > ``` 1345 | > 1346 | > Prints: 1347 | > 1348 | > ```text 1349 | > ["apples", "bananas", "pears", "corn", "potatoes", "carrots"] 1350 | > ``` 1351 | > 1352 | > This isn’t terrible, but wouldn’t it be nice if there was a shorthand way to write this code? 1353 | > 1354 | > For example, something like... 1355 | > 1356 | > ⚠️ Upcoming `const` Warning ⚠️ 1357 | > 1358 | > If you're following along by copy/pasting the code, then you've already declared the `produce` variable with the `const` keyword. The following code will try to redeclare \*and- reassign the variable, so depending on how you're running the code, it might throw an error. 1359 | > 1360 | > Remember that variables declared with `const` cannot be redeclared or reassigned in the same scope. 1361 | > 1362 | > ```javascript 1363 | > const produce = [fruits, vegetables] 1364 | > console.log(produce) 1365 | > ``` 1366 | > 1367 | > Prints: 1368 | > 1369 | > ```text 1370 | > [Array[3], Array[3]] 1371 | > ``` 1372 | > 1373 | > Unfortunately, that doesn’t work. 1374 | > 1375 | > Instead of combining both arrays, this code actually adds the `fruits` array at the first index and the `vegetables` array at the second index of the `produce` array. 1376 | > 1377 | > How about trying the spread operator? 1378 | 1379 | #### Mini-Quiz 1380 | 1381 | ```javascript 1382 | /* 1383 | - Instructions: Use the spread operator to combine the `fruits` and `vegetables` arrays into the `produce` array. 1384 | */ 1385 | 1386 | const fruits = ["apples", "bananas", "pears"] 1387 | const vegetables = ["corn", "potatoes", "carrots"] 1388 | 1389 | const produce = [] 1390 | 1391 | console.log(produce) 1392 | ``` 1393 | 1394 |
1395 | Solution 1396 | 1397 | Got it on the first try! Was trying out Pomodoros with @Bree on Slack. Completed 1.14 in one 25 minute Pomodoro. 1398 | 1399 | ```javascript 1400 | const fruits = ["apples", "bananas", "pears"] 1401 | const vegetables = ["corn", "potatoes", "carrots"] 1402 | 1403 | const produce = [...fruits, ...vegetables] 1404 | 1405 | console.log(produce) 1406 | ``` 1407 | 1408 | ```text 1409 | [ 'apples', 'bananas', 'pears', 'corn', 'potatoes', 'carrots' ] 1410 | ``` 1411 | 1412 | > What Went Well 1413 | > 1414 | > - Your code should have a variable fruits 1415 | > - Your code should have a variable vegetables 1416 | > - Your code should have a variable produce 1417 | > - Your variable produce should be an array 1418 | > - Your variable produce should contain the values from the fruits array 1419 | > - Your variable produce should contain the values from the vegetables array 1420 | > - Your code should use spread operator to combine the fruits and vegetables arrays into the produce array 1421 | > 1422 | > Feedback 1423 | > 1424 | > Your answer passed all our tests! Awesome job! 1425 | 1426 |
1427 | 1428 | ### 1.15. ...Rest Parameter 1429 | 1430 | #### Intro to rest parameter 1431 | 1432 | > If you can use the spread operator to _spread_ an array into multiple elements, then certainly there should be a way to bundle multiple elements back into an array, right? 1433 | > 1434 | > In fact, there is! It’s called the **rest parameter**, and it’s another new addition in ES6. 1435 | 1436 | #### Rest parameter 1437 | 1438 | > The rest parameter, also written with three consecutive dots ( `...` ), allows you to represent an indefinite number of elements as an array. This can be helpful in a couple of different situations. 1439 | > 1440 | > One situation is when assigning the values of an array to variables. For example, 1441 | > 1442 | > ```javascript 1443 | > const order = [20.17, 18.67, 1.5, "cheese", "eggs", "milk", "bread"] 1444 | > const [total, subtotal, tax, ...items] = order 1445 | > console.log(total, subtotal, tax, items) 1446 | > ``` 1447 | > 1448 | > Prints: 1449 | > 1450 | > ```text 1451 | > 20.17 18.67 1.5 ["cheese", "eggs", "milk", "bread"] 1452 | > ``` 1453 | > 1454 | > This code takes the values of the `order` array and assigns them to individual variables using **destructuring**. `total`, `subtotal`, and `tax` are assigned the first three values in the array, however, `items` is where you want to pay the most attention. 1455 | > 1456 | > By using the rest parameter, **`items` is assigned the rest of the values in the array** (as an array). 1457 | > 1458 | > ```javascript 1459 | > // spread 1460 | > const myPackage = ["cheese", "eggs", "milk", "bread"] 1461 | > console.log(...myPackage) 1462 | > ``` 1463 | > 1464 | > Prints: 1465 | > 1466 | > ```text 1467 | > cheese eggs milk bread 1468 | > ``` 1469 | > 1470 | > ```javascript 1471 | > // rest 1472 | > printPackageContents("cheese", "eggs", "milk", "bread") 1473 | > 1474 | > function printPackageContents(...items) { 1475 | > for (const item of items) { 1476 | > console.log(item) 1477 | > } 1478 | > } 1479 | > ``` 1480 | > 1481 | > You can **think of the rest parameter like the opposite of the spread operator**; if the spread operator is like unboxing all of the contents of a package, then the rest parameter is like taking all the contents and putting them back into the package. 1482 | 1483 | #### Variadic functions 1484 | 1485 | > Another use case for the rest parameter is when you’re working with variadic functions. Variadic functions are functions that take an indefinite number of arguments. 1486 | > 1487 | > For example, let’s say we have a function called `sum()` which calculates the sum of an indefinite amount of numbers. How might the `sum()` function be called during execution? 1488 | > 1489 | > ```javascript 1490 | > sum(1, 2) 1491 | > sum(10, 36, 7, 84, 90, 110) 1492 | > sum(-23, 3000, 575000) 1493 | > ``` 1494 | > 1495 | > There’s literally an endless number of ways the `sum()` function could be called. Regardless of the amount of numbers passed to the function, it should always return the total sum of the numbers. 1496 | 1497 | #### Using the arguments object 1498 | 1499 | > In previous versions of JavaScript, this type of function would be handled using the [arguments object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments). The **arguments object** is an array-like object that is available as a local variable inside all functions. It contains a value for each argument being passed to the function starting at 0 for the first argument, 1 for the second argument, and so on. 1500 | > 1501 | > If we look at the implementation of our `sum()` function, then you’ll see how the arguments object could be used to handle the variable amount of numbers being passed to it. 1502 | > 1503 | > ```javascript 1504 | > function sum() { 1505 | > let total = 0 1506 | > for (const argument of arguments) { 1507 | > total += argument 1508 | > } 1509 | > return total 1510 | > } 1511 | > ``` 1512 | > 1513 | > Now this works fine, but it does have its issues: 1514 | > 1515 | > 1. If you look at the definition for the `sum()` function, it doesn’t have any parameters. 1516 | > - This is misleading because we know the `sum()` function can handle an indefinite amount of arguments. 1517 | > 2. It can be hard to understand. 1518 | > - If you’ve never used the arguments object before, then you would most likely look at this code and wonder where the arguments object is even coming from. Did it appear out of thin air? It certainly looks that way. 1519 | 1520 | #### Using the rest parameter 1521 | 1522 | > Fortunately, with the addition of the rest parameter, you can rewrite the `sum()` function to read more clearly. 1523 | > 1524 | > ```javascript 1525 | > function sum(...nums) { 1526 | > let total = 0 1527 | > for (const num of nums) { 1528 | > total += num 1529 | > } 1530 | > return total 1531 | > } 1532 | > ``` 1533 | > 1534 | > This version of the `sum()` function is both **more concise** and is **easier to read**. Also, notice the for...in loop has been replaced with the new **for...of loop**. 1535 | 1536 | ### 1.16. Quiz: Using the Rest Parameter (1-5) 1537 | 1538 | Directions: 1539 | 1540 | Use the rest parameter to create an `average()` function that calculates the average of an \*unlimited- amount of numbers. 1541 | 1542 | TIP: Make sure to test your code with different values. For example, 1543 | 1544 | - `average(2, 6)` should return `4` 1545 | - `average(2, 3, 3, 5, 7, 10)` should return `5` 1546 | - `average(7, 1432, 12, 13, 100)` should return `312.8` 1547 | - `average()` should return `0` 1548 | 1549 | ```javascript 1550 | /* 1551 | - Programming Quiz: Using the Rest Parameter (1-5) 1552 | */ 1553 | 1554 | // your code goes here 1555 | 1556 | function average() {} 1557 | 1558 | console.log(average(2, 6)) 1559 | console.log(average(2, 3, 3, 5, 7, 10)) 1560 | console.log(average(7, 1432, 12, 13, 100)) 1561 | console.log(average()) 1562 | ``` 1563 | 1564 |
1565 | Solution 1566 | 1567 | First attempt: 1568 | 1569 | ```javascript 1570 | function average(...nums) { 1571 | let total = 0 1572 | for (const num of nums) { 1573 | total += num 1574 | total / nums 1575 | } 1576 | return total 1577 | } 1578 | 1579 | console.log(average(2, 6)) 1580 | console.log(average(2, 3, 3, 5, 7, 10)) 1581 | console.log(average(7, 1432, 12, 13, 100)) 1582 | console.log(average()) 1583 | ``` 1584 | 1585 | Just printed the total: 1586 | 1587 | ```text 1588 | 8 1589 | 30 1590 | 1564 1591 | 0 1592 | ``` 1593 | 1594 | I iterated several more times, but wasn't sure how to calculate or display the average: 1595 | 1596 | ```javascript 1597 | function average(...nums) { 1598 | let total = 0 1599 | let avg = 0 1600 | for (const num of nums) { 1601 | avg = (total += num) / nums 1602 | } 1603 | return avg 1604 | } 1605 | 1606 | console.log(average(2, 6)) 1607 | console.log(average(2, 3, 3, 5, 7, 10)) 1608 | console.log(average(7, 1432, 12, 13, 100)) 1609 | console.log(average()) 1610 | ``` 1611 | 1612 | ```text 1613 | NaN 1614 | NaN 1615 | NaN 1616 | 0 1617 | ``` 1618 | 1619 | > What Went Well 1620 | > 1621 | > - Your code should have a function average() 1622 | > - Your average() function should have one parameter 1623 | > - Your average() function should use the rest parameter 1624 | > 1625 | > What Went Wrong 1626 | > 1627 | > - Your average() function doesn't calculate average correctly 1628 | > 1629 | > Feedback 1630 | > 1631 | > Not everything is correct yet, but you're close! 1632 | 1633 | I checked [James Priest's work](https://github.com/james-priest/100-days-of-code-log-r2/blob/master/ES6-Syntax.md#solution-8) for help at this point. I was on the right track, but just needed a few modifications to the syntax: 1634 | 1635 | ```javascript 1636 | function average(...nums) { 1637 | let total = 0 1638 | for (const num of nums) { 1639 | total += num 1640 | } 1641 | if (nums.length > 0) { 1642 | return total / nums.length 1643 | } 1644 | return 0 1645 | } 1646 | 1647 | console.log(average(2, 6)) 1648 | console.log(average(2, 3, 3, 5, 7, 10)) 1649 | console.log(average(7, 1432, 12, 13, 100)) 1650 | console.log(average()) 1651 | ``` 1652 | 1653 | ```text 1654 | 4 1655 | 5 1656 | 312.8 1657 | 0 1658 | ``` 1659 | 1660 | > What Went Well 1661 | > 1662 | > - Your code should have a function `average()` 1663 | > - Your `average()` function should have one parameter 1664 | > - Your `average()` function should use the rest parameter 1665 | > - Your `average()` function should calculate the average of an indefinite amount of numbers 1666 | > 1667 | > Feedback 1668 | > 1669 | > Your answer passed all our tests! Awesome job! 1670 | 1671 |
1672 | 1673 | ### 1.17. Lesson 1 Summary 1674 | 1675 | ## Feedback on JavaScript ES6 lesson 1/4 1676 | 1677 | Very helpful! The documentation was great, and I understood everything well. Definitely glad I'm learning JavaScript after ES6 is available! So much better. 1678 | 1679 | [Next lesson](es6-02-functions.md) 1680 | 1681 | [(Back to top)](#top) 1682 | --------------------------------------------------------------------------------