I have a two columned HTML table which I want to translate from English to Greek.
I desire to do that by double-clicking the Click to translate placeholder under the Greek column and then being prompted there, to insert a Greek text instead it.
In other words, I would interact with the HTML file to translate where the placeholder, right from web browser.
Any such (raw data) change should saved in the HTML file directly, without involving database programs such as a MySQL; thus, this program is aimed to be HTML-CSS-JS only.
table, th, td {
border: 1px solid black;
}
<!DOCTYPE html>
<html>
<body>
<section class="allEnglishSections">
<h2>Conditioning</h2>
<table>
<tr><th>English</th><th>Greek</th></tr>
<tr><td>if</td><td class="changeable">Click to translate</td></tr>
<tr><td>than</td><td class="changeable">Click to translate</td></tr>
<tr><td>else</td><td class="changeable">Click to translate</td></tr>
</table>
</section>
<script>
</script>
</body>
</html>
From searching data on how to achieve this, I understand I should use something similar to this pseudocode:
document.querySelectorAll(".changeable").addEventListener(ondblclick, function() {
myTranslation = prompt();
document.querySelector(".changeable").value = myTranslation;
}
But it is unclear to me how to actually selecting all text of a cell by double clicking it, and then changing it directly from web browser in such a way that the HTML file would change.
Is this even possible with latest ECMAScript and if so how will you achieve this?
An easy system would be to create a textarea inside the HTML on double click, and whenever you experience either the blur event or a specific key combination to remove the textarea and swapping it with a text node of it's own value.
Please note that I am added the events onto the tbody element. This is done because most events bubble up, and this way no matter how many <tr> elements get added in dynamically, I will have the same functionality without having to loop over the nodelist and add the event one-by-one.
I also use focusout instead of blur for a similar reason: blur does not bubble up, but focusout does.
Note The current version using the localStorage won't work on StackOverflow due to sandboxed permissions and so on. Copy/Paste the values into something like JSFiddle or Codepen to see a working example. I personally tested it on this JSFiddle in case you want to look at it.
I also did not use an example with AJAX/XHR as you seem relatively new to Javascript, and that needs for you to look up how REST APIs / XHR work. Feel free to make another question once you've done some fiddling with those, but for now that felt a bit out-of-scope for this question.
// get items from storage. if nothing is there originally we will get a null value
const fromStorage = localStorage.getItem('definitions');
// null values are falsey, so if it is null we need to use our default.
// otherwise we just JSON.parse the values we retrieved
const definitions = !fromStorage ? {
"if": null,
"when": null,
"tomato": null,
} : JSON.parse(fromStorage);
// I don't feel like running this command everytime I need to reference the tbody,
// thus, the reference to it here.
const tbody = document.querySelector('#definitions');
// adding an event listener on the tbody means that any of the same event that bubble up from it's children will reach here.
// This means you can add children in dynamically without needing to worry so long as they have
// a data-editorShown attribute
tbody.addEventListener('dblclick', e => {
const parent = e.target.parentElement;
// if somehow the event gets called from something that is NOT one of the TDs, we don't need to go any further from there.
// sidenote, all values in the Element.dataset are read as strings. you can set them to be whatever
// but when you read them, they will be strings.
if (parent.tagName !== 'TR' || parent.dataset.editorShown === 'true') return;
// The editor is now shown, so let's set that
parent.dataset.editorShown = true;
// get the last TD, which is where we will put our textarea
const dataTd = parent.querySelector('td:last-child');
// create a new textarea element
const textarea = document.createElement('textarea');
// the textarea gets some class
textarea.classList.add('full');
// and a placeholder
textarea.placeholder = "Double click to translate";
// the arguments for this function are backwards to me, but I think it's self explanatory what's happening
dataTd.replaceChild(textarea, dataTd.firstChild);
// get that focus on the textarea.
dataTd.firstChild.focus();
});
function blurOrKeypress(e) {
// split up largely for readability
if (e.target.tagName !== 'TEXTAREA') return false;
if (e.type === 'keypress' && e.code != 'Enter' && !e.ctrlKey) return false;
// a parent, a row, and a newly minted text node walk into a bar...
const parent = e.target.parentElement;
const row = parent.parentElement;
const text = document.createTextNode(e.target.value || 'Double click to translate');
/* .isConnected refers to it's state in the DOM. this was some work to try and stop an error that was
ocurring due to this being simultaneously the 'blur' 'keypress' event handler. Alas, it didn't.
If the error is really an issue, then wrapping the parent.replaceChild in a try/catch block should solve it for you.*/
if (e.target.isConnected) {
// use the dataset key + the textarea's value to update the definitions.
definitions[row.dataset.key] = e.target.value;
// write those to the local storage
localStorage.setItem('definitions', JSON.stringify(definitions));
// Or, if you are using a database, you would use some variety of AJAX/XHR call here.
// get rid of our text element
parent.replaceChild(text, e.target);
// reset the editorshown value in case we need to update this again
row.dataset.editorShown = false;
}
}
// the one thing I miss about jquery event listeners: adding multiple types of event by putting spaces
tbody.addEventListener('keypress', blurOrKeypress);
tbody.addEventListener('focusout', blurOrKeypress);
// gets the key/value pairs and maps them
tbody.append(...Object.entries(definitions).map(([word, translation]) => {
// table row, key TD and value TD cells.
const tr = document.createElement('tr');
const keyTd = document.createElement('td');
const valueTd = document.createElement('td');
// editor is not shown by default
tr.dataset.editorShown = false;
// we use this in an upper function.
tr.dataset.key = word;
// add the bold class just for visual interest. you do not need to do this.
keyTd.classList.add('bold');
// set the inner text to our word
keyTd.innerText = word;
// if it's already set, great! use that. otherwise, 'double click to translate'
valueTd.innerText = translation || 'Double click to translate';
// add these two values to our newly minted tr tag.
tr.append(keyTd, valueTd);
// return the TR tag so that the above tbody.append gets an element to actually append
return tr;
}));
/* Styling here to make myself feel better*/
textarea.full {
height: 1.5rem;
width: 100%;
padding: 3px;
line-height: 1.5rem;
}
.bold {
font-weight: bold;
}
td.bold {
font-style: unset;
}
table {
border-collapse: collapse;
width: 100%;
}
td {
color: #111;
width: 50%;
border: 1px solid lightgray;
box-sizing: border-box;
padding: 5px 10px;
border-width: 1px 0;
font-style: italic;
}
th {
border: 2px solid lightgray;
border-width: 0 0 2px;
padding-bottom: 6px;
text-decoration: underline;
font-style: unset;
font-size: 1.15rem;
}
tbody tr:nth-child(odd) {
background-color: lightgray;
}
<table>
<thead>
<tr>
<th>Word/Phrase</th>
<th>Translation</th>
</tr>
</thead>
<tbody id="definitions">
<!-- HTML generated later -->
</tbody>
</table>
I'll work on an example using contentEditable when I come back to comment this when I wake up. Hope it helps in the meantime.
Try using getElementById.
const if_in_greek = document.getElementById('translate-if');
const than_in_greek = document.getElementById('translate-than');
const else_in_greek = document.getElementById('translate-else');
if_in_greek.ondblclick = function() {
if_in_greek.innerHTML = "Translation of IF goes here"
}
than_in_greek.ondblclick = function() {
than_in_greek.innerHTML = "Translation of THAN goes here"
}
else_in_greek.ondblclick = function() {
else_in_greek.innerHTML = "Translation of ELSE goes here"
}
table,
th,
td {
border: 1px solid black;
}
<section class="allEnglishSections">
<h2>Conditioning</h2>
<table>
<tr>
<th>English</th>
<th>Greek</th>
</tr>
<tr>
<td>if</td>
<td id="translate-if">Click to translate</td>
</tr>
<tr>
<td>than</td>
<td id="translate-than">Click to translate</td>
</tr>
<tr>
<td>else</td>
<td id="translate-else">Click to translate</td>
</tr>
</table>
</section>
( UPDATED: See last section. )
One way of doing this, is to create an object which maps english words to greek words, and then add an eventlistener that catches the double click event on the table element, checks if the 'click to translate' element has been clicked and do the translation:
const dict = {
if: "αν",
than: "από",
else: "αλλιώς"
}
document.querySelector("table").addEventListener("dblclick", function(e){
// Check if the .changeable element has triggered the click event
if ( e.target.classList.contains("changeable")){
// Get the text content from the sibling element which contains the english word
const word = e.target.previousElementSibling.textContent;
// Check if we have the word in our dictionary
if ( dict[word] ){
// Change the text content of the 'click to translate' element with the greek word
e.target.textContent = dict[word];
}
}
});
table {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
<section class="allEnglishSections">
<h2>Conditioning</h2>
<table>
<tr><th>English</th><th>Greek</th></tr>
<tr><td>if</td><td class="changeable">Click to translate</td></tr>
<tr><td>than</td><td class="changeable">Click to translate</td></tr>
<tr><td>else</td><td class="changeable">Click to translate</td></tr>
</table>
</section>
Updated
Notes: As #Jhecht correcly pointed out, the querySelectorAll returns a NodeList that needs to be iterated. The easiest way to do that, is through the forEach method available to the NodeList object. The code would be written like this:
document.querySelectorAll(".changeable").forEach(function(element){
element.addEventListener( "dblclick", function(){ /* CODE HERE */ } );
});
If you see trying to attach multiple event listeners to a web page, you should reconsider your choice and instead attach a single event listener on a parent object which will be receiving all the events of its inner children. I applied this pattern in my solution.
Since the question has been updated, and the intentions are more clear, I am adding another code that enables the user to double click on the Click to translate area and allow them to enter a custom value:
document.querySelector("table").addEventListener("dblclick", function(e){
if ( e.target.classList.contains("changeable")){
e.target.setAttribute("contenteditable", true);
if ( e.target.textContent === "Click to translate" ){
e.target.textContent = "";
e.target.focus();
}
}
});
<section class="allEnglishSections">
<h2>Conditioning</h2>
<table>
<tr><th>English</th><th>Greek</th></tr>
<tr><td>if</td><td class="changeable">Click to translate</td></tr>
<tr><td>than</td><td class="changeable">Click to translate</td></tr>
<tr><td>else</td><td class="changeable">Click to translate</td></tr>
</table>
</section>
var $TABLE = $('#table');
var $BTN = $('#export-btn');
var $EXPORT = $('#export');
$('.table-add').click(function () {
var $clone = $TABLE.find('tr.hide').clone(true).removeClass('hide table-line');
$TABLE.find('table').append($clone);
});
$('.table-remove').click(function () {
$(this).parents('tr').detach();
});
$('.table-up').click(function () {
var $row = $(this).parents('tr');
if ($row.index() === 1) return; // Don't go above the header
$row.prev().before($row.get(0));
});
$('.table-down').click(function () {
var $row = $(this).parents('tr');
$row.next().after($row.get(0));
});
// A few jQuery helpers for exporting only
jQuery.fn.pop = [].pop;
jQuery.fn.shift = [].shift;
$BTN.click(function () {
var $rows = $TABLE.find('tr:not(:hidden)');
var headers = [];
var data = [];
// Get the headers (add special header logic here)
$($rows.shift()).find('th:not(:empty)').each(function () {
headers.push($(this).text().toLowerCase());
});
// Turn all existing rows into a loopable array
$rows.each(function () {
var $td = $(this).find('td');
var h = {};
// Use the headers from earlier to name our hash keys
headers.forEach(function (header, i) {
h[header] = $td.eq(i).text();
});
data.push(h);
});
// Output the result
$EXPORT.text(JSON.stringify(data));
});
.table-editable {
position: relative;
.glyphicon {
font-size: 20px;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.0/jquery.min.js"></script>
<div class="container">
<h1>HTML5 Editable Table</h1>
<p>Through the powers of <strong>contenteditable</strong> and some simple jQuery you can easily create a custom editable table. No need for a robust JavaScript library anymore these days.</p>
<ul>
<li>An editable table that exports a hash array. Dynamically compiles rows from headers</li>
<li>Simple / powerful features such as add row, remove row, move row up/down.</li>
</ul>
<div border="1" id="table" class="table-editable">
<span class="table-add glyphicon glyphicon-plus"></span>
<table style="border: 1px solid black;" class="table">
<tr>
<th>Name</th>
<th>Value</th>
<th></th>
<th></th>
</tr>
<tr>
<td contenteditable="true">Stir Fry</td>
<td contenteditable="true">stir-fry</td>
<td>
<span class="table-remove glyphicon glyphicon-remove"></span>
</td>
<td>
<span class="table-up glyphicon glyphicon-arrow-up"></span>
<span class="table-down glyphicon glyphicon-arrow-down"></span>
</td>
</tr>
<!-- This is our clonable table line -->
<tr class="hide">
<td contenteditable="true">Untitled</td>
<td contenteditable="true">undefined</td>
<td>
<span class="table-remove glyphicon glyphicon-remove"></span>
</td>
<td>
<span class="table-up glyphicon glyphicon-arrow-up"></span>
<span class="table-down glyphicon glyphicon-arrow-down"></span>
</td>
</tr>
</table>
</div>
<button id="export-btn" class="btn btn-primary">Export Data</button>
<p id="export"></p>
</div>
Related
PROBLEM 1:
I cannot get imported array variables to stay as the array changes throughout use. The slice method creates a new array to my understanding and when I use a function on a button to slice the array and change the table itself, I send the sliced element above and remove it from the table. On function use, the button changes to a second function that does the same, but sends the second sliced out element up top and slices another element from the table.
QUESTION 1:
When I slice the array the second time it changes the first initial sliced element.
TABLE AND SCRIPT FORMAT:
I have a Top area where values that are removed from the table and selected are sent above as it's removed.
<div class='top-stick'>
<button style='width:50px; height: 50px'>X</button>
<p id='selectOne' style='text-align: center; background-color: lightgreen; border: solid 3px black; padding: 5px'>First Perk Selected</p>
<p id='selectTwo' style='text-align: center; background-color: lightgreen; border: solid 3px black; padding: 5px'>
Second Perk Selected</p>
<p id='selectThree' style='text-align: center; background-color: lightgreen; border: solid 3px black; padding: 5px'>
Third Perk Selected </p>
<p id='percenttotal' style='text-align: center; background-color: lightgreen; border: solid 3px black; padding: 5px'>Craft %</p>
</div>
<div>
above is the is the top section that receives the values chosen by the button.
<table id='myTable' style='background-color: lightgreen; padding: 5px'>
<tr>
<th style='width: 20px'> Click Box to Select Perk</th>
<th style='width: 100px'>Perk</th>
<th>Effect</th>
<th class='percent' style='width: 40px'>Craft %</th>
</tr>
<tr>
<td style='background-color: black; width: 75px'>
<button id='myBtn' value='1'>Select</button>
<td id='perkOne'></td>
<td id='perkOneEffect'></td>
<td id='perkOnePercent'></td>
</tr>
<tr>
<td style='background-color: black'>
<button id='myBtn'> Select </button>
<td id='perkTwo'></td>
<td id='perkTwoEffect'></td>
<td id='perkTwoPercent'></td>
</tr>
<tr>
<td style='background-color: black'>
<button id='myBtn'> Select </button>
<td id='perkThree'></td>
<td id='perkThreeEffect'></td>
<td id='perkThreePercent'></td>
</tr>
</table>
this is the table but it goes all the way up to 26 rows.
below is the function that imports all the variables into the empty table by Id before any buttons are pressed.
function importBefore() {
document.getElementById("perkOne").innerHTML = perkName[0];
document.getElementById('perkTwo').innerHTML = perkName[1];
document.getElementById('perkThree').innerHTML = perkName[2];
document.getElementById('perkFour').innerHTML = perkName[3];
document.getElementById('perkFive').innerHTML = perkName[4];
document.getElementById('perkSix').innerHTML = perkName[5];
document.getElementById('perkSeven').innerHTML = perkName[6];
document.getElementById('perkEight').innerHTML = perkName[7];
document.getElementById('perkNine').innerHTML = perkName[8];
document.getElementById('perkTen').innerHTML = perkName[9];
document.getElementById('perkEleven').innerHTML = perkName[10];
This is the button function that slices an array and modifies the display of the table while sending the removed array value to the above portion.
function first() {
var perkNameOne = perkName.slice(0, 26)
document.getElementById('selectOne').innerHTML = perkNameOne[0];
perkName = perkName.slice(1, 26);
perkEffect = perkEffect.slice(1, 26);
perkPercent = perkPercent.slice(1, 26);
document.getElementById('selectedFirst').innerHTML = null;
importFirst();
document.getElementById("myBtn").addEventListener("click", function() {
second();
});
}
I import the table itself then use a button to slice and import new values then change the button function at the end of each button press with a reset button at the top left.
The problem I am having is that
var perkNameOne = perkName.slice(0, 26)
document.getElementById('selectOne').innerHTML = perkNameOne[0];
this value changes with each button press that goes along even though
perkName = perkName.slice(1, 26);
I never slice perkNameOne again. To my understanding, slicing creates a new array...? I may need to remake the table and simplify it with an automatic createElement and appendChild method that creates rows based on length of array.
perkNameOne[0] changes to the newly sliced array further down the strip.
PROBLEM 2:
I also can't change perkName[0] to
let n = "";
for (let i = document.getElementById(myBtn).value; i = perkName; i) {
n = perkName[i];
or however that coding line goes. so I can write the button as-
<button id='myBtn' value='0'>Select</button>
<button id='myBtn' value='1'>Select</button>
function slice(a) {
return slice(a);
}
function first(document.getElementById('myBtn').this.value) {
var perkNameOne = perkName.slice(a, 26)
document.getElementById('selectOne').innerHTML = perkNameOne[0];
perkName = perkName.slice(a, 26);
perkEffect = perkEffect.slice(a, 26);
perkPercent = perkPercent.slice(a, 26);
document.getElementById('selectedFirst').innerHTML = null;
importFirst();
document.getElementById("myBtn").addEventListener("click", function() {
second();
});
}
and use the value of each line in itself instead of having 26 different functions on each button.
QUESTION FOR PROBLEM 2:
Not sure how I can get each button to slice the element out of the row it is in on the table so that I wouldn't have to make a different function and event listener for every single button.
I'm new to coding and this is my first complex interactive data-table.
Please fix the question, its very difficult to understand in the current format.
Here are a few tips:
use document.querySelector instead.
use plural words for values like arrays, and singular words for singular
perkNames instead of perkName
then perkName when you need to refer to a single perk's name
don't use spelled out numbers, it makes things more complex than needed.
you can swap the html ids with html classes, then select it inside js with document.querySelector('.perk:nth-child(1)') or something like this document.querySelector('.perks-container>:nth-child(1)')
I have tables present. Now if I click any of the below table names, like feedback, how can I get the values for them. table is dynamic. Below is code for displaying table names.
<table class="table table-hover" style="margin-top: 25px;width:300px;">
<thead>
{% for i in tables %}
<tr>
<th > {{ i }} </th>
</tr>
{% endfor %}
</thead>
</table>
Note, i is value of table name.
Here I want to add 2 things:
A click listener
Get the values by clicking on the tables using JavaScript
To get the element clicked, you can listen for click events on the table, then use the event.target property to get the element that was clicked.
// set up the 'click' event listener
myTable.addEventListener('click', event => {
const clickedElement = event.target;
// now that you have the clicked element, do what you want with it
let stuffIWant = clickedElement.<some method or property of element>;
});
From the example in the question, it appears that you looking for the contents of a <th> element. If that's the case, you can use:
stuffIWant = clickedElement.innerText;
A working example:
// listen for all 'click' events within table
const tbl = document.getElementById('tbl');
tbl.addEventListener('click', event => {
const el = event.target;
alert(`you clicked "${el.innerText}"`);
});
#tbl {
background-color: #aaa;
margin: 12px;
}
th {
padding: 0.5rem 2rem;
border: 1px solid #999;
}
/* change cursor to hand on hover */
th:hover {
cursor: pointer;
}
<table id="tbl">
<thead>
<tr><th>Feedback</th></tr>
<tr><th>Complaint</th></tr>
<tr><th>Praise</th></tr>
</thead>
</table>
What is best way to achieve this below?
Suppose my front end table look like this:
id | usernname | role url
1 | JHON | /welcome... readmore
On initial load it should show just one url, but when the user clicks on read more then role url show all data like this:
id | usernname | role url
1 | JHON | /welcome ,/addnew ,/product, /purchase
Is there any good way to achieve this? Note this role url can contain more url i.e. it is dynamically increasing.
The example below shows how you can add a click event to a span within the URL cell. This then toggles a class within the parent cell which shows/hides the URLs depending on the current state. This relies on you loading two spans, one with the condensed URLs, and one with all the URLs.
I've added some styling to help the user understand the interactivity.
Alternative - instead of loading two spans (which needs you to add /welcome twice), you can create a span with just class .long with the extra URLs. This is demonstrated in row 2 with username b.date.
Update: added a button that starts a timeout to show dynamically adding URLs, if you add a URL you should add a class to the parent td so that it knows it has multiple URLs, this will let you show the show more/less link. It adds it using the unique row id that I have added.
Let me know if this isn't what you wanted.
// Add click event to each element with class toggle-more
// This is dynamic, so will work on any new 'show more'
$('#user-list').on('click', '.toggle-more', function(){
// toggle 'more' class in the closest parent table cell
$(this).closest("td").toggleClass("more");
// Change text of link
if ($(this).text() == "show more") {
$(this).text("show less");
} else {
$(this).text("show more");
}
});
// Click event to start adding URLs
$("#addURL").click( function() {
addURL();
$(this).remove();
});
// Add a new URL
function addURL() {
// Add a new URL, you will have to select the appropriate row in real use - i.e. replace #user-1 with a unique row identifier
$("#user-1 .url-list .toggle-more").before("<span class='url long'> ,/newURL</span>");
// Add a class to the table cell so we know there are multiple URLs, again you will need to replace #user-1 with your unique row identifier.
$("#user-1 .url-list").addClass("multi-url");
// Continue adding URLs
var addURLtimer = setTimeout(addURL, 3000);
}
td .long {
display: none;
}
td.more .long {
display: inherit;
}
td.more .short {
display: none;
}
.url, .toggle-more {
float: left;
}
.url {
padding-left: 4px;
}
.toggle-more {
display: none;
padding-left: 4px;
color: blue;
text-decoration: underline;
cursor: pointer;
}
.multi-url .toggle-more {
display: inherit;
}
table {
border-collapse: collapse;
width: 100%;
}
table, th, td {
border: 1px solid black;
}
th, td {
padding: 4px;
text-align: left;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table id="user-list">
<tr>
<th>
id
</th>
<th>
username
</th>
<th>
role url
</th>
</tr>
<tr id="user-0">
<td>
0
</td>
<td>
j.smith
</td>
<td class="url-list multi-url">
<span class="short url">/ welcome</span>
<span class="long url"> /welcome ,/addnew ,/product, /purchase</span> <a class="toggle-more">show more</a>
</td>
</tr>
<tr id="user-1">
<td>
1
</td>
<td>
b.times
</td>
<td class="url-list">
<span class="url">/ welcome</span>
<span class="toggle-more">show more</span>
</td>
</tr>
</table>
<button id="addURL">Start Adding URLs</button>
I am using the selection. I am selecting a value and getting the result in an input box, but the problem is, it is only working in the first row of my selection and not working when I am clicking second selection. Here is the code, Please share if you can solve this one or advice.
<script type="text/javascript">
function displayResult()
{
document.getElementById("mycall1").insertRow(-1).innerHTML = '<td><select id = "forcx" onchange="fillgap()"><option>Select</option> <option>Force</option><option>Angle</option><option>Area</option></select></td>';
document.getElementById("mycall2").insertRow(-1).innerHTML = '<td><input type="text" id="result1" size = "10" ></td>';
}
function fillgap(event){
var xnumb = 20;
var forcxlist = document.getElementById("forcx");
var forcxlistValue = forcxlist.options[forcxlist.selectedIndex].text;
if (forcxlistValue == "Force"){
document.getElementById("result1").value = xnumb;
}
}
</script>
Ok, so if i understand correctly
1) You want to add the: selection, results & + to the existing table
2) Add the options Force, Angle & Area to the select
3) If Force is selected, put the value '20' in the results td
4) When the + is clicked, a new row is added.
5 The newly added rows should behave exactly the same.
Given the above, I have done the following, I'm using jQuery as its simpler and I'm more familiar with it. Its easy.
The trick here is event delegation. at the time your page loads the new rows don't exist, that's why your JavaScript isn't working on them. you can read about it here: https://learn.jquery.com/events/event-delegation/
Here's the result:
$(document).ready(function() {
// add headers to table
$('table tr:first-child').append('<th>Result</th><th>Add</th>');
//add fields to table
$('table tr:not(:first-child)').append('<td><select class="selection"><option></option><option value="Force">Force</option><option value="Angle">Angle</option><option value="Area">Area</option></select></td><td class="result"></td><td><button type="button" class="displayResultBtn">+</button></td>');
// add new row when button is clicked
$('table').on('click','.displayResultBtn', function( event) {
var tRow = $(this).parent().parent().clone();
$(this).parents('table').append(tRow);
$('table tr:last-child td.result').empty();
});
// when the dropdown is changed, update the result to 20 if "Force" is selected.
$('table').on('change','.selection', function( event) {
var selection = $(this).val();
if (selection == "Force") {
$(this).parent().next().html('20');
// You can add more coditionals if you want to add didferent values for the other options.
} else {
$(this).parent().next().empty();
}
});
});
table,
td,
th {
border: 1px solid black;
white-space: nowrap;
}
table {
border-collapse: collapse;
width: 30%;
table-layout: auto;
}
td {
text-align: center;
vertical-align: center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<table>
<tr>
<th>To</th>
<th>From</th>
<th>Detail</th>
<th>Selection</th>
</tr>
<tr>
<td>A</td>
<td>B</td>
<td>A+B</td>
</tr>
</table>
It's hard to answer with limited code provided, but I think your issue is that you are using id multiple times. Which is invalid. id should be unique and used once only.
I have put together some demo code here that will hopefully help you. It doesn't solve your exact problem(I dont have your html so i cant fully solve it). but hopefully this will give you an idea of how to handle accessing different rows, or specific unique ids.
I'm using jQuery here for simplicity, but the principle is the same:
Here's a fiddle if thats easier to play with: https://jsfiddle.net/BradChelly/4179e26q/
I hope this helps somewhat.
// highlight row by child selectors (:last-child)
$('#selectLastRowBtn').click(function(){
//clear any previous highlighting
$('#myTable tr:not(:first-child)').css('background-color','white');
// highlight the last row in the table.
$('#myTable tr:last-child').css('background-color','lightgrey');
});
// highlight row using a specific unique id
$('#selectRowByIdBtn').click(function(){
//get selected row id from dropdown
var rowId = $('#rowSelector option:selected').val();
//clear any previous highlighting
$('#myTable tr:not(:first-child)').css('background-color','white');
//highlight the row with the matching id from the selection dropdown
$('#myTable #row_'+rowId).css('background-color','lightgrey');
});
//
// ------Below is just stuff to make demo work, not relevant to the question
//
// Add row with unique id
$('#addNewRowBtn').click(function(){
var rowCount = $('#myTable tr').length;
$('#myTable').append('<tr id="row_'+rowCount+'"><td>23124</td><td>23124</td><td>23124</td><td>23124</td></tr>');
populateSelect(rowCount);
});
// populate select options
function populateSelect(rowCount){
$('#rowSelector').append('<option value="'+rowCount+'">'+rowCount+'</option>')
}
table {
width: 100%;
text-align: center;
}
table td {
border: 1px solid #333;
padding: 30px 0px;
}
table tr:first-child {
top: 0px;
background: #333;
}
table tr:first-child th {
color: #fff;
padding: 20px 0px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<table id="myTable">
<tr>
<th>Column One</th>
<th>Column Two</th>
<th>Column Three</th>
<th>Column Four</th>
</tr>
<tr id="row_1">
<td>23124</td>
<td>23124</td>
<td>23124</td>
<td>23124</td>
</tr>
</table>
<button id="addNewRowBtn">Add Row</button>
<h3>Using child selectors:</h3>
<button id="selectLastRowBtn">Highlight last row using child selector</button>
<h3>Highlight a row by id:</h3>
<select name="" id="rowSelector">
<option value="1">1</option>
</select>
<button id="selectRowByIdBtn">Highlight row by selected id</button>
I am storing the html for a table in $scheduletext. I want to be able to edit any cell of the table when clicking it so I am using JQuery for the on click action. When I click on a cell it goes blank but will not allow me to type in it, what do I need to change to be able to type into this?
<html>
<body>
<div id="main">
<?php
print_r($scheduletext);
?>
</div>
<script type="text/javascript">
$('td').click(function(){
$(this).html("<contenteditable>");
});
</script>
</body>
</html>
For editing and testing purposes, since to run my code you also need the table, the CSS doesnt hurt either... I dumped it into JSfiddle to hopefully make it easier for anyone trying to give me a hand : https://jsfiddle.net/4yczepsj/
Thanks in advance!!
EDIT: For some reason JSfiddle doesnt do anything at all when clicked on, but on the live model on my site the cell goes blank when clicked, but nothing can be entered.
In calling .html("<contenteditable>") you're modifying the inner HTML of your td element to contain a <contenteditable> element (which isn't valid). What you actually want to do is set the contenteditable property:
$(this).prop('contenteditable', true);
contentEditable is an attribute.
Try this :
$(this).attr('contentEditable', true);
Content Editable is an attribute, not an element. You want to add the attribute contenteditable to the the elements you want to have editable content.
$('td').click(function(){
$(this).attr("contenteditable");
});
https://jsfiddle.net/4yczepsj/1/
$('table').on('mousedown', 'td', function (event) {
$(event.target).closest('td').prop("contentEditable", true);
});
NOTE this includes information that the other answers contain, credit to them, but I am adding a little bit more and putting it all on one place.
I made the following changes:
added tabindex so table cells are tabable.
give focus to the table cell on click.
add contenteditable attribute on focus.
select contents of table cell on focus.
remove contenteditable on blur(when the td loses focus).
function setSelection(element) {
// code for selection from http://stackoverflow.com/questions/3805852/select-all-text-in-contenteditable-div-when-it-focus-click#answer-3806004
setTimeout(function() {
var sel, range;
if (window.getSelection && document.createRange) {
range = document.createRange();
range.selectNodeContents(element);
sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
} else if (document.body.createTextRange) {
range = document.body.createTextRange();
range.moveToElementText(element);
range.select();
}
}, 0)
}
// add tabindex to all tds.
$('td').attr('tabindex', 0);
$('td').on('focus', function() {
$(this).attr('contenteditable', true);
setSelection(this);
}).on('blur', function() {
$(this).attr('contenteditable', false);
})
table,
th,
td {
border: 1px solid black;
border-collapse: collapse;
font-size: 90%;
}
th,
td {
padding: 8px;
}
td {
text-align: center;
}
table {
margin: 0 auto;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
<div id="main">
<table>
<tr>
<th></th>
<th>22oz Dark</th>
<th>12ct 4oz Dark</th>
</tr>
<tr>
<th>2016-01-01</th>
<td>9785</td>
<td>2478</td>
</tr>
<tr>
<th>2016-01-02</th>
<td>8754</td>
<td>2136</td>
</tr>
<tr>
<th>2016-01-03</th>
<td>13587</td>
<td>2203</td>
</tr>
<tr>
<th>2016-01-04</th>
<td>14111</td>
<td>3297</td>
</tr>
<tr>
<th>2016-01-05</th>
<td>13212</td>
<td>3101</td>
</tr>
<tr>
<th>2016-01-06</th>
<td>16335</td>
<td>3299</td>
</tr>
<tr>
<th>2016-01-07</th>
<td>15421</td>
<td>3100</td>
</tr>
</table>
</div>
</body>
</html>