Bringing the Jquery cursor into focus following the addition of an emoji

Looking to add emojis in a contenteditable div but facing an issue with the cursor placement. Here is a DEMO created on codepen.io. The demo includes a tree emoji example where clicking on the emoji adds it to the #text contenteditable div. However, after adding the emoji, it appears behind the cursor emojis.

How can I make the added emoji appear in front of the cursor? Can anyone help me with this?

$(document).ready(function() {
  $("body").on("paste", "#text", function(e) {
    e.preventDefault();
  });
  $("body").on("keydown", "#text", function(event) {
    //$('.character').text($(this).text().length);
    if ($(this).text().length === 200 && event.keyCode != 8) {
      event.preventDefault();
    }
  });
  // Click to show clicked smiley
  $("body").on("click",".emoji-box", function(){
      var emojiKey = $(this).attr("data-smiley");
      var emojiURL = $(this).attr("data-url"); 
      $("#text").append("<img src="+emojiURL+" class='app-moji'>"); 
      $("#text").focus();
  });
});
html, body {
    width: 100%;
    height: 100%;
    padding: 0px;
    margin: 0px;
    font-family: 'Helvetica Neue', helvetica, arial, sans-serif;
    -moz-osx-font-smoothing: grayscale;
    -webkit-font-smoothing: antialiased;
    -ms-text-size-adjust: 100%;
    -webkit-texts-size-adjust: 100%;
    -webkit-backface-visibility: hidden;
}
.container {
  position:relative;
  width:100%;
  max-width:500px;
  margin:0px auto;
  padding-top:100px;
}
.input {
  position:relative;
  width:100%;
  display:inline-block;
  padding:5px 0px;
}
#text {
  width:100%;
  outline:none;
  border:1px solid #d8dbdf;
  padding:15px;
  border-radius:3px;
  -webkit-border-radius:3px;
  font-weight:300;
  font-size:14px;
  color:#444;  
  line-height: 17px;
  word-wrap: break-word; 
  text-align: left !important;
   
}
#text img { 
  width:25px;
  height:25px;
  vertical-align: middle; 
}
.app-moji {
  margin-left:5px;
  margin-right:5px;
  display: inline-block;
}
[contenteditable=true]:empty:before {
  content: attr(placeholder);
  display: block; /* For Firefox */
  color:#d8dbdf;
}
.button {
  float:right;
  width:100px;
  padding:8px;
  text-align:center;
  background-color:#d8dbdf;
  border-raduis:3px;
  -webkit-border-radius:3px;
  cursor:pointer;
   transition: background-color 1s linear;
    -moz-transition: background-color 1s linear;
    -o-transition: background-color 1s linear;
    -webkit-transition: background-color 1s linear;
}
.button:hover {
  background-color:#000;
  color:#fff;
     
}
div.coloranimation {
  transition: background-color 1s linear;
    -moz-transition: background-color 1s linear;
    -o-transition: background-color 1s linear;
    -webkit-transition: background-color 1s linear;
  background-color:#000000;
}

.ornekgoster {
  position:relative;
  width:100%;
  padding:10px;
}
.ornek {
  position:relative;
  width:100%;
  font-weight:300;
  font-size:13px;
  color:#444;
} 

*{
  box-sizing:border-box;
  -webkit-box-sizing:border-box;
}

.ap-emoji {
  position: relative;
  float: left;
  width: 100%;
  padding-right: 10px;
   overflow:hidden;
 margin-top:20px;
}

.emoji-box {
   position:relative;
   width:29px;
   height:29px;
   float:left;  
   padding:2px;
   cursor:pointer;
}
.emoji-box img {
   width:100%;
   position:relative;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container">
  <div class="input">
    <div id="text" placeholder="Write something..." contenteditable="true"></div>
    <input type="hidden" id="hidden" value=""/>
  </div> 
  <div class="ap-emoji" id="emoji1">
    <div class="emoji-box" data-smiley=":P" data-url="https://cdn.shopify.com/s/files/1/1061/1924/products/Hungry_Emoji_Icon_c20f1808-f3e2-4051-8941-3d157764e8cb.png"><img src="https://cdn.shopify.com/s/files/1/1061/1924/products/Hungry_Emoji_Icon_c20f1808-f3e2-4051-8941-3d157764e8cb.png"></div>
     <div class="emoji-box" data-smiley=":D" data-url="http://www.bigmouthdesign.co.uk/wp-content/uploads/2017/08/Happy-Face.png"><img src="http://www.bigmouthdesign.co.uk/wp-content/uploads/2017/08/Happy-Face.png"></div>
     <div class="emoji-box" data-smiley="<3" data-url="http://clipart.info/images/ccovers/1496184263Heart-Eyes-Emoji-png-transparent-2.png"><img src="http://clipart.info/images/ccovers/1496184263Heart-Eyes-Emoji-png-transparent-2.png"></div>
    </div>
</div>

Answer №1

Utilize the Selection object in this manner

$(document).ready(function() {
  $("body").on("paste", "#text", function(e) {
    e.preventDefault();
  });
  $("body").on("keydown", "#text", function(event) {
    //$('.character').text($(this).text().length);
    if ($(this).text().length === 200 && event.keyCode != 8) {
      event.preventDefault();
    }
  });
  // Click to show clicked smiley
  $("body").on("click", ".emoji-box", function() {
    var emojiKey = $(this).attr("data-smiley");
    var emojiURL = $(this).attr("data-url");
    $("#text").append("<img src=" + emojiURL + " class='app-moji'>");
    $("#text").focus();
    var el = document.getElementById("text");
    placeCaretAtEnd(el);

  });
});

function placeCaretAtEnd(el) {
  el.focus();
  if (typeof window.getSelection != "undefined" &&
    typeof document.createRange != "undefined") {
    var range = document.createRange();
    range.selectNodeContents(el);
    range.collapse(false);
    var sel = window.getSelection();
    sel.removeAllRanges();
    sel.addRange(range);
  } else if (typeof document.body.createTextRange != "undefined") {
    var textRange = document.body.createTextRange();
    textRange.moveToElementText(el);
    textRange.collapse(false);
    textRange.select();
  }
}
html,
body {
  width: 100%;
  height: 100%;
  padding: 0px;
  margin: 0px;
  font-family: 'Helvetica Neue', helvetica, arial, sans-serif;
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
  -ms-text-size-adjust: 100%;
  -webkit-texts-size-adjust: 100%;
  -webkit-backface-visibility: hidden;
}

.container {
  position: relative;
  width: 100%;
  max-width: 500px;
  margin: 0px auto;
  padding-top: 100px;
}

.input {
  position: relative;
  width: 100%;
  display: inline-block;
  padding: 5px 0px;
}

#text {
  width: 100%;
  outline: none;
  border: 1px solid #d8dbdf;
  padding: 15px;
  border-radius: 3px;
  -webkit-border-radius: 3px;
  font-weight: 300;
  font-size: 14px;
  color: #444;
  line-height: 17px;
  word-wrap: break-word;
  text-align: left !important;
}

#text img {
  width: 25px;
  height: 25px;
  vertical-align: middle;
}

.app-moji {
  margin-left: 5px;
  margin-right: 5px;
  display: inline-block;
}

[contenteditable=true]:empty:before {
  content: attr(placeholder);
  display: block;
  /* For Firefox */
  color: #d8dbdf;
}

.button {
  float: right;
  width: 100px;
  padding: 8px;
  text-align: center;
  background-color: #d8dbdf;
  border-raduis: 3px;
  -webkit-border-radius: 3px;
  cursor: pointer;
  transition: background-color 1s linear;
  -moz-transition: background-color 1s linear;
  -o-transition: background-color 1s linear;
  -webkit-transition: background-color 1s linear;
}

.button:hover {
  background-color: #000;
  color: #fff;
}

div.coloranimation {
  transition: background-color 1s linear;
  -moz-transition: background-color 1s linear;
  -o-transition: background-color 1s linear;
  -webkit-transition: background-color 1s linear;
  background-color: #000000;
}

.ornekgoster {
  position: relative;
  width: 100%;
  padding: 10px;
}

.ornek {
  position: relative;
  width: 100%;
  font-weight: 300;
  font-size: 13px;
  color: #444;
}

* {
  box-sizing: border-box;
  -webkit-box-sizing: border-box;
}

.ap-emoji {
  position: relative;
  float: left;
  width: 100%;
  padding-right: 10px;
  overflow: hidden;
  margin-top: 20px;
}

.emoji-box {
  position: relative;
  width: 29px;
  height: 29px;
  float: left;
  padding: 2px;
  cursor: pointer;
}

.emoji-box img {
  width: 100%;
  position: relative;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container">
  <div class="input">
    <div id="text" placeholder="Enter your text here..." contenteditable="true"></div>
    <input type="hidden" id="hidden" value="" id="txt" />
  </div>

  <div class="ap-emoji" id="emoji1">
    <div class="emoji-box" data-smiley=":P" data-url="https://cdn.shopify.com/s/files/1/1061/1924/products/Hungry_Emoji_Icon_c20f1808-f3e2-4051-8941-3d157764...
    <div class="emoji-box" data-smiley=":D" data-url="http://www.bigmouthdesign.co.uk/wp-content/uploads/2017/08/Happy-Face.png"><img src="http://www....
    <div class="emoji-box" data-smiley="<3" data-url="http://clipart.info/images/ccovers/1496184263Heart-Eyes-Emoji-png-transparent-2.png"><img ...
  </div>
</div>

Answer №2

If you're looking for a way to track the updated caret position after insertions or deletions, it's important to consider that users can manually adjust the caret position even amidst inserted emojis. One effective approach is to dynamically insert HTML content at a specified caret position.

This method can also be extended to handle pasting of HTML content. Simply remove the event.preventDefault() from your paste event listener on the document body.

When dealing with contentEditable elements, it's beneficial to explore solutions provided by respected individuals like TimDown on Stack Overflow. Given the complexity involved in working with ContentEditable, there may be further requirements down the line. Utilizing a robust standard library can significantly streamline your development process and save time.

Take a look at the code snippet below for practical implementation:

$(document).ready(function() {
  $("body").on("paste", "#text", function(e) {
    e.preventDefault();
  });
  $("body").on("keydown", "#text", function(event) {
    if ($(this).text().length === 200 && event.keyCode != 8) {
      event.preventDefault();
    }
  });
  
  $("body").on("click",".emoji-box", function(){
      var emojiKey = $(this).attr("data-smiley");
      var emojiURL = $(this).attr("data-url"); 
      insertHTMLAtCaret("<img src="+emojiURL+" class='app-moji'>")
  });
  
  function insertHTMLAtCaret(html) {
    // Implementation goes here
  }
});
... (CSS and HTML code snippets omitted for brevity)

Answer №3

execCommand()

To easily bind text editor functions to buttons and target specific caret positions or selected areas, the execCommand() method can be utilized.

In this demo:

The callback function allows for inserting emojis, bold text, and italicized text either at the caret position or over a selected area. Emojis are represented in decimal format using:

&#CODE;

This format will directly render as HTML. For more emoji options, visit amp-what.com.


Demo

const edit = document.forms.editor;

const editText = event => {
  const evt = event.type;
  const tgt = event.target;
  const cur = event.currentTarget;
  const ui = cur.elements;

  if (evt === 'click') {
    switch (tgt.id) {
      case 'emoji':
        let icon = ui.picto.value;
        document.execCommand('insertHTML', false, icon);
        break;
      case 'bold':
        document.execCommand('bold', false, null);
        break;
      case 'italic':
        document.execCommand('italic', false, null);
        break;
      default:
        break;
    }
  }
};

edit.onclick = editText;
button,
select {
  display: inline-block;
  width: 1.75rem;
  height: 1.5rem;
  line-height: 1.5rem;
  padding: 0;
  font: inherit;
  cursor: pointer;
  text-align: center;
  vertical-align: middle;
}

select {
  width: 3.25rem;
  text-align: left;
}
<!DOCTYPE html>
<html>

<head>
  <meta charset='utf-8'>
  <style>
    html,
    body {
      font: 300 2ch/1.2 'Times';
      color: #333;
    }

    #text {
      outline: none;
      margin: 10px;
      min-height: 200px;
    }
  </style>
</head>

<body>
  <form id='editor'>
    <fieldset id="text" contenteditable="true"></fieldset>
    <fieldset id='panel'>
      <button id='bold' type='button'><b>B</b></button>
      <button id='italic' type='button'><i>I</i></button>
      <select id='picto'>
        <option value='&#11088;'>&#11088;</option>
        <option value='&#127921;'>&#127921;</option>
        <option value='&#128128;'>&#128128;</option>
        <option value='&#128163;'>&#128163;</option>
        <option value='&#129302;'>&#129302;</option>
      </select>
      <button id='emoji' type='button'>&#11088;</button>
    </fieldset>
  </form>
  <script>
    <!--JavaScript goes here-->
  </script>
</body>

</html>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Advancing in the Mozilla fashion

Having an issue with my progress bar design... When viewed in Chrome, the value of the progress bar appears in red due to CSS styling I applied... But when opened in Mozilla, it shows up as a dull grey color. progress[value] { width: 250px; height ...

Guide to comparing the contents of two text fields and highlighting the altered characters using ReactJS

Is there a way to compare the contents of two Material-UI textfields and identify the characters that have changed in both? I came across a similar question, but it was specifically for ReactJS rather than C# Windows Forms: How can you highlight the chara ...

Struggling to connect HTML with CSS in Hugo

I am experimenting with a pre-fabricated theme in Hugo and incorporating some Bootstrap codes. I want to change the color of all links when displayed or hovered over. Despite searching extensively, I have been unable to achieve this through the use of CSS ...

How to manage multiple controllers for the same template in AngularJS?

I am facing a requirement where a single page needs to display a lot of different data, all within one vertical-scrolling page. To manage this, I have implemented collapsible divs on the page controlled by ng-if to efficiently handle the DOM elements. In ...

How can we implement a select-all and deselect-all feature in Vue Element UI for a multi-select with filtering capability?

As a newcomer to VueJs, I am exploring how to create a component that enables multiple selection with a search option and the ability to select all or deselect all options. To achieve this functionality, I am utilizing the vue-element-ui library. You can ...

Infinite loop always occurs with Ui-router FromState being constantly reset

My page is experiencing continuous refreshing and calling $stateChangeStart after the first call to $state.go. I believe this issue may be related to the StateProvider configuration. Can anyone offer suggestions on what might be going wrong? Check out thi ...

Preserving the valuable AJAX response for future reference

My current task involves making AJAX requests within a JavaScript Interval function, specifically once every 10 seconds. The challenge I'm facing is figuring out how to store the response (text from the previous request) in a JavaScript variable. On ...

jQuery plugin is not effectively targeting the directive

Recently, I have been experimenting with using a sleek jQuery datepicker and decided to turn it into a directive for my angular app. The implementation of the directive is currently very straightforward: directive('datePicker', function() { ...

Is there a way to dynamically adjust @keyframes properties through JavaScript?

I am looking to dynamically change the top value of the keyframes based on a JavaScript variable x. While I have experience changing CSS with JavaScript, this particular challenge has me stumped. Code: var x = Math.floor((Math.random() * 1080) + 1); ...

What might be causing the 404 Error to appear on the mobile version of my website?

Currently, I am in the process of developing a static website using HTML, CSS, and Javascript. To add on-scroll animation effects, I have incorporated a plugin known as AOS. In addition, I have included Bootstrap in my project which was installed through n ...

Tips for concealing the body description on the homepage of a blogger website

I want to conceal this description This is my blog and I am facing an issue where I need to hide the description on the home page. I have tried using color:white which worked initially, but when I moved the description to the top or left, the black backgro ...

What are the various ways to display time zone in a different format?

I need to display the timezone in a unique manner. I have captured the user's timezone in a variable called timeZone, which currently returns the value as Asia/Calcutta. console.log(timeZone) // "Asia/Calcutta" Is there a way to showcase the timezon ...

Angular.js: automatically select default option based on ID

In my angular.js single page application, I am retrieving initial data from a rest endpoint. The data consists of a list of IDs representing saved models and a tree of options for cascading dropdowns. How can I automatically set the default path in the t ...

MUI Autocomplete causing a never-ending cycle of requests

One of the challenges I'm facing involves an Autocomplete component in my code. Here's a snippet of the code: <Autocomplete filterOptions={(x) => x} options={items} getOptionLabel= ...

Navigating through segments of an array in javascript

Within my condensed array, I have omitted most of the input data to demonstrate a particular task. For illustrative purposes, here is an extensive example showcasing the elements: storyArray=["#C1", "String showing first message", "String displaying secon ...

Ways to change specific CSS class properties in a unique style

As a Java programmer using primefaces 5.1, I've encountered some challenges with handling CSS classes. One particular issue I'm facing is the need to override certain CSS properties to remove the rounded corners of a DIV element. The rendered cod ...

Having trouble transmitting parameters through ajax

I have been attempting to use ajax to send variables to a php page and then display them in a div, but unfortunately, it's not functioning as expected. Below is the HTML code: <div id="center"> <form> ...

What is the process for creating custom event bindings in AngularJS?

There is a custom event called core-transitionend (specifically triggered by Polymer), and I am able to specify an event handler using document.addEventListener(). However, what would be the most recommended approach for achieving this in AngularJS? Alter ...

When you hover over nested HTML-lists in a webpage, make the tree's long text lines expand gracefully using a combination of HTML, CSS

In my Angular 4 application, I have a left div that displays a tree of items as recursive HTML lists. When there is a long text, it gets cut off by the border of the div and a scrollbar appears. I want to have the text expand beyond the border and show in ...

Is there a way in React JS to attempt a function multiple times using try/catch?

I wrote a function with try catch inside where I make a call to an API for a response. The response returns a Result object with some data. Occasionally, I need to retry the function if certain conditions are met, such as having no name but having a bundle ...