Skip to content

Commit

Permalink
Tutorial Updates (#2051)
Browse files Browse the repository at this point in the history
* Updates to the tutorial so it works on 5.11-beta

---

Commit:  ember-learn/super-rentals-tutorial@52a367d
Script:  https://github.com/ember-learn/super-rentals-tutorial/blob/52a367d4067008f3f854bf3ca0b5485e1610c396/.github/workflows/build.yml
Logs:    https://github.com/ember-learn/super-rentals-tutorial/commit/52a367d4067008f3f854bf3ca0b5485e1610c396/checks

* Switch to using a <form> element and FormData rather than <Input>

---

Commit:  ember-learn/super-rentals-tutorial@a5132bc
Script:  https://github.com/ember-learn/super-rentals-tutorial/blob/a5132bc00ad27196f28e10cce9c1e9dece1660d1/.github/workflows/build.yml
Logs:    https://github.com/ember-learn/super-rentals-tutorial/commit/a5132bc00ad27196f28e10cce9c1e9dece1660d1/checks

* [CRON] Thursday Sep 05, 2024

---

Commit:  ember-learn/super-rentals-tutorial@a5132bc
Script:  https://github.com/ember-learn/super-rentals-tutorial/blob/a5132bc00ad27196f28e10cce9c1e9dece1660d1/.github/workflows/build.yml
Logs:    https://github.com/ember-learn/super-rentals-tutorial/commit/a5132bc00ad27196f28e10cce9c1e9dece1660d1/checks

* Correct link to finished app

---

Commit:  ember-learn/super-rentals-tutorial@e076a15
Script:  https://github.com/ember-learn/super-rentals-tutorial/blob/e076a153d94e7392fc9657461b261f339e498aef/.github/workflows/build.yml
Logs:    https://github.com/ember-learn/super-rentals-tutorial/commit/e076a153d94e7392fc9657461b261f339e498aef/checks

* [CRON] Monday Sep 09, 2024

---

Commit:  ember-learn/super-rentals-tutorial@e076a15
Script:  https://github.com/ember-learn/super-rentals-tutorial/blob/e076a153d94e7392fc9657461b261f339e498aef/.github/workflows/build.yml
Logs:    https://github.com/ember-learn/super-rentals-tutorial/commit/e076a153d94e7392fc9657461b261f339e498aef/checks

* [CRON] Tuesday Sep 10, 2024

---

Commit:  ember-learn/super-rentals-tutorial@e076a15
Script:  https://github.com/ember-learn/super-rentals-tutorial/blob/e076a153d94e7392fc9657461b261f339e498aef/.github/workflows/build.yml
Logs:    https://github.com/ember-learn/super-rentals-tutorial/commit/e076a153d94e7392fc9657461b261f339e498aef/checks

* [CRON] Thursday Sep 12, 2024

---

Commit:  ember-learn/super-rentals-tutorial@e076a15
Script:  https://github.com/ember-learn/super-rentals-tutorial/blob/e076a153d94e7392fc9657461b261f339e498aef/.github/workflows/build.yml
Logs:    https://github.com/ember-learn/super-rentals-tutorial/commit/e076a153d94e7392fc9657461b261f339e498aef/checks

* [CRON] Friday Sep 13, 2024

---

Commit:  ember-learn/super-rentals-tutorial@e076a15
Script:  https://github.com/ember-learn/super-rentals-tutorial/blob/e076a153d94e7392fc9657461b261f339e498aef/.github/workflows/build.yml
Logs:    https://github.com/ember-learn/super-rentals-tutorial/commit/e076a153d94e7392fc9657461b261f339e498aef/checks

* [CRON] Saturday Sep 14, 2024

---

Commit:  ember-learn/super-rentals-tutorial@e076a15
Script:  https://github.com/ember-learn/super-rentals-tutorial/blob/e076a153d94e7392fc9657461b261f339e498aef/.github/workflows/build.yml
Logs:    https://github.com/ember-learn/super-rentals-tutorial/commit/e076a153d94e7392fc9657461b261f339e498aef/checks

* [CRON] Sunday Sep 15, 2024

---

Commit:  ember-learn/super-rentals-tutorial@e076a15
Script:  https://github.com/ember-learn/super-rentals-tutorial/blob/e076a153d94e7392fc9657461b261f339e498aef/.github/workflows/build.yml
Logs:    https://github.com/ember-learn/super-rentals-tutorial/commit/e076a153d94e7392fc9657461b261f339e498aef/checks

* [CRON] Monday Sep 16, 2024

---

Commit:  ember-learn/super-rentals-tutorial@e076a15
Script:  https://github.com/ember-learn/super-rentals-tutorial/blob/e076a153d94e7392fc9657461b261f339e498aef/.github/workflows/build.yml
Logs:    https://github.com/ember-learn/super-rentals-tutorial/commit/e076a153d94e7392fc9657461b261f339e498aef/checks

* [CRON] Tuesday Sep 17, 2024

---

Commit:  ember-learn/super-rentals-tutorial@e076a15
Script:  https://github.com/ember-learn/super-rentals-tutorial/blob/e076a153d94e7392fc9657461b261f339e498aef/.github/workflows/build.yml
Logs:    https://github.com/ember-learn/super-rentals-tutorial/commit/e076a153d94e7392fc9657461b261f339e498aef/checks

* [CRON] Wednesday Sep 18, 2024

---

Commit:  ember-learn/super-rentals-tutorial@e076a15
Script:  https://github.com/ember-learn/super-rentals-tutorial/blob/e076a153d94e7392fc9657461b261f339e498aef/.github/workflows/build.yml
Logs:    https://github.com/ember-learn/super-rentals-tutorial/commit/e076a153d94e7392fc9657461b261f339e498aef/checks

* [CRON] Friday Sep 20, 2024

---

Commit:  ember-learn/super-rentals-tutorial@e076a15
Script:  https://github.com/ember-learn/super-rentals-tutorial/blob/e076a153d94e7392fc9657461b261f339e498aef/.github/workflows/build.yml
Logs:    https://github.com/ember-learn/super-rentals-tutorial/commit/e076a153d94e7392fc9657461b261f339e498aef/checks

* [CRON] Saturday Sep 21, 2024

---

Commit:  ember-learn/super-rentals-tutorial@e076a15
Script:  https://github.com/ember-learn/super-rentals-tutorial/blob/e076a153d94e7392fc9657461b261f339e498aef/.github/workflows/build.yml
Logs:    https://github.com/ember-learn/super-rentals-tutorial/commit/e076a153d94e7392fc9657461b261f339e498aef/checks

* [CRON] Sunday Sep 22, 2024

---

Commit:  ember-learn/super-rentals-tutorial@e076a15
Script:  https://github.com/ember-learn/super-rentals-tutorial/blob/e076a153d94e7392fc9657461b261f339e498aef/.github/workflows/build.yml
Logs:    https://github.com/ember-learn/super-rentals-tutorial/commit/e076a153d94e7392fc9657461b261f339e498aef/checks

* [CRON] Monday Sep 23, 2024

---

Commit:  ember-learn/super-rentals-tutorial@e076a15
Script:  https://github.com/ember-learn/super-rentals-tutorial/blob/e076a153d94e7392fc9657461b261f339e498aef/.github/workflows/build.yml
Logs:    https://github.com/ember-learn/super-rentals-tutorial/commit/e076a153d94e7392fc9657461b261f339e498aef/checks

---------

Co-authored-by: Tomster <tomster@emberjs.com>
  • Loading branch information
github-actions[bot] and ember-tomster committed Sep 23, 2024
1 parent e2d53ec commit d039e1a
Show file tree
Hide file tree
Showing 19 changed files with 84 additions and 54 deletions.
2 changes: 1 addition & 1 deletion guides/release/tutorial/part-1/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Welcome to the Ember Tutorial!

In this tutorial, we will use Ember to build an application called Super Rentals. This will be a website for browsing interesting places to stay during your next vacation. Check out the [finished app](https://ember-super-rentals.netlify.com) to get a sense of the scope of the project.
In this tutorial, we will use Ember to build an application called Super Rentals. This will be a website for browsing interesting places to stay during your next vacation. Check out the [finished app](https://ember-super-rentals.netlify.app) to get a sense of the scope of the project.

<img src="/images/tutorial/part-2/provider-components/homepage-with-rentals-component@2x.png" alt="The finished Super Rentals app" width="1024" height="1328">

Expand Down
8 changes: 4 additions & 4 deletions guides/release/tutorial/part-1/interactive-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ Ember will create an _instance_ of the class whenever our component is invoked.
```js { data-filename="app/components/rental/image.js" data-diff="-3,+4,+5,+6,+7,+8,+9" }
import Component from '@glimmer/component';

export default class RentalImageComponent extends Component {}
export default class RentalImageComponent extends Component {
export default class RentalImage extends Component {}
export default class RentalImage extends Component {
constructor(...args) {
super(...args);
this.isLarge = false;
Expand Down Expand Up @@ -103,7 +103,7 @@ Since this pattern of initializing instance variables in the constructor is pret
```js { data-filename="app/components/rental/image.js" data-diff="-4,-5,-6,-7,+8" }
import Component from '@glimmer/component';

export default class RentalImageComponent extends Component {
export default class RentalImage extends Component {
constructor(...args) {
super(...args);
this.isLarge = false;
Expand All @@ -125,7 +125,7 @@ import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';

export default class RentalImageComponent extends Component {
export default class RentalImage extends Component {
isLarge = false;
@tracked isLarge = false;

Expand Down
10 changes: 5 additions & 5 deletions guides/release/tutorial/part-1/orientation.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ To verify that your installation was successful, run:

```shell
$ ember --version
ember-cli: 5.9.0
node: 18.20.3
ember-cli: 5.11.0
node: 18.20.4
os: linux x64
```

Expand All @@ -38,7 +38,7 @@ We can create a new project using Ember CLI's `new` command. It follows the patt
```shell
$ ember new super-rentals --lang en
installing app
Ember CLI v5.9.0
Ember CLI v5.11.0

Creating a new Ember app in /home/runner/work/super-rentals-tutorial/super-rentals-tutorial/dist/code/super-rentals:
create .editorconfig
Expand Down Expand Up @@ -206,11 +206,11 @@ As text on the welcome page pointed out, the source code for the page is located
```handlebars { data-filename="app/templates/application.hbs" data-diff="-1,-2,-3,-4,-5,-6,-7,+8" }
{{page-title "SuperRentals"}}
{{outlet}}
{{! The following component displays Ember's default welcome message. }}
<WelcomePage />
{{! Feel free to remove this! }}
{{outlet}}
Hello World!!!
```

Expand Down
8 changes: 4 additions & 4 deletions guides/release/tutorial/part-1/reusable-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,15 +148,15 @@ Let's start with our JavaScript file:
import Component from '@glimmer/component';
import ENV from 'super-rentals/config/environment';

export default class MapComponent extends Component {}
export default class MapComponent extends Component {
export default class Map extends Component {}
export default class Map extends Component {
get token() {
return encodeURIComponent(ENV.MAPBOX_ACCESS_TOKEN);
}
}
```

Here, we import the access token from the config file and return it from a `token` _[getter](https://javascript.info/property-accessors)_. This allows us to access our token as `this.token` both inside the `MapComponent` class body, as well as the component's template. It is also important to [URL-encode](https://javascript.info/url#encoding-strings) the token, just in case it contains any special characters that are not URL-safe.
Here, we import the access token from the config file and return it from a `token` _[getter](https://javascript.info/property-accessors)_. This allows us to access our token as `this.token` both inside the `Map` class body, as well as the component's template. It is also important to [URL-encode](https://javascript.info/url#encoding-strings) the token, just in case it contains any special characters that are not URL-safe.

## Interpolating Values in Templates

Expand Down Expand Up @@ -404,7 +404,7 @@ import ENV from 'super-rentals/config/environment';
const MAPBOX_API = 'https://api.mapbox.com/styles/v1/mapbox/streets-v11/static';

export default class MapComponent extends Component {
export default class Map extends Component {
get src() {
let { lng, lat, width, height, zoom } = this.args;

Expand Down
2 changes: 1 addition & 1 deletion guides/release/tutorial/part-2/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Hooray, you've made it to the second part of the tutorial! In the following sect

Along the way, we'll also add some new features to our Super Rentals app. By the end of this section, we'll have implemented some search functionality and refactored a good bit of our code to use some new Ember concepts

<img src="/images/tutorial/part-2/provider-components/filtered-results@2x.png" alt="Search functionality in the Super Rentals app" width="1024" height="798">
<img src="/images/tutorial/part-2/provider-components/filtered-results@2x.png" alt="Search functionality in the Super Rentals app" width="1024" height="833">

In part two, we'll cover the following concepts:

Expand Down
96 changes: 60 additions & 36 deletions guides/release/tutorial/part-2/provider-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ In this chapter, we'll work on adding a new search feature, and refactor our `in

<!-- TODO: make this a gif instead -->

<img src="/images/tutorial/part-2/provider-components/filtered-results@2x.png" alt="The Super Rentals app by the end of the chapter" width="1024" height="798">
<img src="/images/tutorial/part-2/provider-components/filtered-results@2x.png" alt="The Super Rentals app by the end of the chapter" width="1024" height="833">

During this refactor, you will learn about:

- Using Ember's built-in `<Input>` component
- Using forms
- The provider component pattern
- Using block parameters when invoking components
- Yielding data to caller components
Expand All @@ -17,20 +17,22 @@ During this refactor, you will learn about:

As our app grows and as we add more features to it, one thing that would be really nice to have is some search functionality. It would be great if our users could just type a word into a search box and our app could just respond with matching and relevant rentals. So how could we go about implementing this feature?

Well, we can start simple. Before we worry about implementing the "search" part of this feature, let's just get something on the page. The first step is to add an `<input>` tag to our `index` page, and make it look pretty with a class.
Well, we can start simple. Before we worry about implementing the "search" part of this feature, let's just get something on the page. The first step is to add a form with an `<input>` tag to our `index` page, and make it look pretty with a class.

```handlebars { data-filename="app/templates/index.hbs" data-diff="+8,+9,+10,+11,+12" }
```handlebars { data-filename="app/templates/index.hbs" data-diff="+8,+9,+10,+11,+12,+13,+14" }
<Jumbo>
<h2>Welcome to Super Rentals!</h2>
<p>We hope you find exactly what you're looking for in a place to stay.</p>
<LinkTo @route="about" class="button">About Us</LinkTo>
</Jumbo>
<div class="rentals">
<label>
<span>Where would you like to stay?</span>
<input class="light">
</label>
<form>
<label>
<span>Where would you like to stay?</span>
<input class="light">
</label>
</form>
<ul class="results">
{{#each @model as |rental|}}
Expand Down Expand Up @@ -58,10 +60,12 @@ Let's start simple again and begin our refactor by creating a new template for o

```handlebars { data-filename="app/components/rentals.hbs" }
<div class="rentals">
<label>
<span>Where would you like to stay?</span>
<input class="light">
</label>
<form>
<label>
<span>Where would you like to stay?</span>
<input class="light">
</label>
</form>
<ul class="results">
{{#each @rentals as |rental|}}
Expand All @@ -73,18 +77,20 @@ Let's start simple again and begin our refactor by creating a new template for o

There is one minor change to note here: while extracting our markup into a component, we also renamed the `@model` argument to be `@rentals` instead, just in order to be a little more specific about what we're iterating over in our `{{#each}}` loop. Otherwise, all we're doing here is copy-pasting what was on our `index.hbs` page into our new component template. Now we just need to actually use our new component in the index template where we started this whole refactor! Let's render our `<Rentals>` component in our `index.hbs` template.

```handlebars { data-filename="app/templates/index.hbs" data-diff="-7,-8,-9,-10,-11,-12,-13,-14,-15,-16,-17,-18,+19" }
```handlebars { data-filename="app/templates/index.hbs" data-diff="-7,-8,-9,-10,-11,-12,-13,-14,-15,-16,-17,-18,-19,-20,+21" }
<Jumbo>
<h2>Welcome to Super Rentals!</h2>
<p>We hope you find exactly what you're looking for in a place to stay.</p>
<LinkTo @route="about" class="button">About Us</LinkTo>
</Jumbo>
<div class="rentals">
<label>
<span>Where would you like to stay?</span>
<input class="light">
</label>
<form>
<label>
<span>Where would you like to stay?</span>
<input class="light">
</label>
</form>
<ul class="results">
{{#each @model as |rental|}}
Expand Down Expand Up @@ -198,28 +204,45 @@ Now, if we try running our tests, they should all pass after making this change.

<img src="/images/tutorial/part-2/provider-components/pass-1@2x.png" alt="The new test is passing." width="1024" height="1024">

## Using Ember's `<Input>`
## Using a `form`

Now that we have our component all set up, we can finally wire up our search box and store our search query! First things first: let's create a component class to store our query state.
Now that we have our component all set up, we can finally wire up our search box and store our search query! First things first: let's create a component class to store our query state and handle events from the `form` element:

```js { data-filename="app/components/rentals.js" }
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';

export default class RentalsComponent extends Component {
export default class Rentals extends Component {
@tracked query = '';

@action
updateQuery(event) {
let formData = new FormData(event.currentTarget);
this.query = formData.get('rental-search-term');
}

@action
handleSubmit(event) {
event.preventDefault();
this.updateQuery(event);
}
}
```

Next, we'll wire up our query state in the component template.

```handlebars { data-filename="app/components/rentals.hbs" data-diff="-4,+5" }
```handlebars { data-filename="app/components/rentals.hbs" data-diff="-2,+3,-6,+7,+9" }
<div class="rentals">
<label>
<span>Where would you like to stay?</span>
<input class="light">
<Input @value={{this.query}} class="light" />
</label>
<form>
<form {{on "input" this.updateQuery}} {{on "submit" this.handleSubmit}}>
<label>
<span>Where would you like to stay?</span>
<input class="light">
<input name="rental-search-term" class="light">
</label>
<p>The results below will update as you type.</p>
</form>
<ul class="results">
{{#each @rentals as |rental|}}
Expand All @@ -229,9 +252,7 @@ Next, we'll wire up our query state in the component template.
</div>
```

Interesting! There are a few things happening in this one-line template change. First, we're moving from using a plain HTML `<input>` tag to using an `<Input>` tag instead! As it turns out, Ember provides us with a helpful little _[`<Input>` component](../../../components/built-in-components/#toc_input)_ for this exact use case. The `<Input>` component is actually just a wrapper around the `<input>` element.

Ember's `<Input>` component is pretty neat; it will wire up things behind the scenes such that, whenever the user types something into the input box, `this.query` changes accordingly. In other words, `this.query` is kept in sync with the value of what is being searched; we finally have the perfect way of storing the state of our search query!
[`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) is a built-in JavaScript object for handling forms. It requires the `name` attribute on the `input`. We handle both `submit` and `input` events for the form so that the query updates both when the user types into the input and when they submit the form.

<div class="cta">
<div class="cta-note">
Expand All @@ -252,7 +273,7 @@ Now that our search query is wired up to our `<Rentals>` component, we can get i
```js { data-filename="app/components/rentals/filter.js" }
import Component from '@glimmer/component';

export default class RentalsFilterComponent extends Component {
export default class RentalsFilter extends Component {
get results() {
let { rentals, query } = this.args;

Expand All @@ -275,12 +296,15 @@ In our component template, we are not actually _rendering_ anything. Instead, we

Well, in order to answer this question, let's look at how the data that we're yielding is being used in the `<Rentals>` component.

```handlebars { data-filename="app/components/rentals.hbs" data-diff="-8,-9,-10,+11,+12,+13,+14,+15" }
```handlebars { data-filename="app/components/rentals.hbs" data-diff="-11,-12,-13,+14,+15,+16,+17,+18" }
<div class="rentals">
<label>
<span>Where would you like to stay?</span>
<Input @value={{this.query}} class="light" />
</label>
<form {{on "input" this.updateQuery}} {{on "submit" this.handleSubmit}}>
<label>
<span>Where would you like to stay?</span>
<input name="rental-search-term" class="light">
</label>
<p>The results below will update as you type.</p>
</form>
<ul class="results">
{{#each @rentals as |rental|}}
Expand Down Expand Up @@ -330,7 +354,7 @@ This is called the _provider component pattern_, which we see in action with one

Okay, now that we have a better sense of which component is rendering what and the theory behind why all of this is happening, let's answer the big unanswered question: does this even work? If we try out our search box in the UI, what happens?

<img src="/images/tutorial/part-2/provider-components/filtered-results@2x.png" alt="Trying out the search box." width="1024" height="798">
<img src="/images/tutorial/part-2/provider-components/filtered-results@2x.png" alt="Trying out the search box." width="1024" height="833">

Hooray, it works! Awesome. Now that we've tried this out manually in the UI, let's write a test for this new behavior as well.

Expand Down
6 changes: 3 additions & 3 deletions guides/release/tutorial/part-2/service-injection.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,10 @@ Whew! Let's look at the JavaScript class next.
```js { data-filename="app/components/share-button.js" data-diff="-3,+4,+5,+6,+7,+8,+9,+10,+11,+12,+13,+14,+15,+16,+17,+18,+19,+20,+21,+22,+23,+24,+25,+26,+27,+28,+29,+30" }
import Component from '@glimmer/component';

export default class ShareButtonComponent extends Component {}
export default class ShareButton extends Component {}
const TWEET_INTENT = 'https://twitter.com/intent/tweet';

export default class ShareButtonComponent extends Component {
export default class ShareButton extends Component {
get currentURL() {
return window.location.href;
}
Expand Down Expand Up @@ -324,7 +324,7 @@ import Component from '@glimmer/component';

const TWEET_INTENT = 'https://twitter.com/intent/tweet';

export default class ShareButtonComponent extends Component {
export default class ShareButton extends Component {
@service router;

get currentURL() {
Expand Down
6 changes: 6 additions & 0 deletions public/downloads/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@ p {
font-style: italic;
}

.rentals form p {
font-size: 80%;
display: block;
text-align: center;
}

.rentals input {
padding: 11px;
font-size: 18px;
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/images/tutorial/part-2/ember-data/detailed@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/images/tutorial/part-2/ember-data/homepage@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/images/tutorial/part-2/route-params/broken-links@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit d039e1a

Please sign in to comment.