Jasmine test simulate tab keydown and detect newly focused element - javascript

I have a jasmine test where I have 2 input fields. I am focusing on an first input, then simulating a keydown on the 'tab' key, and expecting focus to be on the second input. Unfortunately this is not the case. The focus does not change from the first and my test is failing. How can this be fixed so the failing test passes?
Fiddle of what I'm trying to test: http://jsfiddle.net/G2Qz3/1/
Fiddle of the failing Jasmine test: http://jsfiddle.net/mFUhK/4/
HTML:
<input id="first"></input>
<input id="second"></input>
JavaScript:
function simulateTab() {
var TAB_KEY = 9;
var keyboardEvent = document.createEvent("KeyboardEvent");
var initMethod = typeof keyboardEvent.initKeyboardEvent !== 'undefined' ? "initKeyboardEvent" : "initKeyEvent";
keyboardEvent[initMethod]("keydown", true, true, window, 0, 0, 0, 0, 0, TAB_KEY);
document.dispatchEvent(keyboardEvent);
}
describe('input tabbing test', function() {
beforeEach(function() {
document.getElementById('first').focus();
});
//this passes
it('input with id "first" should be focussed', function() {
expect(document.activeElement.getAttribute('id')).toBe('first');
});
//this fails
it('input with id "second" should be focussed after tabbing', function() {
simulateTab();
expect(document.activeElement.getAttribute('id')).toBe('second');
});
});

This cannot be done. See this SO question, which is based on this tutorial which says:
Note that manually firing an event does not generate the default action associated with that event. For example, manually firing a focus event does not cause the element to receive focus (you must use its focus method for that), manually firing a submit event does not submit a form (use the submit method), manually firing a key event does not cause that letter to appear in a focused text input, and manually firing a click event on a link does not cause the link to be activated, etc. In the case of UI events, this is important for security reasons, as it prevents scripts from simulating user actions that interact with the browser itself.

Related

angularjs ng-change doesn't get fired on ng-model-onblur directive in IE9 and 10

I have an input text box with a ng-model-on-blur directive(copied from Angularjs: input[text] ngChange fires while the value is changing) with a ng-change event handler attached to it. It works great as in when the user types out of the box it fires the function attached to ng-change. I modified the link function so it also binds on change and paste events.
link: function(scope, elm, attr, ngModelCtrl) {
if (attr.type === 'radio' || attr.type === 'checkbox') return;
if($sniffer.hasEvent('input')){
elm.unbind('input').unbind('keydown').unbind('change').unbind('paste');
}
else if($sniffer.hasEvent('change')){
elm.unbind('input').unbind('keydown').unbind('change').unbind('paste');
}
//IE8 doesn't recognize the input or change events and throws error on unbind input.
else{
elm.unbind('keydown').unbind('change').unbind('paste');
}
elm.bind('blur change paste', function() {
scope.$apply(function() {
ngModelCtrl.$setViewValue(elm.val());
});
});
}
I have a button on that page which opens a popup page from where a user can enter something and then transfer the value back to the input text box on the parent page. In most of the other browsers(Chrome, Firefox, Safari, even IE8..) the ng-change is fired when the data is received from the popup to the input text box but in IE9 and 10 ng-change isn't fired when data is received from popup. The directive is calling the ng-change function when user types out of the field, manually pastes the data or through right click on the text box but when the data is coming from the popup it doesn't fire the function.
The popup doesn't use AngularJS but opener property of the window object to transfer the value to the input text box.
Any help would be greatly appreciated!
I was able to solve the issue because of the way the change event was triggered from the popup page on the parent page element. The popup page after setting opener.parentElement.value = newValue; was executing parentElement.fireEvent('change') if document.createEventObject was true, else it was executing parentElement.dispatchEvent() for non-IE browsers.
MS introduced DOM Level 2 events with IE9 and hence fireEvent was not being supported in IE >=9 browsers. Since document.createEventObject was still true for IE>=9 it was executing fireEvent and hence in my directive elm.bind('change') was not getting triggered. I changed my popup javascript file to:
if(element.dispatchEvent) {
//IE >= 9 and other browsers event code
var evt = document.createEvent("HTMLEvents");
evt.initEvent(event, true, true );
return element.dispatchEvent(evt);
}
else if(element.fireEvent){
//IE < 9
var evt = document.createEventObject();
return element.fireEvent('on'+event,evt);
}
and ng-change began triggering for IE >= 9 also. This post Why does .fireEvent() not work in IE9? helped me in understanding the problem and solving it.
Thanks to this awesome community again!

How do I only enable a Safari toolbar button extension when the URL meets certain criteria ('validate' check)?

I have a Safari extension that places a simple button in the toolbar. It works fine right now, but I want to handle a 'validate' event in this way:
1. Button is grey/disabled
2. Listen for 'validate' event (already done)
3. Check URL to see if last four letters are .gif (can implement)
4. Enable button
I am working in a Global.html file and I am new to JS and Safari.
Inside your validate event handler, the target property of the event will refer to the UI element that is emitting the event -- in your case, the toolbar button. Toolbar buttons (instances of SafariExtensionToolbarItem) have a disabled property, which you can set to true or false.
Example:
safari.application.addEventListener('validate', function (evt) {
if (evt.command == 'myToolbarItemCommand') {
// `toolbarButtonShouldBeEnabled` stands for some test
if (toolbarButtonShouldBeEnabled) {
evt.target.disabled = false;
} else {
evt.target.disabled = true;
}
}
}, false);

Dispatch click eventon JSDOM with JavaScript

I'm trying to use JavaScript events to check an input checkbox in JSDOM.
<input type="checkbox" id="foo" />
But I can't seem to get it to check itself by dispatching an event on it:
var evt = document.createEvent("HTMLEvents");
evt.initEvent("click", false, true);
document.querySelector('#foo').dispatchEvent(evt)
However, it does work when I use jQuery's .trigger('click')
Why doesn't this code work in jsdom? I feel there's some minor inconsistency in jsdom and likely some other browser which jQuery fixes.
There is a browser dependency on the way you can manually trigger events in JavaScript.
Here's a demo.
The Code:
document.getElementById("foo").value='500';
if (document.getElementById("foo").fireEvent) {
document.getElementById("foo").fireEvent("onclick");
} else if (document.getElementById("foo").dispatchEvent) {
var clickevent=document.createEvent("MouseEvents");
clickevent.initEvent("click", true, true);
document.getElementById("foo").dispatchEvent(clickevent);
}
Updated Fiddle
Updated Code:
if (document.getElementById("foo").fireEvent) {
document.getElementById('car-make').attachEvent('onchange', update);
document.getElementById("foo").fireEvent("onchange");
} else if (document.getElementById("foo").dispatchEvent) {
document.getElementById('foo').addEventListener('change', update, false);
var clickevent=document.createEvent("MouseEvents");
clickevent.initEvent("change", true, true);
document.getElementById("foo").dispatchEvent(clickevent);
}
function update () {
alert('changed');
}
From the specs:
The change event occurs when a control loses the input focus and its value has been modified since gaining focus. This event is valid for INPUT, SELECT, and TEXTAREA. element.
Bubbles: Yes
Cancelable: No
Context Info: None
Note how this is different from the click event, for example:
The click event occurs when the pointing device button is clicked over an element.
Thus, triggering a change event will not actually change the input value.
Loosely speaking, the Cancelable: No property says that nothing will happen by default.

JavaScript + invalid argument error with fireEvent('onchange')

Some simple event driven code. For whatever reason, I can't seem to pass 'onchange' as a parameter to fireEvent(). Throws me an invalide argument error in ie 7/8. This project needs to be native. Little help?
Custom Event Creation:
createCustomEvent : function(eventName) {
var evt;
if(document.createEvent) {
evt = document.createEvent('CustomEvent');
evt.initEvent(eventName, true, true);
}else if(document.createEventObject) {
evt = document.createEventObject();
evt.eventName = eventName;
}
return evt;
},
dispatchCustomEvent : function (el, evt) {
if(el.dispatchEvent) {
el.dispatchEvent(evt);
}else if(el.fireEvent) {
console.log('on'+evt.eventName); //onchange
el.fireEvent('on'+evt.eventName, evt);
}
}
Usage:
dispatchCustomEvent(element, createCustomEvent('change'));
Okay, so according to most docs the change event will fire on any element and I quote:
"The change event is fired when an element loses focus and its value changed since gaining focus."
https://developer.mozilla.org/en-US/docs/DOM/Mozilla_Event_Reference/change
However, in <= ie8 onchange will not fire on any element besides form elements. This is incredibly lame imo, and makes modern MVC custom event listening and dispatching an issue.
Long story short, in my case I used the blur event to capture changes to my div element. This worked for me as the focus is removed between div element updates. It's a slideshow application, where two divs pop on and off the stack, left to right depending on the current location.

Is there a way to stop all other events in a dojo onBlur event handler?

Maybe I'm on the wrong track...
The setup:
I have a rather complex full dojofied web application. The important part for this question is a longish form in the central region of a dijit.layout.BorderContainer with a navigation tree and some action buttons in the other regions.
What I want to do:
If the user did enter data and did not save, they should get a warning message if he is going to leave the form (if he navigates away, klicks the "new Element" button,...). For a better user experience, I wanted to give a modal dialog with the options "save", "leave anyway", "cancel".
May idea was to use the onBlur event of the form, stop all other events (most likely an onClick on some other widget), check for changes, if there are changes, display the dialog, otherwise let the other events continue.
I do not want to add a checkChanges method to all non-form active elements!
For a first test I just tried to stop the events...
This works
<div id="formArea" dojoType="dijit.form.Form" encoding="multipart/form-data" action="" class="ContentPane" region="center">
<script type="dojo/connect" event="onBlur" >
alert("I don't think so");
</script>
</div>
...but it's ugly and I can't easily continue
This doesn't
<div id="formArea" dojoType="dijit.form.Form" encoding="multipart/form-data" action="" class="ContentPane" region="center">
<script type="dojo/connect" event="onBlur" args="e">
console.log("blur"); // ok
e.preventDefault();//event.stopt(e)//return false //<--neither of these
</script>
</div>
the problem is that if I click on a button outside of the form, the onBlur triggers, but I can't stop the onClick on the button.
I know that onBlur doesn't deliver an event object - so the e.something can't really work...
Is there any way to catch the onClick on the other element?
Pause button event listener(s) in form's onBlur if data are not saved.
See it in action: http://jsfiddle.net/phusick/A5DHf/
You have some button event listeners, register them via on.pausable(node, event, callback) instead of on():
var b1Handler = on.pausable(button1Node, "click", function() {
console.log("b1.onClick");
});
var b2Handler = on.pausable(button2Node, "click", function() {
console.log("b2.onClick");
});
Gather handlers into an array:
var handlersToPause = [b1Handler, b2Handler];
Add onBlur event listener:
on(form, "blur", function(e) {
if (this.isDirty()) {
// if data are not saved, pause button event listeners
handlersToPause.forEach(function(handler) {
handler.pause();
});
// display modal dialog here
}
});
Add e.g. onFocus event listener to resume button event listeners:
on(form, "focus", function(e) {
handlersToPause.forEach(function(handler) {
handler.resume();
});
});
Please note, that handler.pause() is pausing an onclick listener, not an event. The onclick event is waiting in the Event queue and therefore is not accessible in the execution time of onblur.
I would work out some more robust solution, but this is quick and answers your question. Anyway, have a look at dojo/aspect and its around advice to call your checkChanges without the need to change all non-form active elements.
there is afaik only confirm('question?') that will 'deadlock' the events of your page like that.
I have made a similar setup though, the way I came around this (except if user enters url in addressbar and hits enter) was a popup dialog whenever the navigation tree is clicked, sending user to a new view. Consider:
----------------------------------------
| Nav 1 | Asset1 ( view controller ) |
| Nav 2 | Asset2 ( hidden ) |
----------------------------------------
Nav 1 is the default onload view, Asset 1 is loaded, contains a 'setup page' form or similar and can be changed. The trick is, Asset1 and Asset2 is derivative from AbstractAsset which in turn is a simple ContentPane extension.
In AbstractAsset is a 'must-override-for-functionality' function called isDirty
var Viewcontroller = declare("AbstractAsset", [dijit.ContentPane], {
isDirty: function() { return false; }
});
declare("Asset1", [Viewcontroller], {
startup: function() {
// sets up form
...
// and references inputfields to 'this'
this.inputfields = this.form.getChildren();
// and saves (clones) the state of each field
var self = this;
this.inputfields.forEach(function(inputWidget) {
self.states[inputWidget.id] = inputWidget.get("value");
});
},
isDirty: function() {
var self = this;
var dirty = false;
this.form.getChildren().some(input) {
if(self.states[input.id] != input.get("value")) {
dirty = true;
return false; // breaks .some loop
}
return true;
});
return dirty;
}
})
Then in turn, every navigation click must call the currently visible view controller's isDirty function in order to procede. Lets say user clicks the nav-tree (dijit.Tree) row node Nav 2.
var navigation = dojo.declare("NavigationController", [dijit.Tree], {
currentView : null,
onLoad: function() {
// start Asset1 in viewNode by default
this.currentView = new Asset1({ }, this.viewNode);
},
onClick : function() {
if(this.currentView.isDirty()) alert("I Dont Think So");
else {
this.loadFunction(this.model.selection.getSelected());
}
}
});
This is the general idea of implementing the on-unload-check, you Will need to hook any onClick events through your 'master application controller' to determine what should happen. Check this application which serves as cms navigation controller and its page.js:587 for isDirty example

Categories

Resources