Angular star rating directive issue in Angular 1.4.7 - angularjs

I have a little problem with star-rating directive.
Here is star-rating directive code and it is working perfectly in Angular 1.3.16 version but NOT working in Angular 1.4.7 version.
Acutually, It is still working in Angular 1.4.7 version, but keep reporting error "TypeError: scope.onRatingSelected is not a function"
.directive("starRating", function() {
return {
restrict : "EA",
template : "<ul class='rating' ng-class='{readonly: readonly}'>" +
" <li ng-repeat='star in stars' ng-class='star' ng-click='toggle($index)'>" +
" <i class='fa fa-star'></i>" + //&#9733
" </li>" +
"</ul>",
scope : {
ratingValue : "=ngModel",
max : "=?", //optional: default is 5
onRatingSelected : "&?",
readonly: "=?"
},
link : function(scope, elem, attrs) {
if (scope.max == undefined) { scope.max = 5; }
function updateStars() {
scope.stars = [];
for (var i = 0; i < scope.max; i++) {
scope.stars.push({
filled : (i < scope.ratingValue.rating)
});
}
};
scope.toggle = function(index) {
if (scope.readonly == undefined || scope.readonly == false){
scope.ratingValue.rating = index + 1;
scope.onRatingSelected({
rating: index + 1
});
scope.onRatingSelected()(index + 1);
}
};
scope.$watch("ratingValue.rating", function(oldVal, newVal) {
if (newVal) { updateStars(); }
});
}
};
})

Related

Angular js directive call with controller data binding

View
<star-rating ratingValue="ratings" readonly="true"></star-rating>
<div><strong>Rating 1:</strong>{{ratings}}</div>
Controller
app.controller('ProductCtrl', function ($scope, $http, $ionicSlideBoxDelegate, $resource, $state, $stateParams, $rootScope, $compile, $ionicPopup, $location, $sce) {
$scope.ratings = 0;
this.isReadonly = true;
this.rateFunction = function(rating) {
console.log('Rating selected: ' + rating);
};
$http.defaults.useXDomain = true;
$http.get(web_service + 'product/get', {
params: {id: $stateParams.ProductId},
headers: {}
}).success(function (response) {
$scope.product = response.product;
console.log(response.product);
$ionicSlideBoxDelegate.update();
$scope.ratings = response.product.rating;
this.rateFunction = function(rating) {
console.log('Rating selected: ' + rating);
};
})
.error(function (err) {
alert("ERROR");
});
}).directive('starRating', starRating);
Directive
function starRating() {
return {
restrict: 'EA',
template:
'<ul class="star-rating" ng-class="{readonly: readonly}">' +
' <li ng-repeat="star in stars" class="star" ng-class="{filled: star.filled}" ng-click="toggle($index)">' +
' <i class="ion-ios-star"></i>' + // or &#9733
' </li>' +
'</ul>',
scope: {
ratingValue: '=?',
max: '=?', // optional (default is 5)
onRatingSelect: '&?',
readonly: '=?'
},
link: function(scope, element, attributes) {
if (scope.max == undefined) {
scope.max = 5;
}
scope.$observe('ratingValue', function(value){
console.log(value);
//$scope.nav.selection = value
});
function updateStars() {
scope.stars = [];
for (var i = 0; i < scope.max; i++) {
scope.stars.push({
filled: i < scope.ratingValue
});
}
};
scope.toggle = function(index) {
if (scope.readonly == undefined || scope.readonly === false){
scope.ratingValue = index + 1;
scope.onRatingSelect({
rating: index + 1
});
}
};
scope.$watch('ratingValue', function(oldValue, newValue) {
if (newValue) {
updateStars();
}
});
}
};
}
When initial value of $scope.ratings is number like 1,2,3 then starts prints but the value retrieved by ajax request is not getting added to directive and in directive values shows "undefined" and no starts getting printed.
The tag below directive in view code gives retrieved value referring to this Codepen: http://codepen.io/TepigMC/pen/FIdHb
What I am missing in directive?
use ng-if so that the directive is called after you have $scope.ratings.
<star-rating ng-if="ratings" ratingValue="ratings" readonly="true"></star-rating>

angular star rating directive with no star filled

How do I modify the angular star rating directive to display no star filled (by default) on load?
Following is the directive for the same:
responseController.directive('starRating', function(){
return {
restrict: 'EA',
template:
'<ul class="star-rating" ng-class="{readonly: readonly}">' +
' <li ng-repeat="star in stars" class="star" ng-class="{filled: star.filled}" ng-click="toggle($index)">' +
' <i class="fa fa-star"></i>' +
' </li>' +
'</ul>',
scope: {
ratingValue: '=ngModel',
max: '=?',
onRatingSelect: '&?',
readonly: '=?'
},
link: function(scope, element, attributes) {
if (scope.max == undefined) {
scope.max = 5;
}
function updateStars() {
scope.stars = [];
for (var i = 0; i < scope.max; i++) {
scope.stars.push({
filled: i < scope.ratingValue
});
}
};
scope.toggle = function(index) {
if (scope.readonly == undefined || scope.readonly === false) {
scope.ratingValue = index + 1;
}
};
scope.$watch('ratingValue', function(oldValue, newValue) {
if (newValue) {
updateStars();
}
});
}
};
});
You can add updateStars(); before $scope.watch(...). It will render not filled stars. Afterwards if the value of ratingValue is not undefined, the watcher will update stars.
app.directive('starRating', function () {
return {
//This template is used to display the star UX in repeated form.
template: '<ul class="starRating">' + '<li ng-repeat="star in stars" ng-class="star" ng-click="toggleFunck($index)">' + '\u2605' + '</li>' + '</ul>',
scope: {
ratingValue: '= ',
max: '=',
onStarRating: '&'
},
link: function (scope, elem, attrs) {
//This method is used to update the rating run time.
var updateRating = function () {
//This is global level collection.
scope.stars = [];
//Loop called with the help of data-max directive input and push the stars count.
for (var i = 0; i < scope.max; i++) {
scope.stars.push({
filled: i < scope.ratingValue
});
}
};
//This is used to toggle the rating stars.
scope.toggleFunck = function (index) {
alert(index);
//This is used to count the default start rating and sum the number of input index.
scope.ratingValue = index + 1;
scope.onStarRating({
rating: index + 1
});
};
updateRating();
//This is used to watch activity on scope, if any changes on star rating is call automatically and update the stars.
scope.$watch('ratingValue',
function (newV, oldV) {
if (newV) {
updateRating();
}
}
);
}
};
}
);

angularjs directive is not rendered in browser

I am using this timepicker-popup directive from github:
https://github.com/mytechtip/timepickerpop
I am using it in my html:
<timepicker-pop input-time="activeStep.timeTest"
class="input-group" show-meridian='showMeridian'>
</timepicker-pop>
I do not understand, why the timepicker is not even initialized nor rendered.
I am new to the directive stuff but when the directive is named: 'timepickerPop' how can it be that the demo here: https://github.com/mytechtip/timepickerpop/blob/master/demo/timepickerpop-demo.html
uses in the html this format ????
What do I wrong? I get no errors in my browser console!
/**
* Anularjs Module for pop up timepicker
*/
angular.module('timepickerPop', [ 'ui.bootstrap' ])
.factory('timepickerState', function() {
var pickers = [];
return {
addPicker: function(picker) {
pickers.push(picker);
},
closeAll: function() {
for (var i=0; i<pickers.length; i++) {
pickers[i].close();
}
}
};
})
.directive("timeFormat", function($filter) {
return {
restrict : 'A',
require : 'ngModel',
scope : {
showMeridian : '=',
},
link : function(scope, element, attrs, ngModel) {
var parseTime = function(viewValue) {
if (!viewValue) {
ngModel.$setValidity('time', true);
return null;
} else if (angular.isDate(viewValue) && !isNaN(viewValue)) {
ngModel.$setValidity('time', true);
return viewValue;
} else if (angular.isString(viewValue)) {
var timeRegex = /^(0?[0-9]|1[0-2]):[0-5][0-9] ?[a|p]m$/i;
if (!scope.showMeridian) {
timeRegex = /^([01]?[0-9]|2[0-3]):[0-5][0-9]$/;
}
if (!timeRegex.test(viewValue)) {
ngModel.$setValidity('time', false);
return undefined;
} else {
ngModel.$setValidity('time', true);
var date = new Date();
var sp = viewValue.split(":");
var apm = sp[1].match(/[a|p]m/i);
if (apm) {
sp[1] = sp[1].replace(/[a|p]m/i, '');
if (apm[0].toLowerCase() == 'pm') {
sp[0] = sp[0] + 12;
}
}
date.setHours(sp[0], sp[1]);
return date;
};
} else {
ngModel.$setValidity('time', false);
return undefined;
};
};
ngModel.$parsers.push(parseTime);
var showTime = function(data) {
parseTime(data);
var timeFormat = (!scope.showMeridian) ? "HH:mm" : "hh:mm a";
return $filter('date')(data, timeFormat);
};
ngModel.$formatters.push(showTime);
scope.$watch('showMeridian', function(value) {
var myTime = ngModel.$modelValue;
if (myTime) {
element.val(showTime(myTime));
}
});
}
};
})
.directive('timepickerPop', function($document, timepickerState) {
return {
restrict : 'E',
transclude : false,
scope : {
inputTime : "=",
showMeridian : "=",
disabled : "="
},
controller : function($scope, $element) {
$scope.isOpen = false;
$scope.disabledInt = angular.isUndefined($scope.disabled)? false : $scope.disabled;
$scope.toggle = function() {
if ($scope.isOpen) {
$scope.close();
} else {
$scope.open();
}
};
},
link : function(scope, element, attrs) {
var picker = {
open : function () {
timepickerState.closeAll();
scope.isOpen = true;
},
close: function () {
scope.isOpen = false;
}
}
timepickerState.addPicker(picker);
scope.open = picker.open;
scope.close = picker.close;
scope.$watch("disabled", function(value) {
scope.disabledInt = angular.isUndefined(scope.disabled)? false : scope.disabled;
});
scope.$watch("inputTime", function(value) {
if (!scope.inputTime) {
element.addClass('has-error');
} else {
element.removeClass('has-error');
}
});
element.bind('click', function(event) {
event.preventDefault();
event.stopPropagation();
});
$document.bind('click', function(event) {
scope.$apply(function() {
scope.isOpen = false;
});
});
},
template : "<input type='text' class='form-control' ng-model='inputTime' ng-disabled='disabledInt' time-format show-meridian='showMeridian' ng-focus='open()' />"
+ " <div class='input-group-btn' ng-class='{open:isOpen}'> "
+ " <button type='button' ng-disabled='disabledInt' class='btn btn-default ' ng-class=\"{'btn-primary':isOpen}\" data-toggle='dropdown' ng-click='toggle()'> "
+ " <i class='glyphicon glyphicon-time'></i></button> "
+ " <div class='dropdown-menu pull-right'> "
+ " <timepicker ng-model='inputTime' show-meridian='showMeridian'></timepicker> "
+ " </div> " + " </div>"
};
});

AngularJS $watch newValue is undefined

I am trying to make a alert service with a directive. It is in the directive part I have some trouble. My have a directive that looks like this:
angular.module('alertModule').directive('ffAlert', function() {
return {
templateUrl: 'components/alert/ff-alert-directive.html',
controller: ['$scope','alertService',function($scope,alertService) {
$scope.alerts = alertService;
}],
link: function (scope, elem, attrs, ctrl) {
scope.$watch(scope.alerts, function (newValue, oldValue) {
console.log("alerts is now:",scope.alerts,oldValue, newValue);
for(var i = oldValue.list.length; i < newValue.list.length; i++) {
scope.alerts.list[i].isVisible = true;
if (scope.alerts.list[i].timeout > 0) {
$timeout(function (){
scope.alerts.list[i].isVisible = false;
}, scope.alerts.list[i].timeout);
}
}
}, true);
}
}
});
The reason for the for-loop is to attach a timeout for the alerts that has this specified.
I will also include the directive-template:
<div class="row">
<div class="col-sm-1"></div>
<div class="col-sm-10">
<div alert ng-repeat="alert in alerts.list" type="{{alert.type}}" ng-show="alert.isVisible" close="alerts.close(alert.id)">{{alert.msg}}</div>
</div>
<div class="col-sm-1"></div>
</div>
When I run this, I get this error in the console:
TypeError: Cannot read property 'list' of undefined
at Object.fn (http://localhost:9000/components/alert/ff-alert-directive.js:10:29)
10:29 is the dot in "oldValue.list" in the for-loop. Any idea what I am doing wrong?
Edit: I am adding the alertService-code (it is a service I use to keep track of all the alerts in my app):
angular.module('alertModule').factory('alertService', function() {
var alerts = {};
var id = 1;
alerts.list = [];
alerts.add = function(alert) {
alert.id = id;
alerts.list.push(alert);
alert.id += 1;
console.log("alertService.add: ",alert);
return alert.id;
};
alerts.add({type: "info", msg:"Dette er til info...", timeout: 1000});
alerts.addServerError = function(error) {
var id = alerts.add({type: "warning", msg: "Errormessage from server: " + error.description});
// console.log("alertService: Server Error: ", error);
return id;
};
alerts.close = function(id) {
for(var index = 0; index<alerts.list.length; index += 1) {
console.log("alert:",index,alerts.list[index].id);
if (alerts.list[index].id == id) {
console.log("Heey");
alerts.list.splice(index, 1);
}
}
};
alerts.closeAll = function() {
alerts.list = [];
};
return alerts;
});
try like this , angular fires your watcher at the first time when your directive initialized
angular.module('alertModule').directive('ffAlert', function() {
return {
templateUrl: 'components/alert/ff-alert-directive.html',
controller: ['$scope','alertService',function($scope,alertService) {
$scope.alerts = alertService;
}],
link: function (scope, elem, attrs, ctrl) {
scope.$watch(scope.alerts, function (newValue, oldValue) {
if(newValue === oldValue) return;
console.log("alerts is now:",scope.alerts,oldValue, newValue);
for(var i = oldValue.list.length; i < newValue.list.length; i++) {
scope.alerts.list[i].isVisible = true;
if (scope.alerts.list[i].timeout > 0) {
$timeout(function (){
scope.alerts.list[i].isVisible = false;
}, scope.alerts.list[i].timeout);
}
}
}, true);
}
}
});
if you have jQuery available, try this
if(jQuery.isEmptyObject(newValue)){
return;
}
inside scope.$watch

Angularjs, select2 with dynamic tags and onclick

I use angularjs with "ui_select2" directive. Select2 draws new tags with formatting function, there are "" elements with "ng-click" attribute. How to tell angularjs about new DOM elements? Otherwise new "ng-clicks" wont work.
HTML:
<input type="text" name="contact_ids" ui-select2="unit.participantsOptions" ng-model="unit.contactIds" />
JS (angular controller):
anyFunction = function(id) {
console.log(id)
}
formatContactSelection = function(state) {
return "<a class=\"select2-search-choice-favorite\" tabindex=\"-1\" href=\"\" ng-click=\"anyFunction(state.id)\"></a>"
}
return $scope.unit.participantsOptions = {
tags: [],
formatSelection: formatContactSelection,
escapeMarkup: function(m) {
return m
},
ajax: {
url: '/contacts/search',
quietMillis: 100,
data: function(term, page) {
return {
term: term,
limit: 20,
page: page
}
},
results: function(data, page) {
return {
results: data,
more: (page * 10) < data.total
}
}
}
}
The problem is that select2 creates DOM elements, that not yet discovered by angularjs, I read that new DOM elements need to be appended to some element with using angularjs $compile function, but I cannot use it in controller.
I found a solution - I created the directive, that watches for changes in ngModel and apply it on the element, that has already ui_select2 directive. "uiRequiredTags" implements custom behavior I need for my select2 tag choices. The solution is to watch changes in ngModel attribute.
angular.module("myAppModule", []).directive("uiRequiredTags", function() {
return {
restrict: 'A',
require: "ngModel",
link: function(scope, el, attrs) {
var opts;
opts = scope.$eval("{" + attrs.uiRequiredTags + "}");
return scope.$watch(attrs.ngModel, function(val) {
var $requireLink;
$requireLink = el.parent().find(opts.path);
$requireLink.off('click');
$requireLink.on('click', function() {
var id, n, tagIds;
id = "" + ($(this).data('requiredTagId'));
if (opts.removeLinkPath && opts.innerContainer) {
$(this).parents(opts.innerContainer).find(opts.removeLinkPath).data('requiredTagId', id);
}
tagIds = scope.$eval(opts.model).split(',');
n = tagIds.indexOf(id);
if (n < 0) {
tagIds.push(id);
} else {
tagIds.splice(n, 1);
}
scope.$eval("" + opts.model + " = '" + tagIds + "'");
scope.$apply();
return $(this).toggleClass('active');
});
});
}
};

Resources