I am using React Bootstrap Table and I am trying to add a loading cursor when the user clicks the sort button.
While I am using that specific table, the problem seems more universal in that the DOM is unavailable when the table is sorting so anything I do to the cursor only happens after the table is done sorting. If the table contains a lot of items this can take a few seconds.
Is there any way add a loading cursor before the sorting starts?
Here is what I have tried so far in terms of React-Bootstrap-Table:
stopSortLoading = () => {
if (document.querySelector('.table-container').classList.contains('loading-pointer')) {
document.querySelector('.table-container').classList.toggle("loading-pointer")
}
}
startSortLoading = () => {
if (!document.querySelector('.table-container').classList.contains('loading-pointer')) {
document.querySelector('.table-container').classList.toggle("loading-pointer")
}
}
render() {
const options = {
onSortChange: this.startSortLoading,
afterTableComplete: this.stopSortLoading
};
I have also tried adding an onClick to a parent div i.e.
<div className='table-container' onClick={() => {
if (!document.querySelector('.table-container').classList.contains('loading-pointer')) {
document.querySelector('.table-container').classList.toggle("loading-pointer")
}
}}>
...table code
</div>
Nothing I have tried has any effect until AFTER the table is sorted.
Related
I am developing a collapsible list that only shows the first five items and a load more button at first, and when I click the load more button, the full list should be shown. I am using scrollHeight to determine this collapsible list's height and it works fine to show the first five items and the load more button, but after I click the load more button, the height of this collapsible list does not change and the remaining items cannot be displayed due to the height constraint. Does anyone have an idea how to fix it? Thank you so much, I really got stuck here for this whole day.
This is how this collapsible list looks like the collapsible list
This is the code of collapsible list
const Collapsible = ({ showCard, loaded, children }) => {
const collapsibleEl = useRef();
const [scrollHeight, setscrollHeight] = useState();
useEffect(() => {
setscrollHeight(collapsibleEl.current.scrollHeight);
console.log(collapsibleEl.current.scrollHeight);
}, [loaded, showCard]);
return (
<>
<div
className={styles.collapsible}
ref={collapsibleEl}
style={
showCard
? scrollHeight
? { height: `${scrollHeight + 100}px` }
: { height: `${collapsibleEl.current.scrollHeight + 100}px` }
: { height: "0px" }
}
>
{children}
</div>
</>
);
};
export default Collapsible;
I have figured it out, all I need to do is to use settimeout.
like this, just give it a little time to get the scrollheight and then re render.
useEffect(() => {
setTimeout(() => {
setscrollHeight(collapsibleEl.current.scrollHeight);
}, 300);
console.log(collapsibleEl.current.scrollHeight);
}, [showCard, loaded]);
I'm in the initial stages of developing a plugin that will allow the user to insert placeholder elements into HTML content that will be processed server-side and used to incorporate some simple logic into a generated PDF document. To this end, I'm attempting to insert a custom element that I've defined using the web components API.
class NSLoop extends HTMLElement {
constructor() {
super();
}
get source() {
return this.getAttribute('source');
}
get as() {
return this.getAttribute('as');
}
}
window.customElements.define('ns-loop', NSLoop);
The contents of loopediting.js:
import Plugin from "#ckeditor/ckeditor5-core/src/plugin";
import Widget from "#ckeditor/ckeditor5-widget/src/widget";
import {viewToModelPositionOutsideModelElement} from "#ckeditor/ckeditor5-widget/src/utils";
import LoopCommand from "./loopcommand";
export default class LoopEditing extends Plugin {
static get requires() {
return [Widget];
}
constructor(editor) {
super(editor);
}
init() {
this._defineSchema();
this._defineConverters();
this.editor.commands.add('loop', new LoopCommand(this.editor));
this.editor.editing.mapper.on('viewToModelPosition', viewToModelPositionOutsideModelElement(this.editor.model, viewElement => viewElement.is('element', 'ns-loop')));
}
_defineSchema() {
const schema = this.editor.model.schema;
schema.register('loop', {
isBlock: false,
isLimit: false,
isObject: false,
isInline: false,
isSelectable: false,
isContent: false,
allowWhere: '$block',
allowAttributes: ['for', 'as'],
});
schema.extend( '$text', {
allowIn: 'loop'
} );
schema.extend( '$block', {
allowIn: 'loop'
} );
}
_defineConverters() {
const conversion = this.editor.conversion;
conversion.for('upcast').elementToElement({
view: {
name: 'ns-loop',
},
model: (viewElement, {write: modelWriter}) => {
const source = viewElement.getAttribute('for');
const as = viewElement.getAttribute('as');
return modelWriter.createElement('loop', {source: source, as: as});
}
});
conversion.for('editingDowncast').elementToElement({
model: 'loop',
view: (modelItem, {writer: viewWriter}) => {
const widgetElement = createLoopView(modelItem, viewWriter);
return widgetElement;
}
});
function createLoopView(modelItem, viewWriter) {
const source = modelItem.getAttribute('source');
const as = modelItem.getAttribute('as');
const loopElement = viewWriter.createContainerElement('ns-loop', {'for': source, 'as': as});
return loopElement;
}
}
}
This code works, in the sense that an <ns-loop> element is successfully inserted into the editor content; however, I am not able to edit this element's content. Any keyboard input is inserted into a <p> before the <ns-loop> element, and any text selection disappears once the mouse stops moving. Additionally, it is only possible to place the cursor at the beginning of the element.
If I simply swap out 'ns-loop' as the tag name for 'div' or 'p', I am able to type within the element without issue, so I suspect that I am missing something in the schema definition to make CKEditor aware that this element is "allowed" to be typed in, however I have no idea what I may have missed -- as far as I'm aware, that's what I should be achieving with the schema.extend() calls.
I have tried innumerable variations of allowedIn, allowedWhere, inheritAllFrom, isBlock, isLimit, etc within the schema definition, with no apparent change in behaviour.
Can anyone provide any insight?
Edit: Some additional information I just noticed - when the cursor is within the <ns-loop> element, the Heading/Paragraph dropdown menu is empty. That may be relevant.
Edit 2: Aaand I found the culprit staring me in the face.
this.editor.editing.mapper.on('viewToModelPosition', viewToModelPositionOutsideModelElement(this.editor.model, viewElement => viewElement.is('element', 'ns-loop')));
I'm new to the CKE5 plugin space, and was using other plugins as a reference point, and I guess I copied that code from another plugin. Removing that code solves the problem.
As noted in the second edit, the culprit was the code,
this.editor.editing.mapper.on('viewToModelPosition', viewToModelPositionOutsideModelElement(this.editor.model, viewElement => viewElement.is('element', 'ns-loop')));
which I apparently copied from another plugin I was using for reference. Removing this code has solved the immediate problem.
I'll accept this answer and close the question once the 2-day timer is up.
i'm trying to develop an App with React using the Open trivia Api. I have mapped a button component (using material ui) to show the different answers for each question. I'm struggling now to target only the clicked one to apply a css property: if the answer is correct should become green, else red. The problem is the fact that once i click, all button become red or green. I tried to store the index in a state and compare the real index, but it doesn't work. here is my code:
in the main APP.js
const [clickedOne, setClickedOne] = useState({
clickedIndex: null,
});
useEffect(() => {
grabData();
}, []);
const handleClick = (choice, ke) => {
setChoice(choice);
if (choice === data.correct_answer) {
setIsCorrect(true);
} else {
setIsCorrect(false);
}
setClickedOne({ clickedIndex: ke });
grabData();
};
The mapped button inside the Render:
{answers.map((answer, index) => {
return (
<ContainedButtons
choice={handleClick}
answer={answer}
correct={data.correct_answer}
isCorrect={isCorrect}
key={index}
id={index}
clicked={clickedOne}
/>
);
})}
Inside the Button component:
const backStyle = () => {
if (clicked === id) {
if (isCorrect) {
return "green";
} else if (isCorrect === false) {
return "red";
} else {
return null;
}
}
};
return (
<div className={classes.root}>
<Button
style={{ backgroundColor: backStyle() }}
value={answer}
onClick={() => choice(answer, id)}
variant="contained"
>
{decodeURIComponent(answer)}
</Button>
When i check now inside the backstyle function if the clicked===id, now nothing happens anymore. Without that if check, i would have all buttons red or green.
Thank you guys for the help!
I have looked at your codesandbox demo, there are alot of other problems apart from the one your question is about.
First of all, each time you make a request to the API to fetch next question, you are making a request to get 10 questions instead of 1. API request URL contains a query parameter named amount which determines how many questions will be fetched on each request. Change its value to 1.
"https://opentdb.com/api.php?amount=1&encode=url3986"
Secondly, there is a lot of unnecessary code and unnecessary use of useState hook. You only need 2 things to be stored in the state, data and answers
const [data, setData] = useState({});
const [answers, setAnswers] = useState([]);
Now, coming to the original problem of detecting which button is clicked and correctly updating its background color.
To achieve the desired functionality, take following steps:
create couple of CSS classes as shown below
button.bgGreen {
background-color: green !important;
}
button.bgRed {
background-color: red !important;
}
pass a handleClick function from App component to ContainedButtons component. When a button is clicked, this click handler will be invoked. Inside the handleClick function, get the text and the button that was clicked using Event.target and depending on whether user answered correctly or not, add appropriate CSS class, created in step 1, on the button that was clicked.
Instead of using index as key for ContainedButtons in map function, use something that will be unique each time. This is needed because we want React to not re-use the ContainedButtons because if React re-uses the ContainedButtons component, then CSS classes added in step 2 will not be removed from the button.
Here's a working codesanbox demo of your app with the above mentioned steps.
In this demo, i have removed the unnecessary code and also changed the key of ContainedButtons inside map function to key={answer.length * Math.random() * 100}. You can change it to anything that will ensure that this key will be unique each time.
I have a textBox which will allow me to enter the building name and after I click on 'Add Building' button, it takes the value and passes it to the next page.So, in my screenshot below, the values 'AbsBuildingOne' and 'AbsBuildingTwo' comes from the textBox input.So, currently I am able to pass a single value from the textBox to the next page, but I am not able to understand if I want to enter the second building how do I save the previous value in this page and add the second value below it. I guess I have to do it with arrays, but I am not able to figure it out how to proceed.
Currently, my code looks as shown below for passing the single value.I have added a state:
this.state = {
textBoxValue: '',
};
So, in my textBox the onUpdateHandler function looks like this:
onClickAddBuildingTextBoxHandler = (inputData) => {
this.setState({ textBoxValue: inputData.value});
}
My textBox component looks like this.Its a custom textBox designed for the project:
<SceTextBox
placeholder='Enter Building Name'
id='AddBuilding'
type='buildingName'
maxLength='40'
onTextUpdatedHandler={this.onClickAddBuildingTextBoxHandler.bind(this)}
forceValidate={this.state.forceValidate}
validator={app.appUtil.validator}
isError={this.state.textBoxEmptyError}
errorMsg={this.errorMessage}
/>
So, currently I am passing the value of this.state.textBoxValue to my next component where the building names are displayed.
So, how do I proceed with multiple values and how do I save all the building names? Also, I have to delete the building name when i click on close icon. So do I have to work with push/pop in array? Someone please guide me with this.
Edit 1:
My new state looks like this:
this.state = {
buildingNames: {
[id++]: ''
}
};
Both my functions are:
onClickAddBuildingButtonHandler = () => {
const { buildingNames } = this.state;
this.setState({
buildingNames: {
...buildingNames,
[id++]:''
}
});
if (!this.state.selectedOptions) {
this.setState({ isRadioEmptyError: true });
if (!this.state.buildingNames.id) {
this.setState({ textBoxEmptyError: true });
}
}
if (this.state.selectedOptions && this.state.buildingNames) {
this.props.navigateToAddBuilding(this.state.buildingNames);
}
}
onClickAddBuildingTextBoxHandler = (inputData) => {
const { buildingNames } = this.state;
this.setState({
buildingNames:{
...buildingNames,
[inputData.id] : inputData.value
}
});
}
To get an array of values from a textarea you can use the this.state.value.split('\n') to separate split the string on each new line producing an array for you.
Below is a very simple example demonstrating its effectiveness. Just type in the textarea and look at the console. Once you make a new line you'll see a second element appears in the array.
function handleKeyUp() {
const textarea = document.getElementById('my-ta');
console.log(textarea.value.split('\n'));
}
<textarea id='my-ta' onKeyUp='handleKeyUp()'></textarea>
EDIT #1:
Here is a Code Sandbox Example 1 demonstrating how it is done. Type in the textarea, make a new line and type some more and then hit the toggle button. You'll see the values displayed and if you toggle back they're in the textarea still.
EDIT #2:
So here is another example of how something can be done. This will show you how to add an item to a list. Code Sandbox Example 2
I'm going to leave it up to you to combine the two methods.
I am using react-selectize component for customizable dropdown which allows users to add new options.
<Dropdown
options={myOptions}
value={selectedValue}
onValueChange={value => {
this.valueUpdated(emptyStringToNull(value));
}}
createFromSearch={this.createFromSearch}
/>
My createFromSearch and onValueChange functions are as below;
createFromSearch: function(options, search){
if (search.length === 0 || (options.map(function(option){
return option.label;
})).indexOf(search) > -1)
return null;
else {
return {'label': search, 'value': search};
}
},
onValueChange: function(text) {
// update the value in state
},
Everything works fine other than this small UI issue. It shows duplicate options soon after I click .
When I click anywhere in the screen it removes this duplicate layover and showing properly. Can anyone please suggest is it styling issue or any other thing I need to do?
I able to fix this issue by trying several things. I was overriding onValueChange method of the component and passed only the value to the actual onValueChange method as below;
const onValueChangeInDropdown = props => value => {
if (value) {
props.onValueChange(value.value);
} else {
props.onValueChange(null);
}
};
This cause the above styling issue since component couldn't find out item.newOption attribute. So solution is when adding newly created item for the option list add it as item.newOption = 'true' and pass the whole item object to onValueChange method.