angular factory to modify svg on page - angularjs

This is a follow-up to this question, though they're only loosely related.
My factory is intended to place some svg boxes around some breadcrumb links.
My question is: is a factory the right way to do this? A factory should produce something and hand it back. Can I operate on an existing element? Or should I rip the element out of my HTML and have the whole HTML block generated in my factory, then return it to my controller?
Controller:
...
$scope.linkStages = [{'view': 'twitter'}, {'view': 'ad'}, {'view':'campaign'}];
...
var coordinates = function(i) {
i = Number(i);
var x1 = i * stagesWidth + ',' + 0 + ' ';
var x2 = i * stagesWidth + ',' + stagesHeight + ' ';
var x3 = (i + 1) * stagesWidth + ',' + stagesHeight + ' ';
var x4 = (i + 1) * stagesWidth + stagesHeight/2 + ',' + (stagesHeight/2) + ' ';
var x5 = (i + 1) * stagesWidth + ',' + 0;
return x1 + x2 + x3 + x4 + x5;
};
/*
* Calculate the position of the text inside a polygon
* type 'y' will give the y coordinate
* type not 'y' will given you the x coordinate
* i is the position the polygon appears in the guide bar
*/
var textCoord = function(i, type) {
if (type === 'y') {
return (stagesHeight/2) + 5;
} else {
return ((Number(i) + 0.5) * stagesWidth);
}
};
Factory:
// add fields to linkStage objs for visual display
for (var i in linkStages) {
linkStages[i].title = '';
linkStages[i].points = coordinates(i);
linkStages[i].textX = textCoord(i, 'x');
linkStages[i].textY = textCoord(i, 'y');
}
HTML:
<span class="exportBar">
<div class='svgContainer'>
<svg sys-width='{{svgWidth}}' height='35' >
<polygon ng-repeat="stage in linkStages.slice().reverse()" sys-points="{{stage.points}}" ng-class="exportView === stage.view ? 'selected': 'none'"/>
<text ng-repeat="stage in linkStages" text-anchor="middle" sys-x="{{stage.textX}}" sys-y="{{stage.textY}}">{{stage.title | cut:false:12:'...'}}</text>
</svg>
</div>
</span>
Directive:
angular.module('sysomos.ads').
directive('sysX', [
function() {
return function(scope, element, attrs) {
attrs.$observe('sysX', function(value) {
attrs.$set('x', value);
});
};
}
])
...
directive('sysPoints', [
function() {
return function(scope, element, attrs) {
attrs.$observe('sysPoints', function(value) {
attrs.$set('points', value);
});
};
}
]).
directive('sysWidth', [
function() {
return function(scope, element, attrs) {
attrs.$observe('sysWidth', function(value) {
attrs.$set('width', value);
});
};
}
]);

Related

adding ng-click attribute on highcharts tooltip

I'm trying to add an ng-click attribute in the return of my formatter object in highcharts:
formatter: function() {
var item = "";
if (this.series.name == fluidVsConsumerDaily.pondSelection.pondName) {
item = angular.element('<a> Pond Volume Level: <b>' + this.y + '(BBL)</b></a>');
} else {
item = angular.element('<button ng-click="vCtrl.section.dashboard.fluidVsConsumerDaily.openFluidSourceModal(' + this.series.options.fluidSourceFacilityId + ')"> Fluid Source: <b>' + this.series.options.fluidSourceName + '</b> - ' + this.y + '(BBL) </button>');
}
var element = $compile(item)(vm);
$scope.$digest();
return element.html();
},
Unfortunately the click still doesn't work. When i'm inspecting the DOM, ng-click is present.
Thanks in advance.

If element exists by data attribute

I have a number of spans being created with ng-repeat:
<div class="row" id="year-1">
<span class="event" ng-repeat="(key, event) in events" event data-start={{event.date_start}} data-end={{event.date_end}} data-key={{key}} data-type={{event.role}}>
{{event.title}} - {{event.date_start}}
</span>
</div>
I have a directive for event which does a number of things to manipulate each span created accordingly. One of the things is to check is there are other spans with data-type="X".
In my directive, if I do the following, I get all the span's with class 'event':
var parentid = angular.element(document.getElementById('year-1'));
var typeExists = parentid[0].querySelectorAll('.event')[0];
But if I try to narrow it down to data-type="X" such as the following, I get undefined.
var typeExists = parentid[0].querySelectorAll('.event[data-type="' + attr.type + '"]')[0];
Am I overlooking something? Full directive:
angular.module("app").directive("event", function() {
return {
link: function(scope, element, attr) {
var getStart = attr.start.split('-'),
getEnd = attr.end.split('-'),
getKey = attr.key;
getHeight = element[0].offsetHeight;
var parentid = angular.element(document.getElementById('year-1')),
backgroundParent = angular.element(document.getElementsByClassName('year-current'));
// get the month event starts with
var monthStart = angular.element(document.querySelectorAll('[data-location="' + getStart[1] + '"]'));
// get the month event ends with
var monthEnd = angular.element(document.querySelectorAll('[data-location="' + getEnd[1] + '"]'));
// how many events do we have
var eventcount = angular.element(document.getElementsByClassName('event'));
// get width of events container
var eventsContainer = angular.element(document.getElementById('events'));
// does this type exist already, if so get its top
var typeExists = parentid[0].querySelectorAll('.event')[0];
console.log(typeExists);
if(monthStart.length > 0) {
// how many days in start month
var daysStart = getDaysInMonth(getStart[1], getStart[0]),
daysStartPercent = (getStart[2] / daysStart.length);
// how many days in end month
var daysEnd = getDaysInMonth(getEnd[1], getEnd[0]),
daysEndPercent = (getEnd[2] / daysEnd.length);
// determine left starting %
var elementLeft = ((monthStart[0].offsetLeft + (monthStart[0].clientWidth * daysStartPercent)) / eventsContainer[0].clientWidth) * 100;
// determine width in %
var elementRight = ((monthEnd[0].offsetLeft + (monthEnd[0].clientWidth * daysEndPercent)) / eventsContainer[0].clientWidth) * 100;
var width = (elementRight - elementLeft);
// get the background color for this role
var background = angular.element(document.querySelector('.role[data-type="' + attr.type + '"]'))[0].getAttribute('data-background');
element.css({
'left': elementLeft + '%',
'top' : parentid[0].offsetHeight + 'px',
'width': width + '%',
'background': background
});
element.addClass('stretchRight');
parentid.css({'height': parentid[0].offsetHeight + getHeight + 'px'});
backgroundParent.css({'height': parentid[0].offsetHeight + getHeight + 'px'});
} else {
element.css({ 'display': 'none' });
}
}
}
});
I was able to recreate the problem you saw and found that you need to change your line from this:
var typeExists = parentid[0].querySelectorAll('.event[data-type="' + attr.type + '"]')[0];
to this:
var typeExists = parentid.querySelectorAll('.event[data-type="' + attr.type + '"]')[0];
Remove the [0] from your parentid reference because you only have a single element with ID of year-1 which means it's not an array.

Meaning of hyphen in Angular tags

I found a bit of code on Plunker which I don't understand. It's a word cloud where the cloud is added to the page with:
<tang-cloud words="words" on-click="test(word)" width="500" height="500"></tang-cloud>
This is some how picked up by Angular. What I don't understand is I can find no references to "tang-cloud" in the rest of the code. Various "tangcloud" but nothing with a hyphen.
I'm totally new to Angular, I've stumbled across another case where this seems to happen, but all the tutorial cases I've seen would have used "tangcloud". If I remove the hyphen it stops working, so I must just be missing something simple.
Thank you
It's a directive. Since HTML is case-insensitive, angular converts the tangCloud directive to tang-cloud to be readable by HTML.
The tangCloud directive in tangCloud.js is where you'll find the code for that.
Edit: Just to follow up, you see the bit that says restrict: 'E'? That tells angular that you can use the directive as a custom element. When you make a directive camelcase, like tangCloud, angular will automatically convert it to tang-cloud.
.directive('tangCloud', ['$interpolate', '$compile', '$timeout', function ($interpolate, $compile, $timeout) {
var directive = {
restrict: 'E',
scope: {
width: '=',
height: '=',
words: '=',
onClick: '&',
spin: '='
},
template: function (tElement, tAttrs) {
var isClickable = angular.isDefined(tAttrs.onClick);
var clickAttr = isClickable ? 'ng-click="onClick({word : entry.word, id : entry.id})"' : '';
return "<div class='tangcloud'>" +
"<span ng-repeat='entry in words'" + clickAttr + ">{{entry.word}}</span>" +
"</div>";
},
compile: function (elem) {
elem.children().children()
.addClass('tangcloud-item-' + $interpolate.startSymbol() + 'entry.size' + $interpolate.endSymbol())
.addClass('tangcloud-item-hidden');
return function (scope, elem) {
var centerX = scope.width / 2;
var centerY = scope.height / 2;
var outOfBoundsCount = 0;
var takenSpots = [];
if (scope.words) {
scope.words = shuffleWords(scope.words);
determineWordPositions();
}
function shuffleWords(array) {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
function determineWordPositions() {
$timeout(function () {
var trendSpans = elem.children().eq(0).children();
var length = trendSpans.length;
for (var i = 0; i < length; i++) {
setWordSpanPosition(trendSpans.eq(i));
}
});
}
function setWordSpanPosition(span) {
var height = parseInt(window.getComputedStyle(span[0]).lineHeight, 10);
var width = span[0].offsetWidth;
var spot = setupDefaultSpot(width, height);
var angleMultiplier = 0;
while (spotNotUsable(spot) && outOfBoundsCount < 50) {
spot = moveSpotOnSpiral(spot, angleMultiplier);
angleMultiplier += 1;
}
if (outOfBoundsCount < 50) {
takenSpots.push(spot);
addSpanPositionStyling(span, spot.startX, spot.startY);
}
outOfBoundsCount = 0;
}
function setupDefaultSpot(width, height) {
return {
width: width,
height: height,
startX: centerX - width / 2,
startY: centerY - height / 2,
endX: centerX + width / 2,
endY: centerY + height / 2
};
}
function moveSpotOnSpiral(spot, angleMultiplier) {
var angle = angleMultiplier * 0.1;
spot.startX = centerX + (1.5 * angle) * Math.cos(angle) - (spot.width / 2);
spot.startY = centerY + angle * Math.sin(angle) - (spot.height / 2);
spot.endX = spot.startX + spot.width;
spot.endY = spot.startY + spot.height;
return spot;
}
function spotNotUsable(spot) {
var borders = {
left: centerX - scope.width / 2,
right: centerX + scope.width / 2,
bottom: centerY - scope.height / 2,
top: centerY + scope.height / 2
};
for (var i = 0; i < takenSpots.length; i++) {
if (spotOutOfBounds(spot, borders) || collisionDetected(spot, takenSpots[i])) return true;
}
return false;
}
function spotOutOfBounds(spot, borders) {
if (spot.startX < borders.left ||
spot.endX > borders.right ||
spot.startY < borders.bottom ||
spot.endY > borders.top) {
outOfBoundsCount++;
return true;
} else {
return false;
}
}
function collisionDetected(spot, takenSpot) {
if (spot.startX > takenSpot.endX || spot.endX < takenSpot.startX) {
return false;
}
return !(spot.startY > takenSpot.endY || spot.endY < takenSpot.startY);
}
function addSpanPositionStyling(span, startX, startY) {
var style = "position: absolute; left:" + startX + "px; top: " + startY + "px;";
span.attr("style", style);
span.removeClass("tangcloud-item-hidden");
}
};
}
};
return directive;
}]);
The tang-cloud directive is defined as tangCloud - take this example from the angular docs for directive
app.js
.directive('myCustomer', function() {
return {
template: 'Name: {{customer.name}} Address: {{customer.address}}'
};
});
index.html
<div ng-controller="Controller">
<div my-customer></div>
</div>
See the Normalization section in this part of the docs. Try searching 'tangCloud'

directive function calling multiple times in angularjs

I have created directive in anguular js to resize image in angularjs.
Here is the code of directive :
app.directive('resize1', function ($window) {
return {
restrict: 'A',
replace: true,
scope: {
params: '&',
path: '=',
siteurl: '='
},
link: function($scope, element, attrs) {
$scope.resizeImage = function() {
var size = 0;
var width = angular.element($window).width();
src = $(element).attr('src');
if (src != null || src != '') {
var url = parse_url(src).query;
var path = parse_url(src).path;
var str = url.split('&');
var link = '?';
$.each(str, function(index, value) {
pair = value.split('=');
if (pair[0] != 'h' && pair[0] != 'w') {
link = link + value + '&';
}
});
if (width >= 960) {
size = $scope.$eval($scope.params).gt960;
newWidth = size.w;
newHeight = size.h;
} else if (width < 960 && width >= 480) {
size = $scope.$eval($scope.params).bt480960;
newWidth = size.w;
newHeight = size.h;
} else {
size = $scope.$eval($scope.params).lt480;
newWidth = size.w;
newHeight = size.h;
}
link = link + 'h=' + newHeight + '&w=' + newWidth;
$(element).attr('src', path + link);
$(element).attr('ng-src', path + link);
$(element).closest('div.spinner').css({
'height' : newHeight,
'width' : newWidth,
'margin' : '0px auto'
});
}
};
angular.element($window).bind('resize', function() {
$scope.resizeImage();
});
$scope.$watch('path', function (){
//alert('df');
$scope.resizeImage();
});
}
}
});
and my html is :
<div class="list-view" ng-hide="productDetail == null">
<div class="list-view-img">
<div class="spinner">
<img resize1
ng-src="{{siteurl}}thumbnails/index?file={{path}}&h=200&w=200"
params="{lt480: {h: 300,w: 300}, bt480960: {h: 450,w: 450}, gt960: {h: 940,w: 940}}"
path="paths.products.primary_image + productDetail.primary_image"
siteurl="siteUrl"
/>
</div>
</div>
</div>
Now the problem is its working fine but directive function is called 3 times..
Can anybody tell me what can be wrong ? why it is called thrice though i have called it once ?

getClass for actionColumn only in a few rows

I'm using the code below, to set the css class for an action column.
But even if the result is null, some elements are inserted by extjs.
getClass: function(v, meta, data) {
if (data.myDate < new Date())
return null;
else
return 'insert';
}
Generated html for return null:
<img alt="" src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="
class="x-action-col-icon x-action-col-1 null">
The major problem is that cursor is changed to a hand pointer when moving this "blank" space.
There is a way to not generate elements when no icon is to be shown?
I don't see any way to do it without extending action column. IMO easiest way is to provide custom renderer function. Example:
Ext.define('Ext.grid.column.MyAction', {
extend: 'Ext.grid.column.Action',
constructor: function(config) {
var me = this,
cfg = Ext.apply({}, config),
items = cfg.items || [me],
l = items.length,
i,
item,
cls;
me.callParent(arguments);
me.renderer = function(v, meta) {
v = Ext.isFunction(cfg.renderer) ? cfg.renderer.apply(this, arguments)||'' : '';
meta.tdCls += ' ' + Ext.baseCSSPrefix + 'action-col-cell';
for (i = 0; i < l; i++) {
item = items[i];
item.disable = Ext.Function.bind(me.disableAction, me, [i]);
item.enable = Ext.Function.bind(me.enableAction, me, [i]);
cls = (Ext.isFunction(item.getClass) ? item.getClass.apply(item.scope||me.scope||me, arguments) : (me.iconCls || ''));
if (cls !== null) {
v += '<img alt="' + (item.altText || me.altText) + '" src="' + (item.icon || Ext.BLANK_IMAGE_URL) +
'" class="' + Ext.baseCSSPrefix + 'action-col-icon ' + Ext.baseCSSPrefix + 'action-col-' + String(i) + ' ' + (item.disabled ? Ext.baseCSSPrefix + 'item-disabled' : ' ') + (item.iconCls || '') +
' ' + cls + '"' +
((item.tooltip) ? ' data-qtip="' + item.tooltip + '"' : '') + ' />';
}
}
return v;
};
}
});

Resources