# builtIns.scope
`builtIns.scope` binds an **existing** element's scope, typically server-rendered markup, against a sub-value of the view model, optionally through an [itemscope manager](../README.md#itemscope-managers-chrome-146).
[`builtIns.tree`](./tree.md) *creates* a scope from a spec. `builtIns.scope` takes over one that's already there. The same manager class works with both.
**Demo:** [demos/ssr-manager-demo.html](../demos/ssr-manager-demo.html). Run `npm run serve`, then open `/demos/ssr-manager-demo.html`.
## Example
The server rendered this. The attributes say what's what:
```html
Rocket: Falcon 9
Height70 m
```
```JavaScript
import { assignFromAsync, ItemscopeRegistry } from 'assign-gingerly';
class RocketManager {
#extraMeters = 0;
#rebind;
constructor(element, initVals, { rebind }) {
Object.assign(this, initVals); // vm.rocket
this.#rebind = rebind;
}
get title() { return `Rocket: ${this.name}`; }
get heightLabel() { return `${this.height.meters + this.#extraMeters} m`; }
increaseHeightByOne() { this.#extraMeters++; this.#rebind(); }
}
await assignFromAsync(details, {
'?.querySelector?.section =>': {
do: 'builtIns.scope',
bind: {
infer: { byItemprop: true },
pattern: { '?.querySelector?.button +=': { on: 'click', call: 'increaseHeightByOne' } },
withMethods: ['querySelector'],
},
},
}, {
from: vm,
withMethods: ['querySelector'],
registry: { itemscopeRegistry: new ItemscopeRegistry({ 'rocket-manager': RocketManager }) },
});
```
What happens:
1. **The value** is `vm.rocket`, from `itemprop="rocket"`.
2. **The manager** is `rocket-manager`, from `itemscope="rocket-manager"`. It's attached as `section.ish`, constructed with `vm.rocket`, and given `ctx.rebind()`.
3. **The binding** runs `assignFrom(section, bind.pattern, { …bind, from: theManager })`:
- `infer` fills `title` and `heightLabel` from the manager's getters;
- the pattern makes the button call `increaseHeightByOne` on the manager.
4. **Rebinding:** `increaseHeightByOne` calls `ctx.rebind()`, which re-runs step 3.
## Config
```TypeScript
interface ScopeConfig {
do: 'builtIns.scope';
get?: { value?: string }; // what the scope binds to (default: from[itemprop])
ish?: string | ManagerClass; // the manager (default: the itemscope attribute's name)
bind?: { pattern?: Pattern } & AssignFromOptions; // minus from / fromStack
}
```
| What | Default | Override |
|---|---|---|
| **The value** | `from[itemprop]`, from the element's `itemprop` attribute | `get: { value: '?.path' }`; `get: { value: '?.' }` for the whole `from` |
| **The manager** | The name in `itemscope="…"`, if any | `ish: 'name'` or `ish: SomeClass` |
| **The scope's view model** (`from` inside `bind`) | The manager instance, if there's a manager; else the value | (none) |
- **No value, no guess:** with neither an `itemprop` nor `get.value`, it throws, and the message says to add one (`get: { value: '?.' }` to keep the whole `from`).
- **Managers are optional.** A bare `itemscope`, or none at all, means the value itself is the view model: "zoom in on a sub-object, and bind it".
- **Manager names** resolve through the [`registry` option](./code-interjection.md#the-registry-option) first. An unregistered name is **waited for**, as for `ish`, which is why this is a ` =>` handler. A class is used directly and registered nowhere.
## The binding
`bind` is one `assignFrom` call on the element:
- **`bind.pattern`** is its pattern.
- **The other `bind` keys are its options:** `infer`, `withMethods`, `pin`, `aka`, ….
- **`from`** is the scope's view model.
- **Inherited from the outer call:** `pipes`, `protocols`, `registry`, `handlers`, `signal` and `aka`.
**Inside the scope:**
- **`$parent`** is the outer `from`, and **`$root`** the top-level one, with or without `itemscope`.
- **Event bindings** (` +=`) have the view model as their host, so `call` reaches manager methods. They get default dedup keys, so running again replaces the listeners rather than stacking them. As everywhere, a listener is attached once the event-binding module has loaded, just after the call.
- **`infer: { byItemprop: true }`** binds every itemprop in the element's own scope that the view model has, **getters included**. See [inferred-assignments.md](./inferred-assignments.md#infer-all-keys).
## `infer` keeps its covenant
`infer` never enters a nested `itemscope` by itself, and `builtIns.scope` doesn't change that. Stepping into a scope is explicit: you target the scope's element with `builtIns.scope`.
An outer `infer` and `builtIns.scope` combine naturally:
- **The outer `infer`** fills the outer itemprops. For an element like ``, it also routes `vm.rocket` into `section.ish`.
- **`builtIns.scope`** then binds the inside. It reuses a manager that's already attached.
## Pending marks (`data-pending`)
Server-rendered markup can mark elements it couldn't complete with [`data-pending`](./tree.md#pending-marks-data-pending), which CSS can hide until the client restores them. `builtIns.scope` removes the element's mark after its first bind. So a manager's scope stays hidden until it's really bound against the manager.
## Rebinding and reruns
| Event | What happens |
|---|---|
| The manager calls `ctx.rebind()` | `bind` re-runs against the manager |
| An async `onAssigned` settles | `bind` re-runs |
| The same `assignFrom` runs again (new data) | The new value is assigned to `el.ish` (merged into the same manager) or becomes the new view model; `bind` re-runs. The manager is attached only once. |
**Outside `builtIns.tree` and `builtIns.scope`**, a manager attached by a bare `el.ish = …` still gets `ctx.rebind()`, as a no-op. So a manager written for Tree, with `constructor(el, initVals, { rebind })`, works anywhere.