JqueryUI : close autocomplete widget when last match lost - javascript

I'm struggling with an issue on JqueryUI and its autocomplete widget. Whenever it finds at least one match, the widget appears fine. The issue occurs if the user doesn't select one of the proposals and keeps typing. Here is an example:
Auto complete finds matches, everything is fine.
User keeps typing, the searchs results are narrowed, everything is still OK
User keeps typing even more, there is no match left, but the widget remains open.
This is the behaviour I want to get rid of. I'm running JqueryUi version 1.9.2. I read this question and tried using the widget's "response" event, as suggested.
response: function(event, ui) {
if (ui.content.length === 0) {
$("#field").autocomplete('close');
}
}
Unfortunately, this accomplishes nothing in my case, because there is no response event triggered when the last match is lost.
Is there a way to fix this ?
EDIT : It seems that later versions of jQuery UI fix this. Unfortunately, upgrading to versions 1.10+ implies a LOT of changes, and this library is used like everywhere in my project. That sounds like an extremly huge time investment to fix a minor annoyance. What I'm looking for is a easier workaround.

Brewal directed me towards the solution : there was indeed an error in console, about "length" property not available for "undefined". I thought it was related to my addition to the code, but it was actually already there, well hidden. map() uses the length property in the background, it simply couldn't map en empty result set.
I changed this :
success: function(data) {
response($.map(data.fieldList, function() {
// some treatment
}));
}
into this :
success: function(data) {
if (data.fieldList !== undefined) {
response($.map(data.fieldList, function() {
// some treatment
}));
} else {
$("#field").autocomplete("close");
}
}
And that did the job.

Related

Disabling Items in APEX Interactive Grid - Action Dropdown Menu

The idea here is to remove/disable certain items from the Actions Dropdown Menu from an Interactive Grid.
I've managed to disable the "Aggregate" option so far with the following code:
`function(config) {
config.initActions = function( actions ) {
actions.hide("show-aggregate-dialog");
}
return config;
}`
Now I'm trying to do the same with some other options, such as Refresh (shown as Aktualisieren), but the following line, which was added to the previous code, does nothing:
actions.hide("show-filter-dialog");
I've tried a couple things to attempt and remove the rest, such as the the none !important css function, without results:
#ig_id button[data-action="show-filter-dialog"] {
display: none !important;
}
I've attempted the Remove action as well, with no success:
actions.remove("show-filter-dialog");
Also with the use of the "Index menu removal" function, I managed to remove the whole Daten option, though I'd prefer only disabling certain items from it, not the whole thing:
var $ = apex.jQuery;
var toolbarData = $.apex.interactiveGrid.copyDefaultToolbar();
config.toolbarData = toolbarData;
toolbarData[3].controls[0].menu.items[3]['hide'] = true;
return config;
Is there something wrong with the methods I'm using? Are they capable of changing these items? Or I can only change these with plugins?
Also, I sometimes feel confused on where I should be putting some of these codes. Should javascript functions be put only in the Attributes section of the Interactive Grid or in the When Page Loaded section?
So, after a bit of experimenting and messing around with the code, I managed to solve everything here. I'm posting the solution in case someone else might want to use it as well. There might be better, easier and likely more unified ways of hiding it, rather than using so many different functions, but I suppose I have to learn more about Javascript first. This is the code and the properties that worked for me:
function(config) {
config.initActions = function( actions ) {
actions.hide("show-aggregate-dialog"); // hides Aggregate
actions.hide("refresh"); // Hides Refresh inside "Data"
actions.hide("chart-view"); // Hides Chart. Thanks to Alli Pierre Yotti in Apex Forums
actions.remove("show-columns-dialog"); // Hides Columns
actions.remove("show-filter-dialog"); // Hides Filter
actions.remove("show-help-dialog"); // Hides Help
}
var $ = apex.jQuery;
var toolbarData = $.apex.interactiveGrid.copyDefaultToolbar();
config.toolbarData = toolbarData;
toolbarData[3].controls[0].menu.items[4]['hide'] = true; // Hides Format
toolbarData[3].controls[0].menu.items[8]['hide'] = true; // Hides Report
config.features.flashback = false; // Hides Flashback
return config;
}

Adding a selectable 'not found' entry to a Twitter Typeahead Control

I've been combining Twitter Typeahead and Bloodhound into a Knockout custom binding as a bit of an experiment, and so far I have it working quite well. However I have a use case where I wanted to have a selectable entry in the list if a user types in a search term which finds no results. This isn't something that is available by default in typeahead but I did find this issue on Github which demonstrates a workaround that seems to fit the bill. Trouble is I can't get it to work at all and my fairly limited Javascript smarts have all but run out.
The gist of this workaround is that rather than creating a Bloodhound instance and then setting the source property of the typeahead to engine.ttAdaptor() you do the following:
var engine = new Bloodhound(/* ... */);
engine.initialize();
var emptyDatum = { val: 'i am suggestion shown when there are none!' };
var sourceWithEmptySelectable = function(q, cb) {
engine.get(q, injectEmptySelectable);
function injectEmptySelectable(suggestions) {
if (suggestions.length === 0) {
cb([emptyDatum]);
}
else {
cb(suggestions);
}
}
};
$('.typeahead').typeahead(null, {
// ...
source: sourceWithEmptySelectable
});
Using the latest versions of typeahead (v0.11.1) I get an error which simply says missing source. Looking at the source code for Bloodhound it looks to me like the engine.get(q, injectEmptySelectable) call no longer works as there is no method of get on the Bloodhound class with a signature that accepts a query and a callback. There is one on the Transport class but I'm not seeing how that would be the one being used in this example. Am I correct in this or am I missing something, or is there another way to accomplish this?

Observers/hooks in Meteor

I have some collections which are related to others through an ID.
For instance, I have collections Post and Comments. I want to display the number of comments to each posts. Therefore, I have a field in Post called numComments. I could update this number in a method every time a comment with same postId is either inserted og removed but I will instead use some hooks/observers to ensure the number is always updated.
Therefore, I have created a file server/observers.js with content
Comments.find().observe({
added: function(document) {
Posts.update({ postId: document.postId }, { $inc: { numComments: 1 } });
},
changed: function(document) {
},
removed: function(document) {
Posts.update({ postId: document.postId }, { $inc: { numComments: -1 } });
},
});
I like this kind of solution but is it a good way to do it?
My problem is that since I implemented this functionality, the console window prints an awful lot of errors/warnings. I suspect it is because of the observers.
In the documentation (http://docs.meteor.com/#/full/observe), it says:
observe returns a live query handle, which is an object with a stop method. Call stop with no arguments to stop calling the callback functions and tear down the query. The query will run forever until you call this (..)
I am not sure what it means but I think the observers should be stopped manually.
Have a look at this answer. It might lead you in the right direction, since the example is very similar to what you want. You don't need a dedicated field in your collection to get a reactive counting of your comments, you can build it in your publish function.
I am not sure what it means but I think the observers should be
stopped manually
You're right. In the example linked above, the query is wrapped inside a handle variable. Notice the
self.onStop(function () {
handle.stop();
});
It allows you to make sure that no observers will still be running once you stop publishing.

Testing tab navigation order

In one of our tests, we need to make sure that the tab keyboard navigation inside a form is performed in the correct order.
Question: What is the conventional way to check the tab navigation order with protractor?
Currently we are solving it by repeating the following step for as many input fields existing in a form (code below):
check the ID of the currently focused element (using getId())
send TAB key to the currently focused element
Here is the example spec:
it("should navigate with tab correctly", function () {
var regCodePage = new RegCodePage();
browser.wait(protractor.ExpectedConditions.visibilityOf(regCodePage.title), 10000);
// registration code field has focus by default
expect(regCodePage.registrationCode.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
// focus moved to Remember Registration Code
regCodePage.registrationCode.sendKeys(protractor.Key.TAB);
expect(regCodePage.rememberRegistrationCode.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
// focus moved to Request Code
regCodePage.rememberRegistrationCode.sendKeys(protractor.Key.TAB);
expect(regCodePage.requestCode.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
// focus moved to Cancel
regCodePage.requestCode.sendKeys(protractor.Key.TAB);
expect(regCodePage.cancelButton.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
// focus moved back to the input
regCodePage.cancelButton.sendKeys(protractor.Key.TAB);
expect(regCodePage.registrationCode.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
});
where regCodePage is a Page Object:
var RegCodePage = function () {
this.title = element(by.css("div.modal-header b.login-modal-title"));
this.registrationCode = element(by.id("regCode"));
this.rememberRegistrationCode = element(by.id("rememberRegCode"));
this.requestCode = element(by.id("forgotCode"));
this.errorMessage = element(by.css("div.auth-reg-code-block div#message"));
this.sendRegCode = element(by.id("sendRegCode"));
this.cancelButton = element(by.id("cancelButton"));
this.closeButton = element(by.css("div.modal-header button.close"));
};
module.exports = RegCodePage;
It is working, but it is not really explicit and readable which makes it difficult to maintain. Also, another "smell" in the current approach is a code duplication.
If the current approach is how you would also do it, I would appreciate any insights about making it reusable.
I think the PageObject should define a tab order list, since that is really a direct property of the page, and should be expressible as simple data. An array of items seems like a sufficient representation, so something like:
this.tabOrder = [ this.registrationCode, this.rememberRegistrationCode, this.requestCode, this.cancelButton ];
Then you need a bit of generic code that can check a tab order.
function testTabOrder(tabOrder) {
// Assumes TAB order hasn't been messed with and page is on default element
tabOrder.forEach(function(el) {
expect(el.getId()).toEqual(browser.driver.switchTo().activeElement().getId());
el.sendKeys(protractor.Key.TAB);
});
}
Then your test would be something like:
it('has correct tab order', function() {
var regCodePage = new RegCodePage(); // this should probably be in the beforeEach
testTabOrder(regCodePage.tabOrder);
});
Of course, this assumes each element has a "getId()" method that works. (That seems like a reasonable assumption to me, but some environments may not support it.)
I think this keeps the tab-order nicely isolated on the PageObject (so its easy to keep in sync with the page content and doesn't get lost in the code that verifies the order). The testing code seem "optimistic" (I suspect the real world will introduce enough problems that you will end up expanding this code a bit).
I haven't tried any of this yet, so feel free to downvote if this doesn't work. :)
Also, I believe the forEach loop will work as-is, but I wouldn't be surprised if it needs some more explicit promise handling to make the dependencies explicit.

HammerJS event properties are undefined

I'm developing this small website : Website ; and I'm using HammerJS as a touch support library.
It seems to be responding to the events, and it recognizes the event.type property, but when I'm trying to get the event.direction or other related properties to the drag event nothing is output in the console ( I'm logging the results in the console ).
This is how I listen for the drag event "
Application.ApplicationController.prototype.Drag = function(selector, delay, callback) {
return $(selector).on('drag', _.debounce(function(event){
event.preventDefault();
return (typeof callback === 'function' && callback !== undefined) ? callback.apply( event, [ event ] ) : 'Argument : Invalid [ Function Required ]';
}, delay));
};
I'm calling it something like :
this.Drag(selector, delay, function(event) {
console.log(event.type, event.direction);
});
Could someone tell me what am I doing wrong in there or if I'm missing something ?
EDIT : I have just replaced the jQuery library : jquery.specialevents.hammer.js ; with the old jquery.hammer.js ; and it seems like now it's responding to all events and I get all the properties I should. Still I would like to know why isn't the one I tried to work with working ?
EDIT : I have found the underlying cause of my issue, my code depends on some libraries which I'm loading asynchronous with the Yepnope script loader, so somewhere along the way instead of loading all the libraries ( including the jquery plugin for hammer.js ), some of them are lost :) I have fixed that issue and now the events have the properties that they're supposed to.
Still I would like to know why isn't the one I tried to work with working ?
Understanding the difference between jquery.specialevent.hammer.js and jquery.hammer.js should help understand the problem. Damien, the creator of jquery.specialevent.hammer.js, explains why.
However Eight Media decided to create their own namespace in jQuery to
activate the events.
$("#element").hammer({ /* options */ }).on("tap", function(ev) {
console.log(ev);
});
So in the end they are not using the default
jQuery eventing system. That means my existing source code which used
jQuery mobile events has to be change. That’s why I decided to
implement the use of Hammer.JS with the jQuery special eventing API.
$("#element").on("tap", { /* options */ }, function(ev) {
console.log(ev);
});
I put jquery.specialevent.hammer.js onto my
Github where you can also find a demo. Maybe Eight Media accepts my
pull request and it will be part of Hammer.JS.
event.gesture.direction should get you what you are looking for.

Categories

Resources