How to show star-rating dynamically based on response? - angularjs

I need to display star rating dynamically based on response.
I am able to display values from 1 to 5 but if rating is 0 then no empty stars are displaying.
If rating = 0.4 also it's showing 1 star filled.
My controller:
(function() {
'use strict';
angular
var app = angular
.module('app')
app.directive('starRating', function () {
return {
restrict: 'A',
template: '<ul class="rating">' +
'<li ng-repeat="star in stars" ng-class="star" ng-click="toggle($index)">' +
'\u2605' +
'</li>' +
'</ul>',
scope: {
ratingValue: '=',
max: '='
},
link: function (scope, elem, attrs) {
var updateStars = function () {
scope.stars = [];
for (var i = 0; i < scope.max; i++) {
if(i == 0) {
scope.stars = [];
scope.stars.push({
empty: i = 0
});
} else {
scope.stars.push({
filled: i < scope.ratingValue
});
}
}
};
scope.$watch('ratingValue', function (oldVal, newVal) {
if (newVal) {
updateStars();
}
});
}
}
});
app.controller('Controller', Controller);
Controller.$inject = ['UserService', '$location', '$rootScope', '$scope', 'fileUpload', 'FlashService', '$cookieStore', '$timeout', '$window'];
function Controller(UserService, $location, $rootScope, $scope, fileUpload, FlashService, $cookieStore, $timeout, $window) {
$scope.selectTestSubject = function() {
$scope.rating = 0;
console.log(levels.length);
for(var k=0; k<levels.length; k++) {
var respRating = levels[k].rating;
// var respRating = 1.5;
console.log(respRating);
$scope.ratings = [{
current: respRating,
max: 5
}];
if(respRating == 0) {
$scope.defaultRating = true;
} else {
$scope.defaultRating = false;
}
}
}
}
}) ();
My HTML page:
<div><span ng-repeat="rating in ratings">
<div star-rating rating-value="rating.current" max="rating.max"></div>
</span>
</div>

One problem with your solution is your $watch expression. Where you have the following:
scope.$watch('ratingValue', function (oldVal, newVal) {
if (newVal) {
updateStars();
}
});
oldVal and newVal are actually the wrong way around, the $watch function first takes in the new value followed by the old value. Secondly, the condition if (newVal) doesn't work for 0, because 0 is a falsey value.
Instead, you should have:
scope.$watch('ratingValue', function(value, previousValue) {
// Only update the view when the value has changed.
if (value !== previousValue) {
updateStars();
}
});
Your updateStars function also always reinitialises the scope.stars variable and appends onto it. Doing this can have some unwanted side effects and results in the view not reflecting the model value. It's best to initialise the array, then append the item if it doesn't yet exist or update the existing value. So you'll have something like this:
// Initialise the stars array.
scope.stars = [];
var updateStars = function() {
for (var i = 0; i < scope.max; i++) {
var filled = i < Math.round(scope.ratingValue);
// Check if the item in the stars array exists and
// append it, otherwise update it.
if (scope.stars[i] === undefined) {
scope.stars.push({
filled: filled
});
} else {
scope.stars[i].filled = filled;
}
}
};
Since the $watch expression only updates the stars when the value has changed, you'll now need to trigger the update the first time your link function fires. So this is simply:
// Trigger an update immediately.
updateStars();
Your template also does not correctly utilise the filled property on the star, it should instead contain the appropriate ng-class like so:
<ul class="rating">
<li class="star"
ng-repeat="star in stars"
ng-class="{ filled: star.filled }"
ng-click="toggle($index)">
\u2605
</li>
</ul>
With a simple style,
.star {
cursor: pointer;
color: black;
}
.star.filled {
color: yellow;
}
You can also improve this rating system by listening to mouseenter and mouseleave effects, so that the stars appear yellow when the user is selecting a new value. This is pretty common functionality. You can achieve this by making a few modifications.
To begin with, the template should be updated to listen for these events:
<ul class="rating">
<li class="star"
ng-repeat="star in stars"
ng-class="{ filled: star.filled }"
ng-mouseenter="onMouseEnter($event, $index + 1)"
ng-mouseleave="onMouseLeave($event)"
ng-click="toggle($index)">
\u2605
</li>
</ul>
Next, we want to make a small adjustment to the updateStars function to take in a rating parameter:
var updateStars = function(rating /* instead of blank */ ) {
for (var i = 0; i < scope.max; i++) {
var filled = i < Math.round(rating); // instead of scope.ratingValue
// Check if the item in the stars array exists and
// append it, otherwise update it.
if (scope.stars[i] === undefined) {
scope.stars.push({
filled: filled
});
} else {
scope.stars[i].filled = filled;
}
}
};
// Trigger an update immediately.
updateStars(scope.ratingValue /* instead of blank */ );
scope.$watch('ratingValue', function(value, previousValue) {
// Only update the view when the value changed.
if (value !== previousValue) {
updateStars(scope.ratingValue /* instead of blank */ );
}
});
Now we can add in our event callbacks from the view,
// Triggered when the cursor enters a star rating (li element).
scope.onMouseEnter = function (event, rating) {
updateStars(rating);
};
// Triggered when the cursor leaves a star rating.
scope.onMouseLeave = function (event) {
updateStars(scope.ratingValue);
};
And that's it! Full demo here.

Related

Angular directives collision

I would like to use 2 directives in the same app. The problem is that when I use the second one, the first one crashes with an ugly error: TypeError: Failed to execute 'getComputedStyle' on 'Window': parameter 1 is not of type 'Element'.
The first directive is angular-fullpage.js (https://github.com/hellsan631/angular-fullpage.js) and the second one is angular bootstrap affix implementation (https://github.com/maxisam/angular-bootstrap-affix).
When I include both modules (directives), the fullpage directive crashes with the afformentioned error. If I remove the affix directive, then fullpage.js Works fine (just by removing the second directive from the modules).
How can I avoid directive collision? Are there any workarounds for this issue or should I just settle for just 1 of the directives?
Thanks!!!!
app.js:
var myApp = angular
.module(
'myApp',
[
'ngRoute',
'ngAnimate',
'ngMessages',
'ui.bootstrap',
'angular-loading-bar',
'LocalStorageModule',
'ngEnter',
'ng-Static-Include',
'ngResource',
'toastr',
'ng-Static-Include',
'pageslide-directive',
'ngRutChange',
'xmsbsStopPropagation',
'ngEnter',
'ng-rut',
'ngMessages',
'duScroll',
'dynamicNumber',
'xmsbsDirectives',
'salfaDirectives',
'mgcrea.bootstrap.affix',
'fullPage.js',
'ui.tinymce',
'mega-menu',
'bootstrap.fileField',
'ngTagsInput'
]);
Partial view (home) trying to use the fullpage directive and generating the error:
<div class="section">
<div ng-style="{'width': '100%', 'height': vm.divHeight+'px'}" style="margin-top:-7px;background:url(/content/images/03.jpg) center center; background-size:cover;">
<div class="col-sm-12">
<h1 class="fg-grayLight text-center text-shadow vert-align-center" style="z-index:2;" ng-style="{'padding-top':vm.divHeight/9+'px'}">sistema de recursos humanos 2.0</h1>
</div>
</div>
</div>
<div class="section">
<div class="col-sm-12">
<h2 class="text-center">noticias</h2>
</div>
</div>
Affix directive:
'use strict';
angular.module('mgcrea.bootstrap.affix', ['mgcrea.jquery'])
.directive('bsAffix', function($window, dimensions) {
var checkPosition = function(instance, el, options) {
var scrollTop = window.pageYOffset;
var scrollHeight = document.body.scrollHeight;
var position = dimensions.offset.call(el[0]);
var height = dimensions.height.call(el[0]);
var offsetTop = options.offsetTop * 1;
var offsetBottom = options.offsetBottom * 1;
var reset = 'affix affix-top affix-bottom';
var affix;
if(instance.unpin !== null && (scrollTop + instance.unpin <= position.top)) {
affix = false;
} else if(offsetBottom && (position.top + height >= scrollHeight - offsetBottom)) {
affix = 'bottom';
} else if(offsetTop && scrollTop <= offsetTop) {
affix = 'top';
} else {
affix = false;
}
if (instance.affixed === affix) return;
instance.affixed = affix;
instance.unpin = affix === 'bottom' ? position.top - scrollTop : null;
el.removeClass(reset).addClass('affix' + (affix ? '-' + affix : ''));
};
var checkCallbacks = function(scope, instance, iElement, iAttrs) {
if(instance.affixed) {
if(iAttrs.onUnaffix)
eval("scope." + iAttrs.onUnaffix);
}
else {
if(iAttrs.onAffix)
eval("scope." + iAttrs.onAffix);
}
};
return {
restrict: 'EAC',
link: function postLink(scope, iElement, iAttrs) {
var instance = {unpin: null};
angular.element($window).bind('scroll', function() {
checkPosition(instance, iElement, iAttrs);
checkCallbacks(scope, instance, iElement, iAttrs);
});
angular.element($window).bind('click', function() {
setTimeout(function() {
checkPosition(instance, iElement, iAttrs);
checkCallbacks(scope, instance, iElement, iAttrs);
}, 1);
});
}
};
});
fullpage directive (this directive requires the original jQuery fullpage lugin to work http://www.alvarotrigo.com/fullPage/):
(function () {
'use strict';
angular
.module('fullPage.js', [])
.directive('fullPage', fullPage);
fullPage.$inject = ['$timeout'];
function fullPage($timeout) {
var directive = {
restrict: 'A',
scope: { options: '=' },
link: link
};
return directive;
function link(scope, element) {
var pageIndex;
var slideIndex;
var afterRender;
var onLeave;
var onSlideLeave;
if (typeof scope.options === 'object') {
if (scope.options.afterRender) {
afterRender = scope.options.afterRender;
}
if (scope.options.onLeave) {
onLeave = scope.options.onLeave;
}
if (scope.options.onSlideLeave) {
onSlideLeave = scope.options.onSlideLeave;
}
} else if (typeof options === 'undefined') {
scope.options = {};
}
var rebuild = function () {
destroyFullPage();
$(element).fullpage(sanatizeOptions(scope.options));
if (typeof afterRender === 'function') {
afterRender();
}
};
var destroyFullPage = function () {
if ($.fn.fullpage.destroy) {
$.fn.fullpage.destroy('all');
}
};
var sanatizeOptions = function (options) {
options.afterRender = afterAngularRender;
options.onLeave = onAngularLeave;
options.onSlideLeave = onAngularSlideLeave;
function afterAngularRender() {
//We want to remove the HREF targets for navigation because they use hashbang
//They still work without the hash though, so its all good.
if (options && options.navigation) {
$('#fp-nav').find('a').removeAttr('href');
}
if (pageIndex) {
$timeout(function () {
$.fn.fullpage.silentMoveTo(pageIndex, slideIndex);
});
}
}
function onAngularLeave(page, next, direction) {
if (typeof onLeave === 'function' && onLeave(page, next, direction) === false) {
return false;
}
pageIndex = next;
}
function onAngularSlideLeave(anchorLink, page, slide, direction, next) {
if (typeof onSlideLeave === 'function' && onSlideLeave(anchorLink, page, slide, direction, next) === false) {
return false;
}
pageIndex = page;
slideIndex = next;
}
//if we are using a ui-router, we need to be able to handle anchor clicks without 'href="#thing"'
$(document).on('click', '[data-menuanchor]', function () {
$.fn.fullpage.moveTo($(this).attr('data-menuanchor'));
});
return options;
};
var watchNodes = function () {
return element[0].getElementsByTagName('*').length;
};
scope.$watch(watchNodes, rebuild);
scope.$watch('options', rebuild, true);
element.on('$destroy', destroyFullPage);
}
}
})();
Does the second depends on the first one? If does, are you compiling the directive one before embedding the second?
If you have 'collision' it's means that you're using some sort of 'use strict', would be more valuable if you show part of the code and see if transclude is part of the directive.

Change Text in directive

How can I change something (for instance the color) in the directive?
I have a controller which gets after every 3 seconds new data (eye Positions)
myApp.controller('MainController', function ($scope, $interval, externalService, eyeTrackerService) {
$rootScope.gazePoints = [];
var size = 4;
var analyze = function () {
if ($rootScope.gazePoints.length > size) {
$scope.gazeArea = eyeTrackerService.getGazePoints($scope.gazePoints);
//reduce data in array
$scope.gazePoints.splice(0, 3);
}
};
var eyeTrackerData = function () {
externalService.getData().then(function (eyeTrackerData) {
var eyetracker = eyeTrackerData.data.EyeTracker;
var gaze_X = (eyetracker.X_left + eyetracker.X_right) * 0.5 * screen.availWidth;
var gaze_Y = (eyetracker.Y_left + eyetracker.Y_right) * 0.5 * screen.availHeight;
$scope.gazePoints.push({ x: gaze_X, y: gaze_Y });
analyze();
});
};
$interval(eyeTrackerData, 3000)
});
The service gets an array of gazePoints and have to analyse if the user is looking at the panel:
myApp.service('eyeTrackerService', function ($rootScope) {
this.getGazePoints = function (gazePoints) {
var counter = 0;
var date = $rootScope.rect;
for (var i = 0; i < gazePoints.length; i++) {
var x = gazePoints[i].x;
var y = gazePoints[i].y;
if (x >= date.left && x <= date.right && y >= date.top && y < date.bottom) {
counter =+ 1;
console.log("is watching");
if(counter == 5){
///Here it should call the directive and change the color
}
}
else {
console.log("is not watching")
}
}
}
});
my directive:
myApp.directive('dateInfo', function ($rootScope, $interval) {
return {
restrict: 'A',
scope: {
},
templateUrl: '123/Scripts/directives/html/dateInfo.html',
link: function (scope, element, attrs) {
$interval(function () {
$rootScope.rect = element[0].getBoundingClientRect();
//Here i want to change the color
//for example a scope.changetext(); but how??
}, 3000);
}
};
});
my html-file:
<div class="panel panel-primary">
<div class="panel-heading">MyPanel</div>
<div class="panel-body">
<input id="test" name="test">
</div>
</div>
I know it´s a lot of code. But as you can see the directive are also called after every second to get the current position of the panel.
Now I want to change the color in the panel, after the counter is == 5
What is the best solution in that case? I heard it not good to change the text in the controller.
Inside the interval, use
$interval(function () {
$rootScope.rect = element[0].getBoundingClientRect();
element.addClass('someClassThatSetsColor');
}, 3000);
You can set the class based on a parameter of your choice, say if it's not looking you'll set a class that changes the color to red, or whatever.

Angularjs responsive directive live updating issue (possibly due to ng-repeating the directive)

I am creating a post feed by ng-repeating JSON files from the cloud. I tried to make the posts responsive by using angular directives that update the template url with the screen size.
The problem is that only the last post in the ng-repeat responds and changes templates (with or without the reverse filter) when I resize the page. The other posts just remain the template that it was when originally loaded.
Here's the ng-repeat in the page
<div ng-show="post_loaded" ng-repeat="post in posts | reverse | filter:searchText ">
<feed-post>
</feed-post>
</div>
Here's the directive javascript file
app.directive('feedPost', function ($window) {
return {
restrict: 'E',
template: '<div ng-include="templateUrl"></div>',
link: function(scope) {
$window.onresize = function() {
changeTemplate();
scope.$apply();
};
changeTemplate();
function changeTemplate() {
var screenWidth = $window.innerWidth;
if (screenWidth < 768) {
scope.templateUrl = 'directives/post_mobile.html';
} else if (screenWidth >= 768) {
scope.templateUrl = 'directives/post_desktop.html';
}
}
}
};});
This happens because you re-assigning the .onresize in each directive and it stays effective only for the last linked directive.
I'd suggest to use it in a more angular way. You don't actually need a custom directive
In the controller that manages list of posts add reference to $window in $scope
$scope.window = $window;
Then in template make use of it
<div ng-include="directives/post_mobile.html" ng-if="window.innerWidth < 768"></div>
<div ng-include="directives/post_desktop.html" ng-if="window.innerWidth >= 768"></div>
To avoid extra wrappers for posts feed you might want to use ng-repeat-start, ng-repeat-end directives
this is a directive i wrote based on bootstrap sizes and ngIf directive :
mainApp.directive("responsive", function($window, $animate) {
return {
restrict: "A",
transclude: 'element',
terminal: true,
link: function($scope, $element, $attr, ctrl, $transclude) {
//var val = $attr["responsive"];
var block, childScope;
$scope.$watch(function(){ return $window.innerWidth; }, function (width) {
if (width < 768) {
var s = "xs";
} else if (width < 992) {
var s = "sm";
} else if (width < 1200) {
var s = "md";
} else {
var s = "lg";
}
console.log("responsive ok?", $attr.responsive == s);
if ($attr.responsive == s) {
if (!childScope) {
$transclude(function(clone, newScope) {
childScope = newScope;
clone[clone.length++] = document.createComment(' end responsive: ' + $attr.responsive + ' ');
block = {
clone: clone
};
$animate.enter(clone, $element.parent(), $element);
});
}
} else {
if (childScope) {
childScope.$destroy();
childScope = null;
}
if (block) {
block.clone.remove();
block.clone = null;
block = null;
}
}
});
}
};
});

How to dismiss an angularjs alert when user changes route/state

I am using this angular js service/directive github pageto display alerts. An issue has been opened relating to the question I am asking but it has not been addressed by the developer.
i want first alert shown when user logs in to disappear when the user clicks logout but the alerts are stacking up on top of each other.
Here is a fiddle although I could not replicate the issue but it shows the structure of my code. fiddle
html:
<body ng-app="app">
<mc-messages></mc-messages>
<button ng-click="login()">Login</button>
<button ng-click="logout()">Logout</button>
</body>
js:
/*jshint strict:false */
'use strict';
var app = angular.module('app', ['MessageCenterModule']);
app.controller(function ($scope, messageCenterService, $location) {
$scope.login = function () {
$location.path('/');
messageCenterService.add('success',
'You are now loggedin!', {
status: messageCenterService.status.next
});
};
$scope.logout = function () {
$location.path('login');
messageCenterService.add('success',
'You are now loggedout!', {
status: messageCenterService.status.next
}
};
});
// Create a new angular module.
var MessageCenterModule = angular.module('MessageCenterModule', []);
// Define a service to inject.
MessageCenterModule.
service('messageCenterService', ['$rootScope', '$sce', '$timeout',
function ($rootScope, $sce, $timeout) {
return {
mcMessages: this.mcMessages || [],
status: {
unseen: 'unseen',
shown: 'shown',
/** #var Odds are that you will show a message and right after that
* change your route/state. If that happens your message will only be
* seen for a fraction of a second. To avoid that use the "next"
* status, that will make the message available to the next page */
next: 'next',
/** #var Do not delete this message automatically. */
permanent: 'permanent'
},
add: function (type, message, options) {
var availableTypes = ['info', 'warning', 'danger', 'success'],
service = this;
options = options || {};
if (availableTypes.indexOf(type) === -1) {
throw "Invalid message type";
}
var messageObject = {
type: type,
status: options.status || this.status.unseen,
processed: false,
close: function () {
return service.remove(this);
}
};
messageObject.message = options.html ? $sce.trustAsHtml(message) : message;
messageObject.html = !! options.html;
if (angular.isDefined(options.timeout)) {
messageObject.timer = $timeout(function () {
messageObject.close();
}, options.timeout);
}
this.mcMessages.push(messageObject);
return messageObject;
},
remove: function (message) {
var index = this.mcMessages.indexOf(message);
this.mcMessages.splice(index, 1);
},
reset: function () {
this.mcMessages = [];
},
removeShown: function () {
for (var index = this.mcMessages.length - 1; index >= 0; index--) {
if (this.mcMessages[index].status == this.status.shown) {
this.remove(this.mcMessages[index]);
}
}
},
markShown: function () {
for (var index = this.mcMessages.length - 1; index >= 0; index--) {
if (!this.mcMessages[index].processed) {
if (this.mcMessages[index].status == this.status.unseen) {
this.mcMessages[index].status = this.status.shown;
} else if (this.mcMessages[index].status == this.status.next) {
this.mcMessages[index].status = this.status.unseen;
}
this.mcMessages[index].processed = true;
}
}
},
flush: function () {
$rootScope.mcMessages = this.mcMessages;
}
};
}]);
MessageCenterModule.
directive('mcMessages', ['$rootScope', 'messageCenterService', function ($rootScope, messageCenterService) {
/*jshint multistr: true */
var templateString = '\
<div id="mc-messages-wrapper">\
<div class="alert alert-{{ message.type }} {{ animation }}" ng-repeat="message in mcMessages">\
<a class="close" ng-click="message.close();" data-dismiss="alert" aria-hidden="true">×</a>\
<span ng-switch on="message.html">\
<span ng-switch-when="true">\
<span ng-bind-html="message.message"></span>\
</span>\
<span ng-switch-default>\
{{ message.message }}\
</span>\
</div>\
</div>\
';
return {
restrict: 'EA',
template: templateString,
link: function (scope, element, attrs) {
// Bind the messages from the service to the root scope.
messageCenterService.flush();
var changeReaction = function (event, to, from) {
// Update 'unseen' messages to be marked as 'shown'.
messageCenterService.markShown();
// Remove the messages that have been shown.
messageCenterService.removeShown();
$rootScope.mcMessages = messageCenterService.mcMessages;
messageCenterService.flush();
};
$rootScope.$on('$locationChangeStart', changeReaction);
scope.animation = attrs.animation || 'fade in';
}
};
}]);
Hope this is clear enough for someone to help me. If not let me know and I can try to clarify.

AngularJS Directive using Compile cannot access child elements

My intent was to create a directive that could rearrange (not reorder) its child elements into a Bootstrap CSS Grid, but I am having a lot of difficulty getting access to the child elements.
I've tried a lot of different things and have researched Compile vs Link vs Controller directive options. I think I might have to change the 'compile' to 'link' in my directive to get this to work, but I am unsure how to do that.
I have an AngularJS directive on GitHub that takes an array or object of parameters to render a simple or complex grid.
In the example below you can see the layoutOptions.data = [3, 4] which means the grid will have 3 cells in the top row and 4 in the second. This is working well.
The second step is that I would like to render some divs as child elements of the directive and the directive will place these in the cells of the grid as it is created. This is shown by the layoutOptions.content = ['apple', 'orange', 'pear', 'banana', 'lime', 'lemon', 'grape'] but this needs to be de-coupled so that it could be literally anything.
HTML Input
<div ng-app="blerg">
<div ng-controller="DemoCtrl">
<div class="container" hr-layout="layoutOptions">
<div ng-transclude ng-repeat="fruit in layoutOptions.content">{{fruit}}</div>
</div>
</div>
</div>
Desired (not actual) Output
Actual output is as below, but does not include the inner DIVs with fruit names
<div class="container hr-layout" hr-layout="layoutOptions">
<div class="row">
<div class="col-md-4"><!-- from ng-repeat --><div>apple</div></div>
<div class="col-md-4"><!-- from ng-repeat --><div>orange</div></div>
<div class="col-md-4"><!-- from ng-repeat --><div>pear</div></div>
</div>
<div class="row">
<div class="col-md-3"><!-- from ng-repeat --><div>banana</div></div>
<div class="col-md-3"><!-- from ng-repeat --><div>lime</div></div>
<div class="col-md-3"><!-- from ng-repeat --><div>lemon</div></div>
<div class="col-md-3"><!-- from ng-repeat --><div>grape</div></div>
</div>
</div>
And a jsFiddle that uses it here: http://jsfiddle.net/harryhobbes/jJDZv/show/
Code
angular.module('blerg', [])
.controller('DemoCtrl', function($scope, $timeout) {
$scope.layoutOptions = {
data: [3, 4],
content: ['apple', 'orange', 'pear', 'banana', 'lime', 'lemon', 'grape']
};
})
.directive("hrLayout", [
"$compile", "$q", "$parse", "$http", function ($compile, $q, $parse, $http) {
return {
restrict: "A",
transclude: true,
compile: function(scope, element, attrs) {
//var content = element.children();
return function(scope, element, attrs) {
var contentCount = 0;
var renderTemplate = function(value, content) {
if (typeof content === 'undefined' || content.length <= contentCount)
var cellContent = 'Test content(col-'+value+')';
else if (Object.prototype.toString.call(content) === '[object Array]')
var cellContent = content[contentCount];
else
var cellContent = content;
contentCount++;
return '<div class="col-md-'+value+'">'+cellContent+'</div>';
};
var renderLayout = function(values, content) {
var renderedHTML = '';
var rowCnt = 0;
var subWidth = 0;
angular.forEach(values, function(value) {
renderedHTML += '<div class="row">';
if(Object.prototype.toString.call(value) === '[object Array]') {
angular.forEach(value, function(subvalue) {
if(typeof subvalue === 'object') {
renderedHTML += renderTemplate(
subvalue.w.substring(4), renderLayout(subvalue.d)
);
} else {
renderedHTML += renderTemplate(subvalue.substring(4));
}
});
} else {
if(value > 12) {
value = 12;
} else if (value <= 0) {
value = 1;
}
subWidth = Math.floor(12 / value);
for (var i=0; i< value-1; i++) {
renderedHTML += renderTemplate(subWidth);
}
renderedHTML += renderTemplate((12-subWidth*(value-1)));
}
renderedHTML += '</div>';
rowCnt++;
});
return renderedHTML;
};
scope.$watch(attrs.hrLayout, function(value) {
element.html(renderLayout(value.data));
});
element.addClass("hr-layout");
};
}
};
}]);
This may help - http://jsfiddle.net/PwNZ5/1/
App.directive('hrLayout', function($compile) {
return {
restrict: 'A',
// allows transclusion
transclude: true,
// transcludes the content of an element on which hr-layout was placed
template: '<div ng-transclude></div>',
compile: function(tElement, tAttrs, transcludeFn) {
return function (scope, el, tAttrs) {
var data = scope.$eval(tAttrs.hrLayout),
dom = '';
transcludeFn(scope, function cloneConnectFn(cElement) {
// hide the transcluded content
tElement.children('div[ng-transclude]').hide();
// http://ejohn.org/blog/how-javascript-timers-work/‎
window.setTimeout(function() {
for(var row = 0; row < data.data.length; row++) {
dom+= '<div class="row">';
for(var col = 0; col < data.data[row]; col++) {
dom+= '<div class="col-md-' + data.data[row] + '">' + tElement.children('div[ng-transclude]').children(':eq(' + ( row + col ) + ')').html() + '</div>';
}
dom+= '</div>';
}
tElement.after(dom);
}, 0);
});
};
}
};
});
Your approach looks like too complex. Maybe you should use ngRepeat directive http://docs.angularjs.org/api/ng.directive:ngRepeat and orderBy filter http://docs.angularjs.org/api/ng.filter:orderBy insted of updating html of element every time when hrLayout was updated?

Resources