Messages Component

Messages Vue component represents Messages component.

Messages Components

There are following components included:

  • f7-messages - main Messages container
  • f7-message - single message element
  • f7-messages-title - single messages title element

Messages Properties

PropTypeDefaultDescription
<f7-messages> properties
initbooleantrueInitializes Messages component
new-messages-firstbooleanfalseEnable if you want to use new messages on top, instead of having them on bottom
scroll-messagesbooleantrueEnable/disable messages autoscrolling when adding new message
scroll-messages-on-edgebooleantrueIf enabled then messages autoscrolling will happen only when user is on top/bottom of the messages view
typingbooleanfalseAllows to display/toggle typing message indicator
<f7-message> properties
typestringsentMessage type: sent (default) or received
textstringMessage text
avatarstringMessage user's avatar URL
namestringMessage user's name
imagestringMessage image URL
headerstringMessage header
footerstringMessage footer
text-headerstringMessage text header
text-footerstringMessage text footer
firstbooleanfalseDefines that the message is first in the conversation
lastbooleanfalseDefines that the message is last in the conversation
tailbooleanfalseDefines that the message has visual "tail". Usually last message in conversation
same-namebooleanfalseDefines that this message sender name is the same as on previous message
same-headerbooleanfalseDefines that this message header text is the same as on previous message
same-footerbooleanfalseDefines that this message footer text is the same as on previous message
same-avatarbooleanfalseDefines that this message user's avatar URL is the same as on previous message

Messages Events

EventDescription
<f7-message> events
clickEvent will be triggered when user clicks on message bubble
click:nameEvent will be triggered when user clicks on message user's name
click:textEvent will be triggered when user clicks on message text
click:avatarEvent will be triggered when user clicks on message user's avatar
click:headerEvent will be triggered when user clicks on message header
click:footerEvent will be triggered when user clicks on message footer
click:bubbleEvent will be triggered when user clicks on message bubble

Messages Slots

Single message Vue component (<f7-message>) has additional slots for custom elements:

  • default - element will be inserted as a child of <div class="message-bubble"> element in the end
  • start - element will be inserted in the beginning and direct child of main message element <div class="message">
  • end - element will be inserted in the end and direct child of main message element <div class="message">
  • content-start - element will be inserted in the beginning and direct child of the <div class="message-content"> element
  • content-end - element will be inserted in the end and direct child of the <div class="message-content"> element
  • bubble-start - element will be inserted in the beginning and direct child of the <div class="message-bubble"> element
  • bubble-end - element will be inserted in the end and direct child of the <div class="message-bubble"> element. Same as default slot

The following slots can be used inside of single message instead of same props if you need to pass there more complext layout:

  • header - element will be inserted in message header
  • footer - element will be inserted in message footer
  • text - element will be inserted in message text
  • name - element will be inserted in message name
  • image - element will be inserted in message image (supposed to be an <img> element)
  • text-header - element will be inserted in message text header
  • text-footer - element will be inserted in message text footer
<f7-message
  type="sent"
  text="Hello World"
  name="John Doe"
  avatar="path/to/image.jpg"
>
  <div slot="start">Start</div>
  <div slot="end">End</div>
  <div slot="content-start">Content Start</div>
  <div slot="content-end">Content End</div>
  <div slot="bubble-start">Bubble Start</div>
  <div slot="bubble-end">Bubble End</div>
</f7-message>

<!-- Renders to: -->

<div class="message message-sent">
  <div>Start</div>
  <div class="message-avatar" style="background-image: url(path/to/image.jpg);"></div>
  <div class="message-content">
    <div>Content Start</div>
    <div class="message-name">John Doe</div>
    <div class="message-bubble">
      <div>Bubble Start</div>
      <div class="message-text">Hello World</div>
      <div>Bubble End</div>
    </div>
    <div>Content End</div>
  </div>
  <div>End</div>
</div>

Examples

Here is how the full example of Messages page where it can be used together with Messagebar:

<template>
<f7-page>
  <f7-navbar title="Messages" />

  <f7-messagebar
    ref="messagebar"
    v-model:value="messageText"
    :placeholder="placeholder"
    :attachments-visible="attachmentsVisible"
    :sheet-visible="sheetVisible"
  >
    <template #inner-start>
      <f7-link
        icon-ios="f7:camera_fill"
        icon-aurora="f7:camera_fill"
        icon-md="material:camera_alt"
        @click="sheetVisible = !sheetVisible"
      />
    </template>
    <template #inner-end>
      <f7-link
        icon-ios="f7:arrow_up_circle_fill"
        icon-aurora="f7:arrow_up_circle_fill"
        icon-md="material:send"
        @click="sendMessage"
      />
    </template>
    <f7-messagebar-attachments>
      <f7-messagebar-attachment
        v-for="(image, index) in attachments"
        :key="index"
        :image="image"
        @attachment:delete="deleteAttachment(image)"
      ></f7-messagebar-attachment>
    </f7-messagebar-attachments>
    <f7-messagebar-sheet>
      <f7-messagebar-sheet-image
        v-for="(image, index) in images"
        :key="index"
        :image="image"
        :checked="attachments.indexOf(image) >= 0"
        @change="handleAttachment"
      ></f7-messagebar-sheet-image>
    </f7-messagebar-sheet>
  </f7-messagebar>

  <f7-messages>
    <f7-messages-title><b>Sunday, Feb 9,</b> 12:58</f7-messages-title>
    <f7-message
      v-for="(message, index) in messagesData"
      :key="index"
      :type="message.type"
      :image="message.image"
      :name="message.name"
      :avatar="message.avatar"
      :first="isFirstMessage(message, index)"
      :last="isLastMessage(message, index)"
      :tail="isTailMessage(message, index)"
    >
      <template #text>
        <!-- eslint-disable-next-line -->
        <span v-if="message.text" v-html="message.text"></span>
      </template>
    </f7-message>
    <f7-message
      v-if="typingMessage"
      type="received"
      :typing="true"
      :first="true"
      :last="true"
      :tail="true"
      :header="`${typingMessage.name} is typing`"
      :avatar="typingMessage.avatar"
    ></f7-message>
  </f7-messages>
</f7-page>
</template>
<script>
import { f7, f7ready } from 'framework7-vue';
import $ from 'dom7';

export default {
  data() {
    return {
      attachments: [],
      sheetVisible: false,
      typingMessage: null,
      messageText: '',
      messagesData: [
        {
          type: 'sent',
          text: 'Hi, Kate',
        },
        {
          type: 'sent',
          text: 'How are you?',
        },
        {
          name: 'Kate',
          type: 'received',
          text: 'Hi, I am good!',
          avatar: 'https://cdn.framework7.io/placeholder/people-100x100-9.jpg',
        },
        {
          name: 'Blue Ninja',
          type: 'received',
          text: 'Hi there, I am also fine, thanks! And how are you?',
          avatar: 'https://cdn.framework7.io/placeholder/people-100x100-7.jpg',
        },
        {
          type: 'sent',
          text: 'Hey, Blue Ninja! Glad to see you ;)',
        },
        {
          type: 'sent',
          text: 'Hey, look, cutest kitten ever!',
        },
        {
          type: 'sent',
          image: 'https://cdn.framework7.io/placeholder/cats-200x260-4.jpg',
        },
        {
          name: 'Kate',
          type: 'received',
          text: 'Nice!',
          avatar: 'https://cdn.framework7.io/placeholder/people-100x100-9.jpg',
        },
        {
          name: 'Kate',
          type: 'received',
          text: 'Like it very much!',
          avatar: 'https://cdn.framework7.io/placeholder/people-100x100-9.jpg',
        },
        {
          name: 'Blue Ninja',
          type: 'received',
          text: 'Awesome!',
          avatar: 'https://cdn.framework7.io/placeholder/people-100x100-7.jpg',
        },
      ],
      images: [
        'https://cdn.framework7.io/placeholder/cats-300x300-1.jpg',
        'https://cdn.framework7.io/placeholder/cats-200x300-2.jpg',
        'https://cdn.framework7.io/placeholder/cats-400x300-3.jpg',
        'https://cdn.framework7.io/placeholder/cats-300x150-4.jpg',
        'https://cdn.framework7.io/placeholder/cats-150x300-5.jpg',
        'https://cdn.framework7.io/placeholder/cats-300x300-6.jpg',
        'https://cdn.framework7.io/placeholder/cats-300x300-7.jpg',
        'https://cdn.framework7.io/placeholder/cats-200x300-8.jpg',
        'https://cdn.framework7.io/placeholder/cats-400x300-9.jpg',
        'https://cdn.framework7.io/placeholder/cats-300x150-10.jpg',
      ],
      people: [
        {
          name: 'Kate Johnson',
          avatar: 'https://cdn.framework7.io/placeholder/people-100x100-9.jpg',
        },
        {
          name: 'Blue Ninja',
          avatar: 'https://cdn.framework7.io/placeholder/people-100x100-7.jpg',
        },
      ],
      answers: [
        'Yes!',
        'No',
        'Hm...',
        'I am not sure',
        'And what about you?',
        'May be ;)',
        'Lorem ipsum dolor sit amet, consectetur',
        'What?',
        'Are you sure?',
        'Of course',
        'Need to think about it',
        'Amazing!!!',
      ],
      responseInProgress: false,
    };
  },
  computed: {
    attachmentsVisible() {
      const self = this;
      return self.attachments.length > 0;
    },
    placeholder() {
      const self = this;
      return self.attachments.length > 0 ? 'Add comment or Send' : 'Message';
    },
  },
  mounted() {
    const self = this;
    f7ready(() => {
      self.messagebar = f7.messagebar.get(self.$refs.messagebar.$el);
    });
  },
  methods: {
    isFirstMessage(message, index) {
      const self = this;
      const previousMessage = self.messagesData[index - 1];
      if (message.isTitle) return false;
      if (
        !previousMessage ||
        previousMessage.type !== message.type ||
        previousMessage.name !== message.name
      )
        return true;
      return false;
    },
    isLastMessage(message, index) {
      const self = this;
      const nextMessage = self.messagesData[index + 1];
      if (message.isTitle) return false;
      if (!nextMessage || nextMessage.type !== message.type || nextMessage.name !== message.name)
        return true;
      return false;
    },
    isTailMessage(message, index) {
      const self = this;
      const nextMessage = self.messagesData[index + 1];
      if (message.isTitle) return false;
      if (!nextMessage || nextMessage.type !== message.type || nextMessage.name !== message.name)
        return true;
      return false;
    },
    deleteAttachment(image) {
      const self = this;
      const index = self.attachments.indexOf(image);
      self.attachments.splice(index, 1)[0]; // eslint-disable-line
    },
    handleAttachment(e) {
      const self = this;
      const index = $(e.target).parents('label.checkbox').index();
      const image = self.images[index];
      if (e.target.checked) {
        // Add to attachments
        self.attachments.unshift(image);
      } else {
        // Remove from attachments
        self.attachments.splice(self.attachments.indexOf(image), 1);
      }
    },
    sendMessage() {
      const self = this;
      const text = self.messageText.replace(/\n/g, '<br>').trim();
      const messagesToSend = [];
      self.attachments.forEach((attachment) => {
        messagesToSend.push({
          image: attachment,
        });
      });
      if (text.length) {
        messagesToSend.push({
          text,
        });
      }
      if (messagesToSend.length === 0) {
        return;
      }

      // Reset attachments
      self.attachments = [];
      // Hide sheet
      self.sheetVisible = false;
      // Clear area
      self.messageText = '';
      // Focus area
      if (text.length) self.messagebar.focus();
      // Send message
      self.messagesData.push(...messagesToSend);

      // Mock response
      if (self.responseInProgress) return;
      self.responseInProgress = true;
      setTimeout(() => {
        const answer = self.answers[Math.floor(Math.random() * self.answers.length)];
        const person = self.people[Math.floor(Math.random() * self.people.length)];
        self.typingMessage = {
          name: person.name,
          avatar: person.avatar,
        };
        setTimeout(() => {
          self.messagesData.push({
            text: answer,
            type: 'received',
            name: person.name,
            avatar: person.avatar,
          });
          self.typingMessage = null;
          self.responseInProgress = false;
        }, 4000);
      }, 1000);
    },
  },
};
</script>