I want to allow the editing of an ordered list using contenteditable.
When the user changes or adds in a new element in the list I want to be able to manipulate the text (e.g. wrap in span tag, replace text etc).
I've created a listener for the Enter key and can get the last list element value.
I've tried to change this and replace with the new value. However this populates the new list element created on the enter press.
<div>
<ol contenteditable=true class="editor">
<li><br></li>
</ol>
</div>
$('.editor' ).on('keydown .editable', function(e){
if ( e.keyCode === 13 ) {
var insertText = "<span>"+e.target.lastElementChild.innerText+"</span>";
e.target.lastElementChild.innerHTML = insertText;
return true
}
});
What is the best way to implement this functionality for new entries anywhere in the list not just the end? Open to Jquery solutions
example jsfiddle
You could use a MutationObserver to detect when a child is added to your list, and then update the previousSibling to wrap it in a <span>:
function subscriber(mutations) {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
const prev = node.previousSibling;
if (prev) {
prev.innerHTML = `<span>${prev.innerHTML.replace(/<br>$/, '')}</span>`;
}
});
});
}
const observer = new MutationObserver(subscriber);
observer.observe(document.querySelector('ol[contenteditable]'), { childList: true });
.editor span::after {
content: '😀';
}
<ol contenteditable class="editor">
<li>First li</li>
</ol>
You could bind your logic to the last li, and perform your logic from the events it emits.
$('.editor .last-item')
.on('click', function(e){
// clear out the html so the user can just type
e.target.innerHTML = '';
})
.on('keydown', function(e){
if (e.keyCode === 13) {
// ignore the enter key so it doesn't insert a new like
e.preventDefault();
// create the new li before the last one
$('<li>'+ e.target.innerHTML.trim() +'</li>').insertBefore(e.target);
// clear out the html so the user can keep entering items
e.target.innerHTML = '';
}
})
.on('blur', function(e){
// if the user leaves the field, change it back to the instructional text
e.target.innerHTML = 'Click to enter New List Item';
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
<ol class="editor">
<li class="last-item" contenteditable=true>Click to enter New List Item</li>
</ol>
</div>
Instead of taking action when an edit happens, you could set an interval that will modify the html of the li elements as desired.
setInterval(function(){
$('.editor' ).find('li').each(function(){
if ($(this).html().indexOf('span')==-1){
$(this).html('<span>' + $(this).html() + '</span>');
}
});
}, 200);
hello you might wanna check this let me give you a heads up. instead of using the
lastchild.innerHTML
replace it with
nextSibling.innerHTML
like this
$('.editor' ).on('keydown .editable', function(e){
if ( e.keyCode === 13 ) {
var insertText = "<span>"+e.target.lastElementChild.innerText+"</span>";
e.target.nextSibling.innerHTML = insertText;
return true
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
<ol contenteditable=true class="editor">
<li><br></li>
</ol>
</div>
$('.editor' ).on('keydown .editable', function(e){
if ( e.keyCode === 13 ) {
var insertText = "<span>"+e.target.lastElementChild.innerText+"</span>";
e.target.nextSibling.innerHTML = insertText;
return true
}
});
Related
I'm creating a Chrome Extension such that the user can create a todo list. I want to make it so that once a user types out a task, the user can press the "Enter" key to submit it. This will cause the task to go down to the next line. I'm having trouble allowing the user to make the task go onto the next line.
HTML:
<input type="text" name="newtask" value="" spellcheck="false" placeholder="New Task" id="newtask">
<ul id="tasksUL">
<li>test</li>
</ul>
Javascript:
$(() => {
$('#newtask').on('keydown', (e) => {
if(e.keyCode == 13){
???
}
});
});
Attach keypress eventlistener to input element, not to ul. If the key pressed is enter, get the content of input element, create new li element and set it's text with the inputted value, append to ul and then clear the input element.
$(() => {
$('input').on('keypress', function(e) {
if (e.keyCode == 13) {
const newTask = $(this).val();
if (newTask) {
$('#tasksUL').append(`<li>${newTask}</li>`);
$(this).val("");
}
}
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type="text" name="newtask" value="" spellcheck="false" placeholder="New Task" id="newtask">
<ul id="tasksUL">
<li>test</li>
</ul>
With vanilla javascript you just add an eventListener to the actual input element
listening for keydown events.
let tasks = document.getElementById('tasksUL');
document.getElementById('newtask').addEventListener('keydown', (e) => {
if(e.key == 'Enter'){
let new_item = document.createElement('li');
new_item.innerText = e.target.value;
tasks.appendChild(new_item);
}
});
I think you want your list to contain the text entered by user? You can use the jQuery append method to achieve that.
$('#newtask').keydown(function(e){
if (e.which == 13) {
//Construct some html to append:
var newLi = "<li>" + $('#newtask').val() + "</li>";
$('#tasksUL').append(newLi);
}
});
Try my JS Fiddle implementation
I am creating a form. I am trying to build a list which is dynamically populated with items representing valueless input fields from this form. I have created a simplified version in a fiddle to illustrate my issue. Essentially, I have the error list being created as I want. It successfully counts blank input fields and updates this count when data is entered or deleted and populates the list accordingly. However, to avoid duplicate entries in this list I am currently emptying it on each call. This seems fairly inefficient and, as you see on the fiddle, it runs it's empty and build functions immediately after a blur, so if an input is in focus and one attempts to click an error link, it takes a couple of clicks before it scrolls to the empty input in question. So my question is, is there another way I can avoid duplicate entries and it run fast enough so that it can fun the scroll function on click immediately from focus?
I hope this is clear enough. I added some white space to allow for a scroll effect so you can witness the multiple clicks required before the scroll occurs).
$(function(){
var submit = $('input[type="submit"]');
var monday = $("input[name='MONDAY']");
var tuesday = $("input[name='TUESDAY']");
var wednesday = $("input[name='WEDNESDAY']");
$('input').eq(0).focus();
// disable submit
submit.attr("disabled", true);
// call formValidate on load and on input blur
formValidate();
$('input').blur(function(){
formValidate();
});
function formValidate(){
// inputs
monday = $("input[name='MONDAY']");
tuesday = $("input[name='TUESDAY']");
wednesday = $("input[name='WEDNESDAY']");
// store values of inputs in vars
var inputCheck = {
'monday': monday.val(),
'tuesday': tuesday.val(),
'wednesday': wednesday.val()
};
var myErrors = [];
myErrors.splice(0,myErrors.length);
// if input has no value, add to error array
$.each( inputCheck, function(key, value){
if (value === '') {
myErrors.push(key);
}
});
// if any errors, create li for each
if (myErrors.length > 0 ) {
// display errors div
$('.errors').addClass('exist');
$('.errors').find('span').html(myErrors.length);
// empty ul to avoid duplicate entries
$('.error-box-item ul').empty();
// populate ul with any errors
$.each(myErrors, function(i) {
var li = $('<li>');
var link = myErrors[i].replace("-", " ");
var formattedLink = toTitleCase(link);
var tag = "#err-" + myErrors[i];
var a = $('<a></a>').attr("href", tag);
a.append(formattedLink);
li.append(a);
$('.error-box-item ul').append(li);
});
} else {
// if no errors, hide error info and
// enable the submit button
$('.errors').removeClass('exist');
submit.attr("disabled", false);
}
} // end validate form
function toTitleCase(str) {
return str.replace(/(?:^|\s)\w/g, function(match) {
return match.toUpperCase();
});
}
// scroll to link
$('a[href*="#"]:not([href="#"])').click(function() {
if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'') && location.hostname == this.hostname) {
var target = $(this.hash);
target = target.length ? target : $('[name=' + this.hash.slice(1) +']');
if (target.length) {
$('html, body').animate({
scrollTop: target.offset().top - 50
}, 500);
return false;
}
}
}); // end scroll
}); // end ready
.errors {
display: none;
}
.errors.exist {
display: block;
}
.demo-space {
height: 150px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form>
<p>Monday:</p>
<input id="err-monday" type="number" name="MONDAY">
<p>Tuesday:</p>
<input id="err-tuesday" type="number" name="TUESDAY">
<p>Wednesday:</p>
<input id="err-wednesday" type="number" name="WEDNESDAY">
<p>
<input type="submit" name="submit" value="submit">
</p>
</form>
<div class="demo-space"></div>
<div class="errors">
You have <span></span> errors:
<div class="error-box-item">
<ul class="list">
</ul>
</div>
</div>
<div class="demo-space"></div>
I have this code that is supposed to create a new list item (li) that is editable (contenteditable="true"):
var currenthtml = document.getElementById("0").innerHTML;
var newhtml = currenthtml + '<li class="object" contenteditable="true">...</li>';
The object is created and visible, the purehtml for #0 after editing is:
<div id="0">
<li class="object" contenteditable="true">First object</li>
</div>
To save the content, I have the following helper:
function SaveObject(html) {
localStorage.setItem("obj", html);
}
which is supposed to be called because of:
$('li[contenteditable]').keydown(function(e) {
if (e.keyCode === 13) {
document.execCommand('insertHTML', false, '');
$(this).blur();
var html = document.getElementById("0").innerHTML;
SaveObject(html);
return false;
}
});
(I'll change it to save on other cases, but this is the current saving method.)
but when I edit a new item and try to save, it is not saved the text. It stays there temporarily, but then changes to the default "...". How can I make this work?
The #0 is set this way
if(localStorage.getItem("obj") != null) {
document.getElementById("0").innerHTML = localStorage.getItem("obj");
}
Well, there's several steps you should check:
in your keydown handler, check the value of html:
$('li[contenteditable]').keydown(function(e) {
if (e.keyCode === 13) {
document.execCommand('insertHTML', false, '');
$(this).blur();
var html = document.getElementById("0").innerHTML;
console.log(html); // is the value equal to what is expected?
SaveObject(html);
return false;
}
});
Second, try to check if it is saved to local storage directly after you have saved:
$('li[contenteditable]').keydown(function(e) {
if (e.keyCode === 13) {
document.execCommand('insertHTML', false, '');
$(this).blur();
var html = document.getElementById("0").innerHTML;
SaveObject(html);
console.log(localStorage.getItem("obj")); // is the value equal to what was saved?
return false;
}
});
If the two answers are "yes", check out the value after reloading of your page: just type localStorage.getItem("obj") in your console.
Finally, if the data is still there, make sure that you actually add it back to DOM. You haven't shown us the code which adds list items with the saved data to the page.
i'm experimenting with Drag&Drop in Javascript. So far it is working but my editable Content within the dragable objects aren't useable anymore (hence not the way they normally are)
this is an example of an dropable object:
<div id="ziel_2" draggable="true" trvdroptarget="true">
<div> some text<br>some text</div>
<div contenteditable="true">some text</div>
</div>
the whole object shouldn't be dragged if i try to use the contenteditable div, i want to click in the text and edit it or just select some text in it ang drag it just like normal
so the question: how can i cancel the drag-event if e.target.hasAttribute("contenteditable") in ondragstart?
EDIT: this is the Code behind the Scenes so far:
function lieferungen_draggable_add_dragstart(obj)
{
obj.addEventListener('dragstart', function (e) {
if(e.target.hasAttribute("contenteditable")) { /* make something stop this thing */ }
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('Text', this.getAttribute('id'));
return false;
});
return obj;
}
EDIT2:
contenteditableDiv.addEventListener('mousedown', function() { this.parentNode.setAttribute("draggable", false); });
contenteditableDiv.addEventListener('mouseup', function() { this.parentNode.setAttribute("draggable", true); });
this worked for me based on an idea from https://stackoverflow.com/a/9339176/4232410
thanks for your help!
Check for the contentEditable status of the element and any parent elements (see the docs for info about the attribute)
for (var el = e.target; el && el !== el.parentNode; el = el.parentNode) {
if (el.contentEditable === "true") {
return false;
}
}
// Continue processing here
Try this:
if(e.target.hasAttribute("contenteditable")) { return false; }
Basically, that's saying get out and don't do anything else if the target has attribute: contenteditable
I'm working on a project for my JavaScript class, and I don't know how to edit this jQuery where when you select a tab, it will bring you to a new page. I try adding "a href" to the body, but it doesn't look right. Is there a piece of code I have to enter in the jQuery so when you choose "About" that it will bring you to the actual page? Here's the code:
jQuery
function handleEvent(e) {
var el = $(e.target);
if (e.type == "mouseover" || e.type == "mouseout") {
if (el.hasClass("tabStrip-tab") && !el.hasClass("tabStrip-tab-click")) {
el.toggleClass("tabStrip-tab-hover");
}
}
if (e.type == "click") {
if (el.hasClass("tabStrip-tab-hover")) {
var id = e.target.id;
var num = id.substr(id.lastIndexOf("-") + 1);
if (currentNum != num) {
deactivateTab();
el.toggleClass("tabStrip-tab-hover")
.toggleClass("tabStrip-tab-click");
showDescription(num);
currentNum = num;
}
}
}
}
function deactivateTab() {
var descEl = $("#tabStrip-desc-" + currentNum);
if (descEl.length > 0) {
descEl.remove();
$("#tabStrip-tab-" + currentNum).toggleClass("tabStrip-tab-click");
}
}
$(document).bind("click mouseover mouseout", handleEvent);
HTML
<div class="tabStrip">
<div id="tabStrip-tab-1" class="tabStrip-tab">Home</div>
<div id="tabStrip-tab-2" class="tabStrip-tab">About</div>
<div id="tabStrip-tab-3" class="tabStrip-tab">Contact</div>
<div id="tabStrip-tab-3" class="tabStrip-tab">Gallery</div>
</div>
add this to your handler if you need a new page..
window.open('url', 'window name', 'window settings');
or this if you want to redirect the actual view
window.location.href('url');
furthermore this should be a better choice:
$('div[id^=tabStrip-tab]').bind("click mouseover mouseout", handleEvent);
now only the 'tabStrip-*' id´s will trigger the events/handler
The best solution for your problem is to put hidden div with content for every tab you have.
All you have to do is display the current div depending which tag is selected. The another solution is using ajax and then you have a template for the content and you fill the template with the data you have received.