If element exists by data attribute - angularjs

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.

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.

Angularjs: automatically update date diff?

I'm using angular 1.6.1 and I would like to update date diff automatically.
Here is the service that calculate date diff
angular.module("utils", [])
.service('utils', function ($location) {
this.correctDate = function(date) {
var s = date.replace('T', ' ');
return s.replace('.000Z', ' ');
}
this.dateDiff = function(date) {
var oneMinute = 60*1000;
var oneHour = 60*60*1000;
var oneDay = 24*60*60*1000;
var oneWeek = 24*60*60*1000*7;
var oneMonth = 24*60*60*1000*30;
var now = new Date();
var d = new Date(date);
var tNow = now.getTime();
var tDate = d.getTime();
if((tNow - tDate) / oneMonth >= 1){
return Math.floor((tNow - tDate) / oneMonth) + " months";
}
if((tNow - tDate) / oneDay >= 1){
return Math.floor((tNow - tDate) / oneDay) + " days";
}
if((tNow - tDate) / oneHour >= 1){
return Math.floor((tNow - tDate) / oneHour) + " hours";
}
if((tNow - tDate) / oneMinute >= 1){
return Math.floor((tNow - tDate) / oneMinute) + " minutes";
}
return "now";
}
})
Here is the view
<div class="post__date">
<time class="published">
{{dateDiff(correctDate(article.createdAt))}}
</time>
</div>
Now date diff is updated only if I make a click anywhere in app and I even don't understand why. So I have 2 questions :
1) Why does date diff is updated everytime I click anywhere in my angular app ? (Does angular trigger a full digest everytime I click anywhere in app ?)
2) How to update automatically date diff ?

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'

How can I load youtube videos with angularjs?

I am trying to delay loading of youtube videos via javascript code as explained here.
and here is the code for that:
function optimizeYouTubeEmbeds() {
// Get all iframes
var frames = document.getElementsByTagName('iframe');
// Loop through each iframe in the page.
for (var i = 0; i < frames.length; i++) {
// Find out youtube embed iframes.
if (frames[i].src && frames[i].src.length > 0 && frames[i].src.match(/http(s)?:\/\/www\.youtube\.com/)) {
// For Youtube iframe, extract src and id.
var src = frames[i].src;
var p = /^(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(?:\S+)?$/;
var id = (src.match(p) ? RegExp.$1 : false);
if (id == false) { continue; }
// Get width and height.
var w = frames[i].width;
var h = frames[i].height;
if (src == '' || w == '' || h == '') { continue; }
// Thease are to position the play button centrally.
var pw = Math.ceil(w / 2 - 38.5);
var ph = Math.ceil(h / 2 + 38.5);
// The image+button overlay code.
var code = '<img src="http://i.ytimg.com/vi/' + id + '/hqdefault.jpg" style="" />';
// Replace the iframe with a the image+button code.
var div = document.createElement('div');
div.innerHTML = code;
div = div.firstChild;
frames[i].parentNode.replaceChild(div, frames[i]);
i--;
}
}
}
// Replace preview image of a video with it's iframe.
function LoadYoutubeVidOnPreviewClick(id, w, h) {
var code = '<iframe src="https://www.youtube.com/embed/' + id + '/? autoplay=1&autohide=1&border=0&wmode=opaque&enablejsapi=1" width="' + w + '" height="' + h + '" frameborder=0 allowfullscreen style="border:1px solid #ccc;" ></iframe>';
var iframe = document.createElement('div');
iframe.innerHTML = code;
iframe = iframe.firstChild;
var div = document.getElementById("skipser-youtubevid-" + id);
div.parentNode.replaceChild(iframe, div);
}
This code worked before switching my project to the angular way of doing things.
I initially thought I could just wrap that code in a $scope and call it, but that did not work. Here is my code that retrieves the videos:
$scope.renderHtml = function (html) {
return $sce.trustAsHtml(html);
};
$scope.Videos = [];
$scope.getVideos = function() {
// api call
$http.get('/api/Videos/AllVideos')
.success(function(data) {
$scope.Videos = data;
});
};
$scope.getVideos();
and here is the html tag where it is rendered:
<div ng-repeat="vid in Videos">
<div ng-bind-html="renderHtml(vid.url)"></div>
</div>
The vid.url includes all the iframe tag information etc. What do I need to do to make this work?

angular factory to modify svg on page

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);
});
};
}
]);

Resources