I'm attempting to build a form from an array of form fields where each form field looks like this:
{
"name": "state",
"resource": "customer",
"type": "TextBox",
"assetId": "State",
"label": {
"text": "State",
"assetId": "Label"
}
}
However, when I attempt to map it using JSX, the fields don't get successfully displayed if I access certain properties of the object. Take the following code, which functions correctly:
formfields.map(function (formfield, i) {
var returnfield = <div key={i}>{formfield.name}</div>;
switch (formfield.type) {
case "TextBox":
console.log(formfield.label);
returnfield = (
<div key={i}>
<label htmlFor="theinput">{formfield.name}</label>
<input id="theinput" type="text" value={formfield.name} />
</div>
);
break;
}
return returnfield;
});
And compare it with the code that fails:
formfields.map(function (formfield, i) {
var returnfield = <div key={i}>{formfield.name}</div>;
switch (formfield.type) {
case "TextBox":
console.log(formfield.label.text);
returnfield = (
<div key={i}>
<label htmlFor="theinput">{formfield.name}</label>
<input id="theinput" type="text" value={formfield.name} />
</div>
);
break;
}
return returnfield;
});
The astute observer will notice that the only difference between the two is that, in the second, we are logging formfield.label.text instead of formfield.label
I'm totally stumped why simply logging an object's grandchild attribute should cause the form to appear empty (i.e., with no fields). Perhaps I'm running into reserved names or something? Any ideas appreciated.
why didn't I see a javascript error in my developer console? Is there some weird thing where .map() doesn't allow errors to be raised?
After recognizing that checking for null is needed in your project well I suggest you use some concepts of javascript functional programming to compose a function that checks for falsely values before applying them in your logic.
You can use Maybe functor that returns a Maybe(null) which stops immediately. Before returning a null value to your logic and cause a boom!
You can also use Either, this is cool because it's just like maybe but you can also gve some logic to run if the value is falsely.
I have two examples for these suggestions (Copied from jsbin)
//Key container == Something map can iterate over like an object or an array.
//And am talking about the lodash / ramda.js curried map that can iterate over object not the js native one.
//Using Maybe
//Url http://jsbin.com/yumog/edit?js,console
var safeGet = _.curry(function(x,o){
return Maybe(o[x]);
//This will return Maybe(null)
//if it's some property in a container is not found
//which you can check before breaking something
});
var user = {id: 2, name: "Albert"}
var ex3 = compose(map(_.head), safeGet('name'));
assertDeepEqual(Maybe('A'), ex3(user))
console.log("exercise 3...ok!")
//Using Either.io
//url http://output.jsbin.com/bexuc/
// Write a function that uses checkActive()
//and showWelcome() to grant access or return the error
var showWelcome = compose(_.add( "Welcome "), _.get('name'))
//Here either returns a function you give it on the right if it's truthy
//and left if it's falsey (or falsy i don't know english .. )
// So you get to do something if the property in your container is not present.
var checkActive = function(user) {
return user.active ? Right(user) : Left('Your account is not active')
}
var ex1 = compose(map(showWelcome), checkActive);
assertDeepEqual(Left('Your account is not active'), ex1({active: false, name: 'Gary'}))
assertDeepEqual(Right('Welcome Theresa'), ex1({active: true, name: 'Theresa'}))
Links to the libraries.
Maybe: https://github.com/chrissrogers/maybe
Either: https://github.com/fantasyland/fantasy-eithers
You might also need to check on lodash / ramda to have a full idea on these functional concepts.
Related
I'm a C# programmer trying to learn Angular and Ionic.
I'd like to do something as we usually do in C# with dictionaries:
export class HomePage {
private times = [
{key: 'clockOn', value: '09:00'},
{key: 'lunchTime', value: '12:00'},
{key: 'backToWork',value: '13:00'},
{key: 'clockOff', value: '18:00'}
];
}
getHour(name: string) {
if(this.times[name] === 'clockOn'){
console.log('time: ' + this.times[name].value);
}
On HTML
<ion-datetime class="time" id="coff" displayFormat="HH:mm"
[(ngModel)]="cOff" (ionChange)="getHour('clockOn')">
</ion-datetime>
And on Chrome console I see:
clockOnundefined
First of all, is this the correct sintax to access the member 'value'?
this.times[0].value
I want to know if is it possible to access an array element using a string as indexer (key) like we do in C# with dictionaries
For example:
this.times['clockOn'].value //would bring me its value like '09:00'
or the only way to access a element is using numbers as indexers? Like below
this.times[0].key // bring me 'clockOn'
this.times[0].value // bring me '09:00'
Dictionary is Angular/ JavaScript does not work same as C#.
So your code:
this.times['clockOn'].value
this will not work.
You will have to use
this.times[0].key // bring me 'clockOn'
this.times[0].value // bring me '09:00'
If you want access with key like array[key] returning the value run the loop in this manner:
<td *ngFor="(key, value) in dictionary">
{{value}}
</td>
Hope it helps.
Personally I would just modify your data structure, if they are just hardcoded values.
private times = {
clockOn: '09:00',
lunchTime: '12:00',
backToWork: '13:00',
clockOff: '18:00'
};
getHour(key: string)
{
if (this.times[name] === 'clockOn')
{
console.log('time: ' + this.times[name].value);
}
}
<span *ngFor="(key, value) in times" *ngClick=getHour(key)>{{value}}</span>
Having an object with unique keys where you can use bracket notation to look up the desired item is more effective as it allows for quick simple look ups.
If you wanted to use the original data structure you would have to pass in the index of the repeat item so you can access the correct item in the array.
so your mark up would look like this:
<span *ngFor="time in times" let i=index; *ngClick=getHour(i)>{{value}}</span>
getHour(index: number)
{
if (this.times[index].key === 'clockOn')
{
console.log('time: ' + this.times[index].value);
}
}
I have a ion-searchbar that looks like this
<ion-searchbar [(ngModel)]="searchQuery" (keyup.enter)="search();"></ion-searchbar>
and the searchQuery is defined in my typescript file like this
export class SearchPage {
searchQuery: string;
constructor(){}
search() {
this.service.search(this.searchQuery).subscribe(
data => this.searchResults = data,
error => {
//something
}, () => {
//something
}
);
}
}
The problem is that if I change the value too fast and I press Enter, the value of searchQuery is not updated. For example, if I search "test" and I wait two seconds it will work. If I then search "testing" and I type it fast and press Enter right away, the value will still be "test". Now, I know this sounds weird, but it is really happening!
Any ideas why the value is not changed as soon as I type something?
Thank you
In Html try this event change
<form [ngFormModel]="searchForm" (ngSubmit)="search()">
<ion-searchbar [(ngModel)]="searchQuery" (keyup)="search()"></ion-searchbar>
<button type="submit" block>Submit</button>
</form>
Even check here might this help
In ts trim the value
this.service.search(this.searchQuery.trim()).subscribe(.....)
This is how I did it, based on the documentation for ion-searchbar:
<ion-searchbar #searchBar [debounce]="50" (ionChange)="search(searchBar.value)">
</ion-searchbar>
and in the TS file:
search(value: string) {
// do something with value
}
Explanation:
It's pretty self-explanatory, but here it is. The #searchBar creates a 'hook' for the element (sort of a 'self', or 'this', but named). We then use the property value from ion-searchbar to pass it to our function. The last thing is modifying the [debounce] property to make it update faster (but it will trigger more times when people write fast - use with discretion).
Something you can do to improve the way the search bar is being handled is:
In your page.html:
<ion-searchbar primary hideCancelButton [(ngModel)] = "searchQuery" [ngFormControl]="searchQueryControl"></ion-searchbar>
And in your page.ts:
// Remember to import these things:
import {FORM_DIRECTIVES, Control} from 'angular2/common';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
constructor ( ... ) {
//...
this.searchQuery = '';
this.searchQueryControl = new Control();
//...
this.searchQueryControl
.valueChanges
.debounceTime(1000)
.distinctUntilChanged()
.subscribe(text => {
if(text !== '') {
this.searchQuery = text;
this.search();
}
});
//...
}
By doing that, we would only run the code when the input has changed (this.searchQueryControl.valueChanges), and when there hasn't been another change within 1 second (.debounceTime(1000)). The distinctUntilChanged() allow us to run the code if the value is different to the last time it ran. So if the user typed 'asd', hit the backspace key and then retyped the ending 'd' again, nothing would happen.
Example: https://jsfiddle.net/wbellman/ghuw2ers/6/
In an application I am working on, I have a parent container (List, in my example) that contains a list of children (Hero, in my example). The list is governed by an outside object. For simplicity I declared the object directly in the JS. (In my real application the data store is properly namespaced and so forth.)
The problem I have is in the list I have three elements, if I remove an item from the middle, the rendered list appears to remove the last element. However the outside object reflects the proper list.
For example:
My list has the elements: cap, thor, hulk
If you remove "thor", "cap" and "thor" are rendered
The heroList reflects "cap" and "hulk" as it should
I am relatively new to ReactJs, so there is a good chance my premise is fundamentally flawed.
Note: The example reflects a much more complex application. It's structured similarly for purposes of demonstration. I am aware you could make a single component, but it would not be practical in the actual app.
Any help would be appreciated.
Here is the code from JSFiddle:
var heroList = [
{ name: "cap" },
{ name: "thor"},
{ name: "hulk"}
];
var List = React.createClass({
getInitialState() {
console.log("heros", heroList);
return {
heros: heroList
};
},
onChange(e){
this.setState({heros: heroList});
},
removeHero(i,heros){
var hero = heros[i];
console.log("removing hero...", hero);
heroList = _.filter(heroList, function(h){ return h.name !== hero.name;});
this.setState({heros:heroList});
},
render() {
var heros = this.state.heros;
var createHero = (hero,index) => {
return <Hero hero={hero} key={index} onRemove={this.removeHero.bind(this,index,heros)}/>;
};
console.log("list", heros);
return (
<ul>
{heros.map(createHero)}
</ul>
)
}
});
var Hero = React.createClass({
getInitialState() {
return {
hero: this.props.hero
}
},
render() {
var hero = this.state.hero;
return (
<li>Hello {hero.name} | <button type="button" onClick={this.props.onRemove}>-</button></li>
);
}
});
ReactDOM.render(
<List />,
document.getElementById('container')
);
Additional: I was having problems copying the code from JSFiddle, anything I broke by accident should work in the JSFiddle listed at the top of this question.
Edit:
Based on the commentary from madox2, nicole, nuway and Damien Leroux, here's what I ended up doing:
https://jsfiddle.net/wbellman/ghuw2ers/10/
I wish there was a way to give everyone credit, you were all a big help.
Changing your Hero class to this fixed the issue of displaying the wrong hero name for me:
var Hero = React.createClass({
render() {
return (
<li>Hello {this.props.hero.name} | <button type="button" onClick={this.props.onRemove}>-</button></li>
);
}
});
i.e. I removed the local state from the class and used the prop directly.
Generally speaking, try to use the local store only when you really need it. Try to think of your components as stateless, i.e. they get something through the props and display it, that's it.
Along these lines, you should consider passing the hero list through the props to your List component as well.
if you really have problems with managing your data you should use Flux or Redux.
in this code:
heroList = _.filter(heroList, function(h){ return h.name !== hero.name;});
i just dont get why you filer the heroList instead of this.state.heros? every time you add or remove a hero, the heroList in your current scope shouldnt be kept in state? the global heroList is just the initial state.
The problem is with the keys used. Since the key is taken from the index, that key has already been used and thus the hero with that key is shown.
change it to key={Math.random() * 100} and it will work
I'm a beginner of ReactJS and I'm stuck trying to figure out why map only returns a single prop at a time.
In file1.jsx, I have an array that contains 3 objects:
var MainPanelOneData = [{"id":1,"shotviews":15080},{"id":2,"likes":12000},{"id":3,"comments":5100}];
File1.jsx also has a render function to extrapolate the data inside the array:
render: function() {
var ListMainPanelOne = MainPanelOneData.map(function(data) {
return <MainPanelOne key={data.key} shotviews={data.shotviews} likes={data.likes} comments={data.comments} />
});
In file2.jsx, I have this code to render the data object from file1.jsx:
render: function() {
return (
<div>
<span>{this.props.shotviews} shot views</span>
<span>{this.props.likes} likes</span>
<span>{this.props.comments} comments</span>
</div>
)
}
The result shows this:
15080 shot views likes comments
shot views12000 likes comments
shot views likes5100 comments
I'm guessing it repeats like this because it searches through one key at a time? If that's the case, how do I display all keys at the same time? Use indexing?
well your array of data doesnt have all the keys. each one of your objects in PanelOneData has ONE key and is missing the other two; additionally none of them have key called key. so youre making three MainPanelOne components, each with a single prop. the result of that map is this
[
<MainPanelOne key={null} shotviews={15080} likes={null} comments={null} />,
<MainPanelOne key={null} shotviews={null} likes={12000} comments={null} />,
<MainPanelOne key={null} shotviews={null} likes={null} comments={5100} />
]
which is an accurate display of what youre seeing
To get one line you might do something like this.
render: function() {
var ListMainPanelOne = MainPanelOneData.map(function(data) {
return <span key={data.id}> {data.shotviews} {data.likes} {data.comments} </span>
});
Background:
I am currently working on an application with tabs; and I'd like to list the fields / sections that fail validation, to direct the user to look for errors in the right tab.
So I tried to leverage form.$error to do so; yet I don't fully get it working.
If validation errors occur inside a ng-repeat, e.g.:
<div ng-repeat="url in urls" ng-form="form">
<input name="inumber" required ng-model="url" />
<br />
</div>
Empty values result in form.$error containing the following:
{ "required": [
{
"inumber": {}
},
{
"inumber": {}
}
] }
On the other hand, if validation errors occur outside this ng-repeat:
<input ng-model="name" name="iname" required="true" />
The form.$error object contains the following:
{ "required": [ {} ] }
yet, I'd expect the following:
{ "required": [ {'iname': {} } ] }
Any ideas on why the name of the element is missing?
A running plunkr can be found here:
http://plnkr.co/x6wQMp
As #c0bra pointed out in the comments the form.$error object is populated, it just doesn't like being dumped out as JSON.
Looping through form.$errors and it's nested objects will get the desired result however.
<ul>
<li ng-repeat="(key, errors) in form.$error track by $index"> <strong>{{ key }}</strong> errors
<ul>
<li ng-repeat="e in errors">{{ e.$name }} has an error: <strong>{{ key }}</strong>.</li>
</ul>
</li>
</ul>
All the credit goes to c0bra on this.
Another option is to use one of the solutions from this question to assign unique names to the dynamically created inputs.
I made a function that you pass the form to. If there are form errors it will display them in the console. It shows the objects so you can take a look. I put this in my save function.
function formErrors(form){
var errors = [];
for(var key in form.$error){
errors.push(key + "=" + form.$error);
}
if(errors.length > 0){
console.log("Form Has Errors");
console.log(form.$error);
}
};
Brett DeWoody's answer is correct. I wanted to do the logic in my controller though. So I wrote the below, which is based off of the answer user5045936 gave. This may also help some of you who want to go the controller route. By the way Im using the toaster directive to show my users validation messages.
if (!vm.myForm.$valid) {
var errors = [];
for (var key in vm.myForm.$error) {
for (var index = 0; index < vm.myForm.$error[key].length; index++) {
errors.push(vm.myForm.$error[key][index].$name + ' is required.');
}
}
toaster.pop('warning', 'Information Missing', 'The ' + errors[0]);
return;
}
If you have nested forms then you will find this helpful:
function touchErrorFields(form) {
angular.forEach(form.$error, function (field) {
angular.forEach(field, function(errorField) {
if (!errorField.hasOwnProperty('$setTouched')) {
touchErrorFields(errorField);
} else {
errorField.$setTouched();
}
})
});
}