I have two ReactJS components, the first one will display a grid. When a row is clicked, it will populate the 2 component with Name.
But when I try to edit the input box, it cannot be changed. What are the proper way in ReactJS to handle this.
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = { formName : ''}
this.handleFormName = this.handleFormName.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleFormName(e) {
console.log(e.target.value);
this.setState({ formName: e.target.value });
}
handleSubmit(event) {
console.log(this.state.formName);
axios.post(this.props.UrlPost, { FirstName: this.state.formName })
.then(function (response) {
});
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.props.Name} onChange={this.handleFormName} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
class ComponentWithGriddle extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedRowId: 0,
};
}
onRowClick(row) {
console.log(row.props.data.name);
this.setState({ selectedRowId: row.props.data.id });
this.props.handleNameChange(row.props.data.name);
}
render() {
var rowMetadata = {bodyCssClassName: rowData => (rowData.id === this.state.selectedRowId ? "selected" : '')};
//console.log(rowMetadata);
return (
<Griddle results={fakeData} onRowClick={this.onRowClick.bind(this)} columnMetadata={columnMeta} useFixedHeader={true} rowMetadata={rowMetadata} useGriddleStyles={true} />
);
}
}
class RootFrame extends React.Component {
constructor(props) {
super(props);
this.handleNameChange = this.handleNameChange.bind(this);
this.state = { Name: '' };
}
handleNameChange(name)
{
this.setState({ Name: name });
}
render() {
return (
<div>
<ComponentWithGriddle Name={this.state.Name} handleNameChange={this.handleNameChange} />
<NameForm UrlPost={this.props.UrlPost} Name={this.state.Name}/>
</div>
);
};
}
var emptyData = [];
var columnMeta = [
{
"columnName": "id",
"order": 1,
"locked": false,
"visible": true,
"displayName": "ID"
},
{
"columnName": "name",
"order": 2,
"locked": false,
"visible": true,
"displayName": "Name"
},
{
"columnName": "city",
"order": 3,
"locked": false,
"visible": true
},
{
"columnName": "state",
"order": 4,
"locked": false,
"visible": true
},
{
"columnName": "country",
"order": 5,
"locked": false,
"visible": true
},
{
"columnName": "company",
"order": 6,
"locked": false,
"visible": true
},
{
"columnName": "favoriteNumber",
"order": 7,
"locked": false,
"visible": true,
"displayName": "Favorite Number"
}
];
var rowMeta =
{
"key": "id"
};
var propertyGridMeta = [
{
"columnName": "property",
"order": 1,
"locked": false,
"visible": true,
"cssClassName": "properties-name",
"displayName": "Property"
},
{
"columnName": "description",
"order": 2,
"locked": false,
"visible": true,
"cssClassName": "properties-description",
"displayName": "Description"
},
{
"columnName": "type",
"order": 3,
"locked": false,
"visible": true,
"cssClassName": "properties-type",
"displayName": "Type"
},
{
"columnName": "default",
"order": 4,
"locked": false,
"visible": true,
"cssClassName": "properties-default",
"displayName": "Default"
}
]
var fakeData = [
{
"id": 0,
"name": "Mayer Leonard",
"city": "Kapowsin",
"state": "Hawaii",
"country": "United Kingdom",
"company": "Ovolo",
"favoriteNumber": 7
},
{
"id": 1,
"name": "Koch Becker",
"city": "Johnsonburg",
"state": "New Jersey",
"country": "Madagascar",
"company": "Eventage",
"favoriteNumber": 2
},
{
"id": 2,
"name": "Lowery Hopkins",
"city": "Blanco",
"state": "Arizona",
"country": "Ukraine",
"company": "Comtext",
"favoriteNumber": 3
},
];
ReactDOM.render(<RootFrame UrlPost={'Home/SaveData'}/>,
document.getElementById("demoForm"));
Essentially, I am trying to select a row, at the same time be able to change the name and click submit.
Selected row from grid
From the "Name" field, user can change the name.
Your input doesnt work because you are setting a static value this.props.Name for the input. In order to edit your input and still set the initial value with this.props.Name you should make use of state
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = { formName : '', inputName: ''}
this.handleFormName = this.handleFormName.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount() {
this.setState({inputName: this.props.Name});
}
handleFormName(e) {
console.log(e.target.value);
this.setState({ formName: e.target.value });
}
handleSubmit(event) {
console.log(this.state.formName);
this.setState({inputName: event.target.value});
axios.post(this.props.UrlPost, { FirstName: this.state.formName })
.then(function (response) {
});
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.inputName} onChange={this.handleFormName.bind(this)} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
Related
I have select and input component with made by buefy. Everything is ok till I realize how can I get the data.
I'm sort of new on vuejs. So I will be glad if you help me out.
Maybe I can do with Javascript but how?
I'm getting dynamic form from backend
Here is my codes;
Input.vue
<template>
<b-field :label="fieldLabel">
<b-input
:name="inputName"
:type="inputType"
:maxlength="inputType == 'textarea' ? 200 : null"
></b-input>
</b-field>
</template>
<script>
export default {
name: "Input",
props: {
inputType: {
type: String,
required: true,
default: "text",
},
inputName: {
type: String,
required: true,
},
fieldLabel: {
type: String,
required: true,
}
}
};
</script>
Home.vue
<template>
<div class="container is-max-desktop wrapper">
<div v-for="element in offer" :key="element.id">
<Input
v-model="element.fieldValue"
:fieldLabel="element.fieldLabel"
:inputType="element.fieldType"
:inputName="element.fieldName"
v-if="element.fieldType != 'select'"
class="mb-3"
/>
<Select
v-model="element.fieldValue"
:fieldLabel="element.fieldLabel"
:options="element.infoRequestFormOptions"
:selectName="element.fieldName"
v-if="element.fieldType == 'select'"
class="mb-3"
/>
</div>
<b-button type="is-danger" #click="getOffer()">GET</b-button>
</div>
</template>
<script>
import axios from "axios";
import Select from "../components/Select.vue";
import Input from "../components/Input.vue";
export default {
name: "Home",
data() {
return {
offer: [],
};
},
components: {
Select,
Input,
},
methods: {
getOfferForm() {
axios({
method: "get",
url: `/GETDYNAMICFORM`,
})
.then((response) => {
this.offer = response.data;
})
.catch(() => {
this.$buefy.toast.open({
duration: 3000,
message: "oops",
position: "is-bottom",
type: "is-danger",
});
});
},
getOffer() {
console.log(this.offer);
},
},
created() {
this.getOfferForm();
},
};
</script>
Example Dynamic Form Response like;
[ { "id": 58, "fieldLabel": "Name Surname", "providerLabel": "Name
Surname", "fieldName": "nmsrnm", "fieldType": "text", "fieldValue":
null, }, { "id": 60, "fieldLabel": "E-mail", "providerLabel":
"E-mail", "fieldName": "e_mail_60", "fieldType": "email",
"fieldValue": null, }, { "id": 2, "fieldLabel": "Budget",
"providerLabel": "Budget", "fieldName": "bdget", "fieldType":
"select", "fieldValue": "", "infoRequestFormOptions": [ { "id": 1,
"orderNum": 0, "optionValue": 0, "optionText": "Select", "minValue":
null, "maxValue": null }, { "id": 2, "orderNum": 1, "optionValue": 1,
"optionText": "10-30", "minValue": 10, "maxValue": 30 } ] } ]
I have a REST call which returns different sets of result. Each data set contains data for different parts of the page. It contains nested array of objects and I am not sure how do I easily display the name and value pairs from the rest call to my page depending on the certain section.
Here's what the REST call response looks like:
{
"dstrSpecificHelpRs": [
{
"row": [
{
"name": "HELP_TEXT_TITLE_NM",
"value": "TestAgain and again and again and again andagain50",
"link": {
"exists": false,
"linkType": null,
"linkId": null
}
},
{
"name": "HELP_TEXT_ID",
"value": "100114",
"link": {
"exists": false,
"linkType": null,
"linkId": null
}
},
{
"name": "Help",
"value": "Help",
"link": {
"exists": true,
"linkType": "url",
"linkId": "www.google.com"
}
}
]
},
{
"row": [
{
"name": "HELP_TEXT_TITLE_NM",
"value": "Testing Helpline Page and 2301 DisasterONLY",
"link": {
"exists": false,
"linkType": null,
"linkId": null
}
},
{
"name": "HELP_TEXT_ID",
"value": "100174",
"link": {
"exists": false,
"linkType": null,
"linkId": null
}
},
{
"name": "Help",
"value": "Help",
"link": {
"exists": true,
"linkType": "url",
"linkId": "www.google.com"
}
}
]
}
],
"rgstInfoRs": [
{
"row": [
{
"name": "PREFIX_NM",
"value": "MS",
"link": {
"exists": false,
"linkType": null,
"linkId": null
}
},
{
"name": "FST_NM",
"value": "MITZI",
"link": {
"exists": false,
"linkType": null,
"linkId": null
}
}
]
}
],
"insSettlementRs": [
{
"row": [
{
"name": "ON_FILE",
"value": "0",
"link": {
"exists": false,
"linkType": null,
"linkId": null
}
}
]
}
],
"dstrOptionRs": [
{
"row": [
{
"name": "DSTR_OPTION",
"value": "1",
"link": {
"exists": false,
"linkType": null,
"linkId": null
}
},
{
"name": "HELPLINE_AREA_CD",
"value": "818",
"link": {
"exists": false,
"linkType": null,
"linkId": null
}
},
{
"name": "HELPLINE_PHN_NR",
"value": "5055511",
"link": {
"exists": false,
"linkType": null,
"linkId": null
}
}
]
}
],
"correspondenceOutRs": [
{
"row": [
{
"name": "SUMMARY_CD_TX",
"value": "SUPER,SEAL,APPR_INTRO,APPROVAL,ECNA",
"link": {
"exists": false,
"linkType": null,
"linkId": null
}
}
]
},
{
"row": [
{
"name": "SUMMARY_CD_TX",
"value": "9069CL,SEAL",
"link": {
"exists": false,
"linkType": null,
"linkId": null
}
},
{
"name": "ASTN_PGM_CD",
"value": "MISC",
"link": {
"exists": false,
"linkType": null,
"linkId": null
}
},
{
"name": "GENERATED_DT",
"value": "2020-09-03 14:08:10.0",
"link": {
"exists": false,
"linkType": null,
"linkId": null
}
}
]
}
],
"statePhoneRs": [],
"insAssistanceRs": [
{
"row": [
{
"name": "DOC_ID",
"value": "",
"link": {
"exists": false,
"linkType": null,
"linkId": null
}
},
{
"name": "RGSN_ID",
"value": "370002900",
"link": {
"exists": false,
"linkType": null,
"linkId": null
}
}
]
}
]
}
and here's what I have working so far:
class App extends React.Component {
constructor(props) {
super(props);
this.setState = {
data: []
};
}
componentDidMount() {
fetch("https://demo3531217.mockable.io/getRegistrationData")
.then((res) => res.json())
.then((res) => {
const responseData = this.getPayloadObject(res);
this.setState({
data: responseData
});
})
.catch((error) => console.error(error));
}
getPayloadObject = (action) => {
console.log(action);
Object.keys(action).forEach((key) => {
if (typeof action[key] === "object") {
const payload = action[key];
let result = _.map(
payload,
(data, i) => {
const row = data["row"];
const rowData = _.map(row, (item) => {
return {
name: item.name,
value: item.value
};
});
console.log("rowData ", rowData);
},
[]
);
return result;
}
});
};
render() {
return (
<div>
<div>
<h1>Section 1</h1>
</div>
<div>
<h1>Section 2</h1>
</div>
</div>
);
}
}
if I wanted to display certain data for specific section how do I so in my page? Can someone please help?
link for sandbox can be found here: https://codesandbox.io/s/vibrant-sid-i140l?file=/src/App.js:75-1304
Let's try breaking the state into multiple sections. Something like this.
constructor(props) {
super(props);
this.state = {
dstrSpecificHelpRs: [],
rgstInfoRs: [],
insSettlementRs: [],
dstrOptionRs: [],
correspondenceOutRs: [],
statePhoneRs: [],
insAssistanceRs: []
};
}
Then, let's deconstruct the API response and set individual items in the state.
componentDidMount() {
fetch("https://demo3531217.mockable.io/getRegistrationData")
.then((res) => res.json())
.then((res) => {
// const responseData = this.getPayloadObject(res);
const {
dstrSpecificHelpRs,
rgstInfoRs,
insSettlementRs,
dstrOptionRs,
correspondenceOutRs,
statePhoneRs,
insAssistanceRs
} = res;
this.setState({
dstrSpecificHelpRs: dstrSpecificHelpRs,
rgstInfoRs: rgstInfoRs,
insSettlementRs: insSettlementRs,
dstrOptionRs: dstrOptionRs,
correspondenceOutRs: correspondenceOutRs,
statePhoneRs: statePhoneRs,
insAssistanceR: insAssistanceRs
});
})
.catch((error) => console.error(error));
}
Now all you have to do is find a way to loop through individual items in the state. Something like this
render() {
const dstrSpecificHelpRs = this.state.dstrSpecificHelpRs.map(row => row["row"]);
console.log("Sample = ", sample);
return (
<div>
<div>
<h1>Section 1</h1>
<div>
{dstrSpecificHelpRs.map(item => item.map(key => <p>{key.name}</p>))}
</div>
</div>
<div>
<h1>Section 2</h1>
</div>
</div>
);
}
This is by no means a perfect solution though. If you could change the response model, that would have been sweet!.
I want to list plans by month, but my code is not working. I want to set a header, for example September, and then list all plans for that month.
My data:
const plans = [
{
"done": true,
"datetime": "2020-01-22T01:00:00+00:00",
},
{
"done": true,
"datetime": "2020-01-25T03:00:00+00:00",
"name": "Read a book",
},
{
"done": false,
"datetime": "2020-01-29T01:00:00+00:00",
"name": "Travel",
},
{
"done": false,
"datetime": "2020-02-02T02:00:00+00:00",
"name": "Vaccations",
},
{
"done": false,
"datetime": "2020-03-02T01:00:00+00:00",
"name": "No plans",
},
{
"done": false,
"datetime": "2020-04-02T01:00:00+00:00",
"name": "Do excersice",
},
{
"done": false,
"datetime": "2020-05-04T01:00:00+00:00",
"name": "Work",
},
{
"done": false,
"datetime": "2020-05-04T01:00:00+00:00",
"name": "Watch netflix",
},
{
"done": false,
"datetime": "2020-05-04T01:00:00+00:00",
"name": "No plans",
}
]
And this is in my App.js (React):
return (
<div className="App">
{
plans.map((todo, idx) => {
const currentMonth = moment(todo.datetime).format('MM')
const anotherMonth = plans[idx + 1] ? plans[idx + 1].datetime : undefined
const anotherMonthConverted = moment(anotherMonth).format('MM')
console.log(anotherMonthConverted)
return(
<div key={idx}>
<div>
{
currentMonth <= anotherMonth ? (
<div>
<div>{moment(currentMonth).format('MMM')}</div>
</div>
):(
<div>
</div>
)
}
<div >{todo.name}</div>
</div>
</div>
)
})
}
</div>
);
}
image output example
I would suggest grouping your plans to make it easier to render them in a clean way.
const groupedPlans = plans.reduce((acc, val) => {
const valMonth = moment(val.datetime).format('MMM');
const planGroup = acc.find(plan => plan.month === valMonth);
if (!planGroup) {
acc.push({ month: valMonth, plans: [val] })
} else {
planGroup.plans = [...planGroup.plans, val];
}
return acc;
}, [])
This would make your data look like this.
[
{
month: 'Jan',
plans: [
{
done: true,
datetime: '2020-01-22T01:00:00+00:00'
},
{
done: true,
datetime: '2020-01-25T03:00:00+00:00',
name: 'Read a book'
},
{
done: false,
datetime: '2020-01-29T01:00:00+00:00',
name: 'Travel'
}
]
},
{
month: 'Feb',
plans: [
{
done: false,
datetime: '2020-02-02T02:00:00+00:00',
name: 'Vaccations'
}
]
},
{
month: 'Mar',
plans: [
{
done: false,
datetime: '2020-03-02T01:00:00+00:00',
name: 'No plans'
}
]
},
{
month: 'Apr',
plans: [
{
done: false,
datetime: '2020-04-02T01:00:00+00:00',
name: 'Do excersice'
}
]
},
{
month: 'May',
plans: [
{
done: false,
datetime: '2020-05-04T01:00:00+00:00',
name: 'Work'
},
{
done: false,
datetime: '2020-05-04T01:00:00+00:00',
name: 'Watch netflix'
},
{
done: false,
datetime: '2020-05-04T01:00:00+00:00',
name: 'No plans'
}
]
}
]
And you can render it like this, or however you like.
return (
<div className="App">
{
groupedPlans.map((todo, idx) => {
return(
<div key={idx}>
<div>
<div>
<div>{todo.month}</div>
</div>
{todo.plans.map(plan => <div>{plan.name}</div>)}
</div>
</div>
)
})
}
</div>
);
}
You could use moment for this, it works well with date. So you could sort yur array this way :
const App = () => {
const plans = [
{
"done": false,
"datetime": "2020-05-04T01:00:00+00:00", // May as first item
"name": "Watch netflix",
},
{
"done": true,
"datetime": "2020-01-22T01:00:00+00:00",
},
{
"done": true,
"datetime": "2020-01-25T03:00:00+00:00",
"name": "Read a book",
},
{
"done": false,
"datetime": "2020-01-29T01:00:00+00:00",
"name": "Travel",
},
{
"done": false,
"datetime": "2020-02-02T02:00:00+00:00",
"name": "Vaccations",
},
{
"done": false,
"datetime": "2020-03-02T01:00:00+00:00",
"name": "No plans",
},
{
"done": false,
"datetime": "2020-05-04T01:00:00+00:00",
"name": "No plans",
},
{
"done": false,
"datetime": "2020-04-02T01:00:00+00:00",
"name": "Do excersice",
},
{
"done": false,
"datetime": "2020-05-04T01:00:00+00:00",
"name": "Work",
},
];
const orderedPlans = () => {
return plans.sort((a, b) => {
return moment(a.datetime) > moment(b.datetime)? 1 : -1; // handle the 0 case if you need, not done here
});
}
return <div>{orderedPlans().map(plan => <div>{plan.datetime}</div>)}</div>
}
render(<App />, document.getElementById('root'));
Here is a repro on stackblitz
Does anyone know how to set a colour range for this map? The variations between the values aren't large enough to notice the change in colour. So most of the values on the map appear to be in the same colour. I would either like to set my own colours or change much these colours vary by.
I cant find the answer on how to do this in react anywhere
import React, { Component } from 'react';
import AmCharts from "#amcharts/amcharts3-react";
import { connect } from "react-redux"
import { lookup } from 'country-data';
import * as adapter from "../Adapter.js";
class App extends Component {
constructor(props) {
super(props)
this.state = {
allCountriesHouseholdSpending: null,
selectedCoutry: null,
countrySpending: [{
"id": "AU",
"value": 4447100
},
{
"id": "US",
"value": 658188
}]
}
}
componentDidMount() {
adapter.getAllCountriesrtyHouseholdSpending()
.then(spendingData => this.setState({ allCountriesHouseholdSpending: spendingData }))
}
selectedCountrySpending = (country) => {
let selectedCity = country.mapObject.enTitle
if (selectedCity !== "Russia" && selectedCity !== "Venezuela" && selectedCity !== "Bolivia" && selectedCity !== "Svalbard and Jan Mayen" && selectedCity !== "Syria" && selectedCity !== "Tanzania" && selectedCity !== "Democratic Republic of Congo") {
// console.log(lookup.countries({ name: selectedCity }));
console.log(lookup.countries({ name: selectedCity })[0].alpha3);
console.log('selected!', selectedCity)
return selectedCity
}
}
render() {
const config = {
"type": "map",
"theme": "light",
"colorSteps": 10,
"dataProvider": {
"map": "worldLow",
"getAreasFromMap": true,
"areas": [{
"id": "AU",
"value": 658188.6034,
"selected": true
},
{
"id": "AT",
"value": 217653.4063
},
{
"id": "BE",
"value": 255659.6354
},
{
"id": "CA",
"value": 896977.0966
},
],
valueLegend: {
divId: "legenddiv",
left: 10,
bottom: 0,
minValue: "little",
maxValue: "a lot!"
},
},
"areasSettings": {
"selectedColor": "#CC0000",
"selectable": true,
"balloonText": "National Spending in [[title]]: <b>[[value]]</b>"
},
"listeners": [{
"event": "clickMapObject",
"method": (e) => {
console.log(e.mapObject.enTitle)
this.selectedCountrySpending(e)
}
}]
}
return (
<AmCharts.React style={{ width: "100%", height: "600px" }} options={config} />
);
}
}
const mapStateToProps = (state) => ({
})
const mapDispatchToProps = (dispatch) => ({
})
export default connect(mapStateToProps, mapDispatchToProps)(App)
You can customize the start and end colors by setting color and colorSolid in your areasSettings. You can also specify your own color by setting the color directly in the area object, e.g.
{
"id": "AU",
"value": 88323532,
"color": "#00ff00"
},
// ...
Note that setting getAreasFromMap: true essentially enables all the other areas as if they were defined in the dataProvider, which may not be what you want.
Here's a demo of color and colorSolid:
var chart = AmCharts.makeChart("chartdiv", {
"type": "map",
"theme": "light",
"colorSteps": 10,
"dataProvider": {
"map": "worldLow",
//"getAreasFromMap": true,
"areas": [{
"id": "AU",
"value": 658188.6034,
"selected": true
},
{
"id": "AT",
// "color": "#0000ff", //if you want to specify a color directly on an area
"value": 217653.4063
},
{
"id": "BE",
"value": 255659.6354
},
{
"id": "CA",
"value": 896977.0966
},
],
valueLegend: {
divId: "legenddiv",
left: 10,
bottom: 0,
minValue: "little",
maxValue: "a lot!"
},
},
"areasSettings": {
"color": "#880000",
"colorSolid": "#008888",
"selectedColor": "#CC0000",
"selectable": true,
"balloonText": "National Spending in [[title]]: <b>[[value]]</b>"
},
"listeners": [{
"event": "clickMapObject",
"method": (e) => {
}
}]});
html, body {
width: 100%;
height: 100%;
margin: 0px;
}
#chartdiv {
width: 100%;
height: 100%;
}
<script src="//www.amcharts.com/lib/3/ammap.js"></script>
<script src="//www.amcharts.com/lib/3/maps/js/worldLow.js"></script>
<script src="//www.amcharts.com/lib/3/themes/light.js"></script>
<div id="chartdiv"></div>
I have a simple data structure that lists some information. The problem is when I click the icon it opens the both content. I didn't find a solution how to open the current div.
class HotSpots extends React.Component {
constructor(props, context) {
super(props, context);
this.handleClick = this.handleClick.bind(this);
this.state = {
isOpen: false,
icon: 'fa-plus'
}
}
handleClick(event) {
console.log(this.state.isOpen, 'clicked');
this.setState({
isOpen: !this.state.isOpen,
icon: this.state.icon === 'fa-plus' ? 'fa-remove' : 'fa-plus'
})
}
render() {
//console.log(DATA.module.hotSpots.popups);
let items = DATA.module
.hotSpots
.popups.map(n =>
<div data-open={n.openByDefault}
data-coordx={n.config.coords[0]}
data-coordy={n.config.coords[1]}
data-enable-mobile-chords={n.config.useMobileCoords>
<a style={{
marginLeft: n.config.coords[0],
marginRight: n.config.coords[1]
}}
onClick={this.handleClick}>
<i className={`fa ${this.state.icon}`}></i>
</a>
<section data-is-open={this.state.isOpen} className="hotspot-detail">
<h2>{n.headline}</h2>
<p>{n.bodyCopy}</p>
</section>
</div>
);
return (
<section className="hotspots">
{console.log('status' + this.state.isOpen)}
<div>{items}</div>
</section>
)
}
}
class App extends React.Component {
constructor(props) {
super(props)
}
render() {
return <div name={this.props.name}
data-src-phone-highres={this.props.srcPhoneHighRes}
data-src-desktop={this.props.srcDesktop}
data-src-tablet-highres={this.props.srcTabletHighres}
data-src-phone={this.props.phone}
data-src-tablet={this.props.srcTablet}
data-src-desktop-highres={this.props.srcDesktopHighres}>
<HotSpots />
</div>
}
}
React.render( <App name={DATA.moduleName}
srcPhoneHighRes={DATA.images.desktop.externalUrl}
/>, app);
http://codepen.io/agriboz/pen/MyGOMG?editors=0010
Thanks in advance
As suggested by #XtremeCoder. You're not using the unique key identifier to trace your event. So in your react component pass the key in the click handler as follow onClick={this.handleClick.bind(this, i, n) Then include a new state i which can capture the current element clicked.
this.state = {
i: 0,
isOpen: false,
}
}
handleClick(i,n) {
this.setState({
i: i,
isOpen: !this.state.isOpen,
})
Now you can check for boolean on state change to display the content and change the icon
//for icon
<i className={`fa ${(this.state.isOpen && this.state.i==i ? 'fa-close' : 'fa-plus')}` }></i>
//for content
<section data-is-open={this.state.isOpen && this.state.i==i} className="hotspot-detail">
Here is the fully functioning code
let DATA = {
"module": {
"name": "E-Hot-PDP-X9405C-X9305C-Series-en_US-2015-03-M24-fullflush",
"masterName": "E-Hot-PDP-X9405C-X9305C-Series-en_GL-2015-03-M24-fullflush",
"maintenanceStructure": "Electronics/05_Televisions_and_Home_Cinema/01_Televisions/X9405C-X9305C-Series",
"_id": "urn:sony:module:editorial_hotspots:590359239320250140028",
"isLocalized": "false",
"masterId": "urn:sony:module:editorial_hotspots:1991426988117153303078371",
"version": "20160122080005",
"readyForLive": "No",
"mode": "full",
"editorialEnabled": "yes",
"type": "urn:sony:module:editorial_hotspots",
"createdAt": 1430303942648,
"updatedAt": 1453816025859,
"dependents": [
"urn:sony:media:image_family:1995667013507913303041209"
],
"config": {
"style": "dark-text-trans-box",
"background": "true",
"grouping": "group-middle",
"type": "full",
"variation": {
"background": "white"
},
"layout": {
"name": "full",
"text": "full",
"alignment": "center",
"columns": [
"12",
"0"
]
}
},
"hotSpots": {
"popups": [
{
"headline": "Full flush surface",
"alwaysOpen": "no",
"openByDefault": "no",
"bodyCopy": "With thin styling and a full flush screen surface, you’ll enjoy an immersive viewing experience.",
"dropShadow": "no",
"bullets": [],
"links": [],
"config": {
"size": "large",
"useMobileCoords": "no",
"hotSpotsIcon": "yes",
"dropShadow": "yes",
"coords": [
"10%",
"31%"
]
}
},
{
"headline": "Wedge",
"alwaysOpen": "no",
"openByDefault": "no",
"bodyCopy": "The unique shape has been designed to increase speaker capacity for a rich, full sound.",
"dropShadow": "no",
"bullets": [],
"links": [],
"config": {
"size": "large",
"useMobileCoords": "no",
"hotSpotsIcon": "yes",
"dropShadow": "yes",
"coords": [
"78.5%",
"49%"
]
}
}
],
"image": {
"type": "urn:sony:media:image_container",
"imageFamily": {
"name": "ImgF-PDP-X9405C-X9305C-Series-2015-03-M24-fullflush",
"masterName": "ImgF-PDP-X9405C-X9305C-Series-2015-03-M24-fullflush",
"maintenanceStructure": "Electronics/05_Televisions_and_Home_Cinema/01_Televisions/X9405C-X9305C-Series",
"_id": "urn:sony:media:image_family:1995667013507913303041209",
"isLocalized": "false",
"masterId": "urn:sony:media:image_family:1995667013507913303041209",
"localizedImageFamily": {
"name": "Loc-ImgF-PDP-XBR-X9405C-X9305C-Series-2015-03-M24-fullflush-SOLA",
"masterName": "Loc-ImgF-PDP-XBR-X9405C-X9305C-Series-2015-03-M24-fullflush-SOLA",
"maintenanceStructure": "Electronics/05_Televisions_and_Home_Cinema/01_Televisions/X9405C-X9305C-Series",
"_id": "urn:sony:localized_image_family:4512059944822603303579063",
"isLocalized": "false",
"masterId": "urn:sony:localized_image_family:4512059944822603303579063",
"locale": "all",
"version": "20150327064448",
"readyForLive": "No",
"type": "urn:sony:localized_image_family",
"createdAt": 1428930904784,
"updatedAt": 1428930906576,
"localizedImageFamilies": [
{
"imageFamily": {
"_id": "urn:sony:media:image_family:4492784185533253304288460",
"isLocalized": "false",
"locale": "all",
"maintenanceStructure": "Electronics/05_Televisions_and_Home_Cinema/01_Televisions/X9405C-X9305C-Series",
"masterId": "urn:sony:media:image_family:4492784185533253304288460",
"masterName": "ImgF-PDP-XBR-X9405C-X9305C-Series-2015-03-M24-fullflush-SOLA",
"name": "ImgF-PDP-XBR-X9405C-X9305C-Series-2015-03-M24-fullflush-SOLA",
"readyForLive": "No",
"type": "urn:sony:media:image_family",
"version": "20150327061216",
"createdAt": 1428930904949,
"updatedAt": 1428930906679,
"dependents": [],
"images": {
"desktop": {
"internalUrl": "https://prod-imgws.sony.eu/cms/images/Electronics/05_Televisions_and_Home_Cinema/01_Televisions/X9405C-X9305C-Series/E-Hot-PDP-X94-X93-en_GL-2015-03-M24-fullflush_XBR-desktop.jpg",
"md5": "90e6df273a7fe6ba9ef6411a83a995e3",
"mimeType": "image/jpg",
"status": "published",
"uploadAttempts": 1,
"uploadLastAttemptAt": 1428412207508,
"assetHandle": [
"a|23360418"
]
}
}
},
"locales": [
"es_AR",
"es_BO",
"pt_BR",
"es_CL",
"es_CO",
"es_CR",
"es_DO",
"es_EC",
"es_SV",
"es_GT",
"es_HN",
"es_MX",
"es_PA",
"es_PY",
"es_PE",
"es_PR",
"es_VE",
"es_NI"
]
}
],
"dependents": [
"urn:sony:media:image_family:4492784185533253304288460"
]
},
"locale": "all",
"version": "20150327075311",
"readyForLive": "No",
"type": "urn:sony:media:image_family",
"createdAt": 1428930904791,
"updatedAt": 1453731785919,
"dependents": [
"urn:sony:localized_image_family:4512059944822603303579063"
],
"images": {
"desktop": {
"internalUrl": "https://prod-imgws.sony.eu/cms/images/Electronics/05_Televisions_and_Home_Cinema/01_Televisions/X9405C-X9305C-Series/E-Hot-PDP-X94-X93-en_GL-2015-03-M24-fullflush_desktop.jpg",
"mimeType": "image/jpg",
"uploadAttempts": 1,
"uploadLastAttemptAt": 1427203801114,
"status": "published",
"md5": "93a8c28123eede1d42d33871e6553daf",
"assetHandle": [
"a|23104311"
],
"externalUrl": "//sonyglobal.scene7.com/is/image/gwtprod/93a8c28123eede1d42d33871e6553daf?fmt=jpeg"
},
"phone": {
"internalUrl": "https://prod-imgws.sony.eu/cms/images/Electronics/05_Televisions_and_Home_Cinema/01_Televisions/X9405C-X9305C-Series/E-Hot-PDP-X94-X93-en_GL-2015-03-M24-fullflush_phone.jpg",
"mimeType": "image/jpg",
"uploadAttempts": 1,
"uploadLastAttemptAt": 1427203801114,
"status": "published",
"md5": "40df54c624e5cebfa27fdae4d3207f5d",
"assetHandle": [
"a|23104300"
],
"externalUrl": "//sonyglobal.scene7.com/is/image/gwtprod/40df54c624e5cebfa27fdae4d3207f5d?fmt=jpeg"
},
"tablet": {
"internalUrl": "https://prod-imgws.sony.eu/cms/images/Electronics/05_Televisions_and_Home_Cinema/01_Televisions/X9405C-X9305C-Series/E-Hot-PDP-X94-X93-en_GL-2015-03-M24-fullflush_tablet.jpg",
"mimeType": "image/jpg",
"uploadAttempts": 1,
"uploadLastAttemptAt": 1427203801115,
"status": "published",
"md5": "5e9ab8ac405f170a5b24f1ccf1f71c1f",
"assetHandle": [
"a|23104304"
],
"externalUrl": "//sonyglobal.scene7.com/is/image/gwtprod/5e9ab8ac405f170a5b24f1ccf1f71c1f?fmt=jpeg"
}
}
}
}
},
"locale": "en_US",
"contentLocale": "en_US"
},
"moduleId": "editorial_hotspots_590359239320250140028",
"moduleName": "editorial_hotspots"
};
class HotSpots extends React.Component {
constructor(props, context) {
super(props, context);
//this.handleClick = this.handleClick.bind(this);
this.state = {
i: 0,
isOpen: false,
}
}
handleClick(i,n) {
this.setState({
i: i,
isOpen: !this.state.isOpen,
})
}
render() {
//console.log(DATA.module.hotSpots.popups);
let items = DATA.module
.hotSpots
.popups.map((n, i) =>
<div key={i}
onClick={this.handleClick.bind(this, i, n)} data-open={n.openByDefault}
data-coordx={n.config.coords[0]}
data-coordy={n.config.coords[1]}
data-enable-mobile-chords={n.config.useMobileCoords}>
<a style={{
marginLeft: n.config.coords[0],
marginRight: n.config.coords[1]
}}
>
<i className={`fa ${(this.state.isOpen && this.state.i==i ? 'fa-close' : 'fa-plus')}` }></i>
</a>
<section data-is-open={this.state.isOpen && this.state.i==i} className="hotspot-detail">
<h2>{n.headline}</h2>
<p>{n.bodyCopy}</p>
</section>
</div>
);
return (
<section className="hotspots">
{console.log('status' + this.state.isOpen)}
<div>{items}</div>
</section>
)
}
}
class App extends React.Component {
constructor(props) {
super(props)
}
render() {
return <div className={`${DATA.module.config.variation.background} ${DATA.module.config.type}`}
name={this.props.name}
data-src-desktop={this.props.srcDesktop}
data-src-phone={this.props.phone}
data-src-tablet={this.props.srcTablet}>
<HotSpots />
</div>
}
}
React.render( <App name={DATA.moduleName}
srcDesktop={DATA.module.hotSpots.image.imageFamily.images.desktop.externalUrl}
srcPhone={DATA.module.hotSpots.image.imageFamily.images.phone.externalUrl}
srcTablet={DATA.module.hotSpots.image.imageFamily.images.tablet.externalUrl}
/>, app);
Codepen:
http://codepen.io/anon/pen/WwJPLe?editors=0010
Hope it helps!
This is because you are associating same state(isOpen) to all the contents.
To show this modified your pen to this:
handleClick(event) {
this.setState({
parentClass :event.currentTarget.className,
isOpen: !this.state.isOpen,
icon: this.state.icon === 'fa-plus' ? 'fa-remove' : 'fa-plus'
})
}
render() {
let items = DATA.module
.hotSpots
.popups.map((n,i) =>
<div data-open={n.openByDefault}
data-coordx={n.config.coords[0]}
data-coordy={n.config.coords[1]}
data-enable-mobile-chords={n.config.useMobileCoords}>
<a style={{
marginLeft: n.config.coords[0],
marginRight: n.config.coords[1]
}}
onClick={this.handleClick} className={'parent'+i}>
<i className={`fa ${this.state.icon}`}></i>
</a>
<section data-is-open={this.state.isOpen && (this.state.parentClass===("parent"+i)) } className="hotspot-detail">
<h2>{n.headline}</h2>
<p>{n.bodyCopy}</p>
</section>
</div>
);
return (
<section className="hotspots">
{console.log('status' + this.state.isOpen)}
<div>{items}</div>
</section>
)
}
}
http://codepen.io/Debabrata89/pen/bpMOOM?editors=0010
Hope this helps