I am currently working on a form where you can enter diverse data (text, img's and also choose pdf's).
With the latter I am having some troubles. When I want to edit a form, the Selectfield (where the pdf is safed) is empty.
First of, when I open the Modal to edit the entry, I get my data like this:
onMount(async () => {
const data = await api.get('/documents');
console.log({ data });
documents = data.map((doc) => ({ value: doc.id, label: doc.name }));
if (isEdit) {
selectedDocument = data.find((doc) => doc.value === formInput.projectId);
}
});
I want to display the chosen PDF in a SelectInput field:
<div class="sm:col-span-3">
<SelectInput
id="files"
items={documents}
name="files"
label="Dateien"
bind:selectedItem={selectedDocument}
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm"
/>
</div>
The component SelectInput is built like this:
<script>
import Select from 'svelte-select';
export let items = [];
export let selectedItem = undefined;
export let id;
export let name;
export let label;
function handleSelect(event) {
selectedItem = event.detail;
console.log('selectedItem', selectedItem);
}
function handleClear() {
selectedItem = undefined;
}
</script>
<div class={`themed ${$$props.class}`}>
<label for={id} class="block text-base font-medium text-gray-700">{label}</label>
<Select {id} {items} {name} on:select={handleSelect} on:clear={handleClear} />
</div>
TLDR: The Select-value is not preselected like the other text-areas. Basically I want to edit a dB entry. So when I click on "edit" I want to see all the changable values (title, uppertext, lowertext, selectedItem). So far I am only able to see the textareas but not the selectable. When I open the edit, it is empty and I dont know why.
Because I'm not familiar with the svelte-select package, I can't be sure as to why it is not reacting to the changes. But here's what I would try to debug this:
Check if there are any errors printed in the console on the server or browser?
Use bind:items for <SelectInput>
Use bind:items for <Select>
Finally, if none of those work, you can use a key block. This will force it to re-render when the key changes.
{#key items}
<SelectInput
/>
{/key}
Related
I have PrimeReact DataTable with search/filter functionality. My main goal here is to highlight the text or data in DataTable that matches on the Search Input of the user.
I used react-highlight-words for the highlighting of data. Using the props textToHighlight, I can highlighted the text.
The Problem:
textToHighlight only accepts string and I don't have idea how to pass the component DataTable or its data in the props.
I tried the following:
I pass the Input state in textToHighlight props but unfortunately it prints the data outside the table.
I tried to put the DataTable component inside the Highlighter component, but the table doesn't render.
Here's the image:
ThesisList.jsx
// Search Box
const renderHeader = () => {
return (
<div className="flex justify-between">
<Button
className="p-button-outlined"
icon="pi pi-filter-slash"
label="Clear"
onClick={clearFilter}
/>
<span className="p-input-icon-left">
<i className="pi pi-search" />
<InputText
placeholder="Search"
value={globalFilterValue}
onChange={onGlobalFilterChange}
/>
</span>
</div>
);
};
// The function who checks if the input matches the Filters (check initFilter()).
const onGlobalFilterChange = (e) => {
const value = e.target.value;
let _filter = { ...filters };
_filter["global"].value = value;
setFilters(_filter);
setGlobalFilterValue(value);
};
return (
<div className="p-4 w-full h-screen">
//As you can see here I used the Input state
<Highlighter
searchWords={[globalFilterValue]}
textToHighlight={globalFilterValue}
/>
<DataTable>
...
</DataTable>
</div>
);
Each column in Primereact Data table takes a prop called body through which we can format the cells, so in your case, you can pass the Highlighter as the body for each column.
Here's an example with the title column.
<Datatable>
...
<Column
field="title"
header="Title"
body={(rowData) => (
<Highlighter
searchWords={[globalFilterValue]}
textToHighlight={rowData.title}
/>
)}
/>
...
</Datatable>
I have a simple search bar and I want to pre-populate it with some data. I've tried using value but it doesn't let the user change the value in the search bar afterward.
<input type="search" class="navBarSearchForm3 form-control rounded searchterm2" placeholder="search term" value={comparelinks}></input>
I'd like the searchbar to already have a value and let the user be able to change it. Do I need some JS for it or can it be done with just HTML?
You are missing the handle of the change. So, you need to edit the value when the user changes something on the input it would be like:
In this case, I'd use React Hooks (useState) to be able to set one default value and being able to change it.
And finally, use that seCompareLinks when user onChange in the input.
You can see it working here: https://codesandbox.io/s/practical-dewdney-wj7j2?file=/src/App.js:0-543
import React from 'react';
import "./styles.css";
export default function App() {
const [comparelinks, setComparelinks] = React.useState('default value');
const handleChange = (e) => {
setComparelinks(e.target.value);
}
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<input type="search" class="navBarSearchForm3 form-control rounded searchterm2" placeholder="search term" value={comparelinks} onChange={handleChange}></input>
</div>
);
}
You have to handle the update of the input as well, considering you use hooks:
const [comparelinks, setCompareLinks] = useState()
const handleChange = (event) => {
setCompareLinks(event.target.value)
}
<input type="search" class="navBarSearchForm3 form-control rounded searchterm2" placeholder="search term" value={comparelinks} onChange={handleChange}></input>
I am developing a Vuejs application within which I have a field. For this field, users can provide the values and this field expands dynamically based on the user-provided values.
The field name is extensions, initially an Add Extension button will be displayed. With on click of the button, a bootstrap modal will be displayed which has 3 fields: namespace (text), localname (text), datatype(dropdown: string/complex). If the datatype is string then a simple text field will be displayed. However, if the datatype is complex then another button should be displayed and with on click of the button again the same modal is displayed with fields and the process continues. So the created JSON based on this will expand horizontally and vertically.
I am able to complete the first iteration and display the elements to users on the front end. However, for further iteration, I am not understanding how to achieve it using the recursive approach. Since I don't know how many times users may create the extensions I need to have an approach that dynamically does this.
Can someone please help me how to create and display the JSONarray using Vuejs which expands horizontally and vertically?
Following is the code I have so far:
<template>
<div class="container-fluid">
<div class="row">
<div class="col-md-3">
<span>Extensions</span>
<button class="form-control" #click="createExtensions()">
Add Another
</button>
</div>
</div>
<div v-for="extension in extensionList" :key="extension.ID" class="form-inline">
<span>{{ extension.namespace+ ":"+extension.localName }}</span>
<input v-if="extension.dataType == 'string'" type="text" #input="AddExtensionText($event,extension.ID)">
<button v-if="extension.dataType == 'complex'" #click="AddComplextExtension(extension.ID)">
Add another
</button>
</div>
<b-modal
id="Extension"
title="Add Another Element"
size="lg"
width="100%"
:visible="showModal"
>
<b-form id="AddExtension" #submit.prevent="submitExtension">
<div class="form-group">
<label for="message-text" class="col-form-label">Namespace URI:</label>
<input
v-model="extension.namespace"
type="text"
class="form-control"
required
>
</div>
<div class="form-group">
<label for="message-text" class="col-form-label">Local Name:</label>
<input
v-model="extension.localName"
type="text"
class="form-control"
required
>
</div>
<div class="form-group">
<label for="AddExtensionDataType" class="col-form-label">Data Type:</label>
<b-form-select v-model="extension.dataType" class="form-control">
<b-form-select-option value="string">
String
</b-form-select-option>
<b-form-select-option value="complex">
Complex
</b-form-select-option>
</b-form-select>
</div>
</b-form>
<template #modal-footer="{ cancel }">
<b-btn #click="cancel">
Cancel
</b-btn>
<b-btn variant="primary" type="submit" form="AddExtension">
OK
</b-btn>
</template>
</b-modal>
</div>
</template>
<script>
export default {
data () {
return {
extensionList: [],
extensionCount: 0,
extension: {
namespace: '',
localName: '',
dataType: 'string'
},
showModal: false
}
},
methods: {
// Method to create extensions and add
createExtensions () {
this.showModal = true
},
// Function to submit the each extension
submitExtension () {
this.showModal = false
const extensionObj = {}
extensionObj.ID = this.extensionCount
extensionObj.namespace = this.extension.namespace
extensionObj.localName = this.extension.localName
extensionObj.dataType = this.extension.dataType
this.extensionList.push(extensionObj)
this.extensionCount++
},
// On addition of the input field value update List
AddExtensionText (event, extensionID) {
const extension = this.extensionList.filter(ex => ex.ID === extensionID)[0]
if (typeof extension !== 'undefined') {
extension.text = (typeof event.target.value !== 'undefined') ? event.target.value : ''
}
},
// Add complex extension
AddComplextExtension (extensionID) {
this.showModal = true
}
}
}
</script>
<style>
</style>
This is the initial field I have:
This is what I want to achieve where everything is created dynamically and JSON expands both horizontally and vertically:
Can someone please let me know how to create such dynamic JSON using Vuejs and display the same in the frontend.
To display data recursively, you need to use recursive components.
Abstract your v-for code into another component file (let's call it NodeComponent.vue). Pass your extensionList to this component, then inside this component, add another NodeComponent for each extension which has type complex.
Since your extension would be another array if it is complex, you can pass it directly into this NodeComponent as a prop and let recursion work its magic.
NodeComponent.vue
<template>
<div>
<div
v-for="extension in extensionList"
:key="extension.ID"
class="form-inline"
>
<span>{{ extension.namespace + ":" + extension.localName }}</span>
<input
v-if="extension.dataType == 'string'"
type="text"
#input="$emit('AddExtensionText', {$event, id: extension.ID}"
/>
<NodeComponent v-if="extention.dataType == 'complex'" :extentionList="extension" #AddExtensionText="AddExtensionText($event)"/>
<button
v-if="extension.dataType == 'complex'"
#click="AddComplextExtension(extension.ID)"
>
Add another
</button>
</div>
</div>
</template>
<script>
export default {
props: {
extensionList: Array,
extension: Object,
},
methods: {
AddComplextExtension(extensionID) {
// Emit event on root to show modal, or use this.$bvModal.show('modal-id') or create a dynamic modal, see: https://bootstrap-vue.org/docs/components/modal#message-box-advanced-usage
}
AddExtensionText({ value, id }) {
const i = this.extensionList.findIndex((el) => el.ID === id);
this.$set(extensionList, i, value);
}
}
};
</script>
Note that I emit a custom event from child NodeComponents on changing input text so that the parent can make this change in its extensionList array, using this.$set to maintain reactivity.
EDIT: If you want to add new Node components:
You need to have a parent component that holds the first NodeComponent in it. In here you'll define the modal (if you define it inside NodeComponent, you'll have a separate modal reference for each NodeComponent. Judging from your code you're probably using Bootstrap-Vue, it injects modals lazily when shown, so I don't think this will affect your performance too much, but it still doesn't feel like good code.). You need to emit event on root to show the modal. You need to send the extensionList as payload with this event like this: this.$root.emit('showModal', extensionList). In you parent component you can listen to the event and show the modal. Now inside your submitExtension function, you can use this extensionList and push a new object to it. The corresponding NodeComponent will update itself since arrays are passed by reference.
this.$root.on('showModal`, (extensionList) => {
this.editExtensionList = extensionList;
showModal = true;
}
submitExtension() {
this.showModal = false
const extensionObj = {}
extensionObj.ID = this.extensionCount
extensionObj.namespace = this.extension.namespace
extensionObj.localName = this.extension.localName
extensionObj.dataType = this.extension.dataType
this.editExtensionList.push(extensionObj)
this.extensionCount++
}
All being said, at this point it might be worthwhile to invest in implementing a VueX store where you have a global extentionList and define mutations to it.
Fails to run first if condition.
So once component is loaded it shows "Student Progress related Stats" and when once topic-link is set(in some other component which is removed too properly) it shows second display. Everything works fine till now but when topic-link is removed if fails to run first if condition and shows second display still. Basically I want to change my component view based on topic-link is there or not.
import React, { Component } from "react";
import {connect} from 'react-redux';
import {getQuestionsList} from '../../store/actions/questionActions';
class Test extends Component {
render(){
let display;
let topicLink = localStorage.getItem('topic-link');
if(!topicLink){
display =
<div style={{textAlign:'center',
fontSize:'22px'}}>
<p>Student Progress related Stats</p>
</div>
}
else if(topicLink){
display =
this.props.questions.map(question => (
<div key={question.id} style={{border:'1px solid #000',marginBottom:'15px'}}>
<div dangerouslySetInnerHTML={{__html: question.direction}} />
<div dangerouslySetInnerHTML={{__html: question.question}} />
<div>
<form>
<input type="radio" name="option" value="(A)"/>{question.option_a}<br/>
<input type="radio" name="option" value="(B)"/>{question.option_b}<br/>
<input type="radio" name="option" value="(C)"/>{question.option_c}<br/>
<input type="radio" name="option" value="(D)"/>{question.option_d}
</form>
</div>
</div>
))
}
return (
<div>
{display}
</div>
);
}
};
const mapStateToProps = state => {
return {
questions: state.questions.items,
}
}
export default connect(mapStateToProps, {getQuestionsList})(Test);
Here where i set the localStorage. On Every onTopicClick I get the desired output(meaning second if statement runs and it updates the component). Only when localStorage is removed it stays on the second if statement which not be the case(it should run first if statement). I have two fucntions(in seperate components) like onSectionClick and onTopicClick like this:
onTopicClick = () => {
this.props.getQuestionsList(this.props.topicId);
let topicName = this.props.name;
topicName = topicName.replace(/\s+/g, '-').toLowerCase();
let topicLink = "/updates/daily-practice-questions/" + this.props.sectionName + "/" + topicName;
this.props.history.push(topicLink);
localStorage.setItem('topic-link', topicLink);
}
onSectionClick = () => {
this.setState(prevState => ({
isOpened: !prevState.isOpened
}));
let sectionName = this.props.name;
sectionName = sectionName.replace(/\s+/g, '-').toLowerCase();
this.setState({sectionName: sectionName});
this.props.history.push("/updates/daily-practice-questions/" + sectionName);
if(this.state.isOpened){
this.props.history.push("/updates/daily-practice-questions");
localStorage.removeItem('topic-link')
}
}
One of the hardest topics in data communication probably.
let topicLink = localStorage.getItem('topic-link'); This line is the money line. Basically, you already identified the driver of this component, for instance, topicLink.
The next thing is to make sure this component use this variable as a prop input. I noticed you are using redux already, therefore this variable could be part of the state variables there.
Next step will be to see if your store can update this variable from localStorage. This probably is another topic, for instance, check the localStorage every 5 seconds, and then update the store variable.
However, it's not a good idea to sync components via a localStorage variable, instead, you should bootstrap your code and save this localStorage variable as a state variable first and store in your redux for example.
So either way, input prop is the first step to go, it'll help you test the code as well.
I'm trying to create a simple form with react, but facing difficulty having the data properly bind to the defaultValue of the form.
The behavior I'm looking for is this:
When I open my page, the Text input field should be filled in with the text of my AwayMessage in my database. That is "Sample Text"
Ideally I want to have a placeholder in the Text input field if the AwayMessage in my database has no text.
However, right now, I'm finding that the Text input field is blank every time I refresh the page. (Though what I type into the input does save properly and persist.) I think this is because the input text field's html loads when the AwayMessage is an empty object, but doesn't refresh when the awayMessage loads. Also, I'm unable to specify a default value for the field.
I removed some of the code for clarity (i.e. onToggleChange)
window.Pages ||= {}
Pages.AwayMessages = React.createClass
getInitialState: ->
App.API.fetchAwayMessage (data) =>
#setState awayMessage:data.away_message
{awayMessage: {}}
onTextChange: (event) ->
console.log "VALUE", event.target.value
onSubmit: (e) ->
window.a = #
e.preventDefault()
awayMessage = {}
awayMessage["master_toggle"]=#refs["master_toggle"].getDOMNode().checked
console.log "value of text", #refs["text"].getDOMNode().value
awayMessage["text"]=#refs["text"].getDOMNode().value
#awayMessage(awayMessage)
awayMessage: (awayMessage)->
console.log "I'm saving", awayMessage
App.API.saveAwayMessage awayMessage, (data) =>
if data.status == 'ok'
App.modal.closeModal()
notificationActions.notify("Away Message saved.")
#setState awayMessage:awayMessage
render: ->
console.log "AWAY_MESSAGE", this.state.awayMessage
awayMessageText = if this.state.awayMessage then this.state.awayMessage.text else "Placeholder Text"
`<div className="away-messages">
<div className="header">
<h4>Away Messages</h4>
</div>
<div className="content">
<div className="input-group">
<label for="master_toggle">On?</label>
<input ref="master_toggle" type="checkbox" onChange={this.onToggleChange} defaultChecked={this.state.awayMessage.master_toggle} />
</div>
<div className="input-group">
<label for="text">Text</label>
<input ref="text" onChange={this.onTextChange} defaultValue={awayMessageText} />
</div>
</div>
<div className="footer">
<button className="button2" onClick={this.close}>Close</button>
<button className="button1" onClick={this.onSubmit}>Save</button>
</div>
</div>
my console.log for AwayMessage shows the following:
AWAY_MESSAGE Object {}
AWAY_MESSAGE Object {id: 1, company_id: 1, text: "Sample Text", master_toggle: false}
Another way of fixing this is by changing the key of the input.
<input ref="text" key={this.state.awayMessage ? 'notLoadedYet' : 'loaded'} onChange={this.onTextChange} defaultValue={awayMessageText} />
Update:
Since this get upvotes, I will have to say that you should properly have a disabled or readonly prop while the content is loading, so you don't decrease the ux experience.
And yea, it is most likely a hack, but it gets the job done.. ;-)
defaultValue is only for the initial load
If you want to initialize the input then you should use defaultValue, but if you want to use state to change the value then you need to use value. Personally I like to just use defaultValue if I'm just initializing it and then just use refs to get the value when I submit. There's more info on refs and inputs on the react docs, https://facebook.github.io/react/docs/forms.html and https://facebook.github.io/react/docs/working-with-the-browser.html.
Here's how I would rewrite your input:
awayMessageText = if this.state.awayMessage then this.state.awayMessage.text else ''
<input ref="text" onChange={this.onTextChange} placeholder="Placeholder Text" value={#state.awayMessageText} />
Also you don't want to pass placeholder text like you did because that will actually set the value to 'placeholder text'. You do still need to pass a blank value into the input because undefined and nil turns value into defaultValue essentially. https://facebook.github.io/react/tips/controlled-input-null-value.html.
getInitialState can't make api calls
You need to make api calls after getInitialState is run. For your case I would do it in componentDidMount. Follow this example, https://facebook.github.io/react/tips/initial-ajax.html.
I'd also recommend reading up on the component lifecycle with react. https://facebook.github.io/react/docs/component-specs.html.
Rewrite with modifications and loading state
Personally I don't like to do the whole if else then logic in the render and prefer to use 'loading' in my state and render a font awesome spinner before the form loads, http://fortawesome.github.io/Font-Awesome/examples/. Here's a rewrite to show you what I mean. If I messed up the ticks for cjsx, it's because I normally just use coffeescript like this, .
window.Pages ||= {}
Pages.AwayMessages = React.createClass
getInitialState: ->
{ loading: true, awayMessage: {} }
componentDidMount: ->
App.API.fetchAwayMessage (data) =>
#setState awayMessage:data.away_message, loading: false
onToggleCheckbox: (event)->
#state.awayMessage.master_toggle = event.target.checked
#setState(awayMessage: #state.awayMessage)
onTextChange: (event) ->
#state.awayMessage.text = event.target.value
#setState(awayMessage: #state.awayMessage)
onSubmit: (e) ->
# Not sure what this is for. I'd be careful using globals like this
window.a = #
#submitAwayMessage(#state.awayMessage)
submitAwayMessage: (awayMessage)->
console.log "I'm saving", awayMessage
App.API.saveAwayMessage awayMessage, (data) =>
if data.status == 'ok'
App.modal.closeModal()
notificationActions.notify("Away Message saved.")
#setState awayMessage:awayMessage
render: ->
if this.state.loading
`<i className="fa fa-spinner fa-spin"></i>`
else
`<div className="away-messages">
<div className="header">
<h4>Away Messages</h4>
</div>
<div className="content">
<div className="input-group">
<label for="master_toggle">On?</label>
<input type="checkbox" onChange={this.onToggleCheckbox} checked={this.state.awayMessage.master_toggle} />
</div>
<div className="input-group">
<label for="text">Text</label>
<input ref="text" onChange={this.onTextChange} value={this.state.awayMessage.text} />
</div>
</div>
<div className="footer">
<button className="button2" onClick={this.close}>Close</button>
<button className="button1" onClick={this.onSubmit}>Save</button>
</div>
</div>
That should about cover it. Now that is one way to go about forms which uses state and value. You can also just use defaultValue instead of value and then use refs to get the values when you submit. If you go that route I would recommend you have an outer shell component (usually referred to as high order components) to fetch the data and then pass it to the form as props.
Overall I'd recommend reading the react docs all the way through and do some tutorials. There's lots of blogs out there and http://www.egghead.io had some good tutorials. I have some stuff on my site as well, http://www.openmindedinnovations.com.
it's extremely simple, make defaultValue and key the same:
<input defaultValue={myVal} key={myVal}/>
This is one of the recommended approaches at https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#recommendation-fully-uncontrolled-component-with-a-key
To force the defaultValue to re-render all you need to do is change the key value of the input itself. here is how you do it.
<input
type="text"
key={myDynamicKey}
defaultValue={myDynamicDefaultValue}
placeholder="It works"/>
Maybe not the best solution, but I'd make a component like below so I can reuse it everywhere in my code. I wish it was already in react by default.
<MagicInput type="text" binding={[this, 'awayMessage.text']} />
The component may look like:
window.MagicInput = React.createClass
onChange: (e) ->
state = #props.binding[0].state
changeByArray state, #path(), e.target.value
#props.binding[0].setState state
path: ->
#props.binding[1].split('.')
getValue: ->
value = #props.binding[0].state
path = #path()
i = 0
while i < path.length
value = value[path[i]]
i++
value
render: ->
type = if #props.type then #props.type else 'input'
parent_state = #props.binding[0]
`<input
type={type}
onChange={this.onChange}
value={this.getValue()}
/>`
Where change by array is a function accessing hash by a path expressed by an array
changeByArray = (hash, array, newValue, idx) ->
idx = if _.isUndefined(idx) then 0 else idx
if idx == array.length - 1
hash[array[idx]] = newValue
else
changeByArray hash[array[idx]], array, newValue, ++idx
Related issue
Setting defaulValue on control din't not update the state.
Doing reverse works perfectly:
Set state to default value, and the control UI gets updated correctly as if defaulValue was given.
Code:
let defaultRole = "Owner";
const [role, setRole] = useState(defaultRole);
useEffect(() => {
setMsg(role);
});
const handleChange = (event) => {
setRole(event.target.value );
};
// ----
<TextField
label="Enter Role"
onChange={handleChange}
autoFocus
value={role}
/>
Define a state for your default value
Surround your input with a div and a key prop
Set the key value to the same value as the defaultValue of the input.
Call your setDefaultValue defined at the step 1 somewhere to re-render your component
Example:
const [defaultValue, setDefaultValue] = useState(initialValue);
useEffect(() => {
setDefaultValue(initialValue);
}, false)
return (
<div key={defaultValue}>
<input defaultValue={defaultValue} />
</div>
)
Give value to parameter "placeHolder".
For example :-
<input
type="text"
placeHolder="Search product name."
style={{border:'1px solid #c5c5c5', padding:font*0.005,cursor:'text'}}
value={this.state.productSearchText}
onChange={this.handleChangeProductSearchText}
/>
Use value instead of defaultValue and change the value of the input with the onChange method.