diff --git a/guides/release/tutorial/part-1/interactive-components.md b/guides/release/tutorial/part-1/interactive-components.md
index 14c5467694..92a1853ab9 100644
--- a/guides/release/tutorial/part-1/interactive-components.md
+++ b/guides/release/tutorial/part-1/interactive-components.md
@@ -226,11 +226,10 @@ Finally, we added the `@action` decorator to our method. This indicates to Ember
With that, it's time to wire this up in the template section:
-```gjs { data-filename="app/components/rental/image.gjs" data-diff="+4,-15,+16,-19,+20,-22,+23,-26,+27" }
+```gjs { data-filename="app/components/rental/image.gjs" data-diff="-14,+15,-18,+19,-21,+22,-25,+26" }
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
-import { on } from '@ember/modifier';
export default class RentalImage extends Component {
@tracked isLarge = false;
@@ -263,7 +262,7 @@ We changed two things here.
First, since we wanted to make our component interactive, we switched the containing tag from `
` to `
` (this is important for accessibility reasons). By using the correct semantic tag, we will also get focusability and keyboard interaction handling "for free".
-Next, we used the `{{on}}` _[modifier](../../../components/template-lifecycle-dom-and-modifiers/#toc_event-handlers)_ to attach `this.toggleSize` as a click handler on the button. The `{{on}}` modifier is imported from the `@ember/modifier` package, which is part of Ember.
+Next, we used the `{{on}}` _[modifier](../../../components/template-lifecycle-dom-and-modifiers/#toc_event-handlers)_ to attach `this.toggleSize` as a click handler on the button. The `{{on}}` modifier is a built-in keyword in Ember.
With that, we have created our first _interactive_ component. Go ahead and try it in the browser!
@@ -337,11 +336,10 @@ Let's clean up our template before moving on. We introduced a lot of duplication
These changes are buried deep within the large amount of duplicated code. We can reduce the duplication by using an `{{if}}` _[expression](../../../components/conditional-content/#toc_inline-if)_ instead:
-```gjs { data-filename="app/components/rental/image.gjs" data-diff="-14,-15,-16,+17,+18,+19,-21,-22,-23,-24,+25,-27,-28,+29,+30" }
+```gjs { data-filename="app/components/rental/image.gjs" data-diff="-13,-14,-15,+16,+17,+18,-20,-21,-22,-23,+24,-26,-27,+28,+29" }
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
-import { on } from '@ember/modifier';
export default class RentalImage extends Component {
@tracked isLarge = false;
@@ -376,11 +374,10 @@ The expression version of `{{if}}` takes two arguments. The first argument is th
Optionally, `{{if}}` can take a third argument for what the expression should evaluate into if the condition is false. This means we could rewrite the button label like so:
-```gjs { data-filename="app/components/rental/image.gjs" data-diff="-16,-17,-18,-19,-20,+21" }
+```gjs { data-filename="app/components/rental/image.gjs" data-diff="-15,-16,-17,-18,-19,+20" }
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
-import { on } from '@ember/modifier';
export default class RentalImage extends Component {
@tracked isLarge = false;
diff --git a/guides/release/tutorial/part-1/orientation.md b/guides/release/tutorial/part-1/orientation.md
index aff222b8f2..12b272bde2 100644
--- a/guides/release/tutorial/part-1/orientation.md
+++ b/guides/release/tutorial/part-1/orientation.md
@@ -24,7 +24,7 @@ To verify that your installation was successful, run:
```shell
$ ember --version
-ember-cli: 6.12.0
+ember-cli: 7.3.0
node: 20.20.2
os: linux x64
```
@@ -143,14 +143,13 @@ super-rentals
│ │ ├── vendor.css
│ │ └── vendor.js
│ ├── assets
-│ │ ├── app-DgioD2rk.css
-│ │ ├── app-kH0EqUK0.js
-│ │ ├── dist-BUdT4Swa.js
-│ │ ├── main-BINtkAbW.js
-│ │ ├── manager-C2ILo-5D.js
-│ │ ├── modules-4-12-BPXQYON-.js
-│ │ ├── tests-BdSWFklr.css
-│ │ └── tests-CIHkQsCT.js
+│ │ ├── app-B85jO3oc.css
+│ │ ├── app-D1Y5nz2p.js
+│ │ ├── main-Ccofqu7K.js
+│ │ ├── modules-4-12-C-gQXu4k.js
+│ │ ├── rolldown-runtime-hePW80VL.js
+│ │ ├── tests-C0U09n5o.css
+│ │ └── tests-CH0rHjgr.js
│ ├── ember-welcome-page
│ │ └── construction.png
│ ├── tests
@@ -192,7 +191,7 @@ super-rentals
├── testem.cjs
└── vite.config.mjs
-26 directories, 59 files
+26 directories, 58 files
```
We'll learn about the purposes of these files and folders as we go. For now, just know that we'll spend most of our time working within the `app` folder.
@@ -219,11 +218,11 @@ Build successful (9761ms)
Slowest Nodes (totalTime >= 5%) | Total (avg)
-+-
-Babel: @embroider/macros (1) | 330ms
+Babel: @embroider/macros (1) | 257ms
- VITE v8.0.11 ready in 3171 ms
+ VITE v8.3.3 ready in 2587 ms
➜ Local: http://localhost:4200/
```
diff --git a/guides/release/tutorial/part-1/reusable-components.md b/guides/release/tutorial/part-1/reusable-components.md
index b94d6fb374..7296ec1dfe 100644
--- a/guides/release/tutorial/part-1/reusable-components.md
+++ b/guides/release/tutorial/part-1/reusable-components.md
@@ -6,83 +6,149 @@ The last missing feature for the `` component is a map to show the locat
While adding the map, you will learn about:
-- Managing application-level configurations
+- Installing and using third-party packages
+- Using modifiers to interact with the DOM
- Parameterizing components with arguments
- Accessing component arguments
-- Interpolating values in templates
+- Safely injecting styles with `trustHTML`
- Overriding HTML attributes in `...attributes`
- Refactoring with getters and auto-track
- Getting JavaScript values into the test context
+- Centralizing configuration in `config/environment`
-## Managing Application-level Configurations
+## Generating a Component with a Component Class
-We will use the [TomTom](https://developer.tomtom.com/map-display-api/documentation/product-information/introduction) API to generate maps for our rental properties. You can [sign up](https://developer.tomtom.com) for free and without a credit card.
+We will use [MapLibre GL JS](https://maplibre.org/), an open-source mapping library, to render interactive maps. Since MapLibre GL is just an npm package, we can install and use it exactly as we would in any plain JavaScript project.
-TomTom provides a [static map images API](https://developer.tomtom.com/map-display-api/documentation/raster/static-image), which serves map images in PNG format. This means that we can generate the appropriate URL for the parameters we want and render the map using a standard ` ` tag. Pretty neat!
+Let's add it to our app:
-Once you have signed up, grab your _[default public token](https://developer.tomtom.com/user/me/apps)_ and paste it into `config/environment.js`:
+```shell
+$ npm install maplibre-gl@6 --save-dev
+ WARN deprecated eslint@9.39.5: This version is no longer supported. Please see https://eslint.org/version-support for other options.
+../../.. | +23 ++
+devDependencies:
++ maplibre-gl 6.13.0
+```
-```js { data-filename="config/environment.js" data-diff="+48,+49" }
-'use strict';
+Now let's generate a new component for our map.
-module.exports = function (environment) {
- const ENV = {
- modulePrefix: 'super-rentals',
- environment,
- rootURL: '/',
- locationType: 'history',
- EmberENV: {
- RAISE_ON_DEPRECATION: true,
- EXTEND_PROTOTYPES: false,
- FEATURES: {
- // Here you can enable experimental features on an ember canary build
- // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true
- },
- },
+```shell
+$ ember generate component map --component-class=@glimmer/component
+installing component
+ create app/components/map.gjs
+installing component-test
+ create tests/integration/components/map-test.gjs
- APP: {
- // Here you can pass flags/options to your application instance
- // when it is created
- },
- };
+Running "lint:fix" script...
+```
- if (environment === 'development') {
- // ENV.APP.LOG_RESOLVER = true;
- // ENV.APP.LOG_ACTIVE_GENERATION = true;
- // ENV.APP.LOG_TRANSITIONS = true;
- // ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
- // ENV.APP.LOG_VIEW_LOOKUPS = true;
- }
+Since not every component will necessarily have some defined behavior associated with it, the component generator does not generate the JavaScript parts of the file for us by default. As we saw earlier, we can always add the JavaScript class to a component later on.
- if (environment === 'test') {
- // Testem prefers this...
- ENV.locationType = 'none';
+However, in the case of our `` component, we are pretty sure that we are going to need a JavaScript file for some behavior that we have yet to define! To save a step later, we can pass the `--component-class=@glimmer/component` flag to the component generator so that we have everything we need from the get-go.
- // keep test console output quieter
- ENV.APP.LOG_ACTIVE_GENERATION = false;
- ENV.APP.LOG_VIEW_LOOKUPS = false;
+
+
+
+
Zoey says...
+
+
Too much typing? Use ember g component map -gc instead. The -gc flag stands for G limmer c omponent, but you may also remember it as g enerate c lass.
+
+
+
+
+
- ENV.APP.rootElement = '#ember-testing';
- ENV.APP.autoboot = false;
- }
+## Making use of arguments to create a reusable Map component
- if (environment === 'production') {
- // here you can enable a production-specific feature
- }
+Let's update our component to render an interactive map:
- ENV.TOMTOM_ACCESS_TOKEN = 'paste your TomTom API key here';
+```gjs { data-filename="app/components/map.gjs" data-diff="+2,+3,+4,+5,+6,+7,+8,+9,+10,+11,+12,+13,+14,+15,+16,+17,+18,+19,-23,+24,+25,+26,+27" }
+import Component from '@glimmer/component';
+import { modifier } from 'ember-modifier';
+import { Map as MapLibreGLMap, Marker } from 'maplibre-gl';
+import 'maplibre-gl/dist/maplibre-gl.css';
+
+const MAP_STYLE = 'https://tiles.openfreemap.org/styles/liberty';
+
+const displayMap = modifier((element, [lat, lng, zoom]) => {
+ const map = new MapLibreGLMap({
+ container: element,
+ style: MAP_STYLE,
+ center: [lng, lat],
+ zoom,
+ });
- return ENV;
-};
+ new Marker().setLngLat([lng, lat]).addTo(map);
+
+ return () => map.remove();
+});
+
+export default class Map extends Component {
+
+ {{yield}}
+
+
+}
```
-As its name implies, `config/environment.js` is used to _configure_ our app and store API keys like these. These values can be accessed from other parts of our app, and they can have different values depending on the current environment (which might be development, test, or production).
+There is a lot going on here! Let's work through it piece by piece.
-After saving the changes to our configuration file, we will need to restart our development server to pick up these file changes. Unlike the files we have edited so far, `config/environment.js` is not automatically reloaded.
+First, we have imports for `modifier` from `ember-modifier`, `Map` (aliased as `MapLibreGLMap` so as not to conflict with the component class name) and `Marker` from `maplibre-gl`, and the MapLibre CSS file. The CSS provides the map controls and visual elements that MapLibre renders — without it, the map buttons and overlays won't look right.
-
+Next, we define a `MAP_STYLE` constant pointing to [OpenFreeMap](https://openfreemap.org/), an open-source tile server that provides free map tiles with no API key required.
-You can stop the server by finding the terminal window where `npm start` is running, then type `Ctrl + C`. That is, typing the "C" key on your keyboard _while_ holding down the "Ctrl" key at the same time. Once it has stopped, you can start it back up again with the same `npm start` command.
+The heart of this component is `displayMap`, a custom _[modifier](../../../components/template-lifecycle-dom-and-modifiers/)_ created with the `modifier()` function from `ember-modifier`. A modifier is a way to run JavaScript code that directly interacts with a specific DOM element. When Ember renders ``, our modifier function is called with two arguments: the DOM element itself, and an array of any positional arguments passed in the template. Here we use destructuring — `[lat, lng, zoom]` — to unpack that array directly in the function signature.
+
+Inside the modifier, we use `maplibregl` exactly as we would in plain JavaScript: instantiate a `new MapLibreGLMap()`, pass it the container element, the OpenFreeMap style URL, and the coordinates, then add a `Marker` at the same position to visually pin the location. No Ember-specific APIs are needed — it is just regular JavaScript library usage.
+
+Finally, the modifier returns a _cleanup function_, `() => map.remove()`. Ember automatically calls this function when the element is removed from the DOM — for instance, when the user navigates to a different page. Returning a cleanup function is how modifiers signal to Ember what teardown work needs to happen.
+
+Our component's template accepts `@lat`, `@lng`, and `@zoom` as _[arguments](../../../components/component-arguments-and-html-attributes/#toc_arguments)_ to the `
` component that we pass through to the modifier. By _parameterizing_ our component using arguments, we made a reusable component that can be invoked from different parts of the app and customized to meet the needs for those specific contexts. We have already seen this in action when using the `` component [earlier](../building-pages/); we had to specify a `@route` argument so that it knew what page to navigate to.
+
+Let's write some initial tests to make sure the component renders correctly:
+
+```gjs { data-filename="tests/integration/components/map-test.gjs" data-diff="-9,-10,-11,-12,-13,-14,-15,-16,-17,-18,-19,+20,-22,-23,-24,+25,+26,+27,+28,+29,+30,+31,-34,+35" }
+import { module, test } from 'qunit';
+import { setupRenderingTest } from 'super-rentals/tests/helpers';
+import { render } from '@ember/test-helpers';
+import Map from 'super-rentals/components/map';
+
+module('Integration | Component | map', function (hooks) {
+ setupRenderingTest(hooks);
+
+ test('it renders', async function (assert) {
+ // Updating values is achieved using autotracking, just like in app code. For example:
+ // class State { @tracked myProperty = 0; }; const state = new State();
+ // and update using state.myProperty = 1; await rerender();
+ // Handle any actions with function myAction(val) { ... };
+
+ await render( );
+
+ assert.dom().hasText('');
+
+ // Template block usage:
+ test('it renders a map for the specified parameters', async function (assert) {
+ await render(
+
+ template block text
+
+
+ );
+
+ assert.dom().hasText('template block text');
+ assert.dom('.map').exists();
+ });
+});
+```
```shell
$ npm start
@@ -102,115 +168,149 @@ Build successful (13286ms)
Slowest Nodes (totalTime >= 5%) | Total (avg)
-+-
-Babel: @embroider/macros (1) | 336ms
+Babel: @embroider/macros (1) | 250ms
+5:46:52 AM [vite] (client) Re-optimizing dependencies because lockfile has changed
- VITE v8.0.11 ready in 3176 ms
+ VITE v8.3.3 ready in 2457 ms
➜ Local: http://localhost:4200/
```
-## Generating a Component with a Component Class
+
-With the TomTom API key in place, let's generate a new component for our map.
+## Sizing the Map with inline styles
-```shell
-$ ember generate component map --component-class=@glimmer/component
-installing component
- create app/components/map.gjs
-installing component-test
- create tests/integration/components/map-test.gjs
+Our map renders, but it is a fixed size. We want the caller to be able to pass `@width` and `@height` arguments to control the map's dimensions.
-Running "lint:fix" script...
+The natural way to set a size is through an inline `style` attribute. You might try:
+
+```gjs
+
```
-Since not every component will necessarily have some defined behavior associated with it, the component generator does not generate the JavaScript parts of the file for us by default. As we saw earlier, we can always add the JavaScript class to a component later on.
+However, Ember will log a console warning when you do this:
-However, in the case of our `` component, we are pretty sure that we are going to need a JavaScript file for some behavior that we have yet to define! To save a step later, we can pass the `--component-class=@glimmer/component` flag to the component generator so that we have everything we need from the get-go.
+> Binding style attributes may introduce cross-site scripting vulnerabilities...
-
-
-
-
Zoey says...
-
-
Too much typing? Use ember g component map -gc instead. The -gc flag stands for G limmer c omponent, but you may also remember it as g enerate c lass.
-
-
-
-
-
-
-## Parameterizing Components with Arguments
+Ember warns about dynamic string interpolation inside `style` attributes because of the risk of _[XSS (Cross-Site Scripting)](https://owasp.org/www-community/attacks/xss/)_ attacks. If `@width` could ever receive a value from **untrusted** user input, a malicious string could inject arbitrary styles — or worse, `