I am using react to produce a menu of options and currently I am only able to retrieve the last created menu option. Note that I am creating the options via looping, so this must be logic in my loop. Any advice will help.
var FilterMenu = React.createClass({
handleUserInput: function(filterText, selectedOption){
this.props.onUserInput(filterText, selectedOption);
},
render: function(){
return (
<div className="FilterMenu">
<FilterViews/>
<FilterItems
filterText={this.props.filterText}
selectedOptions={this.props.selectedOptions}
onUserInput={this.handleUserInput}
/>
</div>
)
}
});
var FilterViews = React.createClass({
render: function(){
return (<div className="FilterViews"></div>)
}
});
var FilterItems = React.createClass({
loadFiltersFromServer: function(){
ajaxServerRequest().then(fulfilled);
var self = this;
function fulfilled(response){
var filters = Object.keys(response[0]);
filters.length = 10;
self.setState({filters:filters});
}
},
getInitialState: function(){ //these are filters being loaded, not selected
return {filters:[]};
},
componentDidMount: function(){
this.loadFiltersFromServer();
},
handleChange: function(){
console.log('!!!145 - filter return',
this.refs.filterName.getDOMNode().innerText,
this.refs.filterOptionsInput.getDOMNode().value,
this.refs.filterTextInput.getDOMNode().value);
this.props.onUserInput(
this.refs.filterTextInput.getDOMNode().value,
this.refs.filterOptionsInput.getDOMNode().value
);
},
render: function(){
var self = this;
//console.log('FilterItems.this.props',this.props);
//var Cost = [].push(<option>{filter.slice(5,filter.length)}</option>);
var Cost = AppartmentCostRange.map(function(cost,index){
return( <option value={cost} ref="filterOptionsInput" key={index}>
{cost}
</option>)
});
var FilterItems = this.state.filters.map(function(filter, index){
return (
<div>
<span value={filter} ref="filterName" key={index}>{filter}</span>
<select>
{Cost}
</select>
</div>
)
});
return (
<div className="FilterItems">
<h3>Filter Items</h3>
Quick Search
<input
type="text"
placeholder="search.."
value={this.props.filterText}
ref="filterTextInput"
onChange={this.handleChange}
/>
<div onChange={this.handleChange}>
{FilterItems}
</div>
</div>
)
}
});
I think this component is trying to do too much. Split it up
FilteredList
ListItem
CostSelector
you can generate the cost map data once and pass it to the CostSelector that each ListItem will have attached.
Currently you're passing one set of {Costs} to each options and react thinks it's just the one set of options. Wrap the whole select in a component and just pass the properties to it.
Related
I am fairly new to the Facebook's React world. Their documentation seems to be very good but there are a few areas where I need a little bit of clarity. This is one of them.
Src: http://tuts-javascript.appspot.com/reactjs-add-remove-table-row
var CompanyApp = React.createClass({
getInitialState: function() {
return {companylist:this.props.companies};
},
handleNewRowSubmit: function( newcompany ) {
this.setState( {companylist: this.state.companylist.concat([newcompany])} );
},
handleCompanyRemove: function( company ) {
var index = -1;
var clength = this.state.companylist.length;
for( var i = 0; i < clength; i++ ) {
if( this.state.companylist[i].cname === company.cname ) {
index = i;
break;
}
}
this.state.companylist.splice( index, 1 );
this.setState( {companylist: this.state.companylist} );
},
render: function() {
var tableStyle = {width: '100%'};
var leftTdStyle = {width: '50%',padding:'20px',verticalAlign: 'top'};
var rightTdStyle = {width: '50%',padding:'20px',verticalAlign: 'top'};
return (
<table style={tableStyle}>
<tr>
<td style={leftTdStyle}>
<CompanyList clist={this.state.companylist} onCompanyRemove={this.handleCompanyRemove}/>
</td>
<td style={rightTdStyle}>
<NewRow onRowSubmit={this.handleNewRowSubmit}/>
</td>
</tr>
</table>
);
}
});
var CompanyList = React.createClass({
handleCompanyRemove: function(company){
this.props.onCompanyRemove( company );
},
render: function() {
var companies = [];
var that = this; // TODO: Needs to find out why that = this made it work; Was getting error that onCompanyDelete is not undefined
this.props.clist.forEach(function(company) {
companies.push(<Company company={company} onCompanyDelete={that.handleCompanyRemove} /> );
});
return (
<div>
<h3>List of Companies</h3>
<table className="table table-striped">
<thead><tr><th>Company Name</th><th>Employees</th><th>Head Office</th><th>Action</th></tr></thead>
<tbody>{companies}</tbody>
</table>
</div>
);
}
});
var Company = React.createClass({
handleRemoveCompany: function() {
this.props.onCompanyDelete( this.props.company );
return false;
},
render: function() {
return (
<tr>
<td>{this.props.company.cname}</td>
<td>{this.props.company.ecount}</td>
<td>{this.props.company.hoffice}</td>
<td><input type="button" className="btn btn-primary" value="Remove" onClick={this.handleRemoveCompany}/></td>
</tr>
);
}
});
var NewRow = React.createClass({
handleSubmit: function() {
var cname = this.refs.cname.getDOMNode().value;
var ecount = this.refs.ecount.getDOMNode().value;
var hoffice = this.refs.hoffice.getDOMNode().value;
var newrow = {cname: cname, ecount: ecount, hoffice: hoffice };
this.props.onRowSubmit( newrow );
this.refs.cname.getDOMNode().value = '';
this.refs.ecount.getDOMNode().value = '';
this.refs.hoffice.getDOMNode().value = '';
return false;
},
render: function() {
var inputStyle = {padding:'12px'}
return (
<div className="well">
<h3>Add A Company</h3>
<form onSubmit={this.handleSubmit}>
<div className="input-group input-group-lg" style={inputStyle}>
<input type="text" className="form-control col-md-8" placeholder="Company Name" ref="cname"/>
</div>
<div className="input-group input-group-lg" style={inputStyle}>
<input type="text" className="form-control col-md-8" placeholder="Employee Count" ref="ecount"/>
</div>
<div className="input-group input-group-lg" style={inputStyle}>
<input type="text" className="form-control col-md-8" placeholder="Headoffice" ref="hoffice"/>
</div>
<div className="input-group input-group-lg" style={inputStyle}>
<input type="submit" className="btn btn-primary" value="Add Company"/>
</div>
</form>
</div>
);
}
});
var defCompanies = [{cname:"Infosys Technologies",ecount:150000,hoffice:"Bangalore"},{cname:"TCS",ecount:140000,hoffice:"Mumbai"}];
React.renderComponent( <CompanyApp companies={defCompanies}/>, document.getElementById( "companyApp" ) );
This is a very good basic explanation of how ReactJS works. Thanks to the author.
But this comment,
var that = this; // TODO: Needs to find out why that = this made it work; Was getting error that onCompanyDelete is not undefined
Why is that necessary?
Is this the right way to do it? If not, what is?
Thanks in advance.
There's no mystery of "this" that is specific to ReactJS.
This is just a case of standard scoping issues that crop up with callbacks in JavaScript.
When you're in a react component, all methods on the base component will be scoped with the this being the current component, just like any other JavaScript "class".
In your snippet you have a render method which is a function on the base component and therefore this is equal to the component itself. However within that render method you're calling a callback with this.props.clist.forEach, any function callbacks inside the render method will need to be either bound to the correct this scope, or you can do var that = this (although this is an anti-pattern and should be discouraged)`.
Example, slightly simplified version of your snippet:
var MyComponent = React.createClass({
handleCompanyRemove: function(e) {
// ...
},
render: function() {
// this === MyComponent within this scope
this.props.someArray.forEach(function(item) {
// this !== MyComponent, therefore this.handleCompanyRemove cannot
// be called!
})
}
})
As you can see from the comments above, inside your callback for the .forEach you cannot use this directly without either defining a variable outside, or properly binding the function.
Other options to solve this are:
Binding the callback function to the correct this scope. Example:
this.props.someArray.forEach(function(item) {
// this === MyComponent within this scope too now!
// so you can call this.handleCompanyRemove with no problem
}.bind(this))
If you're using Babel/ES6 you can use the Fat Arrow function syntax which guarantees that this scope continues to the callback from the parent scope. Example:
this.props.someArray.forEach((item) => {
// this === MyComponent within this scope too now!
// so you can call this.handleCompanyRemove with no problem
})
I have made a codepen demonstrating a problem I'm having with a checkbox not working. On changes, the value of clipsData does not get updated.
https://codepen.io/bozlurrahman/pen/BeZVzR?editors=1010
<div id="video-clips-wrap">
<div>{{clipsData}}</div>
<li v-for="(clip, index) in clips" v-bind:key="index">
<div class="vl-subsource-container">
<input type="checkbox" value="issubsource" v-model="clip.subsourcesettings" v-on:change="viewSubSource(index)"><label>Not Update on change: {{clip.issubsource}}</label>
<div v-if="clip.subsourcesettings.length">
<label>Dynamic Contents</label>
</div>
</div>
<div class="meditations-options">
<label>Meditations: </label>
<input type="checkbox" value="motivation" v-model="clip.meditations"><label>1. Motivation</label>
<input type="checkbox" value="gratitude" v-model="clip.meditations"><label>2. Gratitude</label>
</div>
</li>
</div>
var video_clip_data_var = "[{\"meditations\":[\"motivation\",\"gratitude\"]}]";
var VideoClip = new Vue({
el: '#video-clips-wrap',
data: {
clips: [],
loading: false,
},
created: function () {
this.clips = JSON.parse(video_clip_data_var);
for (var i = 0; i < this.clips.length; i++) {
// if( typeof this.clips[i].meditations == "string" )
// this.clips[i].meditations = this.clips[i].meditations.split(',');
this.clips[i].subsourcesettings = "issubsource".split(',');
this.clips[i].subsources = [];
}
},
methods: {
viewSubSource: function (index) {
console.log(`this.clips[`+index+`].subsourcesettings`,this.clips[index].subsourcesettings);
console.log(`this.clips`,this.clips);
// this.clipsData = JSON.stringify(this.clips);
},
},
computed: {
clipsData: function () {
return JSON.stringify(this.clips);
},
}
});
Is there any one who can help me to fix this problem? When clicking on the check box, the hidden content should show directly.
Thanks.
Replace that
this.clips[i].subsourcesettings = "issubsource".split(',');
this.clips[i].subsources = [];
to
Vue.set(this.clips[i], 'subsourcesettings', "issubsource".split(','))
Vue.set(this.clips[i], 'subsources', [])
Here you can find more details about your problem.
http://jsfiddle.net/1erw4fba/5/
var App = React.createClass({
getInitialState(){
return {
items:[1,2,3],
isEditing:false
}
},
dlt_item(key){
var newItems = this.state.items.filter((item,i)=> i !== key)
this.setState({items:newItems,isEditing:false})
},
edit_handler(){
this.setState({isEditing:true})
},
isEditing_html(){
return(
<div>
<input type="text" />
<button>Save</button>
</div>
)
},
renderItem(){
return(
this.state.items.map(function(item,i) {
var temp = null;
if(this.state.isEditing){
temp = this.isEditing_html()
}else{
temp = <div onClick={this.edit_handler}><button>Edit</button>
<button onClick={this.dlt_item.bind(this,i)}>Delete</button></div>
}
return (<li key={i}>{item}
{temp}
</li>
)
}.bind(this)
)
)
},
render(){
return(
<ul>
{this.renderItem()}
</ul>
)
}
})
When I click delete button, why the edit input text appear? suppose it will only appear if the state of isEditing is true. Then I try to purposely set that to false, but still it appear. This is unusual to me.
Your problem is here:
temp = <div onClick={this.edit_handler}><button>Edit</button>
<button onClick={this.dlt_item.bind(this,i)}>Delete</button></div>
You put the onClick in the div, so it's called both when you press the Edit button or the Delete button. Just use:
temp = <div><button onClick={this.edit_handler}>Edit</button>
<button onClick={this.dlt_item.bind(this,i)}>Delete</button></div>
var React = require('react');
var Recipe = require('../models/recipes.js').Recipe;
var IngredientCollection =require('../models/recipes.js').IngredientCollection;
var IngredientForm = React.createClass({
getInitialState: function(){
var ingredients = new IngredientCollection();
ingredients.add([{}]);
return{
ingredients: ingredients,
recipe: new Recipe()
};
},
handleAmount: function(e){
var ingredients = this.state.ingredients;
this.props.ingredient.set('amount', e.target.value);
},
handleUnits: function(e){
var ingredients = this.state.ingredients;
this.props.ingredient.set('units', e.target.value);
},
handleName: function(e){
var ingredients = this.state.ingredients;
this.props.ingredient.set('name', e.target.value);
},
render: function(){
var ingredient = this.props.ingredient;
var count = this.props.counter + 1;
return (
<div className="ingredients row">
<h1 className="ingr-heading">Ingredients</h1>
<div className="ingredient-wrapper col-xs-10 col-xs-offset-1">
<input onChange={this.handleAmount} ref={"amount"} type="text" className="amount form-control" id="amount" placeholder="Amount"/>
<select onChange={this.handleUnits} ref={"units"} className="unit form-control" defaultValue="A">
<option disabled value="A">unit</option>
<option value="B">tsp.</option>
<option value="C">tbsp.</option>
<option value="D">fl oz(s)</option>
<option value="E">cup(s)</option>
<option value="F">pt(s)</option>
<option value="G">qt(s)</option>
<option value="H">gal(s)</option>
<option value="I">oz(s)</option>
<option value="J">lb(s)</option>
</select>
<input onChange={this.handleName} ref={"name"} type="text" className="ingr-place form-control" id="name" placeholder="Ingredient"/>
<button id="submit-ingredient" type="button" className="btn btn-default">Add</button>
</div>
</div>
// <div className="recipe-form">
// <form className="holding" onSubmit={this.handleNewRecipe}>
// <span className="make">Makes</span>
// <input type="text" className="servings" onChange={this.handleNameChange} ></input>
// <span className="how-many">Servings</span>
// <button className="btn btn-primary">Adjust Recipe</button>
// </form>
// </div>
)
}
})
var RecipeForm = React.createClass({
getInitialState: function(){
var ingredients = new IngredientCollection();
ingredients.add([{}]);
return{
ingredients: ingredients,
recipe: new Recipe()
};
},
componentWillMount: function(){
var self = this;
var recipe = this.state.recipe;
recipe.on('change', this.update);
this.state.ingredients.on('add', this.update);
},
update: function(){
this.forceUpdate();
},
handleSubmit: function(e){
e.preventDefault();
var router = this.props.router;
var recipe = this.state.recipe;
var ingredients = this.state.ingredients;
recipe.set('ingredients', ingredients.toJSON());
console.log(recipe);
recipe.save().done(function(e){
router.navigate('recipes/add', {trigger: true});
});
},
handleTitleChange: function(e){
this.state.recipe.set('title', e.target.value)
},
render: function(){
return(
<form>
<IngredientForm />
</form>
)
}
})
module.exports = RecipeForm;
Im getting "cannot read property 'set' of undefined". I thought I did set it? I dont understand how else I am suppose to define it other than what Ive written so far. If anyone has any ideas please post, this is for a project that needs to be done soon!
Hi it looks like you have not passed down an ingredient property from the parent. You may want to use getDefaultProps to setup your ingredient property. Otherwise make sure you pass down an ingredient property when initializing the IngredientForm.
<IngredientForm ingredient={myIngredient}/>
Please make sure that your app is defined before the set method call or not.
Looking for a good example of how to set up child models in knockoutjs. This includes binding to child events such as property updates which I haven't been able to get working yet.
Also, it would be better to bind to a single child in this case instead of an array but I don't know how to set it up in the html without the foreach template.
http://jsfiddle.net/mathewvance/mfYNq/
Thanks.
<div class="editor-row">
<label>Price</label>
<input name="Price" data-bind="value: price"/>
</div>
<div class="editor-row">
<label>Child</label>
<div data-bind="foreach: childObjects">
<div><input type="checkbox" data-bind="checked: yearRound" /> Year Round</div>
<div><input type="checkbox" data-bind="checked: fromNow" /> From Now</div>
<div>
<input data-bind="value: startDate" class="date-picker"/> to
<input data-bind="value: endDate" class="date-picker"/>
</div>
</div>
</div>
var ChildModel= function (yearRound, fromNow, startDate, endDate) {
var self = this;
this.yearRound = ko.observable(yearRound);
this.fromNow = ko.observable(fromNow);
this.startDate = ko.observable(startDate);
this.endDate = ko.observable(endDate);
this.yearRound.subscribe = function (val) {
alert('message from child model property subscribe\n\nwhy does this only happen once?');
//if(val){
// self.startDate('undefined');
// self.endDate('undefined');
//}
};
}
var ParentModel = function () {
var self = this;
this.price = ko.observable(1.99);
this.childObjects = ko.observableArray([ new ChildModel(true, false) ]);
};
var viewModel = new ParentModel ();
ko.applyBindings(viewModel);
Try it with the following:
this.yearRound.subscribe(function (val) {
alert('value change');
});
If you want to have the subscriber also being called while loading the page do something like this:
var ChildModel= function (yearRound, fromNow, startDate, endDate) {
var self = this;
this.yearRound = ko.observable();
this.fromNow = ko.observable(fromNow);
this.startDate = ko.observable(startDate);
this.endDate = ko.observable(endDate);
this.yearRound.subscribe(function (val) {
alert('value change');
});
this.yearRound(yearRound);
}
http://jsfiddle.net/azQxx/1/ - this works for me with Chrome 16 and Firefox 10
Every time the checked button changes its value the callback fires.
The observableArray is fine in my opinion if you may have more than one child model associated to the parent.