I have set up an app with angular.js. I am creating a menu via ng-repeat. Every link calls a function inside a directive that uses paper.js to draw text to canvas.
But canvas not updates until i move the mouse around browser. The function that draws on to the canvas is calling immediately, but the canvas shows nothing without mouse moves away from the link?
angular.js controller that calls draw function inside directive:
.controller('NavCtrl', function($scope, $location, $http, WorkService) {
$scope.works = [];
$http({method: 'GET', url: '/api/v1/work'}). //collects all works
success(function(data, status, headers, config) {
$scope.works = data.objects;
});
$scope.setTitle = function(work) {
$scope.currentTitle=work.title;
$scope.writeTitle(work.title);
};
})
angular.js directive:
.directive('draw', function () {
return {
restrict: 'A',
link: function postLink($scope, element, attrs) {
$scope.writeTitle = function(inText){
var letters = inText.split('');
for(var i=0; i<2; i++){
var text = new PointText(new Point(getRandom(0, 200), getRandom(0, 100)));
text.content = letters[i];
text.style = {
fontFamily: 'Arial',
fontWeight: 'normal',
fontSize: 14,
fillColor: 'red',
justification: 'center'
};
}
}
function getRandom(min, max) {
return Math.random() * (max - min) + min;
}
function initPaper() {
paper.install(window);
paper.setup('canvas');
}
initPaper();
}
};
});
the menu in my base.html that calls controller function to draw:
<div ng-controller=NavCtrl>
<nav>
<a href='#/bilgi'>bilgi</a>
<a ng-click="setTitle(work)" href='#/ardiye/{{work.id}}' ng-repeat='work in works'>{{$index+1}}</a>
</nav>
</div>
---- UPDATE ----
I have added:
<script type="text/paperscript" canvas="canvas">
function onFrame(event) {
}
</script>
this empty onFrame function to base.html and now the canvas updates normally. But why?
Have you tried updating the View?
I had an issue on Firefox and IE11 with the canvas not updating until a mouseover after I initiated some operation.
I just called paper.view.update() at the end of each function and it all went down allright.
If this is the issue, it has nothing to do with Angular
Wrap your code inside your directive function within a scope.$apply() function. This will make sure that it updates the UI.
One other thing, it is convention inside a directive to use to use scope (no dollar sign) instead of $scope. In a controller you are actually passing in the named object $scope. Angular needs to know the actual names of the objects you want to inject. Inside your directive's link function, however, the actual names do not matter. This is because inside link attribute the parameters are passed in a specific order: scope, element, attributes. You could use foo, bar, baz if you wanted but they will still be resolved as scope, element, atttributes.
With this in mind you could actually use $scope but realize that you can do that only because of the order it is inside the function parameter list.
link: function postLink(scope, element, attrs)
scope.writeTitle = function(inText){
scope.$apply(function () {
var letters = inText.split('');
for(var i=0; i<2; i++){
var text = new PointText(new Point(getRandom(0, 200), getRandom(0, 100)));
text.content = letters[i];
text.style = {
fontFamily: 'Arial',
fontWeight: 'normal',
fontSize: 14,
fillColor: 'red',
justification: 'center'
};
});
}
Related
I'm usin a directive to show a div on the screen only when the screen size is smaller than 600px. The problem is, the scope value isn't being updated, even using $apply() inside the directive.
This is the code:
function showBlock($window,$timeout) {
return {
restrict: 'A',
scope: true,
link: function(scope, element, attrs) {
scope.isBlock = false;
checkScreen();
function checkScreen() {
var wid = $window.innerWidth;
if (wid <= 600) {
if(!scope.isBlock) {
$timeout(function() {
scope.isBlock = true;
scope.$apply();
}, 100);
};
} else if (wid > 600) {
if(scope.isBlock) {
$timeout(function() {
scope.isBlock = false;
scope.$apply();
}, 100);
};
};
};
angular.element($window).bind('resize', function(){
checkScreen();
});
}
};
}
html:
<div ng-if="isBlock" show-block>
//..conent to show
</div>
<div ng-if="!isBlock" show-block>
//..other conent to show
</div>
Note: If I don't use $timeout I'll get the error
$digest already in progress
I used console logs inside to check if it's updating the value, and inside the directive everything works fine. But the changes doesn't go to the view. The block doesn't show.
You should use do rule in such cases to get the advantage of Prototypal Inheritance of AngularJS.
Basically you need to create a object, that will will have various property. Like in your case you could have $scope.model = {} and then place isBlock property inside it. So that when you are inside your directive, you will get access to parent scope. The reason behind it is, you are having scope: true, which says that the which has been created in directive is prototypically inherited from parent scope. That means all the reference type objects are available in your child scope.
Markup
<div ng-if="model.isBlock" show-block>
//..conent to show
</div>
<div ng-if="!model.isBlock" show-block>
//..other conent to show
</div>
Controller
app.controller('myCtrl', function($scope){
//your controller code here
//here you can have object defined here so that it can have properties in it
//and child scope will get access to it.
$scope.model = {}; //this is must to use dot rule,
//instead of toggle property here you could do it from directive too
$scope.isBlock = false; //just for demonstration purpose
});
and then inside your directive you should use scope.model.isBlock instead of scope.isBlock
Update
As you are using controllerAs pattern inside your code, you need to use scope.ag.model.isBlock. which will provide you an access to get that scope variable value inside your directive.
Basically you can get the parent controller value(used controllerAs pattern) make available controller value inside the child one. You can find object with your controller alias inside the $scope. Like here you have created ag as controller alias, so you need to do scope.ag.model to get the model value inside directive link function.
NOTE
You don't need to use $apply with $timeout, which may throw an error $apply in progress, so $timeout will run digest for you, you don't need to worry about to run digest.
Demo Here
I suspect it has something to do with the fact that the show-block directive wouldn't be fired if ng-if="isBlock" is never true, so it would never register the resize event.
In my experience linear code never works well with dynamic DOM properties such as window sizing. With code that is looking for screens size you need to put that in some sort of event / DOM observer e.g. in angular I'd use a $watch to observe the the dimensions. So to fix this you need to place you code in a $watch e.g below. I have not tested this code, just directional. You can watch $window.innerWidth or you can watch $element e.g. body depending on your objective. I say this as screens will be all over the place but if you control a DOM element, such as, body you have better control. also I've not use $timeout for brevity sake.
// watch window width
showBlock.$inject = ['$window'];
function bodyOverflow($window) {
var isBlock = false;
return {
restrict: 'EA',
link: function ($scope, element, attrs) {
$scope.$watch($window.innerWidth, function (newWidth, oldWidth) {
if (newWidth !== oldWidth) {
return isBlock = newWidth <= 600;
}
})
}
};
}
// OR watch element width
showBlock.$inject = [];
function bodyOverflow() {
var isBlock = false;
return {
restrict: 'EA',
link: function ($scope, element, attrs) {
$scope.$watch($element, function (new, old) {
if (newWidth) {
return isBlock = newWidth[0].offsetWidth <= 600;
}
})
}
};
}
I'm monitoring a CSS style and updating a variable in the scope that's based on that CSS style's value. It works the first time around but when the browser is resized, the scope gets updated but ng-style does not update with the new scope parameter.
JS:
.directive('monitorStyle', function() {
return {
link: function(scope, element, attrs) {
scope[attrs.updateVariable] = $(element).css(attrs.monitorStyle);
angular.element(window).on('resize', function() {
scope[attrs.updateVariable] = $(element).css(attrs.monitorStyle);
});
}
}
})
HTML:
<p class="text" monitor-style="font-size" update-variable="textHeight">Press "<img class="mini up" src="img/select-arrow.png" src="Up" ng-style="{'height': textHeight}">
I'm trying to do this outside of the controller because that's what people recommend. Why is ng-style not updating when the scope gets updated?
The window event isn't an angular event, so angular don't know he have to update the model/scope. You have to add scope.$apply() to tell angular to refresh it :
angular.element(window).on('resize', function() {
scope[attrs.updateVariable] = $(element).css(attrs.monitorStyle);
scope.$apply();
});
Data bindig only works when your model is updated with angular event like $http, $timeout, ng-click, ...
A great article about it : http://jimhoskins.com/2012/12/17/angularjs-and-apply.html
Yo, I made this sick solution.
So if you want to watch styles (even an array of them on a particular element) and then send their values to the $scope you can use this JS:
.directive('monitorStyle', function($timeout) {
return {
link: function(scope, element, attrs) {
function addToScope() {
var updateVariable = attrs.updateVariable.split(',');
var monitorStyle = attrs.monitorStyle.split(',');
for (var i = 0; i < updateVariable.length; i++) {
scope[updateVariable[i]] = $(element).css(monitorStyle[i]);
}
}
addToScope();
angular.element(window).on('resize', function() {
addToScope();
scope.$apply();
});
}
}
})
And apply it like this:
<h2 monitor-style="font-size,line-height" update-variable="headerHeight,headerLineHeight">
This will update the $scope on initialization and on window resizes. You can of course modify it to your own purpose.
Each time the $scope changes you can update other styles like this:
<div ng-style="{'height': headerHeight, 'line-height': headerLineHeight}">
I have a call stack like below and problem is that directive template is not interpolated. So as a result of this I can see {{ data | json }} as a string and ng-repeat is not triggered. How to approach this?
Context of situation is that I have a Highchart's chart where I need to provide clickable plot lines. On line click I need to display popover with dynamic content.
Optional question to answer:
My play with events is working well but I'm not sure if it's also well done. I would welcome any criticism on that. Idea is to hide popover on all following clicks.
Code:
1.
series: {events: {click: function(e) {drillDownCall(e, dataGroups)}
2.
function drillDownCall (e, dataGroups) {
var elem = angular.element('#drilldown');
if (!elem[0]) {
elem = angular.element('<drilldown fancy-name="dataGroups"></drilldown>');
}
elem.css({
position: 'absolute',
top: e.pageY,
left: e.pageX,
width: '150px',
height: '250px',
zIndex: '2000',
background: 'red'
});
var body = angular.element(document).find('body').eq(0);
var scope = $rootScope.$new();
scope.dataGroups = dataGroups;
body.append($compile(elem)(scope));
}
3.
.directive('drilldown', [
'$compile',
'$window',
function (
$compile,
$window
) {
return {
restrict: 'E',
replace: true,
scope: {
data: '=fancyName'
},
template: '' +
'<div id="drilldown">{{ data | json }}' +
'<ul>' +
'<li ng-repeat="group in data">{{ group.name }}</li>' +
'</ul>' +
'</div>',
link: function (scope, element) {
var ele = $compile(element)(scope),
off;
angular.element($window).on('click', function(e) {
scope.$emit('drilldown::click');
});
off = scope.$on('drilldown::click', function() {
angular.element(ele).remove();
angular.element($window).off('click');
off();
});
}
};
}]
)
I am unable test it myself but I think I know why.
Start of everything is drillDownCall and it is called by an event that is outside of Angular.js digest cycle. So Angular.js has no idea that there is a change in scope, and doesn't run a digest cycle, causing the new directive appear as non-compiled bunch of strings. (yes even you used $compile it works like that)
In summary, if I remember correct, you need at least one digest cycle to see that directive compiled. To trigger a digest cycle, you can add
$rootScope.$apply() or $rootScope.$applyAsync() or anything equivalent to it to the end of drillDownCall event handler.
Can you please try this?
i m trying to implement a dropdown on right click using this directive contextMenu in angularjs. This directive works fine in firefox but doesnt close the old menu while opening a different one when using ' google chrome'. Any idea how can i make changes to it.
Here is the plunkr
(function(angular) {
var ngContextMenu = angular.module('directive.contextMenu', []);
ngContextMenu.directive('cellHighlight', function() {
return {
restrict: 'C',
link: function postLink(scope, iElement, iAttrs) {
iElement.find('td')
.mouseover(function() {
$(this).parent('tr').css('opacity', '0.7');
}).mouseout(function() {
$(this).parent('tr').css('opacity', '1.0');
});
}
};
});
ngContextMenu.directive('context', [
function() {
return {
restrict: 'A',
scope: '#&',
compile: function compile(tElement, tAttrs, transclude) {
return {
post: function postLink(scope, iElement, iAttrs, controller) {
var ul = $('#' + iAttrs.context),
last = null;
ul.css({
'display': 'none'
});
$(iElement).bind('contextmenu', function(event) {
event.preventDefault();
ul.css({
position: "fixed",
display: "block",
left: event.clientX + 'px',
top: event.clientY + 'px'
});
last = event.timeStamp;
});
//$(iElement).click(function(event) {
// ul.css({
// position: "fixed",
// display: "block",
// left: event.clientX + 'px',
// top: event.clientY + 'px'
// });
// last = event.timeStamp;
//});
$(document).click(function(event) {
var target = $(event.target);
if (!target.is(".popover") && !target.parents().is(".popover")) {
if (last === event.timeStamp)
return;
ul.css({
'display': 'none'
});
}
});
}
};
}
};
}
]);
})(window.angular);
Change the .click event to .mouseup event and it will work with chrome.
$(document).mouseup(function(event) {
var target = $(event.target);
if (!target.is(".popover") && !target.parents().is(".popover")) {
if (last === event.timeStamp)
return;
ul.css({
'display': 'none'
});
}
});
I faced the same problem, and it worked for me. :)
Looking at the source code (of the directive), I think this context menu directive is a little bit too simple. It simply doesn't do a whole lot more than triggering a show/hide on the element referenced by the context attribute. It may have been enough for the use case of the one who wrote it, but it appears to be too lightweight for a general solution.
What is happening in the directive code: If you happened to trigger a context menu on the same row (or more general reference the same context menu) it works correctly because it will simply show the current context menu at a different place. If you trigger context1 first and then (by clicking on the second row) trigger a different context menu context2 there simply isn't any code that would trigger a hide of the context1 context menu.
You could implement this yourself as well but then keep track of any already opened context menu's and close them before another one is opened.
Btw: this context menu doesn't work for me in Firefox (38, Mac OS X) either. It opens the context menu and immediately closes it again. This is probably because both the contextmenu (on the table row) and the click (on document) are triggered.
I want to change CSS elements while a user scrolls the angular way.
here's the code working the JQuery way
$(window).scroll(function() {
if ($(window).scrollTop() > 20 && $(window).scrollTop() < 600) {
$('header, h1, a, div, span, ul, li, nav').css('height','-=10px');
} else if ($(window).scrollTop() < 80) {
$('header, h1, a, div, span, ul, li, nav').css('height','100px');
}
I tried doing the Angular way with the following code, but the $scope.scroll seemed to be unable to properly pickup the scroll data.
forestboneApp.controller('MainCtrl', function($scope, $document) {
$scope.scroll = $($document).scroll();
$scope.$watch('scroll', function (newValue) {
console.log(newValue);
});
});
Remember, in Angular, DOM access should happen from within directives. Here's a simple directive that sets a variable based on the scrollTop of the window.
app.directive('scrollPosition', function($window) {
return {
scope: {
scroll: '=scrollPosition'
},
link: function(scope, element, attrs) {
var windowEl = angular.element($window);
var handler = function() {
scope.scroll = windowEl.scrollTop();
}
windowEl.on('scroll', scope.$apply.bind(scope, handler));
handler();
}
};
});
It's not apparent to me exactly what end result you're looking for, so here's a simple demo app that sets the height of an element to 1px if the window is scrolled down more than 50 pixels: http://jsfiddle.net/BinaryMuse/Z4VqP/