Learn Hungry elements

An element with an [up-hungry] attribute is updated whenever a server response contains a matching element, even when the render pass targeted something else. This keeps elements in your application layout fresh, like an unread counter or a notification bar, without naming them in every link's target.

Keep hungry elements to a few elements in your layout. For everything else, name the fragments in the link's or form's target.

Marking an element as hungry

Some elements live in the application layout, outside of the fragment that links and forms usually target. Examples include:

  • Unread message counters
  • Page-specific subnavigation
  • Account-wide notifications, e.g. about an expired credit card

Instead of including such elements in every target selector, like .content, .unread-count:maybe, mark the element as [up-hungry]:

<div class="unread-count" up-hungry> <!-- mark: up-hungry -->
  2 new messages
</div>

Now any link or form that updates a fragment on the page will also update the counter:

<a href="/messages/5" up-target=".content">Read message</a>

When the response for /messages/5 contains an .unread-count element, it replaces the hungry element on the page, in the same render pass that updates .content. When the response contains no .unread-count element, the counter stays as it is. No error is thrown.

The server is not told about hungry elements. It renders its page as usual, and Unpoly looks for matches in whatever the response contains. If your server renders only the targeted fragments, make sure to still include hungry elements in the response.

Hungry elements are also updated from responses with an error code, like a form re-rendered with validation errors.

A derivable target is required

To find a hungry element's counterpart in the response, Unpoly derives a target selector from the element. For this the element needs an identifying attribute, such as an [id] or a unique [class].

When no good target can be derived, the element is excluded from the update and a message like this is logged:

[up-hungry] Ignoring untargetable fragment <div>

Animating the update

A hungry element can define its own transition to morph between its old and new content, independent of the primary target:

<div class="unread-count" up-hungry up-transition="cross-fade"> <!-- mark: up-transition="cross-fade" -->
  2 new messages
</div>

You can also set [up-duration] and [up-easing] attributes, as on a link.

Updating only sometimes

Before a hungry element is added to a render pass, an up:fragment:hungry event is emitted on the element. Prevent the event to skip the hungry element for that render pass.

For example, a subnavigation should update when the user navigates to another page, but not when a form validation updates a fieldset. The following only updates #side-nav for render passes that update history:

up.on('up:fragment:hungry', '#side-nav', function(event) {
  if (!event.renderOptions.history) event.preventDefault() // mark: event.preventDefault()
})

The same condition fits into an [up-on-hungry] attribute, with event and renderOptions available as variables:

<nav id="side-nav" up-hungry up-on-hungry="if (!renderOptions.history) event.preventDefault()"> <!-- mark: up-on-hungry -->
  ...
</nav>

You may also decide based on the new element that would replace the hungry element, which the event exposes as event.newFragment. This skips an update when the new element is marked as empty:

up.on('up:fragment:hungry', '.unread-count', function(event) {
  if (event.newFragment.classList.contains('is-empty')) event.preventDefault()
})

Hungry elements on other layers

By default a hungry element is only updated by render passes on its own layer. When an overlay renders, a hungry element on the root layer is left alone.

To also update a hungry element when other layers render, set an [up-if-layer] attribute. A notification bar in the root layout can pick up notifications from any overlay with [up-if-layer="subtree"]:

<div id="notifications" up-hungry up-if-layer="subtree"> <!-- mark: up-if-layer="subtree" -->
  Your credit card has expired.
</div>

The attribute takes a layer reference relative to the hungry element's layer. Use any to piggy-back on render passes for all layers, or combine references like current, child.

This also works when a response closes an overlay: the content of that response is discarded for the overlay, but hungry elements on other layers still pick up their matches. The [up-flashes] element uses this to show confirmation messages from a closing overlay in the parent layer.

Disabling hungry updates

Hungry elements are processed for all updates on their layer. To exclude them from a render pass:

To exclude a single element, set [up-hungry="false"] on the element.

Making other elements hungry

Instead of annotating each element, you can configure selectors for elements that should always be hungry in up.radio.config.hungrySelectors:

up.radio.config.hungrySelectors.push('.unread-count')

Such elements still need a derivable target. Exceptions go into up.radio.config.noHungrySelectors.

Resolving conflicts

Each element in a response can only be inserted once. When hungry elements conflict with each other or with the primary target, Unpoly decides as follows:

  1. When both a target selector and a hungry element match the same element in the response, only the direct render target is updated.
  2. When hungry elements are nested within each other, the outermost element is updated.
  3. When hungry elements on different layers match the same element in the response, the layer closest to the rendering layer wins.