I'm trying set easyautocomplete on my input. Dataset i am geting from ajax json and there is some delay. If user is too fast and writes for example "Adam" and pushes tab, cursor skips to next input, but after easyautocomplete shows dialog on previous input and doesn´t hide it. Is there any way how to show easyautocomplete dialog only when i have cursor in input?
var options = {
minCharNumber: 3,
url: function(phrase) {
return "data?q=" + phrase;
},
getValue: function(element) {
return element.surname + " " + element.name;
},
template: {
type: "description",
fields: {
description: "phone"
}
},
ajaxSettings: {
dataType: "json",
method: "POST",
data: {
dataType: "json"
}
},
list: {
onClickEvent: function() {
/*Some action*/
},
hideAnimation: {
type: "slide", //normal|slide|fade
time: 400,
callback: function() {}
}
},
requestDelay: 400
};
$(".autoComplete").easyAutocomplete(options);
Minimum, Complete, Verifiable, Example
In order to easily see this result, you'll have to open up your dev tools and throttle your network traffic so the ajax connection takes a little while
Here's a Demo of the issue in jsFiddle
Handling Library Events (Doesn't Work)
My initial thought was you could handle this during some of the EAC lifecycle events that fired, like the onLoadEvent or onShowListEvent:
var options = {
url: "https://raw.githubusercontent.com/KyleMit/libraries/gh-pages/libraries/people.json",
getValue: "name",
list: {
match: {
enabled: true
},
onLoadEvent: function() {
console.log('LoadEvent', this)
},
onShowListEvent: function() {
console.log('ShowListEvent', this)
}
},
};
However, these methods don't seem to provide an option to alter the control flow and prevent future events
Updating Source Code (Works)
Peeking into the library's source code, Easy AutoComplete does the following ...
Handles keyup events
Which then calls loadData
Which fires an AJAX request with the provided URL
depending on the network and server speed, any amount of time can pass before step 4, and the input could lose focus
Once the ajax promise is returned, will call showContainer()
Which triggers the "show.eac" event on the container
Which then opens the list with the selected animation
During step 6, we could add one last check to confirm the selected input still has focus before actually opening, which would look like this:
$elements_container.on("show.eac", function() {
// if input has lost focus, don't show container
if (!$field.is(":focus")) {return}
// ... rest of method ...
Here's a working demo in Plunker which modifies the library's source code in a new file
Unfortunately, that's not a great practice as it leaves you fragile to future changes and transfers ownership of the lib maintenance to your codebase. But it does work.
I created Pull Request #388 with the proposed changes, so hopefully a long term fix within the library itself will be available at some point in the future.
Wrapper (Recommended for now)
If you don't want to muck with third party code, there are some weird workarounds to mess with the internal execution. We can't modify the showContainer method since it's inside a closure.
We can add another listener on the show.eac event so we can be a part of the event pipeline, however there are some challenges here too. Ideally, we'd like to fire before the library handler is executed and conditionally stop propagation. However, initializing EAC will both a) create the container we have to listen to and also b) attach an event listener.
Note: Event handlers in jQuery are fired in the order they are attached!
So, we have to wait until after the lib loads to attach our handler, but that means we'll only fire after the list is already displayed.
From the question How to order events bound with jQuery, we can poke into the jQuery internals and re-order attached events so we can fire our handler before the library's handler is called. That'll look like this:
$._data(element, 'events')["show"].reverse()
Note: Both e.stopPropagation() and e.preventDefault() won't work since they prevent future events; instead we need to take more immediate action with e.stopImmediatePropagation()
So the wrapper would look like this:
$("#countries").easyAutocomplete(options);
$(".easy-autocomplete-container").on("show.eac", function(e) {
var inputId = this.id.replace('eac-container-','')
var isFocused = $("#"+inputId).is(":focus")
if (!isFocused ) {
e.stopImmediatePropagation()
}
});
$(".easy-autocomplete-container").each(function() {
$._data(this, 'events')["show"].reverse()
})
Here's a working demo in CodePen
Related
I want to perform some action in my SAPUI5 control when Page Up / Page Down is pressed.
How can we handle jQuery.sap.PseudoEvents such as sappageup and sappagedown in custom control?
In your control definition, try with
metadata: {
// ...
},
init: function() {
// ...
},
onsappageup: function() {
// Page up key pressed!
},
onsappagedown: function() {
// Page down key pressed!
},
From API reference:
SAPUI5 controls should simply implement an onpseudo-event (oEvent) method. It will be invoked only when that specific pseudo event has been recognized. This simplifies event dispatching even further.
Be aware that such pseudo events can be only fired if the control has received the focus first. To make the control focusable, provide the HTML attribute tabindex[video] to your control. E.g.:
render: (renderManager, myCustomControl) => renderManager
.write("<div")
.writeControlData(myCustomControl)
.writeAttribute("tabindex", "-1") // make it focusable
.write(">")
// ...
.write("</div>")
In case you're working on keyboard handling to navigate items, take a look at the delegate sap.ui.core.delegate.ItemNavigation.
API reference of ItemNavigation
Documentation topic "Supporting Keyboard Handling in List-like Controls"
Question about a bit more advanced version of BackboneJS Todo List
BackboneJS Todo List http://todomvc.com/examples/backbone/
On click "X" link I have simple handler (events['click .destroy'] = 'clear',):
clear: function () {
this.model.destroy()
},
As I can see, I can do something like this:
clear: function () {
this.model.destroy()
.fail(function(err) {})
.done(function(resData) {
// HERE I would like to cancel firing other events, like "destroy".
})
},
In done callback I can get info if there was some problem while deleting row from table. In current case - it is Doctrine2 exception related to Foreign Key Constraint. I catch it in my todos.php, set {success: false}, and wish in done callback use it to prevent HTML element "li" to be deleted from Todo list, as it happens now.
Is that so, when method model.destroy is called, no matter failed it or succeded, event "destroy" is fired, and because of that - element "li" is been removed from Todo List ?
Maybe I should use "request" event for collection?
What technique is best practice?
Could you please paste here some example (or link to example)?
JSFiddle here - uses console.log().
window.evtqueue = [];
window.eventHold = function(e){
console.log(e.held);
if (typeof(e.held)==typeof(undefined)){
e.held = 1;
window.evtqueue.push(e);
console.log(e.type+" - "+e.which);
window.setTimeout(function(){
console.log("Triggering: "+e.type+" = "+e.which);
var evt = window.evtqueue.splice(0,1)[0];
$('#edittext').trigger(evt);
}, 1000);
return false;
} else {
console.log("Event actually triggered!");
}
}
$('#edittext').on('keydown keyup', window.eventHold)
I'm making a rich text editor, and was having some issues with quick keystrokes vs. asynchronous code (which is out of my control), so, I decided to make an event queue. However, I'm having trouble getting saved events to fire with .trigger(), and when I can manage that, they don't seem to fire their defaults.
What am I missing? Does this require more events to be bound to actually trigger the defaults, like keypress? Is it failing because the original bound event thing returns false, despite this being asynchronous?
Yes, it's because you returned false before you trigger the event
If you return false, the
event.isPropagationStopped() and event.isDefaultPrevented() is true
The source code show, if both of them is true, the trigger method will do nothing
This always gets me. After initializing all lovely UI elements on a web page, I load some content in (either into a modal or tabs for example) and the newly loaded content does not have the UI elements initialized. eg:
$('a.button').button(); // jquery ui button as an example
$('select').chosen(); // chosen ui as another example
$('#content').load('/uri'); // content is not styled :(
My current approach is to create a registry of elements that need binding:
var uiRegistry = {
registry: [],
push: function (func) { this.registry.push(func) },
apply: function (scope) {
$.each(uiRegistry.registry, function (i, func) {
func(scope);
});
}
};
uiRegistry.push(function (scope) {
$('a.button', scope).button();
$('select', scope).chosen();
});
uiRegistry.apply('body'); // content gets styled as per usual
$('#content').load('/uri', function () {
uiRegistry.apply($(this)); // content gets styled :)
});
I can't be the only person with this problem, so are there any better patterns for doing this?
My answer is basically the same as the one you outline, but I use jquery events to trigger the setup code. I call it the "moddom" event.
When I load the new content, I trigger my event on the parent:
parent.append(newcode).trigger('moddom');
In the widget, I look for that event:
$.on('moddom', function(ev) {
$(ev.target).find('.myselector')
})
This is oversimplified to illustrate the event method.
In reality, I wrap it in a function domInit, which takes a selector and a callback argument. It calls the callback whenever a new element that matches the selector is found - with a jquery element as the first argument.
So in my widget code, I can do this:
domInit('.myselector', function(myelement) {
myelement.css('color', 'blue');
})
domInit sets data on the element in question "domInit" which is a registry of the functions that have already been applied.
My full domInit function:
window.domInit = function(select, once, callback) {
var apply, done;
done = false;
apply = function() {
var applied, el;
el = $(this);
if (once && !done) {
done = true;
}
applied = el.data('domInit') || {};
if (applied[callback]) {
return;
}
applied[callback] = true;
el.data('domInit', applied);
callback(el);
};
$(select).each(apply);
$(document).on('moddom', function(ev) {
if (done) {
return;
}
$(ev.target).find(select).each(apply);
});
};
Now we just have to remember to trigger the 'moddom' event whenever we make dom changes.
You could simplify this if you don't need the "once" functionality, which is a pretty rare edge case. It calls the callback only once. For example if you are going to do something global when any element that matches is found - but it only needs to happen once. Simplified without done parameter:
window.domInit = function(select, callback) {
var apply;
apply = function() {
var applied, el;
el = $(this);
applied = el.data('domInit') || {};
if (applied[callback]) {
return;
}
applied[callback] = true;
el.data('domInit', applied);
callback(el);
};
$(select).each(apply);
$(document).on('moddom', function(ev) {
$(ev.target).find(select).each(apply);
});
};
It seems to me browsers should have a way to receive a callback when the dom changes, but I have never heard of such a thing.
best approach will be to wrap all the ui code in a function -even better a separate file -
and on ajax load just specify that function as a call back ..
here is a small example
let's say you have code that bind the text fields with class someclass-for-date to a date picker then your code would look like this ..
$('.someclass-for-date').datepicker();
here is what i think is best
function datepickerUi(){
$('.someclass-for-date').datepicker();
}
and here is what the load should look like
$('#content').load('/uri', function(){
datepickerUi();
})
or you can load it at the end of your html in script tag .. (but i dont like that , cuz it's harder to debug)
here is some tips
keep your code and css styles as clean as possible .. meaning that for text fields that should be date pickers give them one class all over your website ..
at this rate all of your code will be clean and easy to maintain ..
read more on OOCss this will clear what i mean.
mostly with jquery it's all about organization ... give it some thought and you will get what you want done with one line of code ..
edit
here is a js fiddle with something similar to your but i guess it's a bit cleaner click here
I have a jQuery grid plugin I am creating based on KnockoutJS 2.2.1. So far it is coming along well, but when the plugin is initialized on an element, the 'computed' loadGrid method invokes 3 times.
Just for a little context I am including the loadGrid method and some other related code. (The actual plugin is quite large so for brevity I only am including part of the plugin)
function GridDataModel() {
var self = this;
self.gridState = {
currentPage: ko.observable(opts.gridState.currentPage),
pageSize: ko.observable(opts.gridState.pageSize),
totalPages: ko.observable(opts.gridState.totalPages),
orderBy: ko.observable(opts.gridState.orderBy),
};
self.loadGrid = ko.computed({
read: function () {
console.log('load grid');
if (opts.dataUrl != '') {
var requestData = self.gridState;
if (self.columns.length == 0) requestData.needColumns = true;
$.getJSON(opts.dataUrl, requestData, function (data, textStatus, jqXHR) {
self.loadData(data);
});
}
},
owner: this,
deferEvaluation: false
});
}
gridDataModel = new GridDataModel();
ko.applyBindings(gridDataModel);
Notice the only dependency this computed has is on self.gridState which isn't changing to my knowledge.
I need to determine what is causing the initialization to call the load 3 times. I know loadGrid gets called when defined (b/c deferEvaluation == false), but I need to find out what is causing the other two events to fire.
So for the question...What is a way to trace what event causes a computed to reevaluate?
On another note, I set deferEvaluation : true but when I issue
gridDataModel.gridState.currentPage.valueHasMutated()
The computed does not fire. So the only way I can even get the computed to work is if deferEvaluation == false.
Chrome developer tools on the 'Sources' tab might be able to help. Just check out the panels on the right that will let you set breakpoints on various DOM elements.
See this overview of the scripts panel (now named the 'Sources' panel) or this overview of creating breakpoints on DOM events for more help.
I use the knockoutjs chrome plugin and I use messages for KO, that way you can display stuff to the console. Example of what I did in the past.
self.messages.push(response.msg);