Layer’s UI Libraries

As part of Layer’s eXperience Development Kit (XDK), a library of UI widgets is provided to streamline building your applications. UI Libraries serve the following goals:

  • Provide commonly used high level UI Components such as the Conversation View and Conversation List
  • Provide a library of customizable lower level UI Components such as Avatars, Presence, Compose Bar, Typing Indicators, etc…
  • Enable application developers to focus on their business logic and customizations rather than focusing on providing, debugging and maintaining basic building blocks of a messaging application.
  • Provide a library of different types of messages that can be sent among users, and provide enhanced interactivity to your application.

Using the Layer XDK UI Components

Using the UI Components of this Library is as simple as adding an html tag to your page. The following would generate a suitable UI:

<html>
  <head>
    <!-- STEP 1: Load the Layer Web XDK -->
    <script src='https://cdn.layer.com/xdk/4.0/layer-xdk.min.js'></script>
    <link rel='stylesheet' href='https://cdn.layer.com/xdk/4.0/themes/layer-basic-blue.css' />

    <script>
      // STEP 2: Initialize the Client and UI library
      var client = Layer.init({
        appId: 'layer:///apps/staging/UUID'
      });

      // STEP 3: Setup event handlers on UI Components
      document.addEventListener('DOMContentLoaded', function() {
        var conversationsList = document.querySelector('layer-conversation-list');
        var conversationView = document.querySelector('layer-conversation-view');

        // Whenever the user selects a conversation in the list,
        // tell the conversation panel that this is the selected conversation
        conversationsList.onConversationSelected = function(evt) {
          conversationView.conversation = evt.detail.item;
        };
      });

      // STEP 4: Handle authentication challenge
      client.on('challenge', function(evt) {
	      // Add your authentication code here
      });

      // STEP 6: Start Authentication
      client.connect();
    </script>

    <style>
      body {
        display: flex;
        flex-direction: row;
      }
      layer-identity-list {
        width: 200px;
        border-right: solid 1px #999;
      }
      layer-conversation-list {
        width: 200px;
        border-right: solid 1px #999;
      }
      layer-conversation-view {
        width: 500px;
        flex-grow: 1;
      }
    </style>
  </head>

  <!-- STEP 7: Use the widgets in your page or by generating DOM nodes -->
  <body>
    <layer-conversation-list></layer-conversation-list>
    <layer-conversation-view></layer-conversation-view>
  </body>
</html>

Note

The above code is an example. For a more complete and working example, see the Sample Apps Repo

Using Webcomponents means that you can also create an element simply with:

var conversationWidget = document.createElement('layer-conversation-view');
document.body.appendChild(conversationWidget);

Working with Webcomponents

As with any DOM node, a Webcomponent has attributes and properties; we do not define one without the other. What that means:

<layer-conversation-view conversation-id="layer:///conversations/UUID" />

will mean the same as:

var conversationView = document.createElement('layer-conversation-view');
conversationView.conversationId = 'layer:///conversations/UUID';

Any property you see documented in this UI Library can be accessed as an attribute using the dash-separated-name or as a property using its camelCasedName. The key difference between attributes and properties is that attributes will be converted by the browser to a string, making it less than ideal for passing in functions and complex objects.

Why Webcomponents?

The goal of this library is to provide UI Components that do not depend upon any one UI library, but which will work without conflict in ANY UI Library. To accomplish this, we use Webcomponents, which is a technique for defining new DOM nodes such as <layer-conversation-view/>. What this means for you:

  • If working on a raw Javascript project, you can directly add the widgets to your html and they will just work.
  • If working with a templating engine such as an Angular template file, you can directly add the widgets to your html template, and they will add the widget to your page. Template frameworks that provide bindings should also work.
  • If working with a Flux architecture, understand that as with all DOM nodes, some state is managed internally and some can (optional) be managed via your application state. All UI Components are setup to be able to manage their own state, obtain their own data from the server and should work either autonomously or with data provided by your Flux architecture.
  • Webcomponents are natively supported on most browsers (MS Edge and IE 11 use a polyfill). See CanIUse: custom-elementsv1 for details on browser support for the features we depend upon, and Webcomponentsjs for the polyfill that is included in the XDK Build.

Adapters

While UI Frameworks can work with webcomponents, they tend to have built in expectations around what types of DOM nodes exist making it more complicated to incorporate webcomponents into your project. To address this, UI Framework Adapters are provided that wraps the webcomponent in a Component from that UI Framework, allowing you to use it more naturally within your application.

Note that the Layer.init() call defines the webcomponents that these adapters are designed to wrap, and as such, must be called before initializing an adapter.

The examples below illustrate common widgets the developers will use, but Components are created for every component in the XDK. For any UI Component name, just remove layer from the name and capitalize the words to come up the proper name:

DOM Node Name Import Syntax
<layer-avatar /> { Avatar } = Layer.UI.adapter.react()
<layer-compose-bar /> { ComposeBar } = Layer.UI.adapter.backbone()
<layer-conversation-view /> { ConversationView } = Layer.UI.adapter.someAdaptorName()

For npm based builds, you must explicitly import the adapter to make it available prior to using it.

React

The React adapter provides a library of React Components that wraps the Webcomponents, and insures that any property or attribute you set is properly passed onto the widget (property names and attribute names can be used interchangably), and that the shouldComponentUpdate method is correctly implemented.

Recommended practice in using the React adaptor is to setup a file such as get-layer.js that looks like:

// File Name: get-layer.js
import React, { Component, PropTypes } from 'react';
import ReactDom from 'react-dom';
import Config from './LayerConfiguration.json';

// STEP 1: Import the WebXDK, and the adapters and UI Components that are required
import * as Layer from '@layerhq/web-xdk';
import '@layerhq/web-xdk/ui/adapters/react'; // Import the adapter to use it
import '@layerhq/web-xdk/ui/components/layer-conversation-list'; // Import the conversation list if using it
import '@layerhq/web-xdk/ui/components/layer-send-button'; // Import the send button if using it

// STEP 2: Initialize XDK with your appId
const layerClient = Layer.init({ appId: Config[0].appId });

// STEP 3: Run the react adapter to get all of the React Components that wrap webcomponents
const LayerReactComponents = Layer.UI.adapters.react(React, ReactDom);

// STEP 4: Export the UI Components, Layer Client and the Layer Namespace
module.exports = { LayerReactComponents, Layer, layerClient };

Any file that uses Layer and its classes/components should import get-layer and not directly import from @layerhq/web-xdk.

For example:

import { LayerReactComponents } from './get-layer';
const { ConversationList, ConversationView, SendButton } = LayerReactComponents;

class App extends React.Component {
  render() {
      return (
        <div>
          <ConversationsList
            onConversationSelected={this.conversationSelected}></ConversationsList>
          <ConversationView conversationId={this.state.conversationId}></ConvewrsationView>
        </div>
      );
  }
  conversationSelected(evt) {
    this.setState({ conversationId: evt.detail.item ? evt.detail.item.id : null });
  }
}

Angular 1.5

The Angular adapter is tested with Angular 1.5, and provides a library of Directives which will allow you to use any layer widget directly in your templates. Add the ng- prefix to attribute names if you need scope to be evaluated; if assigning a literal value, directly use the attribute name.

  <layer-notifier notify-in-foreground="toast"></layer-notifier>
  <layer-conversation-view ng-query="myscopeProp.query"></layer-conversation-view>
  <layer-conversation-list ng-conversation-selected="myscope.handleSelectionFunc">
  </layer-conversation-list>

Call this function to initialize angular 1.x Directives which will be part of the “layerXDKControllers” controller:

import { UI, init } from '@layerhq/web-xdk'
import '@layerhq/web-xdk/ui/adapters/angular';
import '@layerhq/web-xdk/ui/components/layer-conversation-list'; // Import the conversation list if using it
import '@layerhq/web-xdk/ui/components/layer-send-button'; // Import the send button if using it

init({ appId: 'layer:///apps/staging/UUID' });

UI.adapters.angular(angular); // Creates the layerXDKControllers controller
angular.module('MyApp', ['layerXDKControllers']);

Backbone

Initialize this adapter for access to XDK Backbone Views using:

import { UI, init } from '@layerhq/web-xdk'
import '@layerhq/web-xdk/ui/adapters/backbone';

// Import optional UI Components
import '@layerhq/web-xdk/ui/components/layer-conversation-list';
import '@layerhq/web-xdk/ui/components/layer-send-button';

import Backbone from 'backbone';
init({ appId: 'layer:///apps/staging/UUID' });

const {
  ConversationView,
  ConversationList,
  SendButton,
} = UI.adapters.backbone(Backbone);

// Now we access the Views
var conversationView = new ConversationView(client, {
  conversationId: 'layer:///conversations/UUID'
});
var conversationsListView = new ConversationsList(client);

Any occurances of a layer widget in your html should be associated with these views:

<layer-conversation-list></layer-conversation-list>
<layer-conversation-view conversation-id="layer:///conversations/UUID"></layer-conversation-view>

Events and Custom Behaviors

The Web XDK UI Components come with various events and default behaviors built in. Examples:

  • The user hits the ENTER key.
    • Default Behavior: The user’s text is sent as a Message.
    • Event: layer-send-message is triggered, and calling evt.preventDefault() will prevent the message from sending. Or you can modify the message and then let it continue to send.
  • The user clicks to select a Conversation in the Conversation List
    • Default Behavior: The Conversation List highlights the new selection, events trigger that cause the Conversation View to show the selected Conversation
    • Event: layer-conversation-selected is triggered and calling evt.preventDefault() will prevent the selection from changing.
  • The Layer Notifier component detects that the user has received a new message
    • Default Behavior: Show a Desktop or Toast notification
    • Event: layer-message-notification is triggered and calling evt.preventDefault() will prevent the notification from being shown and allow your app to handle this some other way

All of these actions result in an event that allows your app to cancel the specified result, and optionally provide your own result.

Events and custom behaviors are provided via the Javascript CustomEvent Object. A typical flow would be something like this:

  1. User hits ENTER key
  2. Widget triggers a layer-send-message CustomEvent and passes the layer.Core.Message instance that it created and is about to send.
  3. App listens for the event using node.addEventListener('layer-send-message', handler); note that events bubble up, so the node in question could be document.body.
  4. App receives the event and may modify the Message’s MessageParts, and may call evt.preventDefault()
  5. If evt.preventDefault() was called then the Message will not be sent by the widget. Perhaps you will send it later, or perhaps you want to send it some other way.
  6. If evt.preventDefault() is NOT called by your handler, then the UI Component will send your Message.
document.body.addEventListener('layer-send-message', function(evt) {
  var model = evt.detail.model;
  if (model.getModelName() == 'ProductModel') {

    // Prevent this message from being sent while we do a price check
    evt.preventDefault();
    doPriceCheck((result) => {
      if (result) model.message.send({title: "New Product", text: "Your $75 penny is ready"});
    });
  } else {
    // Not calling evt.preventDefault() allows this message to be sent after we finish modifying it.
    model.message.addPart({
      mimeType: 'application/json+message-metadata',
      body: JSON.stringify({
        someState: someValue
      })
    });
  }
});

All parameters of a CustomEvent are passed in the evt.detail sub-object. Events are documented in detail in the API Reference.

Listeners

Some widgets are designed to work with other widgets. For example, whenever you select a Conversation in a Conversation List, the Conversation Panel would typically be expected to render the selected Conversation. However, this is not automatic; you may have many Conversation Lists and Many Conversation Panels, and may need to control this behavior. So instead, the XDK provides the listen-to attribute which you can choose to use:

  <layer-notifier id="notifier"></layer-notifier>
  <layer-conversation-list id="conversation-list"></layer-conversation-list>
  <layer-conversation-view listen-to="conversation-list, notifier"></layer-conversation-view>

This tells the Conversation Panel to listen to the Conversation List; any selection events will automatically update the Conversation Panel’s conversation property. Compare this to using events to do the same:

var conversationsList = document.querySelector('layer-conversation-list');
var conversationView = document.querySelector('layer-conversation-view');

// Whenever the user selects a conversation in the list,
// tell the conversation panel that this is the selected conversation
conversationsList.onConversationSelected = function(evt) {
  conversationView.conversation = evt.detail.item;
});

Both are simple, but one can be easily wired up in html/template files.

Adding the <layer-notifier> into the example means that when a user clicks on a notification, the Conversation Panel will notice and select the correct conversation to show the message from the notification.

Privacy Core Components