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.
Related
I am using Jest and Enzyme to test a React component. I am trying to test my form validation rules when submitting a form. The tests need to cover all possible cases of this function
const handleSubmit = event => {
event.preventDefault();
const { createPassword, confirmPassword } = event.target.elements;
if (createPassword.value !== confirmPassword.value) {
setPassValidationError("*Passwords must match!");
} else if (createPassword.value.length < 8) {
setPassValidationError("*Passwords must be at least 8 characters long!");
} else if (createPassword.value.search(/[A-Z]/) < 0) {
setPassValidationError(
"*Passwords must contain at least one uppercase letter!"
);
} else if (createPassword.value.search(/[!##$%^&*]/) < 0) {
setPassValidationError(
"*Passwords must contain at least one special character!"
);
} else {
props.updatePassword({
uid: props.uid,
token: props.token,
new_password: createPassword.value
});
event.target.reset();
}
};
This function is pretty straight forward createPassword and confirmPassword are the values for 2 different input fields. When the form is submitted and this function gets called I am testing the password on different criteria. If the password is not strong enough, the setPassValidationError hook is called and updates a state variable.
I am currently trying to test the function with a password shorter than 8 characters.
it("passwords must be 8 char long", () => {
const wrapper = mount(<NoAuthPasswordChange />);
const passInput = wrapper.find("#create-password");
const confirmPass = wrapper.find("#confirm-password");
passInput.simulate("change", { target: { value: "QQQQQQ" } });
confirmPass.simulate("change", { target: { value: "QQQQQQ" } });
const submitButton = wrapper.find("#submit-button");
submitButton.simulate("click");
expect(wrapper.find("#password-validation-error").text()).toContain(
"*Passwords must be at least 8 characters long!"
);
});
Jest is telling me that #password-validation-error cannot be found (expected 1 node found 0). Now this particular part of the code is only rendered if passValidationError has data.
{passValidationError ? (
<h2
className={styles.passwordError}
id="password-validation-error"
>
{passValidationError}
</h2>
) : null}
I'm not sure if I just have a simple bug in my test or if something more advanced needs to be done in order to use Jest and have a function call a hook update.
Edit: I am beginning to wonder if the event parameter required by the handleSubmit function is problematic due to the function being called by Jest.
This can be cause by not updating the component itself. Have you tried to force your wrapper to be re-rendered:
https://airbnb.io/enzyme/docs/api/ShallowWrapper/update.html
https://airbnb.io/enzyme/docs/api/ReactWrapper/update.html
I have found a solution to my issue. The test needs to call the form submission on the form element itself and not via a button click. So instead of submitButton.simulate("click") I need to simulate a submit on my form element. I am unsure why this solution works and the posted code does not.
My users complain that they can enter new value (one that is not included in the options) even when that is not exactly the case.
When you input text, without selecting item from options and then leave the typeahead, the text stays there, which leads users to believe that new value (one that is not included in options) can be entered.
What would be the right way to deal with this?
I am quite new to frontend development, so the answer might actually be obvious.
One way to address this is to clear the typeahead when the user blurs the input unless they've made a valid selection. Here's an example:
https://codepen.io/anon/pen/qLBaYK
class BlurryTypeahead extends React.Component {
state = {
selected: [],
};
render() {
return (
<Typeahead
onBlur={this._handleBlur}
onChange={this._handleChange}
options={['one', 'two', 'three']}
ref={typeahead => this._typeahead = typeahead}
selected={this.state.selected}
/>
);
}
_handleBlur = () => {
// Check if there are selections.
if (!this.state.selected.length) {
// Clear the component if not.
this._typeahead.getInstance().clear();
}
}
_handleChange = (selected) => {
// Track the selected state
this.setState({ selected });
}
}
It's all in the title. The cursor hitting a wall (so to speak) would be much better than the dialog box closing, checking the input length, and then telling the user they have to type everything over again, just less text, because they typed too much.
I have sort of worked around the validation problem here, I get its not idea, But just pass the entered in text back into the model
$scope.generateUpdatedDocumentButtonClicked = function (ev, textContentValue) {
var versionNumber = $scope.data.Version + 0.1;
var confirm = $mdDialog.prompt()
.title('New Test Document Version')
.textContent(textContentValue)
.initialValue(versionNumber)
.targetEvent(ev)
.required(true)
.ok('Generate')
.cancel('Cancel');
$mdDialog.show(confirm).then(function (result) {
if (//validate to be true) {
$scope.generateUpdatedDocument(result);
} else
{
$scope.generateUpdatedDocumentButtonClicked(ev, 'Value Invalid');
}
}, function () {
alert('Cancel Clicked');
});
}
The initial call to the fuction will pass in a empty string.
Again this is not great but works around my problem
You can just prevent to type more than N symbols using HTML5 maxLength input feature
<input type="text" maxlength="5">
So user would be unable to type more than 5 symbols, as an example.
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.
I'm writing a component that takes a String and converts it into a list of <span>s. Each <span> gets a String character and is assigned a random color.
The component invocation looks like this:
<span ng-repeat="char in ctrl.chars" style="color: {{ctrl.randomColor}};">
{{char}}
</span>
And the component definition looks like this:
import 'package:angular/angular.dart';
import 'dart:math';
#NgComponent(
selector: 'tokens',
templateUrl: './component.html',
cssUrl: './component.css',
publishAs: 'ctrl',
map: const {
'text' : '#text',
}
)
class TokensComponent {
String text;
List<String> get chars => text.split('');
String get randomColor {
var colors = ['red', 'green', 'yellow', 'blue'];
return colors[new Random().nextInt(colors.length)];
}
}
This works, but generates an error:
5 $digest() iterations reached. Aborting!
Watchers fired in the last 3 iterations:
...
It isn't clear to me just what is being watched here, beyond the getter I am defining.
If I leave the code involving the Random in the getter, but simply return a hard-coded String, the error message goes away.
Any idea what is wrong here?
Binding a getter method with random return value seems like calling for all kind of weird stuff to happen.
From what I see, your intention seems to be to show all characters of a string in a different, random color - but not changing colors (meaning that the characters shouldn't constantly change their color) - is that right?
Unfortunately, the <span> isn't just created with the current return value of randomColor and then forgotten about - binding a property has the advantage that changes to the property are reflected in the view. Of course, since the "property" is a getter and has a random return value, it constantly changes, resulting in constant updates to the view.
If this error wouldn't occur to prevent this endless loop, all characters probably would have the same (rapidly changing) color.
EDIT
This question has a good answer to the problem:
AngularDart custom filter call() method required to be idempotent?
And alternative approach:
ORIGINAL
I'm not sure what you try to achieve but maybe it helps anyway
Screenshot of the result:
library main;
import 'dart:math';
import 'package:angular/angular.dart';
import 'package:di/di.dart';
class MyAppModule extends Module {
MyAppModule() {
type(TokensComponent);
}
}
void main() {
ngBootstrap(module: new MyAppModule());
}
#NgComponent(
selector: 'tokens',
template: '''<span ng-repeat="item in ctrl.items" style="color: {{item.color}};">
{{item.char}}
</span>''',
//cssUrl: './component.css',
publishAs: 'ctrl',
map: const {
'text' : '#text',
}
)
class TokensComponent {
String text = 'abcdefg';
List<Item> items = new List<Item>();
TokensComponent() {
text.split('').forEach((e) => items.add(new Item(e, randomColor)));
}
var colors = \['red', 'green', 'yellow', 'blue'\];
String get randomColor {
return colors\[new Random().nextInt(colors.length)\];
}
}
class Item {
Item(this.char, this.color);
String char;
String color;
}