is it possible to have access on link or data of a directive in a service?
my service:
myApp.service('myService', function () {
this.analyze = function (data) {
for (var i = 0; i < data.length; i++) {
if( data[i].x == ) //<--- here i want to compare data with data in directive
}
}
});
my directive:
myApp.directive('dateInfo', function ($interval) {
return {
restrict: 'A',
scope: {
},
//templateUrl: '123/Scripts/directives/html/dateInfo.html',
link: function (scope, element, attrs) {
element.html('<div class="panel panel-primary">' +
'<div class="panel-heading">test</div>' +
'<div class="panel-body"></div></div>');
$interval(function () {
var rect = element[0].getBoundingClientRect();
x = rect.left;
y = rect.top;
w = rect.right - rect.left;
h = rect.bottom - rect.top;
}, 3000);
}
};
});
I want access to "element"
How is it possible?
thix in advance
Related
This is my current code and feels like it's not very efficient and would perhaps be better if Timer/Timeout is used. However, I'm lost as to how to go about it.
Could someone help? Not very efficient with javascript. My JS guy is on leave.
app.directive('ScrollBar', function () {
return {
restrict: 'A',
scope: {},
link: function postLink(scope, elem, attrs) {
jQuery(window).scroll(function(){
var SBar = jQuery("#ScrollStop").offset();
var screenPosition = jQuery(document).scrollTop() + window.innerHeight;
if (screenPosition < SBar.top) {
jQuery(".ScrollClass").fadeIn();
}
if (screenPosition >= SBar.top) {
jQuery( ".ScrollClass" ).fadeOut();
}
});
}
};
})
It would be good if you differentiate the scroll direction by binding scroll function ,I have a fiddle,hope it helps.
http://jsfiddle.net/kavinhuh/17hca7wa/
myApp.directive('scrolly', function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var lastScrollTop = 0;
var raw = element[0];
console.log('loading directive');
element.bind('scroll', function () {
console.log('in scroll');
if(raw.scrollTop < lastScrollTop)
{
alert("scroll up");
lastScrollTop = raw.scrollTop;
}
else{
lastScrollTop = raw.scrollTop;
}
if (raw.scrollTop + raw.offsetHeight > raw.scrollHeight) {
scope.$apply(attrs.scrolly);
}
});
}
};
});
I build an application with interactive SVG map with angular.js
Whole app controlled by controller, svg wrapped in a directive, and every <path> in svg is a partial directive too.
When user clicked on a wall, <path> id is memorized in a custom service and next when user select a color, I need to fill this wall with that color.
The problem is that I can't access directive within a controller. I also cant share a scope with a directive because i have multiple nested directives in my controller and want to access them by id.
Maybe there is a design mistake? how can I achieve this workflow?
There is my code for controller, service and directives
ColorPicker Controller
app.controller("ColorPicker", function($scope, $timeout, appConfig, ColorService) {
$scope.colors = [];
$scope.currentColorSet = 0;
$scope.svgTemplateUrl = appConfig.arSvg[$scope.$parent.roomType.id][$scope.$parent.roomStyle.id] + 'over.svg';
$scope.maxColorSet = Math.max.apply(Math, jsondata.roomtype[$scope.$parent.roomType.id].data.roomstyle[$scope.$parent.roomStyle.id].colors.map(function(color) {
return color.color.group;
}));
$scope.sliderConfig = {
min: 0,
max: $scope.maxColorSet,
step: 1
}
$scope.emptyColors = ( ColorService.getColors().length === 0 );
$scope.currentProps = {};
$scope.applyColor = function(color) {
console.log($scope.currentProps);
//**here I need to set fill with selected color to selected directive**
}
$scope.prevColorSet = function(){
if($scope.currentColorSet > 0) {
$scope.currentColorSet--;
generateColorSet($scope.currentColorSet);
}
}
$scope.nextColorSet = function(){
if($scope.currentColorSet < $scope.maxColorSet) {
$scope.currentColorSet++;
generateColorSet($scope.currentColorSet);
}
}
generateColorSet = function(setIndex){
$scope.colors = [];
for(var i = 0; i < jsondata.roomtype[$scope.$parent.roomType.id].data.roomstyle[$scope.$parent.roomStyle.id].colors.length; i++){
if(jsondata.roomtype[$scope.$parent.roomType.id].data.roomstyle[$scope.$parent.roomStyle.id].colors[i].color.group == setIndex) {
$scope.colors.push(jsondata.roomtype[$scope.$parent.roomType.id].data.roomstyle[$scope.$parent.roomStyle.id].colors[i].color)
}
}
}
generateColorSet($scope.currentColorSet);
$scope.$watch("currentColorSet", function(newValue, oldValue) {
generateColorSet($scope.currentColorSet);
});
});
Directive for whole SVG
app.directive('svgmap', ['$compile', function ($compile) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var svg = $(element).find('svg');
svg.removeAttr('xmlns:a');
svg.attr('width', '869px');
svg.attr('height', '489px');
element.append(svg);
var regions = element[0].querySelectorAll('path');
angular.forEach(regions, function (path, key) {
var regionElement = angular.element(path);
regionElement.attr("region", "");
$compile(regionElement)(scope);
});
},
}
}]);
Directive for region (wall)
app.directive('region', ['$compile','ColorService','$timeout', function ($compile, ColorService, $timeout) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.properties = {
elementId : element.attr("id"),
stroke: '',
fill: '#fff'
};
scope.regionClick = function () {
scope.currentProps = scope.properties;
if(ColorService.selectedRegion != scope.properties.elementId) {
scope.properties.stroke = '#e5514e';
ColorService.selectedRegion = scope.properties.elementId;
} else {
scope.properties.stroke = '';
ColorService.selectedRegion = '';
}
console.log('click: ' + scope.properties.elementId + ' : ' + scope.properties.stroke);
};
scope.setStroke = function () {
scope.stroke = '#e5514e';
};
scope.removeStroke = function() {
if(ColorService.selectedRegion != scope.properties.elementId) {
scope.properties.stroke = '';
}
//console.log('enter: ' + scope.elementId + ' : ' + scope.stroke);
};
scope.removeColor = function() {
console.log('removed');
scope.properties.fill = '#fff';
ColorService.remove(scope.properties.elementId);
};
ColorService.getColors().forEach(function(item) {
if(item.id === scope.properties.elementId) {
scope.properties.fill = item.info.val;
}
});
element.attr("ng-click", "regionClick()");
element.attr("ng-attr-stroke", "{{properties.stroke}}");
element.attr("ng-attr-fill", "{{properties.fill}}");
element.attr("ng-mouseenter", "setStroke()");
element.attr("ng-mouseleave", "removeStroke()");
element.attr("ng-right-click", "removeColor()");
element.after('<rect width="300" height="100" style="fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)" />');
element.removeAttr("region");
$compile(element)(scope);
}
}
}]);
I am using a directive for star rating. But the template the is loaded before data is loaded from HTTP. So i want to reload directive template after HTTP request is successful.
HTML
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js" type="text/javascript"></script>
</head><body>
<div ng-app="myapp" ng-controller="movieCtrl">
<div star-rating rating="starRating" read-only="false" max-rating="10" click="click(param)" mouse-hover="mouseHover(param)"
mouse-leave="mouseLeave(param)"></div>
</div></body></html>
JS
var app = angular.module('myapp', []);
app.controller("movieCtrl", function($scope, $http) {
$scope.starRating = 0;
$scope.hoverRating = 0;
$scope.mouseHover = function(param) {
$scope.hoverRating1 = param;
};
$scope.mouseLeave = function(param) {
$scope.hoverRating1 = param + '*';
};
//problem here
//actual data coming via http
//when value is changed i want to re-render below directive template
setTimeout(function() {
$scope.starRating = 5
}, 1000);
});
app.directive('starRating', function() {
return {
scope: {
rating: '=',
maxRating: '#',
readOnly: '#',
click: "&",
mouseHover: "&",
mouseLeave: "&"
},
restrict: 'EA',
template: "<div style='display: inline-block; margin: 0px; padding: 0px; cursor:pointer;' ng-repeat='idx in maxRatings track by $index'> \
<img ng-src='{{((hoverValue + _rating) <= $index) && \"http://www.codeproject.com/script/ratings/images/star-empty-lg.png\" || \"http://www.codeproject.com/script/ratings/images/star-fill-lg.png\"}}' \
ng-Click='isolatedClick($index + 1)' \
ng-mouseenter='isolatedMouseHover($index + 1)' \
ng-mouseleave='isolatedMouseLeave($index + 1)'></img> \
</div>",
compile: function(element, attrs) {
if (!attrs.maxRating || (Number(attrs.maxRating) <= 0)) {
attrs.maxRating = '5';
};
},
controller: function($scope, $element, $attrs) {
$scope.maxRatings = [];
for (var i = 1; i <= $scope.maxRating; i++) {
$scope.maxRatings.push({});
};
$scope._rating = $scope.rating;
$scope.isolatedClick = function(param) {
if ($scope.readOnly == 'true') return;
$scope.rating = $scope._rating = param;
$scope.hoverValue = 0;
$scope.click({
param: param
});
};
$scope.isolatedMouseHover = function(param) {
if ($scope.readOnly == 'true') return;
$scope._rating = 0;
$scope.hoverValue = param;
$scope.mouseHover({
param: param
});
};
$scope.isolatedMouseLeave = function(param) {
if ($scope.readOnly == 'true') return;
$scope._rating = $scope.rating;
$scope.hoverValue = 0;
$scope.mouseLeave({
param: param
});
};
}
};
});
See Codepen for more info.
Here is a simple rating directive which uses stars, note that the logic is in the link function, rather than the controller.
function starRating() {
return {
restrict: 'EA',
template:
'<ul class="star-rating" ng-class="{readonly: readonly}">' +
// see ng-repeat here? this will update when scope.stars is updated
' <li ng-repeat="star in stars" class="star" ng-class="{filled: star.filled}" ng-click="toggle($index)">' +
' <i class="fa fa-star"></i>' + // or ★
' </li>' +
'</ul>',
scope: {
ratingValue: '=ngModel',
max: '=?', // optional (default is 5)
onRatingSelect: '&?', // callback
readonly: '=?' // set whether this should be changeable or not
},
link: function(scope, element, attributes) {
if (scope.max == undefined) {
scope.max = 5;
}
function updateStars() { // update to rating value
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();
}
});
}
};
}
Use $scope.$apply() on setTimeout function and your code will work fine
also i have made simple modification to your code .. check here
i created a service to share data b/n controllers
added some $watch function to detect value change
var app = angular.module('myapp', []);
app.controller("movieCtrl", function($scope, $http, share) {
$scope.starRating = 0;
$scope.hoverRating = 0;
$scope.mouseHover = function(param) {
$scope.hoverRating1 = param;
};
$scope.mouseLeave = function(param) {
$scope.hoverRating1 = param + '*';
};
$scope.$watch('starRating', function() {
share.rating = $scope.starRating
});
setTimeout(function() {
console.log('timeout set');
$scope.starRating = 5;
$scope.$apply();
}, 1000);
});
app.factory('share', function() {
var obj = {
rating: 0
}
return obj;
});
app.directive('starRating', function() {
return {
scope: {
rating: '=',
maxRating: '#',
readOnly: '#',
click: "&",
mouseHover: "&",
mouseLeave: "&"
},
restrict: 'EA',
templateUrl: "star1.html",
compile: function(element, attrs) {
if (!attrs.maxRating || (Number(attrs.maxRating) <= 0)) {
attrs.maxRating = '5';
};
},
controller: function($scope, $element, $attrs, share) {
$scope.maxRatings = [];
$scope.rating = share.rating;
$scope.$watch('rating', function() {
$scope._rating = share.rating;
});
for (var i = 1; i <= $scope.maxRating; i++) {
$scope.maxRatings.push({});
};
$scope._rating = share.rating;
$scope.isolatedClick = function(param) {
if ($scope.readOnly == 'true') return;
$scope.rating = $scope._rating = param;
$scope.hoverValue = 0;
$scope.click({
param: param
});
};
$scope.isolatedMouseHover = function(param) {
if ($scope.readOnly == 'true') return;
$scope._rating = 0;
$scope.hoverValue = param;
$scope.mouseHover({
param: param
});
};
$scope.isolatedMouseLeave = function(param) {
if ($scope.readOnly == 'true') return;
$scope._rating = $scope.rating;
$scope.hoverValue = 0;
$scope.mouseLeave({
param: param
});
};
}
};
});
.directive('scrollWatch', function($rootScope) {
return function(scope, elem, attr) {
var start = 0;
var threshold = 150;
elem.bind('scroll', function(e) {
if(e.detail.scrollTop - start > threshold) {
$rootScope.slideHeader = true;
} else {
$rootScope.slideHeader = false;
}
console.log('e'+ e.detail.scrollTop);
if ($rootScope.slideHeaderPrevious >= e.detail.scrollTop - start) {
$rootScope.slideHeader = false;
}
console.log($rootScope.slideHeader);
$rootScope.slideHeaderPrevious = e.detail.scrollTop - start;
$rootScope.$apply();
});
};
})
This give me e.detail.scrollTop
But I want e.detail.scrollBottom. I cannot get about this.
help me
Simple! basic formula for scrollbottom is
$(document).height() - $(#element).scrollTop() - $(#element).height();
since i do not have your entire code i can not give exact solution. But with some trail you can get it. Just simply follow the formula.
that is
document height - current element position - current element window
height
and your code will be like this.
.directive('scrollWatch', function($rootScope) {
return function(scope, elem, attr) {
var start = 0;
var threshold = 150;
elem.bind('scroll', function(e) {
if(e.detail.scrollTop - start > threshold) {
$rootScope.slideHeader = true;
} else {
$rootScope.slideHeader = false;
}
console.log('e'+ e.detail.scrollTop);
if ($rootScope.slideHeaderPrevious >= e.detail.scrollTop - start) {
$rootScope.slideHeader = false;
}
console.log($rootScope.slideHeader);
$rootScope.slideHeaderPrevious = $(document).height()-e.detail.scrollTop - e.detail.height();
$rootScope.$apply();
});
};
})
I've created a numeric stepper for use with CSS styles, but am having issues getting it to fire the ng-change when you type in it manually.
I created a log on the plunker to illustrate when the callback is being fired. As you can see from playing with it, it works fine when you click on the stepper arrows, but not when you type in the box directly.
Current Code Example: Plunker
HTML:
<div class="stepper-container">
<input type="text" ng-model="ngModel">
<button class="stepper-up fa fa-chevron-up" ng-click="increment()"></button>
<button class="stepper-down fa fa-chevron-down" ng-click="decrement()"></button>
</div>
JavaScript:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.myModel = null;
$scope.log = [];
$scope.someMethod = function () {
$scope.log.push('Change event on ' + $scope.myModel);
}
});
app.directive('numericStepper', function () {
return {
restrict: 'EA',
require: 'ngModel',
scope: {
ngModel: '='
},
replace: true,
templateUrl: 'numeric-stepper.html',
link: function (scope, element, attrs, ngModelCtrl) {
console.log('NumericStepper::link', ngModelCtrl.$viewValue);
var sizingUnit = null;
var css3Lengths = [
// Percentage
'%',
// Font Relative
'em', 'ex', 'ch', 'rem',
// Viewport Relative
'vw', 'vh', 'vmin', 'vmax',
// Absolute
'cm', 'mm', 'in', 'px', 'pt', 'pc'
];
scope.$watch(function () {
return ngModelCtrl.$modelValue;
}, function (newValue, oldValue) {
updateValue();
});
ngModelCtrl.$formatters.unshift(function (value) {
value = isNaN(parseInt(value)) ? 0 : value;
return value;
});
scope.increment = function () {
updateValue(1)
};
scope.decrement = function () {
updateValue(-1);
};
function updateValue(amount) {
var matches = ngModelCtrl.$viewValue.toString().split(/(-?\d+)/);
var value = (parseInt(matches[1]) || 0) + (amount || 0);
sizingUnit = matches[2].trim();
ngModelCtrl.$setViewValue(value + sizingUnit);
sanityCheck();
}
function sanityCheck() {
var validity = css3Lengths.indexOf(sizingUnit) != -1;
ngModelCtrl.$setValidity('invalidUnits', validity);
}
}
}
});
Change your template text box to include an isolate scope call for ngChange. In that function, use timeout to allow the model update/digest to happen before calling parent controllers change function...
So change your template textbox:
<input type="text" ng-model="ngModel" ng-change="textChanged()">
Then change your directive:
// $timeout works better here than watch
app.directive('numericStepper', function ($timeout) {
return {
restrict: 'EA',
require: 'ngModel',
scope: {
ngModel: '=',
ngChange: '&' // add me!
},
replace: true,
templateUrl: 'numeric-stepper.html',
link: function (scope, element, attrs, ngModelCtrl) {
console.log('NumericStepper::link', ngModelCtrl.$viewValue);
var sizingUnit = null;
var css3Lengths = [
// Percentage
'%',
// Font Relative
'em', 'ex', 'ch', 'rem',
// Viewport Relative
'vw', 'vh', 'vmin', 'vmax',
// Absolute
'cm', 'mm', 'in', 'px', 'pt', 'pc'
];
/********** DONT NEED THIS
// scope.$watch(function () {
// return ngModelCtrl.$modelValue;
// }, function (newValue, oldValue) {
// updateValue();
// });
******************/
// Add this function
scope.textChanged = function() {
$timeout(function(){
updateValue();
scope.ngChange(); }, 500); // could be lower
}
ngModelCtrl.$formatters.unshift(function (value) {
value = isNaN(parseInt(value)) ? 0 : value;
return value;
});
scope.increment = function () {
updateValue(1)
};
scope.decrement = function () {
updateValue(-1);
};
function updateValue(amount) {
var matches = ngModelCtrl.$viewValue.toString().split(/(-?\d+)/);
var value = (parseInt(matches[1]) || 0) + (amount || 0);
sizingUnit = matches[2].trim();
ngModelCtrl.$setViewValue(value + sizingUnit);
sanityCheck();
}
function sanityCheck() {
var validity = css3Lengths.indexOf(sizingUnit) != -1;
ngModelCtrl.$setValidity('invalidUnits', validity);
}
}
}
});
And a working plunker
addon to what #doog abides already said
you can use $timeout interval as 0 and it will work the same then
scope.textChanged = function() {
$timeout(function(){
updateValue();
scope.ngChange(); }, 0); // could be Zero