ViewChannel Concepts

1. Overview

A ViewChannel is a class which represents an area on the screen which contains visual elements (views) which are visible for a certain amount of time, or until when the user performs a certain action. Here are some examples:

A. Flash message

A flash message is a message to the user informing the user if an action succeeded or failed.

View code
import { ViewChannel } from "@uiloos/core";

const flashMessageChannel = new ViewChannel(
  {},
  subscriber
);

const ANIMATION_DURATION = 200;

document.documentElement.style.setProperty(
  '--flash-message-animation-duration',
  `${ANIMATION_DURATION}ms`
);

function subscriber(viewChannel, event) {
  const flashMessagesContainerEl = document.getElementById(
    'flash-messages-container'
  );

  if (event.type === 'PRESENTED') {
    const view = event.view;
    const flashMessage = view.data;

    const zIndex = viewChannel.views.length - view.index;

    const flashMessageEl = document.createElement('div');
    flashMessageEl.id = flashMessage.id;

    flashMessageEl.className = `
      flash-message flash-message-${flashMessage.type}
    `;
    flashMessageEl.style.zIndex = zIndex;

    flashMessageEl.onclick = () => view.dismiss();
    flashMessageEl.onmouseover = () => view.pause();
    flashMessageEl.onmouseleave = () => view.play();

    flashMessageEl.innerHTML = `
      <div class="flash-message-row">
        <div class="flash-message-content">
          <span class="flash-message-icon">
            ${typeToSymbol(flashMessage.type)}
          </span>
          <p>${flashMessage.text}</p>
        </div>
        <span class="flash-message-close">𐄂</span>
      </div>

      <div 
        id="${flashMessage.id}-progress" 
        class="
          flash-message-progress 
          flash-message-progress-${flashMessage.type}
        "
      ></div>
    `;

    // Insert before the current item holding the 
    // index, if that index does not exist provide
    // `null` so it is appended to the list.
    flashMessagesContainerEl.insertBefore(
      flashMessageEl,
      flashMessagesContainerEl.children[view.index] ?? null
    );

    const progressEl = document
      .getElementById(`${flashMessage.id}-progress`);

    progressEl.style.animation = `
      progress ${view.autoDismiss.duration}ms ease-out
    `;

    return;
  }

  if (event.type === 'DISMISSED') {
    const view = event.view;
    const flash = view.data;

    const flashMessageEl = document.getElementById(flash.id);

    flashMessageEl.classList.add('flash-message-exit');

    flashMessageEl.onanimationend = (event) => {
      if (event.animationName === 'slide-out') {
        flashMessageEl.remove();
      }
    };

    return;
  }

  if (event.type === 'AUTO_DISMISS_PLAYING') {
    const progressEl = document.getElementById(
      `${event.view.data.id}-progress`
    );
    progressEl.style.animationPlayState = 'running';

    return;
  }

  if (event.type === 'AUTO_DISMISS_PAUSED') {
    const progressEl = document.getElementById(
      `${event.view.data.id}-progress`
    );
    progressEl.style.animationPlayState = 'paused';

    return;
  }
}

export function infoFlashMessage(text) {
  flashMessageChannel.present({
    data: {
      id: Math.random(),
      text,
      type: 'info'
    },
    priority: 4,
    autoDismiss: {
      duration: 2000,
      result: undefined
    }
  });
}

export function warningFlashMessage(text) {
  flashMessageChannel.present({
    data: {
      id: Math.random(),
      text,
      type: 'warning'
    },
    priority: 1,
    autoDismiss: {
      duration: 3000,
      result: undefined
    }
  });
}

export function errorFlashMessage(text) {
  flashMessageChannel.present({
    data: {
      id: Math.random(),
      text,
      type: 'error'
    },
    priority: 0,
    autoDismiss: {
      duration: 5000,
      result: undefined
    }
  });
}

export function successFlashMessage(text) {
  flashMessageChannel.present({
    data: {
      id: Math.random(),
      text,
      type: 'success'
    },
    priority: 2,
    autoDismiss: {
      duration: 2000,
      result: undefined
    }
  });
}

// UTILS

function typeToSymbol(type) {
  switch (type) {
    case 'info':
      return 'ⓘ';

    case 'warning':
      return '⚠';

    case 'error':
      return '☠';

    case 'success':
      return '✓';

    default:
      return 'unknown';
  }
}

// Button events

document.getElementById('flashInfo').onclick = () => {
  infoFlashMessage("An info message");
};

document.getElementById('flashSuccess').onclick = () => {
  successFlashMessage("A success message");
};

document.getElementById('flashWarning').onclick = () => {
  warningFlashMessage("A warning message");
};

document.getElementById('flashError').onclick = () => {
  errorFlashMessage("An error message");
};
<div class="flash-messages">
  <div 
    id="flash-messages-container" 
    class="flash-messages-container"
    role="status"
    aria-live="polite"
  ></div>
</div>

<div class="flash-trigger-buttons">
  <button 
    id="flashInfo"
    class="flash-trigger-button flash-message-info"
  >
    Info
  </button>
  
  <button 
    id="flashSuccess"
    class="flash-trigger-button flash-message-success"
  >
    Success
  </button>
  
  <button 
    id="flashWarning"
    class="flash-trigger-button flash-message-warning"
  >
    Warning
  </button>
  
  <button 
    id="flashError"
    class="flash-trigger-button flash-message-error"
  >
    Error
  </button>
</div>
:root {
  /* Set by the javascript */
  --flash-message-animation-duration: 0ms;
}

.flash-messages {
  position: fixed;
  top: 80px;
  left: 0;
  width: 100%;
  text-align: center;
}

.flash-messages-container {
  display: grid;
  gap: 16px;
  padding: 0 10px;
  margin: 0 auto;
  width: 100%;
  max-width: 800px;
}

.flash-message {
  will-change: transform, opacity;
  animation: slide-in var(--flash-message-animation-duration) ease-out;

  padding: 8px;

  font-size: 22px;

  cursor: pointer;

  display: flex;
  flex-direction: column;
  justify-content: space-between;

  font-family: sans-serif;
}

.flash-message-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.flash-message-info {
  color: white;
  background-color: #3b82f6;
}

.flash-message-warning {
  color: white;
  background-color: #eab308;
}

.flash-message-error {
  color: white;
  background-color: #ef4444;
}

.flash-message-success {
  color: white;
  background-color: #22c55e;
}

.flash-message-content {
  display: flex;
  align-items: center;
  gap: 8px;
}

.flash-message-content p {
  margin: 0;
}

.flash-message-icon {
  font-size: 38px;
}

.flash-message-close {
  font-size: 32px;
  margin-right: 32px;
  margin-top: -5px;
}

@keyframes slide-in {
  from {
    transform: translate3d(0, -100%, 0);
    visibility: visible;
  }

  to {
    transform: translate3d(0, 0, 0);
  }
}

@keyframes slide-out {
  from {
    transform: translate3d(0, 0, 0);
    opacity: 1;
    visibility: visible;
  }

  to {
    transform: translate3d(0, -100%, 0);
    opacity: 0;
  }
}

.flash-message-exit {
  animation: slide-out var(--flash-message-animation-duration) ease-out;
}

.flash-message-progress {
  will-change: animation, background-position;
  position: relative;
  top: 8px;
  left: -8px;
  height: 4px;
  width: calc(100% + 16px);

  background-position: right bottom;
}

.flash-message-progress-info {
  background: linear-gradient(to right, #3b82f6 50%, #063b91 50%);
  background-size: 200% 100%;
}

.flash-message-progress-warning {
  background: linear-gradient(to right, #eab308 50%, #755904 50%);
  background-size: 200% 100%;
}

.flash-message-progress-error {
  background: linear-gradient(to right, #ef4444 50%, #8d0c0c 50%);
  background-size: 200% 100%;
}

.flash-message-progress-success {
  background: linear-gradient(to right, #22c55e 50%, #10622f 50%);
  background-size: 200% 100%;
}

@keyframes progress {
  from {
    background-position: right bottom;
  }
  to {
    background-position: left bottom;
  }
}

.flash-trigger-buttons {
  display: flex;
  gap: 8px;
  margin-bottom: 8px;
}

.flash-trigger-button {
  padding: 8px;
  border: 1px lightgray solid;
}

.flash-trigger-button:hover {
  box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
}
B. Confirm dialog

A confirm dialog is a component which asks the user if the user is sure he wants to perform an action.

View code
import { ViewChannel } from "@uiloos/core";

export const confirmationDialogChannel = new ViewChannel(
  {},
  subscriber
);

function subscriber(viewChannel, event) {
  const dialogsContainerEl = document
    .getElementById('confirmation-dialogs');

  if (event.type === 'PRESENTED') {
    const view = event.view;
    const dialog = view.data;

    const backdropEl = document.createElement('div');
    backdropEl.id = `${dialog.id}-backdrop`;
    backdropEl.className = 'confirmation-dialog-backdrop';
    backdropEl.onclick = () => view.dismiss(false);

    const dialogEl = document.createElement('div');
    dialogEl.id = `${dialog.id}-dialog`;
    dialogEl.className = 'confirmation-dialog';
    dialogEl.innerHTML = `
      <strong>Please confirm</strong>
      <p>${dialog.text}</p>
    `;

    const dialogButtonBar = document.createElement('div');
    dialogButtonBar.className = 'confirmation-dialog-button-bar';

    const okButtonEl = document.createElement('button');
    okButtonEl.className = "confirm-choice-button";
    okButtonEl.textContent = 'Ok';
    okButtonEl.onclick = () => view.dismiss(true);

    const cancelButtonEl = document.createElement('button');
    cancelButtonEl.className = "confirm-choice-button";
    cancelButtonEl.textContent = 'Cancel';
    cancelButtonEl.onclick = () => view.dismiss(false);

    dialogButtonBar.append(okButtonEl, cancelButtonEl);

    dialogEl.append(dialogButtonBar);

    dialogsContainerEl.append(backdropEl, dialogEl);

    return;
  }

  if (event.type === 'DISMISSED') {
    const view = event.view;
    const dialog = view.data;

    const dialogEl = document
      .getElementById(`${dialog.id}-dialog`);

    dialogEl.remove();

    const backdropEl = document
      .getElementById(`${dialog.id}-backdrop`);

    backdropEl.remove();

    return;
  }
}

export function confirmDialog(text) {
  return confirmationDialogChannel.present({
    data: {
      id: Math.random(),
      text
    }
  }).result;
}

const confirmMessageEl = document.getElementById("confirm-message");

document.getElementById('confirmTrigger').onclick = async () => {
  const result = await confirmDialog("Are you sure you want to delete the database?");

  if (result) {
    confirmMessageEl.innerHTML = 
      "<strong>Database and backups</strong> were destroyed";
  } else {
    confirmMessageEl.innerHTML = 
      "<strong>The database lives</strong> to query another day";
  }
};
<div 
  id="confirmation-dialogs" 
  role="status"
  aria-live="polite">
</div>

<p id="confirm-message"></p>

<button 
  id="confirmTrigger"
  class="confirm-trigger"
>
  Delete database
</button>
.confirmation-dialog-backdrop {
  position: fixed;
  top: 0;
  right: 0;
  background-color: gray;
  opacity: 0.6;
  width: 100%;
  height: 100%;
  z-index: 2;
}

.confirmation-dialog {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background-color: white;
  padding: 16px;
  opacity: 1;
  z-index: 3;
  color: black;
}

.confirmation-dialog-button-bar {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

.confirm-choice-button {
  width: 100%;
  color: white;
  padding: 8px;
  background-color: rgb(192 132 252);
  display: block;
  margin-bottom: 8px;
}

.confirm-trigger:hover, .confirm-choice-button:hover {
  color: white;
  background-color: #6b21a8;
}

.confirm-trigger {
  color: white;
  padding: 8px;
  background-color: #ef4444;
  margin-bottom: 8px;
}

.confirm-trigger:hover {
  color: white;
  background-color: #8d0c0c;
}
C. Modals

A modal or a dialog is a component which takes center stage, so the user can perform an action, or read an important notice.

View code
import { ViewChannel } from "@uiloos/core";

export const modalChannel = new ViewChannel(
  {},
  subscriber
);

function subscriber(viewChannel, event) {
  const modalsContainerEl = document.getElementById('modals');

  if (event.type === 'PRESENTED') {
    const view = event.view;
    const modal = view.data;

    const backdropEl = document.createElement('div');
    backdropEl.id = `${modal.id}-backdrop`;
    backdropEl.className = 'modal-backdrop';
    backdropEl.onclick = () => view.dismiss(modalCancelled);

    const modalEl = document.createElement('div');
    modalEl.id = `${modal.id}-modal`;
    modalEl.className = 'modal';
    modalEl.setAttribute('role', 'dialog');
    modalEl.ariaLabel = modal.info.title;
    modalEl.setAttribute('aria-describedby', 'modal-description');

    const headerEl = document.createElement('div');
    headerEl.className = 'modal-close';

    const closeButtonEl = document.createElement('button');
    closeButtonEl.textContent = '✖';
    closeButtonEl.className = 'modal-close';
    closeButtonEl.onclick = () => view.dismiss(modalCancelled);

    headerEl.append(closeButtonEl);
    modalEl.append(headerEl);

    const h1El = document.createElement('h1');
    h1El.textContent = modal.info.title;

    modalEl.append(h1El);

    const pEl = document.createElement('p');
    pEl.id = 'modal-description';
    pEl.textContent = modal.info.description;

    modalEl.append(pEl);

    const innerModalEl = document.createElement('div');

    modal.render(view, innerModalEl);

    modalEl.append(innerModalEl);

    modalsContainerEl.append(backdropEl, modalEl);

    return;
  }

  if (event.type === 'DISMISSED') {
    const view = event.view;
    const modal = view.data;

    const dialogEl = document.getElementById(`${modal.id}-modal`);
    dialogEl.remove();

    const backdropEl = document.getElementById(`${modal.id}-backdrop`);
    backdropEl.remove();

    return;
  }
}

// A symbol which represents a modal's result being
// cancelled. By using a Symbol here we make sure we
// never accidentally collide with any success result
// coming from the actual modals.
export const modalCancelled = Symbol('modal cancelled');

export function showModal(render, info) {
  const view = modalChannel.present({
    data: {
      id: Math.random(),
      // A function which accepts a ViewChannelView,
      // a HTML div element to render the modal into.
      render,
      info
    }
  });

  return view.result;
}

export function doctorWhoModal(view, modalEl) {
  function onSelected(doctor) {
    view.dismiss(doctor);
  }

  modalEl.innerHTML = `
    <button 
      id="Matt Smit"
      class="modal-choice-button"
    >
      Matt Smit
    </button>
  
    <button 
      id="David Tennant"
      class="modal-choice-button"
    >
      David Tennant
    </button>
  
    <button 
      id="Robert Picardo"
      class="modal-choice-button"
    >
      Robert Picardo
    </button>
  
    <button 
      id="Jodie Whittaker"
      class="modal-choice-button"
    >
      Jodie Whittaker
    </button>
  `;

  modalEl.querySelectorAll('button').forEach((btn) => {
    btn.onclick = () => onSelected(btn.id);
  });
}

const modalSelectedEl = document.getElementById('modal-selected');

document.getElementById('modalTrigger').onclick = async () => {
  const result = await showModal(doctorWhoModal, {
    title: "Who is your favorite doctor?",
    description: `Please select your favorite actor to have portrayed the eponymous "doctor" from the show "Doctor Who".`
  });

  if (result === modalCancelled) {
    modalSelectedEl.textContent = "What you don't like Doctor Who?";
  } else if (result === 'Robert Picardo') {
    modalSelectedEl.innerHTML = `<strong>${result}</strong> having a Tardis would have saved him some time!`;
  } else {
    modalSelectedEl.innerHTML = `<strong>${result}</strong> is a very good choice.`;
  }
};
<div id="modals"></div>

<p id="modal-selected">
  Please select your favorite doctor.
</p>

<button 
  id="modalTrigger"
  class="modal-trigger"
>
  Trigger modal
</button>
.modal-backdrop {
  position: fixed;
  top: 0;
  right: 0;
  background-color: gray;
  opacity: 0.6;
  width: 100%;
  height: 100%;
  z-index: 1;
}

.modal {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background-color: white;
  padding: 16px;
  opacity: 1;
  z-index: 2;
  width: 100%;
  max-width: 600px;
  color: black;
}

.modal-close {
  display: flex;
  justify-content: flex-end;
  cursor: pointer;
}

.modal-choice-button {
  width: 100%;
  color: white;
  padding: 8px;
  background-color: rgb(192 132 252);
  display: block;
  margin-bottom: 8px;
}

.modal-trigger {
  color: white;
  padding: 8px;
  background-color: rgb(192 132 252);
  margin-bottom: 8px;
}

.modal-trigger:hover, .modal-choice-button:hover {
  color: white;
  background-color: #6b21a8;
}
D. Notification center

A notification center is usually a space on the left or the right of the screen, which contains messages which the users must clear themselves.

No notifications yet

View code
import { ViewChannel } from "@uiloos/core";

export const notificationChannel = new ViewChannel(
  {},
  subscriber
);

function subscriber(viewChannel, event) {
  const isEmpty = viewChannel.views.length === 0;

  const clearAllButtonEl = document
  .getElementById('clear-notification');
  
  clearAllButtonEl.onclick = () => {
    viewChannel.dismissAll(undefined);
  };

  clearAllButtonEl.style.display = !isEmpty 
    ? 'block' 
    : 'none';

  const emptyMessageEl = document
    .getElementById('notification-empty');

  emptyMessageEl.style.display = isEmpty ? 'block' : 'none';

  const notificationContainerEl = document.getElementById(
    'notifications-container'
  );

  if (event.type === 'PRESENTED') {
    const view = event.view;
    const notification = view.data;

    const notificationEl = document.createElement('div');
    notificationEl.id = notification.id;
    notificationEl.className = 'notification';
    notificationEl.textContent = notification.text;

    const buttonsEl = document.createElement('div');
    buttonsEl.className = 'notification-buttons';

    const clearButtonEl = document.createElement('button');
    clearButtonEl.textContent = 'Clear';
    clearButtonEl.onclick = () => view.dismiss(undefined);
    clearButtonEl.className = 'notification-button';

    buttonsEl.append(clearButtonEl);

    notificationEl.append(buttonsEl);

    // Insert before the current item holding the 
    // index, if that index does not exist provide
    // `null` so it is appended to the list.
    notificationContainerEl.insertBefore(
      notificationEl,
      notificationContainerEl.children[view.index] ?? null
    );
    
    return;
  }

  if (event.type === 'DISMISSED') {
    const view = event.view;
    const notification = view.data;

    const notificationEl = document
      .getElementById(notification.id);

    notificationEl.remove();

    return;
  }

  if (event.type === 'DISMISSED_ALL') {
    notificationContainerEl.innerHTML = '';
    return;
  }
}

export function addNotification({ priority = 0, text }) {
  notificationChannel.present({
    priority,
    data: {
      id: Math.random(),
      text
    }
  });
}

addNotification({ text: "Top priority" });

// Create a notification every 5 seconds with a 
// different priority.
setInterval(() => {
  if (notificationChannel.views.length < 5) {
    const priority = Math.ceil(Math.random() * 100);
    addNotification({ 
      priority, 
      text: `Message with priority ${priority}`
    });
  }
}, 5000);
<div class="notifications">
  <button 
    id="clear-notification" 
    class="notification-button"
  >
    Clear all
  </button>

  <p id="notification-empty"><strong>No notifications yet</strong></p>

  <div 
    id="notifications-container" 
    class="notifications-container"
  ></div>
</div>
.notifications-container {
  margin: 8px 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.notification {
  padding: 8px;
  border: solid 1px lightgray;
  cursor: grab;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.notification-buttons {
  align-self: flex-end;
}

.notification-button {
  color: white;
  padding: 8px;
  background-color: #ef4444;
}

.notification-button:hover {
  color: white;
  background-color: #8d0c0c;
}

The general idea is that often areas on the screen exists which contain contain a specific type of visual element. These elements are often presented (triggered) from code at a distance from the area they are displayed in. This is why `ViewChannel` is considered a "channel", it is a way of transporting views.

This way you can have one part of the code consume the channel, and use it to simply display the views, and many places (in code) where you put views on the channel.

The idea of the ViewChannel is that you instantiate one for each type of View you want to support, for example: you might have a flash message and a modal ViewChannel instance. Then you use these instances to send "views" to the channel.

The views are represented by a class called ViewChannelView. The ViewChannelView knows whether or not it is displayed, can dismiss itself, and has a result. The value of the result depends on the meaning you give it as a developer.

For example: when building a confirmation dialog the result may be a boolean. When building a modal containing choices the result can be a string representing the choice.

The result is stored inside of a Promise allowing for asynchronous communication. This allows you to present the view and await the result.

2. Initialization

A ViewChannel can be initialized by calling the constructor. The constructor takes two arguments the config and an optional subscriber

The config allows you to tell the ViewChannel how many history items it should track, see 8. History for more information.

The second argument is an optional subscriber, the subscriber is a callback function, allowing you to observe changes of the ViewChannel. When using vanilla JavaScript the callback is the place to perform any DOM manipulations. The callback receives the event that occurred so you can act on it.

When using reactive frameworks such as React, Vue, Angular or Svelte etc etc. The subscriber is not necessary since your framework of choice will do the heavy lifting of syncing the state of the ViewChannel with the DOM. For more information see "Usage with Frameworks"

Initialization code example
import { ViewChannel } from '@uiloos/core';

const config = {
  keepHistoryFor: 100
};

function subscriber(activeList, event) {
  console.log(event);
}

const viewChannel = new ViewChannel(config, subscriber);
import { ViewChannel ViewChannelEvent } from '@uiloos/core';

const config = {
   keepHistoryFor: 100
};

type FlashMessage = {
  text: string;
  icon: string;
}

function subscriber(
  viewChannel: ViewChannel<FlashMessage, boolean>,
  event: ViewChannelEvent<FlashMessage, boolean>
) {
  console.log(event);
}

const viewChannel = new ViewChannel(config, subscriber);

3. Live properties

A ViewChannel tracks the number of displayed views in a views array property. Loop over the views to render all views associated with the ViewChannel.

Each time you either present or dismiss a ViewChannelView the views array will be immediately updated.

The views array contains ViewChannelViews, which have live properties. A live properties is an instance variable which automatically sync whenever the ViewChannelView is changed.

  1. isPresented whether or not the ViewChannelView is currently presented. In other words if it is inside of a ViewChannels views array. When a ViewChannelView is dismissed the isPresented will be false.

    isPresented is only relevant when keeping a reference (either by accident or intentional) to a ViewChannelView, or when you access the history array. See 8. History for more information.

  2. index the index this view has, or used to have when dismissed, inside of the ViewChannelViews views array.
  3. result the result of the ViewChannelView. Is a Promise which will be resolved with the result when the ViewChannelView is dismissed.

    You the developer decide what the resulting value of the promise will be. It might be a boolean for a confirmation dialog, or simply undefined for a flash message.

    The promise will never be rejected only resolved.

    Note: this promise might never get resolved the ViewChannelView is never dismissed.

  4. autoDismiss whether or not the AutoDismiss is currently playing, and at what duration. See 7. AutoDismiss for more information.

4. Priority

The views array within a ViewChannel is sorted by priority. The ViewChannelViews with the highest priority are placed earlier in the views array.

Whenever you call present on a ViewChannel, you can provide the priority of the presented view, by setting the priority property the viewConfig argument.

The value of priority is either a number or an array of numbers. The lower the number the higher the priority. This matches the way the DEFCON scale works: 0 means war, and 5 means "normal readiness".

If priority is expressed as an array of numbers, each index in the array represents a "level" in the priority, the earlier levels (the lower indexes) have higher priority over the later levels (the higher indexes).

If two priorities are compared, first the level at index zero is compared, if they are the same the index at the second level is compared, if the second level is also the same the third level is compared, and so on and so on until there are no more levels.

A couple examples:

  1. [0, 0, 0] has a higher priority than [1, 0, 0]
  2. [1, 0, 0] has a higher priority than [2, 0, 0]
  3. [0, 0, 0] has a higher priority than [0, 1, 0]
  4. [0, 1, 0] has a higher priority than [0, 2, 0]
  5. [0, 0, 0] has a higher priority than [0, 0, 1]
  6. [0, 0, 1] has a higher priority than [0, 0, 2]
  7. [0, 0, 1] has a higher priority than [1, 0, 0]

If the priority arrays when compared differ in size, the missing items are considered zeros. So for example:

  1. [0] has a higher priority than [1, 0, 0]
  2. [0, 0] has a higher priority than [0, 1, 1]

If two priorities match exactly the view is placed after the existing view with the same priority. This means that the order will be the order of insertion.

The priority is tracked inside of the ViewChannelViews priority property, in which it is always an array of numbers. When you provide a number in the viewConfig it will be transformed into an array. For example: 10 will become [10].

5. AutoDismiss

A ViewChannelView can be put into AutoDismiss mode, this means that it dismiss itself after the configured duration. The ViewChannelView will then dismiss itself with the configured result.

The use case for AutoDismiss are components such as flash messages, these types of messages appear only briefly to the user.

The AutoDismiss can be paused via pause() and then resumed again via play(). When resumed it will take into account the duration that had already passed. For example: if the duration is 1000 milliseconds, and the user pauses after 800 milliseconds, when resumed the item will stay presented for another 200 milliseconds. This is because there was 200 milliseconds remaining for the duration.

The AutoDismiss can also be stopped via stop(). The difference between stop() and pause(), is that when play() is called after stop() the duration is reset. For example: if the duration is 500 milliseconds, and the user stops after 250 milliseconds, when play is called, the item will stay presented for 1000 milliseconds. This is because the duration is reset and not remembered.

6. History

The ViewChannelView can keep track of all events that occurred, by default is it configured not to keep any history.

When you set keepHistoryFor to a number, the ViewChannel will make sure the history array will never exceed that size.

If the size is exceeded the oldest event is removed to make place for the new event. This is based on the first in first out principle.

Tutorial
Learn how to use the ViewChannel