Dynamically transform in css with a parameter in angular directive - javascript

I am building a progress indicator with a circle. Where the circle is built with angular directive, and the progress level is passed as an argument to the directive where values are extracted with angular repeat.
the problem is that all the progress levels are getting the same value despite passing different values.
To set the update the progress level, I first tried to use jquery .css function as follows, which gave the same value for all the progress levels
$(this).find('.ppc-progress-fill').css('transform','rotate('+ deg +'deg)');
Then I tried to embed the progress with ng-style into the template which also did not work, given the same progress level to all.
<div class="ppc-progress-fill" ng-style="{'transform': 'rotate('+deg+'deg)', '-webkit-transform': 'rotate('+deg+'deg)', '-ms-transform': 'rotate('+deg+'deg)'}"></div>
Here is the relevant portion of the code
AppModule.directive("skillWidget", function(){
var linkFunction = function(scope, element, attributes){
//console.log(attributes);
scope.text = attributes["text"];
scope.percent = attributes["percent"];
percent = parseInt(scope.percent),
deg = 360*percent/100;
//console.log($(this).find('.ppc-progress-fill'));
$(this).find('.ppc-progress-fill').css('transform','rotate('+ deg +'deg)');
//$('.ppc-progress-fill').css('transform','rotate('+ deg +'deg)');
};
return {
restrict: "E",
templateUrl:"views/skillTemplate.html",
link: linkFunction,
scope: {
text: "#text",
percent : "#percent"
}
}
});
Here is the template:
<div class="progress-pie-chart"><!--Pie Chart -->
<div class="ppc-progress">
<div class="ppc-progress-fill" ng-style="{'transform': 'rotate('+deg+'deg)', '-webkit-transform': 'rotate('+deg+'deg)', '-ms-transform': 'rotate('+deg+'deg)'}"></div>
</div>
<div class="ppc-percents">
<div class="pcc-percents-wrapper">
<span>{{text}}</span>
</div>
</div>
What am I missing?
Further infos:
The circular progress bar is based on this example http://codepen.io/shankarcabus/pen/GzAfb.

In my opinion all you have to do is:
Create a function scope.deg to provide the deg value to your view
Apply normal style attribute to the ppc-progress-fill in the view but using the deg() function we created below to perform the transformations.
Remove all jquery code
I created this plunkr for you http://embed.plnkr.co/tzPVwVue8Jhi1iIvUMJ7/preview.
It does not have any styles but I'm using an image as background for the ppc-progress-fill so it's easier to note when the div rotates. Note that if you change the percent in it the AngularJs logo will rotate ;).
I usually try to keep the presentation layer (HTML and CSS) separated from the code, in this case the directive code. And not to use use jQuery if I'm working with AngularJs, most of the things you normally tend to do with jQuery can be accomplished without it. That helped me to make the switch to AngularJs faster.
Happy coding!
ps: It would be great if you can provide a working snippet/jsfiddle/plunkr/etc.. the next time so it's easier for us to help you finding the solution. ;)

My guess would be that the this in $(this).find(...) is higher level than you think. Therefore the find() return all your .ppc-progress-fill. So the .css (...) is applied to all of them which is why all directives display the same level even if you input different values.
To be sure you target only the element associated to the directive, use the element attribute in your link function. From angular doc:
element is the jqLite-wrapped element that this directive matches.
element.find('.ppc-progress-fill').css('transform','rotate('+ deg +'deg)');

Related

Change after property in ng-style without using ng-class

So I'm trying to create a directive for a some kind of stepper arrow progress bar.
Please see this codepen to understand what I'm talking about. (Note that i wrote the params in the function scope to have an easier read).
The problem I am facing now, is that I wanna pass the colors as param to my directive and I need to apply the colors to both the class and the :after property.
I use that sample to change the background-color of the block-head class, using ng-style. The ng-class I use is to get plain start and end.
<div ng-repeat="element in elements"
class="block-head"
ng-class="{'block-head-first': $first, 'block-head-last': $last}"
ng-style="{'width' : selectedKey === element.key ? 'calc(100% - ' +
widthCalc + 'px)' : '',
'background-color': getColor($index)}">
$scope.widthCalc = ($scope.elements.length - 1) * 26
$scope.getColor = function($index) {
if ($scope.elements[$index].key === $scope.selectedKey)
return $scope.colors['current']
if ($scope.elements[$index].key < $scope.selectedKey)
return $scope.colors['success']
if ($scope.elements[$index].key > $scope.selectedKey)
return $scope.colors['disable']
}
Below is the sample of code I wanna change with ng-style (I set it to lightblue so you understand visually what i'm trying to change).
.block-head:after {
color: lightblue;
I thought of two potential fixes but I have no idea on how to do them. First would be to 'heritate' the color value from the parent in background-color in pure css (not even sure that it is possible ?). Second would be to create classes in my js code and use those classes using ng-class.
All the related questions (here, and here) always advice to use ng-class, but I see no obvious fix with it on my case.

Changing text based on width of nested directive Angular

So I have an ng-repeat that loops over data objects and displays it in a list directive. One of the objects in the list should use the abbreviated version when the length of the text element is equal to or exceeds the length of the container. So what I have is this:
//this is the list directive
<shift-list selection="selection" shifts="shifts"></shift-list>
//this is the template of the list directive that adds site-base-name directive
<ion-item collection-repeat="shift in shifts">
<div class="item-text">
<site-base-name shift="shift"></site-base-name>
</div>
//and here is what my site-base-name directive looks like
return {
restrict: 'E',
link: function(scope, element) {
$timeout(function(){
scope.siteBaseWidth = element[0].offsetWidth;
scope.parentWidth = element.parent()[0].offsetWidth;
});
},
replace: true,
scope: {
shift: '='
},
template: ''+
'<div class="sitebase">{{(siteBaseWidth + 20 >= parentWidth) && shift.sites_abbreviation || shift.sites_name }} : {{shift.bases_name}}</div>'
}
}
So what I'm doing is nesting the directive site-base-name inside the list directive and then finding the size of the element and its parent. Then I'm using the abbreviated version of the name (shift.sites_abbreviation) if the size exceeds my condition (siteBaseWidth + 20 >= parentWidth). The problem with this is the behavior I'm getting is buggy and inconsistent. It also applies the changes after the DOM is loaded, so you can see the text change within the window.
My question is what is the best way to find the width of the text element and parent element then apply a condition that decides what data to populate the binding with? Preferably a clean Angular solution.
Relying on the element offsetWidth is not very clean, as if forces you to wait for the DOM to be updated (hence your $timeout and the flickering observed).
A cheap way to solve your issue would be to initially display your text with an opacity of 0.01 (so it is not visible, but still takes some space), and with your site-base-name directive, once you know its size and adjust the text, you can set its opacity back to 1.
Now a better solution would be to get rid of this $timeout and offsetWidth. If you use a monospace font, you could just calculate thestring.length times pixelsPerCharacter, before even it is displayed.

NG Style Failing to automatically bind to page

I'm trying to make a page with two sections, which can be slid back and forth horizontally to take up different relative widths on the page. The idea was to track the percentile width of the (to-be) draggable bar separating the two panes/sections, and use ng_style to automatically update the widths of the two "panes" in relation to where that bar is dragged.
The following is in a Rails app, with integrated Angular. The Angular's loading just fine -- no errors, and the rest is all working -- and the ng_style is being loaded from the Angular controller when the page first loads up -- but it's not changing when I attempt to drag the spacer in between the two "panes", as it's supposed to.
Here's a simplified version of my HAML (sorta like Jade. Just indented HTML):
#full-page.fluid{ ng_controller: "ExerciseCtrl", ng_mousemove: 'updateSidebarWidth($event)', ng_mouseup: 'untrackMouseMove()', ng_cloak: true }
.spacer
.fixed-section-container
.exercise-show
%div
.sidebar-section{ ng_style: '{{ sidebarWidthStyle }}' }
.sidebar_header
.sidebar
%div{ markdown: #exercise.body }
.sidebar-toggle{ ng_mousedown: 'trackMouseMove()' }
.work_area{ ng_style: '{{ workAreaWidthStyle }}' }
And here are the relevant lines in my (Coffeescript) Angular Controller.
$scope.sidebarWidth = 35
$scope.trackingMouse = false
$scope.trackMouseMove = -> $scope.trackingMouse = true
$scope.untrackMouseMove = -> $scope.trackingMouse = false
$scope.updateSidebarWidth = (event) ->
if $scope.trackingMouse
pageWidth = $('.emelyn-layout.middle.fluid').width()
x_percent = (event.pageX * 100) / pageWidth
x_percent = Math.max( Math.min(100, x_percent), 0 )
$scope.sidebarWidth = x_percent
$scope.workAreaWidthStyle = { width: "#{99 - $scope.sidebarWidth}%", marginLeft: "#{$scope.sidebarWidth + 1}%" }
$scope.sideBarWidthStyle = { width: "#{$scope.sidebarWidth}%" }
In other words, I have this .sidebar-section on the left, then a .sidebar-toggle (just a vertical bar), which should be draggable (which causes the width styles to change, once I get this working), and then the .work-area, which is resized, along with the .sidebar, on drag of the toggle.
The issue is that, although the ng_styles are loaded on page load, and although the ng_style values visibly change when I inspect the page and drag the toggle bar, the ng_style changes aren't propagating to the style attributes of the given elements.
In other words, when I inspect the element, I see something like this:
<div class="work_area" ng_style="{ 'width':'48.142857142857146', 'marginLeft':'51.857142857142854' }">
I took a look at this post and tried wrapping updates to the styles in a $scope.$watch, but that didn't change any of the above behavior at all, and it seems wrong to me -- the docs on ng_style suggest to me that the way I'm using it should be effectively correct -- as in, Angular handles the binding for you, without your needing to explicitly tell it when to update the DOM jQuery style.
Any ideas what I'm doing wrong and how to fix it? (Or what might be a better or easier way of doing the above?)
Thanks, and please let me know if you'd like to see any other files or anything,
Sasha
ngStyle works a little differently than what you have. It is evaluated as is, no need for {{}} to point to a $scoped variable.
#full-page.fluid{ ng_controller: "ExerciseCtrl", ng_mousemove: 'updateSidebarWidth($event)', ng_mouseup: 'untrackMouseMove()', ng_cloak: true }
.spacer
.fixed-section-container
.exercise-show
%div
.sidebar-section{ ng_style: 'sidebarWidthStyle' }
.sidebar_header
.sidebar
%div{ markdown: #exercise.body }
.sidebar-toggle{ ng_mousedown: 'trackMouseMove()' }
.work_area{ ng_style: 'workAreaWidthStyle' }
When you use {{}} it evaluates that to a string and then runs it through ngStyle. ngStyle ends up watching a string and not a variable.

A solution for two-binding between angular and a style sheet

I know this sounds silly but I'm writing a wysiwyg editor that allows Designers to create style guides. I've become very fascinated with 2 way binding in angular and was curious if it was possible to two-way bind between a css sheet and a ng-model input field. Currently, I'm making a dynamic style guide that allows a designer to colorpick a primary, secondary colors of a page. These color will change the entire theme of the site uniformly and it would be awesome to do this from the stylesheet itself.
HTML
<input type="text" ng-model="color.primary" />
<button class="btn primary-color" />
CSS
.primary-color {
background: {{color.primary}};
}
js
$scope.color {primary: '00f', secondary: '#e58'}
I know there are plenty of directives like ng-style and ng-class But I fear that every tag will have to be a directive because everything could have an ng-style/ng-class tag. Thus making my code not very dry and hard to maintain.
What if I wanted a dynamic style guide of css. A sheet that I could store as key value pairs of CSS into server like firebase, perhaps even 3way bind the changing of colors in real time? I'm pretty sure this cannot be accomplished using solely angular... would anyone have any ideas on pre compilers or hacks to accomplish this task so that it would result in one clean style guy?
This was pretty fun to work on.
You have access to all of the styles on a page through document.styleSheets, so you just need scope the rules on a style. So lets say that I have this class:
.class {
font-size: 20px;
color: blue;
}
How jsfiddle implements sheets, this is the third style sheet added to the document, so I can just assign to the scope like this:
myApp.controller('TestController', ['$scope', function ($scope) {
$scope.styles = document.styleSheets[3].rules;
}]);
This would let you do things like $scope.styles[0].style['color'] = 'red' to change the color of anything with class to red. Since it's the first thing in the style array.
But that's not cool enough, so we want to create a directive where we can change these from a ui. So we'd like to know all of the things that class controls, to create controls for them, So we can manipulate the css string to get all of those.
Next we have to create a temporary scoped object on the directive that starts off with all of the styles. The reason is that style sheets have checking, so as you type into an input if you do something like $scope.styles[0].style['color'] = 'g' and it was red, it will just reset to red.
So we create an input for each style type, with ng-model of our temp, then just listen for changes and attempt to assign to the style sheet.
I created a fiddle where I implement it, but the directive looks like this.
myApp.directive('styler', function() {
return {
scope: {
styles: '='
},
restrict: 'EA',
template: '<div ng-repeat="type in types">{{type}} <input ng-change="Change(type)" ng-model="temp_styles[type]"/></div>',
link: function(scope, elt, attrs) {
// get the actual styles
var types = scope.styles.cssText.replace(/ /g, '').split('{')[1].split('}')[0].split(';');
scope.types = [];
scope.temp_styles = {};
// get rid of "" element at the end
types.pop();
for (var i in types) {
var split = types[i].split(':');
scope.types.push(split[0]);
scope.temp_styles[split[0]] = split[1];
}
scope.Change = function(type) {
scope.styles.style[type] = scope.temp_styles[type];
};
}
};
});
Cool, dynamic two way binding of styles!
Hope this helped!

create HTML element dynamically

I am very new to angular js. I want to create an input box on click of particular div. Here I need to create element on div which repeating.
<div><div ng-repeat ng-click="create();"></div><div>
What will be the best way to do so?
DOM manipulation in Angular is done via directives (There is paragraph on 'Creating a Directive that Manipulates the DOM' here)
First, read through this excellent article: How do i think in Angular if i have a jQuery background
The Angular Team also provides a pretty neat tutorial, which definetly is worth a look: http://docs.angularjs.org/tutorial
While Angular is pretty easy and fun to use once you have wrapped your head around the concepts, it can be quite overwhelming to dive into the cold. Start slow and do not try to use each and every feature from the beginning. Read a lot.
I strongly recommend egghead.io as a learning resource. The video-tutorials there are bite-sized and easy to watch and understand. A great place for both beginners and intermediates. Start from the bottom here.
Some folks have done great things with Angular. Take a look at http://builtwith.angularjs.org/ and check out some source code.
Use an array and ng-repeat to do that. Have a look at the following code.
I crated scope variable as an empty array. Then created a function to add values to that array.
app.controller('MainCtrl', function($scope) {
$scope.inputFields = [];
$scope.count = 0;
$scope.addField = function(){
$scope.inputFields.push({name:"inputText"+$scope.count++});
}
});
I used ng-repeat with this array. and called the function on the click event of a div.
<div ng-click="addField()">Click here to add</div>
<div ng-repeat="inputField in inputFields">
<input type="text" name="inputField.name">
</div>
Check this working link
Update - Show only one text box on click
I created addField() as follows.
$scope.addField = function(){
$scope.newTextField = "<input type='text' name='myTxt'>";
}
To render this html in my view file I created a new directive called compile as follows.
app.directive('compile', function($compile) {
// directive factory creates a link function
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
// watch the 'compile' expression for changes
return scope.$eval(attrs.compile);
},
function(value) {
// when the 'compile' expression changes
// assign it into the current DOM
element.html(value);
// compile the new DOM and link it to the current
// scope.
// NOTE: we only compile .childNodes so that
// we don't get into infinite loop compiling ourselves
$compile(element.contents())(scope);
}
);
};
});
Then used this directive in my view.html file
<body ng-controller="MainCtrl">
<div ng-click="addField()">Click to Add</div>
<div compile="newTextField"></div>
</body>
click here to view the working link

Categories

Resources