I have a autocomplete control that triggers a onAutoCompleteSearch() after a debounce where I retrieve results from the server. However, if the user enters text and hits enter (key code 13) then a signal should be raised that will cancel the next invocation of an autocomplete. Since this is a 3rd party control I don't have control over the invocation of onAutoCompleteSearch() that occurs after a set debounce time.
I am using a Subject to do the signalling:
private cancelAutoComplete$ = new Subject<boolean>();
If user hits enter key:
onKeyUp(e) {
if (e.keyCode === 13) {
this.cancelAutoComplete$.next(true);
this.fireExecuteSearch(); // fire full search
} else {
this.fireSearchChange(); // trigger user input change
}
}
When an autocomplete is to be executed:
onAutoCompleteSearch(e) {
console.log('starting autocomplete!');
this.cancelAutoComplete$
.first()
.defaultIfEmpty(false)
.subscribe(c => {
if (c) {
console.log('autocomplete cancelled!');
} else {
console.log('execute the autocomplete!');
this.executeAutoComplete.next(e.query);
}
});
}
the above does not quite work... what I wish to do is check the cancelAutoComplete stream for an element, if one exists then take it off the stream, if the cancel flag is true then abort the autocomplete. If there isn't an element then return a default element of false so I can continue with the autocomplete.
How can I accomplish this? Basically if there is an cancel signal pending from the onKeyUp -> keycode 13 event I want to abort the call, if not continue.
I know I can use a simple boolean to track this but wanted to know how to do it via RxJs Subjects.
Firstly I'd make cancelAutoComplete$ a BehaviorSubject initialised to false. Send it false whenever the keyCode is not 13:
private cancelAutoComplete$ = new BehaviorSubject<boolean>(false);
onKeyUp(e) {
if (e.keyCode === 13) {
this.cancelAutoComplete$.next(true); // prevent autocomplete
this.fireExecuteSearch();
} else {
this.cancelAutoComplete$.next(false); // allow autocomplete
this.fireSearchChange();
}
}
Then I'd use the takeUntil operator as part of your executeAutoComplete stream as follows:
onAutoCompleteSearch(e) {
this.executeAutoComplete.next(e.query);
}
this.executeAutoComplete
.switchMap(query => this.backend.fetchAutoCompleteResults(query).takeUntil(
this.cancelAutoComplete$.filter(c => c === true)
))
.subscribe(...);
I've assumed your backend api is named this.backend.fetchAutoCompleteResults - the takeUntil will abort it if cancelAutoComplete$ is initially true or becomes true while the call is in flight.
Related
I have a form and I allow the user to click as many times as he wants on a refresh button. Of course, I use debounceTime operator but I don't know how to:
either cancel the previous http requests
or indicate to my service to return the value of the latest emission.
For example:
t1: click => received data in 2000ms
t2: click => received data in 200ms
Therefore, I will get the data from t1 moment whereas the latest one is at t2.
I've tried with pipe(last()), switchMap but I don't return data.
My component:
this.filtersForm.valueChanges.pipe(debounceTime(500)).subscribe(
form => {
this.service.setFilters(form); // Set private field in service (1)
this.onSubmit();
}
);
onSubmit() {
if (this.filtersForm.valid) {
this.service.notifFiltersHasChanged();
}
}
Service:
ctor(...) {
this.filters$.subscribe(f => this.getData());
}
notifFiltersHasChanged() {
this.filters$.next(this._filters); // (1) _filters is set by setFilters method
}
getData(): void {
// ...
this.backEndService.getAll(this._filters).subscribe(data => this._data = data);
}
BackEndService:
getAll(filters: any): Observable<Data> {
return this.httpClient.get<Data>(url).pipe(last());
}
The main trick is to use a single subscription (or even zero, if you'll use | async pipe in your template). So you source from an Observable and chain through your services.
Heres an updated example of yours:
Component
onDestroy$ = new Subject<void>();
constructor(){
this.filtersForm.valueChanges.pipe(
// accept only valid values
filter(() => this.filtersForm.valid),
// debounce them
debounceTime(500),
// when a value comes in -- we switch to service request
// subsequent values would cancel this request
switchMap(formValues => this.service.getData(formValues)),
// this is needed to unsubscribe from the service
// when component is destroyed
takeUntil(this.onDestroy$)
)
.subscribe(data=>{
// do what you need with the data
})
}
ngOnDestroy() {
this.onDestroy$.next(void 0);
}
Service
// service becomes stateless
// its only responsible for parsing and passing data
getData(filters): Observable<Data> {
return this.backEndService.getAll(filters);
}
BackEndService
getAll(filters: any): Observable<Data> {
return this.httpClient.get<Data>(url).pipe(last());
}
Another way would be to have a Subject, that you would push to. Otherwise it would be the same chaining on top of that Subject.
Hope this helps
I'm using bot-Framework SDK3 C#.
I want to allow user input anything which is not in "PromptDialog.Choice"'s options. Any better ways to recommend?
This is my code.
private async Task SelectCategory(IDialogContext context)
{
List<string> options = new List<string>();
options = category.Keys.ToList();
options.Add("Category1");
options.Add("Category2");
options.Add("Category3");
PromptOptions<string> promptOptions = new PromptOptions<string>(
prompt: "which one do you prefer?",
tooManyAttempts: "",
options: options,
attempts: 0);
PromptDialog.Choice(context: context, resume: ResumeAfterSelectCategory, promptOptions: promptOptions);
await Task.FromResult<object>(null);
}
private async Task ResumeAfterSelectCategory(IDialogContext context, IAwaitable<string> result)
{
try
{
selected = await result;
}
catch (Exception)
{
// if the user's input is not in the select options, it will come here
}
}
But the problem is it always send the message "tooManyAttempts". If I set it to empty, I will send me "0".
I suppose you are using NodeJS. You can use the simple builder.Prompts.choice with maxRetries set to 0. Here is a sample snippet. It asks user to choose some option from a list, or they can enter something which is not in the list.
If you are using C# SDK, you can find some similar option for the list.
bot.dialog("optionalList", [
function(session){
builder.Prompts.choice(
session,
"Click any button or type something",
["option1", "option2", "option3"],
{maxRetries: 0} // setting maxRetries to zero causes no implicit checking
)
},
function(session, result){
// something from the list has been clicked
if(result.response && result.response.entity){
console.log(result.response.entity); // use the clicked button
} else {
console.log(session.message.text) // if user entered something which is not in the list
}
}
]);
EDIT 1:
Hi, Saw that you are using C# SDK. I am not that proficient with that but I can give you some suggestion.
The list which you generate in the async task SelectCategory you can generate in some other place, which is also accessible to the second async task ResumeAfterSelectCategory, (like making it a class variable or getting from database).
Now that the list is accessible in the 2nd task, you can compare what user has typed against the list to determine if the message is from the list or not.
If message is something from the list, then take action accordingly, otherwise user has entered something which is not in the list, and then take action accordingly.
Your 2nd problem is
And if user typed, it will show a message "you tried to many times"
What is meant by that? Does bot sends "you tried to many times" to the bot visitor. In which case it could be the behavior of library. You will be able to control that only if library provides some option. Else I don't know. Hope, that helps
EDIT 2:
I came across this SO question Can I add custom logic to a Bot Framework PromptDialog for handling invalid answers?
You can use that questions answer. Basically extending PromptDialog.PromptChoice<T>.Here is an example.
Override TryParse method like this
protected override bool TryParse(IMessageActivity message, out T result)
{
bool fromList = base.TryParse(message, out result);
if (fromList)
{
return true;
} else {
// do something here
return true; // signal that parsing was correct
}
}
I used node.js and to get message which user entered. use this code snippet.
(session, args) => {
builder.Prompts.text(session, "Please Enter your name.");
},
(session, args) => {
session.dialogData.username = args.response;
session.send(`Your user name is `${session.dialogData.username}`);
}
Is there a way in Angular2 to have an event fired when my component becomes visible?
It is placed in a tabcontrol and I want to be notified when the user switches. I'd like my component to fire an event.
What I finally did (which is not very beautiful but works while I don't have a better way to do it...) is to use the ngAfterContentChecked() callback and handle the change myself.
#ViewChild('map') m;
private isVisible: boolean = false;
ngAfterContentChecked(): void
{
if (this.isVisible == false && this.m.nativeElement.offsetParent != null)
{
console.log('isVisible switched from false to true');
this.isVisible = true;
this.Refresh();
}
else if (this.isVisible == true && this.m.nativeElement.offsetParent == null)
{
console.log('isVisible switched from true to false');
this.isVisible = false;
}
}
There is no such event, but if you're using a tab control, the proper way to do this would be to create a tab change #Output for your tab control if it's custom, otherwise, most tab controls (like ng-bootstrap) have some tab change event as well.
If your component has to be aware of this, you can use this tab change event to detect which tab is visible, and if you know which tab is visible, you also know if your component is visible or not. So you can do something like this:
onTabChange(event) {
this.currentTab = /** Get current tab */;
}
And then you can send it to your component itself if you have an input:
#Input() activated: boolean = false;
And then you can apply it with:
<my-component [activated]="currentTab == 'tabWithComponent'"></my-component>
Now you can listen to OnChanges to see if the model value activated changed to true.
You can also refactor this to use a service with an Observable like this:
#Injectable()
export class TabService {
observable: Observable<any>;
observer;
constructor() {
this.observable = Observable.create(function(observer) {
this.observer = observer;
});
}
}
When a component wishes to listen to these changes, it can subscribe to tabService.observable. When your tab changes, you can push new items to it with tabService.observer.next().
You can use the ngAfterViewInit() callback
https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html
Update
The new Intersection Observer API can be used for that
https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
See also https://stackoverflow.com/a/44670818/217408
For those watching at home, you can now use ngAfterContentInit() for this, at least on Ionic anyway.
https://angular.io/guide/lifecycle-hooks
Best way to work around this limitation of Angular is to use a shared service that provides a Subject your component can subscribe to. That way new values could be pushed onto the Observable and the components which subscribe get the newest data and can act accordingly.
Fyi: The difference between a normal Observable and a Subject is that a Subject is multicast whereas an Observable could only be subscribed to by one Subscriber.
As a small example I show you a possible implementation of a shared-service and following the subscription inside the component that needs this new data.
Shared-service:
// ...
private actualNumberSubject = new Subject<number>()
public actualNumber$ = this.actualNumberSubject.asObservable()
/**
* #info CONSTRUCTOR
*/
constructor() {}
/**
* #info Set actual number
*/
setActualNumber(number: number) {
this.actualNumberSubject.next(internalNumber)
}
// ...
Push new value onto the subject from anywhere where shared.service is imported:
// ...
this.sharedService.setActualNumber(1)
Subscribe to sharedService.actualNumber$ in component to process/display that new data:
// ...
this.sharedService.actualNumber$.subscribe(number => {
console.log(number)
// e.g. load data freshly, etc.
})
// ...
I have the same purpose and cannot get a satisfy approach to it. The first answer will call so many times.
There is a compromised way I used, of course, not elegant either.
In parent component, I set a method:
parentClick() {
setTimeout(() => {
// TO-DO
This.commonService.childMethod();
}, time);
}
Maybe the method not accurate in time, but in some way, you reach the destiny.
I'm using jQuery with it's widget factory and using custom events to handle events in my application.
This means that all my event binding looks a lot like:
//...in the widget factory code
$(this.element).addClass('customEventClass');
$(this.element).bind('mysite.loadNextPage', $.proxy(this, 'loadNextPage');
and the events are triggered by doing:
$('.customEventClass').trigger('mysite.loadNextPage');
Because the events are directly bound to the elements that need to receive them, I don't need to have these custom events to bubble up through the DOM. I know I can check whether the even has bubbled up or not by doing this in the event handler code:
if (event.target != event.currentTarget) {
event.stopPropagation();
event.preventDefault();
return;
}
But at the moment because most of the elements that are listening for the custom events don't have a handler registered for 'mysite.loadNextPage' there are 51 events generated where only 1 actually does anything. Is there a way to either:
tell jQuery not to bubble these events at all or
Add a default 'stop propagation' handler to all DOM objects that have class 'customEventClass' to stop them from bubbling up an event that they don't have a specific handler for.
Or are there any other good practices for only triggering events on elements that are interesting in those events, rather than having lots of events be triggered for elements that aren't interested in those events.
You can also return false from your event handler function to stop propagation, that's what I normally use:
Returning false from an event handler will automatically call event.stopPropagation() and event.preventDefault(). A false value can also be passed for the handler as a shorthand for function(){ return false; }. So, $( "a.disabled" ).on( "click", false ); attaches an event handler to all links with class "disabled" that prevents them from being followed when they are clicked and also stops the event from bubbling.
See http://api.jquery.com/on/
It looks like there's no good way to do it with jQuery as it is, but it's very easy to write a new function to allow this.
First I wrote a new function to stop the event from bubbling(, and also log the event why not).
function eventWrapper(event){
var logString = 'Event called: ' + event.type + ":" + event.namespace;
if (jQuery.isFunction(this.log) == true) {
this.log(logString);
}
else if (jQuery.isFunction(Logger.log) == true) {
Logger.log(logString);
}
else{
console.log(logString);
}
event.stopPropagation();
}
And now a new function that is added to jQuery.
// A global GUID counter for objects
guidWrapper: 1,
proxyWrapper: function(wrappedFn, fn, context, wrapFn ) {
var args, proxy, tmp;
if ( typeof context === "string" ) {
tmp = fn[ context ];
context = fn;
fn = tmp;
}
// Quick check to determine if target is callable, in the spec
// this throws a TypeError, but we will just return undefined.
if ( !jQuery.isFunction( fn ) ) {
return undefined;
}
// Simulated bind
args = core_slice.call( arguments, 3 );
proxy = function() {
wrappedFn.apply( context || this, args.concat( core_slice.call( arguments ) ) );
return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) );
};
// Set the guid of unique handler to the same of original handler, so it can be removed
proxy.guid = fn.guid = fn.guid || jQuery.guid++;
return proxy;
},
And then instead of binding the function like this:
$(this.element).bind('click', $.proxy(this.click, this));
Instead bind it like this.
$(this.element).bind('click', $.proxyWrapper(eventWrapper, this.click, this));
This means that when the event is triggered, the first element that is listening for that event will call event.stopPropagation on the event, and so it won't bubble up to other elements that may also be listening for that event.
for a menu checkItem, when user clicks at it, by default it will trigger checkchange; i am wondering how to, if a certain case is met, not change its check status after clicked, in other words, stop this event chain.
I tried following codes, but not work:
listeners: {
'click': function(item, evt) {
if(1) { //verify whether that certain case
evt.stopEvent(); //since click_event is triggered before setChecked()/checkChange, I thought this may stop its going further...
alert('case met!');
}
},
checkHandler: function(item, checked) {
//...
}
You can listen to the beforecheckchange event. Here it is in the docs.
As per the docs, just apply your conditional logic and if it doesn't pass, return false from the handler.
E.g.:
listeners: {
'beforecheckchange': function(item, checked) {
if(!1) { // or whatever your conditional logic is
return false;
}
},
}