Angular directive and SVG - javascript

so, this is a little complicated.
I have a directive that loads an SVG file based on some criteria. It is a large directive so I won't be posting it here as it does not affect my issue, you just need to know it exists.
In that directive I have a template file that has this bit of HTML:
<div ng-include="svgPath" onload="loaded()"></div>
note the onload function. This is called when the SVG has loaded.
Inside this SVG there are some hidden groups that I need to interact with. Because this is changing the DOM I have written another directive, which looks like this:
.directive('kdGraphicsRepeater', function () {
return {
restrict: 'A',
scope: {
targetElementId: '#kdGraphicsRepeater'
},
templateUrl: '/assets/tpl/directives/graphicsRepeater.tpl.html',
link: function (scope, element) {
// Declare our options
scope.options = [];
// Get our target elements
var target = angular.element(document.getElementById(scope.targetElementId)),
svg = angular.element(target.find('svg')),
children = svg.children();
console.log(target.length);
console.log(svg.length);
console.log(children.length);
// Loop through the SVG children
for(var i = 0; i < children; i++) {
// Get the current child
var child = children[i],
childId = child.attr('id');
console.log(childId);
// If we have an option
if (childId && childId.indexOf('options-') > -1) {
// Push the name to our options array
scope.options.push(childId.replace('options-'));
}
}
}
}
})
and the corresponding template looks like this:
<div ng-repeat="item in options">
{{ item }}
</div>
As you should be able to make out. I am trying to loop through the SVG immediate children and check to see if any of the elements have a partial id match to options-. If they do I add them (omitting the "options-" string) to the scope.options variable. My template simply loops through them displays them.
The problem is that the SVG has not loaded for this directive and the console.log(svg.length) returns 0 for that very reason. I need some way of knowing when the SVG has loaded.
Can someone help me out?

Okay, I'm not sure about this but maybe try using the ng-if directive by angular.
in your loaded() function, have like a boolean that says its loaded, say its called svgLoaded. use ng-init instead of onload. Make sure this would be the same scope as the one in your svg.
then in your svgPath, bind ng-if to your boolean
<"element where you are attaching kdGraphicsRepeater"
ng-if="svgLoaded" kdGraphicsRepeater
>
Sorry, I'm making assumption that you have same scope, and that is possible to do.

Related

AngularJS bind data to a compiled element

I'm creating elements inside a for loop using $compile and I need to bind data to them.
for (var item in data) {
var elem = $compile('<panel ng-model="item"></panel>')($scope)[0];
container.append(elem);
}
How can I get the panel directive to access the item data? I have to append the elements manually, so I can't use ng-repeat.
Don't do a loop and compile for each item. That would be bad performance for your app. Do:
$scope.data = data;
var elem = $compile('<div ng-repeat="item in data"><panel ng-model="item"></panel></div>')($scope)[0];
container.append(elem);
You place data on the $scope, because that's how the binding occurs. Note that the HTML you write is compiled against a specific $scope.
I think you can do without the [0] as well, but not sure.
I've solved my problem like this:
for (var index in data) {
var elem = $compile('<panel data="data[' + index + ']"></panel>')($scope)[0];
container.append(elem); // pseudocode line
}

Using CDN with <img> tags

I have just added a CDN service to make my website load faster.
I have a question regarding photos that are fetched using a <img> tag.
I have many cases like this:
// userProfile.thumbnailPhotoSrc = some/relative/path.png
ng-src="{{userProfile.thumbnailPhotoSrc}}"
(I use AngularJS).
On startup, I have a script that saves the CDN endpoint to window.cdn variable. In case the script decides there's no endpoint available:
window.cdn = '';
So I want to be able to do something like:
ng-src="window.cdn + {{userProfile.thumbnailPhotoSrc}"
But this is impossible, as window.cdn is not evaluated. But neither this is working:
ng-src="{{window.cdn + userProfile.thumbnailPhotoSrc}"
Because I can only access $scope properties under {{}}.
I can't save the CDN endpoint to $scope, because there exists a $scope for every controller (I have many, this is not maintainable).
The last thing I thought of, hoping ng-src allows that - is adding a transformation function that is run on any ng-src attribute, but I could not find how to do that.
What do you suggest I do? keep in mind that I would like the website fetch the photo from the CDN and fallback to the origin server (fetch the relative path) in case of CDN malfunction. How can I obtain that behavior?
Thanks
You could create a custom filter in Angular
You would then have then in your templates:
ng-src="{{userProfile.thumbnailPhotoSrc | cdn}"
You could add the $window service as a dependency to the filter service so you don't have to pull it from the global object and you can test the filter as well.
If you are trying to update all the <img> tag's src. You can create a directive:
angular.module('imageCdnSrc', [])
.directive('img',['$interpolate', function($interpolate) {
return {
restrict: 'E',
scope: false,
compile: function(element, attrs) {
var ngSrc = attrs.ngSrc;
delete attrs.ngSrc; // prevent ngSrc directive to trigger
return function($scope,element,attrs) {
var ngSrcVal = $interpolate(ngSrc)($scope); //returns the string value of ngSrc
/* Add Logic Below to decide what to do */
if(window.cdn && ngSrcVal && ngSrcVal.length>0){
attrs.ngSrc = window.cdn + ngSrcVal;
/* if image fails to load with cdn, load the default */
element.one('error', function() {
angular.element(this).attr("src", ngSrcVal);
});
}
else{
attrs.ngSrc = ngSrcVal;
}
};
}
};
}]);

Efficient OOP way to generate DOM elements dynamically with JavaScript?

I'm tinkering with writing a more efficient methodology in the creation of dynamically generated DOM elements via JavaScript. This is something I intend to add into my own JS framework later on. Looking for other OOP devs that could help better refine what I do have.
Here's a link to the working CodePen:
http://codepen.io/DaneTheory/pen/yeLvmm/
Here's the JS:
function CreateDOMEl() {};
CreateDOMEl.prototype.uiFrag = document.createDocumentFragment();
CreateDOMEl.prototype.elParent = function(elParent, index) {
this.elParent = document.getElementsByTagName(elParent)[index];
}
CreateDOMEl.prototype.elType = function(type) {
newEl = document.createElement(type);
this.uiFrag.appendChild(newEl);
}
CreateDOMEl.prototype.elContent = function(elContent) {
this.elContent = elContent;
newEl.textContent = elContent;
}
CreateDOMEl.prototype.buildEl = function() {
this.elParent.appendChild(this.uiFrag);
}
var div = new CreateDOMEl();
div.elParent('body', 0);
div.elType('DIV');
div.elContent('OK');
div.buildEl();
console.log(div);
var bttn = new CreateDOMEl();
bttn.elParent('body', 0);
bttn.elType('BUTTON');
bttn.elContent('SUBMIT');
bttn.buildEl();
console.log(bttn);
And some CSS to get elements to appear on page:
div {
width:100px;
height:100px;
border: 1px solid red;
}
My thoughts:
For performance, using the prototype to build methods versus placing all the logic in the constructor.
Rather than directly appending elements to the page, append to a single Document Fragment. Once the element is built out as a Doc Frag, appending the Doc Frag to to the DOM. I like this method for performance, but would like to improve upon it. Any useful implementations of requestnimationFrame, or using range and other versions of the document fragment method?
Silly, but I think for debugging it'd be nice to see the generated Element type within the Object property's on console log. As of right now, console logging a created element will show the elements parent and text content. It'd be great to show the elements type as well.
Creating more than one element at a time is another piece of functionality I'd like to offer as an option. For instance, creating a div element creates one div element. What's a good way to add another optional method to create multiple instances of div's.
div.elType('DIV');
// After calling the elType method, do something like this:
div.elCount(20);
// This would create 20 of the same divs
Lastly, a nice clean way to optionally add attributes (i.e: classes, an ID, value, a placeholder, custom attributes, data-* attributes, etc.). I've got a nice helper function I use that adds multiple attributes to an element in an object literal syntax looking way. Adding this as a method of the constructor would be ideal. Here's that function:
function setAttributes(el, attrs) {
for(var key in attrs) {
el.setAttribute(key, attrs[key]);
}
}
// A use case using the above
// function would be:
var anInputElement = document.createElement("TEXTAREA");
setAttributes(anInputElement, {
"type": "text",
"id": "awesomeID",
"name": "coolName",
"placeholder": "Hey I'm some placeholder example text",
"class": "awesome"
});
// Which creates the following HTML snippet:
<textarea type="text" id="awesomeID" name="coolName" placeholder="Hey I'm some placeholder example text" class="awesome">
As a side note, realizing now that the above helper function needs rewritten so that multiple classes could be created.
Respectfully, I believe you may be overthinking it. Just use the tools available in JavaScript and get 'er done. In terms of performance, computers are so fast at running your JavaScript that you (and me) are unable to perceive, or even comprehend, the speed. Here's how I add a link to an MDL nav menu, for example. It's just vanilla JS. Don't forget to add event listeners.
function navMenuAdd(type,text){
var newAnchor = doc.createElement("anchor");
newAnchor.classList.add('mdl-navigation__link');
newAnchor.classList.add(type);
newAnchor.href = "javascript:void(0)";
var anchorContent = doc.createTextNode(text);
newAnchor.appendChild(anchorContent);
newAnchor.addEventListener('click', navMenuClickHandler, false);
//newAnchor.style.display = 'none';
if (type === 'Thingy A'){
//insertAfter(newAnchor, navMenuCredentials);
navMenuCredentialsPanel.appendChild(newAnchor);
} else if (type === 'Thingy B'){
//insertAfter(newAnchor, navMenuDevices);
navMenuDevicesPanel.appendChild(newAnchor);
}
}

Angular: Selecting elements from DOM

I'm using an ng-repeat to create items. I would like to determine the height of each element that is created by using a function.
I know how to pass the index of the element that is created by ng-repeat to the function that should determine the height, but I'm getting stuck in actually selecting that item.
This is what I'm using now:
$scope.getItemHeight = function(index) { // index is index of element in ng-repeat
var itemHeight = angular.element('li').eq('+index+').offsetHeight;
return itemHeight;
};
But that doesn't work due to error: Error: [jqLite:nosel] Looking up elements via selectors is not supported by jqLite!
I also tried:
$scope.getItemHeight = function(index) {
var itemHeight = document.querySelectorAll('ul:nth-child('+index+')');
return itemHeight;
};
This returns an element with length 0 so the selector doesn't work.
What am I missing here?
Codepen here: http://codepen.io/squrler/pen/LxsfE?editors=101
EDIT:
What I want is not possible at this time. More information here: https://github.com/driftyco/ionic/issues/1691
Edit: After looking at this further it seems a bit more complicated. The directive that is firing the repeater needs to run in order for the lis to be rendered, as the li is rendered (assuming you move this to a directive) it triggers the directive to get its height, but the li and it's corresponding data are not yet fully rendered and thus have no height. If you wait for the rendering using timeout, the repeater will just continue rendering without valid height data while the timeout waits. So it seems you have a bit of a conundrum. You might try using http://binarymuse.github.io/ngInfiniteScroll/ or something of the like.
This should be put in a directive which will give you easy access to the li as it is rendered.
Something like:
.directive('getHeight', ['$filter', function ($filter) {
'use strict';
return {
restrict: 'A',
scope: true,
link: function (scope, element, attrs) {
var li = element[0];
var height = li.offsetHeight;
console.log('Height': height)
}
};
}]);
Not sure what you're looking to do with the height once you have it...
Otherwise you can just go:
var ul=document.getElementsByTagName('ul')[0];
var li=ul.getElementsByTagName('li')[index];
var height=li.offsetHeight;

Angular.js ng-style won't bind value

I've got a problem with angularjs and even after research I just couldn't find where I'm wrong.
I need to recalculate the css value "left" for an element. I'm using the 'ng-style' directive and a method that will return an object with the css value. Thats - afaik - what I have to do. But when I update the value, it wont update the style.
ng-bind usage:
<div ng-style="getCssShiftObject()">
method to create object
$scope.getCssShiftObject =function(){
return {'left':this.cssShift+'px'};
};
method to change the object
$scope.nextPosition = function(){
if((this.currentPosition+1) <= this.maxPosition){
this.currentPosition = this.currentPosition+1;
this.cssShift = (this.currentPosition*this.slideSize)*-1;
}
return this.currentPosition;
};
It will update at another place in the content when I use it like that:
{{getCssShiftObject()}}
I hope you can give mit a hit, thanks for your time!
I came across a similar problem. I was trying to use ngStyle to load a background image, but if the variable in an expression is not immediately available (which might be the case if it's part of a resource promise), it won't work.
To address this, I created my own ngStyle directive that addresses this issue. Hopefully this is better than creating functions for every single scenario where you want to use ngStyle in this way.
app.directive("myStyle", function (){
return {
restrict: 'A',
link: function(scope, element, attrs)
{
var el = element[0],
attr = el.getAttribute('style');
el.setAttribute('style', attr);
// We need to watch for changes in the style in case required data is not yet ready when compiling
attrs.$observe('style', function (){
attr = el.getAttribute('style');
if(attr)
{
el.setAttribute('style', attr);
}
});
}
};
});
Then, you can use it this way:
<a my-style style="background-image: url('{{promise.myImage}}')"></a>
Thx for your time! I solved the Problem with the input from Cherniv, but I'm not sure how. I changed the way I create the values. Now it's working.
$scope.calcCssShift = function(){
this.cssShift = ($scope.currentPosition * $scope.slideSize)*-1;
};
$scope.getCssShiftObject =function(){
return {'left':$scope.cssShift+'px'};
};
$scope.nextPosition = function(){
if((this.currentPosition+1) <= this.maxPosition){
$scope.currentPosition = this.currentPosition+1;
$scope.calcCssShift();
}
};
I had a similar problem with the style attribute. My binding was not working in some browsers, especially IE. I solved it by using ng-attr-style="{{yourBindingExpression}}".
Read more about ng-attr interpolation at https://docs.angularjs.org/guide/interpolation

Categories