Messages
Messages component will help you with visualisation of comments and messaging system in your app.
Messages Layout
<div class="page">
<div class="page-content messages-content">
<div class="messages">
<!-- Date stamp -->
<div class="messages-title"><b>Sunday, Feb 9</b> 12:58</div>
<!-- Sent message (by default - green and on right side) -->
<div class="message message-sent">
<div class="message-content">
<!-- Bubble with text -->
<div class="message-bubble">
<div class="message-text">Hi, Kate</div>
</div>
</div>
</div>
<!-- Another sent message -->
<div class="message message-sent">
<div class="message-content">
<div class="message-bubble">
<div class="message-text">How are you?</div>
</div>
</div>
</div>
<!-- Received message (by default - grey on left side) -->
<div class="message message-received">
<!-- Sender avatar -->
<div class="message-avatar" style="background-image:url(path/to/avatar.jpg)"></div>
<div class="message-content">
<!-- Sender name -->
<div class="message-name">Blue Ninja</div>
<!-- Bubble with text -->
<div class="message-bubble">
<div class="message-text">Hi there, I am also fine, thanks! And how are you?</div>
</div>
</div>
</div>
</div>
</div>
</div>
Messages page layout:
messages-content
- required additional class for messages wrapper. Should be added topage-content
messages
- required additional wrapper for messages bubbles. Required element.messages-title
- messages titlemessage
- single message
Single Message Layout
Here is a full single message layout:
<div class="message">
<div class="message-avatar" style="background-image:url(path/to/avatar)"></div>
<div class="message-content">
<div class="message-name">John Doe</div>
<div class="message-header">Message header</div>
<div class="message-bubble">
<div class="message-text-header">Text header</div>
<div class="message-image">
<img src="path/to/image" />
</div>
<div class="message-text">Hello world!</div>
<div class="message-text-footer">Text footer</div>
</div>
<div class="message-footer">Message footer</div>
</div>
</div>
message-avatar
- sender avatar, optionalmessage-name
- sender name, optionalmessage-header
- single message header, optionalmessage-text-header
- text header inside of bubble, optionalmessage-image
- message image, optionalmessage-text
- message text, optionalmessage-text-footer
- text footer inside of bubble, optionalmessage-footer
- footer text after bubble, optional
Additional classes for single message container
message-sent
- additional class for single message which indicates that this message was sent by user. It stays on right side with green background color.message-received
- additional class for single message which indicates that this message was received by user. It stays on left side with grey background color.message-tail
- additional class for single message (received or sent) to add bubble "tail"message-same-name
- additional class for indicating that message has same sender name as previous messagemessage-same-avatar
- additional class for indicating that message has same avatar as previous messagemessage-same-header
- additional class for indicating that message has same message-header as previous messagemessage-same-footer
- additional class for indicating that message has same message-footer as previous messagemessage-last
- additional class for single message (received or sent) to indicate last received or last sent message in current conversation by one sendermessage-first
- additional class for single message (received or sent) to indicate first received or first sent message in current conversation by one sender
Messages App Methods
Now, when we have Messages' HTML, we need to initialize it. We need to use related App's method:
app.messages.create(parameters) | Initialize Messages with parameters
|
app.messages.destroy(el) | Destroy Messages instance
|
app.messages.get(el) | Get Messages instance by HTML element
|
Messages Parameters
Let's look on list of all available parameters:
Parameter | Type | Default | Description |
---|---|---|---|
autoLayout | boolean | true | Enable Auto Layout to add all required additional classes automatically based on passed conditions |
newMessagesFirst | boolean | false | Enable if you want to use new messages on top, instead of having them on bottom |
scrollMessages | boolean | true | Enable/disable messages autoscrolling when adding new message |
scrollMessagesOnEdge | boolean | true | If enabled then messages autoscrolling will happen only when user is on top/bottom of the messages view |
messages | array | Array with initial messages. Each message in array should be presented as an object with single message parameters | |
on | object | Object with events handlers. For example:
| |
renderMessage | function(message) | Function to render single message. Must return full message HTML string | |
Autolayout Conditions | |||
firstMessageRule | function(message, previousMessage, nextMessage) | Function that must return boolean true or false based on required condition depending on previous and next messages. In case of match then message-first class will be added to message | |
lastMessageRule | function(message, previousMessage, nextMessage) | Function that must return boolean true or false based on required condition depending on previous and next messages. In case of match then message-last class will be added to message | |
tailMessageRule | function(message, previousMessage, nextMessage) | Function that must return boolean true or false based on required condition depending on previous and next messages. In case of match then message-tail class will be added to message | |
sameNameMessageRule | function(message, previousMessage, nextMessage) | Function that must return boolean true or false based on required condition depending on previous and next messages. In case of match then message-same-name class will be added to message | |
sameHeaderMessageRule | function(message, previousMessage, nextMessage) | Function that must return boolean true or false based on required condition depending on previous and next messages. In case of match then message-same-header class will be added to message | |
sameFooterMessageRule | function(message, previousMessage, nextMessage) | Function that must return boolean true or false based on required condition depending on previous and next messages. In case of match then message-same-footer class will be added to message | |
sameAvatarMessageRule | function(message, previousMessage, nextMessage) | Function that must return boolean true or false based on required condition depending on previous and next messages. In case of match then message-same-avatar class will be added to message | |
customClassMessageRule | function(message, previousMessage, nextMessage) | Function that must return additional message classes as string, based on required condition depending on previous and next messages. |
Single Message Parameters
Let's look on single message parameters object that we should use when we pass messages
array:
Parameter | Type | Default | Description |
---|---|---|---|
text | string | Message text | |
header | string | Single message header | |
footer | string | Single message footer | |
name | string | Sender name | |
avatar | string | Sender avatar URL string | |
type | string | sent | Message type - sent or received |
textHeader | string | Message text header | |
textFooter | string | Message text footer | |
image | string | Message image HTML string, e.g. <img src="path/to/image" /> . Can be used instead of imageSrc parameter | |
imageSrc | string | Message image URL string. Can be used instead of image parameter | |
isTitle | boolean | Defines whether it should be rendered as a message or as a messages title | |
cssClass | string | Additional CSS class to set on message HTML element | |
attrs | object | Object with additional HTML attributes to be set on message HTML element. For example to set extra data attributes it should look like:
|
Messages Methods & Properties
So to create Messages we have to call:
var messages = app.messages.create({ /* parameters */ })
After we initialize Messages we have its initialized instance in variable (like messages
variable in example above) with helpful methods and properties:
Properties | |
---|---|
messages.params | Object with passed initialization parameters |
messages.el | Messages container HTML element (<div class="messages"> ) |
messages.$el | Dom7 element with messages HTML element |
messages.messages | Array with messages |
Methods | |
messages.showTyping(message) | Show typing message indicator
|
messages.hideTyping() | Hide typing message indicator |
messages.addMessage(message, method, animate); | Add new message to the end or to the beginning depending on method parameter
|
messages.addMessages(messages, method, animate); | Add multiple messages per once.
|
messages.removeMessage(message); | Remove message
|
messages.removeMessages(messages); | Remove multiple messages
|
messages.scroll(duration, position); | Scroll messages to top/bottom depending on newMessagesFirst parameter
|
messages.renderMessages() | Render messages HTML depending on messages array |
messages.layout(); | Force messages auto layout |
messages.clear(); | Clear/remove all the messages |
messages.destroy(); | Destroy messages instance |
Messages Events
Messages will fire the following DOM events on messages element and events on app and messages instance:
DOM Events
Event | Target | Description |
---|---|---|
messages:beforedestroy | Messages Element<div class="messages"> | Event will be triggered right before Messages instance will be destroyed |
App and Messages Instance Events
Messages instance emits events on both self instance and app instance. App instance events has same names prefixed with messages
.
Event | Target | Arguments | Description |
---|---|---|---|
beforeDestroy | messages | (messages) | Event will be triggered right before Messages instance will be destroyed |
messagesBeforeDestroy | app |
Messages Auto Initialization
If you don't need to use Messages API and your Messages is inside of the page and presented in DOM on moment of page initialization then it can be auto initialized with just adding additional messages-init
class to messages element, and all required parameters can be passed using data-
attributes:
<div class="messages messages-init" data-new-messages-first="true">
...
</div>
Parameters that used in camelCase, for example newMessagesFirst, in data- attributes should be used in kebab-case as data-new-messages-first
CSS Variables
Below is the list of related CSS variables (CSS custom properties).
Note that commented variables are not specified by default and their values is what they fallback to in this case.
:root {
--f7-message-text-header-opacity: 0.65;
--f7-message-text-header-font-size: 12px;
--f7-message-text-footer-opacity: 0.65;
--f7-message-text-footer-font-size: 12px;
--f7-message-bubble-line-height: 1.2;
--f7-message-header-font-size: 12px;
--f7-message-footer-font-size: 11px;
--f7-message-name-font-size: 12px;
--f7-message-name-font-weight: inherit;
--f7-message-avatar-border-radius: 50%;
--f7-messages-title-font-weight: inherit;
--f7-message-typing-indicator-bg-color: #000;
}
:root .dark,
:root.dark {
--f7-message-typing-indicator-bg-color: #fff;
}
.ios {
--f7-message-text-header-text-color: inherit;
--f7-message-text-footer-text-color: inherit;
--f7-messages-title-font-size: 11px;
--f7-message-avatar-size: 29px;
--f7-message-margin: 10px;
--f7-message-bubble-min-height: 32px;
--f7-message-bubble-font-size: 17px;
--f7-message-bubble-border-radius: 16px;
--f7-message-bubble-padding-vertical: 6px;
--f7-message-bubble-padding-horizontal: 16px;
--f7-message-typing-indicator-opacity: 0.35;
/*
--f7-message-sent-bg-color: var(--f7-theme-color);
*/
--f7-message-sent-text-color: #fff;
--f7-messages-content-bg-color: #fff;
--f7-message-received-text-color: #000;
--f7-message-received-bg-color: #e5e5ea;
--f7-messages-title-text-color: rgba(0, 0, 0, 0.45);
--f7-message-header-text-color: rgba(0, 0, 0, 0.45);
--f7-message-footer-text-color: rgba(0, 0, 0, 0.45);
--f7-message-name-text-color: rgba(0, 0, 0, 0.45);
}
.ios .dark,
.ios.dark {
--f7-message-received-bg-color: #252525;
--f7-message-received-text-color: #fff;
--f7-messages-content-bg-color: transparent;
--f7-messages-title-text-color: rgba(255, 255, 255, 0.54);
--f7-message-header-text-color: rgba(255, 255, 255, 0.54);
--f7-message-name-text-color: rgba(255, 255, 255, 0.54);
--f7-message-footer-text-color: rgba(255, 255, 255, 0.54);
}
.md {
--f7-messages-content-bg-color: transparent;
--f7-messages-title-font-size: 12px;
--f7-message-avatar-size: 32px;
--f7-message-margin: 16px;
--f7-message-bubble-min-height: 40px;
--f7-message-bubble-font-size: 16px;
--f7-message-bubble-border-radius: 20px;
--f7-message-bubble-padding-vertical: 10px;
--f7-message-bubble-padding-horizontal: 16px;
--f7-message-typing-indicator-opacity: 0.6;
}
.md,
.md .dark,
.md [class*='color-'] {
--f7-message-sent-bg-color: var(--f7-md-primary);
--f7-message-sent-text-color: var(--f7-md-on-primary);
--f7-message-received-bg-color: var(--f7-md-surface-variant);
--f7-message-received-text-color: var(--f7-md-on-surface);
--f7-message-text-header-text-color: var(--f7-md-on-surface-variant);
--f7-message-text-footer-text-color: var(--f7-md-on-surface-variant);
--f7-messages-title-text-color: var(--f7-md-on-surface-variant);
--f7-message-header-text-color: var(--f7-md-on-surface-variant);
--f7-message-footer-text-color: var(--f7-md-on-surface-variant);
--f7-message-name-text-color: var(--f7-md-on-surface-variant);
}
Examples
<template>
<div class="page">
<div class="navbar">
<div class="navbar-bg"></div>
<div class="navbar-inner sliding">
<div class="title">Messages</div>
</div>
</div>
<div class="toolbar messagebar" @messagebar:attachmentdelete=${deleteAttachment}>
<div class="toolbar-inner">
<a class="link icon-only" @click=${sheetToggle}>
<i class="icon f7-icons if-not-md">camera_fill</i>
<i class="icon material-icons md-only">camera_alt</i>
</a>
<div class="messagebar-area">
<textarea class="resizable" placeholder="Message"></textarea>
</div>
<a class="link icon-only demo-send-message-link" @click=${sendMessage}>
<i class="icon f7-icons if-not-md">arrow_up_circle_fill</i>
<i class="icon material-icons md-only">send</i>
</a>
</div>
<div class="messagebar-sheet">
${images.map((image) => $h`
<label class="checkbox messagebar-sheet-image" @change=${handleAttachment}>
<input type="checkbox" />
<i class="icon icon-checkbox"></i>
<img src=${image} />
</label>
`)}
</div>
</div>
<div class="page-content messages-content">
<div class="messages">
<div class="messages-title"><b>Sunday, Feb 9,</b> 12:58</div>
<div class="message message-sent">
<div class="message-content">
<div class="message-bubble">
<div class="message-text">Hi, Kate</div>
</div>
</div>
</div>
<div class="message message-sent">
<div class="message-content">
<div class="message-bubble">
<div class="message-text">How are you?</div>
</div>
</div>
</div>
<div class="message message-received">
<div class="message-avatar"
style="background-image:url(https://cdn.framework7.io/placeholder/people-100x100-9.jpg)"></div>
<div class="message-content">
<div class="message-name">Kate</div>
<div class="message-bubble">
<div class="message-text">Hi, I am good!</div>
</div>
</div>
</div>
<div class="message message-received">
<div class="message-avatar"
style="background-image:url(https://cdn.framework7.io/placeholder/people-100x100-7.jpg)"></div>
<div class="message-content">
<div class="message-name">Blue Ninja</div>
<div class="message-bubble">
<div class="message-text">Hi there, I am also fine, thanks! And how are you?</div>
</div>
</div>
</div>
<div class="message message-sent">
<div class="message-content">
<div class="message-bubble">
<div class="message-text">Hey, Blue Ninja! Glad to see you ;)</div>
</div>
</div>
</div>
<div class="message message-sent">
<div class="message-content">
<div class="message-bubble">
<div class="message-text">Hey, look, cutest kitten ever!</div>
</div>
</div>
</div>
<div class="message message-sent">
<div class="message-content">
<div class="message-bubble">
<div class="message-image">
<img src="https://cdn.framework7.io/placeholder/cats-200x260-4.jpg"
style="width:200px; height: 260px" />
</div>
</div>
</div>
</div>
<div class="message message-received">
<div class="message-avatar"
style="background-image:url(https://cdn.framework7.io/placeholder/people-100x100-9.jpg)"></div>
<div class="message-content">
<div class="message-name">Kate</div>
<div class="message-bubble">
<div class="message-text">Nice!</div>
</div>
</div>
</div>
<div class="message message-received">
<div class="message-avatar"
style="background-image:url(https://cdn.framework7.io/placeholder/people-100x100-9.jpg)"></div>
<div class="message-content">
<div class="message-name">Kate</div>
<div class="message-bubble">
<div class="message-text">Like it very much!</div>
</div>
</div>
</div>
<div class="message message-received">
<div class="message-avatar"
style="background-image:url(https://cdn.framework7.io/placeholder/people-100x100-7.jpg)"></div>
<div class="message-content">
<div class="message-name">Blue Ninja</div>
<div class="message-bubble">
<div class="message-text">Awesome!</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default (props, { $f7, $el, $on, $ }) => {
const 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'
];
const 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'
},
];
const 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!!!',
];
let responseInProgress = false;
let messagebar;
let messages;
const sheetToggle = () => {
messagebar.sheetToggle();
}
const deleteAttachment = (e, index) => {
var image = messagebar.attachments.splice(index, 1)[0];
messagebar.renderAttachments();
checkAttachments();
// Uncheck in sheet
var imageIndex = images.indexOf(image);
$el.value.find('.messagebar-sheet .checkbox').eq(imageIndex).find('input').prop('checked', false);
}
const handleAttachment = (e) => {
var index = $(e.target).parents('label.checkbox').index();
var image = images[index];
if (e.target.checked) {
// Add to attachments
messagebar.attachments.unshift(image)
} else {
// Remove from attachments
messagebar.attachments.splice(messagebar.attachments.indexOf(image), 1);
}
messagebar.renderAttachments();
checkAttachments();
}
const checkAttachments = () => {
if (messagebar.attachments.length > 0) {
messagebar.attachmentsShow();
messagebar.setPlaceholder('Add comment or Send');
} else {
messagebar.attachmentsHide();
messagebar.setPlaceholder('Message');
}
}
const sendMessage = () => {
var text = messagebar.getValue().replace(/\n/g, '<br />').trim();
var messagesToSend = [];
messagebar.attachments.forEach(function (attachment) {
var size = attachment.split('placeholder/cats-')[1].split('-')[0].split('x');
messagesToSend.push({
image: '<img src="' + attachment + '" style="width: ' + (size[0] / 2) + 'px; height: ' + (size[1] / 2) + 'px">'
});
});
if (text.trim().length) {
messagesToSend.push({
text: text
});
}
// Reset attachments
messagebar.attachments = [];
checkAttachments();
// Hide sheet
messagebar.sheetHide();
// Uncheck selected images in sheet
messagebar.$sheetEl.find('input').prop('checked', false);
// Clear area
messagebar.clear();
// Focus area
if (text.length) messagebar.focus();
// Exit when nothing to send
if (!messagesToSend.length) return;
// Send message
messages.addMessages(messagesToSend);
// Mock response
if (responseInProgress) return;
responseInProgress = true;
setTimeout(function () {
var answer = answers[Math.floor(Math.random() * answers.length)];
var person = people[Math.floor(Math.random() * people.length)];
messages.showTyping({
header: person.name + ' is typing',
avatar: person.avatar
});
setTimeout(function () {
messages.addMessage({
text: answer,
type: 'received',
name: person.name,
avatar: person.avatar
});
messages.hideTyping();
responseInProgress = false;
}, 4000);
}, 1000);
}
$on('pageInit', () => {
messagebar = $f7.messagebar.create({
el: $el.value.find('.messagebar'),
attachments: []
});
messages = $f7.messages.create({
el: $el.value.find('.messages'),
firstMessageRule: function (message, previousMessage, nextMessage) {
if (message.isTitle) return false;
if (!previousMessage || previousMessage.type !== message.type || previousMessage.name !== message.name) return true;
return false;
},
lastMessageRule: function (message, previousMessage, nextMessage) {
if (message.isTitle) return false;
if (!nextMessage || nextMessage.type !== message.type || nextMessage.name !== message.name) return true;
return false;
},
tailMessageRule: function (message, previousMessage, nextMessage) {
if (message.isTitle) return false;
if (!nextMessage || nextMessage.type !== message.type || nextMessage.name !== message.name) return true;
return false;
}
});
})
$on('pageBeforeRemove', () => {
messagebar.destroy()
messages.destroy()
})
return $render;
};
</script>