It is often important to simultaneously show a fairly large number of elements on a web page to provide users with the
most concise information possible. In order to make all those elements visible on a screen, their functionality usually
has to be considerably limited. That is when the ability to open every separate element in a fullscreen view can be very
helpful. This post shows how this can be achieved in AngularJS, with two custom directives which allow to move elements
to a fullscreen view without changing their scope.
Here is an example of the scenario described above.
Picture 1. Charts
As it can be seen on the Picture 1, there are three charts on the page, rendered side by side. Let's assume that the
page is designed to provide users with some overall statistical information. So, it is necessary to display all the
charts on the same page at once. But, at the same time, we would like to allow users to magnify a specific chart and
turn off / on a chart line (here, charts were taken just as an example, they could be replaced with any other elements,
such as grids, or forms).
Ideally, there should be an ability to display each chart in a fullscreen mode without affecting the other charts or
reloading the page. Another significant thing, it would be nice if the chart had the same scope regardless of its state
(whether it is displayed in a fullscreen view or as a part of a page).
Writing the directives
Let's get to writing the directives. The first directive, called fullscreenBlock, compiles as a button and serves
as a trigger for executing the second modalWrapper directive which does the actual work of moving the element to
a fullscreen view:
directives.directive('fullscreenBlock',['$compile',function($compile){varjqLite=angular.element;varlink=function(scope,element,attrs){// Compiles the default 'fullscreen' button for opening the fullscreen view.// This fullscreen button can be overridden by passing a custom html string to// the `fullscreen-block` attribute.varfullscreenButton=function(){returnjqLite($compile('<div class="row">'+'<span class="pull-right">'+// scope information'<p>(scope id: <b>'+scope.$id+'</b>)</p>'+'<p>(parent scope id: <b>'+scope.$parent.$id+'</b>)</p>'+'<span name="fullscreen" ng-click="fullscreenMode($event)"'+'tooltip="Expand" style="cursor: pointer">'+'<i class="glyphicon glyphicon-fullscreen"></i>'+'</span>'+'</span>'+'</div>')(scope));};// if there is no custom html passed to the attribute, default one is usedelement.prepend((attrs.fullscreenBlock.length>0)?attrs.fullscreenBlock:fullscreenButton());};// This function helps to find the first parent element which contains the// 'fullscreen-block' attribute. The function can be replaced with JQuery// `closest()` method if JQuery library is used.varclosest=function(element){return(jqLite(element).parent().attr('fullscreen-block')!==undefined)?jqLite(element).parent():closest(jqLite(element).parent());};varcontroller=function($scope){// handles the ng-click event$scope.fullscreenMode=function(event){varmodalWrapper='<modal-wrapper></modal-wrapper>';jqLite(closest(event.target)).wrap(modalWrapper);$compile(modalWrapper)($scope);};};return{link:link,controller:['$scope',controller]}}]);
directives.directive('modalWrapper',["$compile","$document",function($compile,$document){varjqLite=angular.element;/* * The modal window template */vartemplate='<div id="modal-fullscreen" class="modal modal-fullscreen"'+'style="background: white">'+'<div class="modal-header">'+'<button type="button" class="close" ng-click="close()">×</button>'+'</div>'+'<div class="modal-body row" style="margin: 0; display: flex;"></div>'+'</div>';varchart;/* * The link function. */varlink=function(scope,element){scope.open();// watches if modal is openedscope.$watch('showModal',function(newVal){varmodalEl=jqLite('modal-wrapper');varfullscreenIcon=modalEl.find('[name="fullscreen"]');// if modal is openedif(newVal){// hides the fullscreen icon for the modalfullscreenIcon.addClass("ng-hide");// inserts content from the page to the body of modal element.// JQuery's `insertBefore()` method can be used as well.modalEl[0].parentNode.insertBefore(element[0],modalEl[0]);// shows content a fullscreen viewvarcontent=modalEl.children();// puts the element inside the modalelement.find(".modal-body").append(content);// removes the <modal-wrapper> tagmodalEl.remove();// makes the modal visiblejqLite('#modal-fullscreen').show();// changes the element width// (`expand` is a custom class that sets width to 100%)content.addClass('expand');// initializes the chart variable// (this line is specific to this concrete case)chart=content.scope()[element.find('canvas').attr('id')];// if modal is closed}elseif(!newVal){// shows the 'fullscreen' button after the modal is closedfullscreenIcon.removeClass("ng-hide");// looks up the elementvarmodalBody=modalEl.find(".modal-body").children();// inserts content from modal back to the page.// JQuery's `insertBefore()` method can be used as well.modalEl[0].parentNode.insertBefore(modalBody[0],modalEl[0]);// removes the modalmodalEl.remove();// changes the element width// (`expand` is a custom class that sets width to 100%)content.removeClass('expand');}// triggers the chart resizing// (this line is specific to this concrete case)chart.resize(chart.render,true);})};/* * The controller function. */varcontroller=function($scope){$scope.open=function(){$scope.showModal=true;};$scope.close=function(){$scope.showModal=false;};// closes the modal when the back button is clicked$scope.$on('$locationChangeStart',function(event){if($scope.showModal){event.preventDefault();$scope.close();}});};return{restrict:'E',scope:{},template:template,link:link,controller:controller}}]);
Here is a description of the directives shown above:
fullscreenBlock - compiles and inserts the 'fullscreen' button -- clicking this button wraps the enclosed
element in the <modal-wrapper> directive. The directive is represented as an attribute which can receive an HTML
string in order to render a custom button. This button should have the ng-click="fullscreenMode($event)" attribute
defined, for instance:
modalWrapper - responsible for moving a selected element to the modal template, as a result the chart is displayed
in a fullscreen without affecting the other elements in the DOM. The important part is that the chart is just moved
from one place and to another, which allows you not to create a new scope, but to work with the initial one. Closing the
modal window puts the chart back, leaving its state untouched.
Using directive in HTML
Next, let's see how to use the fullscreen-block directive in html.
12345678910111213141516
<divclass="row"ng-controller="FullscreenModeController"><divfullscreen-blockng-repeat="chart in data"class="col-md-4"><divclass="row"><divclass="col-md-10"><canvasid="{{chart.name}}"></canvas></div><divng-if="showLegend()"class="col-md-2"><divng-repeat="dataset in chart.datasets"class="row"><label>{{dataset.label}}</label><inputtype="checkbox"ng-model="dataset.show"ng-change="changeDataset(chart, dataset)"/></div></div></div></div></div>
The key point here is putting the <fullscreen-block> directive on the top of the element that is supposed to be scaled
(the directive is a marker that encloses the element which should be moved and displayed in a fullscreen view).
Result
Finally, let's execute the code:
Picture 2. A few charts that have the fullscreen-block attribute defined
Picture 3. The fullscreen view that shows one of the charts
As you can see on the Picture 2, the fullscreen directive inserts the button
(the top-right corner of the chart) which allows to magnify the chart element by displaying it in the fullscreen mode
(Picture 3).
Note that Twitter Bootstrap's modal-fullscreen class was used for the fullscreen view.
You can play around with the jsFiddle example,
try to open one of the charts, turn off a chart line, and then close the preview window. You will see that all the
changes that have been made in the fullscreen mode are applied because the chart scope is always the same.