How to hold event triggering in angular.
This question related with my another question : how to stop array.filter() function
I have tried those all answers. But am not satisfied with that answers.
So I did something from my own idea.
Problem:
I have a ngmodelchange event and that is for calling a method. You can see my latest question for why am using ngModelChange() event. Anyway I will explain here also.
Am search and filtering large amount of data using ngModelChange event. So the problem is when I typing character's continuously the application is hanging.
My Suggestion
So I have suggested myself for use some logic to call the filter function after user has stop the typing. But we don't know when the user will stop typing.
So I create a logic below.
What am Did
(ngModelChange)="clearSearch(gloablFilterValue)"// html side
In .ts side
clearSearch(newValue: string) {
this.oldFilterText = newValue;
if (this.isLooping) {
return false;
}
for (let i = 0; i > newValue.length + 1; i++) {
this.isLooping = true;
if (this.oldFilterText == newValue) {
this.splitCustomFilter();//filter function
}
}
}
The above code is not working. am just starting to write logic. So if some have any idea. please come with me you suggestion and ideas to achieve this.
Main question
How can we stop the event triggering until the user has typing values in text box? And how can we start the event triggering the user has typed values in textbox?
**Important: I don't want to use focus out and blur events. because the filter should works while the user typing the values.
What you're looking for is called deboucing. A good alternative is throttling. RxJS supports has both operators, called debounceTime and throttleTime. Here's a quick example of how to utilize them.
In the template, listen for the input event (or some other event, based on what you're listening to in your use-case).
<input type=text (input)="onInput($event.target.value)">
In your component code, create a subject and then use the appropriate operator on it before you subscribe to it.
// create a subject
private subject = new Subject<string>()
// function to call when event fires
public onInput(input: string) { this.subject.next(input) }
ngOnInit() {
// debounce the stream
this.subject.debounceTime(300).subscribe(inputText => {
// this is where you can do you .filter
console.log(inputText)
})
}
Of course, you can tweak the 300 parameter as you wish.
Related
In CKEditor 4 I want to fire some action on key and paste events. I've got working code for single event:
$('#some_id').ckeditor({
some: config
}).ckeditor().editor.on('key', function(evt) {
//some action here
});
And I don't want to repeat all code for next event. I've searched ckeditor docs - and it says that on method takes only string, so give it an array of events isn't possible. I've tried pass multiple events as string key, paste - it wasn't best idea. Another way could be made an array of events and iterate it with code above - this solution seems to be not ideal, but the best I can figure out for now. Have You any better ideas for this problem?
Since nobody have any idea in this matter, I finished with best solution I could figure out on this moment: provide array of events and iterate it. I paste here my solution for others facing same dillema:
var editor = $('#textarea').ckeditor({
//some:config
}).ckeditor().editor;
var events = ['event1', 'event2'];
for (event of events) {
editor.on(event, function(evt) {
//Yours actions
}
}
I have a probably really simple question but did not find anything about this or maybe did not find the right words for my problem.
If have a function to be executed on keypress which also changes my variable A - fine, and it works.
But now I want to give an alternative value to my variable A if the keypress event is not happening.
So I'm looking for the correct command for the naive logic of
if ("keypress event happens") {
A = 1
} else {
A = 2
}
Is there any way to do that in js or jquery with simple true/false checks for the key event?
I've been trying and trying and it did not work once.
Usually, the way one solves this problem is with a setTimeout(). You set the timer for N seconds. If the keypress happens, you cancel the timer. If the keypress doesn't happen, the timer will fire giving you your alternate event.
You probably wrap this in some sort of function that you can trigger whenever you want, but you didn't share the overall context so this is just the general idea:
$("#myObj").keypress(function(e) {
if (timer) {
clearTimeout(timer);
}
// process key
});
var timer = setTimeout(function() {
timer = null;
// key didn't happen within the alltoted time so fire the alternate behavior
}, 5000);
I hope I'm not breaking any guidelines or anything as this is mostly a showcase of my search setup. There are a few question at the bottom. I promise.
I've made a Sencha Touch 2 PhoneGap app (which is now live in the AppStore for iOS, Android will be ready soon).
The basic premise of the app is a (somewhat huge) searchable list (with a couple of additional filters). Thus search performance is essential.
A rundown of my search controller:
When looking at examples I found people using either a submit button or search on keyup, since the latter interested me the most this is where I started.
In the examples I found, only the keyup listener was activated, a quick fix:
'searchfield[itemId=searchBox]' : {
clearicontap : 'onClearSearch',
keyup: 'onSearchKeyUp',
submit: 'onSubmit',
action: 'onSubmit',
paste: 'onSubmit',
blur: 'onSubmit'
}
Now, why are keyup and submit different you might ask?
Well, the onSubmit function just runs the search function (more on that later).
The onClearSearch just clears the filters set on the store and then adds the non search based ones back in.
The onSearchKeyUp function is where it gets interesting. Since the list is huge, searching on every single letter equals poor performance. The search field stalls while searching (especially on older devices), so while the user keeps tapping letters on the keyboard nothing shows up, while at the same time queuing up javascript and (in some cases) leading to the user tapping letters again so that when all the search functions are actually done, the input might not even be what the user intended.
I remedied this by trying to deduce when the user has finished typing. I did this by having the keyup triggering a timer, in this instance 650 ms, if another keyup event is triggered within this period, the timer is cancelled, if it runs its course it triggers the search function. This timer can of course be set higher or lower. Lower means the user has type faster for it not to search while typing. Higher means that the user will have to wait longer from their last keyup event for the actual searching to begin (perceived as lag).
Of course, there is an exception, if the keyup event is from the enter key, it searches right away. Code:
onSearchKeyUp: function(searchField,e) {
if (e.event.keyCode == 13){
window.clearTimeout(t);
window.timer_is_on=0;
searchFunction();
} else {
doTimer();
}
function timedCount()
{
t=setTimeout(function(){timedSearch();},650);
}
function timedSearch()
{
console.log('b4 search');
searchFunction();
window.timer_is_on=0;
}
function doTimer()
{
if (window.timer_is_on === 0)
{
window.timer_is_on=1;
timedCount();
} else {
window.clearTimeout(t);
window.timer_is_on=0;
console.log('cleared timeout');
doTimer();
}
}
}
onSubmit: function(searchField,e) {
if (window.timer_is_on === 0)
{
console.log('timer was not on when done was pressed');
} else {
console.log('timer was on when done was pressed');
window.clearTimeout(t);
window.timer_is_on=0;
searchFunction();
}
} else {
searchFunction();
},
So for the search function. I think I might have some room for improvement here.
First off, I noticed that when I had scrolled down the list and searched, I could end up below the actual list content, unable to scroll up. So the first thing the search function does is scroll to top.
As I mentioned in the beginning of the post, I have some additional filters. These are toolbar buttons which basically set variables and then trigger the search function. Within the search function, filtering is done based on the variables. I combined the search and filter functions as they both needed to clear filters and add them back in.
You'll also notice a loading mask and a delay. The delay is strictly empirical, turned out that a delay of 25 ms was needed for the loading mask to actually show.
My search function code:
function searchFunction() {
Ext.Viewport.setMasked({
xtype: 'loadmask',
message: 'Searching'
});
setTimeout(function() {
Ext.getCmp('lablist').getScrollable().getScroller().scrollTo(0,0);
var searchField = Ext.getCmp('searchBox');
queryString = searchField.getValue();
console.log('Please search by: ' + queryString);
var store = Ext.getStore('LabListStore');
store.clearFilter();
genderFilterFuncForSearch();
ageFilterFuncForSearch();
if(queryString){
var thisRegEx = new RegExp(queryString, "i");
store.filterBy(function(record) {
if (thisRegEx.test(record.get('Analysis'))||thisRegEx.test(record.get('Groupname'))) {
return true;
}
return false;
});
}},25);
}
function ageFilterFuncForSearch() {
if (ageFilter === 'A') {
Ext.getStore('LabListStore').filter('adult', '1');
} else if (ageFilter === 'C') {
Ext.getStore('LabListStore').filter('child', '1');
}
Ext.Viewport.setMasked(false);
}
function genderFilterFuncForSearch() {
if (genderFilter === 'M') {
Ext.getStore('LabListStore').filter('Male', '1');
} else if (genderFilter === 'F') {
Ext.getStore('LabListStore').filter('Female', '1');
}
}
That's basically my search setup. As this is my first Sencha Touch project it's been sort of a trial and error based workflow but where it's at now seems pretty good.
Hopefully someone'll have a few pointers to share (and hopefully I had some that some of you hadn't thought of).
A big part of this was collected from loads of different SO posts, thanks!
I promised I would have some questions:
Can you clear individual filters?
If so, would it be better
for performance to check which filters are set and then just
clearing those insted of calling store.clearFilter?
As you can see my searchfunction uses regex, is there a way to achieve the same
kind of search without converting it to a regex object, would it be
better for performance?
PS. Regarding 1)- I've found one way, store.filter('field', '', true); would actually clear a filter that was set in the same way. I couldn't clear the search filter in the same way though.
Background
I've got asp.net webform with a grid, and when users update textboxes in that grid, the onchange event kicks off a WebMethod call and updates the rest of the changed row. Nothing is saved at that time -- we're just updating the UI.
To commit the changes, you click the save button.
This actually works reliably in almost every scenario. However, there is one very persistant one that it feels like I should be able to solve, but it's time to call in the specialists.
The Problem Scenario
I'm using jQuery to capture the enter key, and unfortunately that event fires first, causing the page to submit before the callback completes. The row is not updated correctly. Stale and bewildering data is saved.
Update
I don't think you can make the enter behavior depend on the callback, because you could save without changing a row. In that case, if you didn't change a row, it would never save.
Now if there was some way to inspect javascript's internal list of things to do, or maybe create my own and then manage it somehow, that would work. But that's some heavy lifting for something that should be easy. So unless an expert tells me otheriwse, I have to assume that's wrong.
Attempts
Right now I'm using the built-in jQuery events and I've got this elaborate setTimeout persisting the fact that a save was attempted, pausing long enough for the WebMethod to at least get called, and relying on the callback to do the submit. But it turns out javascript ansychrony doesn't work the way I hoped, and the onchange event doesn't even fire until that chunk of code completes. That was surprising.
I was thinking I could use my own little object to queue up these events in the right order and find a clever way to trigger that, etc.
This all seems like the wrong direction. Surely this is insane overkill, this is a common problem and I'm overlooking a simple solution because I don't work in javascript 24/7.
Right?
Code
Here's what I've got right this minute. This obviously doesn't work -- I was trying to take advantage of the async nature of jquery, but all of this apparently has to conclude before the row's onchange event event fires:
$(document).bind("keypress", function (e) {
if (e.keyCode == 13) {
handleEnter();
return false; //apparently I should be using e.preventDefault() here.
}
});
function handleEnter() {
setTimeout(function () {
if (recalculatingRow) { //recalculatingRow is a bit managed by the onchange code.
alert('recalculating...');
return true; //recur
}
//$('input[id$="ButtonSave"]').click();
alert('no longer recalculating. click!');
return false;
}, 1000);
}
And then a typical row looks like this. Note that I'm not using jquery to bind this:
<input name="ctl00$MainContent$GridOrderItems$ctl02$TextOrderItemDose" type="text" value="200.00" maxlength="7" id="ctl00_MainContent_GridOrderItems_ctl02_TextOrderItemDose" onchange="recalculateOrderItemRow(this);" style="width:50px;" />
I could post the code for recalculateOrderItemRow, but it's really long and right now the problem is that it doens't fire until the after keypress event concludes.
Update Dos
According to Nick Fitzgerald (and man is that a cool article) the use of setTimeout should cause this to become async. Digging further into interactions between setTimeout and jQuery, as well as interactions between normal javascript events and jQuery events.
Preventing ENTER shouldn't be causing you so much trouble! Make sure you have something like this on your code:
$(document).on('keydown', 'input', function(e) {
if(e.keyCode == 13) {
e.preventDefault();
}
});
UPDATE
It looks like you do want to save on ENTER, but only after the UI is updated on change. That is possible. You could use a flag a Matthew Blancarte suggested above, trigger save from the change callback, and get rid of the setTimeout.
But I wouldn't recommend that. You are better off relying solely on the save button for saving. If you don't, your users will have to wait for two async operations to complete before saving is finished. So you'd have to block the UI, or keep track of all async operations, aborting some as needed. I think it's not worthy, ENTER becomes less intuitive for the users if saving takes too long.
The hideous mass of workarounds below, which effectively took me all day today and half of yesterday to write, seems to solve every permutation.
The amusing thing is that enter itself doesn't trigger onchange, if you call e.preventDefault(). Why would it? The change doesn't actually happen until the default behavior of clicking the save button occurs.
Very little else about this is amusing.
//Used in handleEnter and GridOrderItems.js to handle a deferred an attempt to save by hitting enter (see handleEnter).
var isSaving = false;
var saveOnID = '';
//When one of the fields that trigger WebMethods get focus, we put the value in here
//so we can determine whether the field is dirty in handleEnter.
var originalVal = 0;
//These fields trigger callbacks. On focus, we need to save their state so we can
//determine if they're dirty in handleEnter().
$('[id$=TextOrderItemDose], [id$=TextOrderItemUnits]').live("focus", function() {
originalVal = this.value;
});
$(document).bind("keypress", function (e) {
if (e.keyCode == 13) { //enter pressed.
e.preventDefault();
handleEnter();
}
});
//Problem:
//In the products grid, TextOrderItemDose and TextOrderItemUnits both have js in their onchange events
//that trigger webmethod calls and use the results to update the row. Prsssing enter is supposed to
//save the form, but if you do it right after changing one of those text fields, the row doesn't always
//get updated due to the async nature of js's events. That leads to stale data being saved.
//Solution:
//First we capture Enter and prevent its default behaviors. From there, we check to see if one of our
//special boxes has focus. If so, we do some contortions to figure out if it's dirty, and use isSaving
//and saveOnID to defer the save operation until the callback returns.
//Otherwise, we save as normal.
function handleEnter() {
var focusedElement = $("[id$=TextOrderItemDose]:focus, [id$=TextOrderItemUnits]:focus")
//did we press enter with a field that triggers a callback selected?
if (isCallbackElement(focusedElement) && isElementDirty(focusedElement)) {
//Set details so that the callback can know that we're saving.
isSaving = true;
saveOnID = focusedElement.attr('id');
//Trigger blur to cause the callback, if there was a change. Then bring the focus right back.
focusedElement.trigger("change");
focusedElement.focus();
} else {
forceSave();
}
}
function isCallbackElement(element) {
return (element.length == 1);
}
function isElementDirty(element) {
if (element.length != 1)
return false;
return (element.val() != originalVal);
}
function forceSave() {
isSaving = false;
saveOnID = '';
$('input[id$="ButtonSave"]').click();
}
This gets called in the change event for the textboxes:
function recalculateOrderItemRow(textbox) {
//I'm hiding a lot of code that gathers and validates form data. There is a ton and it's not interesting.
//Call the WebMethod on the server to calculate the row. This will trigger a callback when complete.
PageMethods.RecalculateOrderItemRow($(textbox).attr('id'),
orderItemDose,
ProductItemSize,
orderItemUnits,
orderItemUnitPrice,
onRecalculateOrderItemRowComplete);
}
And then, at the end of the WebMethod callback code we pull the updated form values out, put the caret where it needs to be using jquery.caret, and check to see if we need to force a save:
function onRecalculateOrderItemRowComplete(result) {
var sender, row;
sender = $('input[id="' + result.Sender + '"]');
row = $(sender).closest('tr');
row.find('input[id$="TextOrderItemDose"]').val(result.Dose);
row.find('input[id$="TextOrderItemUnits"]').val(result.Units);
row.find('span[id$="SpanTotalPrice"]').html(formatCurrency(result.TotalPrice));
calculateGrandTotalPrice();
$(document.activeElement).select();
if (isSaving && saveOnID == result.Sender) {
forceSave();
}
}
result.Sender is the ID of the calling control, which I stuffed into the WebMethod call and then returned. saveOnID may not be perfect, and it might actually be even better to maintain a counter of active/uncallback-ed WebMethod calls to be totally sure that everything wraps up before save. Whew.
Can you post your javascript? Sounds like you're on the right track. I would change my OnChange events to increment a variable before making the AJAX call. I'll call the variable inProcess and initialize it to zero. When the AJAX call comes back, I would update the inProcess to the current value minus one. On the Enter key event, I would check to that inProcess equals zero. If not, you could either warn the user or set a timeout to try again in a bit.
You could unbind the Enter key capture while you are in the onChange event, then rebind it at the end of the callback function. If you post some code, I could give a more specific answer.
It sounds like you shouldn't be calling the WebMethod asynchronously. Call it synchronously, and on success, save your data.
I'm working on a validation project and I currently have it set up where my inputs are listed as objects. I currently have this code to setup and run the events:
setup method and functions used
function setup(obj) {
obj.getElement().onfocus = function() {startVal(obj)}
obj.getElement().onblur = function() {endVal(obj)}
}
function startVal(obj) {
obj.getElement().onkeyup = validate(obj)
}
function endVal(obj) {
obj.getElement().onkeyup = ""
}
Take note to how I have it where the onkeyup event should set when the object is receives focus, However when I activate the input it acts like I tagged the validate() function directly to the onfocus and it only validates when I initially focus the input.
edit the reason I have it set up this way is so that I don't have every single one of my form elements validating each time I launch an onkeyup event(which would be a lot since forms usually involve a decent amount of typing). I got it to work by simply attaching the validate() function to the onkeyup event. I just would prefer limit it this way so the there's no unnecessary processing.
Can you not set events with other events or is there something more specific that I'm doing wrong?
Any help is appreciated!
Here is some additional information that might help:
getElement Method
function getElement() {
return document.getElementById(this.id)
}
setEvents function
function setEvents() {
firstName.setup(firstName)
}
You are calling validate directly. Unless it is returning a function, it won't work (maybe you should have read my other answer more thoroughly ;)). I think you want:
obj.getElement().onkeyup = function() {validate(obj)};
And as I stated in my comment, there is no reason to add or remove the event handler on focus. The keyup event is only raised if the element receives input, so not when other elements receive input.