React and JavaScript - binding functions explained [duplicate] - javascript

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
})

Related

Computed Methods not updating on change checkbox value in vue js

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.

How to get value of input text field without knowing its id in Javascript

I have created a javascript script in which all clicks and action of a particular IP it tracked down when attached in a page. It all works fine except when the user searches something from an input field. I need to track that text which user has searched.
But I don't have any information regarding that input field i.e id or class also a page can have.
multiple input fields i.e A form for submission too.
I need to get that text when the enter key is pressed or any button(search) is pressed
In my case at present html page, I have below code.
<div class="navbar-form navbar-right">
<!-- Search Page -->
<div id="search-container" class="search-container" style="float: left;">
<div class="search-wrapper">
<div class="search-input-wrapper">
<input type="text" class="search-layouts-input" ng-model="searchQuery" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" placeholder="So, what are you looking for?" ng-keyup="$event.keyCode == 13 ? actionSearch() : null">
<button type="submit" class="s-layout-btn" ng-click="actionSearch();">
<svg id="Layer_1" width="20px" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="#222">
<defs>
<style>.cls-1{fill:none;stroke:#222;stroke-miterlimit:10;stroke-width:2px;}</style>
</svg>
</button>
</div>
</div>
</div>
<!-- Search Page -->
</div>
Is this the kind of thing that you're looking to implement? Of course most of it is pseudo code, but it's pretty straight-forward, just get all relevant inputs and buttons and whatnot, attach some event handler to them and update the application state.
I've included some basic functions that you may wish to fire in certain scenarios, such as onStateUpdate, there may be no need for this function, but it probably wouldn't hurt to keep it for the sake of simplcity.
I've used mostly ES6 oriented syntax because it allows you to achieve the same results with less code, I'm just that lazy.
The reason why I used a self invoked function just so there's no issues with variable names and nothing can be manipulated on the global scope, etc. If you'd like to read or know more about why self invoked functions can be pretty good, then I suggest you read sources such as this.
// Self invoked anonymous function.
(function() {
// The application state.
const state = {};
// Lazy way to use querySelectorAll
const $e = qs => document.querySelectorAll(qs);
// Make a copy of the state and make it global.
const getState = () => {
window.state = { ...state};
console.clear();
console.log(window.state);
};
// A function to run when the state updates.
const onStateUpdate = () => {
// Do some other stuff...
getState();
};
// Handle the key up event.
const inputHandler = (i, index) => i.onkeyup = () => {
i.id == null || i.id == '' ? i.setAttribute("id", index) : null;
const id = i.id;
state[id] = i.value;
onStateUpdate();
};
// Handle a button being clicked.
const clickHandler = btn => btn.onclick = onStateUpdate;
// Handle the enter key being pressed.
const enterHandler = e => e.keyCode == 13 ? onStateUpdate() : null;
// Assign all relevant events to the relevant functions.
const dispatchEvents = () => {
const inputs = $e("#search-container input[type=text]");
const buttons = $e("#search-container button");
inputs.forEach((i, index) => inputHandler(i, index));
buttons.forEach(b => clickHandler(b));
window.onkeypress = enterHandler;
};
// Fire the dispatch function.
dispatchEvents();
}());
<!-- this one does nothing as it's outside of the search-container element -->
<input type="text" id="testing" placeholder="I do nothing!" />
<div id="search-container">
<input type="text" id="test" />
<input type="text" id="demo" />
<input type="text" id="markup" />
<input type="text" />
<button>Search</button>
</div>
Older Syntax
// Self invoked anonymous function.
(function() {
// The application state.
var state = {};
// Lazy way to use querySelectorAll
var $e = function(qs) {
return document.querySelectorAll(qs);
};
// Make a copy of the state and make it global.
var getState = function() {
window.state = JSON.parse(JSON.stringify(state));
console.clear();
console.log(window.state);
};
// A function to run when the state updates.
var onStateUpdate = function() {
// Do some other stuff...
getState();
};
// Handle the key up event.
var inputHandler = function(i, index) {
i.onkeyup = function() {
if (i.id == null || i.id == '') {
i.setAttribute("id", index);
}
var id = i.id;
state[id] = i.value;
onStateUpdate();
};
};
// Handle a button being clicked.
var clickHandler = function(btn) {
btn.onclick = onStateUpdate;
};
// Handle the enter key being pressed.
var enterHandler = function(e) {
if (e.keyCode == 13) {
onStateUpdate();
};
};
// Assign all relevant events to the relevant functions.
var dispatchEvents = function() {
var inputs = $e("input[type=text]");
var buttons = $e("button");
inputs.forEach(function(i, index) {
inputHandler(i, index)
});
buttons.forEach(function(b) {
clickHandler(b)
});
window.onkeypress = enterHandler;
};
// Fire the dispatch function.
dispatchEvents();
}());
<input type="text" id="test" />
<input type="text" id="demo" />
<input type="text" id="markup" />
<input type="text" />
<button>Search</button>

JavaScript Vanilla Two Way Binding

I have found this very short yet handy two way binding code written in pure JavaScript. The data binding works fine, but what I want is to take the value from the first input and multiply it by a desired number and bind the outcome to the next input. I will appreciate any kind of help.
This is my HTML Code:
<input class="age" type="number">
<input class="age" type="number">
and the JavaScript Code:
var $scope = {};
(function () {
var bindClasses = ["age"];
var attachEvent = function (classNames) {
classNames.forEach(function (className) {
var elements = document.getElementsByClassName(className);
for (var index in elements) {
elements[index].onkeyup = function () {
for (var index in elements) {
elements[index].value = this.value;
}
}
}
Object.defineProperty($scope, className, {
set: function (newValue) {
for (var index in elements) {
elements[index].value = newValue;
}
}
});
});
};
attachEvent(bindClasses);
})();
If the desired results is take first input value, do something with it, put it to second input then why so serious?
(function () {
var bindClasses = ["age"];
var attachEvent = function (classNames) {
classNames.forEach( function( className ) {
var els = document.getElementsByClassName( className ),
from, to;
if ( 2 > els.length ) {
return;
}
from = els[0];
to = els[els.length - 1];
from.addEventListener( 'keyup', function() {
var v = this.value;
// Do whatever you want with that value
// Then assign it to last el
if ( isNaN( v ) ) {
return;
}
v = v / 2;
to.value = v;
});
});
};
attachEvent(bindClasses);
})();
Another simple approach to two-way binding in JS could be something like this:
<!-- index.html -->
<form action="#" onsubmit="vm.onSubmit(event, this)">
<input onchange="vm.username=this.value" type="text" id="Username">
<input type="submit" id="Submit">
</form>
<script src="vm.js"></script>
// vm.js - vanialla JS
let vm = {
_username: "",
get username() {
return this._username;
},
set username(value) {
this._username = value;
},
onSubmit: function (event, element) {
console.log(this.username);
}
}
JS Getters and Setters are quite nice for this - especially when you look at the browser support for this.

How to retrieve all menu options

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.

ko.subscribe on child model properties

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.

Categories