I'm trying to migrate old jQuery code to angularjs.
The issue that I'm having is that I'm not sure on the best approach.
Bascially, depending on the selector a different type of 'event' needs to be pushed into a array called gt.
The purpose of the jQuery code is to provide detailed info of clients having issues while filling in a form. the gt array is picked up by third party software that helps the clients by asking if they want to chat.
Example of how the array is populated:
$('a').live('click', { element: this }, function (element) {
_clickedElement = this;
var linkUrl = element.currentTarget.hostname + element.currentTarget.pathname;
var querystring = window.location.search
var shortLocationUrl = window.location.href.replace(querystring, "").replace("http://", "").replace("https://", "");
if (element.currentTarget.hostname.length > 0 && element.currentTarget.target != "_blank" && linkUrl != shortLocationUrl) { //click on a link that opens in the current window and points to a page external to this part
_gt.push(['event', { eventName: 'Leave_Page_' + chat.name, name: chat.name, pageName: chat.pageName, locale: _locale, isClient: chat.isClient }]);
_pushLeavePageEvent = false;
}
else if (this.id == backButtonId) { //click "previous"
_gt.push(['event', { eventName: 'Go_Back_' + chat.name, name: chat.name, pageName: chat.pageName, locale: _locale, isClient: chat.isClient }]);
_pushLeavePageEvent = false;
}
return true;
});
So for all the a tags inside my page (or form) the above code needs to be executed.
What would be a good approach to have similar behaviour in Angularjs?
I was thinking of a directive but I'm not sure whether to make this a directive at the level of my form or make a directive that I then use throughout my page?
P.S.: similar behaviour is needed (pushing an event into the gt array) for all the input, textarea and select fields on the page as well as the errors on the page caused by the clients and when a client hovers over a tooltip.
I've creeated an Apache Cordova project for a single page application. On the start page there is a list of items. When I click on a item I navigate to the details page with
window.navigate("#/details/" + id);
Angular.js displays the details template but when I use the hardware backbutton on a windows phone it suspends the application instead ov navigating back. So I tried to hook up to the backbutton event
function onDeviceReady() {
// Handle the Cordova pause and resume events
document.addEventListener('pause', onPause.bind(this), false);
document.addEventListener('resume', onResume.bind(this), false);
document.addEventListener("backbutton", onBackButton, false);
}
but this event isn't even fired when I click the back button. I also use WinJS and tried WinJS.Application.onbackclick but even this doesn't work.
So how can I handle the back button on Windows Phone universal?
This looks like a bug in the Windows Phone Runtime target for Cordova. In Silverlight the CordovaPage.xaml.cs file hooks up the Windows Phone BackButton event handling and forwards it to Cordova's backButton event.
This doesn't happen in the Runtime version, so you'll have to add your own platform specific handling either by handling WinJS's onbackclick event or the Windows.Phone.UI.Input.HardwareButtons.BackPressed event.
This worked for me...
function onBackPressed(eventArgs) {
eventArgs.handled = true;
/* Your behaviour/navigation */
}
Windows.Phone.UI.Input.HardwareButtons.addEventListener("backpressed", onBackPressed);
To prevent default behaviour (e.g. suspending the app):
eventArgs.handled = true
More Information: HardwareButtons.BackPressed
Windows Phone WinJS onbackclick needs to return true or false depending on if you want to exit your app (when you've navigated all the way back and finally do want to exit) or not.
//for not Windows Phone users
document.addEventListener("backbutton", function () { goBack(); }, false);
//for Windows Phone users
if (Windows && WinJS){
WinJS.Application.onbackclick = function (event) {
event.handled = true;
return goBack();
};
}
goBack function returns true when it executes and false when it can't go back any further.
var backstack = 0;
function goForward(){
backstack++;
//additional navigation code
};
function goBack(){
//show hide back arrow
if (Windows && WinJS) {
var currentview = Windows.UI.Core.SystemNavigationManager.getForCurrentView();
currentview.appViewBackButtonVisibility = backstack < 1;
}
if(backstack > 1) {
backstack--;
//additional back navigation code
return true;
}
else return false;
};
We are working on an application in which we are using Angularjs + webapi and making a Single Page App.
We have quite few Modal Pop-ups from Bootstrap used in the app, but we are facing an issue
-> on click of the modal button, the backdrop initializes and then is stuck with no modal. and we have to force refresh the browser. We are pulling the templates from other files into the modals.
initially for closing the modal, I've used $(".modal.in").hide(); and the close is fine with out the "Stuck" on grey screen.
then later for init the modal itself it started to appear then I removed the fade class and removed the animations from the css but didn't help much.
Really need a promising fix.
$.support.transition = (function () {
var thisBody = document.body || document.documentElement
, thisStyle = thisBody.style
, support = thisStyle.transition !== undefined || thisStyle.WebkitTransition !== undefined || thisStyle.MozTransition !== undefined || thisStyle.OTransition !== undefined
return support
})()
// set CSS transition event type
if ( $.support.transition ) {
transitionEnd = "TransitionEnd"
if ( $.browser.webkit ) {
transitionEnd = "webkitTransitionEnd"
} else if ( $.browser.mozilla || $.browser.msie ) {
transitionEnd = "transitionend"
} else if ( $.browser.opera ) {
transitionEnd = "oTransitionEnd"
}
}
The above code snippet was advised in some forums.
But in the Bootstrap.js Bootstrap v3.2.0 (http://getbootstrap.com)
function transitionEnd() {
var el = document.createElement('bootstrap')
var transEndEventNames = {
WebkitTransition : 'webkitTransitionEnd',
MozTransition : 'transitionend',
OTransition : 'oTransitionEnd otransitionend',
transition : 'transitionend'
}
for (var name in transEndEventNames) {
if (el.style[name] !== undefined) {
return { end: transEndEventNames[name] }
}
}
return false // explicit for ie8 ( ._.)
}
You can create a directive that includes the templates you want and launch and then control the boostrap modals from with in that directive. the external communication can be achieved in many ways my favorite using custom events watched by the directive to know when to hide or show the modals that way you can use all the bootstrap code you already have from INSIDE your directive. this can also be done through a service, not my favorite solution, but is also accepted. for more ideas look at the angular-ui project and the modal service
http://angular-ui.github.io/bootstrap/
I have a SPA application on stack ASP MVC + AngularJS and I'd like to test the UI.
For now I'm trying Selenium with PhantomJS and WebKit drivers.
This is a sample testing page - view with single element. The list items <li> load dynamically from server and are bounded by Angular.
<div id="items">
<li>text</li>
<li>text2</li>
</div>
I'm trying to pass a test and there is an error in this line:
_driver.FindElements(By.TagName('li'))
At this point there are no loaded elements and _driver.PageSource doesn't contain elements.
How can I wait for the items to load? Please do not suggest Thread.Sleep()
This will wait for page loads / jquery.ajax (if present) and $http calls, and any accompanying digest/render cycle, throw it in a utility function and wait away.
/* C# Example
var pageLoadWait = new WebDriverWait(WebDriver, TimeSpan.FromSeconds(timeout));
pageLoadWait.Until<bool>(
(driver) =>
{
return (bool)JS.ExecuteScript(
#"*/
try {
if (document.readyState !== 'complete') {
return false; // Page not loaded yet
}
if (window.jQuery) {
if (window.jQuery.active) {
return false;
} else if (window.jQuery.ajax && window.jQuery.ajax.active) {
return false;
}
}
if (window.angular) {
if (!window.qa) {
// Used to track the render cycle finish after loading is complete
window.qa = {
doneRendering: false
};
}
// Get the angular injector for this app (change element if necessary)
var injector = window.angular.element('body').injector();
// Store providers to use for these checks
var $rootScope = injector.get('$rootScope');
var $http = injector.get('$http');
var $timeout = injector.get('$timeout');
// Check if digest
if ($rootScope.$$phase === '$apply' || $rootScope.$$phase === '$digest' || $http.pendingRequests.length !== 0) {
window.qa.doneRendering = false;
return false; // Angular digesting or loading data
}
if (!window.qa.doneRendering) {
// Set timeout to mark angular rendering as finished
$timeout(function() {
window.qa.doneRendering = true;
}, 0);
return false;
}
}
return true;
} catch (ex) {
return false;
}
/*");
});*/
Create a new class that lets you figure out whether your website using AngularJS has finished making AJAX calls, as follows:
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.ExpectedCondition;
public class AdditionalConditions {
public static ExpectedCondition<Boolean> angularHasFinishedProcessing() {
return new ExpectedCondition<Boolean>() {
#Override
public Boolean apply(WebDriver driver) {
return Boolean.valueOf(((JavascriptExecutor) driver).executeScript("return (window.angular !== undefined) && (angular.element(document).injector() !== undefined) && (angular.element(document).injector().get('$http').pendingRequests.length === 0)").toString());
}
};
}
}
You can use it anywhere in the your code by using the following code:
WebDriverWait wait = new WebDriverWait(getDriver(), 15, 100);
wait.until(AdditionalConditions.angularHasFinishedProcessing()));
We have had a similar issue where our in house framework is being used to test multiple sites, some of these are using JQuery and some are using AngularJS (and 1 even has a mixture!). Our framework is written in C# so it was important that any JScript being executed was done in minimal chunks (for debugging purposes). It actually took a lot of the above answers and mashed them together (so credit where credit is due #npjohns). Below is an explanation of what we did:
The following returns a true / false if the HTML DOM has loaded:
public bool DomHasLoaded(IJavaScriptExecutor jsExecutor, int timeout = 5)
{
var hasThePageLoaded = jsExecutor.ExecuteScript("return document.readyState");
while (hasThePageLoaded == null || ((string)hasThePageLoaded != "complete" && timeout > 0))
{
Thread.Sleep(100);
timeout--;
hasThePageLoaded = jsExecutor.ExecuteScript("return document.readyState");
if (timeout != 0) continue;
Console.WriteLine("The page has not loaded successfully in the time provided.");
return false;
}
return true;
}
Then we check whether JQuery is being used:
public bool IsJqueryBeingUsed(IJavaScriptExecutor jsExecutor)
{
var isTheSiteUsingJQuery = jsExecutor.ExecuteScript("return window.jQuery != undefined");
return (bool)isTheSiteUsingJQuery;
}
If JQuery is being used we then check that it's loaded:
public bool JqueryHasLoaded(IJavaScriptExecutor jsExecutor, int timeout = 5)
{
var hasTheJQueryLoaded = jsExecutor.ExecuteScript("jQuery.active === 0");
while (hasTheJQueryLoaded == null || (!(bool) hasTheJQueryLoaded && timeout > 0))
{
Thread.Sleep(100);
timeout--;
hasTheJQueryLoaded = jsExecutor.ExecuteScript("jQuery.active === 0");
if (timeout != 0) continue;
Console.WriteLine(
"JQuery is being used by the site but has failed to successfully load.");
return false;
}
return (bool) hasTheJQueryLoaded;
}
We then do the same for AngularJS:
public bool AngularIsBeingUsed(IJavaScriptExecutor jsExecutor)
{
string UsingAngular = #"if (window.angular){
return true;
}";
var isTheSiteUsingAngular = jsExecutor.ExecuteScript(UsingAngular);
return (bool) isTheSiteUsingAngular;
}
If it is being used then we check that it has loaded:
public bool AngularHasLoaded(IJavaScriptExecutor jsExecutor, int timeout = 5)
{
string HasAngularLoaded =
#"return (window.angular !== undefined) && (angular.element(document.body).injector() !== undefined) && (angular.element(document.body).injector().get('$http').pendingRequests.length === 0)";
var hasTheAngularLoaded = jsExecutor.ExecuteScript(HasAngularLoaded);
while (hasTheAngularLoaded == null || (!(bool)hasTheAngularLoaded && timeout > 0))
{
Thread.Sleep(100);
timeout--;
hasTheAngularLoaded = jsExecutor.ExecuteScript(HasAngularLoaded);
if (timeout != 0) continue;
Console.WriteLine(
"Angular is being used by the site but has failed to successfully load.");
return false;
}
return (bool)hasTheAngularLoaded;
}
After we check that the DOM has successfully loaded, you can then use these bool values to do custom waits:
var jquery = !IsJqueryBeingUsed(javascript) || wait.Until(x => JQueryHasLoaded(javascript));
var angular = !AngularIsBeingUsed(javascript) || wait.Until(x => AngularHasLoaded(javascript));
If you're using AngularJS then using Protractor is a good idea.
If you use protractor you can use it's waitForAngular() method which will wait for http requests to complete. It's still good practise to wait for elements to be displayed before acting on them, depending on your language and implementation it might look this in a synchronous language
WebDriverWait wait = new WebDriverWait(webDriver, timeoutInSeconds);
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id<locator>));
Or in JS you can use wait method which executes a function until it returns true
browser.wait(function () {
return browser.driver.isElementPresent(elementToFind);
});
You may just mine protractor for useful code snippets. This function blocks until Angular is done rendering the page. It is a variant of Shahzaib Salim's answer, except that he is polling for it and I am setting a callback.
def wait_for_angular(self, selenium):
self.selenium.set_script_timeout(10)
self.selenium.execute_async_script("""
callback = arguments[arguments.length - 1];
angular.element('html').injector().get('$browser').notifyWhenNoOutstandingRequests(callback);""")
Replace 'html' for whatever element is your ng-app.
It comes from https://github.com/angular/protractor/blob/71532f055c720b533fbf9dab2b3100b657966da6/lib/clientsidescripts.js#L51
I did the following code and it helped me for the async race condition failures.
$window._docReady = function () {
var phase = $scope.$root.$$phase;
return $http.pendingRequests.length === 0 && phase !== '$apply' && phase !== '$digest';
}
Now in selenium PageObject model, you can wait for
Object result = ((RemoteWebDriver) driver).executeScript("return _docReady();");
return result == null ? false : (Boolean) result;
If your web app is indeed created with Angular as you say, the best way to do end-to-end testing is with Protractor.
Internally, Protractor uses its own waitForAngular method, to ensure Protractor waits automatically until Angular has finished modifying the DOM.
Thus, in the normal case, you would never need to write an explicit wait in your test cases: Protractor does that for you.
You can look at the Angular Phonecat tutorial to learn how to set up Protractor.
If you want to use Protractor seriously, you will want to adopt pageobjects. If you want an example of that have a look at my page object test suite for the Angular Phonecat.
With Protractor you write your tests in Javascript (Protractor is indeed based on Node), and not in C# -- but in return Protractor handles all waiting for you.
For my particular problem with the HTML page containing iframes and developed with AnglularJS the following trick saved me a lot of time:
In the DOM I clearly saw that there is an iframe which wraps all the content.
So following code supposed to work:
driver.switchTo().frame(0);
waitUntilVisibleByXPath("//h2[contains(text(), 'Creative chooser')]");
But it was not working and told me something like "Cannot switch to frame. Window was closed".
Then I modified the code to:
driver.switchTo().defaultContent();
driver.switchTo().frame(0);
waitUntilVisibleByXPath("//h2[contains(text(), 'Creative chooser')]");
After this everything went smoothly.
So evidently Angular was mangling something with iframes and just after loading the page when you expect that driver is focused on default content it was focused by some already removed by Angular frame.
Hope this may help some of you.
If you don't want to make the entire switch to Protractor but you do want to wait for Angular I recommend using Paul Hammants ngWebDriver (Java). It's based on protractor but you don't have to make the switch.
I fixed the problem by writing an actions class in which I waited for Angular (using ngWebDriver's waitForAngularRequestsToFinish()) before carrying out the actions (click, fill, check etc.).
For a code snippet see my answer to this question
Here is an example for how to wait on Angular if you're using WebDriverJS. Originally I thought you had to create a custom condition, but wait accepts any function.
// Wait for Angular to Finish
function angularReady(): any {
return $browser.executeScript("return (window.angular !== undefined) && (angular.element(document).injector() !== undefined) && (angular.element(document).injector().get('$http').pendingRequests.length === 0)")
.then(function(angularIsReady) {
return angularIsReady === true;
});
}
$browser.wait(angularReady, 5000).then(...);
Sadly this doesn't work with PhantomJS because of CSP (content-security-policy) and unsafe-eval. Can't wait for headless Chrome 59 on Windows.
I have implemented usage based on D Sayar's answer
And it might helpful for someone. You just have to copy all boolean functions mention over there in to single class, And then add below PageCallingUtility() method. This method is calling internal dependency.
In your normal usage you need to directly call PageCallingUtility() method.
public void PageCallingUtility()
{
if (DomHasLoaded() == true)
{
if (IsJqueryBeingUsed() == true)
{
JqueryHasLoaded();
}
if (AngularIsBeingUsed() == true)
{
AngularHasLoaded();
}
}
}
Beside eddiec's suggest. If you test an AngularJS app, I strongly suggest you to think about protractor
Protractor will help you solve the waiting matter (sync, async). However, there are some notes
1 - You need to develop your test in javascript
2 - There are some different mechanism in handling flow
The following code is needed in 2 different controllers (at the moment, maybe more controllers later). The code works around a problem I've found in ng-grid and allows the delayed selection of a row (once the data has been loaded).
// Watch for the ngGridEventData signal and select indexToSelect from the grid in question.
// eventCount parameter is a hack to hide a bug where we get ngGridEventData spam that will cause the grid to deselect the row we just selected
function selectOnGridReady(gridOptions, indexToSelect, eventCount) {
// Capture the grid id for the grid we want, and only react to that grid being updated.
var ngGridId = gridOptions.ngGrid.gridId;
var unWatchEvent = $scope.$on('ngGridEventData', function(evt, gridId) {
if(ngGridId === gridId) {
//gridEvents.push({evt: evt, gridId:gridId});
var grid = gridOptions.ngGrid;
gridOptions.selectItem(indexToSelect, true);
grid.$viewport.scrollTop(grid.rowMap[0] * grid.config.rowHeight);
if($scope[gridOptions.data] && $scope[gridOptions.data].length) {
eventCount -= 1;
if(eventCount <= 0) {
unWatchEvent(); // Our selection has been made, we no longer need to watch this grid
}
}
}
});
}
The problem I have is where do I put this common code? It's obviously UI code, so it doesn't seem like it belongs in a service, but there is no classical inheritance scheme (that I have been able to discover) that would allow me to put it in a "base class"
Ideally, this would be part of ng-grid, and wouldn't involve such a nasty hack, but ng-grid 2.0 is closed to features and ng-grid 3.0 is who knows how far out into the future.
A further wrinkle is the $scope that I guess I would have to inject into this code if I pull it from the current controller.
Does this really belong in a service?
I would probably just put this in a service and pass $scope into it but you do have other options. You may want to take a look at this presentation as it covers different ways of organizing your code: https://docs.google.com/presentation/d/1OgABsN24ZWN6Ugng-O8SjF7t0e3liQ9UN7hKdrCr0K8/present?pli=1&ueb=true#slide=id.p
Mixins
You could put it in its own object and mix it into any controllers using angular.extend();
var ngGridUtils = {
selectOnGridReady: function(gridOptions, indexToSelect, eventCount) {
...
}
};
var myCtrl = function() {...};
angular.extend(myCtrl, ngGridUtils);
Inheritance
If you use the 'controller as' syntax for your controllers then you can treat them like classes and just use javascript inheritance.
var BaseCtrl = function() {
...
}
BaseCtrl.prototype.selectOnGridReady = function(gridOptions, indexToSelect, eventCount) {
...
};
var MyCtrl = function() {
BaseCtrl.call(this);
};
MyCtrl.prototype = Object.create(BaseCtrl.prototype);
HTML:
<div ng-controller="MyCtrl as ctrl"></div>