check if directive with given name exists, angularjs - javascript

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()

Related

How to use an if statement to only collect forms fields that contain an '#' symbol in?

I'm currently collecting form abandonments from my website using the javascript code below within the tag manager of analytics software (Piwik pro).
It currently works great, however, I'm now trying to make it so that only emails with an '#' collect and nothing else.
I'm thinking that an if statement that has .includes("#") will do the job, however as I'm a beginner and I did not write this code, I am struggling to make it work. The below shows what I've tried (amongst many other attempts) and it either continues to collect everything or doesn't collect anything...
(Also I'm unsure if _paq.push is used outside of the analytics software I use, but that line creates a custom event within Piwik pro.)
formFields = ['input_3_1','input_3_2','input_3_3','input_3_4']
let formFieldText = document.getElementById('input_3_1').value
let fieldHasAtSymbol = formFieldText.includes("#")
if (fieldHasAtSymbol = 'true') {
function trackUnsubmittedField(fieldId){
let formField = document.getElementById(fieldId)
if(formField.nodeName==="SELECT"){
formField.addEventListener('change',()=>{
_paq.push(['trackEvent','formAbandonment',fieldId,document.getElementById(fieldId).value])
})
}
else{
formField.addEventListener('focusout',()=>{
_paq.push(['trackEvent','formAbandonment',fieldId,document.getElementById(fieldId).value])
})
}
}
}
formFields.map(element=>trackUnsubmittedField(element))
I've also tried using the same form field variable that's declared within the function by placing a .includes() if statement within it, but no luck.
formFields = ['input_3_1','input_3_2','input_3_3','input_3_4']
function trackUnsubmittedField(fieldId){
let formField = document.getElementById(fieldId)
if (formField.includes("#") = "true") {
if(formField.nodeName==="SELECT"){
formField.addEventListener('change',()=>{
_paq.push(['trackEvent','formAbandonment',fieldId,document.getElementById(fieldId).value])
})
}
else{
formField.addEventListener('focusout',()=>{
_paq.push(['trackEvent','formAbandonment',fieldId,document.getElementById(fieldId).value])
})
}
}
formFields.map(element=>trackUnsubmittedField(element))
There's probably a lot wrong with the code I added onto this but any help would be appreciated.

Filtering smart-table on transformed data

Apologies in advance, I am not a very experienced JS programmer, and even less so with AngularJS, but am trying to make some improvements on a legacy codebase that is using Angular 1.5.9 and Smart Table to display information from a database.
I've read all about st-search and st-safe-src vs. st-table, etc., but I am having trouble filtering on my table, since there are transformations happening on the underlying data before it gets displayed. The ng-repeat variable in my case is transaction, which has various fields to hold information for that transaction, such as payee, which holds a UUID pointing to another database document. In the app, we display the name of that payee using a function from another controller (dbCtrl.getPayeeName()), but the underlying data is the UUID. Thus, when attempting to filter with Smart Table, it does not filter on the displayed names, and only works when entering the UUID into the filter field.
A small example (with lots of the intervening bits removed, but hopefully enough to demonstrate my confusion):
<div class="account"
st-table="displayedTransactions"
st-safe-src="transactions"
disable-ng-animate>
...
<div><input st-search="payee" placeholder="search for payee" class="input-sm form-control" type="search"/></div>
...
<div ng-repeat="transaction in displayedTransactions track by transaction.id">
...
<div class="account__td" transaction-field-focus-name="payee">
{{dbCtrl.getPayeeName(transaction.payee)}}
</div>
...
</div>
Is there a relatively simple way to get the filtering to work for a situation like this where the displayed data is different than the underlying data? From what I'm reading in the documentation, it sounds like this might require some sort of custom plugin, which sounds like more work, but I could maybe figure out. I just wanted to see if I'm missing something obvious before heading down that route.
Circling back on this, I was able to accomplish what I needed using the st-set-filter attribute as described in the Strict mode filtering section of the documentation, as well as this helpful answer from laurent back in 2014.
Essentially, I added st-set-filter="transactionFilters" to my table in my html template, as well as input tags with st-search="prop_to_search" attributes. Then in my applications module (I put this in one of the controllers, not sure if that's totally correct) I defined a filter such as below. expression gets passed into this code as an object with string values for whatever you typed in, so if you had three search fields, you'd get an object like:
{
"prop_to_search1": "value1",
"prop_to_search2": "value2",
"prop_to_search3": "value3"
}
In the filter function, I wrote an if block for each property that could come in, and then do my custom transformations and pattern matching there. This way, I have full control over the eventual matching, and rather than searching on the UUID, I can do something like $rootScope.dbCtrl.getPayeeName(uuidToSearch) instead. This is all acceptably performant in my use case, but I could probably cache those database lookups as a potential optimization.
angular.module('myApp').filter('transactionFilters', function($rootScope, $filter){
return function(array, expression){
// console.log(`expression is: ${JSON.stringify(expression, null, 4)}`)
return array.filter(function(val, index){
// in this function's context, `expression` is an object with
// the active filters entered in each field; `val` is the data
// representation of each row of the table
// if this function returns true, the row will match and smart-table
// will show it, otherwise it will be hidden
// define matches all at once, check them all, then return
// a big logical AND on all of them
let accountMatch = true;
let payeeMatch = true;
let categoryMatch = true;
if (expression.account) {
uuidToSearch = val.account // this is the account UUID
strToSearch = $rootScope.dbCtrl.getAccountName(uuidToSearch).toLowerCase(); // convert to an account name (we could memoize this to improve performance)
if (strToSearch) {
// if the account had a name (it always should, but catch in case)
// then check if the row's account contains the text entered in the filter field
accountMatch = strToSearch.includes(expression.account.toLowerCase());
} else {
accountMatch = false;
}
}
if (expression.payee){
if (val.payee) {
uuidToSearch = val.payee
strToSearch = $rootScope.dbCtrl.getPayeeName(uuidToSearch).toLowerCase();
}
if (strToSearch) {
payeeMatch = strToSearch.includes(expression.payee.toLowerCase());
} else {
payeeMatch = false;
}
}
if (expression.category) {
if (val.category) {
strToSearch = $rootScope.dbCtrl.getCategoryName(val.category, val.date).toLowerCase()
categoryMatch = strToSearch.includes(expression.category.toLowerCase())
} else {
categoryMatch = false;
}
}
return (
accountMatch &&
payeeMatch &&
categoryMatch
)
})
}
});

Interfacing old system with new Angular 4 frontend

We are in the process of converting our systems over to Angular 4. Since the conversion will take some time, we were hoping to convert our components one by one into angular and have angular wrap around our old pages and run as they do now in conjunction with the new angular components. In our current system is run in multiple frames, header, navigation, working area. We are removing frames in our new angular system.
Our old system primarily replaces the contents of the the working area with a new html page which is entirely composed on the server.
I've seen code to launch an iframe from a component.html and then attach a listener to catch onClicks to anything on that frame. I want to evaluate those click actions from the old system then determine whether to allow the href (etc..) to continue as it use to or re-rout it to the new angular router (ie: this.router.navigate(['/customer']) ) and stop the requested event. I'm able to get most of this to work, but when my new function runs it doesn't have access to needed pieces of the component, methods and properties are not found. (things like str.replace(), str.match() which are normal javascript methods used in conjunction with regexp , but also properties included in the component, like router.navigate)
Can this be done or is there a better way to accomplish this?
I've also looked at the alt-angularjs-migration-using-iframes-demo which I run into the same situation.
Here is how I am currently trying to do this:
contact.component.html
<div class="iBody1" (click)="onRightClick($event)">
<iframe #iframe [src]="'https://www.volgistics.com/ex/Cp.dll?ACT=7' +
urlUserStr | safeUrl" (load)="myFunction()"
id="work1" name="WorkArea" scrolling="no" marginwidth="0" marginheight="0"
frameborder="no" >
<p>Your browser does not support iframes.</p>
</iframe>
</div>
contact.component.ts
myFunction() {
let doc = this.iframe.nativeElement.contentDocument ||this.iframe.nativeElement.contentWindow;
if (typeof doc.addEventListener !== undefined) {
doc.addEventListener("click", this.iframeClickHandler, false)
} else if (typeof doc.attachEvent !== undefined) {
doc.attachEvent("onclick", function (this) {this.iframeClickHandler(this)} )
}
}
iframeClickHandler(e): void {
e.preventDefault();
let vars : string = e.target;
let rx = new RegExp("([^?=&]+)(=([^&]*))?", "g");
let xx = rx.exec( vars );
// let var1 : String = vars.match(/(\?|\&)([^=]+)\=([^&]+)/g).toString(); // match is undefined if used
// let var2 : String = JSON.parse('{"' + decodeURI(var1.replace(/\?/g, "").replace(/&/g, "\",\"").replace(/=/g,"\":\"")) + '"}') // replace is undefined if used
this.router.navigate(['/customer/100100109']); // Uncaught TypeError: Cannot read property 'navigate' of undefined
}
Thanks for any help in advance.
Rob Thompson
I'm not sure if this helps you in any way, but we have achieved quite satisfactory results with splicing angular on top of an existing application by ignoring angular's routing and just slowly replacing old server-site generated html with angular components.
If I understand your case correctly, you want hrefs from within the iframe to navigate to pages in angularJS. The approach we used was we'd actually let it navigate to the page, and then the html of that page would just be
<our-new-component></our-new-component>
which would display our component
Once the transformation is done, you can switch to full angular incl. routing.

Prototyping an Angular Directive

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.

Auto-load/include for JavaScript

I have file called common.js and it's included in each page of my site using <script />.
It will grow fast as my sites functionality will grow (I hope; I imagine). :)
Lets example I have a jQuery event:
$('#that').click(function() {
one_of_many_functions($(this));
}
For the moment, I have that one_of_many_functions() in common.js.
Is it somehow possible that JavaScript automatically loads file one_of_many_functions.js when such function is called, but it doesn't exist? Like auto-loader. :)
The second option I see is to do something like:
$('#that').click(function() {
include('one_of_many_functions');
one_of_many_functions($(this));
}
That not so automatically, but still - includes wanted file.
Is any of this possible? Thanks in an advice! :)
It is not possible to directly auto-load external javascripts on demand. It is, however, possible to implement a dynamic inclusion mechanism similar to the second route you mentioned.
There are some challenges though. When you "include" a new external script, you aren't going to be able to immediately use the included functionality, you'll have to wait until the script loads. This means that you'll have to fragment your code somewhat, which means that you'll have to make some decisions about what should just be included in the core vs. what can be included on demand.
You'll need to set up a central object that keeps track of which assets are already loaded. Here's a quick mockup of that:
var assets = {
assets: {},
include: function (asset_name, callback) {
if (typeof callback != 'function')
callback = function () { return false; };
if (typeof this.assets[asset_name] != 'undefined' )
return callback();
var html_doc = document.getElementsByTagName('head')[0];
var st = document.createElement('script');
st.setAttribute('language', 'javascript');
st.setAttribute('type', 'text/javascript');
st.setAttribute('src', asset_name);
st.onload = function () { assets._script_loaded(asset_name, callback); };
html_doc.appendChild(st);
},
_script_loaded: function (asset_name, callback) {
this.assets[asset_name] = true;
callback();
}
};
assets.inlude('myfile.js', function () {
/* do stuff that depends on myfile.js */
});
Sure it's possible -- but this can become painful to manage. In order to implement something like this, you're going to have to maintain an index of functions and their corresponding source file. As your project grows, this can be troublesome for a few reasons -- the 2 that stick out in my mind are:
A) You have the added responsibility of maintaining your index object/lookup mechanism so that your scripts know where to look when the function you're calling cannot be found.
B) This is one more thing that can go wrong when debugging your growing project.
I'm sure that someone else will mention this by the time I'm finished writing this, but your time would probably be better spent figuring out how to combine all of your code into a single .js file. The benefits to doing so are well-documented.
I have created something close to that a year ago. In fact, I have found this thread by search if that is something new on the field. You can see what I have created here: https://github.com/thiagomata/CanvasBox/blob/master/src/main/New.js
My project are, almost 100% OOP. So, I used this fact to focus my solution. I create this "Class" with the name "New" what is used to, first load and after instance the objects.
Here a example of someone using it:
var objSquare = New.Square(); // Square is loaded and after that instance is created
objSquare.x = objBox.width / 2;
objSquare.y = objBox.height / 2;
var objSomeExample = New.Stuff("some parameters can be sent too");
In this version I am not using some json with all js file position. The mapping is hardcore as you can see here:
New.prototype.arrMap = {
CanvasBox: "" + window.MAIN_PATH + "CanvasBox",
CanvasBoxBehavior: "" + window.MAIN_PATH + "CanvasBoxBehavior",
CanvasBoxButton: "" + window.MAIN_PATH + "CanvasBoxButton",
// (...)
};
But make this more automatic, using gulp or grunt is something what I am thinking to do, and it is not that hard.
This solution was created to be used into the project. So, the code may need some changes to be able to be used into any project. But may be a start.
Hope this helps.
As I said before, this still is a working progress. But I have created a more independent module what use gulp to keep it updated.
All the magic que be found in this links:
https://github.com/thiagomata/CanvasBox/blob/master/src/coffee/main/Instance.coffee
https://github.com/thiagomata/CanvasBox/blob/master/src/node/scripts.js
https://github.com/thiagomata/CanvasBox/blob/master/gulpfile.js
A special look should be in this lines of the Instance.coffee
###
# Create an instance of the object passing the argument
###
instaceObject = (->
ClassElement = (args) ->
window[args["0"]].apply this, args["1"]
->
ClassElement:: = (window[arguments["0"]])::
objElement = new ClassElement(arguments)
return objElement
)()
This lines allows me to initialize a instance of some object after load its file. As is used in the create method:
create:()->
#load()
return instaceObject(#packageName, arguments)

Categories

Resources