Currently I'm working on my React Tic-Tac-Toe App and I'm facing problem which I don't understand.
My file is very simple. I'm using one state and one function which is in useEffect. This function is supposed to choose whenever use X sign or O sign depending on current state value.
Everything was working just fine (X and O were switching on click) until I've added this condition to the checkPlayer function
if(e.target.innerText === ''){
//add mark to the TD
}else{
return;
}
This condition was supposed to prevent rewriting old inner text of td (For example when 1st player clicks on that td, X mark was added. But when 2nd player clicks that same td, it was rewriten to O mark).
But now, It's not switching between X and O and only writing X. Console is not throwing any errors and I don't get why is this happening.
Obviously, my goal is to prevent clicking on td, which already has some mark inside, so it won't be rewriten, state won't be changed and player won't be switched.
My State and useEffect with function
const [sign,setSign] = useState(true)
useEffect(()=>{
function checkPlayer(e){
if(e.target.innerText === ''){
//1st player
if(sign === true ){
e.target.innerText = 'X';
setSign(false)
}
//2nd player
if(sign === false){
e.target.innerText = 'O'
setSign(true)
}
}else{
return;
}
}
//Convert HTML collection to an array, then add eventListener to every item in array
let all = document.getElementsByTagName('td');
let tds = Array.from(all);
tds.forEach(td => {
td.addEventListener('click',checkPlayer)
})
},[sign])
HTML part of App
<div className="App">
<div className="container">
<table>
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</div>
</div>
Related
I'm trying to scrape a website and there is a table with clickable elements and text. I managed to use this to grab the innerText of the table elements:
const result = await page.$$eval('tableselector tr', rows => {
return Array.from(rows, row => {
const columns = row.querySelectorAll('td');
return Array.from(columns, column => column.innerText);
});
});
I've tried just returning columns and using result[row][column].getProperty('innerText').jsonValue() to try and grab the innerText but it doesn't work. Could someone explain where I'm going wrong?
EDIT:
Here is an HTML Segment that represents the structure of the table I am trying to scrape.
<table id = "table_id">
<body>
<!-- input button is the clickable element I want to grab -->
<tr class = "GridRowStyle">
<td>input button</td><td>text2</td><td>text3</td><td>text4</td><td>text5</td><td>text6</td><td>text7</td>
</tr>
<tr class = "GridAlternatingStyle">
<td>input button</td><td>text2</td><td>text3</td><td>text4</td><td>text5</td><td>text6</td><td>text7</td>
</tr>
<tr class = "GridRowStyle">
<td>input button</td><td>text2</td><td>text3</td><td>text4</td><td>text5</td><td>text6</td><td>text7</td>
</tr>
</body>
The initial text of A, B, C, D, and the number need to be removed in the frontend because I require it in the backend.
The HTML structure of table row is like this:
<tr ng-repeat="(key, field) in nbd_fields" ng-show="field.enable && field.published" class="ng-scope">
<td class="ng-binding">A,B,C,D: 1 - Auswahl Wunschkarte : <b class="ng-binding">Wähle eine Option</b>
</td>
<td ng-bind-html="field.price | to_trusted" class="ng-binding"></td>
</tr>
Before Input:
Current Output:
If you notice that the selected option is also not visible. Is it because of the $(window).load() ?
Required Output:
Code that I am using:
jQuery(".ng-scope td.ng-binding:first-child").text(function(i, oldVal) {
return oldVal.replace(/^[^-]+ - /,"");
});
});
How can I make it so that it does not affect the <b> tag inside?
I used the above code for the steps heading with a different selector on the same page* and it worked because it did not have any children to alter.
I had to wrap it around $(window).load() so that the changes are applied after the table is loaded. $(document).ready() did not work with it. Not sure why?
(Edit: Modified to accommodate restated requirement in comment below.)
To strip "everything up to and including the '-'" from the text of first column table cells while leaving the rest untouched:
// strip "everything up to and including the '-'"
// from table cell contents
function stripPrefix(tblCell) {
// only evaluate first td in tr
if (tblCell.previousElementSibling) {
return;
}
const tNode = tblCell.firstChild;
// ignore if table cell is empty
if (!tNode) {
return;
}
const chars = tNode.nodeValue.split('');
const iFirstDash = chars.indexOf('-');
if (iFirstDash === -1) { return; }
tNode.nodeValue = chars.slice(iFirstDash+1).join('');
}
function stripAllPrefixes() {
const tds = document.getElementsByTagName('td');
for (const td of tds) {
stripPrefix(td);
}
}
td {
border: 1px solid gray;
}
<h4>Strip "everything up to and including the '-'" from Table Cells</h4>
<table>
<tr>
<td>A,B,C,D: 1 - Auswahl Wunschkarte : <b>Wähle eine Option</b></td>
<td></td>
</tr>
<tr>
<td>B,C,D,E: 20 - A different leader : <b>should also be stripped</b></td>
<td></td>
</tr>
<tr>
<td>Oops no dash here <b>Just checking</b></td>
<td></td>
</tr>
</table>
<button onclick="stripAllPrefixes();">Strip All</button>
It does not effect the b tag, your code is working, you just need to use the right method and do the replacement to the HTML code and not the text nodes:
jQuery(".nbd-field-header label, .nbo-summary-table .ng-binding").html(function(i, oldVal) {
return oldVal.replace(/^[^-]+ - /,"");
});
I have a Table which when you click a td tag that is an plusbutton it should show the details about that row. Something like this:
Right now I am just testing it like this:
props.info.map((l, i) => {
return (
<tr>
<td>{i + 1}</td>
<td>{l.UserId}</td>
<td onClick={props.onShowInfoDetails}>
<MenuPlusButton /></td>
{props.showInfoDetails && (
<DetailsTable />
)
}
</tr>
)
})
where the DetailsTable is the thing i want to render onClick
export const DetailsTable = (props: Props) => {
return (
<tr>
<td>Hello</td>
</tr>
)
}
There is two problems with this. First the DetailsTable renders to the right of the rest of the content and not under it like in the picture. second problem is that when I click it all table rows show the hello not just the one that I clicked. Both of these I can't seem to figure out. The second problem I guess is because it says if props.showEntryDetails is true it renders the DetailsTable and the onClick sets it to true but how do I make it so it's only true for that row that I clicked?
I'm looking to remove the divs from a html table but retain the content?
<table id="datatable">
<thead>
<tr>
<th></th>
<th>Jane</th>
<th>John</th>
</tr>
</thead>
<tbody>
<tr>
<th>Apples</th>
<td><div>3</div></td>
<td><div>4</div></td>
</tr>
</tbody>
</table>
I have tried:
alert($('#datatable').html().replace('<div>', ''));
But what is alerted still contains the
<div>
tags
I can't remove them from the source because they are used for other purposes.
To keep the DOM unmodified (IE: Leave the <div> tags in the source) and only modify the HTML variable you can do:
var html = $('#datatable').html();
var tags = ["<div>", "</div>"];
for (var i = 0; i < tags.length; i++) {
while (html.indexOf(tags[i]) > -1) {
html = html.replace(tags[i], "");
}
}
alert(html);
This is available as a demo at this fiddle.
The problem with your initial solution, is that JavaScript replace only removes the first occurrence of the specified string. Hence the while loop.
Use $('#datatable div').contents().unwrap() to remove the divs from the table and alert($('#datatable').html()) to show the remaining elements of the table.
var backup = $('#datatable').html();//Keep the html
$('#datatable div').contents().unwrap();//Remove divs
alert($('#datatable').html());//Show the table (without divs)
$('#datatable').html(backup);//Bring the old, full html back
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id="datatable">
<thead>
<tr>
<th></th>
<th>Jane</th>
<th>John</th>
</tr>
</thead>
<tbody>
<tr>
<th>Apples</th>
<td><div>3</div></td>
<td><div>4</div></td>
</tr>
</tbody>
</table>
Try this
$('#datatable').find('div').remove();
If you want to keep content try this
$('#datatable').find('div').replaceWith(function(){
return $(this).text()
});
$('#datatable').find('div').replaceWith(function(){
return $(this).text()
});
alert($('#datatable').html());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id="datatable">
<thead>
<tr>
<th></th>
<th>Jane</th>
<th>John</th>
</tr>
</thead>
<tbody>
<tr>
<th>Apples</th>
<td><div>3</div></td>
<td><div>4</div></td>
</tr>
</tbody>
</table>
actually there are 3 common ways
1. Using the .html('') method
$("#my_element").html(''); // the quotes are important as just .html() returns the html DOM container within the target element
2. Using the .remove() method
$("#my_element #my_element_child").remove(); // removes the targeted child element
3. Using the .empty() method
$("#my_element").remove(); // similar to the .html('') method it removes all children divs
Edit It seams i have made a mistake in understanding the OP's original intention as pointed out by #JosephGarrone and hence i made the following edit.
var dom = $("#my_element").html() // get the elements DOM structure
var regex = /(<div>|<\/div>)/g; // a regex to pickup the <divs in the DOM
var div_less_dom = dom.replace(regex, '') // do something with the "<div>" free DOM
One approach, in plain JavaScript is:
// a descriptive, but ridiculously named, function,
// htmlString: String, the string of HTML from which
// you wish to remove certain element-types,
// toRemove: String, the element-type you wish to remove,
// this is passed to querySelectorAll(), so a
// CSS selector is fine, although to guard against
// '<div>' I have removed '<' and '>' characters:
function removeElementFromHTMLString(htmlString, toRemove) {
// here we create a <div> element:
let div = document.createElement('div'),
// and declare an 'empty' variable for
// later use:
parent;
// here we convert the supplied selector to lower-caase,
// and remove the '<' and '>' characters to prevent
// errors from the user supplying '<div>', converting it
// to 'div'; this does mean that the child combinator '>'
// cannot be used in the selector (as currently written):
toRemove = toRemove.toLowerCase().replace(/<|>/g,'');
// assigning the htmlString as the innerHTML of the
// created-<div>:
div.innerHTML = htmlString;
// passing the supplied selector to querySelectorAll(),
// converting the Array-like NodeList to an Array, and
// iterating over that Array with Array.prototype.forEach():
Array.from(div.querySelectorAll(toRemove)).forEach(function(elem){
// 'elem' refers to the current element in the Array of
// elements over which we're iterating:
// assigning the elem.parentNode to a variable for reuse:
parent = elem.parentNode;
// while the found element has a first child:
while (elem.firstChild) {
// we insert that first child ahead of the
// current element:
parent.insertBefore(elem.firstChild, elem);
}
// and then, once the element has no child
// elements, we remove the element from its
// parent:
parent.removeChild(elem);
});
// and then, assuming you want a HTML String without
// those elements matching the selector, we return
// the innerHTML to the calling context:
return div.innerHTML;
}
console.log(removeElementFromHTMLString(document.getElementById('datatable').outerHTML, 'div'));
function removeElementFromHTMLString(htmlString, toRemove) {
let div = document.createElement('div'),
parent;
toRemove = toRemove.toLowerCase().replace(/<|>/g, '');
div.innerHTML = htmlString;
Array.from(div.querySelectorAll(toRemove)).forEach(function(elem) {
parent = elem.parentNode;
while (elem.firstChild) {
parent.insertBefore(elem.firstChild, elem);
}
parent.removeChild(elem);
});
return div.innerHTML;
}
console.log(removeElementFromHTMLString(document.getElementById('datatable').outerHTML, 'div'));
td {
color: orange;
}
td > div {
color: limegreen;
}
<table id="datatable">
<thead>
<tr>
<th></th>
<th>Jane</th>
<th>John</th>
</tr>
</thead>
<tbody>
<tr>
<th>Apples</th>
<td>
<div>3</div>
</td>
<td>
<div>4</div>
</td>
</tr>
</tbody>
</table>
I want to achieve expandable row functionality for table.
Let's assume we have table with task names and task complexity. When you click on one task the description of task is shown below. I try to do it this way with ReactJS (in render method):
if (selectedTask === task.id) {
return [
<tr>
<td>{task.name}</td>
<td>{task.complexity}</td>
</tr>,
<tr>
<td colSpan="2">{task.description}</td>
</tr>
];
} else {
return <tr>
<td>{task.name}</td>
<td>{task.complexity}</td>
</tr>;
}
And it doesn't work. It says:
A valid ReactComponent must be returned. You may have returned undefined, an array or some other invalid object
I tried also to wrap 2 rows in a div but I get wrong rendering.
Please, suggest correct solution.
The render() method on a React component must always return a single element. No exceptions.
In your case, I would suggest wrapping everything inside a tbody element. You can have as many of those as you want in a table without disrupting your row structure, and then you'll always return one element inside render().
if (selectedTask === task.id) {
return (
<tbody>
<tr>
<td>{task.name}</td>
<td>{task.complexity}</td>
</tr>,
<tr>
<td colSpan="2">{task.description}</td>
</tr>
</tbody>
);
} else {
return (
<tbody>
<tr>
<td>{task.name}</td>
<td>{task.complexity}</td>
</tr>
</tbody>
);
}