$compile function and replace - angularjs

I had a problem with my code and by playing a little I found a solution but I have no idea why is it working. Here's the thing:
// recompile all audio buttons
angular.element(textAsDom).find('sound-button').each(function(key, element) {
$compile(angular.element(element))(scope);
});
I got a raw html as a parameter, and inside it I'm looking for directives named "soundButton" and I compile them in order to use their attached scope functions and properties.
However, my first solution was to do it like this:
// recompile all audio buttons
angular.element(textAsDom).find('sound-button').each(function(key, element) {
var compiledSound = $compile(angular.element(element))(scope);
angular.element(element).replaceWith(compiledSound);
});
So I first compile the soundButton and link it to the scope and I got a bound element in return. Then I replace the original element with the bound element. But then all angular dirty checking nor event handlers don't work anymore.
As for now my problem is solved, however for the sake of understanding why it didn't work, I'm asking you guys if you can shed some lights about this esoteric stuff.
Thanks

Sounds like you're approaching this a little weird.
Why not just wire up a directive to sound-button? No need to do .find.
.directive("sound-button", function(){
return {
class: 'C',
replace: true,
....
}
});

Related

Why are the width and height of a element directive calculated as 0?

On a project I'm currently working on, I have directives with restrict: 'E'.
At some point in my app, I need to know the hight of one of these directives but no matter how I try
to determine the width and height of the directive, I always get 0.
I've found some similar questions, but the solutions didn't help as the questions were primarily about
finding the height/height of $element inside the directive controller/linker. In my case,
the code that tries to find out the dimensions is outside of the directive-linker. In fact, the
directives have been already inserted in the DOM for a while.
The code looks like this:
// the directive
app.directive("widget", function() {
return {
restrict: 'E',
transclude: true,
scope: {
config: "="
},
....
};
});
<!-- the view -->
<widget config="config" ng-attr-id="{{ config.id }}"></widget>
And this is the function that tries to get the dimensions of that widget directive
function widget_dimensions(id)
{
var $w = $('#' + id);
if($w.length != 1)
return;
return {
width: $w.width(),
height: $w.height(),
};
}
This function always returns { width: 0, height: 0} when I pass a real widget id.
I prepared a jsfiddle example that demonstrates this. The directives are already in the DOM.
The only function that returns a non-0 on the element directive is getBoundingClientRect(),
but here the height is 68.9px and it should be 50px.
Why is this happening? I've read the $compile documentation a few times, hoping to find
something that I've missed before.
I use google-chrome Version 67.0.3396.87 for linux. I tested my
jsfiddle example on Chromium 57.0.2987.98 and Vivaldi 1.15.1147.47 and I got the same
results. However the same test on Firefox 60.0.2 yields the correct result. Is this a bug in
chrome family of browsers? Or is it a webkit/blink problem?
EDIT
As Lex points out in the comments, this might not have anything to do with angular after all.
I've updated my jsfiddle example which now prints the display property.
Now chrome and firefox say
$d1.css("display"): block
$d2.css("display"): inline
It's interesting that <dtest2></dtest2> is an inline element even though it behaves like a block
element. Still, chrome and firefox behave differently when it comes to calculating the dimensions.
Which one is correct?
As being said adding 'display: block' fix all issues, and in both firefox and chrome you get 52px (Cause borders are also counted).
Problems without 'display: block' is related to fact that you should not put block (i.e. div) inside inline (i.e. span).
P.S.
you can use clientWidth, clientHeight instead of this pretty getBoundingClientRect.
you can see same height and width in dev console.
you should not use jquery and methods like function(id) in angular app

mdbottomsheet disable drag down to close

I would like to disable the drag down to close gesture of mdbottomsheet. I've found a work around on scripts but I'm not sure where to put the code. Thanks for the help.
As you say that angular-material doesn't provide any option to disable it, obviously you will have to make changes in its source code.
Now, you haven't mentioned whether you want to disable it at specific places or turn drag-down-to-close for bottomSheets everywhere.
1) In case of latter, it would be quite straightforward, as the only thing you need to do is remove the event listeners for drag events.
If you use angular-material.js file, heres what you can do:
Find the function BottomSheet(element, parent). This function basically registers the drag events which close the sheet. We need make it not attach the listeners.
Reduce it to:
function BottomSheet(element, parent){
return {
element: element,
cleanup: angular.noop
};
}
The cleanup function basically de-registers the listeners on drag event.This function is called when the scope of the bottomSheet is destroyed. To make minimal changes, we have just reduced the cleanup function to do nothing.
2) If you want to be able to pass an option while creating the sheet in your controller, you do the same thing, but conditionally based on the option you pass. Wont write the code because I assume you know how angular works, but here are the steps:
=> Add a boolean variable along with other options(template,scope,etc. ). Lets call it dragDownToClose.
=> In the defaults injector function inside the provider function of MdbottomSheet , assign it a default value (true/false).
=>Pass this along with element and parent during instantiation of BottomSheet() inside the onShow function.
=> So BottomSheet() will now have three argument - dragDownToClose being the new one.
=> As we did in the former case, return the element without any handler attached when the value is false, and let the original function be when its true.
Of-course there are various ways in which you can actually implement this. However, I hope you get the idea.
First, inject $element into your controller. You known what AngularJS $element do, right?
Then we both known that the drag events are registered in BottomSheet
parent.on('$md.dragstart', onDragStart)
.on('$md.drag', onDrag)
.on('$md.dragend', onDragEnd);
So, the simple solution is: Remove those events, override those events... without override the function BottomSheet, right?
$element
.on('$md.dragstart', function (ev) {
return false;
})
.on('$md.drag', function (ev) {
return false;
})
.on('$md.dragend', function (ev) {
return false;
});
Something still wrong here! The backdrop still draggable! So, we do the same for backdrop
var parent = $element.parent();
var backdrop = parent.find('md-backdrop');
backdrop
.on(blah blah blah
These is code in case you are asking for
You can try
$mdBottomSheet.show({
template: *yourTemplate*,
clickOutsideToClose:false
})
this will not let the user close the bottom sheet even with drag or click outside.

AngularJS: Attempt to dynamically apply directive using ngClass causing weird functional and performance issues

Requirement
I want a textarea that expands or contracts vertically as the user types, alla Facebook comment box.
When the textarea loses focus it contracts to one line (with ellipsis if content overflows) and re-expands to the size of the entered text upon re-focus (this functionality not found on Facebook)
Note: Clicking on the textarea should preserve caret position exactly where user clicked, which precludes any dynamic swapping of div for textarea as the control receives focus
Attempted Solution
I'm already well into an AngularJS implementation, so...
Use Monospaced's Angular Elastic plugin. Nice.
Two attempts...
Attempt A: <textarea ng-focus="isFocussed=true" ng-blur="isFocussed=false" ng-class="'msd-elastic': isFocussed"></textarea> Fails because ng-class triggers no re-$compile of the element after adding the class, so Angular Elastic is never invoked
Attempt B: create a custom directive that does the needed re-$compile upon class add. I used this solution by hassassin. Fails with the following problems
Attempt B problems
Here's a JSFiddle of Attempt B Note that Angular v1.2.15 is used
I. Disappearing text
go to the fiddle
type into one textarea
blur focus on that textarea (eg click in the other textarea)
focus back on the text-containing textarea
result: text disappears! (not expected or desired)
II. Increasingly excessive looping and eventual browser meltdown
click into one textarea
click into the other one
repeat the above for as long as you can until the browser stops responding and you get CPU 100% or unresponsive script warnings.
you'll notice that it starts out OK, but gets worse the more you click
I confirmed this using: XP/Firefox v27, XP/Chrome v33, Win7/Chrome v33
My investigations so far
It seems that traverseScopesLoop in AngularJS starting at line 12012 of v1.2.15, gets out of control, looping hundreds of times. I put a console.log just under the do { // "traverse the scopes" loop line and clocked thousands of loops just clicking.
Curiously, I don't get the same problems in Angular v1.0.4. See this JSFiddle which is identical, except for Angular version
I logged this as an AngularJS bug and it was closed immediately, because I'd not shown it to be a bug in Angular per se.
Questions
Is there another way to solve this to avoid the pattern in Attempt B?
Is there a better way to implement Attempt B? There's no activity on that stackoverflow issue after hassassin offered the solution I used
Are the problems with Attempt B in my code, angular-elastic, hassassin's code, or my implementation of it all? Appreciate tips on how to debug so I can "fish for myself" in future. I've been using Chrome debug and Batarang for a half day already without much success.
Does it seem sensible to make a feature request to the AngularJS team for the pattern in Attempt A? Since you can add directives by class in Angular, and ngClass can add classes dynamically, it seems only natural to solve the problem this way.
You are severely overthinking this, and I can't think of any reason you would ever need to dynamically add / remove a directive, you could just as easily, inside the directive, check if it should do anything. All you need to do is
Use the elastic plugin you are using
Use your own directive to reset height / add ellipsis when it doesn't have focus.
So something like this will work (not pretty, but just threw it together):
http://jsfiddle.net/ss6Y5/8/
angular.module("App", ['monospaced.elastic']).directive('dynamicClass', function($compile) {
return {
scope: { ngModel: '=' },
require: "?ngModel",
link: function(scope, elt, attrs, ngModel) {
var tmpModel = false;
var origHeight = elt.css('height');
var height = elt.css('height');
var heightChangeIndex = 0;
scope.$watch('ngModel', function() {
if (elt.css('height') > origHeight && !heightChangeIndex) {
heightChangeIndex = scope.ngModel.length;
console.log(heightChangeIndex);
}
else if (elt.css('height') <= origHeight && elt.is(':focus')) {
heightChangeIndex = 0;
}
});
elt.on('blur focus', function() {
var tmp = elt.css('height');
elt.css('height', height);
height = tmp;
});
elt.on('blur', function() {
if (height > origHeight) {
tmpModel = angular.copy(scope.ngModel);
ngModel.$setViewValue(scope.ngModel.substr(0, heightChangeIndex-4) + '...');
ngModel.$render();
}
});
elt.on('focus', function() {
if (tmpModel.length) {
scope.ngModel = tmpModel;
ngModel.$setViewValue(scope.ngModel);
ngModel.$render();
tmpModel = '';
}
});
}
};
})

Replacing static text content inside ngrepeat with ngclick directive in AngularJS

I've tried so many different things here with no luck. Basically i have an array of strings (just sentences). I need to ngRepeat over those and output them. No problem. But I need to be able to do a find and replace for a particular word combination ("social security" in this case), wrap it inside a link and add an ngClick directive to that link.
I'm pretty certain that i need to use a directive and this has something to do with compiling and linking. But Thats about as far as i can get with this. Any help would be most appreciated.
I think you could use something like this : http://jsfiddle.net/DotDotDot/Z9CHL/2/
I'm not sure it's the best implementation, but at least it works
The HTML code is quite simple, I send the custom directive one parameter, the full sentence
<ul ng-repeat='sentence in list'>
<li><span the-dir txt="sentence"></span></li>
</ul>
On the javascript side, it's quite easy but longer :
.directive('theDir', function($compile){
var r=/(social security)/ig;
return {
scope:{txt:'='},
link:function(scope,element,attributes){
scope.aFunction=function(){
console.log('In the directive, you clicked on social security');
}
if(r.test(scope.txt)) {
splitted=scope.txt.split(r);
console.log(splitted)
var newSpan=new angular.element('<span>');
for(var i=0;i<splitted.length;i++)
{
if(r.test(splitted[i])){
var newLink=new angular.element('<a>');
newLink.attr('ng-click','aFunction()');
newLink.addClass('social-security');
newLink.html(splitted[i]);
newSpan.append(newLink);
}
else
newSpan.append(splitted[i]);
}
element.append(newSpan);
$compile(newSpan)(scope);
}
else
{
element.html(scope.txt);
}
}
}
});
I use a Regular Expression to find all occurrences of your sentence 'social security', split the full sentence on these occurences, then replace each time the words by a new angular.element (an HTML link in this case, with a ng-click attribute). I append all the regular words and the links to the original element, and then $compile the newly created span with the working links in order to have a working ng-click.
And it seems to work
I let you dig in the code, and I hope it helps you
Have fun

backbone.js: encapsulating view functions?

Pretty new to backbone.js so forgive me of my ignorance. I'm wondering, is there a way to encapsulate functions within the View class specifically?
I ask because when setting default events...
events {
'click .something' : 'doSomething'
}
... I'd prefer to have doSomething be nested in an encapsulating object for optimal organization. For example:
ui: {
doSomething: function() {}
}
But then I can't seem to get the default events to work.
events {
'click .something' : 'ui.doSomething' // this doesn't work
}
Any help is greatly appreciated. Or, if you can tell me why I shouldn't be doing this then I'd appreciate that, as well. Thanks!
Looking through the source that binds the events (delegateEvents) which is called from the constructor, it is pretty clear that it works on variables with in the scope of the object.
http://documentcloud.github.com/backbone/docs/backbone.html#section-118
You could, however, override delegateEvents to be a bit smarter... You could parse the value for dots and chain your tokens. You could even check the type of the value and use an actual function in place of the string. That might give you better control the way you want.
More info on the delegateEvents function: http://documentcloud.github.com/backbone/#View-delegateEvents

Resources