Angularjs: ng-class and CSS animations (running only once) - javascript

I am an AngularJS newbie so please be patient.
First, the code. Here's the relevant .html part:
<span ng-class="{'error-text': true, 'animated-error': vm.email_error}" ng-show="vm.email_error" translate>EMAIL_ERROR</span>
The .scss part:
.error-text{
position: absolute;
&.animated-error{
animation: bigandnormal 2s;
}
}
And there is a .js that is modifying vm.email_error after a click:
var validateEmail = function(){
var isEmailValid = /(.+)#(.+){2,}\.(.+){2,}/.test(vm.email);
vm.email_error = !isEmailValid;
return isEmailValid;
};
A button click runs the validateEmail() function.
What happens here is that when I perform the first click (with an invalid email) the class is added correctly to the span and the animation runs.
But if I click again, the animation is NOT run again.
Looking at the inspector, it seems that the class in never added again.
I have even tried to reset vm.email_error to false in the first line of the validateEmail() function and removed the animated-error from the console before clicking again:
angular.element(".error-text").each(function(index, el) {
angular.element(el).removeClass('animated-error');
});
But I cannot see anymore the animation and the class.
But in any case I do not think I am not doing it the right way, there should be a way to accomplish this.
This is what I am asking you :)
Thanks in advance.
UPDATE: I if put vm.email_error = !isEmailValid; in a setTimeout it works properly. It would be great to have an explanation from someone who knows that technology. Of course, it is clear that this cannot be the real solution.

Try to declare your email_error variable as a $scope or $rootScope variable rather than as a controller variable.
var validateEmail = function(){
var isEmailValid = /(.+)#(.+){2,}\.(.+){2,}/.test(vm.email);
$scope.email_error = !isEmailValid; // or $rootScope.email_error = !isEmailValid;
return isEmailValid;
};
And you don't need the quotes around your css class name.
Then call it in your html without prefixes, like this:
<span ng-class="{error-text: true, animated-error: email_error}" ng-show="email_error" translate='EMAIL_ERROR'></span>
Also, be careful on how to use angular translate :
If you use it as a directive, you have to write it as
<p translate='MY_TRAD'></p>
If you use it as a filter, then it's
<p>{{ 'MY_TRAD' | translate }}</p>
(angular translate doc: https://github.com/angular-translate/angular-translate)

Related

Angular: Run method every time a directive updates

I'm looking to have a function run every time an angular directive updates. In my case, I have an array of modal configurations that get used on a modal markup template.
Every time the template is used to generate a modal due to a change in the model, I want to run a positionModal() method.
scope.$watch in the link function doesn't seem to notice when I change the model, and I cant think of any other way of doing this. I tried a post-link function thinking that the compile function would get called when the directive was applied, but that doesn't seem to work either. Here is my example controller:
MyApp.controller("ModalController", function () {
//Define scope vars
$scope.modals = [];
$scope.$on("modalTrigger", function (event, settings) {
$scope.modals.push(settings);
});
});
Note: I've simplified the controller here- know that it DOES work.
Here is the template code:
<div class="modalParent" ng-controller="ModalController">
<div id="{{modal.id}}" class="modal" ng-class="modal.type" ng-repeat="modal in modals">
<div class="content">
<h2 ng-show="modal.title">{{modal.title}}</h2>
<p>{{modal.message}}</p>
<button>{{modal.button}}</button>
</div>
</div>
</div>
The directive is currently like this:
MyApp.directive("modalParent", function () {
var positionModals = function (element) {
element.find(".modal .content").each(function () {
//position logic here
});
};
return {
restrict: "C",
compile: function (tElement) {
positionModals(tElement);
}
};
});
Note: Also simplified for the purposes here.
The positionModals() call works when the first modal gets pushed to the array. After that, it stops working.
I've tried using the linking function as well, same result. scope.$watch(modals, function(){...}) does not work.
Can somebody help me figure out what I'm doing wrong?
Figured it out!
I was applying the directive to the parent, ".modalParent".
The ng-repeated element in this case is the modal itself ".modal".
You would want the directive to run on elements that get updates as the model changes, because then the linking function will get called each time the element is instantiated, rather than sitting and watching the parent and trying to update from there.
Hope this helps somebody.
Instead of calling like this, my approach is to write this in the services and inject that services in the controller wherever you want to get that function or the data to be notified as,
Services.js
as.service("xyzservice",function(factoryname){
//here the code goes...
})
Now inject in Controller,
ac.controller("controllername",function(xyzservice){
})
ac.controller("controllername",function(servicename){
})
ac.controller("controllername",function(xyzservice){
})
Here we have injected it in the two controller, we can get it.

Releasing OrbitControls in threejs

I have a Single Page Application with a lot of stuff in it, using durandal. On one page I have a link that leads to a different page where 3d model is rendered. OrbitControls are used to make model turn etc. That takes away my default left click and right click. After leaving that page, it still keeps mouse bindings and my left click and right click become useless for some uses like - selecting an tag meaning that input tags cannot be accessed again.
I could release bindings and reset them if I knew how. There is a deactivate function which is called when that 3d window is closed, but I have no idea what piece of code to write there. Any help would be extremely useful. I doubt any code will be of any use so I won't put any.
Thank you!
Due to request, here is simplified viewmodel:
define(['services/logger'], function (logger) {
var vm = {
attached: attached
};
return vm;
function attached(view) {
var camera, cameraTarget, scene, renderer, controls;
init();
animate();
function init() {
...
controls = new THREE.OrbitControls(camera);
...
}
function animate(){...}
function render(){...}
}
}
View is extremely complicated, but pasted here in full:
<div id="canvasDiv" style="overflow: hidden; width:100%; height:100%">
</div>
Actually, it would probably be helpful to include your code to setup OrbitControls (I'm not familiar with it).
The best approach to this whole issue would probably be to write a Knockout custom binding (google if you don't know them). A custom binding is a great place to abstract away DOM manipulation, in your case for setting up OrbitControls.
Let's assume that you have a div on which you set up OrbitControls. You could then do something like the following:
HTML:
<div data-bind="myOrbitControlsBinding: { someSetting: true; someOtherSetting: false }"></div>
JavaScript:
ko.bindingHandlers.myOrbitControlsBinding = {
init: function (element, valueAccessor) {
var settings = ko.utils.unwrapObservable(valueAccessor());
setupOrbitControlsOnElement(element, settings); // This should be your setup code for OrbitControls
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
// Here, you should unbind the event handlers for mouse clicks. How you do this depends on how OrbitControls sets them up. Please refer to their documentation for this. Maybe there is a generic dispose function?
disposeOrbitControls(element);
});
}
}
Edit:
Ah, I didn't realize you use Three. I've quickly scanned their documentation to see if they use some sort of input module that captures the events. They don't seem to. Which probably means that some where in your code, there is the keyword 'addEventListener' (search for it). This will be where the events are caught.
Your view probably has a viewmodel attached to it since you're using durandal. Inside the viewmodel, add a 'deactivate' method (and return it). In this method, you need to remove the event listener again. You probably already guessed it, but the method is called removeEventListener (see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget.removeEventListener for an explanation)
I still highly recommend that you create a custom binding to setup Three. This will give you much more control over creation/deletion than you might have now. If you don't want to, make sure that Three is initialized inside the viewmodel as well, in the activate method.
Let me know if that helped, otherwise some viewmodel / three init code would be helpful.

Loading data ajax style

I jsut started learning angular.js. Can you guys show me the right way to make a page that initially presents an ajax loader element saying 'Loading data' or something like that. Then after data's been fetched it would update the view and hide the element. I can put stuff in page load event using jquery, but how do you do that using pure angular? So far I figured out how to put that in click event:
<div ng-app="VideoStatus" ng-controller="VideoStatusCtrl">
<button ng-click="getVideos()">get videos</button>
</div>
<script type="text/javascript">
angular.module('VideoStatus', ['ngResource']).run(function(){
// I guess somehow I can start fetching data from the server here,
// but I don't know how to call Controller methods passing the right scope
});
function VideoStatusCtrl($scope, $resource) {
$scope.videoStatus = $resource('/Videos/GetStatuses', { callback: 'JSON_CALLBACK' });
$scope.getVideos = function () {
$scope.videoResult = $scope.videoStatus.get();
console.log('videos fetched');
};
};
</script>
Kudos to Adam Webber & Peter Bacon Darwin
Here is the working plunker
Here is my version plunker that make loading as a directive with modal popup feature
Here is the tutorial to use my version
you only need loading.js and modal.js and reference jQuery and twitterbootstrap css.
in your code,
Only 2 steps you need to do with your code.
Add the following code to HTML
< div data-loading> < /div>
Add LoadingModule module to your application module.
angular.module('YourApp', ['LoadingModule'])

AngularJS - Any way to call javascript after the template is linked?

I'm trying to use a jQuery plugin (Plupload) with AngularJS. I have created a directive that will be my file upload "widget". The directive looks like this (The code in the link function is a very simplified version of the example on the Plupload site):
.directive('myFileUpload', function () {
return {
restrict: 'E',
replace: true,
link: function(scope, element, attrs) {
scope.uploaderProperties = {
runtimes : 'html5,flash,html4',
url : 'media/upload',
max_file_size : '10mb',
container: 'fileUploadContainer',
drop_element: 'fileUploadDropArea'
};
scope.uploader = new plupload.Uploader(scope.uploaderProperties);
scope.uploader.init();
scope.uploader.bind('FilesAdded', function(up, files) {
scope.$apply();
});
},
templateUrl: 'upload.html'
};
});
My upload.html file looks like this:
<div id="{{uploaderProperties.container}}">
<div id="{{uploaderProperties.drop_element}}" style="border: 1px black dashed;">Drop files here<br><br><br></div>
Files to upload:<br>
<div ng-repeat="currFile in uploader.files">{{currFile.name}} ({{currFile.size}}) </div>
<br><br>
<!-- for debugging -->
{{uploader.files}}
<br><br>
</div>
When I include the directive on my page with a <my-file-upload> element, all the data bindings happen correctly. The problem is, when scope.uploader.init(); runs, the ids haven't been inserted into the DOM yet, and so Plupload complains and breaks since it can't select those elements. If I just hard-code the fileUploadContainer and fileUploadDropArea ids in the template, it works just fine. However, I'd really like to define those ids in only one place.
So, is there any way I can run the init() on the uploader after the template is linked in? I thought about using $timeout to delay when it runs, but that seems like a pretty big hack to me. Is there a more correct way of accomplishing this?
UPDATE
I wrapped the init() in a $timeout like this
$timeout(function() {
scope.uploader.init();
}, 2000);
just to make sure it would behave the way I was thinking, and sure enough, with this delay the plugin gets set up correctly and works. However, I do not want to have to rely on the timing of the $timeout. If there was just some way I could call scope.uploader.init(); after the template is linked in, everything should work fine. Anyone know of a good way of doing this?
Your problem is actually the other way around - The link function happens after the template is put in. So in this case, scope.uploaderProperties isn't set when the template happens.
It looks like your template is too fancy for the templateUrl option, really. You could try manually setting your ids with jQuery, or setting everything up in the compile function.
http://docs.angularjs.org/guide/directive

ExtJS: starting HtmlEditor defaulting to source

I'm using ExtJS 3.2.1 and I need a component almost identical to the bundled HtmlEditor, with one exception: it must start editing the HTML source code directly. The reason I don't use a normal TextArea is that the user should be able to preview the result of his actions before submitting.
I've tried calling toggleSourceEdit(), as per ExtJS documentation, with no success. Debugging, I see that the editor object has the sourceEditMode property set to true, and the Source Edit button seems as if it was "pressed", but clicking on it does not render the typed HTML, and clicking it again goes to the Source Mode.
I've tried calling toggleSourceEdit() after the container show() method, on the container afterLayout listener and on the editor afterRender listener. I've tried also calling it on another button that I added to the container. The result is the same on every try.
The only other option I see is updating ExtJS to 3.3.0, but I haven't seem anything related on the changelogs. Either way, it's going to be my next step. EDIT: The app had another problems when updating, we'll make a bigger effort to update later. As of right now, we are using the HtmlEditor in its original setting.
Thanks!
ran into the same problem (using 3.3.0 by the way)
stumbled upon a fix by dumb luck. i have no idea why this works, but second time is the charm. call it twice in a row to achieve the desired effect..
HTMLEditor.toggleSourceEdit(true);
HTMLEditor.toggleSourceEdit(true);
hope that helps!
Rather calling toggleSourceEdit(), try to setup the configuration while you create HtmlEditor Object
Using toggleSourceEdit() caused some problems for me. One was that this seemed to put the editor somewhere in limbo between source edit and WYSIWYG mode unless I used a timeout of 250ms or so. It also puts the focus in that editor, and I don't want to start the form's focus in the editor, especially since it's below the fold and the browser scrolls to the focused html editor when it opens.
The only thing that worked for me was to extend Ext.form.HtmlEditor and then overwrite toggleSourceEdit, removing the focus command. Then adding a listener for toggling to the source editor when the component is initialized. This is for Ext 4.1 and up. For older versions, replace me.updateLayout() with me.doComponentLayout().
var Namespace = {
SourceEditor: Ext.define('Namespace.SourceEditor', {
extend: 'Ext.form.HtmlEditor',
alias: 'widget.sourceeditor',
initComponent: function() {
this.callParent(arguments);
},
toggleSourceEdit: function (sourceEditMode) {
var me = this,
iframe = me.iframeEl,
textarea = me.textareaEl,
hiddenCls = Ext.baseCSSPrefix + 'hidden',
btn = me.getToolbar().getComponent('sourceedit');
if (!Ext.isBoolean(sourceEditMode)) {
sourceEditMode = !me.sourceEditMode;
}
me.sourceEditMode = sourceEditMode;
if (btn.pressed !== sourceEditMode) {
btn.toggle(sourceEditMode);
}
if (sourceEditMode) {
me.disableItems(true);
me.syncValue();
iframe.addCls(hiddenCls);
textarea.removeCls(hiddenCls);
textarea.dom.removeAttribute('tabindex');
//textarea.focus();
me.inputEl = textarea;
} else {
if (me.initialized) {
me.disableItems(me.readOnly);
}
me.pushValue();
iframe.removeCls(hiddenCls);
textarea.addCls(hiddenCls);
textarea.dom.setAttribute('tabindex', -1);
me.deferFocus();
me.inputEl = iframe;
}
me.fireEvent('editmodechange', me, sourceEditMode);
me.updateLayout();
}
})
}
Then to use it:
Ext.create('Namespace.SourceEditor', {
/*regular options*/
listeners: {
initialize: function(thisEditor) {
thisEditor.toggleSourceEdit();
}
}
});
htmlEditor.toggleSourceEdit(true);
one time should be enough if you do this listening to the afterrender event of the editor.

Categories

Resources