angular star rating directive with no star filled - angularjs

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();
}
}
);
}
};
}
);

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 Number Picker Directive Expression is undefined

I've read a few questions having to do with this topic and cannot figure out what I'm missing in my own direcitve.
angular.module('app')
.directive('numberPicker', [NumberPicker]);
function NumberPicker () {
var getTarget, getType;
getTarget = function (e) { return angular.element(e.target); }
getType = function (e) { return getTarget(e).attr('direction-type'); }
return {
restrict: 'E',
replace: true,
require: 'ngModel',
scope: {
value: '='
},
template: '<div class="ui action input">' +
'<input value="{{value}}" type="text" />' +
'<button class="ui icon button" type="button" direction-type="up" ng-class="{disabled : canUp === false}">' +
'<i class="angle up icon" direction-type="up"></i>' +
'</button>' +
'<button class="ui icon button" type="button" direction-type="down" ng-class="{disabled : canDown === false}">' +
'<i class="angle down icon" direction-type="down"></i>' +
'</button>' +
'</div>',
controller: function ($scope) {},
link: function (scope, element, attrs, ctrl) {
scope.value = 0;
var options = {
min: 0,
max: 10,
step: 1
};
scope.$watch('value', function (newValue) {
scope.canDown = newValue > options.min;
scope.canUp = newValue < options.max;
if (ctrl.$viewValue != newValue) {
ctrl.$setViewValue(newValue);
}
});
var changeNumber = function (event) {
var type = getType(event);
if ('up' === type) {
if (scope.value >= options.max) {
return;
}
scope.value += options.step;
}
if ('down' === type) {
if (scope.value <= options.min) {
return;
}
scope.value -= options.step;
}
}
var btn = element.find('button');
var input = element.find('input');
btn.on('click', function (e) {
scope.$apply(function () {
changeNumber(e);
});
e.preventDefault();
});
input.on('change', function (e) {
scope.value = input[0].value;
scope.$apply();
})
scope.$on('$destroy', function () {
btn.off('touchstart touchend click')
});
}
}
}
The purpose of this was to create a number picker form element for Semantic UI. It was working perfectly a few days ago. And this error is so vague I can't even process where to start. Did I mention I am an Angular noob?
The error is :
Error: [$compile:nonassign] Expression 'undefined' used with directive 'numberPicker' is non-assignable!
How do you use the directive?
According to the definition you need to have both attributes "value" and "ng-model" set.
For example:
<number-picker value="xyz" ng-model="abc"></number-picker>
The error "Expression 'undefined' used with directive..." is normally thrown if one of the scope values is not set.

Angular star rating directive issue in Angular 1.4.7

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(); }
});
}
};
})

generating a variable-length dropdown based on input value in an AngularJS directive

I want to provide a page selection directive that generates "Page [ 1 ] of x". The number of pages in the dropdown is dependent upon values passed into the directive, so it can't be part of a static template. I'm having a difficult time figuring out how/where to generate the <select><option>...</select>.
I have tried, unsuccessfully, to do it via:
an $observe (and $watch) in link
a function added to $scope in controller, which returns $compile(markup)($scope) (This gives the error Error: [$parse:isecdom] Referencing DOM nodes in Angular expressions is disallowed!)
a sub-directive for the <select> element (The link $observer never seemed to get the recordCount updates, regardless of inherited or shared scope.)
ng-repeat in the template
Here's my mangled code, as it currently stands.
HTML
<x-pager
record-count="{{recordCount}}"
page-size="pageSize"
page-number="pageNumber"
set-page="selectPage(page)"
></x-pager>
JS
module.directive("pager", ["$compile",
function ($compile)
{
return {
template: "<div class='pager' ng-show='recordCount > pageSize'>\
{{recordCount}} results\
<button>« Prev</button>\
page <select>\
<option>#</option>\
</select> of {{calcPages()}}\
<button>Next »</button>\
</div>",
replace: true,
restrict: "E",
scope: {
recordCount: "#",
pageSize: "=",
pageNumber: "=",
setPage: "&"
},
link: function (scope, element, attrs)
{
/*
* We can't build the page selection dropdown until
* we know how many records we have. Register an
* observer to do this when recordCount changes.
*/
attrs.$observe("recordCount", function (recCnt)
{
var html;
var pages;
var i;
if (angular.isDefined(recCnt)) {
html = "<select>\n";
pages = Math.ceil(scope.recordCount / scope.pageSize);
for (i=1; i<=pages; i++) {
html += " <option value='" + i + "'>" + i + "</option>\n";
}
html += "</select>";
console.log("generatePageSelect html", html);
html = $compile(html)(scope);
// add the template content
// angular.element("select.page-selector").html(html);
// template: page <select class='page-selector'></select> of {{calcPages()}}\
}
});
},
controller: function ($scope)
{
$scope.calcPages = function ()
{
return Math.ceil($scope.recordCount / $scope.pageSize);
};
function genPagesArray ()
{
var pages = $scope.calcPages();
var i;
var pagesArray = [];
for (i=0; i<pages; i++) {
pagesArray.push(i);
}
return pagesArray;
}
$scope.pagesArray = genPagesArray();
console.log("$scope.pagesArray", $scope.pagesArray);
// template: page {{generatePageSelect()}} of {{calcPages()}}\
$scope.generatePageSelect = function ()
{
var html = "<select>\n";
var pages = $scope.calcPages();
var i;
for (i=1; i<=pages; i++) {
html += " <option value='" + i + "'>" + i + "</option>\n";
}
html += "</select>";
return $compile(html)($scope);
};
}
};
}
]);
To expand on my comment from earlier, here's a directive that does (most of) what you want it to do.
angular.module('Test', []).controller('TestCtrl', function($scope) {
$scope.pageSize = 10;
$scope.pageNumber = 1;
$scope.recordCount = 30;
}).directive("pager", function () {
return {
template: '<div class="pager" ng-show="recordCount > pageSize">\
{{recordCount}} results\
<button ng-click="pageNumber = pageNumber - 1" ng-disabled="pageNumber <= 1">« Prev</button>\
page <select ng-model="pageNumber" ng-options="i for i in pages"></select> of {{totalPages}}\
<button ng-click="pageNumber = pageNumber + 1" ng-disabled="pageNumber >= totalPages">Next »</button>\
</div>',
replace: true,
restrict: "E",
scope: {
recordCount: "#",
pageSize: "=",
pageNumber: "=",
setPage: "&"
},
link: function (scope, element, attrs) {
attrs.$observe("recordCount", function (count) {
if (angular.isDefined(count)) {
scope.recordCount = parseInt(count);
var i;
scope.totalPages = Math.ceil(scope.recordCount / scope.pageSize);
scope.pages = [];
for (i=1; i<=scope.totalPages; i++) {
scope.pages.push(i);
}
}
});
}
}
});
Plunkr here.

How to create a tree list in Angular with ajax loading of each new layer?

I'm new to Angular and trying to wrap my brain around how to do stuff.
I'm trying to create a list that's populated on page load by an ajax call to a REST API service, and the elements of that list would fire ajax calls to that same service when clicked on that populated sub-lists below those elements, and so on to depth n.
The initial population of the list is easy: the controller makes the ajax call, gets the JSON object, assigns it to scope and the DOM is handled with a ng-repeat directive. I'm having trouble with the subsequent loading of the sub-lists.
In jQuery, I would have a function tied to each appropriately classed element clicked upon via onClick which would get the required parameters, take the JSON output, parse it into HTML and append that HTML after the element which fired the event. This is direct DOM manipulation and therefore Angular heresy.
I've already looked at this question here, but I still don't quite understand how to implement something like this "the Angular way".
Help?
Edit: Solved this problem by making a recursive directive. Instructions from here: http://jsfiddle.net/alalonde/NZum5/light/.
Code:
var myApp = angular.module('myApp',[]);
myApp.directive('uiTree', function() {
return {
template: '<ul class="uiTree"><ui-tree-node ng-repeat="node in tree"></ui-tree-node></ul>',
replace: true,
transclude: true,
restrict: 'E',
scope: {
tree: '=ngModel',
attrNodeId: "#",
loadFn: '=',
expandTo: '=',
selectedId: '='
},
controller: function($scope, $element, $attrs) {
$scope.loadFnName = $attrs.loadFn;
// this seems like an egregious hack, but it is necessary for recursively-generated
// trees to have access to the loader function
if($scope.$parent.loadFn)
$scope.loadFn = $scope.$parent.loadFn;
// TODO expandTo shouldn't be two-way, currently we're copying it
if($scope.expandTo && $scope.expandTo.length) {
$scope.expansionNodes = angular.copy($scope.expandTo);
var arrExpandTo = $scope.expansionNodes.split(",");
$scope.nextExpandTo = arrExpandTo.shift();
$scope.expansionNodes = arrExpandTo.join(",");
}
}
};
})
.directive('uiTreeNode', ['$compile', '$timeout', function($compile, $timeout) {
return {
restrict: 'E',
replace: true,
template: '<li>' +
'<div class="node" data-node-id="{{ nodeId() }}">' +
'<a class="icon" ng-click="toggleNode(nodeId())""></a>' +
'<a ng-hide="selectedId" ng-href="#/assets/{{ nodeId() }}">{{ node.name }}</a>' +
'<span ng-show="selectedId" ng-class="css()" ng-click="setSelected(node)">' +
'{{ node.name }}</span>' +
'</div>' +
'</li>',
link: function(scope, elm, attrs) {
scope.nodeId = function(node) {
var localNode = node || scope.node;
return localNode[scope.attrNodeId];
};
scope.toggleNode = function(nodeId) {
var isVisible = elm.children(".uiTree:visible").length > 0;
var childrenTree = elm.children(".uiTree");
if(isVisible) {
scope.$emit('nodeCollapsed', nodeId);
} else if(nodeId) {
scope.$emit('nodeExpanded', nodeId);
}
if(!isVisible && scope.loadFn && childrenTree.length === 0) {
// load the children asynchronously
var callback = function(arrChildren) {
scope.node.children = arrChildren;
scope.appendChildren();
elm.find("a.icon i").show();
elm.find("a.icon img").remove();
scope.toggleNode(); // show it
};
var promiseOrNodes = scope.loadFn(nodeId, callback);
if(promiseOrNodes && promiseOrNodes.then) {
promiseOrNodes.then(callback);
} else {
$timeout(function() {
callback(promiseOrNodes);
}, 0);
}
elm.find("a.icon i").hide();
var imgUrl = "http://www.efsa.europa.eu/efsa_rep/repository/images/ajax-loader.gif";
elm.find("a.icon").append('<img src="' + imgUrl + '" width="18" height="18">');
} else {
childrenTree.toggle(!isVisible);
elm.find("a.icon i").toggleClass("icon-chevron-right");
elm.find("a.icon i").toggleClass("icon-chevron-down");
}
};
scope.appendChildren = function() {
// Add children by $compiling and doing a new ui-tree directive
// We need the load-fn attribute in there if it has been provided
var childrenHtml = '<ui-tree ng-model="node.children" attr-node-id="' +
scope.attrNodeId + '"';
if(scope.loadFn) {
childrenHtml += ' load-fn="' + scope.loadFnName + '"';
}
// pass along all the variables
if(scope.expansionNodes) {
childrenHtml += ' expand-to="expansionNodes"';
}
if(scope.selectedId) {
childrenHtml += ' selected-id="selectedId"';
}
childrenHtml += ' style="display: none"></ui-tree>';
return elm.append($compile(childrenHtml)(scope));
};
scope.css = function() {
return {
nodeLabel: true,
selected: scope.selectedId && scope.nodeId() === scope.selectedId
};
};
// emit an event up the scope. Then, from the scope above this tree, a "selectNode"
// event is expected to be broadcasted downwards to each node in the tree.
// TODO this needs to be re-thought such that the controller doesn't need to manually
// broadcast "selectNode" from outside of the directive scope.
scope.setSelected = function(node) {
scope.$emit("nodeSelected", node);
};
scope.$on("selectNode", function(event, node) {
scope.selectedId = scope.nodeId(node);
});
if(scope.node.hasChildren) {
elm.find("a.icon").append('<i class="icon-chevron-right"></i>');
}
if(scope.nextExpandTo && scope.nodeId() == parseInt(scope.nextExpandTo, 10)) {
scope.toggleNode(scope.nodeId());
}
}
};
}]);
function MyCtrl($scope, $timeout) {
$scope.assets = [
{ assetId: 1, name: "parent 1", hasChildren: true},
{ assetId: 2, name: "parent 2", hasChildren: false}
];
$scope.selected = {name: "child 111"};
$scope.hierarchy = "1,11";
$scope.loadChildren = function(nodeId) {
return [
{assetId: parseInt(nodeId + "1"), name: "child " + nodeId + "1", hasChildren: true},
{assetId: parseInt(nodeId + "2"), name: "child " + nodeId + "2"}
];
}
$scope.$on("nodeSelected", function(event, node) {
$scope.selected = node;
$scope.$broadcast("selectNode", node);
});
}
Template:
<div ng-controller="MyCtrl">
<ui-tree ng-model="assets" load-fn="loadChildren" expand-to="hierarchy" selected-id="111" attr-node-id="assetId"></ui-tree>
<div>selected: {{ selected.name }}</div>
</div>
Here's a solution which I prototyped for my own use.
https://embed.plnkr.co/PYVpWYrduDpLlsvto0wR/
Link updated

Resources