I am currently trying to insert a string of text from a dropdown list into the Summernote text editor. However, upon insertion, the string closes any formatting tags, such as <'strong'>, <'p'>, and more.
Here is an example of the HTML after I have inserted the string "AND"
Bob AND I Loves Ice Cream and Cake. How about You?
<p><span style="font-size: 12px;"><b>Bob </b></span>AND <span style="font-size: 12px;">
<b>I </b></span><span style="font-size: 12px;">
<b>Loves Ice Cream and Cake. How about You?</b></span>
I need to be able to prevent the auto-closing and auto-opening of tags to the right and to the left of where the cursor was. I was looking into using the insertNode() and insertText() functions, but both of these end up doing the same thing.
Any ideas? Thanks!
EDIT: Here's a sample of the code that inserts at the cursor. I am currently extending Summernote's button with the UppercaseButton.
var UppercaseButton = function (context) {
var ui = $.summernote.ui;
// create button
var button = ui.button({
contents: '<i class="fa fa-child"/> Uppercase',
tooltip: 'Highlight a data attribute that you would like to make uppercase',
click: function () {
var range = $('#taBody').summernote('createRange');
var newText = range.toString();
console.log("newText: " + newText);
console.log(range);
context.invoke("editor.insertText", newText.toUpperCase());
}
});
return button.render(); // return button as jquery object
}
Here's my approach. This should grab the current node context the cursor is in and then append the desired text into that node
function insertAtCaret(text) {
// deleteContents() will replace any text that is currently highlighted, if any
let newRange = $("#taBody").summernote("getLastRange").deleteContents();
// Insert the desired text inside the formatted tag of the first part of the highlighted text. That way the formatting applies to the inserted text
newRange.sc.textContent = newRange.sc.textContent.substring(0, newRange.so) + text + newRange.sc.textContent.substring(newRange.so, newRange.sc.textContent.length);
// Update the cursor position to just after the inserted text
$("#taBody").summernote('setLastRange', $.summernote.range.create(/* start container */newRange.sc, /* start offset */ newRange.so + text.length).select());
}
var UppercaseButton = function (context) {
var ui = $.summernote.ui;
// create button
var button = ui.button({
contents: '<i class="fa fa-child"/> Uppercase',
tooltip: 'Highlight a data attribute that you would like to make uppercase',
click: function () {
insertAtCaret(newText.toUpperCase())
}
});
return button.render(); // return button as jquery object
}
Related
I have a scenario/dilemma, I am a little stuck trying to figure out. Below will be my best of a visual representation.
1. I select
This is dummy text to select
2. I bold and it gets wrapped with a span
This is dummy text to select
<h2 This is dummy <span style="font-weight: 700;">text to select</span></h2>
3. Now lets say I want to select some text but we know it's already wrapped in a span
This is dummy [BOLD]text[BOLD] to select
4. Its a crossover with selected text, so now my code. In my important comment, I can identify that the element is found in the selection. But how can I find it and then remove the span.
const el = document.createElement('span');
function handleNodeWrap(){
if(selection?.containsNode(el,true)){
//! IMPORTANT find and remove element of selected text
}
surroundSelection(el);
}
Here's one approach to remove the span from the selected text:
const el = document.createElement('span');
function handleNodeWrap() {
if (selection?.containsNode(el, true)) {
// find parent node of the selected text
let parentNode = selection.anchorNode.parentNode;
// create a new range that selects the entire content of the parent node
let range = document.createRange();
range.selectNodeContents(parentNode);
// replace the content of the parent node with the plain text version
let text = range.extractContents().textContent;
parentNode.textContent = text;
} else {
surroundSelection(el);
}
}
How to achieve this? In an HTML page, there is a p tag with some content (but only with indentations through and new line tags <br>).
I'd like to offer two buttons: one to convert the p tag into a text area so that the existing content can be adapted by the user, one to copy the content (if the content was not adapted, the original content should be copied, if adapted, certainly the adapted code should be copied).
I have prepared the code below so far. The final functionality should be (most of it not working/implemented in the snippet; the solution should be JS only):
Click Copy: copies the changed content or original content if not adapted
Click Adapt: turns the p tag into a text area, user can adapt the text
The text area should retain the original format: indentations and the line breaks but it should not display the HTML tags (e.g. <br>)
When the adapted content is copied after clicking Copy, the text area should disappear and the original content should be displayed again (the original p tag, not the text area), i.e. all the changes should be reverted
When clicking Adapt and then Adapt a second time, also all the changes should be reverted and the original p should be displayed again (otherwise, the text area would not vanish until page reload). Idea: When the Adapt button gets clicked, the name of the button should change to something like "Revert", and after clicking "Revert" the original p and the button "Adapt" could be displayed again.
How to position the Copy button in the first place and then the Adapt button right next to it? How to then reference to the p tag? It must work without a predefined ID.
Any further ideas? Thanks!
<button onclick="edit(this)">Adapt</button>
<p>some text 1<br> some text 2<br> some text 3 some text 4 && .... <br> ..... <br> ...... <br> ...... <br>...... <br><br></p>
<button title="Copy to clipboard" onclick="copy_text(this)">Copy</button>
<script>
function edit(item) {
var a = item.nextElementSibling;
a.outerHTML = a.outerHTML.replace(/<p>/g, '<textarea style="width: 100%; height: 100px ">').replace(/<\/p>/g, '</textarea>');
}
</script>
<script>
function copy_text(item) {
const str = item.previousElementSibling.innerText;
const el = document.createElement("textarea");
el.value = str;
el.setAttribute("readonly", "");
el.style.position = "absolute";
el.style.left = '-9999px';
document.body.appendChild(el);
el.select();
document.execCommand("copy");
document.body.removeChild(el);
};
</script>
Test it here in StackBlitz
I don't know how you intend finding the p element an id or a class or an attribute but for this answer I just used the tag.
Check this answer for why I used the Clipboard API
ReplaceWith MDN
let original = '';
const buttons = [...document.querySelectorAll('button')];
function edit(button) {
let paragraph = button.nextElementSibling.nextElementSibling;
button.innerText = paragraph.tagName == 'P' ? 'Revert' : 'Adapt';
if (paragraph.tagName == 'P') {
original = paragraph.innerHTML;
const textarea = Object.assign(document.createElement('textarea'), {
innerHTML: paragraph.innerText, // I use the innerText so that the HTML tags will be stripped but the space and new lines will be kept
style: 'width: 100%; height: 100px;' // Style for the textarea
});
paragraph.replaceWith(textarea); // Replace the p tag directly with the new textarea
} else {
const p = Object.assign(document.createElement('p'), {
innerHTML: original
});
paragraph.replaceWith(p);
}
}
function copy_text(button) {
let paragraph = button.nextElementSibling;
const str = paragraph.value || paragraph.innerText;
navigator.clipboard.writeText(str).then(_ => {
if (paragraph.tagName !== 'P') { // To convert the textarea back to the p
const p = Object.assign(document.createElement('p'), {
innerHTML: original // Convert the new lines back to br tag and the spaces to  
});
paragraph.replaceWith(p);
}
button.previousElementSibling.innerText = 'Adapt';
alert('Copied');
}, e => alert(`Failed: ${e}`));
};
// Event Listeners
buttons.forEach(button => button.addEventListener('click', e => {
const buttonType = button.innerText.toLowerCase();
if (['adapt', 'revert'].includes(buttonType)) edit(button);
else if (buttonType == 'copy') copy_text(button);
}));
<h1>Paragraph 1</h1>
<button>Adapt</button>
<button title="Copy to clipboard">Copy</button>
<p>some text 1<br> some text 2<br> some text 3 some text 4 && .... <br> ..... <br> ...... <br> ...... <br>...... <br><br></p>
<h1>Paragraph 2</h1>
<button>Adapt</button>
<button title="Copy to clipboard">Copy</button>
<p>some text 1<br> some text 2<br> some text 3 some text 4 && .... <br> ..... <br> ...... <br> ...... <br>...... <br><br></p>
I am fairly new to JS, and have created a little piece of script and it does exactly what I want which is find some elements then adds elements with data populated from via ajax....
So I go from this...
<select><select/>
to this...
<select>
<option value="{ajax value data}"> {ajax text data} <option/>
...
<select/>
using this piece of script...
filteredSelectIds.forEach(function (item) {
let itemId = '#' + item;
let itemData = item.split('-')[0] + 's';
$.each(data[itemData], function (i, color) {
$(itemId).append($('<option/>', {
value: color.optionValue,
text : color.optionText
}));
});
});
Now, what I am trying to do is at the same time add a Font Awesome icon to each element so I need to end up with something like this,,,,
<select>
<option value="{ajax value data}"><i class="fa fa-icon"> {ajax text data} <i/><option/>
...
<select/>
How would I do that??
I'm also new at JS, try this.
element = '<i class="fa fa-icon"> {0} <i/>'.format("{ajax text data}")
$('<option/>').append( element );
So #brk gave me this solution which worked, and would work for putting an Element inside another
"Create the option tag & i tag & first append itag to option tag and then append option tag to item"
filteredSelectIds.forEach(function (item) {
let itemId = '#' + item;
let itemData = item.split('-')[0] + 's';
$.each(data[itemData], function (i, color) {
var selOption = $('<option value="' + color.optionValue + '"></option>');
selOption.append('<i class="fa fa-icon">'+color.optionText+'<i/>');
$(itemId).append(selOption); }); });
However, although this placed the element inside the element as I wanted, and this could principle could probably be used to place any element within another, Tibrogargan correctly pointed to a question that makes the point that elements cannot be place within elements (Not really the Point of my question, but helpful). My solution was simply using the unicode for the Font Awesome icon and escaping it with \u then used \xa0 for additional spaces as follows:-
filteredSelectIds.forEach(function (item) {
let itemId = '#' + item;
let itemData = item.split('-')[0] + 's';
$.each(data[itemData], function (i, color) {
$(itemId).append($('<option/>', {
value: color.optionValue,
text : '\ue905 \xa0\xa0\xa0' +color.optionText
}));
});
});
Thanks!
Following uraimo's great response here I have managed to create a handler for making the text bold:
addHandler('bold', function (obj) {
var isBold = getStyle(obj, 'fontWeight') === 'bold';
setStyle(obj, 'textDecoration', isUnderline ? '' : 'underline');
});
It is attached to the button and it works fine whenever there is a portion of the text selected.
However, I need to also react on the button clicks when there is no text selected. Clicking a button in such case would result in bold text that is subsequently typed (just like hitting the "bold button" works in any text editor).
How can I do that?
You can bold text that is subsequently typed and also the one applied by setting the preperty of text
addHandler('bold', function (obj) {
var isBold = getStyle(obj, 'fontWeight') === 'bold';
var style = {};
style['fontwieight'] = isBold;
object.setSelectionStyles(style);
object.set('fontweight', isBold);
});
Suppose I have a div that contains a sentence such as the one listed below:
<div class="sentence">This is a sentence</div>
I am trying to create a function/event handler that will recognize when the user clicks on the text inside the <div>. But, when they click inside the <div> the function needs to know exactly where they clicked so I can insert a <span> at that location. For example, if the user clicks in between the letters 'n' and 't' in the word 'sentence', then I want to insert a <span> there so I end up with
<div class="sentence">This is a sen<span>test</span>tence</div>
Something like this:
$('.sentence').on('click', function (e) {
var to_insert = '<span>test</span>';
// determine exactly where inside the div I clicked
// insert 'to_insert' at that location
});
Any ideas?
You can to get the position of the click in a string, using window.getSelection().anchorOffset
JS
$('.sentence').on('click', function (e) {
var to_insert = 'test';
var anchorOffset = window.getSelection().anchorOffset;
var resultHtml = $(this).html().substring(0, anchorOffset) + to_insert + $(this).html().substring(anchorOffset);
$(this).html(resultHtml);
});
DEMO