Angular directive template css class change delayed - javascript

Code snippet from my angular directive class:
if (currentIndex < newMessages.length) {
scope.message = newMessages[currentIndex];
} else {
scope.message = newMessages[0];
}
$timeout(function () {
var msgText = $('#msg-text');
console.log("xx:" + msgText.attr('class'));
// has-image no-desc no-desc-remove
});
When I change the scope.message, angular refreshes the template, and $timeout ensures that its containing function is called after the template is updated with new elements, etc. Right?
It seems it's working, but the problem is, I got classes like "has-image no-desc no-desc-remove". Later it's changed to "has-image".
How can I catch and event, where the classes are fully updated? Because This is no good for my code logic.

How can I catch and event, where the classes are fully updated? Because This is no good for my code logic.
By you code and requirement, you need to know the moment when your $scope.message.imageURL and $scope.message.Description updated. For the most basic way you can go with $scope.$watchGroup()
Example of $watchGroup. (Here I put the condition for both has some value you can adjust the condition in the way you wanted)
$scope.$watchGroup(['message.imageURL', 'message.Description'], function (newValues, oldValues){
// newValues[0] => is => $scope.message.imageURL
// newValues[1] => is => $scope.message.Description
// case of both having some value
if(newValues[0] && newValues[1]){
console.log('things has been changed');
}
});

Turns out I had angular-animate.js included in my html, and it automatically does "Class and ngClass animation hooks". More here https://docs.angularjs.org/guide/animations. Disabled it where needed with $animate.enabled(false, myElement);

Related

ExtJS: How to change component `labelStyle` within a function?

tl/dr:
There is not any setter for labelStyle. So how can I change it's value on a function?
I've a afterrender listener and aim to set color of fieldLabel as white and set a emptyText instead of fieldLabel and meanwhile I need to change fieldLabel color to be white! So I need to access current component's lableStyle property. I've try to go within config but couldn't find it. As well tried to use Ext.apply() to set a property for related combo but this is not work either.
How can I achieve my aim over here?
//Related component `listeners`;
listeners : {
afterrender: 'removeLabel',
focusenter: 'createLabel'
},
//Related function;
removeLabel: function () {
let formComponent = ['foocombobox', 'bartextfield', 'zetdatefield'];
Ext.each(formComponent, function (eachComp) {
let currentComp = Ext.ComponentQuery.query(eachComp)[0];
if (currentComp.value === '')) {
let currentLabel = currentComp.fieldLabel;
currentComp.setEmptyText(currentLabel);
//This can not work because of I've reach constructor config. So how can I reach?
//currentComp.labelStyle = "color:red;";
//I tried to manipulate through Ext.apply but did not effect;
//Ext.apply(currentComp, {labelStyle: "color:red;"});
}
});
},
I get dom node by currentComp.labelTextEl.dom and then run setAttribute function:
FIDDLE
if (!currentComp.value) {
urrentComp.setEmptyText(currentComp.fieldLabel);
currentComp.labelTextEl.dom.setAttribute('style', 'color:white;');
}

AngularJS: Add inline custom code using a directive

Here's the scenario.
In the app, you can add inline custom code (HTML attributes ex. style="", onclick="alert('Test')") in an element (ex. input texts, divs). The custom code is binded to the main model and loaded to the element using a custom directive I've created. I'm doing this to control dynamically generated fields that I want to hide and show based on different inputs.
This is my custom directive that loads inline attributes on the element:
app.directive('addCustomHtml', function() {
return {
scope: {
customHtml: "="
},
link: function(scope, element, attributes){
scope.$watch('customHtml', function(newVal, oldVal) {
if (newVal) {
var attrs = newVal.split('\n');
for (var i = 0; i < attrs.length; i++) {
var result = attrs[i].split('=');
var attr = result.splice(0,1);
attr.push(result.join('='));
if (attr[1]) {
element.attr(attr[0], attr[1].replace(/^"(.*)"$/, '$1'));
}
}
} else {
if (oldVal) {
var attrs = oldVal.split('\n');
for (var i = 0; i < attrs.length; i++) {
var attr = attrs[i].split('=');
if (attr[0]) {
element.removeAttr(attr[0]);
}
}
}
}
})
}
}
});
It is binded to the element like this:
<input type="checkbox" add-custom-html custom-html="checkbox1.customHtml">Yes
To see it in action, you can check the plunkr here: https://plnkr.co/edit/xjjMRPY3aE8IVLIeRZMp?p=preview
Now my problem is, when I try to add AngularJS directives (ex. ng-show, ng-if) using my custom directive, AngularJS doesn't seem to recognize them and the model scope I'm passing inside.
Another problem is when I try to add vanilla Javascript event functions (ex. onclick="", onchange=""), it does work but sometimes AngularJS does not read them especially when the element has an ng-change, ng-click attributes.
Again, I am doing this approach on the app because I have generic fields and I want to control some of them by adding this so called "custom codes".
Any help would be highly appreciated!!
If you want to add HTML code and compile it within current $scope, you should use the $compile service:
let someVar = $compile(yourHTML)($scope);
// you can now append someVar to any element and
// angular specific markup will work as expected
Being a service, you'll need to inject it into current controller (or pre/post link function) to be able to use it.

AngularJS typeahead otions not up to date

I'm writing a directive wrapper around a typeahead input. This directive listens for changes on a link and get's new data + options for the typeahead.
I can simply simulate this behaviour with a $timeout and demonstrated it in this plnkr.co.
JS
app.controller('sample', function($scope, $timeout) {
$scope.options = ['1800', '1900', '2100'];
// Simulate some latency
$timeout(function () {
$scope.options.push('1850');
}, 4000);
});
HTML
<div>
<input type="text" ng-model="optionValue" typeahead="opt for opt in options | filter:$viewValue">
</div>
If you start typing '18' in the input field it shows 1800 as expected. But when 1850 get's added after an amount of time, the selectable options from typeahead are not being updated.
-- FYI my real live directive looks like this --
$scope.$watch($interpolate(url), function (newUrl) {
$http.get(newUrl).then(function (response) {
$scope.options = response;
});
});
I tried to use typeahead="opt for opt in getData()" but this doesn't work because the interpolated value is not yet up to date. It's always one value behind.
Seems like an issue to post on AngularUI Bootstrap website. Matches are getting selected on every keystroke but they don't get updated if you change the underlying data between keystrokes. I don't see any work-around for this, except maybe triggering the appropriate key event handler on the input manually (when you change the collection).
If someone interested in the solution, here is how I solved it at the moment. I'm not happy with the end result, please provide me some feedback :-).
Plunkr
Check out updated-bootstrap.js, I had to add the following in order to make it work:
A custom attribute that'll be use for the $watchCollection
var customOptions = attrs.typeaheadCustomOptions || '';
In the function where it gets the matches I've added a watch if customOptions is provided:
if (customOptions) {
originalScope.$watchCollection(customOptions, function (matches) {
resetMatches();
updateMatches(matches);
});
}
And that was basically it :-), the updateMatches is just an abstraction of existing code. It's not being used by me and the manual update.
var updateMatches = function(matches) {
for (var i = 0; i < matches.length; i++) {
locals[parserResult.itemName] = matches[i];
scope.matches.push({
id: getMatchId(i),
label: parserResult.viewMapper(scope, locals),
model: matches[i]
});
}
scope.query = modelCtrl.$viewValue;
};
Opened issue on github

Angularjs watch for change in parent scope

I'm writing a directive and I need to watch the parent scope for a change. Not sure if I'm doing this the preferred way, but its not working with the following code:
scope.$watch(scope.$parent.data.overlaytype,function() {
console.log("Change Detected...");
})
This it logged on window load, but never again, even when overlaytype is changed.
How can I watch overlaytype for a change?
Edit: here is the entire Directive. Not entirely sure why I'm getting a child scope
/* Center overlays vertically directive */
aw.directive('center',function($window){
return {
restrict : "A",
link : function(scope,elem,attrs){
var resize = function() {
var winHeight = $window.innerHeight - 90,
overlayHeight = elem[0].offsetHeight,
diff = (winHeight - overlayHeight) / 2;
elem.css('top',diff+"px");
};
var watchForChange = function() {
return scope.$parent.data.overlaytype;
}
scope.$watch(watchForChange,function() {
$window.setTimeout(function() {
resize();
}, 1);
})
angular.element($window).bind('resize',function(e){
console.log(scope.$parent.data.overlaytype)
resize();
});
}
};
});
If you want to watch a property of a parent scope you can use $watch method from the parent scope.
//intead of $scope.$watch(...)
$scope.$parent.$watch('property', function(value){/* ... */});
EDIT 2016:
The above should work just fine, but it's not really a clean design. Try to use a directive or a component instead and declare its dependencies as bindings. This should lead to better performance and cleaner design.
I would suggest you to use the $broadcast between controller to perform this, which seems to be more the angular way of communication between parent/child controllers
The concept is simple, you watch the value in the parent controller, then, when a modification occurs, you can broadcast it and catch it in the child controller
Here's a fiddle demonstrating it : http://jsfiddle.net/DotDotDot/f733J/
The part in the parent controller looks like that :
$scope.$watch('overlaytype', function(newVal, oldVal){
if(newVal!=oldVal)
$scope.$broadcast('overlaychange',{"val":newVal})
});
and in the child controller :
$scope.$on('overlaychange', function(event, args){
console.log("change detected")
//any other action can be perfomed here
});
Good point with this solution, if you want to watch the modification in another child controller, you can just catch the same event
Have fun
Edit : I didn't see you last edit, but my solution works also for the directive, I updated the previous fiddle ( http://jsfiddle.net/DotDotDot/f733J/1/ )
I modified your directive to force it to create a child scope and create a controller :
directive('center',function($window){
return {
restrict : "A",
scope:true,
controller:function($scope){
$scope.overlayChanged={"isChanged":"No","value":""};
$scope.$on('overlaychange', function(event, args){
console.log("change detected")
//whatever you need to do
});
},
link : function(scope,elem,attrs){
var resize = function() {
var winHeight = $window.innerHeight - 90,
overlayHeight = elem[0].offsetHeight,
diff = (winHeight - overlayHeight) / 2;
elem.css('top',diff+"px");
};
angular.element($window).bind('resize',function(e){
console.log(scope.$parent.data.overlaytype)
resize();
});
}
};
});
You should have the data property on your child scope, scopes use prototypal inheritance between parent and child scopes.
Also, the first argument the $watch method expects is an expression or a function to evaluate and not a value from a variable., So you should send that instead.
If you're looking for watching a parent scope variable inside a child scope, you can add true as second argument on your $watch. This will trigger your watch every time your object is modified
$scope.$watch("searchContext", function (ctx) {
...
}, true);
Alright that took me a while here's my two cents, I do like the event option too though:
Updated fiddle
http://jsfiddle.net/enU5S/1/
The HTML
<div ng-app="myApp" ng-controller="MyCtrl">
<input type="text" model="model.someProperty"/>
<div awesome-sauce some-data="model.someProperty"></div>
</div>
The JS
angular.module("myApp", []).directive('awesomeSauce',function($window){
return {
restrict : "A",
template: "<div>Ch-ch-ch-changes: {{count}} {{someData}}</div>",
scope: {someData:"="},
link : function(scope,elem,attrs){
scope.count=0;
scope.$watch("someData",function() {
scope.count++;
})
}
};
}).controller("MyCtrl", function($scope){
$scope.model = {someProperty: "something here");
});
What I'm showing here is you can have a variable that has two way binding from the child and the parent but doesn't require that the child reach up to it's parent to get a property. The tendency to reach up for things can get crazy if you add a new parent above the directive.
If you type in the box it will update the model on the controller, this in turn is bound to the property on the directive so it will update in the directive. Within the directives link function it has a watch setup so anytime the scope variable changes it increments a counter.
See more on isolate scope and the differences between using = # or & here: http://www.egghead.io/

Calling a jQuery method at declaration time, and then onEvent

I've been writing JS (mainly jQuery) for quite a few months now, but today I decided to make my first abstraction as a jQuery method. I already have working code but I feel/know that I'm not doing it the right way, so I come here for some enlightenment.
Note: Please do not reply that there's already something out there that does the trick as I already know that. My interest in this matter is rather educational.
What my code is intended to do (and does):
Limit the characters of a textfield and change the color of the counter when the user is approaching the end.
And here's what I have:
$(function(){
$('#bio textarea').keyup(function(){
$(this).char_length_validation({
maxlength: 500,
warning: 50,
validationSelector: '#bio .note'
})
})
$('#bio textarea').trigger('keyup');
})
jQuery.fn.char_length_validation = function(opts){
chars_left = opts.maxlength - this.val().length;
if(chars_left >= 0){
$(opts.validationSelector + ' .value').text(chars_left);
if(chars_left < opts.warning){
$(opts.validationSelector).addClass('invalid');
}
else{
$(opts.validationSelector).removeClass('invalid');
}
}
else{
this.value = this.value.substring(0, opts.maxlength);
}
}
In the HTML:
<div id="bio">
<textarea>Some text</textarea>
<p class="note>
<span class="value">XX</span>
<span> characters left</span>
</p>
</div>
Particularly I feel really uncomfortable binding the event each on each keyup instead of binding once and calling a method later.
Also, (and hence the title) I need to call the method initially (when the page renders) and then every time the user inputs a character.
Thanks in advance for your time :)
chars_left is a global variable which is not good at all. Here is a better (slightly changed) version:
jQuery.fn.char_length_validation = function(opts) {
this.each(function() {
var chars_left = opts.maxlength - $(this).val().length;
$(this).keyup(function() {
chars_left = opts.maxlength - $(this).val().length;
if (chars_left >= 0) {
$(opts.validationSelector).text(chars_left);
if (chars_left < opts.warning) {
$(opts.validationSelector).addClass('invalid');
}
else {
$(opts.validationSelector).removeClass('invalid');
}
}
else {
$(this).val($(this).val().substring(0, opts.maxlength));
}
});
});
this.keyup(); // makes the "initial" execution
return this;
};
See a DEMO.
Some explanation:
In a jQuery plugin in function, this refers to the elements selected by the selector. You should use this.each() to loop over all of these and set up every element accordingly.
In this example, every element gets its on chars_left variable. The event handler passed to keyup() has access to it as it is a closure. Update: It is already very late here ;) It is not necessary to declare it here as you recompute the value every time anyway. Still, it should give you an idea how to have private variables that persist over time.
You should always return this to support chaining.
Further thoughts:
You might want to think about how you could make it work for several textareas (i.e. you have to think about the validation selector). Don't tie it to a specific structure.
You should have default options.
Update: Of course you can make your plugin work with only one textarea (like some jQuery functions work).
You can do the binding and initial triggering in the method:
jQuery.fn.charLengthValidation = function(opts) {
return this.keyup(function() {
var charsLeft = opts.maxLength - $(this).val().length;
if (charsLeft >= 0) {
$(opts.validationSelector + ' .value').text(charsLeft);
$(opts.validationSelector).toggleClass('invalid', charsLeft < opts.warning);
} else {
$(this).val($(this).val().substring(0, opts.maxLength));
}
}).trigger('keyup');
}
$(function() {
$('#bio textarea').charLengthValidation({
maxLength: 25,
warning: 10,
validationSelector: '#bio .note'
});
});

Categories

Resources