Custom Angular directive to dynamically change font size? - javascript

I have some content on my site that doesn't format well because different browsers/screens render the font-size a little differently. To counteract this, I'm attempting to use Angular to get the height of some <p> tags, and if they're taller than my layout allows, lower their font size.
The <p> tags I'm trying to manipulate are contained in a directive which generates multiple content boxes based on some JSON.
I have created this directive:
spaModule.directive ("resizeParagraph", function() {
return function (scope, element, attrs) {
while (element.height() > 400) {
element.css("font-size", (parseInt(element.css("font-size")) -1 + "px"));
}
}
});
This is the directive which creates those boxes (this works):
<div ng-repeat="data in homeCtrl.homeData" class="content-box">
<img class="content-image" ng-src="images/home/{{ data.imageSrc }}"/>
<div class="sub-content">
<h1>
{{ data.heading }}
</h1>
<p resize-paragraph class="large-text">
{{ data.body }}
</p>
<a ng-href="#/{{ data.linkUrl }}" class="box-link">
{{ data.linkValue }}
</a>
</div>
</div>
I'm at home creating custom directives with a source URL, but this is my first go at creating a logical attribute-based directive.
What have I done wrong?

Try adding jQuery, before loading angular.js. In this post, it is written that Angular is using its own library jqLite to substitute for jQuery when the jQuery library is not included. jqLite does not include a height() function. To be able to use height(), you have to include the full jQuery library.
Just add <script type="text/javascript" src="http://code.jquery.com/jquery-1.11.3.js"></script> before the line where you include angular.js.
I tested it with the following code:
<style type="text/css">
.large-text {
font-size: 600px;
}
</style>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.11.3.js"></script>
<script src="angular.js"></script>
<script type="text/javascript">
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.homeCtrl = {};
$scope.homeCtrl.homeData = [
{
heading: 'Heading',
body: 'Body',
linkValue: 'LinkValue'
}
];
});
app.directive("resizeParagraph", function() {
return function (scope, element, attrs) {
while (element.height() > 100) {
element.css("font-size", (parseInt(element.css("font-size")) -1 + "px"));
}
}
});
</script>
<div ng-app="myApp" ng-controller="myCtrl">
<div ng-repeat="data in homeCtrl.homeData" class="content-box">
<img class="content-image" ng-src="images/home/{{ data.imageSrc }}"/>
<div class="sub-content">
<h1>
{{ data.heading }}
</h1>
<p resize-paragraph class="large-text">
{{ data.body }}
</p>
<a ng-href="#/{{ data.linkUrl }}" class="box-link">
{{ data.linkValue }}
</a>
</div>
</div>
</div>
EDIT
After some testing, I found the reason why it is not working as expected. The function inside the directive is called, before the expression {{data.body}} is executed in the template. It means that at the moment the directive is called, the text inside the paragraph is literally {{data.body}}. What you want is to postpone the execution of the directive after the expression has been executed. You can do it as follows:
app.directive("resizeParagraph", function() {
return function (scope, element, attrs) {
scope.$watch(name, function () {
while (element.height() > 50) {
element.css("font-size", (parseInt(element.css("font-size")) -1 + "px"));
}
})
}
});
I can also confirm that element.height() and element.context.offsetHeight return the same value. The height of the element in px. So, it doesn't matter which of the two you'll use.
I hope this helps.

Related

use jQuery.matchHeight from Angular directive

I am trying to set elements to the same height using jQuery.matchHeight. I call the function from an Angular directive
angular.module('myApp')
.directive('matchHeight', ['$timeout', function ($timeout) {
var linkFunction = function(scope, element) {
$timeout(function() {
angular.element(element).matchHeight();
});
};
return {
restrict: 'A',
link: linkFunction
};
}]);
The matchHeight plugin and jQuery are included in index.html
<html>
<head>
all head stuff
</head>
<body>
<div class="row usps">
<div class="col-sm-4 usp-block" ng-repeat="block in content.marketing" match-height>
<a href="{{block.link_url}}" class="thumbnail">
// Rest of html
</a>
</div>
</div>
<script src="bower_components/jquery/dist/jquery.js"></script>
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/matchHeight/dist/jquery.matchHeight.js"></script>
<script src="scripts/app.js"></script>
<script src="scripts/directives/matchheight.js"></script>
</body>
</html>
The problem is that the height is not set although the directive is being applied to the element.
The jQuery.matchHeight plugin will set all the items in an array to the height of the tallest element in that array.
the match-height directive is applied to a single element. Because there is no array, the height is not set on the element.
Moving the directive to the parent element in the DOM and adding the class equal to the child element(s) gives the array needed to set the height.
<div class="row usps" match-height>
<div class="col-sm-4 usp-block equal" ng-repeat="block in content.marketing">
<a href="{{block.link_url}}" class="thumbnail">
// Rest of html
</a>
</div>
</div>
In the service I apply the matchHeight function to all elements with the class equal
angular.module('myApp')
.directive('matchHeight', ['$timeout', function ($timeout) {
var linkFunction = function(scope, element) {
$timeout(function() {
angular.element(element).find('.equal').matchHeight();
});
};
return {
restrict: 'A',
link: linkFunction
};
}]);
Check this out, guys.
https://github.com/christopher-weir/AngularJs-MatchHeight
this custom directive is working fine. Also, codebase is pretty simple you can tweaks as your project need.

AngularJS, D3 and JQuery in the same page. D3 can't read the correct DOM dimensions

I am struggling with my page that can't load properly. I am using a simple header-body-footer structure in html5 and CSS3.
+----------+
| HEADER |
+---+----------+---+
| BODY |
+---+----------+---+
| FOOTER |
+----------+
What I am doing right now is create a svg with D3 inside the body space, reading width and height dynamically after window is loaded (and pictures also).
Now I want to add angular to avoid code redundancy inside each page of the site and I did this:
(function() {
var app = angular.module('neo4art', []);
var pages = {
"genesis.html": "The genesis of the project",
"about.html": "About Us",
"team.html": "The Team",
"index.html": "Traversing Art Through Its Connections"
}
app.directive("header", function() {
return {
restrict : 'E',
templateUrl : 'header.html'
};
});
app.directive("footer", function() {
return {
restrict : 'E',
templateUrl : 'footer.html'
};
});
app.controller('menuController', function($scope, $location, rememberService){
$scope.isActive = function(route){
return rememberService.getActualPage() === route;
};
});
app.controller('MainController', function(Page){
this.page = pages;
});
app.factory('rememberService', function($location) {
return {
getActualPage: function(){
var arr = $location.$$absUrl.split("/");
var pageName = arr[arr.length -1];
return pageName;
}
};
});
app.factory('Page', function(rememberService) {
return {
getTitle: function(){
return "neo4Art - "+ pages[rememberService.getActualPage()];
}
};
});
})();
To handle footer and header with Directives (< header>< /header>)
this is (part of) the code used to create the svg. I will only show you what I am interested in, the part that read the "on-field" measurements.
function Search(){
var width = $(".container-svg").width();
var height = $(".container-svg").height();
console.log("width:" + width + " - height:"+ height);
}
Before the use of angular I was using this in:
$(window).load(function(d) {
var search = new Search();
});
and all was going pretty well.
now using:
angular.element(window).load(function() {
var search = new Search();
});
I have a wrong height inside the var. It seems like the "new Search()" is called when the svg is still too high (the header is not yet rendered)
This is the html of the index (simplified)
<!DOCTYPE html>
<html lang="it-IT" ng-app="neo4art" ng-controller="MainController as mc">
<head>
<meta charset="utf-8">
<script src="resources/js/jquery/jquery-2.1.3.min.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
<link rel="stylesheet" type="text/css" media="screen" href="resources/css/style.css" />
<link rel="stylesheet" type="text/css" media="screen" href="resources/css/style-index-new.css" />
<link rel="stylesheet" type="text/css" href="resources/css/font-awesome-4.3.0/css/font-awesome.min.css" />
<title>{{"neo4Art - " + mc.page["about.html"]}}</title>
</head>
<body>
<script type="text/javascript" src="resources/js/angular.min.js"></script>
<script type="text/javascript" src="resources/js/search.js"></script>
<script type="text/javascript" src="resources/js/neo4art.js"></script>
<div class="container-page scrollbar-macosx" when-ready="isReady()">
<div class="content-home">
<header></header>
<div class="text-home">
<svg class="container-svg">
</svg>
</div>
<div class="footer">
© 2015 neo4<span class="palette-dark-blue">A</span><span class="palette-blue">r</span><span class="palette-orange">t</span> - All
rights reserved <a href="//www.iubenda.com/privacy-policy/430975" class="iubenda-white iubenda-embed"
title="Privacy Policy">Privacy Policy</a>
</div>
</div>
</div>
</body>
</html>
This is the code of the header:
<div class="header">
<div class="logo">
<img src="resources/img/neo4art-logo-big.png" />
<div class="motto">Traversing Art through its connections</div>
</div>
<form method="get" action="graph.html" name="query">
<div class="menu">
<div class="search-bar-container">
<span class="icon"><i class="fa fa-search"></i></span><input type="search" id="search" placeholder="Search..." name="query" />
</div>
<ul>
<li>HOME</li>
<li>ABOUT</li>
<li>GENESIS</li>
<li>TEAM</li>
</ul>
</div>
</form>
</div>
Without angularjs it is rendered correct, with angular I have a wrong value like the image is not loaded at all.
I hope my question is clear enough, the problem I think is in when each function is called during the load of the page.
EDIT: It's strange also that sometimes the page is loaded correctly (random occurency)
AngularJS version 1.3.16
So you are having this weird cross road in understanding of angular. First lets talk about angular.element is not the same as a jquery element. You shouldn't be doing DOM manipulation from an element you create like that you should have a directive with a link function to make sure this is happening in the digest cycle when you want it to. This is a hard concept to get but I think the best example I can find is from this other question.
Angular.js: set element height on page load
This does a reasonable job of showing and explaining what we are talking about.
A simple trick to see if it's related to initialization of the page would be to wrap you measuring code in a setTimeout call and delay it for 500ms. If this fixes the measurement you'd have to go a search for the actual correct place to put your measuring code :-)

ng-click does not work on my directive of a slider

I use the structure provided by the yeoman with angular-generator.
The ng-click does not work in my directive, of a slider show, when I put the html directly in main.html (It only works when I put in the directive an templateurl, linked to the main.html , but this causes delay to load).
Html, that is inserted directly into main.html
<div images="images" class="slider" id="mauseOnOut">
<div class="slide" ng-repeat="image in images" ng-show="image.visible">
<a ng-href="{{image.url}}"><img ng-src="{{image.src}}" width="444" height="250"/>
<p class="texto">{{image.texto}}</p>
</a>
</div>
<ul class="minimagem" ng-show="images.length">
<li ng-repeat="image in images"><a ng-click="returner($index)"><img ng-src="{{image.src}}" width="70" height="56"/></a></li>
</ul>
<div class="arrows">
<img src="http://s5.postimg.org/qkfwdwi7n/right_arrow.png"/>
</div>
</div>
Main part of the directive (in jsFiddle have it complete)
myApp.directive('images', function ($timeout) {
return {
restrict: 'AE',
scope:{
images: '='
},
link: function (scope) {
scope.currentIndex=0;
scope.returner = function(index){
scope.currentIndex = index;
};
scope.next=function(){
scope.currentIndex<scope.images.length-1?scope.currentIndex++:scope.currentIndex=0;
};
scope.prev=function(){
scope.currentIndex>0?scope.currentIndex--:scope.currentIndex=scope.images.length-1;
};
scope.$watch('currentIndex',function(){
scope.images. forEach(function(image){
image.visible=false;
});
scope.images[scope.currentIndex].visible=true;
});
},
};
});
Put an example in jsFiddle ; when use angular 1.1, on jsFiddle, operate normally, with 1.2 or higher does not work. In my application I use the angular 1.3.10 .
How could make it work in my application? It could be to ' compile ' or in some other way , the important thing is the click staying active in the image thumbnails and arrows .
Edited: I came back with the best known directive , best to understand.

angular, access to scope outside of the ng-view wrapping div

I am trying to set up custom themeing on my app, so what I am doing is letting the user choose certain themes and it will change the apps theme holistically. I have a service which sends a piece of json and listens for it changing inside the controller of each view. Now this works fine within the view itself - for reference here's some snippets of the working code.
my factory controlling the theme -
angular.module('demoApp')
.factory('templatingFactory', function () {
var meaningOfLife =
{
'h1': '#ea6060',
'bg': '#ffffff'
};
return {
setTheme: function(theme) {
meaningOfLife = theme;
},
getTheme: function() {
return meaningOfLife;
}
};
});
One of my example controllers showing and changing the theme (and listening for changes)
$scope.themeStore = templatingFactory.getTheme();
console.log($scope.themeStore);
//send new themes
$scope.themeOne = function () {
var newT1 = { 'h1': '#8A6516',
'bg': '#000000'};
templatingFactory.setTheme(newT1);
};
$scope.themeTwo = function () {
var newT2 = { 'h1': '#ffffff',
'bg': '#ea6060'};
templatingFactory.setTheme(newT2);
};
$scope.themeThree = function () {
var newT3 = { 'h1': '#ea6060',
'bg': '#ffffff'};
templatingFactory.setTheme(newT3);
};
//listen for new themes
$scope.watchThemes = templatingFactory.getTheme();
$scope.$watch(templatingFactory.getTheme, function (newTheme) {
$scope.themeStore = newTheme;
});
and then on the template/view itself i do something like this -
<h3 ng-style="{ 'color' : themeStore.h1 }">Title</h3>
So my issue is that this works fine inside the view. However the ng-view tag is inside the body and outside of it are the body containers, as well as the header and footer menus that I would like to be able to hook onto with this theme object. So my quesiton is, is there any way to use that scope outside of the ng-view? I don't think it's possible but I'm not sure how else I could access and put a ng-style on the header footer and body to change some css on it with this method I am using.
So for a simple reference it looks like this -
<body ng-app="myApp">
<div class="container">
<div class="header" ng-style="{ 'background-color' : themeStore.bg }">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<i class="fa fa-bars"></i>
</button>
<div class="headerLogo"></div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
</ul>
</div>
</div>
<div ng-view class="velocity-opposites-transition-slideUpIn" data-velocity-opts="{ duration: 500 }" ng-style="{ 'background-color' : themeStore.bg }"> </div>
<div class="footer">
<p></p>
</div>
</div>
</body>
So as you can see - I'm trying to hook onto the header to change the background color, which does not work like this. What I noticed though, is if I put it on the ng-view div itself, it works alright.
I would much appreciate any input as I've been stuck on this for a while. Thank you for taking the time to read!
The DOM elements outside of your ng-view must have controllers of their own, with templatingFactory injected as a dependency.
First I would modify the html like so:
<div class="header" ng-controller="headerController" ng-style="{ 'background-color' : themeStore.bg }">
Then add headerController to your module:
angular.module('demoApp').controller('headerController', function($scope, templatingFactory){
$scope.themeStore = templatingFactory.getTheme();
$scope.$watch(templatingFactory.getTheme, function (newTheme) {
$scope.themeStore = newTheme;
});
});
A more reusable solution would be to create a directive that adds this controller functionality to whatever DOM element it is applied to, but the above is a little more straight forward.
I think the best way to have angular functions and variables outside ui-view or ng-view is to use a global service. in this case you should do your theming logic inside 'templatingFactory'. Then inject this service not in your controllers, but in your module.
angular.module('demoApp').run(['$rootScope', 'templatingFactory', function($rootScope, templatingFactory){
$rootScope.templatingService = templatingFactory;
}]);
So your service will be avaible in the $rootScope. now you can use it this way.
<body ng-app="myApp">
<div class="container">
<div class="header" ng-style="{ 'background-color' : templatingService.getTheme().bg }"> </div>
</div>
</div>
ps: I'm relative new in angular too, so I don't know nothing about good/wrong practices!
For the directive approach, a simple example might look something like this:
demoApp.directive('themeHeader', function (templatingFactory) {
return {
restrict: 'A',
link : function (scope, element, attrs) {
scope.$watch(templatingFactory.getTheme, function () {
element.css('background-color', newTheme.bg);
});
}
}
});
and the html would look like this:
<div theme-header>
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"><i class="fa fa-bars"></i></button>
<div class="headerLogo"></div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right"></ul>
</div>

AngularJS ng-repeat applied multiple times in $compiled directive

I've written a directive that dynamically creates a popover for an element:
app.directive('popover', function($compile, $timeout){
return {
link: function(scope, element, attrs) {
$timeout(function() {
// grab template
var tpl = $(element).find('.popover-template')
// grab popover parts of template
var template = {
//$compile( $(element).siblings(".pop-content").contents() )(scope)
title: tpl.find('.template-title').contents(),
content: tpl.find('.template-content').contents()
};
// render template with angular
var content = $compile(template.content)(scope);
var title = $compile(template.title)(scope);
$(element).popover({
html: true,
placement: "right",
content: content,
title: title
});
scope.$digest()
});
}
};
});
In application it looks like this:
<span popover>Click me</span>
<div ng-hide="true" class="popover-template">
<div class="template-title">
<strong>{{ x.name }} and {{ y.name }}</strong>
</div>
<div class="template-content">
<div>
<pre>f in [1,2,3]</pre>
<div ng-repeat="f in [1,2,3]">
item {{ f }}, index {{ $index }}
</div>
</div>
</div>
</div>
The popover is created and displayed. The title works correctly as well. However, ng-repeat is applied multiple times in any iteration:
As you can see, the iteration that should only include 3 elements in fact includes 3*3 elements. The directive creates popovers for exactly 3 elements, so I guess that's where my mistake lies. How can I make sure that within each popover, ng-repeat is only called once?
The problem
Since the popover-template element is already in the document when you bootstrapped the angular application (at page load), it has already been compiled once. The ng-repeat element is replaced with 3 new elements:
<!-- original -->
<div ng-repeat="f in [1,2,3]">item {{ f }}, index {{ $index }}</div>
<!-- replaced -->
<div ng-repeat="f in [1,2,3]">item 1, index 0</div>
<div ng-repeat="f in [1,2,3]">item 2, index 1</div>
<div ng-repeat="f in [1,2,3]">item 3, index 2</div>
When you compile it again in the link function, each of the 3 ng-repeats is triggered, making 3 identical copies, 9 total.
The solution
Keep your popover-template in a separate file so it is not compiled on page load. You can then load it with the $templateCache service.
In general, just make sure you don't compile your HTML multiple times.
Instead using the compiled html for the popover template, load the template using $http or templateCache.
The HTML:
<span popover>Click me</span>
<script type="text/ng-template" id="popover.html">
<div class="popover-template">
<div class="template-title">
<strong>{{ x.name }} and {{ y.name }}</strong>
</div>
<div class="template-content">
<div>
<pre>f in [1,2,3] track by $index</pre>
<div ng-repeat="f in [1,2,3]">
item {{ f }}, index {{ $index }}
</div>
</div>
</div>
</div>
</script>
The Javascript:
angular.module('app',[]).directive('popover', function($compile, $timeout, $templateCache){
return {
link: function(scope, element, attrs) {
$timeout(function() {
// grab the template (this is the catch)
// you can pass the template name as a binding if you want to be loaded dynamically
var tpl = angular.element($templateCache.get('popover.html'));
// grab popover parts of template
var template = {
title: tpl.find('.template-title').contents(),
content: tpl.find('.template-content').contents()
};
// render template with angular
var content = $compile(template.content)(scope);
var title = $compile(template.title)(scope);
$(element).popover({
html: true,
placement: "right",
content: content,
title: title
});
scope.$digest()
});
}
};
});
Also, I have made this plunker with an working example: http://embed.plnkr.co/IoIG1Y1DT8RO4tQydXnX/

Categories