I have a React component for a table. If the user is not me, then when I see that user's profile page, I only see the first three columns of the table. If the user is me, then I see four columns. However, dynamically changing the columns causes the following error:
Uncaught Error: Invariant Violation: processUpdates(): Unable to find child 3 of element. This probably means the DOM was unexpectedly mutated (e.g., by the browser), usually due to forgetting a <tbody> when using tables, nesting tags like <form>, <p>, or <a>, or using non-SVG elements in an <svg> parent. Try inspecting the child nodes of the element with React ID `.0.1.1.0.0.1.0.0`.
I've looked around a lot and made sure that my table is encased with . How can I allow for this table flexibility in React?
My outer table shell looks like this:
var CategoriesTable = React.createClass({
render: function() {
var includeReps = false;
var repsHeader = '';
if (this.props.currentUser.username === this.props.user.username) {
includeReps = true;
repsHeader = <th>Reps</th>;
}
return (
<div className="categoriesTable panel panel-default">
<CategoriesHeader user={this.props.user} />
<table className="table table-bordered table-striped">
<tbody>
<tr>
<th>Category</th>
<th>Direct Rep</th>
<th>Crowd Rep</th>
{repsHeader}
</tr>
{this.props.user.categories.map(function(category) {
return <CategoriesItem key={category.id} category={category.name} directRep={category.directScore} prevDirectRep={category.previousDirectScore} crowdRep={category.crowdScore} reps={category.reps} includeReps={includeReps} />;
})}
</tbody>
</table>
</div>
);
}
});
Each table row looks like this:
var CategoriesItem = React.createClass({
render: function() {
var reps = this.props.includeReps ? <td>{this.props.reps}</td> : '';
return (
<tr className="categoriesItem">
<td>{this.props.category}</td>
<td><ScoreBar directRep={this.props.directRep} prevDirectRep={this.props.prevDirectRep} category={this.props.category}/></td>
<td>{this.props.crowdRep}</td>
{reps}
</tr>
);
}
});
Why can I do to make React accept these table changes? When I start with the table with all four columns and then switch to a different user's profile page, the fourth table data piece becomes a
Perhaps a hack, but giving a react component a key will force the entire component to re-render when the key changes. If each profile page gives the table a unique key, then this problem goes away.
Related
I want react to render a DOM at multiple spots on my page. For example, if there's the class (.react-container), I want it to render the react component.
How can I do this if there are multiple spots on the page?
IMPORTANT: ArrayOne amount changes depending on where it's rendered.
APP.JS FILE
const App = () => {
return (
<div>
<div className="react-container">
{
ArrayOne.map(item=> (
<div className={
clsx({
'card': true,
'card-visible': state
})} >
<h2>{item.title}</h2>
<p>{item.text}</p>
</div>
))
}
</div>
</div>
);
};
INDEX.JS FILE
for (const el of document.querySelectorAll(".react-container")) {
ReactDOM.render(<App/>, el);
}
Code above is not running how I would like it to. It gives me two rows of only 1 card. When it should be 4 cards on row one and 1 card on row two. Console shows:
run query selector
running (4)
run query selector
running(1)
run query selector
running(1)
When I want it to do.
run query selector
running(4)
run query selector
running(1)
Interesting code. But i believe you are mixing some stuff.
get the layout working first, since seems you are not using React to do the layout.
<div>
<div className="react-container"></div>
<div className="react-container"></div>
<div className="react-container"></div>
<div className="react-container"></div>
</div>
render them into it with your lines.
for (const el of document.querySelectorAll(".react-container")) {
ReactDOM.render(<App/>, el);
}
Keep in mind, if you want to use your rest of code, you need to render it into a root, as in a typical React setup.
<div id="root"></div>
So pick one approach, either render once, but let react control everything underneath it, or render multiple times. You can't do both.
The examples presented below are not the actual problematic code, but as close to a simplified case as I can make it for the purposes of discussion here.
Broken Example
The following code renders a table, but puts the Component values, unexpectedly, above the table element, both visually and when I inspect it in the DOM:
<html>
<head><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script></head>
<body>
<div id="app">
<table border="1">
<tr><td>A</td><td>B</td></tr>
<test></test>
<tr><td>Y</td><td>Z</td></tr>
</table>
</div>
<script>
const Test = {
template: '<tr><td>ONE</td><td>TWO</td></tr>'
}
const app = new Vue({
el: '#app',
components: { Test },
});
</script>
</body>
</html>
Rendered output:
Examined DOM:
A Similar Example, But Works
However, VueJS doesn't seem to have a problem doing it with other nested objects. The following works just fine. Here's the same thing, but with a list:
<html>
<head><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script></head>
<body>
<div id="app">
<ul>
<li>TOP</li>
<test></test>
<li>BOTTOM</li>
</ul>
</div>
<script>
const Test = {
template: '<li>MIDDLE</li>'
}
const app = new Vue({
el: '#app',
components: { Test },
});
</script>
</body>
</html>
At a high level, these appear to be equivalent in structure -- one container containing three items. In the first case it's a table holding rows (broken), in the second it's a list holding items (works).
The Question
Can someone explain the behavior, what's going on under the hood, and ideally how to fix?
Background of the Real Problem
The actual code involves the table having a table header (thead) section with a title row (tr), while the component data appears inside of a table body (tbody), and for various reasons I do not want to pollute the page with additional components, nor alter existing component code.
Useful Aside
And while unrelated, for readers in a similar boat, I discovered by accident <test/> and <test></test> are not the same thing. In fact, if you take the working case above and try it, you'll discover the last element goes missing. Pop on over to VueJS and read about self-closing-components, as only official "void" elements can be self-closing.
This issue is related to DOM template parsing in Vue.js. As mentioned in the docs:
Some HTML elements, such as <ul>, <ol>, <table> and <select> have restrictions on what elements can appear inside them, and some elements such as <li>, <tr>, and <option> can only appear inside certain other elements.
This will lead to issues when using components with elements that have such restrictions. For example:
<table>
<blog-post-row></blog-post-row>
</table>
The custom component <blog-post-row> will be hoisted out as invalid content, causing errors in the eventual rendered output.
This is the exact same issue that you are facing, as <Test> is rendered outside the table in your demo. Fortunately, the is special attribute offers a workaround for this issue. You will simply need to call <Test> inside parent component like:
<table border="1">
<tr><td>A</td><td>B</td></tr>
<tr is="test"></tr>
<tr><td>Y</td><td>Z</td></tr>
</table>
Working Demo:
const Test = {
template: '<tr><td>ONE</td><td>TWO</td></tr>'
}
const app = new Vue({
el: '#app',
components: {
Test
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet">
<div id="app">
<table class="table">
<tr>
<td>A</td>
<td>B</td>
</tr>
<tr is="test"></tr>
<tr>
<td>Y</td>
<td>Z</td>
</tr>
</table>
</div>
I'm having a couple of issue with Jquery. Basically I have a .html() response printed and I have to highlight rows of a table on mouseover but it doesn't work.
HTML table
<table id='simplehighlight'>
<tr>
<td>header 1</td>
<td>header 2</td>
</tr>
<tr>
<td>bla bla bla</td>
<td>highlight this row</td>
</tr>
<tr>
<td>bla bla bla</td>
<td>or highlight this row</td>
</tr>
</table>
the table above is printed with PHP echos. The PHP script is called with ajax and the response is printed inside a div with the .html() function. Example:
function(data, textStatus) {
if(textStatus == "success") {
$('#resultBox').html(data);
}
}, 'text/html');
}
data is the HTML table. Last but not least I have the jquery code for active the highlight which it doesn't work.
$("simplehighlight tr").not(':first').hover(
function () {
$(this).css("background","yellow");
},
function () {
$(this).css("background","");
}
);
instead of highlighting the row I have put a simple alert to check if it works, but obviusly it does not. Why? What's wrong in my code? How can I accomplish my task, aka highlight these rows?
Thanks everyone,
Alberto-
Attaching an event handler via jQuery's on should work:
$('body').on('mouseenter mouseleave', '#simplehighlight tr:not(:first-child)', function() {
$( this ).toggleClass( 'highlighted' );
} );
See this jsFiddle.
The good thing with using on this way is, that the table you mentioned may be added at any time (e.g. through an AJAX call). When calling on directly on the jQuery match (as in jQuery("#simplehighlight tr").not(':first').on(...)), jQuery binds the handler only to the currently existing DOM elements.
Note: I replaced the hover event with mouseenter mouseleave, because hover is removed since jQuery 1.9:
Deprecated in jQuery 1.8, removed in 1.9: The name "hover" used as a
shorthand for the string "mouseenter mouseleave".
Source: http://api.jquery.com/on/
You have to remember that commands such as the one your are trying to execute, will only be executed against elements that already exist in your DOM. So basically you have to combine the two segments of your code like this:
function(data, textStatus) {
if(textStatus == "success") {
$('#resultBox').html(data);
$("simplehighlight tr").not(':first').hover(
// do highlight stuffs here
alert("IT WORKS??");
});
}
}, 'text/html');
}
Instead of firing the hover() function manually, you should set up an event handler to do so. Events will also get captured from inserted elements; so you may set up the event listener even before the AJAX call.
jQuery( 'simplehighlight tr' ).not(':first').on( 'hover', function() {
var currentTR = jQuery( this );
currentTR.addClass( 'highlight' );
} );
try
jQuery("#simplehighlight tr").not(':first').mouseover(function(){
// do highlight stuffs here
alert("IT WORKS??");
});
here a js fiddle
here
I have a table where the user can add rows to it but each row is numbered. Now the user enters a number in a textbox for the number of rows he/she wants to add before they actually start adding rows. Below is the code where if the number of rows that has been added is over the number entered by the user, then it stops adding the rows.
if (qnum > <?php echo (int)#$_POST['textQuestion']; ?>) {
return;
}
Example: if user entered in the number 5 in a textbox, then the user can only add 5 rows, if the user tries to add another row, then no row is added because user can't add more than 5 rows.
What my question is that if the user has already reach the max number of rows they have added, then I want it to disable a textarea (user wont be able to click in the textarea and I want to give it the correct colour so that you can tell the textarea is disabled). I also want to disable a hyperlink so that user cannot click on the hyperlink (again suitable color change so user can tell hyperlink is disabled) Does anyone know how to do this?
Below is code for hyperling and the textarea:
<table id="question">
<tr>
<th colspan="2">
Question Number <span id="questionNum">1</span>
</th>
</tr>
<tr>
<td rowspan="3">Question:</td>
<td rowspan="3">
<textarea id="questionTextArea" rows="5" cols="40" name="questionText"></textarea>
<span href="#" class="link">[Question link]</span>
</td>
</tr>
</table>
Jquery code showing example of how a table row is added:
function insertQuestion(form) {
var questionarea=(form.questionText.length)
? form.questionText[0]
: form.questionText;
var context = $('#optionAndAnswer');
var currenttotal = context.find('.answerBtnsOn').length;
alertErrors = "";
// Note, this is just so it's declared...
if (questionarea.value == ""){
if (qnum > <?php echo (int)#$_POST['textQuestion']; ?>) {
return;
}
var $tbody = $('#qandatbl > tbody');
var $tr = $("<tr class='optionAndAnswer' align='center'></tr>");
var $qid = $("<td class='qid'>" + qnum + "</td>");
$tr.append($qid);
$tbody.append($tr);
}
Html table where the table row is added to:
<table id="qandatbl" align="center">
<thead>
<tr>
<th class="qid">Question No</th>
</tr>
</thead>
<tbody></tbody>
</table>
look at this jsfiddle for example here, you can write a question on top using the textarea and when you have done that then click on the button to add it in a new row. It is the top textarea I want to disable only if the number of rows has met its limit.
Are you adding rows via JavaScript/Ajax or on page load?
If the former (which I'm guessing your first code example illustrates), use a JavaScript counter to represent the number of rows, and when they trigger the add row function (which you write), check that number first; alert and disabled accordingly:
jQuery allows you to disable form elements, and just replace the link with the link text (ie. minus the tag), and modify it's color, either with a $(ele).css() call, or by wrapping it in a span tag.
If the latter, you can just write the textarea via PHP with the disabled="disabled" property added to the opening tag. The link: use the same method as above (wrapping it in a span tag, rather than an a tag).
// To disable
$('.someElement').attr('disabled', 'disabled');
// To enable
$('.someElement').removeAttr('disabled');
Obviously, you need to include the jQuery framework. I usually use Google's: https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js.
It's kinda difficult to explain what I want to achieve (and feel free to modify the title if you can think of a better one), so I'll give you an example:
Street: First Lane
South side 28
City: Duckburg
Country: Disneyland
ZIP: 1234567890-XY
This is what I want the user to see. But I also want the user to be able to select only the right column, so he can copy-paste the contents of it elsewhere. If I do this with a table, the user can only select whole rows, and a copy-paste operation will copy row headers as well. If I do this with two separate containers next to each other, the labels get out of synch with the contents if some item has more than one line.
Can this be achieved somehow?
Yes. Try something like this:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Page Title</title>
<style type="text/css" media="screen">
#left_side { float: left; text-align: right;}
</style>
</head>
<body>
<div id="container">
<div id="left_side">
Street:<br><br>
City:<br>
Country:<br>
ZIP:
</div>
<div id="right_side">
First Lane<br>
South side 28<br>
Duckburg<br>
Disneyland<br>
1234567890-XY
</div>
</div>
</body>
</html>
Yes, it is possible.
Use YUI datatable. It works even with JQuery.
Although this sample use row selection you can use column selection
You can use any input format including JSON, HTML table, XML and text. No input field required. I use somenting like
App http://img74.imageshack.us/img74/1833/singled.gif
According to above, when i click (yes, mouse click) a single row, it will be highlighted (selected) and supported actions will be applied (Edit). Supported actions will be applied according to your business requirement
In your case, a HTML table, you set up according to (You can test it if you want):
First lets set up CSS and JavaScript
<!-- Combo-handled YUI CSS files: -->
<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/combo?2.7.0/build/paginator/assets/skins/sam/paginator.css&2.7.0/build/datatable/assets/skins/sam/datatable.css">
<style type="text/css">
.center {text-align:center;}
</style>
<!-- Combo-handled YUI JS files: -->
<script type="text/javascript" src="http://yui.yahooapis.com/combo?2.7.0/build/yahoo-dom-event/yahoo-dom-event.js&2.7.0/build/connection/connection-min.js&2.7.0/build/element/element-min.js&2.7.0/build/paginator/paginator-min.js&2.7.0/build/datasource/datasource-min.js&2.7.0/build/datatable/datatable-min.js&2.7.0/build/json/json-min.js"></script>
Our body (generated on server side)
<body class="yui-skin-sam">
<div id="container">
<table id="source">
<thead>
<tr>
<th>AAA</th>
<th>BBB</th>
<th>CCC</th>
<th>HIDDEN</th>
</tr>
</thead>
<tbody>
<tr>
<td>a</td>
<td>b</td>
<td>c</td>
<td>0</td>
</tr>
<tr>
<td>a</td>
<td>b</td>
<td>c</td>
<td>1</td>
</tr>
<tr>
<td>a</td>
<td>b</td>
<td>c</td>
<td>2</td>
</tr>
</tbody>
</table>
</div>
<div id="actionContainer">
<a id="action" href="#">Edit row</a>
</div>
</body>
Now lets configure script after body (code commented)
<script type="text/javascript">
var settings = {
widgetList:{
reference:null,
datatable:{
columnSettings:[
// key attribute matches key attribute in dataSource fields attribute - see bellow
{key:"AAA", label:"A custom label"},
// if label is omitted, default to key value
// className customizes a class to apply to a column
{key:"BBB", className:"center"},
{key:"CCC"},
// i do not want to show id value, so i hide it through hidden attribute
{key:"HIDDEN", hidden:true},
// i want to generate a custom value regardless dataSource, so i set up a custom formatter function - see below
{key:"CUSTOM", label:"A custom value", formatter:customValue}
],
settings:{
selectionMode:"single"
}
}, // eof datatable
dataSource:{
// use $("#source")[0] whether you use JQuery (do not forget set up JQuery)
// source points to data that will populate our datatable
// in our case data will be retrieved from a HTML table
// see responseType bellow
source:YAHOO.util.Dom.get("source"),
settings:{
responseSchema:{
fields:[
// key attribute matches th content
{key:"AAA"},
{key:"BBB"},
{key:"CCC"},
{key:"HIDDEN"}],
// set up input
responseType:YAHOO.util.DataSource.TYPE_HTMLTABLE
}
}
}, // eof dataSource
create:function() {
this.reference = new YAHOO.widget.DataTable("container", this.datatable.columnSettings, new YAHOO.util.DataSource(this.dataSource.source, this.dataSource.settings), this.datatable.settings);
} // eof create
} // eof widgetList
}; // eof setting
// sets up custom value
function customValue(container, record, column, data) {
// container references a cell
container.innerHTML = record.getData("AAA") + " - " + record.getData("BBB") + " - " + record.getData("CCC") + " - " + record.getData("HIDDEN");
}
(function() {
// use $("#actionContainer").set("display", "none"); in JQuery
YAHOO.util.Dom.setStyle("actionContainer", "display", "none");
settings.widgetList.create();
// RIA applications
YAHOO.util.Event.addListener("action", "click", function(e) {
e.preventDefault();
var datatable = settings.widgetList.reference;
var recordArray = datatable.getRecordSet().getRecords();
for(var i = 0; i < recordArray.length; i++) {
if(datatable.isSelected(recordArray[i])) {
alert("You have selected id: " + recordArray[i].getData("HIDDEN") + "\nYou can use a JQuery dialog to collect data changes");
}
}
});
// rowClickEvent - use subscribe
settings.widgetList.reference.subscribe("rowClickEvent", function(args) {
// args.target is a Record instance
if(this.isSelected(args.target)) {
this.unselectRow(args.target);
YAHOO.util.Dom.setStyle("actionContainer", "display", "none");
} else {
this.unselectAllRows();
this.selectRow(args.target);
YAHOO.util.Dom.setStyle("actionContainer", "display", "block");
}
});
})();
</script>
</html>
Minimal changes are required if you use JSON, XML or text. Feel free to ask for them.
In order to use column selection use columnClickEvent instead.
regards,
Could you have all of the right hand column of your example in 1 cell somehow? That way it would be all selected together.
The row headers would stay aligned as long as the number of rows in each part of the address was always the same.