Navigate the cursor beyond the span within the content that is editable

I am currently developing a tag input system for a template builder. My main focus right now is on assisting the editor in distinguishing between regular text and formatted text. I am structuring it similar to WordPress shortcodes, where a templated element would be enclosed in square brackets like [shortcode]. However, I am facing an issue where the cursor in my div includes any text typed after an inserted shortcode into the styled div as well.

$(function() {
  /**
   * Position tracking.
   */
  class PositionTracker {
    constructor(start, end) {
      this.start = start;
      this.end = end;
    }
  }

  /**
   * Variable insert selector
   * @type {jQuery|HTMLElement}
   */
  const $selector = $("#variableSelector");
  /**
   * Message content editor
   * @type {jQuery|HTMLElement}
   */
  const $body = $("#bodyContent");
  /**
   * Message Recorder
   * @type {jQuery|HTMLElement}
   */
  const $recorder = $('#bodyRecorder');


  /**
   * Position of cursor
   * @type {PositionTracker}
   */
  const position = new PositionTracker(0, 0);

  $selector.on('change', function() {
    let content = $body.html();
    let tag = $('<span>', {
      class: 'text-tag',
      text: $(this).val().toString()
    });
    $body.append(tag);
  });
  $body.on('input mousedown mouseup mouseout', function() {
    $recorder.val($(this).html);
    let selection = window.getSelection();
    position.start = selection.anchorOffset;
    position.end = selection.focusOffset;
  });

});
.text-editor {
  background: #fff;
  margin: 1rem 0;
  padding: 10px 8px;
  font: inherit;
  font-size: 1.15em;
  line-height: 1.35;
  color: #000;
  border: solid 1px rgba(6, 26, 45, 0.65);
  box-shadow: inset 2px 2px 6px rgba(4, 24, 39, 0.35);
}

.text-editor:focus {
  outline: none;
}

.text-editor[contenteditable=true]:empty:before {
  color: darkgray;
  content: attr(placeholder);
  pointer-events: none;
  display: block;
  /* For Firefox */
}

.text-editor.body {
  min-height: 170px;
}

.text-tag {
  background: rgba(0, 195, 255, 0.1);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form>
  <input name="content" type="hidden" id="contentRecoreder" required="required">
  <div id="bodyContent" contenteditable="true" class="content-body text-editor" placeholder="Hi [customer-name], this is [my-name] with [my-company]. We can do that job for just [quote-price]. If you have any questions, call or text us at [my-phone]"></div>

  <label>Variables</label>
  <select name="variables" id="variableSelector">
    <option value="empty">(choose one)</option>
    <option value="[customer-name]">Customer Name</option>
    <option value="[my-name]">My Name</option>
    <option value="[my-company]">My Company</option>
    <option value="[my-price" ]>Quote Price</option>
    <option value="[my-phone]">My Phone</option>
  </select>

  <button type="submit">Save</button>
</form>

Answer №1

One way to achieve this is by adding the contenteditable attribute to your span element and setting its value to false. Make sure to do this after populating it with your shortcode value.

Although this method doesn't physically move the cursor, it prevents any text entered after an inserted shortcode from appearing within the styled span. This ensures that the shotcode tags remain uneditable unless specifically targeted for editing.

For example:

$(function() {
  // Position tracking.
  class PositionTracker {
    constructor(start, end) {
      this.start = start;
      this.end = end;
    }
  }

  // Variable insert selector
  const $selector = $("#variableSelector");
  // Message content editor
  const $body = $("#bodyContent");
  // Message Recorder
  const $recorder = $('#bodyRecorder');

  // Cursor position
  const position = new PositionTracker(0, 0);

  $selector.on('change', function() {
    let content = $body.html();
    let tag = $('<span>', {
      class: 'text-tag',
      text: $(this).val().toString()
    });
    // Prevent editing of the span content
    tag.attr('contenteditable', false);
    $body.append(tag);
  });
  $body.on('input mousedown mouseup mouseout', function() {
    $recorder.val($(this).html);
    let selection = window.getSelection();
    position.start = selection.anchorOffset;
    position.end = selection.focusOffset;
  });

});
.text-editor {
  background: #fff;
  margin: 1rem 0;
  padding: 10px 8px;
  font: inherit;
  font-size: 1.15em;
  line-height: 1.35;
  color: #000;
  border: solid 1px rgba(6, 26, 45, 0.65);
  box-shadow: inset 2px 2px 6px rgba(4, 24, 39, 0.35);
}

.text-editor:focus {
  outline: none;
}

.text-editor[contenteditable=true]:empty:before {
  color: darkgray;
  content: attr(placeholder);
  pointer-events: none;
  display: block;
}

.text-editor.body {
  min-height: 170px;
}

.text-tag {
  background: rgba(0, 195, 255, 0.1);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form>
  <input name="content" type="hidden" id="contentRecoreder" required="required">
  <div id="bodyContent" contenteditable="true" class="content-body text-editor" placeholder="Hi [customer-name], this is [my-name] with [my-company]. We can do that job for just [quote-price]. If you have any questions, call or text us at [my-phone]"></div>

  <label>Variables</label>
  <select name="variables" id="variableSelector">
    <option value="empty">(choose one)</option>
    <option value="[customer-name]">Customer Name</option>
    <option value="[my-name]">My Name</option>
    <option value="[my-company]">My Company</option>
    <option value="[my-price" ]>Quote Price</option>
    <option value="[my-phone]">My Phone</option>
  </select>

  <button type="submit">Save</button>
</form>

Edit (see comments):

https://i.stack.imgur.com/WW3LG.jpg

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

A component with angular features will not persist

I have a menu component: <nav class="mxmls-mobile-nav"> <button class="mobile-menu-btn visible-xs visible-sm " ng-click="asideVm.open = false"> <i class="ion-android-close"></i> </button> </nav> <a class ...

Executing a command to modify the local storage (no need for an API request) using redux persist in a React Native environment

Currently, I am facing an issue where I am attempting to trigger an action in Redux sagas to assign an ID to a local store: import { call, takeEvery } from 'redux-saga/effects'; import { BENEFITS } from '../actions/types'; function* ...

Encountering installation issues with npm for bcrypt installation due to a

While working on an Express JS project, I encountered issues trying to install the bcrypt module for data authentication. Despite multiple attempts, I kept receiving the same errors. [email protected] install /media/iron/1d6c195f-2350-423c-a3f0-050 ...

Transferring JavaScript to PHP

I have a challenge where I need to pass a string stored in a variable to a MySQL table using PHP. Currently, my workaround involves using a hidden <input>. I assign the variable value to it and submit it through a form. It works, but it's not e ...

The ng-controller directive fails to function on the content of Kendo tabstrip tabs

My ng-controller is not functioning properly for the kendo tabstrip tab content. Could you please review my code below? <!--tabstripCtrl.js--> angular.module('tabstripApp',[]); var app = angular.module('tabstripApp'); app.con ...

How can I remove specific items from a session array when they are selected?

I am seeking a solution that will allow me to remove a specific item from an array when that item is clicked. For example, if I click on "Two", it should disappear. See a demonstration here: CODE: <form method="post"> <input type="text" i ...

Constructing a table in React using columns instead of the traditional rows

Currently tackling a project in react, I am looking to construct a material-ui table with specific characteristics. Within my array of elements, each element represents a column in the table and contains a name and the number of cells it covers. For examp ...

Is there a reason why the slide up feature is not working when I include the ul tag?

I need help with a jQuery code that will pull up/slide up an Html "p" tag when my page finishes loading. This snippet of jQuery code seems to be working fine: $(function () { $('.graybgc').slideUp(0); }); This is the HTML structure: <p ...

Library that supports Base64 encoding and decoding for both C and JavaScript environments

Are there any base 64 encoding and decoding libraries that are compatible with both C and JavaScript? Ideally, the library would have identical algorithms for both the encode and decode functions, allowing it to be used for desktop application clients (C++ ...

javascript conceal other sections upon hovering

There are 4 list items (<li>) that I want to use as triggers for linked images. In this project, I am using vanilla JavaScript as jQuery is not allowed. Below is the code snippet: var children = document.querySelectorAll('#resistorContent > ...

What is the best way to conceal a menu that automatically scrolls to the content when it is clicked?

Below is a Codepen with a menu that isn't behaving as expected. var menu = document.querySelector('.nav__list'); var burger = document.querySelector('.burger'); var doc = $(document); var l = $('.scrolly'); var panel = $ ...

Why do identical elements show different scrollHeights when overflowed and how can this discrepancy be resolved?

I am using a plugin that generates a <p> element and continuously fills it with the content from a <textarea>. This plugin positions the <p> directly below the <textarea>, and styles them so that they appear identical in terms of th ...

Why is it that my terminal doesn't close after my gulp process completes?

I am looking to implement React in my NodeJs application. Here is the content of my gulpfile: let gulp = require('gulp'); let uglify = require('gulp-uglify'); let browserify = require('browserify'); let babelify = require(& ...

Retrieve information from a form in AngularJS without relying on two-way data binding

Utilizing two-way data binding, the code operates effectively. However, there is a stipulation - instead of using =, use <. Upon the initial launch of the application, the text inputs will contain predefined values. The objective is to enable users to ...

Adding a div to a different webpage using jQuery

I am currently working on a small admin panel and I was curious if it is feasible to use jQuery to add a div box inside the index.php file after clicking a button in the panel? If this is possible, an example demonstrating how to do so would be greatly ap ...

Utilizing external functions in Node.js by importing them from different files

Struggling to import methods from my ./db/index.js into my server.js file in order to retrieve data from the database and show it. The content of /db/index.js is as follows: 'use strict'; const pgp = require('pg-promise')(); const pg ...

Step-by-step guide on triggering a button using another button

I have a unique question that sets it apart from others because I am looking to activate the button, not just fire it. Here is my syntax: $("#second_btn").click(function(){ $('#first_btn').addClass('active'); }) #first_btn ...

Associating data with controller upon click event

My application displays a tab full of objects for the user to choose from by clicking on any line. Once they make their selection, I need to send specific data related to that object to the server. This is what the interface looks like: The tab is create ...

A guide on how to assign a placeholder as the default value for a date picker

Currently using Vue3 with options API, and this question does not pertain to date formatting. Looking at the code provided on StackBlitz here, the default format for the date being initially set is dd.mm.yyyy. I am interested in knowing how to set the date ...

When two elements overlap, adjust their CSS positions to ensure responsiveness

Is there a way to adjust the position of an element when it comes into contact with another? Check out this example http://jsfiddle.net/mZYR8/1/ In the example, if .title-wrap (yellow) touches .right-wrap (orange), I want the orange element to slide unde ...