Prototyping an Angular Directive - javascript

Based on instruction from this question, I have added the following code to my application within the config stage:
$provide.decorator('formDirective', function($delegate) {
var directive = $delegate[0];
directive.controller.prototype.inputs = {};
console.log(directive.controller);
return $delegate;
});
All I want to do is create another field and a few methods to the existing angular form object. All of that appears to be defined within the formDirective controller but when I prototype new fields and methods into that controller they are not available after my application is completed bootstrapping. Is there something I'm missing, is this even possible without modifying the source?
Thanks in advance!
EDIT Code Pen of Design Patterns Here
http://codepen.io/anon/pen/EadRBo

Thanks for your help. I did get this working and if your curious, this is why it was so important:
$provide.decorator('formDirective', function($delegate) {
var directive = $delegate[0];
directive.controller.prototype.$validate = function () {
var form = this;
var p;
for(p in form) {
if(form.hasOwnProperty(p) && p.indexOf('$') < 0) {
form[p].$setTouched();
}
}
};
A simple way to mark every element as touched causing the fields to be invalidated and error logic to kick in. I wanted this when a form was attempted to be submitted so that the user could see all the fields that were required. This also helps to keep my controllers slim without the extra overhead of an additional service.

Related

check if directive with given name exists, angularjs

Is there a way of checking if an directive in angular exists?
What I want to do, oder better what I have:
A Collection of 'protocols', which share some similarities, such as: name, created by, last edited, comment etc., but they all have special fields.
I have this one directives which matches with all those protocols. Showing the name and all the things which all the protocol have in common.
Within this directive I want to check if a special (more specific) view of the given protocol is available. In my case I call it "CompactView". The CompactView will give more detailed information of this protocol, it should be displayed within the actual directives. So a nested directive - no big deal.
My first approched worked fine, I had an array which had the protocol name as key, and directive html element (string) as value:
let decider = {
'cardiovascular_function': '<hcd-Cardiovascular-Function-Compact-View protocol="protocol"/>'
};
//use suitable directive
let protocolDirective = decider[scope.protocol.name];
let templateHook = angular.element(element).find('.extend-protocol-info')[0];
//use base template
//hooks protocol directive into template if possible
if (typeof protocolDirective != 'undefined') {
let compiled = $compile(angular.element(protocolDirective))(scope);
angular.element(templateHook).html(compiled);
scope.defaultTxt = false;
}else{
scope.defaultTxt = true;
}
If no key exited within the decider array I will display some default txt explaining, that there is no CompactView available.
The application will likely grow, and a lot of protocols will follow, so when ever someone creates a protocol the decider array needs to be extend. Prone to failure, and it would be much nicer if I can just check if a directive with some name exists and compile it.
My new approached look like the following:
let protocolName = scope.protocol.name;
let directiveName = 'hcd' + _.capitalize(_.camelCase(protocolName)) + 'CompactView';
//example: <hcd-Cardiovascular-Function-Compact-View protocol="protocol"/>
console.log(directiveName);
console.log($injector.has(directiveName));
try {
console.log(angular.module('hcdProtocol').directive(directiveName));
} catch (err) {
console.log(err);
}
//check if directive for given protocol exists
if ($injector.has(directiveName)) {
scope.defaultTxt = false;
let templateHook = angular.element(element).find('.extend-protocol-info')[0];
let protocolDirective = '<hcd-' + _.kebabCase(protocolName) + '-Compact-View protocol="protocol" />';
let compiled = $compile(angular.element(protocolDirective))(scope);
angular.element(templateHook).html(compiled);
} else {
//display default if no compact view exists
scope.defaultTxt = true;
}
So $injector.has is not working, since directives are not registered within $provide. Redeclaring and checking for errors, the try catch block, is not working either.
I'm currently trying to understand $compileProvider, somewhere within angular is the information which directive exist, I would love to find it.
Any ideas or suggestions, on how to fix this "generic directive" approach?
Maybe my concept with this nested directives is wrong, and there is a much simpler approach?
Both code blocks are within the link function of directive()

Displaying json data via angular

I have created a datatable showing a list of persons and their details. When the datatable is clicked, it has to show the entity of the single person with their details but my problem is, when I click the datatable it is opening a chat box showing the entity of the last clicked person changing all other chat box details.
1.How can I limit the append directive, that is, one chatbox for one id?
2.How to display the name of the particular person without changing the older chat box entity?
Here is the link to Plunker http://plnkr.co/edit/VTWcZQjlAda0KQ9sjFzI?p=preview
Actually i really think there is no need for a directive here.
It can simply be done by using ng-repeat and a collection.
See it working in this plunker
I added this in the controller :
$scope.bottomListCollection = [];
$scope.addToBottomList = function(artist) {
$scope.bottomListCollection.push(artist);
}
And this kind of ng-click on your rows :
ng-click="addToBottomList(item)"
Some advices to do things cleaner in angular :
Never use :
$compile.
$element.
$broadcast.
Jquery.
Take care of custom directives, theses are 90% miss-used.
Just a reminder : Directives are intended to add a behavior on an element. Not adding html.
Hope it helped.
Some tips that fix your issue
childScope = $scope.$new(true);
to ve an isolated scope you ve to use the first parameter of $new method
for more info look at the docs ($new(isolate, parent); --> https://docs.angularjs.org/api/ng/type/$rootScope.Scope)
then you need to add a control on the AppendText function like this to check if the same chat already exist
$scope.AppendText = function(idx) {
$scope.userInfo = $scope.artists[idx];
var chat = $('#chat_'+$scope.userInfo.shortname);
console.log(chat);
if ($scope.stage === 'Add' && chat.length==0 ) {
childScope = $scope.$new(true);
childScope.userInfo = $scope.userInfo; //<--- add old scope info to new scope
var compiledDirective = $compile('<div my-directive></div>');
var directiveElement = compiledDirective(childScope);
$('.my-directive-placeholder').append(directiveElement);
} else {
$scope.stage = 'Add';
}
}
working plunker http://plnkr.co/edit/TFjlN0U2i4irKtG2D5yu?p=preview
To answer the second part of your question : Create an isolate scope!
That can be done by passing true while creating a new scope: childScope = $scope.$new(true).
Once the isolate scope is created, you can do:
childScope.userInfo = $scope.userInfo;
PLUNK : http://plnkr.co/edit/IxPh4EmLpr8WAqRWtRlo?p=preview
Also, a hackish solution using one time databinding (not recommended):
http://plnkr.co/edit/RjZNOSyaemqg2eZ4Gma1?p=preview
To answer the first part: You could keep track of the id's that are passed to the $scope.AppendText function perhaps?
PLUNK: http://plnkr.co/edit/BCNju0rToyVYvNjqzVON?p=preview
Hope this helps! IMHO it would be much more simpler if you could just ng-repeat over your json data to generate the chatboxes.

Check if AngularJS module is bootstrapped

I have an iframe with ASP.NET application, that contains UpdatePanel. I started using Angular inside the application, but things didn't work because of the .NET postbacks.
To solve this, I used this solution:
with (Sys.WebForms.PageRequestManager.getInstance()) {
add_endRequest(onEndRequest); // regester to the end Request
}
function onEndRequest(sender, args) {
angular.bootstrap($('#mainDiv'), ['defaultApp']);
var rootscope = angular.element('#mainDiv').scope();
if (rootscope) {
rootscope.$apply();
}
}
And it works great.
The problem is that when I dynamically load a different user control in the ASP.NET page, with another ng-controller, Angular throws an error saying the app is already loaded:
App Already Bootstrapped with this Element
So the question is: How can I check if the app is already bootstrapped? Can I reload this module? Can I remove it from the element and than bootstrap it again?
Thanks.
It's not good practice to access scope from outside the app, so it's not enabled in well-built production applications. If you need to access/apply scope then there's something strange/unsupported about your use case.
However, the right way to check whether an element has been bootstrapped is the way the Angular library does it which is to load up the element and check for an injector. So you'd want angular.element(document.querySelector('#mainDiv')).injector(); which makes your code:
function onEndRequest(sender, args) {
var element = angular.element(document.querySelector('#mainDiv'));
//This will be truthy if initialized and falsey otherwise.
var isInitialized = element.injector();
if (!isInitialized) {
angular.bootstrap(element, ['defaultApp']);
}
// Can't get at scope, and you shouldn't be doing so anyway
}
Can you tell us why you need to apply the scope?
You could simply check for the scope of mainDiv, if angular.element(document.querySelector('#mainDiv')).scope() is not undefined then that means angular has been not initialized yet.
You code will be like below.
CODE
function onEndRequest(sender, args) {
//below flag will be undefined if app has not bootsrap by angular.
var doesAppInitialized = angular.element(document.querySelector('#mainDiv')).scope();
if (angular.isUndefined(doesAppInitialized)) //if it is not
angular.bootstrap($('#mainDiv'), ['defaultApp']);
var rootscope = angular.element('#mainDiv').scope();
if (rootscope) {
rootscope.$apply(); //I don't know why you are applying a scope.this may cause an issue
}
}
Update
After angular 1.3+ release in later Aug 2015, there it added performance related improvement by disabling debugging information by disabling debug info. So normally we should enable debuginfo option to false to have good performance improvement on Production environment. I don't wanted to write too much about it as its already covered by #AdamMcCormick answer, which is really cool.

Javascript \ Angular function help - mental block

I don't know what it is about JS, but I have a mental block. I apologize for the dumb question, but I'm at a loss because no matter how much I read I cannot get the academics into practice. Especially when it comes to nested functions.
I have a controller, lets say FileCtrl. Inside of it I have the the following that listens for file added to an input field via a directive. I'm attempting to inject an Angular JS factory service service called fileReader (a queue service for HTML5 FileReader).
However,I keep getting a undefined error on fileReader. I know why because, it cannot see fileReader, but injecting it at $scope.$on and then again on $scope.$apply doesn't work. Also, adding fileReader as a closure at the end of $scope.$on doesn't work either.
I should add that I can see the args.file and if I remove the fileReader code it will push the file no problem, but I then have no thumbnail. So I it works, just not with the fileReader and that is because Im doing something wrong with injection.
Side note, to Vals comment below I use apply as I found there was a image render sync issue without it which works fine for smaller images, but with larger images it freezes which is why I'm attempting to create and use a $q fileReader service. I suppose another way to solve for it would be to create a watch / directive on the array entry and when img comes back with the 64 encode string populate the html element ... like I said JS mental block :)
myApp.controller('FileController', ['$scope', 'FileReaderService', function($scope, FileReaderService ){
$scope.$on("fileSelected", function (event, args) {
$scope.$apply(function () {
$scope.progress = 0;
fileReader.readAsDataUrl(args.file, $scope)
.then(function(result) {
$scope.imageSrc = result;
});
$scope.files.push(args.file);
});
});
});
In AngularJS not all functions are been processed by Dependency Injection. In Controllers, Directives (in definition of directive and in controller, not on link or compile), Servicies AngularJS inject requested instances, but in some other functions (like event listeners) arguments are passed by position.
In your case you need to put fileReader into definition on controller, not on event listener.
Also you need to remove apply because event listeners added via $on are included into digest loop.
Thanks to all for your replies. Val you made me go back and do a little more research and I found the answer with a little debugging. Not sure I understand why yet, but I have an idea.
If there is an error in your factory service, in my case, FileReaderService angular won't always explode when bootstrapping the service, will only explode when you call the service, which makes kind of makes sense. If something is wrong in the service the entire service will not boot. Also, you won't get any error message when injecting it into the controller. I had to place a watch on the module and noticed there was a reference error. I found I had a missing function.
Purely inexperience on my end, but I kept trying to capture the results form the $q service, which is was doing fine, but then attempting to inject to outside the $q return i.e. I was attempting to capture $scope.imageSrc = result and insert it post the .then, which doesn't work as you have a sync issue. I could see the value in the $scope.files, but it would not console.log or show up in HTML. So I moved all the file manipulation into the .then and it works perfectly. Logical when you think about it :) why have a $q if you not going to use it ... lol.
// problem code
fileReader.readAsDataUrl(args.file, $scope)
.then(function(result) {
$scope.imageSrc = result;
});
// cannot and should not try to work the results outside the return promise
$scope.files.imgSource = $scope.imageSrc;
$scope.files.push(args.file);
//Fixed and working code
myApp.controller('FileController', ['$scope', 'FileReaderService', function($scope, FileReaderService ){
var reader;
$scope.files = [];
//listen for the file selected event
$scope.$on("fileSelected", function (event, args) {
$scope.progress = 0;
var file = args.file;
FileReaderService.readAsDataUrl(file, $scope)
.then(function(result) {
file.imgSource = result;
$scope.files.push(file);
});
});
});

How to remove the "name" param in for fields in ExtJS 4

I am integrating a payment provider into a ExtJS websites.
Basically, a form needs to be created and the form fields is send to the payment provider using Ajax.
The problem is that the payment provider does not allow that the form fields has a "name" param assigned to the "" tag. They do a manual check of the implementation and makes sure it is not there.
I assume it is a counter-mesasure for when the visitor has Ajax dissabled and the form gets submitted to my server instead, revealing the credit card. I know it does not make any sense with ExtJS, as it would not work without Javascript turned on, but non-the-less, that is the rule from the payment provider.
So, how can I force ExtJS to not put a "name" param in the form field? I have tried putting "name: ''" into the fields, but that gets ignored.
Do I use the template-system in ExtJS to solve this?
So Eric is perfectly right that it can be done much easier then modifying the whole template but non the less I would use a plugin for such a special case. I made a quick one:
Ext.define('Ext.form.field.plugin.NoNameAttribute', {
extend: 'Ext.AbstractPlugin',
alias: 'plugin.nonameattribute',
init: function(cmp) {
Ext.Function.interceptAfterCust(cmp, "getSubTplData", function(data){
delete data['name'];
return data;
});
}
});
Note the used method interceptAfterCust is a custom one of mine that modify the existing one by handing the result of the original to the intercepting one as argument. It is also using the given original object (which can be threaten as a scope) as scope for the original method. The easiest would be to add these method to Ext.Function
Ext.Function.interceptAfterCust = function(object, methodName, fn, scope) {
var method = object[methodName] || Ext.emptyFn;
return (object[methodName] = function() {
return fn.call(scope || this, method.apply(object, arguments));
});
}
Here is a working JSFiddle where the first field will not have a name attribute on the dom even if it exist in the component.
There's a surprisingly simple solution to this. I tested it with Ext.form.field.Text and Ext.form.field.ComboBox and it works well, but I don't know if it works for all form fields or if there are any negative side-effects. Use with caution.
Ext.define('Override.form.field.Base', {
override: 'Ext.form.field.Base',
getSubTplData: function(){
var data = this.callParent(arguments);
delete data.name;
return data;
}
});
Basically, it removes the auto-generated name from the render data before passing it along. The best part is that no private methods are involved so this should be a stable solution.
I prefer this in the field config options:
submitValue: false
Available since ExtJS 3.4

Categories

Resources