I am trying to do the custom star rating with angular.js, where I will have different set of images. I need to change it dynamically on hover the image. I am having 5 images
X X X X X
if I move the mouse pointer to 4th X I should be able to dynamically change
X
I used directive to achieve it.
.directive('fundooRating', function () {
return {
restrict: 'A',
template: '<ul class="rating">' +
'<li ng-repeat="star in stars" ng-class="star"
ng-click="toggle($index)"><img ng-mouseenter="hoveringOver($index)"
ng-src="{{con}}" />' +
'',
scope: {
ratingValue: '=',
max: '=',
readonly: '#',
onRatingSelected: '&'
},
link: function (scope, elem, attrs) {
var updateStars = function() {
scope.stars = [];
for (var i = 0; i < scope.max; i++) {
scope.stars.push({filled: i < scope.ratingValue});
}
};
scope.con = "Images/Rating/empty.png";
scope.hoveringOver = function(index){
var countIndex = index+1;
scope.Condition = "Good.png"
console.log("Hover " + countIndex);
};
scope.toggle = function(index) {
if (scope.readonly && scope.readonly === 'true') {
return;
}
scope.ratingValue = index + 1;
scope.onRatingSelected({rating: index + 1});
};
scope.$watch('ratingValue', function(oldVal, newVal) {
if (newVal) {
updateStars();
}
});
}
} });
How can I able to find which image my mouse pointer is and how to change the rest of Images. I want to do the custom rating option.
Angular UI gives you premade directives for the same purpose, did you try it?
http://angular-ui.github.io/bootstrap/
Go down to the Rating Title in the same page, i think it might solve your purpose.
You'll need a condition for each star in your updateStars function, either as a property for each, or a separate array. Then, you can do something like this:
scope.hoveringOver = function(index){
for (var i = 0; i <= index; i++) {
scope.stars[i].Condition = "Good.png";
}
};
Or the separate array way (assuming the array is scope.conditions):
scope.hoveringOver = function(index){
for (var i = 0; i <= index; i++) {
scope.conditions[i] = "Good.png";
}
};
You also need a function opposite of hoveringOver to remove the states to default/previous versions.
Related
The Problem
Unable to update Model;
Using the set function I can change the ngModel value, and through the pipeline function $parsers, however the Del function uses splice to process the array, but it can not pass the pipeline function and realizes the update model value.
I tried to use $scope.$apply () to execute after ngModel.$setViewValue ($scope.images) and still can't be solved.
The version of the angular used is 1.6.6.
Online code, online code links, I hope you can help me to see where the problem is in the end.
View code
<div ng-controller="appController">
<div image-uploads ng-model="files"></div>
<p style="display: block; color: red">{{files}}</p>
</div>
Javascript code
var app = angular.module('app', []);
app.controller('appController', function ($scope) {
$scope.files = '1,2,3,4';
});
app.directive('imageUploads', function () {
return {
require: '?^ngModel',
restrict: 'EA',
template: '<div class="image-upload-box"><p class="image_upload" ng-repeat="image in images track by $index"><button ng-click="set()">setModel</button><button ng-click="del($index)">{{image}}</button></p></div>',
link: function ($scope, element, attrs, ngModel) {
ngModel.$formatters.push(function (modelValue) {
var images = new Array();
if (!ngModel.$isEmpty(modelValue)) {
var values = modelValue.split(",");
for (var j = 0; j < values.length; j++) {
images.push({
'id': values[j]
});
}
}
return images;
});
ngModel.$parsers.push(function (viewValue) {
var s = "";
for (var j = 0; j < viewValue.length; j++) {
if (viewValue[j].id != null) {
if (j > 0) {
s += ",";
}
s += viewValue[j].id;
}
}
return s;
});
ngModel.$render = function () {
$scope.images = ngModel.$viewValue;
};
$scope.del = function (i) {
$scope.images.splice(i, 1);
ngModel.$setViewValue($scope.images);
};
$scope.set = function () {
console.log('set');
$scope.images = [{id: 5}, {id: 6}, {id: 7}];
ngModel.$setViewValue($scope.images);
}
}
};
});
I have a piece of code like this:
Module.Article.directive('article', function ($compile) {
return {
restrict: 'C',
link: function (scope, element, attrs) {
var el = angular.element('<div></div>');
scope.showNum = function(i){
console.log(i);
};
for (var i = 1; i <= 5; i++) {
el.append('<span ng-mouseover="showNum(i)">' + i + '</span>');
}
$compile(el)(scope);
element.append(el);
}
and I want to get this result:
<div>
<span ng-mouseover="showNum(1)">1</span>
<span ng-mouseover="showNum(2)">2</span>
<span ng-mouseover="showNum(3)">3</span>
<span ng-mouseover="showNum(4)">4</span>
<span ng-mouseover="showNum(5)">5</span>
</div>
but instead I get such result:
<div>
<span ng-mouseover="showNum(i)">1</span>
<span ng-mouseover="showNum(i)">2</span>
<span ng-mouseover="showNum(i)">3</span>
<span ng-mouseover="showNum(i)">4</span>
<span ng-mouseover="showNum(i)">5</span>
</div>
Does anybody know how to pass i to the showNum() handler?
Change it to this, so the actual i variable gets printed instead of the letter i:
Module.Article.directive('article', function ($compile) {
return {
restrict: 'C',
link: function (scope, element, attrs) {
var el = angular.element('<div></div>');
scope.showNum = function(i){
console.log(i);
};
for (var i = 1; i <= 5; i++) {
el.append('<span ng-mouseover="showNum(' + i + ')">' + i + '</span>');
}
$compile(el)(scope);
element.append(el);
}
Edit: I agree that the other answer offers more flexibility, this answer points out a minor mistake in the way the person who asked the question was trying to implement it though.
You could use ng-repeat to render template and pass value, only you need to create a items array inside your directive link function, ng-repeat will take care of rendering part of the html.
Directive
Module.Article.directive('article', function($compile) {
return {
restrict: 'C',
template: '<div>' +
'<span ng-repeat="i in items" ng-mouseover="showNum(i)">{{i}}</span>'+
'</div>',
link: function(scope, element, attrs) {
var el = angular.element('<div></div>');
scope.showNum = function(i) {
console.log(i);
};
scope.items = [];
for (var i = 1; i <= 5; i++) {
scope.items.push(i)
}
}
}
});
Demo Plunkr
I have created a access key Angular directive.
angular.module('tcne.common').directive("accessKey", function () {
return {
restrict: "A",
scope: {
},
link: function (scope, element, attrs) {
var $element = $(element);
$element.attr("accesskey", attrs.accessKey);
var content = $element.html();
for (var i = 0; i < content.length; i++) {
var char = content[i];
if (char.toLowerCase() === attrs.accessKey.toLowerCase()) {
content = content.substr(0, i) + "<u>" + char + "</u>" + content.substr(i + 1);
break;
}
}
$element.html(content);
},
replace: false
};
});
It underscores the access key in the button Label and adds the access key attribute to the element. Can I somehow prevent the accesskey from setting the button in focus? It kills the purpose of keyboard short cuts
edit: Rolled my own acccess key
angular.module('tcne.common').directive('accessKey', ['$compile', '$interval', function ($compile, $interval) {
var modifierPressed = false;
$("body").keyup(function (e) {
if (modifierPressed && !e.altKey) {
modifierPressed = false;
digestScopes();
}
});
$("body").keydown(function (e) {
modifierPressed = e.altKey;
if (modifierPressed && scopes.hasOwnProperty(String.fromCharCode(e.which).toLowerCase())) {
var scope = scopes[String.fromCharCode(e.which).toLowerCase()];
scope.handle();
return;
}
if (modifierPressed) {
e.preventDefault();
digestScopes();
}
});
function digestScopes() {
for (var index in scopes) {
if (scopes.hasOwnProperty(index)) {
var scope = scopes[index]
scope.$digest();
}
}
}
function isModifierPressed() {
return modifierPressed;
}
var scopes = {};
return {
restrict: 'A',
scope: {
},
link: function (scope, element, attrs) {
var key = attrs.accessKey.toLowerCase();
var content = element.html();
var char;
for (var i = 0; i < content.length; i++) {
char = content[i];
if (char.toLowerCase() === key) {
content = content.substr(0, i) + '<u><strong ng-if="highlight()">{{char}}</strong><span ng-if="!highlight()">{{char}}</span></u>' + content.substr(i + 1);
break;
}
}
element.html(content);
var underscoreScope = scope.$new();
underscoreScope.char = char;
underscoreScope.highlight = isModifierPressed;
underscoreScope.handle = element.click.bind(element);
scopes[key] = underscoreScope;
scope.$on('$destroy', function () {
delete scopes[key];
});
$compile(element.find("u"))(underscoreScope);
},
replace: false
};
}]);
It also highlights the access key button when alt key is pressed which is nice
Any pit falls with this code? Thanks
Found a pitfall, element.html and then $compile will break any directives inside the element that is already compiled. So I changed to
var captionElement = element.contents().first(":text");
var content = captionElement.text();
And then I add my custom content like
var view = $("<span>").html(content);
captionElement.replaceWith(view);
$compile(view)(vm);
Please let me know if this is considered bad practice
So my use case is:
cols = [{field="product.productId"},{field="product.productPrice"}];
data = {products:[{product:{productId:1,productPrice:10}, {product:{productId:2, productPrice:15}}]}
What I would like to do is:
<div ng-repeat="product in data.products">
<div ng-repeat="col in cols">
<input type="text" ng-model="product[col.field]"></>
</div>
</div>
Now if col.field was just 'someField' and not 'some.deep.field' this would work. Because the field has many elements, the correct way to do ng-model would be "product[some][deep][field]" if I didn't want to be generic and allow my data and columns to change. I've tried this approach and it worked for a non-generic use case.
What I have tried to make it generic:
Recompiling my 'input' element. This creates the perfect HTML E.G it has ng-model="product['some']['deep']['field']" on it, but in no way is the field bound whatsoever. Perhaps I am compiling with the wrong scope here? I have tried addingAttribute ng-init="hello='Hey'" ng-model="hello" at this point and it worked and bound properly... so I feel I am missing something regarding scope here.
compile: function (templateElement) {
templateElement[0].removeAttribute('recursive-model');
templateElement[0].removeAttribute('recursive-model-accessor');
return {
pre: function (scope, element, attrs) {
function convertDotToMultiBracketNotation(dotNote) {
var ret = [];
var arrayOfDots = dotNote.split('.');
for (i = 0; i < arrayOfDots.length; i++) {
ret.push("['" + arrayOfDots[i] + "']");
}
return ret.join('');
}
if (attrs.recursiveModel && attrs.recursiveModelAccessor) {
scope[scope.recursiveModel] = scope.ngModel;
element[0].setAttribute('ng-model', scope.recursiveModel + convertDotToMultiBracketNotation(scope.recursiveModelAccessor));
var res = $compile(element[0])(scope);
console.info('new compiled element:', res);
return res;
}
}
}
}
Messing with the NgModelController to format and parse. In this case I have put the entire 'row' object into ng-model and then used formatter/parser to only mess with the 1 field I was interested in. This works until you clear the field. At that point it seems to wipe out modelCtrl.$modelValue completely.
In other words - my console.log says:
Setting field to val 'Text' on row [object]
Setting field to val 'Tex' on row [object]
Setting field to val 'Te' on row [object]
Setting field to val 'T' on row [object]
Setting field to val '' on row [object]
Setting field to val 'A' on row undefined
link: function (scope, element, attrs, ctrls) {
if(ctrls[2] && scope.recursiveModelAccessor){
var modelCtrl = ctrls[2];
modelCtrl.$formatters.push(function (inputValue) {
function getValue(object, string){
var explodedString = string.split('.');
for (i = 0, l = explodedString.length; i < l; i++) {
object = object[explodedString[i]];
}
return object;
};
function getValueRecursive (row, field) {
if (field instanceof Array) {
var ret = [];
for (var i = 0; i < col.field.length; i++) {
ret.push(getValue(row, field[i]));
}
return ret.join('/');
} else {
return getValue(row, field);
}
};
return getValueRecursive(modelCtrl.$modelValue, scope.recursiveModelAccessor);
});
modelCtrl.$parsers.push(function (inputValue) {
function setValueRecursive (row, field, newValue) {
if (field instanceof Array) {
var firstField = field.shift();
if(field.length==1){
field = field[0];
}
setValueRecursive(row[firstField], field, newValue);
} else {
console.log("Setting "+field+" to val:"+newValue+" on row:"+row);
row[field]=newValue;
}
};
setValueRecursive(modelCtrl.$modelValue, scope.recursiveModelAccessor.split('.'), modelCtrl.$viewValue);
return modelCtrl.$modelValue;
});
Long story short (8 solid hours wasted on this) - don't put ng-model="something" on your object, if you then plan to re-compile after modifying the ng-model attribute.
A working directive for rebinding the ngModel (Just don't have the attribute already on your object!)
<div ng-repeat="product in data.products">
<div ng-repeat="col in cols">
<input type="text" recursive-model="product" recursive-model-accessor="some.deep.field"></input>
</div>
</div>
Just make sure you don't have ng-model="something".
Of course - a 100% perfect solution would throw exception if ng-model attribute was present :)
module.directive('rebindModel', ['$compile','$parse',function($compile,$parse){
return {
restrict:'A',
compile: function (templateElement) {
templateElement[0].removeAttribute('recursive-model');
templateElement[0].removeAttribute('recursive-model-accessor');
return {
post: function (scope, element, attrs) {
function convertDotToMultiBracketNotation(dotNote) {
var ret = [];
var arrayOfDots = dotNote.split('.');
for (i = 0; i < arrayOfDots.length; i++) {
ret.push("['" + arrayOfDots[i] + "']");
}
return ret.join('');
}
if (attrs.recursiveModel && attrs.recursiveModelAccessor) {
var parsedModelAccessor = $parse(attrs.recursiveModelAccessor)
var modelAccessor = parsedModelAccessor(scope);
element[0].setAttribute('ng-model', attrs.recursiveModel + convertDotToMultiBracketNotation(modelAccessor));
var res = $compile(element[0])(scope);
return res;
}
}
}
},
}
}]);
Below is my directive, which is an utility (reusable).
var ChartNavigationDirective = { 'ChartNavigation': function (Items) {
return {
restrict: 'EA',
require: '?ngModel',
replace: false,
template: '<div class="chart-nav">' +
' </div>',
scope: {
options: '=ChartNavigation'
},
link: function (scope, element, attrs) {
scope.parameters = {
"prev_arrow": "#prev-arrow",
"next_arrow": "#next-arrow"
};
Items.getItems(scope.options.Source, scope.options.Type).then(function (d) {
scope.links = d.Items;
for (var link in scope.links) {
scope.links[link].Id = link;
}
var chartList = scope.links;
setNavigation(chartList);
});
var setNavigation = function (chartList) {
scope.totalCharts = chartList.length;
if (scope.totalCharts <= 0) {
$(scope.parameters.next_arrow).removeClass("on").addClass("off");
$(scope.parameters.prev_arrow).removeClass("on").addClass("off");
}
if (scope.totalCharts > 0) {
scope.currentItem = scope.links[0];
scope.currentIndex = Number(scope.currentItem.Id) + 1;
$(scope.parameters.prev_arrow).removeClass("off").addClass("on");
$(scope.parameters.next_arrow).removeClass("off").addClass("on");
}
updateNavigation();
};
var updateNavigation = function () {
if (scope.currentIndex <= 1) {
$(scope.parameters.prev_arrow).removeClass("on").addClass("off");
}
else {
$(scope.parameters.prev_arrow).removeClass("off").addClass("on");
}
if (scope.currentIndex >= scope.totalCharts) {
$(scope.parameters.next_arrow).removeClass("on").addClass("off");
}
else {
$(scope.parameters.next_arrow).removeClass("off").addClass("on");
}
};
scope.Previous = function () {
var currentIdx = scope.currentIndex;
var previousIdx = currentIdx - 1;
if (previousIdx >= 0) {
scope.currentItem = scope.links[previousIdx - 1];
scope.currentIndex = Number(scope.currentItem.Id) + 1;
}
updateNavigation();
};
scope.Next = function () {
var currentIdx = scope.currentIndex;
var nextIdx = currentIdx + 1;
if (nextIdx <= scope.totalCharts) {
scope.currentItem = scope.links[nextIdx - 1];
scope.currentIndex = Number(scope.currentItem.Id) + 1; ;
}
updateNavigation();
};
}
};
}
};
I would like to watch scope.currentItem from my controller. I did try using broadcast it's working fine. But I would like use watch instead. Here is my controller.
var myController = function ($scope) {
$scope.templateUrl = '/_layouts/AngularControls/myController/Views/Charts.html';
$scope.currentConfig = '';
// $rootScope.$on('curentConfig', function (event, args) {
// $scope.currentConfig = args.data;
// });
$scope.$watch("currentItem", function () {
alert(JSON.stringify($scope.currentItem));
});
}
Can anyone point out where I am doing mistake ? If there any suggestions Please let me know.
You're trying to watch a directive scope variable from a controller. That won't work because they are two different scopes (you are using an isolate scope in your directive).
You can watch from the directive controller (but I'm not sure that's what you want), or simply pass in the main controller scope variable into the directive, and have the directive manipulate it instead of its own scope variable. I guess using scope.$parent.currentItem is also possible, but definitely not recommended because of the directive reusability.
Broadcasting is also fine, not sure why you don't want to do it.
This is your structure:
controller scope
- directive scope
currentItem
And you're watching for this:
controller scope
watching for currentItem here, it does not exist
- directive scope
currentItem
I figured it my self. Using $rootScope in both directive an controller and watching for the $rootScope variable has resolved the issue.
In Direcive :
$rootScope.currentItem= myItem;
In Controller:
$scope.$watch("currentItem", function () {
$scope.currentConfig = $rootScope.currentItem;
});