Creating a new html page from controller - angularjs

I have an input form page, that when the user submits, it updates that page with some tables of data. If the user likes what they see, they should be able to print a cleaned up (no header nav, footer nav, etc) version of those tables with formatting intact.
I'm creating the tables using bootstrap and angularjs. What I'm trying to do is that when the uset clicks 'print', my angular controller grabs the desired content from the generated page (in a div called 'template') and inserts it into a new html page that I can then open in a new window for printing.
I created a directive that I was hoping would allow me to use $compile to transclude my data:
app.directive('printTemplate', function () {
return {
templateUrl: 'app/partials/printtemplate.html',
replace: true,
transclude: true,
restrict: 'A'
};
});
And in my controller:
$scope.printTemplate = function () {
var page = angular.element(template).contents();
var newpage = $compile("<html><body><div print-template>" + page + "</div></body></html>")($scope);
var win = window.open();
win.document.write(newpage);
//win.print();
};
But this doesn't seem to do what I expect. And it feels like I'm doing it wrong. The idea was that because my data tables require bootstrap script and css references I need to include those in my printtemplate.html file.
Is there an easier/better way to do this, or what am I missing?

Instead of trying to create a whole new page, why not simply use specific css for print media.
As always the w3 is the best place to start http://www.w3.org/TR/CSS2/media.html

Related

Same element id's on different pages of AngularJS app

I'm very new to AngularJS, and am working to convert an existing web application written using JQuery and Bootstrap to one using AngularJS and Ionic.
I have 3 pages in my web application, each with a html5 canvas and a button. The canvas layout and styling are exactly the same on each page, however the button draws a different thing to each canvas, e.g. on page 1 draw a dog, page 2 a cat, and page 3 a chicken.
Because of this, I use the same element ID's between the three pages, e.g. '#myCanvas' for the canvas. I have a Javascript object called 'Drawer', which draws the relevant thing to '#myCanvas' when the relevant button is pressed.
When converting to AngularJS, I placed each page into a template, and converted the 'Drawer' object into a factory called DrawFactory. Each page is linked to a different controller that uses DrawFactory.
My problem is that, say for example I'm on page 2 and click the draw cat button, it draws it to the #myCanvas on page 1. Previously this wasn't a problem as each page loaded separately and therefore all ids were unique.
How can I achieve what I want without renaming each of the canvases to (for example) #myCanvas1, #myCanvas2, #myCanvas3- and creating 3 separate DrawFactories that individually draw to one of them?
I'm sure I'm just missing some key AngularJS concept. Thanks.
Without having your code posted, I suspect you may still be using jQuery selectors to find elements (e.g. $('#myCanvas')). You may wish to restructure your code to behave like this at a high level.
<div ng-controller="PageOneCtrl">
<drawing source="animal"></drawing>
<button ng-click="drawAnimal()">Draw</button>
</div>
The controller would be defined similar to the following:
angular.module('app').controller('PageOneCtrl', function ($scope) {
$scope.animal = null;
$scope.drawAnimal = function () {
$scope.animal = 'dog.png';
};
});
And the drawing directive like so:
angular.module('app').directive('drawing', function () {
return {
scope: {
source: '='
},
template: '<canvas width="100" height="100"></canvas>',
link: function (scope, element) {
scope.$watch('source', function (newSource) {
if (!newSource) return;
var context = element.getContext('2d'),
image = new Image(100, 100);
image.src = newSource;
image.onload = function () {
context.drawImage(image, 0, 0);
};
});
}
};
});

AngularJS loading message/please wait

How can I achieve the following in AngularJS:
If I have my page that will show some widgets and each widget has a button called refresh. When the button is clicked then the content of that widget is reloaded from server. While the content is reloaded I want to show the user a message within that widget, please wait ... with a possible fading effect.
How can I achieve that in AngularJS?
I kind of taught about having a common service for that purpose and then somehow each widget controller will use that service or something like that, maybe also a directive that will show the actual loading/please wait message?
What is your advise?
P.S. There should be also a loading/please wait message with fading for the whole page, when the route is changing ... like switching between pages.
In my recent project I'm using https://github.com/cgross/angular-busy
Very nice thing, all you have to do is put your promise into $scope, and then add cg-busy attr to your element which should have spinner (beside registering module obviously):
Controller:
$scope.myPromise = restangular.get('something',12).then(function(response) { ... })
Html:
<div cg-busy="myPromise"></div>
You can also customize template that's gonna be displayed (which includes spinner and text message).
there are implementations of this available on github : angular-spinner or angular-sham-spinner. Read this BLOG which details how the spinner works with angularjs
if you want to implement it yourself to be reusable...
app.directive("spinner", function(){
return: {
restrict: 'E',
scope: { enable: "=" },
template: '<div class="spinner" ng-show="enable"><img src="content/spinner.gif"></div>'
}
});
i havent tested the code but directive wont be more complex than above...
As harish point out the right way would be a directive, but theres no need if you want of include another dependency, you could do something like this
You can create a nice CSS3 only loading (so not images required) animation with the help of CssLoad
Create a directive with a linking function so you can call and stop the animations within your controller the angular way:
.directive('appLoading', function(){
return {
restrict: 'E',
templateUrl: 'template-file.html', // or template: 'template html code inline' Display none to the code is important so is not visible if youre not caling the methods
replace: true,
link: function(scope, elem) {
scope.$on('app-start-loading', function(){
elem.fadeIn(); //asumming you have jquery otherwise play with toggleClass and visible and invisible classes
});
scope.$on('app-finish-loading', function(){
elem.fadeOut();
});
}
}
})
Include in your html code the directive: <app-loading></app-loading>
Now all you need to do is call the scope methods in your controller like this:
$scope.$broadcast('app-start-loading'); // to start the loading animation
$scope.$broadcast('app-finish-loading'); // to stop the animation
NOTE: if all your widgets share a scope, the loading may be triggered in all of them

Understanding the 'directive' paradigm in Angularjs

I have been thinking about directives in Angularjs like user controls in ASP.Net, and perhaps I have it wrong.
A user control lets you encapsulate a bunch of functionality into a widget that can be dropped into any page anywhere. The parent page doesn't have to provide anything to the widget. I am having trouble getting directives to do anything close to that. Suppose that I have an app where, once the user has logged in I hang onto the first/last name of the user in a global variable somewhere. Now, I want to create a directive called 'loggedinuser' and drop it into any page I want. It will render a simple div with the name of the logged in user pulled from that global variable. How do I do that without having to have the controller pass that information into the directive? I want the usage of the directive in my view to look as simple as
<loggedinuser/>
Is this possible?
I guess you can roughly sum up what a directive is as "something that encapsulates a bunch of functionality into a widget that can be dropped into any page anywhere", but there's more to it than that. A directive is a way to extend HTML by creating new tags, allowing you to write more expressive markup. For instance, instead of writing a <div> and a bunch of <li> tags in order to create a rating control, you could wrap it up with a new <rating> tag. Or, instead of lots of <div>s, and <span>s and whatnot to create a tabbed interface, you could implement a pair of directives, say, <tab> and <tab-page>, and use them like this:
<tab>
<tab-page title="Tab 1"> tab content goes here </tab-page>
<tab-page title="Tab 2"> tab content goes here </tab-page>
</tab>
That's the truly power of directives, to enhance HTML. And that doesn't mean that you should only create "generic" directives; you can and should make components specific to your application. So, back to your question, you could implement a <loggedinuser> tag to display the name of the logged user without requiring a controller to provide it with the information. But you definitely shouldn't rely on a global variable for that. The Angular way to do it would be make use of a service to store that information, and inject it into the directive:
app.controller('MainCtrl', function($scope, userInfo) {
$scope.logIn = function() {
userInfo.logIn('Walter White');
};
$scope.logOut = function() {
userInfo.logOut();
};
});
app.service('userInfo', function() {
this.username = ''
this.logIn = function(username) {
this.username = username;
};
this.logOut = function() {
this.username = '';
};
});
app.directive('loggedinUser', function(userInfo) {
return {
restrict: 'E',
scope: true,
template: '<h1>{{ userInfo.username }}</h1>',
controller: function($scope) {
$scope.userInfo = userInfo;
}
};
});
Plunker here.
The Angular dev guide on directives is a must-go place if you want to start creating powerful, reusable directives.

AngularJS Directive-Controller-Service Interaction

I'm trying to design a single page pagination app that displays the different pages of a document beneath each other in the same window. It has to meet the following requirements:
A pagination toolbar where the user can click next/previous/... and submit a page to go to.
The window scrolls to the right page of the document after a page has been submitted
If the user scrolls manually, the current page should update automatically
I tried some different stuff but was never really satisfied with the result. This is how I see the solution:
The app consists of 1 factory:
DocumentFactory: Stores the current page of the document and has the following methods:
setPage(page): sets the page in a factory so different controllers/directives can use this page
broadcast(pageChanged): broadcasts an event after the page has changed so the controllers/directives can listen to this even and react approprialty
2 controllers:
PaginationCtrl[DocumentFactory]: The pagination toolbar controller, updates the page by calling the setPage(method) of the DocumentFactory and listens to the pageChange event to update it's own scope when the page changes in an other controller/directive
DocumentCtrl: The controller of the document
1 Directive:
Page[DocumentFactory]: Resembles a page in the document and has the following methods/listeners
scrollToPage(): If the currentPage equals this pages number (added to the directive as an attribute, scroll to this page)
If this page is visible and the highest in the window of all visible pages, change the current page to this page's number by calling the DocumentFactory setPage(page) method.
Is this the right approach to store the page in a service and use events for the other controllers/directives to listen to it?
Should I create a controller in the directive to listen to the event or add a $watch in the link function to watch for changes in the current page (inherited from the parent Ctrl scope)?
Should I let each directive check if it's page number equals the current page on page change or should I let the DocumentCtrl scroll to the right element?
AS you already have the methods in the controller calling the factory, what you need is to use the '&' isolate scope in the directive to call the method you want.
Create the methods in the controller
var app = angular.module('app', []);
app.controller("Ctrl", function ($scope) {
$scope.goForward = function (){
alert("call to the paginator page up service");
};
$scope.goBack = function (){
alert("call to the paginator page down service");
};
});
Then set up the '&' scope isolates in the directive:
app.directive('paginator', function (){
return{
restrict: 'E',
scope:{
forward: '&',
back: '&'
},
template: '<button ng-click="forward()">Page up</button>' +
'<button ng-click="back()">Page down</button>'
}
});
Finally add the directive to the page with your attributes as defined in the directive:
<paginator forward="goForward()" back="goBack()"></paginator>
Here's the code in Plnkr.
HTH

The angular.js best practices way to query the current state of the DOM

I've started working with angular js and have a problem that requires getting the current state of the DOM inside of my controller. Basically I'm building a text editor inside of an contenteditable div. Revisions to the text in the div can come from an external service(long polling pushes from the server) as well as the user actually typing in the field. Right now the revisions from the server are manipulating my angular model, which then updates the view through an ng-bind-html-unsafe directive. The only problem with this is that this blows away the users current cursor position and text selection.
I've figured out a way around the problem, but it requires directly manipulating dom elements in my controller, which seems to be discouraged in angular. I'm looking for either validation of my current method, or reccomendations on something more "angulary".
Basically what I've done is added two events to my model, "contentChanging" and "contentChanged". The first is fired right before I update the model, the second right after. In my controller I subscribe to these events like this.
//dmp is google's diff_match_patch library
//rangy is a selection management library http://code.google.com/p/rangy/wiki/SelectionSaveRestoreModule
var selectionPatch;
var selection;
scope.model.on("contentChanging", function() {
var currentText = $("#doc").html();
selection = rangy.saveSelection();
var textWithSelection = $("#doc").html();
selectionPatch = dmp.patch_make(currentText, textWithSelection);
});
scope.model.on("contentChanged", function() {
scope.$apply();
var textAfterEdit = $("#doc").html();
$("#doc").html(dmp.patch_apply(selectionPatch, textAfterEdit)[0]);
rangy.restoreSelection(selection);
});
So basically, when the content is changing I grab the current html of the editable area. Then I use the rangy plugin which injects hidden dom elements into the document to mark the users current position and selection. I take the html without the hidden markers and the html with the markers and I make a patch using google's diff_match_patch library(dmp).
Once the content is changed, I invoke scope.$apply() to update the view. Then I get the new text from the view and apply the patch from earlier, which will add the hidden markers back to the html. Finally I use range to restore the selection.
The part I don't like is how I use jquery to get the current html from the view to build and apply my patches. It's going to make unit testing a little tricky and it just doesn't feel right. But given how the rangy library works, I can't think of another way to do it.
Here's a simple example of how you would start:
<!doctype html>
<html ng-app="myApp">
<head>
<script src="http://code.angularjs.org/1.1.2/angular.min.js"></script>
<script type="text/javascript">
function Ctrl($scope) {
$scope.myText = "Here's some text";
}
angular.module("myApp", []).directive('texteditor', function() {
return {
restrict: 'E',
replace: true,
template: '<textarea></textarea>',
scope: {
text: '=' // link the directives scopes `text` property
// to the expression inside the text attribute
},
link: function($scope, elem, attrs) {
elem.val($scope.text);
elem.bind('input', function() {
// When the user inputs text, Angular won't know about
// it since we're not using ng-model so we need to call
// $scope.$apply() to tell Angular run a digest cycle
$scope.$apply(function() {
$scope.text = elem.val();
});
});
}
};
});
</script>
</head>
<body>
<div ng-controller="Ctrl">
<texteditor text="myText"></texteditor>
<p>myText = {{myText}}</p>
</div>
</body>
</html>
It's just binding to a textarea, so you would replace that with your real text editor. The key is to listen to changes on the text in your text editor, and update the value on your scope so that the outside world know that the user changed the text inside the text editor.

Resources