MUI - chat window

Posted by kee1108 on Thu, 20 Jan 2022 09:05:10 +0100

preface

Some time ago, MUI was used to make an APP, and the customer put forward ideas on whether to add a chat function to ensure the communication of all participating roles in the work order system.

OK, arrange!

Chat layout analysis

We need to implement a chat window, and we need to give priority to the composition of its layout.

Referring to QQ or wechat chat groups, we can see that they have the following common points:

  • 1. The group chat window needs an information display window.
  • 2. Messages sent by yourself are displayed on the right, and chat messages sent by others are displayed on the left.
  • 3. There needs to be an input box to trigger the soft keyboard, and a send button.
  • 4. Everyone has different information and needs to use token for judgment.

In addition, voice chat and animated expressions are not considered at present. Let's first realize a simple chat window and information display.

Page layout

In the interface template of MUI, there is an IM chat HTML chat page layout case, with reference to its functions, get rid of cumbersome other function options.

Message presentation template

In the case, there is a template class. When the message is sent by itself, the information will be displayed and displayed on the right side of the screen, otherwise it will be displayed on the left side of the screen.

<script id='msg-template' type="text/template">
	<!--Traversal message array object-->
	<% for(var i in record){ var item=record[i]; %>
	<!-- Get the attribute parameter information in the object, judge the generator of the current message, and save other data information at the same time -->
	<div class="msg-item <%= (item.sender=='self'?' msg-item-self':'') %>" msg-type='<%=(item.type)%>' msg-content='<%=(item.content)%>'>
		<!--Message time display-->
		<% if(item.time){ %>
		<div class="" style="text-align: center;">
			<span style="color: #C7C7CC;font-size: 12px;" id="chatTime">
				<%= item.time %>
			</span>
		</div>
		<% } %>

		<!--Chat content Avatar-->
		<% if(item.sender=='self' ) { %>
		<i class="msg-user">
			<span style="display: block;font-size: 10px;overflow:hidden;line-height: 30px;" >
				<%= item.username %>
			</span>
		</i>
		<% } else { %>
		<!--<img class="msg-user-img" src="../images/logo.png" alt="" />-->
		<i class="msg-user-img">
			<span style="display: block;font-size: 10px;overflow:hidden;line-height: 30px;">
				<%= item.username %>
			</span>
		</i>
		<% } %>

		<!--Chat content-->
		<div class="msg-content">
			<div class="msg-content-inner">
				<% if(item.type=='text' ) { %>
				<%=( item.content|| '&nbsp;&nbsp;') %>
				<% } %>
			</div>
			<!--div Chat content border layout-->
			<div class="msg-content-arrow"></div>
		</div>
		
		<!--There is a space between each chat message-->
		<div class="mui-item-clear"></div>
		
	</div>
	<% } %>
</script>

Message display and message input box, send button layout

<!--Intermediate content display-->
<div class="mui-content">
	<div id='msg-list'>
	</div>
</div>
<!--bottom-->
<footer>
	<!--picture-->
	<div class="footer-left">
		<!--Remove the photo function and keep the layout-->
	</div>
	<!--Input box-->
	<div class="footer-center">
		<textarea id='msg-text' type="text" class='input-text'></textarea>
	</div>
	<!--Icon click event is label label-->
	<label for="" class="footer-right">
		<i id='msg-type' class="mui-icon mui-icon-paperplane"></i>
	</label>
</footer>

Complete page layout and logical analysis

The ui intercepted above shows the layout considerations, and then shows the complete layout and other js operation logic.

<!doctype html>
<html>

	<head>
		<meta charset="UTF-8">
		<title></title>
		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
		<link href="../css/mui.min.css" rel="stylesheet" />
		<style type="text/css">
			html,
			body {
				height: 100%;
				margin: 0px;
				padding: 0px;
				overflow: hidden;
				-webkit-touch-callout: none;
				-webkit-user-select: none;
			}
			
			footer {
				position: fixed;
				width: 100%;
				height: 50px;
				min-height: 50px;
				border-top: solid 1px #bbb;
				left: 0px;
				bottom: 0px;
				overflow: hidden;
				padding: 0px 50px;
				background-color: #fafafa;
			}
			
			.footer-left {
				position: absolute;
				width: 50px;
				height: 50px;
				left: 0px;
				bottom: 0px;
				text-align: center;
				vertical-align: middle;
				line-height: 100%;
				padding: 12px 4px;
			}
			
			.footer-right {
				position: absolute;
				width: 50px;
				height: 50px;
				right: 0px;
				bottom: 0px;
				text-align: center;
				vertical-align: middle;
				line-height: 100%;
				padding: 12px 5px;
				display: inline-block;
			}
			
			.footer-center {
				height: 100%;
				padding: 5px 0px;
			}
			
			.footer-center [class*=input] {
				width: 100%;
				height: 100%;
				border-radius: 5px;
			}
			
			.footer-center .input-text {
				background: #fff;
				border: solid 1px #ddd;
				padding: 10px !important;
				font-size: 16px !important;
				line-height: 18px !important;
				font-family: verdana !important;
				overflow: hidden;
			}
			
			.footer-center .input-sound {
				background-color: #eee;
			}
			
			.mui-content {
				height: 100%;
				padding: 44px 0px 50px 0px;
				/*When there are many messages, add a scroll bar*/
				overflow: auto;
				background-color: #eaeaea;
			}
			
			#msg-list {
				height: 100%;
				overflow: auto;
				-webkit-overflow-scrolling: touch;
			}
			
			.msg-item {
				padding: 8px;
				clear: both;
			}
			
			.msg-item .mui-item-clear {
				clear: both;
			}
			/*.msg-item .msg-user {
				width: 38px;
				height: 38px;
				border: solid 1px #d3d3d3;
				display: inline-block;
				background: #fff;
				border-radius: 3px;
				vertical-align: top;
				text-align: center;*/
			/*Messages not sent by yourself float to the left*/
			/*float: left;
				padding: 3px;
				color: #ddd;
			}*/
			
			.msg-item .msg-user {
				width: 40px;
				height: 40px;
				border: solid 1px #d3d3d3;
				display: inline-block;
				background: #fff;
				border-radius: 4px;
				vertical-align: top;
				text-align: center;
				/*Messages not sent by yourself float to the left*/
				float: left;
				padding: 3px;
				background-color: #ddd;
			}
			
			.msg-item .msg-user-img {
				width: 40px;
				height: 40px;
				border: solid 1px #d3d3d3;
				display: inline-block;
				border-radius: 4px;
				vertical-align: top;
				text-align: center;
				float: left;
				padding: 3px;
				background-color: #ddd;
			}
			
			.msg-item .msg-content {
				display: inline-block;
				border-radius: 5px;
				border: solid 1px #d3d3d3;
				background-color: #FFFFFF;
				color: #333;
				padding: 8px;
				vertical-align: top;
				font-size: 15px;
				position: relative;
				margin: 0px 8px;
				max-width: 75%;
				min-width: 35px;
				float: left;
			}
			
			.msg-item .msg-content .msg-content-inner {
				overflow-x: hidden;
			}
			
			.msg-item .msg-content .msg-content-arrow {
				position: absolute;
				border: solid 1px #d3d3d3;
				border-right: none;
				border-top: none;
				background-color: #FFFFFF;
				width: 10px;
				height: 10px;
				left: -5px;
				top: 12px;
				-webkit-transform: rotateZ(45deg);
				transform: rotateZ(45deg);
			}
			/*Self sending caution, and finally float to the right*/
			
			.msg-item-self .msg-user,
			.msg-item-self .msg-content {
				float: right;
			}
			
			.msg-item-self .msg-content .msg-content-arrow {
				left: auto;
				right: -5px;
				-webkit-transform: rotateZ(225deg);
				transform: rotateZ(225deg);
			}
			
			.msg-item-self .msg-content,
			.msg-item-self .msg-content .msg-content-arrow {
				background-color: #4CD964;
				color: #fff;
				border-color: #2AC845;
			}
			
			footer .mui-icon {
				color: #000;
			}
			
			footer .mui-icon:active {
				color: #007AFF !important;
			}
			/*The pseudo class before indicates that the content content is placed before the class content*/
			
			footer .mui-icon-paperplane:before {
				content: "send out";
			}
			
			footer .mui-icon-paperplane {
				/*-webkit-transform: rotateZ(45deg);
				transform: rotateZ(45deg);*/
				font-size: 16px;
				word-break: keep-all;
				line-height: 100%;
				padding-top: 6px;
				color: rgba(0, 135, 250, 1);
			}
			/*voice*/
			
			#msg-sound {
				-webkit-user-select: none !important;
				user-select: none !important;
			}
			
			@-webkit-keyframes spin {
				0% {
					-webkit-transform: rotate(0deg);
				}
				100% {
					-webkit-transform: rotate(360deg);
				}
			}
			
			@keyframes spin {
				0% {
					transform: rotate(0deg);
				}
				100% {
					transform: rotate(360deg);
				}
			}
			
			#h {
				background: #fff;
				border: solid 1px #ddd;
				padding: 10px !important;
				font-size: 16px !important;
				font-family: verdana !important;
				line-height: 18px !important;
				overflow: visible;
				position: absolute;
				left: -1000px;
				right: 0px;
				word-break: break-all;
				word-wrap: break-word;
			}
			
			.cancel {
				background-color: darkred;
			}
		</style>
		<header class="mui-bar mui-bar-nav">
			<a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>
			<!--Name information of each role in the chat group(Super long use"..."replace)-->
			<h1 class="mui-title" id="headerText"></h1>
		</header>
	</head>

	<body contextmenu="return false;">
		<pre id='h'></pre>

		<script id='msg-template' type="text/template">
			<% for(var i in record){ var item=record[i]; %>
			<div class="msg-item <%= (item.sender=='self'?' msg-item-self':'') %>" msg-type='<%=(item.type)%>' msg-content='<%=(item.content)%>'>
				<!--Message time display-->
				<% if(item.time){ %>
				<div class="" style="text-align: center;">
					<span style="color: #C7C7CC;font-size: 12px;" id="chatTime">
						<%= item.time %>
					</span>
				</div>
				<% } %>

				<!--Chat content Avatar-->
				<% if(item.sender=='self' ) { %>
				<i class="msg-user">
					<span style="display: block;font-size: 10px;overflow:hidden;line-height: 30px;" >
						<%= item.username %>
					</span>
				</i>
				<% } else { %>
				<!--<img class="msg-user-img" src="../images/logo.png" alt="" />-->
				<i class="msg-user-img">
					<span style="display: block;font-size: 10px;overflow:hidden;line-height: 30px;">
						<%= item.username %>
					</span>
				</i>
				<% } %>

				<!--Chat content-->
				<div class="msg-content">
					<div class="msg-content-inner">
						<% if(item.type=='text' ) { %>
						<%=( item.content|| '&nbsp;&nbsp;') %>
						<% } %>
					</div>
					<!--div Chat content border layout-->
					<div class="msg-content-arrow"></div>
				</div>
				<!--There is a space between each chat message-->
				<div class="mui-item-clear"></div>
			</div>
			<% } %>
		</script>
		<!--Intermediate content display-->
		<div class="mui-content">
			<div id='msg-list'>
			</div>
		</div>
		<!--bottom-->
		<footer>
			<!--picture-->
			<div class="footer-left">
				<!--Remove the photo function and keep the layout-->
			</div>
			<!--Input box-->
			<div class="footer-center">
				<textarea id='msg-text' type="text" class='input-text'></textarea>
			</div>
			<!--Icon click event is label label-->
			<label for="" class="footer-right">
				<i id='msg-type' class="mui-icon mui-icon-paperplane"></i>
			</label>
		</footer>
		<script src="../js/apps.js" type="text/javascript" charset="utf-8"></script>
		<script src="../js/moment.min.js" type="text/javascript" charset="utf-8"></script>
		<script src="../js/mui.min.js"></script>
		<script src="../js/arttmpl.js"></script>
		<script type="text/javascript" charset="utf-8">
			(function($, doc) {
				$.init({
					//Gesture event
					gestureConfig: {
						tap: true, //The default is true
						doubletap: true, //The default is false
						longtap: true, //The default is false
						swipe: true, //The default is true
						drag: true, //The default is true
						hold: true, //The default value is false. Do not listen
						release: true //The default value is false. Do not listen
					}
				});
				
				//js/arttmpl.js must import the JS file
				template.config('escape', false);

				$.plusReady(function() {
					var self = plus.webview.currentWebview();
					var workId = self.workId;
					var workName = self.workName;
					
					//The title section of the chat box displays the task name
					if(typeof(workName) == "undefined") {
						mui.alert("Failed to get the task name. Please re-enter this page", 'reminder','determine',function(){},'div');
						return;
					}
					if(workName.length > 6) {
						workName = workName.substring(0, 5) + "...";
						console.log("Modify the task name after splicing:" + workName);
					}
					document.getElementById("headerText").innerText = workName;

					//Define a global array, and store the id in the array every time a message is received or sent successfully (avoid repeatedly requesting the server to obtain data information)
					var chatMsgIdArray = new Array();
					
					// Input box soft keyboard style
					plus.webview.currentWebview().setStyle({
						softinputMode: "adjustResize"
					});

					

					//The server requests message recording first
					getChatMsg(workId, "");
					//Scheduled refresh (the current maximum dialog id value is passed in each time)
					// In principle, it is better to use websocket technology here
					setInterval(function() {
						getChatMsg(workId, "");
					}, 3500);

					//Display keyboard
					// var showKeyboard = function() {
					// 	var Context = plus.android.importClass("android.content.Context");
					// 	var InputMethodManager = plus.android.importClass("android.view.inputmethod.InputMethodManager");
					// 	var main = plus.android.runtimeMainActivity();
					// 	var imm = main.getSystemService(Context.INPUT_METHOD_SERVICE);
					// 	imm.toggleSoftInput(0, InputMethodManager.SHOW_FORCED);
					// 	//var view = ((ViewGroup)main.findViewById(android.R.id.content)).getChildAt(0);
					// 	imm.showSoftInput(main.getWindow().getDecorView(), InputMethodManager.SHOW_IMPLICIT);
					// 	//alert("ll");
					// };
					
					//Record information (message object body, including message sender and sent content, message style, etc. at present, only text is considered)
					var record = [
						//Display format
						/*{
							sender: 'zs',
							type: 'text',
							content: 'Hi,I'm MUI housekeeper! "
						}*/
					];
					
					// It is a bit similar to object-oriented. Here, each node object is obtained, saved and encapsulated
					var ui = {
						body: doc.querySelector('body'),
						footer: doc.querySelector('footer'),
						footerRight: doc.querySelector('.footer-right'),
						btnMsgType: doc.querySelector('#msg-type'),
						boxMsgText: doc.querySelector('#msg-text'),
						areaMsgList: doc.querySelector('#msg-list'),
						h: doc.querySelector('#h'),
						content: doc.querySelector('.mui-content')
					};
					
					// Set segmentation for each message
					ui.h.style.width = ui.boxMsgText.offsetWidth + 'px';
					//alert(ui.boxMsgText.offsetWidth );--261
					
					var footerPadding = ui.footer.offsetHeight - ui.boxMsgText.offsetHeight;

					//Display the message content function and recalculate the scrollTop property value of the message display area
					var bindMsgList = function() {
						//First call template processing (avoid garbled code) to display the information in areaMsgList
						//MSG template the id of the script that traverses the loop
						ui.areaMsgList.innerHTML = template('msg-template', {
							"record": record
						});
						//The data should be displayed to the latest message after each transmission
						ui.areaMsgList.scrollTop = ui.areaMsgList.scrollHeight + ui.areaMsgList.offsetHeight;
					};
					
					// Call the function to display the content
					bindMsgList();
					
					//Monitoring when the interface window changes
					window.addEventListener('resize', function() {
						ui.areaMsgList.scrollTop = ui.areaMsgList.scrollHeight + ui.areaMsgList.offsetHeight;
					}, false);
					
					//Send message function
					var send = function(msg) {
						// The message object array holds the new message information
						record.push(msg);
						// Send the message and recalculate the scrollTop property
						bindMsgList();
						//Server communication (multi hot group chat, messages need to be delivered to the server)
						toRobot(msg.content);
					};
					
					//The server requests to send a chat message
					var toRobot = function(info) {
						//Send conversation
						console.log("Send conversation content:" + info);
						mui.ajax(config.host1 + "/work/workdialog/chat", {
							data: {
								workid: workId,
								content: info
							},
							dataType: "json",
							type: "post",
							timeout: 10000,
							headers: {
								'Content-Type': 'application/json',
								'authorization': localStorage.getItem('accessToken')
							},
							success: function(res) {
								console.log("Send successfully, return:" + JSON.stringify(res));
								// The retransmission mechanism should also be considered here
							},
							error: function(xhr, type, errorThrown) {
								console.log(type);
								if(type == "abort") {
									mui.toast("Please check your network!");
								} else if(type == "timeout") {
									mui.toast("Message sending timeout!");
								}
							}
						})
					};
					
					// Message input box received focus
					function msgTextFocus() {
						ui.boxMsgText.focus();
						// Delay double reception to ensure that the input box will get the focus
						setTimeout(function() {
							ui.boxMsgText.focus();
						}, 100);
					}
					
					//Solve the problem that the keyboard is closed due to long pressing the "send" button;
					ui.footerRight.addEventListener('touchstart', function(event) {
						if(ui.btnMsgType.classList.contains('mui-icon-paperplane')) {
							msgTextFocus();
							event.preventDefault();
						}
					});
					
					//Solve the problem that the keyboard is closed due to long pressing the "send" button;
					ui.footerRight.addEventListener('touchmove', function(event) {
						if(ui.btnMsgType.classList.contains('mui-icon-paperplane')) {
							msgTextFocus();
							event.preventDefault();
						}
					});

					//Release gesture listening event after long pressing send button
					ui.footerRight.addEventListener('release', function(event) {
						if(ui.btnMsgType.classList.contains('mui-icon-paperplane')) {
							//showKeyboard();
							ui.boxMsgText.focus();
							setTimeout(function() {
								ui.boxMsgText.focus();
							}, 150);
							//event.detail.gesture.preventDefault();
							
							// Get the login account user name information in the current cache
							var userName = localStorage.getItem('userName');
							if(userName.length >= 2){
								userName = userName.substring(0,2)+"...";
							}
							//The input of this machine will be displayed on the interface first
							send({
								sender: 'self',
								type: 'text',
								//g global matching i case sensitive m multiline matching
								content: ui.boxMsgText.value.replace(new RegExp('\n', 'gm'), '<br/>'),
								username: userName,
								time: moment().format('HH:mm:ss')
							});
							//Clear the data in the input box
							ui.boxMsgText.value = '';
							$.trigger(ui.boxMsgText, 'input', null);
						}
					}, false);

					//Monitor the event information when the content in the input box changes
					ui.boxMsgText.addEventListener('input', function(event) {
						//Original code
						//ui.btnMsgType.classList[ui.boxMsgText.value == '' ? 'remove' : 'add']('mui-icon-paperplane');
						//Change remove to add, and "pseudo class" will be displayed regardless of whether there is a value in the input tag -- that is, the word "send" will be displayed
						ui.btnMsgType.classList['add']('mui-icon-paperplane');
						ui.btnMsgType.setAttribute("for", ui.boxMsgText.value == '' ? '' : 'msg-text');
						ui.h.innerText = ui.boxMsgText.value.replace(new RegExp('\n', 'gm'), '\n-') || '-';
						ui.footer.style.height = (ui.h.offsetHeight + footerPadding) + 'px';
						console.log("Important parameters offsetHeight: " + ui.h.offsetHeight);
						console.log("Important parameters:" + footerPadding);
						ui.content.style.paddingBottom = ui.footer.style.height;
					});
					
					var focus = false;
					ui.boxMsgText.addEventListener('tap', function(event) {
						ui.boxMsgText.focus();
						setTimeout(function() {
							ui.boxMsgText.focus();
						}, 0);
						focus = true;
						setTimeout(function() {
							focus = false;
						}, 1000);
						event.detail.gesture.preventDefault();

					}, false);
					
					//Click the message list to close the keyboard
					ui.areaMsgList.addEventListener('click', function(event) {
						if(!focus) {
							// After sending, the message input box loses focus
							ui.boxMsgText.blur();
						}
					})

					/**
					 * Request server chat message record
					 * @param {Object} taskIDs  Task id
					 * @param {Object} dialogIDs  Last maximum conversation ID (it needs to be stored in the local database and read here every time)
					 */
					function getChatMsg(taskIDs, dialogIDs) {
						mui.ajax(config.host1 + "/work/workdialog/chat", {
							data: {
								workid: taskIDs
							},
							dataType: "json",
							type: "get",
							timeout: 10000,
							headers: {
								'Content-Type': 'application/x-www-form-urlencoded',
								'authorization': localStorage.getItem('accessToken')
							},
							success: function(res) {
								console.log(JSON.stringify(res));
								if(res.code != 200) {
									//Sending failed (subsequent processing)
									return;
								} 
								
								// Parsing data information
								var datas = res.data;
								// Traversal data information
								for (var i = 0; i < datas.length; i++) {
									var obj = datas[i];
									// Get message id information
									var msgId = obj.id;
									// Determine whether the message already exists in the array
									if(idWhetherInArray(msgId)){
										return;
									}
									// If it does not exist in the array, the id information is saved to the array
									chatMsgIdArray.push(msgId);
									
									// Get sender name
									var user_name = obj.user_name?obj.user_name:"Not yet";
									if(user_name.length >= 2){
										user_name = user_name.substring(0,2)+"...";
									}
									
									var content = obj.content;
									var create_time = obj.create_time;
									var nowDate = moment().format('YYYY-MM-DD');
									var serviceDate = moment.unix(create_time).utcOffset(8).format('YYYY-MM-DD');
									
									//Display the time today and the specific date and time in the future
                                    var times;
                                    if(nowDate == serviceDate) {
                                        times = moment.unix(create_time).utcOffset(8).format('HH:mm:ss');
                                    } else {
                                        times = moment.unix(create_time).utcOffset(8).format('YYYY-MM-DD  HH:mm:ss');
                                    }
                                    
                                    // Obtain the distinguishing identifier to judge whether the message is sent by yourself or by others
                                    var is_mine = obj.is_mine;
                                    if(1 == is_mine){
                                    	// Self sent messages
                                    	record.push({
                                            sender: 'self',
                                            type: 'text',
                                            content: content,
                                            username: user_name,
                                            time: times
                                        });
                                    }else{
                                    	// It is not sent by yourself and is displayed on the left side of the window
                                    	record.push({
                                            sender: 'zs',
                                            type: 'text',
                                            content: content,
                                            username: user_name,
                                            time: times
                                        });
                                    }
                                    
                                    //Without this, it will not be displayed (put it in the judgment -- to avoid that each successful callback will make the layout recalculate, so that the historical message cannot be viewed upward)
                                    bindMsgList();
								}
							},
							error: function(xhr, type, errorThrown) {

							}
						})
					}

					//Determine whether the id in the ajax callback has the same value in the array
					function idWhetherInArray(id) {
						var isExist = false;
						for(var i = 0; i < chatMsgIdArray.length; i++) {
							if(chatMsgIdArray[i] == id) {
								isExist = true;
								break;
							}
						}
						return isExist;
					}


				});
			}(mui, document));
		</script>
	</body>

</html>

matters needing attention

You must ensure that arttmpl JS file.

Get detailed Library

Right click the hbuiller editor to create an APP application and select the MUI style template.

Topics: Javascript Front-end html