From 75781e14b15b228cee0e82404d23583fdf260bc0 Mon Sep 17 00:00:00 2001 From: Sally McGrath Date: Thu, 24 Oct 2024 15:11:50 +0100 Subject: [PATCH 1/7] 1049 Add a <100-word one-pager about how to interact with the curriculum (#1126) * it's more like 150 words * self review have made it more paragraphs so it all fits on one laptop screen * Suggested edits to discuss (#1127) * Suggested edits to discuss * Reflow * Revert "Suggested edits to discuss (#1127)" (#1128) This reverts commit 7f342b62a1fc9bafd03f52197497092834e5cbcc. * take daniel's edits but not his formatting 48a33a9f9804b99d13028c4c5eb96a13754cf689 * add primo link this will be replaced with magic class link in time * formatting again * Update common-content/en/module/how-our-curriculum-works/overview/index.md Co-authored-by: Daniel Wagner-Hall --------- Co-authored-by: Daniel Wagner-Hall --- .../overview/index.md | 37 +++++++++++++++++++ org-cyf-how-this-works/content/prep/index.md | 3 ++ org-cyf-itp/content/_index.md | 2 +- 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 common-content/en/module/how-our-curriculum-works/overview/index.md diff --git a/common-content/en/module/how-our-curriculum-works/overview/index.md b/common-content/en/module/how-our-curriculum-works/overview/index.md new file mode 100644 index 000000000..4e4494ecb --- /dev/null +++ b/common-content/en/module/how-our-curriculum-works/overview/index.md @@ -0,0 +1,37 @@ ++++ +title="Overview" +description="tl;dr How Our Curriculum Works" +emoji="🦌" +time=2 +[objectives] + 1="Explain the structure of our curriculum" +[build] + render = 'never' + list = 'local' + publishResources = false + ++++ + +The course is divided into modules, each with a theme and learning objectives. Modules are divided into week-long sprints. Each sprint contains these activities in this order: + +##### 1. Start with the: **πŸ§‘πŸΎβ€πŸ’» Prep**, which explains the main concepts of the sprint. + +**Learners** complete prep before class. **Mentors** browse prep to know what learners are learning. + +##### 2. Then go to: **🏷️ Backlog**, a list of coursework assignments as issues. + +**Learners** clone issues to project boards and work on them. **Mentors** browse issues to know what learners are doing. + +##### 3. For class it's the: **πŸ§‘πŸΎβ€πŸ€β€πŸ§‘πŸΎ Day Plan**, a timestamped agenda for class day. + +Usually a lively morning workshop and quieter afternoon study-group. **Everyone** should review the plan to prepare for class. + +##### 4. Review with: **βœ… Success**, the learning objectives for the sprint. + +**Learners** check off goals. **Mentors** help us focus on the sprint goals. + +### Still lost? + +1. πŸ” Search: filter by module, sprint, and view. +1. πŸ¦‰ [Overview](/overview): your high level map with themes. +1. πŸ“š [How this works](/how-this-works): our programme in detail. diff --git a/org-cyf-how-this-works/content/prep/index.md b/org-cyf-how-this-works/content/prep/index.md index ce07e9e62..6a80d1f2a 100644 --- a/org-cyf-how-this-works/content/prep/index.md +++ b/org-cyf-how-this-works/content/prep/index.md @@ -27,4 +27,7 @@ src="module/how-our-curriculum-works/curriculum" [[blocks]] name="πŸ›£οΈ using this module" src="module/how-our-curriculum-works/using-this-module" +[[blocks]] +name="overview" +src="module/how-our-curriculum-works/overview" +++ diff --git a/org-cyf-itp/content/_index.md b/org-cyf-itp/content/_index.md index 24ddf3a57..d996435a5 100644 --- a/org-cyf-itp/content/_index.md +++ b/org-cyf-itp/content/_index.md @@ -1,6 +1,6 @@ +++ title="Intro to Programming" map=["start here", "programming", "next steps"] -description="Here's how you find out _what_ to do _when_" +description="New? Lost? [Quickstart](how-this-works/prep/#overview)" emoji= "πŸ§‘πŸΏβ€πŸ«" +++ From 141df079fa3634bcab8d632e4ea0e8f60eb17f87 Mon Sep 17 00:00:00 2001 From: Sally McGrath Date: Thu, 24 Oct 2024 15:52:46 +0100 Subject: [PATCH 2/7] 857 Data Groups / Sprint 1 - Referenced Medium article is member-only (#1116) * update modules * Improve replace directive tooling messaging * Add codefences around code to be copied * Fix tests * add replacement --------- Co-authored-by: Daniel Wagner-Hall --- org-cyf-itp/go.mod | 15 ++++++----- org-cyf-itp/go.sum | 6 +++-- .../go/cmd/local-overrides-enforcer/main.go | 8 ++++-- .../checker/checker.go | 26 +++++++++---------- .../checker/checker_test.go | 10 ++++--- 5 files changed, 38 insertions(+), 27 deletions(-) diff --git a/org-cyf-itp/go.mod b/org-cyf-itp/go.mod index b80e74cfb..0a915549d 100644 --- a/org-cyf-itp/go.mod +++ b/org-cyf-itp/go.mod @@ -3,12 +3,13 @@ module github.com/CodeYourFuture/curriculum/org-cyf-itp go 1.22.6 require ( - github.com/CodeYourFuture/CYF-PD v1.0.1-0.20240721130916-d70fc853a278 // indirect - github.com/CodeYourFuture/curriculum/common-content v0.0.0-20241008095008-281a5517463f // indirect - github.com/CodeYourFuture/curriculum/common-theme v0.0.0-20241008095008-281a5517463f // indirect - github.com/CodeYourFuture/curriculum/org-cyf-guides v0.0.0-20240721115017-ac0d39b0bbe3 // indirect - github.com/CodeYourFuture/curriculum/org-cyf-how-this-works v0.0.0-00010101000000-000000000000 // indirect - github.com/CodeYourFuture/curriculum/org-cyf-piscine v0.0.0-20240818070728-e8702788f7cb // indirect + github.com/CodeYourFuture/CYF-PD v1.0.1-0.20240905150045-7be086ff6886 // indirect + github.com/CodeYourFuture/curriculum/common-content v0.0.0-20241022142707-ee3974f18eb2 // indirect + github.com/CodeYourFuture/curriculum/common-theme v0.0.0-20241022142707-ee3974f18eb2 // indirect + github.com/CodeYourFuture/curriculum/org-cyf-guides v0.0.0-20241022142707-ee3974f18eb2 // indirect + github.com/CodeYourFuture/curriculum/org-cyf-how-this-works v0.0.0-20241022142707-ee3974f18eb2 // indirect + github.com/CodeYourFuture/curriculum/org-cyf-piscine v0.0.0-20241022142707-ee3974f18eb2 // indirect + github.com/CodeYourFuture/curriculum/org-cyf-theme v0.0.0-20241022142707-ee3974f18eb2 // indirect ) replace github.com/CodeYourFuture/curriculum/common-content => ../common-content @@ -20,3 +21,5 @@ replace github.com/CodeYourFuture/curriculum/org-cyf-guides => ../org-cyf-guides replace github.com/CodeYourFuture/curriculum/org-cyf-how-this-works => ../org-cyf-how-this-works replace github.com/CodeYourFuture/curriculum/org-cyf-piscine => ../org-cyf-piscine + +replace github.com/CodeYourFuture/curriculum/org-cyf-theme => ../org-cyf-theme diff --git a/org-cyf-itp/go.sum b/org-cyf-itp/go.sum index 7a73f31bf..5676f91e0 100644 --- a/org-cyf-itp/go.sum +++ b/org-cyf-itp/go.sum @@ -1,2 +1,4 @@ -github.com/CodeYourFuture/CYF-PD v1.0.1-0.20240721130916-d70fc853a278 h1:QkyzRerSZyumZ4gxoE7gehj86zWdDY7O4K9pqW8OLtg= -github.com/CodeYourFuture/CYF-PD v1.0.1-0.20240721130916-d70fc853a278/go.mod h1:DJj2pdPceTBoHErILtnZMEYPZZuC8W9Fmt/faaW2tcw= +github.com/CodeYourFuture/CYF-PD v1.0.1-0.20240905150045-7be086ff6886 h1:ZKZYah39sNwr1VpTjl/q8mWq/0bj9X3cRGJpUHt8CKQ= +github.com/CodeYourFuture/CYF-PD v1.0.1-0.20240905150045-7be086ff6886/go.mod h1:DJj2pdPceTBoHErILtnZMEYPZZuC8W9Fmt/faaW2tcw= +github.com/CodeYourFuture/curriculum/org-cyf-theme v0.0.0-20241022142707-ee3974f18eb2 h1:GpkMPRNPE1718eMFHYouqq+0jl1dgbwMGiE6pPBDi6M= +github.com/CodeYourFuture/curriculum/org-cyf-theme v0.0.0-20241022142707-ee3974f18eb2/go.mod h1:5RMrsJNpszVsZskrrC9fmMurvjlcvU74VBcJz8I4LtA= diff --git a/tooling/go/cmd/local-overrides-enforcer/main.go b/tooling/go/cmd/local-overrides-enforcer/main.go index f261ac209..081323d37 100644 --- a/tooling/go/cmd/local-overrides-enforcer/main.go +++ b/tooling/go/cmd/local-overrides-enforcer/main.go @@ -55,13 +55,17 @@ func main() { if err != nil { return fmt.Errorf("failed to read %s: %w", path, err) } - expectedContents, ok, err := checker.CheckFile(path, content, parentModule) + expectedContents, ok, missingReplaces, err := checker.CheckFile(path, content, parentModule) if err != nil { return fmt.Errorf("failed to check %s: %w", path, err) } if !ok { sawBadFile = true - fmt.Printf("⚠️ File at path %s didn't have some local overrides - its contents should be:\n%s\n", path, expectedContents) + missingReplacesStr := "" + for _, missingReplace := range missingReplaces { + missingReplacesStr += " - " + missingReplace + "\n" + } + fmt.Printf("⚠️ File at path %s didn't have some replace directives.\nMissing replace directives for modules:\n%s\nYou should replace %s with exactly this string:\n```\n%s\n```\n", path, missingReplacesStr, path, expectedContents) } return nil }) diff --git a/tooling/go/internal/local-overrides-enforcer/checker/checker.go b/tooling/go/internal/local-overrides-enforcer/checker/checker.go index 7d6408924..8f7c59b1b 100644 --- a/tooling/go/internal/local-overrides-enforcer/checker/checker.go +++ b/tooling/go/internal/local-overrides-enforcer/checker/checker.go @@ -9,19 +9,19 @@ import ( // CheckFile checks that a go.mod file has local overrides for all children of the passed parent module. // It returns one of: -// - If the contents was correct: "", true, nil -// - If the contents was not correct: the expected contents, false, nil -// - If an error occurred: "", false, error -func CheckFile(goModPath string, contents []byte, parentModule string) (string, bool, error) { +// - If the contents was correct: "", true, nil, nil +// - If the contents was not correct: the expected contents, false, []string{"missing/override"...}, nil +// - If an error occurred: "", false, nil, error +func CheckFile(goModPath string, contents []byte, parentModule string) (string, bool, []string, error) { gomodFile, err := modfile.Parse(goModPath, contents, nil) if err != nil { - return "", false, fmt.Errorf("failed to parse %s as go.mod file: %w", goModPath, err) + return "", false, nil, fmt.Errorf("failed to parse %s as go.mod file: %w", goModPath, err) } parentModuleWithTrailingSlash := parentModule + "/" if !strings.HasPrefix(gomodFile.Module.Mod.Path, parentModuleWithTrailingSlash) { - return "", false, fmt.Errorf("module at path %s was named %s which isn't a child of %s", goModPath, gomodFile.Module.Mod.Path, parentModule) + return "", false, nil, fmt.Errorf("module at path %s was named %s which isn't a child of %s", goModPath, gomodFile.Module.Mod.Path, parentModule) } slashCount := strings.Count(gomodFile.Module.Mod.Path[len(parentModule):], "/") @@ -30,7 +30,7 @@ func CheckFile(goModPath string, contents []byte, parentModule string) (string, replaces[replace.Old.Path] = struct{}{} } - missingReplaces := false + var missingReplaces []string for _, require := range gomodFile.Require { modPath := require.Mod.Path if !strings.HasPrefix(modPath, parentModuleWithTrailingSlash) { @@ -39,18 +39,18 @@ func CheckFile(goModPath string, contents []byte, parentModule string) (string, if _, isReplaced := replaces[modPath]; isReplaced { continue } - missingReplaces = true + missingReplaces = append(missingReplaces, modPath) rel := modPath[len(parentModuleWithTrailingSlash):] if err := gomodFile.AddReplace(modPath, "", strings.Repeat("../", slashCount)+rel, ""); err != nil { - return "", false, fmt.Errorf("failed to add replace: %w", err) + return "", false, nil, fmt.Errorf("failed to add replace: %w", err) } } - if missingReplaces { + if len(missingReplaces) != 0 { formatted, err := gomodFile.Format() if err != nil { - return "", false, fmt.Errorf("failed to serialize go.mod file: %w", err) + return "", false, nil, fmt.Errorf("failed to serialize go.mod file: %w", err) } - return string(formatted), false, nil + return string(formatted), false, missingReplaces, nil } - return "", true, nil + return "", true, nil, nil } diff --git a/tooling/go/internal/local-overrides-enforcer/checker/checker_test.go b/tooling/go/internal/local-overrides-enforcer/checker/checker_test.go index a358296b4..6ec48090d 100644 --- a/tooling/go/internal/local-overrides-enforcer/checker/checker_test.go +++ b/tooling/go/internal/local-overrides-enforcer/checker/checker_test.go @@ -21,9 +21,10 @@ require ( ) ` - newContent, ok, err := checker.CheckFile("/some/go.mod", []byte(goModContent), "github.com/CodeYourFuture/curriculum") + newContent, ok, missingReplaces, err := checker.CheckFile("/some/go.mod", []byte(goModContent), "github.com/CodeYourFuture/curriculum") require.NoError(t, err) require.True(t, ok) + require.Empty(t, missingReplaces) require.Equal(t, "", newContent) } @@ -40,9 +41,10 @@ func TestMissingReplace(t *testing.T) { ) ` - newContent, ok, err := checker.CheckFile("/some/go.mod", []byte(goModContent), "github.com/CodeYourFuture/curriculum") + newContent, ok, missingReplaces, err := checker.CheckFile("/some/go.mod", []byte(goModContent), "github.com/CodeYourFuture/curriculum") require.NoError(t, err) require.False(t, ok) + require.Equal(t, missingReplaces, []string{"github.com/CodeYourFuture/curriculum/common-content"}) require.Contains(t, newContent, "replace github.com/CodeYourFuture/curriculum/common-content => ../common-content") } @@ -60,11 +62,11 @@ require ( ) ` - _, _, err := checker.CheckFile("/some/go.mod", []byte(goModContent), "github.com/CodeYourFuture/curriculum") + _, _, _, err := checker.CheckFile("/some/go.mod", []byte(goModContent), "github.com/CodeYourFuture/curriculum") require.ErrorContains(t, err, "module at path /some/go.mod was named github.com/CodeYourFuture/wrong which isn't a child of github.com/CodeYourFuture/curriculum") } func TestInvalidGoModFile(t *testing.T) { - _, _, err := checker.CheckFile("/some/go.mod", []byte("hello"), "github.com/CodeYourFuture/curriculum") + _, _, _, err := checker.CheckFile("/some/go.mod", []byte("hello"), "github.com/CodeYourFuture/curriculum") require.ErrorContains(t, err, "failed to parse /some/go.mod as go.mod file: ") } From a9f6fccbf00d9a0e720939f6ae55c79db6ef51a1 Mon Sep 17 00:00:00 2001 From: Sally McGrath Date: Thu, 24 Oct 2024 16:25:53 +0100 Subject: [PATCH 3/7] 1105 Include more theme/heading information on index pages (#1130) * bring themes and desc into module view obviously I hate this but I will endure it * Update prep descriptions (#1134) If we're displaying these more prominently, these seem more useful :) --------- Co-authored-by: Daniel Wagner-Hall --- common-theme/assets/styles/04-components/timeline.scss | 9 ++++++++- common-theme/layouts/_default/module.html | 8 ++++++-- org-cyf-itp/content/user-data/prep/index.md | 2 +- org-cyf-sdc/content/tools/prep/index.md | 2 +- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/common-theme/assets/styles/04-components/timeline.scss b/common-theme/assets/styles/04-components/timeline.scss index e767446b5..56eae8b96 100644 --- a/common-theme/assets/styles/04-components/timeline.scss +++ b/common-theme/assets/styles/04-components/timeline.scss @@ -44,10 +44,17 @@ } &__title { - text-transform: lowercase; + text-transform: capitalize; margin: 0; @include on-event(false, ".c-timeline__entry") { text-decoration: underline; } } + + &__link { + display: flex; + flex-flow: row wrap; + align-items: center; + gap: 0 var(--theme-spacing--1); + } } diff --git a/common-theme/layouts/_default/module.html b/common-theme/layouts/_default/module.html index 15256f67c..09c0949d9 100644 --- a/common-theme/layouts/_default/module.html +++ b/common-theme/layouts/_default/module.html @@ -14,8 +14,12 @@ "module" }}
  • -

    {{ .Title }}

    {{ .Title }}

    + {{ $meta := .Params.theme | default .Description | default "" }} + {{ if $meta }} +

    : {{ $meta }}

    + {{ end }}
  • {{ end }} diff --git a/org-cyf-itp/content/user-data/prep/index.md b/org-cyf-itp/content/user-data/prep/index.md index efc81ff9d..74dce5fad 100644 --- a/org-cyf-itp/content/user-data/prep/index.md +++ b/org-cyf-itp/content/user-data/prep/index.md @@ -1,6 +1,6 @@ +++ title = 'Prep' -description = 'There is just so much to do!' +description = 'What to do before the module starts' layout = 'prep' emoji= 'πŸ§‘πŸΎβ€πŸ’»' menu_level = ['module'] diff --git a/org-cyf-sdc/content/tools/prep/index.md b/org-cyf-sdc/content/tools/prep/index.md index 51b46689e..9b7168687 100644 --- a/org-cyf-sdc/content/tools/prep/index.md +++ b/org-cyf-sdc/content/tools/prep/index.md @@ -1,6 +1,6 @@ +++ title = 'Prep' -description = 'There is just so much to do!' +description = 'What to do before the module starts' layout = 'prep' emoji= 'πŸ§‘πŸΎβ€πŸ’»' menu_level = ['module'] From 87f9454be9fda1a2531a78f77728219157288768 Mon Sep 17 00:00:00 2001 From: Sally McGrath Date: Thu, 24 Oct 2024 16:30:44 +0100 Subject: [PATCH 4/7] suppress step index and breadcrumb (#1135) --- .../layouts/partials/breadcrumbs.html | 22 ++++++++++--------- org-cyf-itd/content/steps/_index.md | 3 ++- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/common-theme/layouts/partials/breadcrumbs.html b/common-theme/layouts/partials/breadcrumbs.html index f9a199ffa..447b39df0 100644 --- a/common-theme/layouts/partials/breadcrumbs.html +++ b/common-theme/layouts/partials/breadcrumbs.html @@ -26,16 +26,18 @@ {{- end }} -
  • - {{ .Title }} -
  • + {{ if ne .Params.Build.render "never" }} +
  • + {{ .Title }} +
  • + {{ end }} {{/* Here's a tiny drop of structured data for google's hungry maw diff --git a/org-cyf-itd/content/steps/_index.md b/org-cyf-itd/content/steps/_index.md index 7410abfd9..16da6e73e 100644 --- a/org-cyf-itd/content/steps/_index.md +++ b/org-cyf-itd/content/steps/_index.md @@ -3,5 +3,6 @@ title = 'Steps' description = 'The steps to complete the Intro to Digital programme' layout = 'module' emoji= 'πŸ“š' -menu = ['syllabus'] +[build] +render = 'never' +++ From 17adc7db5a96a7f4710b5a352908eabf6171d594 Mon Sep 17 00:00:00 2001 From: Sally McGrath Date: Thu, 24 Oct 2024 16:38:33 +0100 Subject: [PATCH 5/7] bring descriptions into itd home page (#1131) --- .../assets/styles/04-components/card.scss | 4 ++-- .../custom-theme/04-components/card-for-itd.scss | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 org-cyf-itd/assets/custom-theme/04-components/card-for-itd.scss diff --git a/common-theme/assets/styles/04-components/card.scss b/common-theme/assets/styles/04-components/card.scss index 577c846dd..4254975aa 100644 --- a/common-theme/assets/styles/04-components/card.scss +++ b/common-theme/assets/styles/04-components/card.scss @@ -6,8 +6,8 @@ position: relative; grid-template: ". . . . ." var(--theme-spacing--gutter) - ". title . image ." minmax(7ch, auto) - ". meta . image ." minmax(0, 1fr) + ". title . image ." minmax(1em, auto) + ". meta . image ." minmax(0, auto) ". description . image ." minmax(0, 1fr) ". . . . ." var(--theme-spacing--gutter) / var(--theme-spacing--gutter) minmax( diff --git a/org-cyf-itd/assets/custom-theme/04-components/card-for-itd.scss b/org-cyf-itd/assets/custom-theme/04-components/card-for-itd.scss new file mode 100644 index 000000000..63f953db8 --- /dev/null +++ b/org-cyf-itd/assets/custom-theme/04-components/card-for-itd.scss @@ -0,0 +1,15 @@ +.c-map .c-card { + &__description { + position: static; + font-size: inherit; + opacity: 100%; + background-color: transparent; + } + &:hover, + &:focus, + &:active { + .c-card__description { + animation: none; + } + } +} From 4f8508cdd55a877b3325fea41122bb4280c2d905 Mon Sep 17 00:00:00 2001 From: Daniel Wagner-Hall Date: Fri, 25 Oct 2024 10:35:35 +0100 Subject: [PATCH 6/7] Non-trivial tweaks to Data Flows sprints 2 and 3 (#1111) As well as a bunch of copy edits, a few non-trivial changes: * Sprint 2's code didn't actually fully work in a bunch of ways. Add soem blocks and steps making sure we actually step through all the issues that come across in the code we're teaching. I'm consciously leaving this mostly as a worked example, rather than do-yourself exercises, because the trainees already have the TV Show project as an entire do-yourself exercise about the same topics. * When working with promises, consistently tell people to save examples to files and run them, rather than run them in the repl. This is because if you execute these snippets in a repl you get printed out extra Promise values (because the repl prints out each evaluated expression), which makes it much harder to do things like "spot the difference between what returns a promise and what evaluates to a value"). * Completely remove `Promise.catch` This is probably the most controversial but I think it's important. Two main reasons: 1. We haven't actually taught `try-catch` anywhere, so we can't just rely on transferring "it's the same as you already know, just async". 2. There are already _way_ too many novel concepts in Data Flows Sprint 3, and it's already very hard to follow from scratch. If we add `try-catch` earlier in the course (which I think we should), we should re-add this. --- .../module/js3/actually-re-rendering/index.md | 45 +++++++++ .../en/module/js3/async-await/index.md | 73 +++++++------- .../en/module/js3/asynchrony/index.md | 15 ++- .../en/module/js3/break-down/index.md | 11 ++- .../en/module/js3/callbacks/index.md | 13 +-- .../en/module/js3/capturing-events/index.md | 3 +- .../en/module/js3/fetch-films/index.md | 93 ++++-------------- .../en/module/js3/fetching-data/index.md | 6 +- .../en/module/js3/identifying-state/index.md | 8 +- .../module/js3/introducing-new-state/index.md | 4 +- common-content/en/module/js3/latency/index.md | 2 +- .../en/module/js3/promises/index.md | 4 +- .../en/module/js3/re-rendering/index.md | 95 +++++++++++++++++++ .../en/module/js3/reacting/index.md | 6 +- .../refactoring-to-state-and-render/index.md | 14 ++- .../js3/rendering-based-on-state/index.md | 2 +- common-content/en/module/js3/then/index.md | 20 ++-- .../en/module/js3/using-fetch/index.md | 33 ++++--- .../data-flows/sprints/2/prep/index.md | 8 +- .../data-flows/sprints/3/prep/index.md | 3 - .../content/data-flows/success/index.md | 11 +-- 21 files changed, 282 insertions(+), 187 deletions(-) create mode 100644 common-content/en/module/js3/actually-re-rendering/index.md create mode 100644 common-content/en/module/js3/re-rendering/index.md diff --git a/common-content/en/module/js3/actually-re-rendering/index.md b/common-content/en/module/js3/actually-re-rendering/index.md new file mode 100644 index 000000000..e5be46832 --- /dev/null +++ b/common-content/en/module/js3/actually-re-rendering/index.md @@ -0,0 +1,45 @@ ++++ +title = 'πŸ” Actually re-rendering' + +time = 30 +facilitation = false +emoji= 'πŸ”' +[objectives] + 1='Group UI components by whether they need to re-render' + 2='Control which UI components are re-rendered' +[build] + render = 'never' + list = 'local' + publishResources = false + ++++ + +We have seen that when we search, we're only _adding_ new elements, and not removing existing elements from the page. + +We previously identified our strategy of clearing old elements before adding new ones. But we are not doing this. + +We can clear out the existing children of an element by setting its `textContent` propery to the empty string: + +```js +document.body.textContent = ""; +``` + +Add this to your `render` function before you add new elements. Try using your page. Try searching for a particular film. + +Oh no, our search box is gone! + +{{}} +Work out why our search box is gone. Remember what we just changed, and what we were trying to do by making that change. +{{}} + +We removed our search box from the page because we removed everything from the entire document body. + +This was not our intention - we only wanted to remove any films we had previously rendered. + +A way to solve this is to introduce a container element which our `render` function will re-fill every time it's called. + +We should identify which elements in our page should be re-drawn every time we render, and which should always be present. + +Introduce a new container, and keep any "always present" UI elements outside of it. Update your `render` function to clear and append to the container, not the whole body. + +Remember to use semantic HTML. Your container should be an appropriate tag for the contents it will have. diff --git a/common-content/en/module/js3/async-await/index.md b/common-content/en/module/js3/async-await/index.md index 3616bdb69..1409f5d42 100644 --- a/common-content/en/module/js3/async-await/index.md +++ b/common-content/en/module/js3/async-await/index.md @@ -1,12 +1,13 @@ +++ title = '🍬 async/await' -time = 20 +time = 30 facilitation = false emoji= '🧩' [objectives] 1='Define syntactic sugar' - 2='Write a function using the async keyword' + 2='Write a function using async/await' + 3='Explain what happens when an async function is awaited' [build] render = 'never' list = 'local' @@ -14,18 +15,36 @@ emoji= '🧩' +++ -```mermaid -graph LR - Promise{{🀝 Promises}} --> |syntax| async{{πŸƒβ€β™‚οΈ async}} - async --> |syntax| await{{πŸ“­ await}} - await --> |resolves to| Response{{πŸ“€ Response}} +These two blocks of code do exactly the same thing: + +```js +const getProfile = async (url) => { + const response = await fetch(url); + const data = await response.json(); + const htmlUrl = data.html_url; + console.log(htmlUrl); +} + +getProfile("https://api.github.com/users/SallyMcGrath"); ``` -Async/await is {{}}A simpler, or "sweeter" way to write the same thing. The code works the same under the hood, but it's easier to read. {{}} for Promises. We group them together: async/await, because we {{}}We can only use `await` inside an `async` function or at the [top level](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#top_level_await) of a module.{{}} +```js +const getProfile = (url) => { + return fetch(url) + .then((response) => response.json()) + .then((data) => data.html_url) + .then((htmlUrl) => console.log(htmlUrl)); +}; +getProfile("https://api.github.com/users/SallyMcGrath"); +``` + +Async/await is {{}}A simpler, or "sweeter" way to write the same thing. The code works the same under the hood, but it's easier to read. {{}} for Promises. + +We group `async` and `await` together: async/await, because we {{}}We can only use `await` inside an `async` function or at the [top level](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#top_level_await) of a module.{{}} We use the [`async`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) [keyword](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#keywords) to define a function that returns a Promise. An async function always returns a Promise. -We can see this with a simple function which doesn't need to await anything: +We can see this with a simple function which doesn't need to await anything. Save this in a file and run it with node: ```js const getProfile = async (url) => url; @@ -45,44 +64,16 @@ const getProfile = async (url) => { }; ``` -We use the [`await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await) operator to _wait_ for a Promise to resolve. This allows us to write code that looks like it's happening in time order, but doesn't block our main thread. +We use the [`await`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await) operator to say "don't move on until this is done". Importantly, we are not actually _waiting_ for a Promise to resolve. We are scheduling a callback that will be called when the Promise resolves. But this allows us to write code that looks like it's happening in time order (as if we _are_ waiting), without actually blocking our main thread. ```js const getProfile = async (url) => { const response = await fetch(url); return response.json(); }; -``` -Go ahead and call this in your Node REPL in your terminal: `getProfile("https://api.github.com/users/SallyMcGrath").then(console.log)`. It works the same as before. - -### 🫠 Handling errors - -When we use `await`, we are saying, "Wait for this Promise to resolve before moving on to the next line of code." But if the Promise _doesn't_ resolve, the next line of code will never run and an [error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) will be thrown. - -Let's try this. Call `getProfile` with a url that doesn't exist: `getProfile("invalid_url");` - -You will get a curious response: - -
    Uncaught (in promise) TypeError... - -```js -getProfile("invalid_url") -Promise { - , - [...] -} -> Uncaught [TypeError: Failed to parse URL from invalid_url] { - [cause]: TypeError: Invalid URL - [...] { - code: 'ERR_INVALID_URL', - input: 'invalid_url' - } -} +getProfile("https://api.github.com/users/SallyMcGrath") + .then((response) => console.log(response)) ``` -_Some lines redacted [...] for clarity._ - -
    - -JavaScript is telling us we need to `catch` the error, but how, and why? +Save this to a file and run with with node. It works the same as before. diff --git a/common-content/en/module/js3/asynchrony/index.md b/common-content/en/module/js3/asynchrony/index.md index f87625c8e..400d56310 100644 --- a/common-content/en/module/js3/asynchrony/index.md +++ b/common-content/en/module/js3/asynchrony/index.md @@ -15,7 +15,7 @@ emoji= '🧩' +++ -We can handle latency using {{}}run code in a different order.{{}} To understand asynchrony we first need to be clear about {{}}run code in the order it is written.{{}}. +We can handle latency using {{}}Asynchronous execution is running code in a different order than it was written.{{}} To understand asynchrony we first need to be clear about {{}}Synchronous execution is running code in the order it is written.{{}}. We have written a lot of JavaScript programs that execute sequentially. This means that each line of code is run in order, one after the other. {{}} @@ -47,7 +47,16 @@ When we call a function, the function will run to completion before the next lin ### Event Loop -We have already used asynchronous execution. We have defined `eventListener`s that _listen_ for events to happen, _then_ execute a callback function. But here's a new idea: eventListeners are part of the [Event API](https://developer.mozilla.org/en-US/docs/Web/API/Event). They are not part of JavaScript! 🀯 This means you can't use them in a Node REPL, but they are implemented in web browsers. The core of JavaScript is the same everywhere, but different contexts may add extra APIs. +We have already used asynchronous execution. We have defined `eventListener`s that _listen_ for events to happen, _then_ execute a callback function. + +```js +const search = document.getElementById("search"); +search.addEventListener("input", handleInput); +``` + +When we called `addEventListener` it didn't immediately call `handleInput`. + +But here's a new idea: eventListeners are part of the [Event API](https://developer.mozilla.org/en-US/docs/Web/API/Event). They are not part of the JavaScript language! 🀯 This means you can't use them in a Node REPL. But they are implemented in web browsers. The core of JavaScript (e.g. strings and functions) is the same everywhere, but different contexts may add extra APIs. When you set an eventListener you are really sending a call to a Web API and asking it do something for you. @@ -56,7 +65,7 @@ const search = document.getElementById("search"); search.addEventListener("input", handleInput); ``` -The callback `handleInput` cannot run until the user types. With `fetch`, the callback function cannot run until the data arrives. In both cases, we are waiting for something to happen before we can run our code. +The callback `handleInput` does not run until the user types. With `fetch`, the callback function does not run until the data arrives. In both cases, we are waiting for something to happen before we can run our code. We use a function as a way of wrapping up the code that needs to be run later on. This means we can tell the browser _what_ to do when we're done waiting. diff --git a/common-content/en/module/js3/break-down/index.md b/common-content/en/module/js3/break-down/index.md index 022211edf..1c01e6ca1 100644 --- a/common-content/en/module/js3/break-down/index.md +++ b/common-content/en/module/js3/break-down/index.md @@ -4,6 +4,7 @@ title = '🧩 Break down the problem' time = "30" facilitation = false emoji= '🧩' +hide_from_overview = true [objectives] 1='Identify and sequence sub tasks' [build] @@ -13,11 +14,13 @@ emoji= '🧩' +++ +We already have a website for displaying film listings. + Let's think through building this film search interface step-by-step. Write down your sequence of steps to build this interface. -> _Given_ a view of film cards and search box -> _When_ a user types in the search box -> _Then_ the view should update to show only matching films +> _Given_ a view of film cards and search box +> _When_ a user types in the search box +> _Then_ the view should update to show only matching films. {{}} {{}} @@ -47,7 +50,7 @@ The key aspects we need to handle are capturing input and updating UI. ### πŸ‘‚πŸΏ Capturing Input -We need to listen for the `input` event on the search box to react as the user types. When the event fires, we can read the updated string value from the search box input element. +We need to listen for the [`input`](https://developer.mozilla.org/en-US/docs/Web/API/Element/input_event) event on the search box to react as the user types. When the event fires, we can read the updated string value from the search box input element. ### 🎬 Filtering Data diff --git a/common-content/en/module/js3/callbacks/index.md b/common-content/en/module/js3/callbacks/index.md index f42a25442..5eca44c68 100644 --- a/common-content/en/module/js3/callbacks/index.md +++ b/common-content/en/module/js3/callbacks/index.md @@ -1,7 +1,7 @@ +++ title = 'πŸͺƒ Callbacks' -time = 20 +time = 30 facilitation = false emoji= '🧩' [objectives] @@ -19,9 +19,9 @@ Consider this visualisation of an asynchronous program: **πŸ‘‰πŸ½ [Code running out of order and off the thread](http://latentflip.com/loupe/?code=c2V0VGltZW91dChmdW5jdGlvbiB0aW1lb3V0KCkgewogICAgY29uc29sZS5sb2coIjEiKTsKfSwgMjAwMCk7CnNldFRpbWVvdXQoZnVuY3Rpb24gdGltZW91dCgpIHsKICAgIGNvbnNvbGUubG9nKCIyIik7Cn0sIDUwMCk7CnNldFRpbWVvdXQoZnVuY3Rpb24gdGltZW91dCgpIHsKICAgIGNvbnNvbGUubG9nKCIzIik7Cn0sIDApOwo%3D!!!)** -When we call [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout) we send **a function call** to a client side Web API. The code isn't executing in our single thread any more, so we can run the next line. The countdown _is_ happening, but it's not happening _in our thread_. +When we call [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout) we send **a function call** to a client side Web API. The code isn't executing in our single thread any more, so we can run the next line. The countdown _is_ happening, but it's not happening _in code we control_. -When the time runs out, our Web API sends a message to our program to let us know. This is called an {{}}An event is a signal that something has happened.{{}}. Our API sends its message to our {{}}The event loop is a JavaScript mechanism that handles asynchronous callbacks.{{}}. And what message does the event loop send? It sends a **callback**. It sends _our_ call _back_. It tells our thread to run the code in that function. +When the time runs out, the Web API sends a message to our program to let us know. This is called an {{}}An event is a signal that something has happened.{{}}. The API sends its message to our {{}}The event loop is a JavaScript mechanism that handles asynchronous callbacks.{{}}. And what message does the event loop send? It sends a **callback**. It sends _our_ call _back_. It tells our thread to run the code in that function. {{}} A callback is our function call, sent back to us through the event loop, for us to run. @@ -34,15 +34,16 @@ A callback is our function call, sent back to us through the event loop, for us Use your model to predict the order of logged numbers in the following code snippet: ```js -setTimeout(function timeout() { +setTimeout(function timeout1() { console.log("1"); }, 2000); -setTimeout(function timeout() { +setTimeout(function timeout2() { console.log("2"); }, 500); -setTimeout(function timeout() { +setTimeout(function timeout3() { console.log("3"); }, 0); +console.log("4"); ``` {{}} diff --git a/common-content/en/module/js3/capturing-events/index.md b/common-content/en/module/js3/capturing-events/index.md index 5ec6966d0..3612c9959 100644 --- a/common-content/en/module/js3/capturing-events/index.md +++ b/common-content/en/module/js3/capturing-events/index.md @@ -34,6 +34,7 @@ So our key steps are: 1. Add an input event listener to the search box 2. In the handler, get `value` of input element 3. Set the new state based on this value. +4. Call our `render` function again. {{}} But we're not going to do all of these at once! Stop and implement just the first two steps (adding the event listener, and getting the value), and `console.log` the search term. @@ -56,5 +57,3 @@ function handleSearchInput(event) { console.log(searchTerm); } ``` - -Now that we've shown we can log the search text, we can set the new value of the searchTerm state, and re-render the page. diff --git a/common-content/en/module/js3/fetch-films/index.md b/common-content/en/module/js3/fetch-films/index.md index a8519d2b9..f6ae795eb 100644 --- a/common-content/en/module/js3/fetch-films/index.md +++ b/common-content/en/module/js3/fetch-films/index.md @@ -1,7 +1,7 @@ +++ title = 'πŸ• 🎞️ fetch films' -time = 60 +time = 30 facilitation = false emoji= '🧩' [objectives] @@ -13,88 +13,33 @@ emoji= '🧩' +++ -Now that we have a basic understanding of Web APIs and Promises, let's use our knowledge to get some data from an API. There's [a list of films](/js3/blocks/fetch-films/data.json) stored in a JSON file in this directory. We'll use `fetch` to get the data from this API and then render it to the page. - -> 🎯 **Success criterion:** You have a working app that fetches data from an API and renders it to the page. - -{{}} -{{}} - -### 🧠 Think back to your [filterFilms](/filterFilms.html) project. - -1. Find your completed code. You're going to iterate on this code to fetch the films from the API instead of using the data in the file. -2. Update the state to start with an empty array. We can't work with films we haven't fetched yet! +Now that we have a basic understanding of Web APIs and Promises, let's use look again at our code for fetching film data: ```js -const state = { - films: [], -}; -``` +const endpoint = "https://programming.codeyourfuture.io/dummy-apis/films.json"; -3. Make a new `getFilms` function to use `fetch` to get the data from the API. The URL is `//curriculum.codeyourfuture.io/js3/blocks/fetch-films/data.json` - -4. Use: - -- `fetch` to get the data -- `async`/`await` to make sure the function waits for the fetch to complete before trying to get the json data from the response -- `response.json()` to get the data from the response -- a `try...catch` block to handle any errors that might occur - - {{}} - {{}} - -```js -const getFilms = async () => { - try { - const response = await fetch( - "//curriculum.codeyourfuture.io/js3/blocks/fetch-films/data.json" - ); - return await response.json(); - } catch (error) { - console.error(error); - return []; - } +const fetchFilms = async () => { + const response = await fetch(endpoint); + return await response.json(); }; -``` - -{{}} -{{}} - -We've added a `try...catch` block to handle any errors that might occur. We've also added `await` to the `fetch` and `response.json()` calls. This means that the function will sensibly wait for the `fetch` to complete before trying to get the json data from the response. - -In our last implementation, we called the render function straight away. This time, we need to wait for the films to be fetched before we can render them. Write a new async function to initialise our app. Try to write it yourself first, then check your understanding below. - -
    - -Your `init` function should look something like this: - -```js -// Initial render, which is distinct from the render function as it loads our films into memory from the API. -// Subsequent render calls do not need to call the API to get the films - we already know the films and can remember them. -async function init() { - try { - const films = await getFilms(); - state.films = films; - render(filmContainer, films); - } catch (error) { - console.error(error); - } -} +fetchFilms().then((films) => { + // When the fetchFilms Promise resolves, this callback will be called. + state.films = films; + render(); +}); ``` -The name _`init` is a convention. It has no special meaning in the JavaScript language._ +We are defining `fetchFilms`: an `async` function - a function which returns a `Promise`. -
    +When we call `fetchFilms`, what we get is an unresolved `Promise`. -### 🎁 Finally! +What `fetchFilms` does is fetch a URL (with our call to `fetch` itself returning a `Promise` resolving to a `Response`). When the `Promise` from `fetch` resolves, `fetchFilms` reads the body of the `Response` (a string), and parses is as JSON. The `Promise` returned by `fetchFilms` then resolves with the result of parsing the string as JSON. -And let's now call this function at the end of our script. +When the `Promise` from `fetchFilms` resolves, our next callback is called: We update our `state`, and call `render()`. -```js -init(); -``` +After this is done, the rest of our code works exactly the same as it did before. We _have_ our list of films in our state, so we never need to fetch the list of films again. + +`render` works the same - it only cares that `state.films` is an array of films, it doesn't care where they came from. -{{}} -🧧 Here's an [example implementation](/js3/blocks/fetch-films/filterFilms.html) you can download. -{{}} +When we change our filter by typing, events fire and our event handler will be called back exactly the same as it did before. diff --git a/common-content/en/module/js3/fetching-data/index.md b/common-content/en/module/js3/fetching-data/index.md index 85bf11001..1552797b0 100644 --- a/common-content/en/module/js3/fetching-data/index.md +++ b/common-content/en/module/js3/fetching-data/index.md @@ -16,9 +16,9 @@ emoji= '🧩' So far we have displayed film data stored in our JavaScript code. But real applications fetch data from servers over the internet. We can restate our problem as follows: -> _Given_ an **API that serves** film data -> _When_ the page first loads -> _Then_ the page should `fetch` and display the list of film data, including the film title, times and film certificate +> _Given_ an **API that serves** film data +> _When_ the page first loads +> _Then_ the page should `fetch` and display the list of film data, including the film title, times, and film certificate. ### πŸ’» Client side and 🌐 Server side APIs diff --git a/common-content/en/module/js3/identifying-state/index.md b/common-content/en/module/js3/identifying-state/index.md index b02201e74..b75662154 100644 --- a/common-content/en/module/js3/identifying-state/index.md +++ b/common-content/en/module/js3/identifying-state/index.md @@ -1,7 +1,7 @@ +++ title = 'πŸ”Ž Identifying state' -time = 15 +time = 20 facilitation = false emoji= '🧩' [objectives] @@ -33,11 +33,13 @@ In our film example, we would not store "is the search term empty" and "what is #### πŸ–‡οΈ If two things always change together, they should be one piece of state. -If our website had light mode and dark mode, we would not have one state for "is dark mode enabled" and one state for "is light mode enabled". We would have one piece of state: a {{}}true or false. On or off.{{}} +If a website allows log-in, we would not have one state for "is a user logged in" and one state for "what user is logged in". We would have one piece of state: The currently logged in user. We would set that state to `null` if there is no logged in user. We can answer the question "is a user logged in" by checking if the currently logged in user is `null`. We don't need a separate piece of state. + +#### State in our example In our film example, we need two pieces of state: 1. Our list of all films 2. The search term -When we introduce filtering films based on the search term **we will not introduce new state**. Our filtered list of films can be _derived_ from our existing state. +When we introduce filtering films based on the search term **we will not introduce other new state**. We will not store a filtered list of films in state. Our filtered list of films can be _derived_ from our existing state. diff --git a/common-content/en/module/js3/introducing-new-state/index.md b/common-content/en/module/js3/introducing-new-state/index.md index 06f72ef03..6bc2328ee 100644 --- a/common-content/en/module/js3/introducing-new-state/index.md +++ b/common-content/en/module/js3/introducing-new-state/index.md @@ -4,8 +4,6 @@ title = 'πŸ†• Introducing new state' time = 30 facilitation = false emoji= '🧩' -[objectives] - 1='' [build] render = 'never' list = 'local' @@ -41,7 +39,7 @@ const state = { We needed to pick an initial value for this state. We picked the empty string, because when someone first loads the page, they haven't searched for anything. When someone types in the search box, we will change the value of this state, and re-render the page. -We could pick _any_ initial value. This actually allows us to finish implementing our render function _before we even introduce a search box into the page_. In real life, our `searchTerm` state will be empty, but we can use different values to help us with development. We can make the page look like someone searched for "Pirate", even before we introduce a search box. +We could pick _any_ initial value. This actually allows us to finish implementing our render function _before we even introduce a search box into the page_. In real life, our `searchTerm` state will be empty at first, but we can use different values to help us with development. We can make the page look like someone searched for "Pirate", even before we introduce a search box in the UI. This is because we have split up our problem into three parts: diff --git a/common-content/en/module/js3/latency/index.md b/common-content/en/module/js3/latency/index.md index dada5534f..e010e7632 100644 --- a/common-content/en/module/js3/latency/index.md +++ b/common-content/en/module/js3/latency/index.md @@ -25,6 +25,6 @@ Latency is the time taken for a request to traverse the network. > πŸ’‘ Network latency is travel time. -Why is latency a problem? Because it means we need to **wait** for our data. But our program can only do one thing at a time - if we stopped our program to wait for data, then we wouldn't be able to do anything else. We need to handle this time problem. +Why is latency a problem? Because it means we need to **wait** for our data. But our program can only do one thing at a time. If we stopped our program to wait for data, then we wouldn't be able to do anything else (like show the rest of the page, or respond to a user clicking in the page). We need to handle this time problem. Programming often involves time problems, and latency is just one of them. diff --git a/common-content/en/module/js3/promises/index.md b/common-content/en/module/js3/promises/index.md index 5a7475ec0..1e542b25e 100644 --- a/common-content/en/module/js3/promises/index.md +++ b/common-content/en/module/js3/promises/index.md @@ -23,7 +23,7 @@ graph LR To get data from a server, we make a request with `fetch`. We act on what comes back: the response. But what happens in the middle? We already know that JavaScript is single-threaded: it can only do one thing at a time. -So do we just stop and wait? No! We have a special object to handle this time problem. Run this code in your Node REPL: +So do we just stop and wait? No! We have a special object to handle this time problem. Put this code in a file and run it with node: ```js const url = "https://api.github.com/users/SallyMcGrath"; // try your own username @@ -65,7 +65,7 @@ Promise { -The `response` in this code is not labelling [the data](https://api.github.com/users/SallyMcGrath). It's labelling a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). +The `response` variable in this code is not labelling [the data](https://api.github.com/users/SallyMcGrath). It's labelling a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). A promise is exactly what it sounds like: a promise to do something. You can use this promise object to _sequence_ your code. You can say, "When the data comes back, `then` do this." diff --git a/common-content/en/module/js3/re-rendering/index.md b/common-content/en/module/js3/re-rendering/index.md new file mode 100644 index 000000000..cffd579d7 --- /dev/null +++ b/common-content/en/module/js3/re-rendering/index.md @@ -0,0 +1,95 @@ ++++ +title = 'πŸ” Re-rendering' + +time = 10 +facilitation = false +emoji= 'πŸ”' +[objectives] + 1='Re-render a page based on user input' + 2="Debug why a page isn't re-rendering as expected" +[build] + render = 'never' + list = 'local' + publishResources = false + ++++ + +Now that we've shown we can log the search text, we can set the new value of the `searchTerm` state, and re-render the page. + +We should have a page like this: + +```html + + + + Film View + + + + + + + +``` + +We want to change our search input handler to update `state.searchTerm` and call `render()` again. + +Implement this and try searching. What happens? Play computer to work out why what's happening isn't what we expected. diff --git a/common-content/en/module/js3/reacting/index.md b/common-content/en/module/js3/reacting/index.md index bda2f065d..94b417288 100644 --- a/common-content/en/module/js3/reacting/index.md +++ b/common-content/en/module/js3/reacting/index.md @@ -5,7 +5,7 @@ time = 5 facilitation = false emoji= '🧩' [objectives] - 1='Explain how Javascript can react to user input from a search input' + 1='Explain how JavaScript can react to user input from a search input' [build] render = 'never' list = 'local' @@ -13,11 +13,11 @@ emoji= '🧩' +++ -As users interact with web applications, they trigger events like clicking buttons, submitting forms, or typing text that we need to respond to. Let's explore a common example: searching. +As users interact with web applications, they trigger events by doing things like clicking buttons, submitting forms, or typing text. We need to respond to these events. Let's explore a common example: searching. ```html ``` diff --git a/common-content/en/module/js3/refactoring-to-state-and-render/index.md b/common-content/en/module/js3/refactoring-to-state-and-render/index.md index 0ba2fa20c..4e7ba2526 100644 --- a/common-content/en/module/js3/refactoring-to-state-and-render/index.md +++ b/common-content/en/module/js3/refactoring-to-state-and-render/index.md @@ -15,19 +15,19 @@ emoji= '🧩' +++ -We are going to introduce a common pattern in writing UIs, which is to use a function called `render`. +We are going to introduce a common pattern in writing UIs, which is to define and use a function called `render`. Up until now, our film website has been **static**: it never changes. By introducing a search input, our website is becoming **dynamic**: it can change. This means that we may need to re-run the code which creates our UI elements. -So before we add the new functionality to our website, we are going to {{}}**Refactoring** is when we change _how_ our code is structured, without changing _what_ it does. Even though we have changed our code, it does exactly the same thing it did before. {{}}. Find your code from last week that creates the film cards and adds them to the page. Move your code into a function called `render`: +So before we add the new functionality to our website, we are going to {{}}**Refactoring** is when we change _how_ our code is structured, without changing _what_ it does. Even though we have changed our code, it does exactly the same thing it did before. {{}}. Find your code that creates the film cards and adds them to the page. Move your code into a function called `render`: ```js const films = [ - // You have this array from last time. + // You have this array from before. ]; function createFilmCard(filmData) { - // You should have an implementation of this function from last time. + // You should have an implementation of this function from before. } function render() { @@ -57,6 +57,8 @@ render(); Your application should now work exactly the same as it did before. Because we moved our code into a function, this means we can call that function again if we need to, for instance when someone searches for something. +We saw this same pattern when we made the character limit component. We called the same function on page load, and when someone typed something. + ### Storing our state somewhere Up until now, we had a variable called `films`, and we created some cards based on that variable. @@ -87,7 +89,9 @@ const state = { Each time we need to store more information we should think: Is this a piece of state, or is this something we're deriving from existing state? Whenever something in our state changes, we will tell our UI just to show "whatever is in the state" by calling the `render` function. In this way, we simplify our UI code by making it a function of the state. {{}} -We don't _need_ to store our state in a variable called `state`. It was _already_ state when it was called `films`. But naming this variable `state` can help us to think about it. +We don't _need_ to store our state in a variable called `state`. It was _already_ state when it was called `films`. But naming this variable `state` can help us to think about it more clearly. {{}} +Make sure to update any references to the `films` variable you may have had before to instead reference `state.films`. + This is another refactoring: we didn't change what our application does, we just moved a variable. diff --git a/common-content/en/module/js3/rendering-based-on-state/index.md b/common-content/en/module/js3/rendering-based-on-state/index.md index a965c8ca9..0170384b1 100644 --- a/common-content/en/module/js3/rendering-based-on-state/index.md +++ b/common-content/en/module/js3/rendering-based-on-state/index.md @@ -1,7 +1,7 @@ +++ title = '🎱 Rendering based on state' -time = 15 +time = 30 facilitation = false emoji= '🧩' [objectives] diff --git a/common-content/en/module/js3/then/index.md b/common-content/en/module/js3/then/index.md index 4e01f1d95..2e3d55ca0 100644 --- a/common-content/en/module/js3/then/index.md +++ b/common-content/en/module/js3/then/index.md @@ -19,15 +19,7 @@ graph LR Response ---> |sequence with| then{{πŸͺ†οΈ then}} ``` -[`.then()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then) is a method that belongs to the Promise {{}}A prototype object is like a template. `then` is a method available on any Promise.{{}} You can think of the commands as - -1. _given_ a request to `fetch` some data -2. _when_ the `response` comes back / the promise resolves to a [response object](https://developer.mozilla.org/en-US/docs/Web/API/Response) -3. `then` do this next thing with the data / execute this callback - -The `.then()` method takes in a [callback](https://www.w3schools.com/js/js_callback.asp) function that will run once the promise resolves. - -For example: +[`.then()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then) is a method that all `Promise`s have. You can interpret this code: ```js const url = "https://api.github.com/users/SallyMcGrath"; @@ -35,6 +27,12 @@ const callback = (response) => response.json(); // .json() is an instance method fetch(url).then(callback); ``` +1. _given_ a request to `fetch` some data +2. _when_ the `response` comes back / the promise resolves to a [response object](https://developer.mozilla.org/en-US/docs/Web/API/Response) +3. `then` do this next thing with the data / execute this callback + +The `.then()` method takes in a [callback](https://www.w3schools.com/js/js_callback.asp) function that will run once the promise resolves. + We can also inline the `callback` variable here - this code does exactly the same as the code above: ```js @@ -48,12 +46,12 @@ It's a similar idea as the event loop we have already investigated, but this tim The `then()` method of a `Promise` always returns a new `Promise`. {{
    }} -We can chain multiple `.then()` calls to run more logic, passing the resolved value to the next callback in the chain. This allows us to handle the asynchronous response in distinct steps. Let's create a getProfile function which we can try out in our Node REPL: +We can chain multiple `.then()` calls to run more logic, passing the resolved value to the next callback in the chain. This allows us to handle the asynchronous response in distinct steps. Let's create a getProfile function in a file, call it, and try running the file with node: ```js const getProfile = (url) => { return fetch(url) - .then((response) => response.json()) // This callback consumes the response and parses it as JSON into an object. + .then((response) => response.json()) // This callback consumes the response string and parses it as JSON into an object. .then((data) => data.html_url) // This callback takes the object and gets one property of it. .then((htmlUrl) => console.log(htmlUrl)); // This callback logs that property. }; diff --git a/common-content/en/module/js3/using-fetch/index.md b/common-content/en/module/js3/using-fetch/index.md index 07dc01dd8..2bd583a32 100644 --- a/common-content/en/module/js3/using-fetch/index.md +++ b/common-content/en/module/js3/using-fetch/index.md @@ -5,9 +5,7 @@ time = 20 facilitation = false emoji= '🧩' [objectives] -1="List 5 preceding concepts of asynchronous programming in JavaScript" -2="Identify 2 unknown concepts still to be learned" -3="Fetch data from a server side API using a client side Web API" +1="Fetch data from a server side API using a client side Web API" [build] render = 'never' list = 'local' @@ -17,32 +15,37 @@ emoji= '🧩' So now we have these pieces of our giant concept map -1. πŸ“€ we know that we can [send a request](#fetching-data) using `fetch()` -1. πŸ• we know that `fetch` is a [πŸ’» client side 🧰 Web API](#fetching-data) -1. πŸ—“οΈ we know that sending πŸ“€ requests over a network takes [time](#latency) -1. 🧡 we know that we should [not stop our program](#asynchrony) to wait for data -1. πŸͺƒ we know that we can [use callbacks](#callbacks) to manage events +1. πŸ“€ we know that we can send a request using `fetch()` +1. πŸ• we know that `fetch` is a πŸ’» client side 🧰 Web API +1. πŸ—“οΈ we know that sending requests over a network takes time +1. 🧡 we know that we should not stop our program to wait for data +1. πŸͺƒ we know that we can use callbacks to manage events -But we still don’t know how to use `fetch` to get data from a server side API. Let’s find this out now. In [our filterFilms code](https://curriculum.codeyourfuture.io/filterfilms), replace the films array with data fetched from a server. +But we still don’t know how to use `fetch` to get data from a server side API. Let’s find this out now. + +Let's pick our film display exercise back up. Before we had a list of films hard-coded in our `state`. We're going to replace the films array with data fetched from a server. ```js // Begin with an empty state const state = { films: [], + searchTerm: "", }; -// Data -const endpoint = "//curriculum.codeyourfuture.io/dummy-apis/films.json"; + +const endpoint = "https://programming.codeyourfuture.io/dummy-apis/films.json"; const fetchFilms = async () => { const response = await fetch(endpoint); return await response.json(); -}; // our async function returns a Promise +}; // Our async function returns a Promise fetchFilms().then((films) => { - render(filmContainer, films); // when + // When the fetchFilms Promise resolves, this callback will be called. + state.films = films; + render(); }); ``` -πŸ• `fetch` returns a πŸ«±πŸΏβ€πŸ«²πŸ½ ‍`Promise`; the πŸ«±πŸΏβ€πŸ«²πŸ½ `Promise` fulfils itself with a πŸ“₯ response; the response contains our πŸ’Ύ data. +`fetch` returns a `Promise`; the `Promise` fulfils itself with a response; the response contains our data. -We will dig into this syntax: `Promises`, `async`, `await`, and `then` in our next sprint and complete our concept map. +Next we will dig into `Promise`s, `async`, `await`, and `then`, and complete our concept map. diff --git a/org-cyf-itp/content/data-flows/sprints/2/prep/index.md b/org-cyf-itp/content/data-flows/sprints/2/prep/index.md index 28761e80f..273e944a0 100644 --- a/org-cyf-itp/content/data-flows/sprints/2/prep/index.md +++ b/org-cyf-itp/content/data-flows/sprints/2/prep/index.md @@ -12,7 +12,7 @@ src="module/js3/reacting" name="Step-through-prep workshop" src="https://www.youtube.com/watch?v=7kYwo6W89j4" [[blocks]] -name="Decomposition" +name="Breaking down the problem" src="module/js3/break-down" [[blocks]] name="Identifying state" @@ -29,4 +29,10 @@ src="module/js3/rendering-based-on-state" [[blocks]] name="Capturing events" src="module/js3/capturing-events" +[[blocks]] +name="Re-rendering" +src="module/js3/re-rendering" +[[blocks]] +name="Actually re-rendering" +src="module/js3/actually-re-rendering" +++ diff --git a/org-cyf-itp/content/data-flows/sprints/3/prep/index.md b/org-cyf-itp/content/data-flows/sprints/3/prep/index.md index fd8686bed..56a6999d8 100644 --- a/org-cyf-itp/content/data-flows/sprints/3/prep/index.md +++ b/org-cyf-itp/content/data-flows/sprints/3/prep/index.md @@ -32,9 +32,6 @@ src="module/js3/then" name="Async/Await" src="module/js3/async-await" [[blocks]] -name="Catch" -src="module/js3/catch" -[[blocks]] name="Fetch Films" src="module/js3/fetch-films" +++ diff --git a/org-cyf-itp/content/data-flows/success/index.md b/org-cyf-itp/content/data-flows/success/index.md index 7ca2a761e..913c90bde 100644 --- a/org-cyf-itp/content/data-flows/success/index.md +++ b/org-cyf-itp/content/data-flows/success/index.md @@ -9,7 +9,7 @@ weight = 11 1="Link to your PR for [Array Destructuring](https://github.com/CodeYourFuture/Module-Data-Flows/issues/24), which you have had reviewed and responded to feedback." 2="Link to your PR for your [Book Library Project](https://github.com/CodeYourFuture/Module-Data-Flows/issues/31) which you have had reviewed and responded to feedback." 3="Work out your LinkedIn [Social Selling Index](https://github.com/CodeYourFuture/Module-Data-Flows/issues/12), and posted the actions you will take to improve this" -4="Link to your PR for [Javascript challenges](https://github.com/CodeYourFuture/Module-Data-Flows/issues/21), which you have had reviewed and responded to feedback." +4="Link to your PR for [JavaScript challenges](https://github.com/CodeYourFuture/Module-Data-Flows/issues/21), which you have had reviewed and responded to feedback." +++ @@ -17,14 +17,13 @@ weight = 11 ### 🎯 You've achieved your learning objectives if you can: ```objectives +- Describe the concept of state, how to use it and what should be stored in it. +- Identify the state in a problem - Use the map function to transform data -- Create elements in the DOM, using Javascript +- Create elements in the DOM, using JavaScript +- Create re-usable components in the DOM - Trigger changes by listening to events - Fetch data from an API - -```see above -- Catch errors in your code -- Describe the concept of state, how to use it and what should be stored in it. - Build a UI that renders content using data stored in state, which can be updated on user interaction ``` From 21a309fa37551aa078c7490a2dfee6aa4cf0fb6d Mon Sep 17 00:00:00 2001 From: Sally McGrath Date: Sat, 26 Oct 2024 09:46:03 +0100 Subject: [PATCH 7/7] fix missing workshop class starts in 10... --- .../content/structuring-data/sprints/1/day-plan/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org-cyf-itp/content/structuring-data/sprints/1/day-plan/index.md b/org-cyf-itp/content/structuring-data/sprints/1/day-plan/index.md index 4640ab72a..785c29914 100644 --- a/org-cyf-itp/content/structuring-data/sprints/1/day-plan/index.md +++ b/org-cyf-itp/content/structuring-data/sprints/1/day-plan/index.md @@ -17,7 +17,7 @@ src="blocks/workshop" time="140" [[blocks.nested.blocks]] name="Reporting Bugs [PD] (60 Mins)" - src="https://github.com/CodeYourFuture/CYF-Workshops/tree/main/git-cli" + src="https://github.com/CodeYourFuture/CYF-Workshops/tree/main/reporting-bugs" time="0" [[blocks.nested.blocks]] name="Playing Computer [Tech] (60 Mins)"