Creating an Interactive Button in Vanilla JavaScript: Learn how to generate a button that appears only after a user has selected text, and utilize that selected text for a specific action

I am currently working on a project that involves allowing users to upload .docx files and read them directly within the app, alongside files uploaded by other users. To achieve this, I have extracted text from the docx files and displayed it in a separate view and HTML page within a div. My goal now is to provide users with the option to select specific text from this div and display a button that hovers over the selected text, enabling them to easily add it to their notes. This functionality is similar to what MS Edge does when text is selected, where it automatically adds three dots to open a menu for actions such as copying. Another example is the 'Save Note' button on Coursera's website, which allows users to save selected text to their notes with just one click.

Example of Coursera's 'Save Note' button

Although I have an idea of using window.getSelection to capture the selection and send it via fetch to my server (built with Django) to add it to the Notes Model, I am unsure of how to implement this feature effectively. Specifically, I am looking for guidance on how to make a button appear over the selected text only when a selection is made. Any assistance or suggestions on implementing this behavior are greatly appreciated! Please note that I am aiming to accomplish this using Vanilla JavaScript to avoid the need for additional libraries or frameworks like React.

Answer №1

Dividing the issue into smaller tasks :

  1. Identify selection start and end points - This can be achieved using onmouseup and onmousedown correspondingly.
  • Utilize the event object accessible to the EventHandler to determine the coordinates of the selected area

  • When using onmousedown, save the starting coordinates of the selection

  • In case of onmouseup, obtain the ending coordinates of the selection

  • Utilize the starting and ending coordinates for positioning the copy button (using CSS)

  1. Retrieve the selected text - window.getSelection can retrieve the selected text upon clicking the button.

Upon detecting the selection and obtaining the selected text, it can be sent to the server.

Something similar to this :

const copyBtn = document.createElement("button");
copyBtn.innerHTML = "Press this button to log selected text";
copyBtn.style.position = "absolute";
copyBtn.style.display = "none";
copyBtn.onclick = function(evt) {
  console.log(window.getSelection().toString())
}
document.body.appendChild(copyBtn);

const paragraph = document.querySelector('p');
let startX = 0;
let startY = 0;

function mouseDown(evt) {
  console.log(`Selection started at : ${evt.clientX}, ${evt.clientY}`);
  startX = evt.clientX;
  startY = evt.clientY;

  copyBtn.style.display = "none";
}

function mouseUp(evt) {
  console.log(`Selection ended at : ${evt.clientX}, ${evt.clientY}`);

  copyBtn.style.display = "block";
  copyBtn.style.top = `${Math.min(startY, evt.clientY)}px`;
  copyBtn.style.left = `${(startX + evt.clientX) / 2}px`;

}
paragraph.onmouseup = mouseUp;
paragraph.onmousedown = mouseDown
<!DOCTYPE html>
<html>
<body>

<p>
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
</p>

</body>
</html>

Answer №2

In JavaScript, there exists a handy function that can be used to fetch the currently selected text:

function getSelectionText() {
    if (document.getSelection().toString().length === 0) {
//do nothing
    } else if (document.getSelection().toString().length > 0) {
        //trigger button function
    }
    return text;
}

It seems like there is no built-in function that triggers only when text is selected, so this particular function may need to be constantly activated or tied to a "mouseup" event in the document object model.

Answer №3

Having solved my own query for future reference, I want to express my gratitude to Tyler Durden and Endoxos for their input. After investing a few hours into experimentation, I have successfully crafted the code below which, for the most part, accomplishes what I set out to achieve (the code is also comprehensively explained within this response):

/* Custom function to dynamically add 'Save Note' button after text selection */
document.addEventListener('DOMContentLoaded', function() {
    /* Utilize this function within a div containing displayed content from files */
    const content = document.querySelector('#docContent');
    /* Create and insert button element */
    const noteBtn = document.createElement('button');
    noteBtn.innerHTML = 'Save Note';
    noteBtn.style.position = 'absolute';
    noteBtn.style.display = 'none';
    noteBtn.className = 'btn btn-sm btn-danger';

    content.appendChild(noteBtn);

    let startX = 0;
    let startY = 0;

    /* Record initial X and Y coordinates on mousedown event, relative to entire page rather than client viewport,
     ensuring that the button aligns with user's selection location irrespective of view position */
    content.addEventListener('mousedown', function(evt){
        startX = evt.pageX;
        startY = evt.pageY;
    });
    
    /* When mouse is released, verify end X and Y coordinates differ from start values
    before positioning the button at selection endpoint, where the user's cursor naturally lands.
    The functionality spans across all page and dom elements except extreme right selections,
    wherein adjustments or recalculations could be implemented to handle these scenarios. 
    If start and end positions match, it indicates user intent to hide the button, prompting its display toggle */
    content.addEventListener('mouseup', function(evt) {  
        if (evt.pageX != startX && evt.pageY != startY ) {
            noteBtn.style.top = `${evt.pageY}px`;
            noteBtn.style.left = `${evt.pageX}px`;
            noteBtn.style.display = 'block';
        } else {
            noteBtn.style.display = 'none';
        }
    });

    /* Incorporate button click listener to capture selected text and transmit to Django view.
    This example features csrf_exempt, a standard security practice for such operations */
    noteBtn.addEventListener('click', function() {
        const note = document.getSelection().toString();
        const id = content.querySelector('.reading_content_id').value;
        fetch(`/add_note/${id}`, {
            method: 'POST',
            body: JSON.stringify({
                note:`${note}`
            })
        }).then (function() {
            document.getSelection().collapseToEnd();
            noteBtn.style.display = 'none';
        });
    });
});

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

Saving functions in the localStorage API of HTML5: A step-by-step guide

I have encountered an issue while trying to store an array in local storage using JSON.stringify. The array contains functions (referred to as promises) within an object, but it seems that when I convert the array to a string, the functions are removed. Su ...

Is there a way to make the first Image Element within the div automatically clickable?

Is there a way to set the first image in a div as the default clicked element? I have a div with multiple images and I want the first one to be clicked by default. This is part of a React functional component that serves as a view. <div className=&quo ...

Finding the precise identifier of the selected input from a list with JQUERY

I need help with using jquery to correctly identify the button that I'm clicking. I have a list of dynamically generated categories with Remove buttons to delete them from the database. Here are my input examples: <input id="deletesector" class= ...

Tips for resolving the error message "Uncaught TypeError: Cannot read property '0' of undefined" in React rendering

When I try to map through my list in the render function, it doesn't work, even though it works fine within my component class DesktopList extends Component { constructor(props) { super(props); this.state = { i ...

The Angular @Input directive may be prone to receiving inaccurate model data

I am currently working on setting up @Input for my component using a model that resembles the following: interface Car { sail?: never tires: number weight: number } interface Boat { tires?: never sail: boolean weight: number } exp ...

Arranging Items in a JavaScript Array based on a Specific Attribute

I have devised a somewhat rudimentary method to achieve this task, but I thought it would be better to seek advice from the numerous experts here at SO. Essentially, I possess an array structured like the one below: var bugs = [ { id: "197526" ...

Extract information from an array using JavaScript

When working with highcharts, I need to generate parsed data to create series. The API data is structured like this: [ date : XX, series : [ player_id : 1, team_id : 1, score : 4 ], [ player_id ...

Can parameters be effectively passed to the $.change() method in any way?

I created a function that is triggered when the text in an input textbox changes - In my HTML file - <input id="unique" type="text" data-ng-change="KeywordChange(filterKey)" ng-model="$parent.filterKey"> In the controller - $scope.KeywordChange ...

Tips for showing images with the full path URL retrieved from JSON using AngularJS

I am currently working on a project that involves displaying images from a JSON file. The URLs of these images are stored in the JSON file. Currently, my code is only outputting the URLs themselves, which is expected. However, I am looking for a way to act ...

Error: Attempting to access the 'clipboard' property on an undefined object results in a TypeError when invoking this.$q.electron.clipboard

I'm currently working on incorporating copy to clipboard functionality into my app using Electron. This is the command I am using: methods: { copyToClipboard () { if (process.env.MODE === 'electron') { this.$q.electro ...

Aligning a text box horizontally with a label when they are in separate div elements

I need help aligning a series of text boxes with their corresponding labels. The challenge is that they are in separate divs, preventing the use of the inherit keyword. I attempted to align them using javascript/jquery code below, but $(tb).css('left& ...

"Switching a div's visibility when a link is clicked

http://jsfiddle.net/FsCHJ/2/ Currently, when I add another link, it automatically uses the same functionality as the toggle button. What I want is for "Toggle Edit Mode" to toggle a hidden div on and off. I attempted to modify the code from $("a").click(f ...

Maintain the state of various panels on page load with the Bootstrap Accordion feature

I have implemented a Bootstrap accordion on my form page which allows users to open multiple panels simultaneously. Upon submitting the form, I want to preserve the state of any open Bootstrap accordion panels. To achieve this, I utilized code from stack ...

"Creating Eye-Catching CSS Animation Effects with Dynamic Initial States

I am looking to understand how I can seamlessly transition from a hover-triggered animation to an exit-hover animation, regardless of where the hover animation is in progress. Currently, if I exit hover too quickly, the animation jumps back to its starting ...

3D spinning icosahedron adorned with circles at each corner using three.js

In my project, I am working with an interactive icosahedron mesh that undergoes rotation. As part of the animation loop, circle geometries are dynamically added, and their locations are set to each vertex of the mesh at every frame. geometry = new THREE.I ...

"Hidden panels in Sencha Touch only respond to show() and hide() methods after a resize event

Check out this demonstration of a Sencha Touch app by visiting this link. The button located in the bottom-left corner is supposed to show or hide the menu panel on top of the "Location info goes here" bar, but it seems to be functioning in an unexpected m ...

How does Elasticsearch accurately identify escaped or reserved characters?

As I have a string like 'message-ID: 1394.00 This is Henry.Lin', my goal is to utilize elasticsearch to locate all the words or phrases containing '.'. For this particular example, the words I am interested in are 1394.00 and Henry.Lin. ...

To navigate to the next page, simply click on the box instead of relying on text links

Here is a piece of code where you can click on the (account) text to open the next page. I am looking to modify this so that clicking on (.box.boxAccounts) will also take you to the next page, not just the text (account). The link_to function provides the ...

The onLayoutChange function keeps overwriting the data stored in my local storage

When my layout changes, the new changes are saved into local storage and the layout state is updated. onLayoutChange(layouts) { saveToLS("layouts", layouts); } However, an issue arises when the page is refreshed. The layout g ...

Unable to modify the variable within angular.js

I have been working on developing an Ionic application with angular.js and I am almost done, but I have encountered a minor issue. Inside my templates/menu.html file, I have the following code: <ion-item nav-clear menu-close ng-click="filterByPeriod(w ...