|
| 1 | +_Arguments_ and _attributes_ are the two kinds of things that you can pass to a |
| 2 | +component when you use it in a template. Arguments are values that are prefixed |
| 3 | +with the `@` symbol, and attributes are values that are not: |
| 4 | + |
| 5 | +```handlebars |
| 6 | +<Tooltip @content="Required Value" class="error"/> |
| 7 | +``` |
| 8 | + |
| 9 | +In this example, we're passing the `@content` argument and the `class` attribute |
| 10 | +to an instance of the `Tooltip` component. |
| 11 | + |
| 12 | +Arguments are _JavaScript values_, which are accessible in both the component |
| 13 | +template and its class instance, if a class exists. All kinds of values can be |
| 14 | +passed as arguments to a component, and can be consumed and used with JavaScript |
| 15 | +code. |
| 16 | + |
| 17 | +Attributes, by contrast, are specifically _HTML attributes_, such as `class`, |
| 18 | +`id`, `data-test`, and `role`. These get reflected directly onto HTML elements, |
| 19 | +and are not accessible directly in either the template of a component, or its |
| 20 | +class. Instead, components can direct where they are applied with the |
| 21 | +`...attributes` syntax. |
| 22 | + |
| 23 | +## Arguments |
| 24 | + |
| 25 | +Arguments are values that you pass down to your component, and that can be |
| 26 | +accessed in the template with the `@` symbol. If we define a `Tooltip` |
| 27 | +component, we can pass a `@content` argument to it like so: |
| 28 | + |
| 29 | +```handlebars |
| 30 | +<Tooltip @content="Required Value" /> |
| 31 | +``` |
| 32 | + |
| 33 | +And then we can access it in the template for the `Tooltip` component like this: |
| 34 | + |
| 35 | +```handlebars {data-filename=src/ui/components/tooltip/template.hbs} |
| 36 | +{{@content}} |
| 37 | +``` |
| 38 | + |
| 39 | +The same symbol `@` is used on both sides, so its easy to remember that this is |
| 40 | +an argument coming from the caller whenever you're looking at the template. In |
| 41 | +the component class you can access the arguments on the `args` property of the |
| 42 | +class, since `@` is special character in JavaScript reserved for decorators: |
| 43 | + |
| 44 | +```js {data-filename=src/ui/components/tooltip/component.js} |
| 45 | +export default class Tooltip extends Component { |
| 46 | + get upperCased() { |
| 47 | + return this.args.content.toUpperCase(); |
| 48 | + } |
| 49 | +} |
| 50 | +``` |
| 51 | + |
| 52 | +In templates arguments are valid identifiers that can be used anywhere a |
| 53 | +standard identifier can: |
| 54 | + |
| 55 | +```handlebars |
| 56 | +{{!-- in a helper --}} |
| 57 | +{{capitalize @content}} |
| 58 | +
|
| 59 | +{{!-- in an attribute --}} |
| 60 | +<div class="{{@class}}"></div> |
| 61 | +
|
| 62 | +{{!-- as an argument --}} |
| 63 | +<Icon @type={{@iconType}} /> |
| 64 | +``` |
| 65 | + |
| 66 | +You can pass strings as arguments to components, or you can pass literal values |
| 67 | +using double curlies: |
| 68 | + |
| 69 | +```handlebars |
| 70 | +<Tooltip |
| 71 | + @icon="warning" |
| 72 | + @count={{4}} |
| 73 | + @isError={{false}} |
| 74 | +/> |
| 75 | +``` |
| 76 | + |
| 77 | +Note that if you do _not_ wrap literal values in double curlies, they are |
| 78 | +treated as strings, like standard HTML attributes: |
| 79 | + |
| 80 | +```handlebars |
| 81 | +<Tooltip @isError=false /> |
| 82 | +
|
| 83 | +{{!-- is equivalent to... --}} |
| 84 | +<Tooltip @isError="false" /> |
| 85 | +``` |
| 86 | + |
| 87 | +You can also pass a class property, or the result of a helper, through |
| 88 | +arguments: |
| 89 | + |
| 90 | +```handlebars |
| 91 | +<Tooltip |
| 92 | + @icon={{this.icon}} |
| 93 | + @isError={{not this.isWarning}} |
| 94 | +/> |
| 95 | +``` |
| 96 | + |
| 97 | +In short, arguments are how you pass _JavaScript values_ into a component. This |
| 98 | +is part of why they are named _arguments_ - if you think of using a component |
| 99 | +like calling a _function_, whose result is a new component that produces some |
| 100 | +HTML, then arguments to a component are the same as arguments to a function. |
| 101 | +They're values that you pass to the component that the component then uses to |
| 102 | +produce a result (the final HTML). |
| 103 | + |
| 104 | +### Argument Defaults |
| 105 | + |
| 106 | +At some point, you may want to add default values to your arguments if one |
| 107 | +wasn't passed to your component. Arguments are not mutable, so if you attempt to |
| 108 | +reassign a value on `this.args`, it'll fail. Instead, you should define a getter |
| 109 | +on your component that provides the default value if the argument was not |
| 110 | +provided. |
| 111 | + |
| 112 | +For instance, if you wanted to create a tooltip icon that had a standard icon |
| 113 | +and class, you could do it like so: |
| 114 | + |
| 115 | +```javascript {data-filename=src/ui/components/tooltip/component.js} |
| 116 | +import Component from '@glimmer/component'; |
| 117 | + |
| 118 | +export default class Tooltip extends Component { |
| 119 | + get icon() { |
| 120 | + return this.args.icon || 'icon-info'; |
| 121 | + } |
| 122 | + |
| 123 | + get tooltipClass() { |
| 124 | + return this.args.tooltipClass + ' tooltip'; |
| 125 | + } |
| 126 | +} |
| 127 | +``` |
| 128 | + |
| 129 | +```handlebars {data-filename=src/ui/components/tooltip/template.hbs} |
| 130 | +<div class="{{this.tooltipClass}}"> |
| 131 | + <i class="{{this.icon}}"></i> |
| 132 | + {{@content}} |
| 133 | +</div> |
| 134 | +``` |
| 135 | + |
| 136 | +Now when called like so: |
| 137 | + |
| 138 | +```handlebars |
| 139 | +<Tooltip @content="I'm a tooltip!"/> |
| 140 | +``` |
| 141 | + |
| 142 | +The result will be: |
| 143 | + |
| 144 | +```html |
| 145 | +<div class="tooltip"> |
| 146 | + <i class="icon-info"></i> |
| 147 | + I'm a tooltip! |
| 148 | +</div> |
| 149 | +``` |
| 150 | + |
| 151 | +Note that because arguments are prefixed with `@` in templates, and placed on |
| 152 | +`args` in the component definition, we can use the same name for our `icon` and |
| 153 | +`tooltipClass` getters, which is pretty convenient. We can also tell clearly |
| 154 | +when we look at the template for the tooltip that `this.tooltipClass` and |
| 155 | +`this.icon` are values that come from the class definition, and that means they |
| 156 | +probably have been used in some kind of dynamic code (in this case, our |
| 157 | +defaulting logic). |
| 158 | + |
| 159 | +### Attributes |
| 160 | + |
| 161 | +While arguments are a way for you to pass JavaScript values into your |
| 162 | +components, _attributes_ are about passing HTML attributes like `class`, `id`, |
| 163 | +and `role` through your component, down to the final elements that get rendered. |
| 164 | +This allows you to easily customize a component without having to add a bunch of |
| 165 | +customization logic to every single component. |
| 166 | + |
| 167 | +For instance, in our `<Tooltip>` component from above, instead of adding the |
| 168 | +`@tooltipClass` argument, we could use attributes: |
| 169 | + |
| 170 | +```handlebars {data-filename=src/ui/components/tooltip/template.hbs} |
| 171 | +<div class="tooltip" ...attributes> |
| 172 | + <i class="{{this.icon}}"></i> |
| 173 | + {{@content}} |
| 174 | +</div> |
| 175 | +``` |
| 176 | + |
| 177 | +We would then invoke the component like this: |
| 178 | + |
| 179 | +```handlebars |
| 180 | +<Tooltip |
| 181 | + @content="I'm a tooltip!" |
| 182 | + class="warning-tooltip" |
| 183 | +/> |
| 184 | +``` |
| 185 | + |
| 186 | +Then our result is: |
| 187 | + |
| 188 | +```html |
| 189 | +<div class="tooltip warning-tooltip"> |
| 190 | + <i class="icon-info"></i> |
| 191 | + I'm a tooltip! |
| 192 | +</div> |
| 193 | +``` |
| 194 | + |
| 195 | +The `...attributes` syntax is how you specify where the attributes should be |
| 196 | +applied. It can be applied to elements or components, and you can use it on as |
| 197 | +many elements or components within your component as you want: |
| 198 | + |
| 199 | +```handlebars |
| 200 | +<BlogPostTitle @title={{title}} ...attributes/> |
| 201 | +<BlogPostContent> |
| 202 | + <section ...attributes> |
| 203 | + <p> |
| 204 | + Here's some content! |
| 205 | + </p> |
| 206 | + </section> |
| 207 | +</BlogPostContent> |
| 208 | +``` |
| 209 | + |
| 210 | +Although typically you'll just want to put it on one of the top level components |
| 211 | +or elements. If you don't use `...attributes` in your component and someone |
| 212 | +tries to pass an attribute to it, Ember will do nothing, and the attributes will |
| 213 | +not be applied. |
| 214 | + |
| 215 | +#### Attribute Order |
| 216 | + |
| 217 | +The positioning of `...attributes` matters, with respect to the other attributes |
| 218 | +in the element it is applied to. Attributes that come _before_ `...attributes` |
| 219 | +can be overriden, but attributes that come _after_ cannot: |
| 220 | + |
| 221 | +```handlebars |
| 222 | +<p |
| 223 | + data-overridable="you can override me" |
| 224 | + ...attributes |
| 225 | + data-non-overridable="but you can't override me!" |
| 226 | +> |
| 227 | +</p> |
| 228 | +``` |
| 229 | + |
| 230 | +There is one exception to this, which is the `class` attribute. `class` will get |
| 231 | +merged, since its more often the case that users of the component want to _add_ |
| 232 | +a class than completely override the existing ones. For `class`, the order of |
| 233 | +`...attributes` will determine the order of merging. Putting it before: |
| 234 | + |
| 235 | +```handlebars |
| 236 | +<p ...attributes class="friend-greeting">Hello {{@friend}}, I'm {{this.name}}!</p> |
| 237 | +``` |
| 238 | + |
| 239 | +Results in: |
| 240 | + |
| 241 | +```html |
| 242 | +<p class="red-alert friend-greeting">Hello {{@friend}}, I'm {{this.name}}!</p> |
| 243 | +``` |
| 244 | + |
| 245 | +And putting it after: |
| 246 | + |
| 247 | +```handlebars |
| 248 | +<p class="friend-greeting" ...attributes>Hello {{@friend}}, I'm {{this.name}}!</p> |
| 249 | +``` |
| 250 | + |
| 251 | +Results in: |
| 252 | + |
| 253 | +```html |
| 254 | +<p class="friend-greeting red-alert">Hello {{@friend}}, I'm {{this.name}}!</p> |
| 255 | +``` |
| 256 | + |
| 257 | +#### Attributes and Modifiers |
| 258 | + |
| 259 | +Modifiers are a concept that we haven't covered too deeply just yet, but they're |
| 260 | +similar to helpers, except that they are applied directly to elements: |
| 261 | + |
| 262 | +```handlebars |
| 263 | +<div {{did-insert this.setupElement}}> |
| 264 | + ... |
| 265 | +</div> |
| 266 | +``` |
| 267 | + |
| 268 | +Modifiers can also be applied to components, and when they are, they are also |
| 269 | +passed forward and applied to an element with `...attributes`: |
| 270 | + |
| 271 | +```handlebars |
| 272 | +<Tooltip {{did-insert this.setupTooltip}}/> |
| 273 | +``` |
0 commit comments