I am trying to create a Service to manage all of the different pop-up modals that will be used in my application. So far everything is going well except for having the ability to update the position of the modal on the screen every time it comes into view.
Here is my UPDATED directive
app.directive('skModal', function($window){
return {
restrict:'EA',
link: function(scope, elem, attrs){
var top, left;
scope.$watch(attrs.ngShow, function(newVal){
if(newVal) {
top = ($window.innerHeight - elem.outerHeight()) / 2;
left = ($window.innerWidth - elem.outerWidth()) / 2;
elem.css({ 'top': top, 'left': left });
}
});
}
} ;
});
I simply use ng-include to populate any template I wish into the modal, then I use $watch to check when the ngShow variable is flipped. The problem is that my elem DOM element is not IN the DOM at the time, so the outerHeight() and outerWidth() functions return 0
Without jQuery you should used angular.element,
angular.element(element).prop('offsetHeight');
You will give the same result
Angular.element does NOT support outerHeight() and height() ;)
I see from your comment that you fixed your issue with a large offset.
In case you want a different solution, you could also try removing ng-hide just before you take your measurement. Given that ngShow has just flipped, you may not need to re-apply it, though it will disrupt animation (if you're using it) if you don't.
scope.$watch(attrs.ngShow, function(newVal){
if(newVal) {
// remove ng-hide to take the measurement
angular.element(elem).removeClass('ng-hide');
top = ($window.innerHeight - elem.outerHeight()) / 2;
left = ($window.innerWidth - elem.outerWidth()) / 2;
// optionally re-apply ng-hide here
elem.css({ 'top': top, 'left': left });
}
});
Related
I have a page in which I have a few elements. My scenario is, when I double click on the background i.e., not clicking on any of the elements. I need to go 1 page back using angular. I am pretty new to angular. I am trying to figure out a way to implement. Any suggestions/help is appreciated.
Javascript has a native dblclick event, which is described here.
You'll need to check that the event is coming from the body itself, instead of one of the elements generating an event that then bubbles up to the body.
Try something like this, passing the body as the element:
link: function(scope, element, attrs) {
element.on('dblclick', function(e) {
if(e.target === element){
$window.history.back();
}
});
}
I am trying to rotate an arrow image based on a scope variable $scope.bearing. I have seen a few examples using CSS classes, but without setting up 360 of them, and having to manage the removal of the existing one and add a new one... sounds impractical. I did find this answer that does it programmatically on a button click, but I'm still kind of at a loss as to how I can set it from my scope variable without getting into some jQuery in the controller when the value is updated, and if I'm doing that, I may as well just use JQuery and CSS.
For example:
locSvc.addUpdateListener(function(pos, dir, spd) {
console.log("handleLocationChange()");
$scope.location = pos;
$scope.bearing = dir;
$scope.speed = spd;
$('#bearing-indicator').css({
"-webkit-transform": "rotate(" + dir+"deg)",
"-moz-transform": "rotate(" + dir+"deg)",
"transform": "rotate(" + dir+"deg)"
});
});
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 = '';
}
});
}
};
})
I am trying to bind directive's attribute to a scope variable. I would like my UI Bootstrap control to dynamically change appearance when this scope variable changes value.
Here is the plunker:
http://plnkr.co/edit/gEjerpXhy3IuT5qRNeL8?p=preview
Every time you press on some of the checkmarks, $scope.max is increased by 1.
But, since that same $scope.max is passed to the 'rating' element as the 'max' attribute, I would like this 'rating' element to put additional checkmark on the right every time I click on some of the existing checkmarks.
I guess I am trying to re-draw this 'rating' element with new parameters. Is this possible and how?
I was able to simulate the behavior you want by adding an ng-if to the element, and modifying a boolean value when the rating changes.
<rating ng-if="render" value="rating.x" max="rating.max"...
$scope.rating.max += 1;
$scope.render = false;
$timeout(function() {
$scope.render = true;
}, 0, true);
Here is a demo, forked from your original plunkr.
For what it is worth, I think you would be better off customizing the directive's source or just writing your own. This workaround isn't going to scale, and it wouldn't be too hard to sprinkle in the functionality you need.
Im making a mobile optimised site with a text input which filters a list as you type. Its similar to this: http://jquerymobile.com/test/docs/lists/lists-search.html
For phones, it would be a usability benefit if when you selected the input the page scrolled down so the input was at the top of the page. That way as much of the list below would be visible as you type. Is this possible and/or does anyone have experience doing it? Thanks
Agreed - that'd be nice for usability.
If you're using jQuery, this should do the trick:
$('#id-of-text-input').on('focus', function() {
document.body.scrollTop = $(this).offset().top;
});
A better solution would be:
$('#id-of-text-input').on('focus', function() {
document.body.scrollTop += this.getBoundingClientRect().top - 10
});
Because offsetTop (or .offset().top if using Jquery) returns the distance from the first positioned parent, whereas you need the distance from the top of the document.
getBoundingClientRect().top returns the distance between the current viewport position to the element, u.e. if you're scrolled 300px below the element, it would return -300. So adding the distance using += would scroll to the element. -10 is optional - to allow some space at the top.
$('input, textarea').focus(function () {
$('html, body').animate({ scrollTop: ($('input, textarea').offset().top - 10) }, 1);
return false;
});