Angular, call function in directive from controller (broadcast?) - javascript

I am not too sure I am taking the best or correct approach to this, but basically what I am tyring to do is call a function in a directive. The reason being is I'm trying to clear a scope out that is controlled in the directive. For reference I am using this https://github.com/darylrowland/angucomplete directive, and the desired effect is when I add the selected item, it would clear out of the input.
So what I am trying to do is after I add the selectedObject to my list where I am storing (the add function) I am calling a broadcast to the directive like so
$scope.$broadcast('angucomplete:clearInput');
So for reference, here is the entire add function that is called
$scope.addLesson = function(){
var skillCheck = true;
for(i=0;i<$scope.lessonsHere.length;i++){
if($scope.lessonsHere[i].id === $scope.testObj.originalObject.id ){
errorOffScreen("You cannot add the same Lesson more than once");
skillCheck = false;
}
}
if(skillCheck){
$scope.lessonsHere.push($scope.testObj.originalObject);
$scope.testObj = {};
$scope.$broadcast('angucomplete:clearInput');
}
}
So in the directive itself I just have
$scope.clearInput = function(responseData, str) {
console.log("hit!");
};
It seems it does not work, I am not sure if this is the right approach, but I found another directive that calls itself from the controller with a broadcast so I figured it must be a good starting point.
Would appreciate any help here, as I am a bit in the dark with this topic. Thanks for reading!

Related

mxgraph infinite loops on apply

I am extending mxgraph delete control example to add delete like controls to nodes which are generated dynamically in my graph. The source code for the example is available here
The problem is in this part of the code -
// Overridden to add an additional control to the state at creation time
mxCellRendererCreateControl = mxCellRenderer.prototype.createControl;
mxCellRenderer.prototype.createControl = function(state)
{
mxCellRendererCreateControl.apply(this, arguments);
var graph = state.view.graph;
if (graph.getModel().isVertex(state.cell))
{
if (state.deleteControl == null)
mxCellRendererCreateControl.apply inside the overridden call back of createControl seems to work as intended (calls the original function before creating additional controls) with the initial state of the graph on load. But, once I add nodes dynamically to the graph and the callback is invoked by mxgraph's validate/redraw, the control goes into an infinite loop, where 'apply' function basically keeps calling itself (i.e, the callback).
I am a bit clueless because when I debug, the context(this) looks fine, but I can't figure out why instead of invoking the prototype method, it just keeps invoking the overridden function in a loop. What am I doing wrong?
It looks like you are not cloning your original function the right way, please try the following :
Function.prototype.clone = function() {
var that = this;
return function theClone() {
return that.apply(this, arguments);
};
};
Add that new method somewhere in your main code so it will available in the whole application, now you can change your code to :
// Overridden to add an additional control to the state at creation time
let mxCellRendererCreateControl = mxCellRenderer.prototype.createControl.clone();
mxCellRenderer.prototype.createControl = function(state) {
mxCellRendererCreateControl(state);
var graph = state.view.graph;
if (graph.getModel().isVertex(state.cell)) {
if (state.deleteControl == null) {
// ...
}
}
// ...
};
This should work if I understood your problem correctly, if it does not, please change the old function call back to the apply. Otherwise let me know if something different happened after the Function prototype change.
It seems that your overriding code is being called multiple times (adding a simple console.log before your overriding code should be enough to test this)
Try to ensure that the code that overrides the function only gets called once, or validate whether the prototype function is the original or yours.
Here is an example of how you can check if the function is yours or not
if (!mxCellRenderer.prototype.createControl.isOverridenByMe) {
let mxCellRendererCreateControl = mxCellRenderer.prototype.createControl;
mxCellRenderer.prototype.createControl = function(state) { /* ... */ };
mxCellRenderer.prototype.createControl.isOverridenByMe = true;
}
There are other ways, like using a global variable to check if you have overriden the method or not.
If this doesn't fix your issue, please post more about the rest of your code (how is this code being loaded/called would help a lot)

How to know the valueChanged origin in Aurelia?

I created a custom element in Aurelia and I also have the valueChanged, however I need to do a certain action only when the value is changed outside of the custom element. Since the signature is valueChanged(newValue, oldValue), how would I know when the value gets changed from the ViewModel and not from the custom element itself? Is that doable somehow with an observer or observable?
I actually got kind of a working sample, I saw that there's also an __array_observer__ property when the value is changed from the ViewModel, and it works but it's probably not ideal. So I got this piece of code which kinda works
valueChanged(newValue, oldValue) {
if (newValue !== oldValue && newValue.__array_observer__) {
// value got changed outside of this custom element
}
}
This is probably not ideal though, or is it? Any other suggestion in knowing where the value got changed outside of the custom element?
EDIT
As much as possible, I'm looking for a solution that will still have access to the custom element. Even if I want to get triggered by an external value change call, I still need to call an internal function of the same custom element.
EDIT #2
To give a little more description of my issue, I need to know when the value got changed from the outside because this will trigger an action that will re-affect the value. Without knowing if the change was from the outside of the custom element, I fall in a recursive call with no way to stop it. What I'm looking for is similar to what used to be the caller and the callee but this was removed with ES5 and Strict Mode, however this would have been very useful.
Still looking for an answer :(
You could use a CustomBindingBehavior to intercept the updateTarget event. For instance:
export class InterceptBindingBehavior {
bind(binding, scope, interceptor) {
binding['originalUpdateTarget'] = binding['updateTarget'];
binding.updateTarget = val => {
alert('property was changed outside of the element');
//do something here
binding['originalUpdateTarget'](val);
}
}
unbind(binding, scope) {
binding.updateTarget = binding['originalUpdateTarget'];
binding['originalUpdateTarget'] = null;
}
}
Usage:
<template>
<require from="./intercept-binding-behavior"></require>
<some-element value.bind="message & intercept"></some-element>
</template>
Runnable example: https://gist.run/?id=bcd7d39ed94856caf586f224f89fd1ff
I haven't tested this in many cases and I'm not sure if it's best way.
If you want to do the opposite (intercept when the property is changed from the element instead of the VM) just replace updateTarget for updateSource.
More info about CustomBindingBehaviors http://aurelia.io/hub.html#/doc/article/aurelia/binding/latest/binding-binding-behaviors/8
Hope this helps!
As discussed in gitter, you can use a suppress flag
value: number;
suppressValueChanged: boolean;
valueChanged(){
if(this.suppressValueChanged){
this.suppressValueChanged = false;
this.logger.debug("the value has been changed from inside the element");
return;
}
this.logger.debug("the value has been changed from outside the element");
// here comes the code to run when the value is changed outside
}
internalSetValue(value: number){
this.suppressValueChanged = true;
this.value = value;
}
The reason I reset the flag in the changed method is that depending on the circumstances valueChanged can be called by Aurelia asynchronously so you cannot just do the following
this.suppressValueChanged = true;
this.value = 123;
this.suppressValueChanged = false;
Sometimes, using a task will work
this.taskQueue.queueTask(() => {
this.suppressValueChanged = true;
this.value = 123;
this.suppressValueChanged = false;
});
It really depends where exactly in Aurelia code you are changing the value. I've found that the first option gives the most consistent result.

FRP, angular and global event handlers

I just started using Bacon.js and it's truly awesome. Although sometimes I do struggle to find the right way of doing things. For example I want to have an angular directive with a draggable part. I shamelessly pick into someone's jsBin and tried to adapt that code for angular
I am trying to make a table with resizable columns. So if I do something like this
in column-header directive
link: (scope, element, attrs)->
el = element.find('.column-separator')
doc = $(document)
mMove = doc.asEventStream('mousemove')
startDrag = el.asEventStream('mousedown')
endDrag = doc.asEventStream('mouseup').takeWhile mMove
# in this case unlike the example in jsBin I don't care about vertical axis,
# only horizontal "X"
getDelta = (t)-> a = t[1]; b = t[0]; return a-b
add = (p1,p2)-> p1 + p2
draggingDeltas = startDrag.flatMap ->
return mMove
.map '.clientX'
.slidingWindow 2,2
.map getDelta
.takeUntil endDrag
pos = draggingDeltas.scan 0, add
pos.onValue (pos)-> el.css left: pos+"px"
This kinda works, but now this directive will register 'mousemove' and 'mouseup' events all over the page. I probably can add some takeWhile statements, and the matter of fact I just tried and it didn't really work.
I mean what's the pattern of using global event handler's like $(document).asEventStream('click') in angular app?
You can create handlers in a directive and then use takeWhile, takeUntil but then that will work only once, since the stream eventually stops. Do you have to re-initialize the stream every time you need to respond to a document.click?
Also isn't it a bad thing to have "document" level events in bunch of places? If you write in a directive $(document).asEventStream('mouseup') and use that directive two hundred times, wouldn't that create actual two hundred listeners?
Or you gotta introduce these sort of stream variables globally for entire app to use, and then inside a directive do map, filter and reduce? But then what if someone calls takeUntil and stream stops flowing completely and can't be used in other parts of the app?
Or maybe listen on the top level of the app and emit $rootScope event for every value in the stream and then in a directive or view use ng-bacon's $rootScope.$asEventStream(event)?
Wouldn't that make the app somewhat less responsive? Say if you need to respond on 'keydown' and 'keyup' events?
Can someone show me an example how FRP can be used in angular directives (particularly drag-N-drop sample would be appreciated)
I'm not sure how exactly this fits into the angular philosophy, but I would definitely just add those handlers once.
In a global singleton class:
var mouseHandler = {
up: $(document).asEventStream('mouseup'),
move: $(document).asEventStream('mousemove')
}
The inside a single component, you add handlers to those but make sure you always use takeUntil, that way bacon only handles those events if there is an actual need.
function dragHandler(element) {
var start = $(element).asEventStream('mousedown')
var delta = start.flatMap(function() {
return mouseHandler.move
.map('.clientX')
.slidingWindow(2,2)
.map(getDelta)
.takeUntil(mouseHandler.up)
})
var pos = delta.scan(0, add)
pos.onValue(function(p) {
$(element).css({left: p + "px"})
})
function getDelta(t) { return t[1]-t[0] }
function add(a,b) { return a+b }
}
http://jsbin.com/yekojitake/3/edit

How to animate unchanged ng-repeat with AngularJS

I have a template that looks like this:
<p ng-repeat="item in myobj.items" class="toAnimate">{{item}}</p>
and I would like to use the animate module do a jQueryUI addClass/removeClass animation on the element using the JavaScript method described in the docs:
ngModule.animation('.toAnimate', function() {
return {
enter: function(element) {
element.addClass('pulse').removeClass('pulse', 2000);
}
};
});
This works beautifully, but the problem is that, since I want to use the p.toAnimate element to display status messages, it will not change the content according to angular.
To break it down a little further, say I have a name field. When I click Save the message Name was saved successfully. is displayed. Now if I modify the name and click save again, assuming the save was successful, the message should be re-displayed to give the user feedback of the newly edited name. The pulse does not happen, however, because the items in myobj.items didn't technically change.
I realize that I could remove the item after a period of time (and that is probably the route I will take to implement the real solution), but I'm still interested to see if this sort of thing can be done using AngularJS.
What I want to do is register with angular that the message should be treated as new even though it is not. Is there any way to do this?
A fiddle to go along with this: http://jsfiddle.net/Jw3AT/
UPDATE
There is a problem with the $scope.$$phase approach in my answer, so I'm still looking for the "right" way to do this. Basically, $scope.$$phase is always returning $digest, which causes the conditional to fail. Removing the conditional gives the correct result in the interface, but throws a $rootScope:inprog.
One solution I found is to add a $apply in the middle of the controller function:
$scope.updateThingy = function () {
$scope.myobj.items = [];
if (!$scope.$$phase) {
$scope.$apply();
}
$scope.myobj.items = ['Your name was updated.'];
};
Updated fiddle: http://jsfiddle.net/744Rv/
May not be the best way, but it's an answer.

AngularJS losing reference to service variable on re-assignment?

I didn't know a better way to phrase that question. I've written a basic service to be used by two controllers.
JsFiddle: http://jsfiddle.net/aditya/2Nd8u/2/
Clicking 'notify' works as expected; it adds a notification to the array. But 'reset' breaks it. Clicking either button after 'reset' doesn't do anything. Does anyone know what's going on here?
PS. I think it has something to do with Angular losing the reference since notifs is being re-assigned (technically), so I wrote getters and setters, but even emptying the array involves pop()ing till it's empty, which doesn't seem very efficient.
Plunkr if JsFiddle is down: http://plnkr.co/edit/mzfLLjFXCwsxM5KDebhc
I've forked your plunker and propose a solution:
In reset function, try to remove the objects of the array instead of declaring as new empty array:
notificationService.notifs.splice(0, notificationService.notifs.length);
Or, like suggested by #Wizcover:
notificationService.notifs.length = 0
This will notify angular that are modifications in the original array.
I changed your service to this:
.factory("notificationService", function(){
var notifications = [];
return {
notifs: notifications,
clear: function(){
angular.copy([], notifications);
},
get: function(){
return notifs;
}
}
})
and your controller :
$scope.reset = function(){
console.log("reset");
notificationService.clear();
console.log(notificationService);
}
and it works for me.
Naturally it should be a little bit tidier, in that instead of notifs you should have a get, and add and a remove method, but i just wanted to show you where the code changed.
The angular.copy is the method that makes sure the changes are made within angular's lifecycle.
As you can't bind variable but only methods, you could do this:
$scope.getNotifications = notificationService.get;
That should work.

Categories

Resources