angularjs bootstrapui print iframe modal

1. Print modal fit in one screen

If the content of the modal is in one screen, we could just put a css in the modal template to hide all other content

/*general styles*/
.printable{display:none;}
/* print styles*/
@media print {
 .printable {display:block;}
 .screen {display:none;}
}

Or put a wrap to the parent content and assign an id to it then in the print css, use the id and set it as display: none

2. Print modal with long iframe

If we load a iframe with long content in the modal, the above print will just print the on-screen part content which is not desirable since we definite want to print the whole iframe.

2.1 add a function in the modal controller

	$scope.printIframe = function (iFrameId) {
		var frame = window.frames[iFrameId];
		if (!frame) {
			console.log('No iFrame found for id: ' + iFrameId);
			return;
		}
		frame.focus();
		frame.print();
	};

2.2 assign id to the iframe and call the print function from the template

      <iframe ng-src="{{request.url}}" frameborder="0" style="overflow-x:hidden; height: 100%; width: 100%;" id="modalIframe" name="modalIframe"></iframe>

....

<button class="btn btn-primary" ng-click="printIframe('modalIframe')">Print</button>

The other part is just regular setup

//popup for detail data
$scope.openDetail = function (mmid) {
//	$event.preventDefault();
	var detailUrl = '/someurl?rptid=' + $scope.rptId + '&firmname=' + $scope.reportData.firmName + '&mmid=' + mmid;
	$scope.detailRequest = {url:detailUrl};
	$modal.open({
		templateUrl: 'detailModalContent.html',
		controller: 'ModalInstanceCtrl',
		resolve: {
			request: function () {
				return $scope.detailRequest;
			}
		},
		// set width to 900px(bootstrap);
		size: 'lg'
	});
};

3. Print modal with long content(no iFrame)

The problem here is if the content is longer than the modal view port, then we can only see the viewable area in the print.
The only CSS that works for me when the modal is LONGER than the view port is:

@media print {
    .modal {
        position: absolute;
        left: 0;
        top: 0;
        margin: 0;
        padding: 0;
        overflow: visible!important;
    }
}

Note: position:absolute and overflow:visible are MUST have.

Advertisements

AngularJS Headless End to End Testing With Protractor and Selenium

FROM HERE

For those of you who are already somewhat familiar with Protractor and Selenium and want to skip right to the end, you might take a look at my Vagrant VM for headless browser testing in Ubuntu, and Chef cookbook for the same. These set up a standalone server that can be used to run end to end tests of AngularJS sites in Chrome, Firefox, or PhantomJS, and address some of the issues that you will run into along the way.

What is an End to End Test?

An end to end test runs against the front-end of a fully functional application. In the case of a web application, this implies driving a browser that loads pages, runs Javascript, interacts with the DOM, fills and submits forms, and so forth. The web application is served from a machine with a fully functional backend and suitably populated database. The setup is intended to mimic as closely as possible the live application, environment, and use cases.

You might compare this with unit testing, wherein a unit test runs against a small piece of functionality in isolation. Any data that might come from the rest of the application is mocked for the purposes of a unit test.

An AngularJS Test Philosophy

A fair way to approach design of an AngularJS application is to use a REST-like backend, push all of the business logic into services, and treat controllers as little more than glue holding together routes, directives, and the aforementioned services. When doing this, you will find that pretty much every piece of code worthy of a unit test ends up in a service and can be somewhat decoupled from $http requests. Thus you need only unit test the client-side business logic, and whether that involves the use of the mock $httpBackend or just ad-hoc construction of mock data is up to you. Personally I find that the latter is more easily set up and maintained.

The remaining test coverage of the codebase can be ensured with end to end tests. These by their nature will exercise directives, the glue controllers, and the functionality of the application when running against a known backend. A build and deployment system should incorporate the setup of a site database and server that is used for this purpose.

I find that a combination of more limited service-focused unit testing followed by a layer of end to end testing is more cost-effective than trying to push out to a high level of coverage for unit tests alone. Writing mock server responses for unit tests is exceedingly time-consuming, both to create the things and then to later maintain them: to my eyes it is better to use that time to set up a known database and backend and build end to end tests to run against it.

Karma and ngScenario: the Obsolete End to End Test Setup for AngularJS

The AngularJS documentation discusses the use of Karma and ngScenario to run end to end tests against a running web application server. The ngScenario framework provides a Selenium-like API for driving a browser and the odds are good that you are already using Karma for unit tests, so it seems like a simple evolution of existing work to start using it. That said, I have not been able to make ngScenario work to my satisfaction: local tests function but the same tests running against remote sites fail for deep reasons I have not put in the time to debug. Judging by what I’ve read I seem to be in a minority there, unfortunately, so it’s hard to say what it is that I am doing wrong.

Either way, this framework for end to end testing is now obsolete and is in the process of being replaced by Protractor. So don’t put any time into it.

Protractor, WebDriver, and Selenium

protractor-components

Selenium and WebDriver provide local and server APIs for driving a browser and manipulating and inspecting the DOM on loaded pages – and thus running tests against a site. Selenium, like most of the tools in this ecosystem, is presently evolving into a new configuration, but is reliable. A typical Selenium set up is:

  • A Selenium standalone server listens at a port for API commands.
  • Commands arrive indicating that Selenium should start a browser session.
  • Selenium loads a WebDriver implementation to control a particular browser.
  • The browser is started and pointed to a web site, usually on another server.

Typically test scripts run on server A and connect to Selenium on server B. Selenium fires up a browser on server B to connect to a web application running on server C. The test scripts on server A instruct Selenium on server B to drive the browser around the site served from server C, checking the state of the DOM and content in response to various actions.

It is perfectly possible, but painful, to write end to end tests for an AngularJS site using only Selenium tools. The challenge lies in determining when AngularJS is actually done with a given action, such as a change of view – this is somewhat more difficult than is the case for straightforward old-school AJAX operations. So Selenium test scripts for AngularJS tend to bloat with wait commands and checks.

Protractor is a Node.js framework that sits on top of the Selenium / WebDriver APIs. It acts as an abstraction layer to make it easier to write and run end to end tests against AngularJS web applications.

How it works

protractor-processes

Control Flow

One very important aspect in Protractor/webdriverjs is that it put your test code into the ‘controlFlow’ which is a promise based flow implicitly. 

With promises, the sequence code would be:

// pseudo code
driver.get(page1).then(function () {
	driver.click(E1);
});

Do you smell callback hell in there? To make it more neat, WebDriverJS has a wrapper for Promise called as ControlFlow.

In simple words, this is how ControlFlow prevents callback hell:

  • It maintains a list of schedule actions.
  • The exposed functions in WebDriverJS do not actually do their stuff, instead they just push the required action into the above mentioned list.
  • ControlFlow puts every new entry in the then callback of the last entry of the list, thus ensuring the sequence between them.

And so, it enables us to simply do:

// pseudo code
driver.get(page1);
// Implicitly add to previous action's then()
driver.click(E1);

Isn’t that awesome!

More about controlFlow and Frame

一个中文文章

get parent iframe element from inner page without knowing id

Parent html

<html>
	<body>
		<div>
			<iframe src="test.html" hash="r4d5f7"/>
			<iframe src="test.html" hash="8f7x97"/>
			<iframe src="test.html" hash="gg4v5e"/>
			<iframe src="test.html" hash="e54f87"/>
		</div>
	</body>
</html>

iframe html

<html>
<script>
function getHash()   {
           var ifs = window.top.document.getElementsByTagName("iframe");
           for(var i = 0, len = ifs.length; i < len; i++)  {
              var f = ifs[i];
              var fDoc = f.contentDocument || f.contentWindow.document;
              if(fDoc === document)   {
                 alert(f.getAttribute("hash"));
              }
           }
        }
</script>
	<body>
		<h1 id="goodid">good</h1>
	</body>
	<button onclick="getHash()">Click me</button>
</html>

This code works in FF and IE6, Opera, Chrome (requires a web server and both files coming from same domain protocol and port)

 

In my case, i need to use this in the gwt, so i have to replace the fDoc===document with fDoc===$wnd.document or $doc.

More detail in this POST.

cross domain script

permission denied or Blocked a frame with origin xxx from accessing a cross-origin frame is common when you have iframe and calling outside stuff from js.

Factor 1: set Domain

You’re developing an Ajax-based application. You have an application server at example.com which serves up all your JavaScript, HTML and CSS, and a data server at xml.example.com which delivers all the XML data to the application via a hidden IFRAME.

You know that cross-domain security will prevent any JavaScript from accessing the data in the IFRAME. so, you configure the data server to set the security domain of the IFRAME to “example.com” — the common suffix between the two domains — with a small piece of JavaScript:

<script type="text/javascript">
  document.domain="example.com";
</script>

Having done this, you test your application and get a “permission denied” error. What happened?

Depending on your browser, it may not be enough to only set the security domain of the IFRAME. You must set all of the frames and windows to the same domain, too. This is true even if the domain name you’re trying to set already matches the domain of the server that’s currently serving the page. For example, if you have two frames with pages served from example.com and you use JavaScript to set the security domain of one frame to “example.com” the frames will be unable to communicate.

Older browsers might let you get away with this.

Factor 2: X-Frame-Options

The X-Frame-Options HTTP response header can be used to indicate whether or not a browser should be allowed to render a page in a <frame> or<iframe>. Sites can use this to avoid clickjacking attacks, by ensuring that their content is not embedded into other sites.

There are three possible values for X-Frame-Options:

DENY
The page cannot be displayed in a frame, regardless of the site attempting to do so.
SAMEORIGIN
The page can only be displayed in a frame on the same origin as the page itself.
ALLOW-FROM uri
The page can only be displayed in a frame on the specified origin.

In other words, if you specify DENY, not only will attempts to load the page in a frame fail when loaded from other sites, attempts to do so will fail when loaded from the same site. On the other hand, if you specify SAMEORIGIN, you can still use the page in a frame as long as the site including it in a frame is the same as the one serving the page.

Configuring Apache

To configure Apache to send the X-Frame-Options header for all pages, add this to your site’s configuration:

Header always append X-Frame-Options SAMEORIGIN

Access-Control-Allow-Origin

One thing to notice is if Access-Control-Allow-Origin is set to *, then the same origin policy is not applied. 

An example is https://mathiasbynens.be/demo/ip, where you can send xhr to because the server has the Access-Control-Allow-Origin=*.

var getJSON = function(url, successHandler, errorHandler) {
  var xhr = typeof XMLHttpRequest != 'undefined'
    ? new XMLHttpRequest()
    : new ActiveXObject('Microsoft.XMLHTTP');
  xhr.open('get', url, true);
  xhr.onreadystatechange = function() {
    var status;
    var data;
    // https://xhr.spec.whatwg.org/#dom-xmlhttprequest-readystate
    if (xhr.readyState == 4) { // `DONE`
      status = xhr.status;
      if (status == 200) {
        data = JSON.parse(xhr.responseText);
        successHandler && successHandler(data);
      } else {
        errorHandler && errorHandler(status);
      }
    }
  };
  xhr.send();
};

getJSON('https://mathiasbynens.be/demo/ip', function(data) {
  alert('Your public IP address is: ' + data.ip);
}, function(status) {
  alert('Something went wrong.');
});

 

 

iframe and javascript

Introduction to Iframes

Iframes, or inline frames, allow you to load separate html files into an existing document. Iframes can be placed anywhere in the document flow. CSS and JavaScript can be used to manipulate properties of the iframe, such as its position and size. JavaScript can also be used to pass data back and forth between the document containing the iframe and the document loaded into it.

The following demonstrates an iframe with basic attribute settings:


The iframe’s src attribute specifies the URL of the document to be displayed in the iframe.

The document loaded into the iframe does not inherit styles such as font, colors or background from the containing document. Styles to be applied to iframe content need to be included in that document in the usual ways, i.e., linked, embedded, etc.

Communication to and from Iframes

The document containing the iframe can obtain references to properties and elements in the iframed document through contentDocument or contentWindow properties. The contentDocument property has broad support among current browsers, including Internet Explorer 8+, Firefox, Chrome, Safari and Opera. The contentWindow property is supported by Internet Explorer 5.5+, Firefox, Chrome, Safari and Opera.

we can have something like this

	this.getIFrameDocument = function(p_iframe)
	{
		var iframe_doc = null;
		if (p_iframe.contentDocument) {
			// Firefox
			iframe_doc = p_iframe.contentDocument;
		}
		else if (p_iframe.contentWindow.document) {
			// IE post 5.5
			iframe_doc = p_iframe.contentWindow.document;
		}
		else if (p_iframe.document) {
			// IE pre 5.5
			iframe_doc = p_iframe.document
		}
		return iframe_doc;
	}

References from Iframed Document

frameElement: A frameElement property provides access to the iframe element in which the iframed document resides. This property is supported by current browsers, including Internet Explorer 5.5+, Firefox, Chrome, Safari and Opera. The iframed document can get a reference to the id of its containing iframe element as follows:

window.frameElement.id;

The iframed document can also gain access to parentNode, previousSibling, offsetHeight, and many other properties through its frameElement; for example:

window.frameElement.offsetHeight;

parent: The document located inside the iframe can refer to any object available in the window or document containing the iframe via the parent keyword. For example, the id of a form in the containing document can be obtained using:

parent.document.getElementById('testForm');

Same Origin Policy

The same origin policy is a security feature of JavaScript that prevents access to properties and methods of documents from different domains. In other words, if the containing document and the iframed document are not from the same domain, attempts to reference each other’s objects will result in access denied or similar error messages.

The document.domain property can be set to ease the restriction somewhat. For example, if a document at http://www.example.com wants to communicate with a document at forums.example.com, the document.domain property could be set to example.com in both documents to allow JavaScript interaction between them.

example:

try {
 document.domain = 'finra.org';
 }
 catch(e) {
 }

HTML5 introduces cross-document messaging using postMessage which is designed to enable documents from separate domains to communicate with each other while still providing protection from cross-site scripting attacks.

The postMessage method is supported by Internet Explorer 8+, Firefox 3+, Opera 10, Safari and Chrome. There is a jQuery PostMessage Plugin as well as other code that provides fallback mechansims for older browsers. See an article on Cross-Domain Communication with IFrames by Michael Mahemoff for more information.