Angular UI Boostrap Carousel setting active slide after making new slides - angularjs

I have a simple Carousel example. The example on Plnkr shows what I do in my application. I have to change the slides in my application. When I set the active slide after I change slides it goes to that slide and then slides out into oblivion or it goes to the first slide. How can I solve this problem? So that after making new slides I can go to the right slide?
http://plnkr.co/edit/PJg9U4HZ1k5aSTSvbl3k?p=preview
angular.module('plunker', ['ui.bootstrap', 'ngTouch']);
function CarouselDemoCtrl($scope) {
$scope.genderPerson = "men";
$scope.myInterval = -1;
$scope.slides = [];
$scope.$watch("genderPerson", function( newValue, oldValue ) {
$scope.MakeSlides();
});
$scope.MakeSlides = function() {
var newSlides = [];
for ( var i = 0; i < 10; i++ ) {
newSlides[i] = { image: 'http://api.randomuser.me/portraits/' + $scope.genderPerson + '/' + i + '.jpg' };
}
$scope.slides = newSlides;
if ( $scope.slides[6] ) {
$scope.slides[6].active=true;
}
}
}

Looks like there is a race condition, if I wrap the active slide set in a timeout with a delay it seems to work:
$timeout(function () {
$scope.slides[6].active=true;
}, 100);

I just struggled with this problem. Here's my overly technical hack totally legit solution:
1. Create a new directive that overrides the addSlide method
.directive('onCarouselChange', function ($animate, $parse) {
return {
require: 'carousel',
link: function (scope, element, attrs, carouselCtrl) {
var origAdd = carouselCtrl.addSlide;
carouselCtrl.addSlide = function(slide, element) {
origAdd.apply(this, arguments);
$animate.on('enter', element, function (elem, phase) {
if (phase === 'close') {
scope.$emit('carouselEntered');
}
});
};
}
};
})
This will emit an event when the carousel's ngRepeat has finished parsing its new elements.
2. Add the new directive to the carousel element
<carousel interval="myInterval" on-carousel-change>
3. Set the active slide on an event listener
Add an event listener to the function where you add the elements, and set the active slide on its callback
$scope.MakeSlides = function() {
var newSlides = [];
for ( var i = 0; i < 10; i++ ) {
newSlides[i] = { image: 'http://api.randomuser.me/portraits/' + $scope.genderPerson + '/' + i + '.jpg' };
}
$scope.slides = newSlides;
var dereg = $scope.$on('carouselEntered', function(event, data) {
if ($scope.slides[6]) {
$timeout(function () {
$scope.slides[6].active=true;
});
dereg();
}
});
}
All of this is possible thanks to the magic of $animate's events.

Related

Angular directives collision

I would like to use 2 directives in the same app. The problem is that when I use the second one, the first one crashes with an ugly error: TypeError: Failed to execute 'getComputedStyle' on 'Window': parameter 1 is not of type 'Element'.
The first directive is angular-fullpage.js (https://github.com/hellsan631/angular-fullpage.js) and the second one is angular bootstrap affix implementation (https://github.com/maxisam/angular-bootstrap-affix).
When I include both modules (directives), the fullpage directive crashes with the afformentioned error. If I remove the affix directive, then fullpage.js Works fine (just by removing the second directive from the modules).
How can I avoid directive collision? Are there any workarounds for this issue or should I just settle for just 1 of the directives?
Thanks!!!!
app.js:
var myApp = angular
.module(
'myApp',
[
'ngRoute',
'ngAnimate',
'ngMessages',
'ui.bootstrap',
'angular-loading-bar',
'LocalStorageModule',
'ngEnter',
'ng-Static-Include',
'ngResource',
'toastr',
'ng-Static-Include',
'pageslide-directive',
'ngRutChange',
'xmsbsStopPropagation',
'ngEnter',
'ng-rut',
'ngMessages',
'duScroll',
'dynamicNumber',
'xmsbsDirectives',
'salfaDirectives',
'mgcrea.bootstrap.affix',
'fullPage.js',
'ui.tinymce',
'mega-menu',
'bootstrap.fileField',
'ngTagsInput'
]);
Partial view (home) trying to use the fullpage directive and generating the error:
<div class="section">
<div ng-style="{'width': '100%', 'height': vm.divHeight+'px'}" style="margin-top:-7px;background:url(/content/images/03.jpg) center center; background-size:cover;">
<div class="col-sm-12">
<h1 class="fg-grayLight text-center text-shadow vert-align-center" style="z-index:2;" ng-style="{'padding-top':vm.divHeight/9+'px'}">sistema de recursos humanos 2.0</h1>
</div>
</div>
</div>
<div class="section">
<div class="col-sm-12">
<h2 class="text-center">noticias</h2>
</div>
</div>
Affix directive:
'use strict';
angular.module('mgcrea.bootstrap.affix', ['mgcrea.jquery'])
.directive('bsAffix', function($window, dimensions) {
var checkPosition = function(instance, el, options) {
var scrollTop = window.pageYOffset;
var scrollHeight = document.body.scrollHeight;
var position = dimensions.offset.call(el[0]);
var height = dimensions.height.call(el[0]);
var offsetTop = options.offsetTop * 1;
var offsetBottom = options.offsetBottom * 1;
var reset = 'affix affix-top affix-bottom';
var affix;
if(instance.unpin !== null && (scrollTop + instance.unpin <= position.top)) {
affix = false;
} else if(offsetBottom && (position.top + height >= scrollHeight - offsetBottom)) {
affix = 'bottom';
} else if(offsetTop && scrollTop <= offsetTop) {
affix = 'top';
} else {
affix = false;
}
if (instance.affixed === affix) return;
instance.affixed = affix;
instance.unpin = affix === 'bottom' ? position.top - scrollTop : null;
el.removeClass(reset).addClass('affix' + (affix ? '-' + affix : ''));
};
var checkCallbacks = function(scope, instance, iElement, iAttrs) {
if(instance.affixed) {
if(iAttrs.onUnaffix)
eval("scope." + iAttrs.onUnaffix);
}
else {
if(iAttrs.onAffix)
eval("scope." + iAttrs.onAffix);
}
};
return {
restrict: 'EAC',
link: function postLink(scope, iElement, iAttrs) {
var instance = {unpin: null};
angular.element($window).bind('scroll', function() {
checkPosition(instance, iElement, iAttrs);
checkCallbacks(scope, instance, iElement, iAttrs);
});
angular.element($window).bind('click', function() {
setTimeout(function() {
checkPosition(instance, iElement, iAttrs);
checkCallbacks(scope, instance, iElement, iAttrs);
}, 1);
});
}
};
});
fullpage directive (this directive requires the original jQuery fullpage lugin to work http://www.alvarotrigo.com/fullPage/):
(function () {
'use strict';
angular
.module('fullPage.js', [])
.directive('fullPage', fullPage);
fullPage.$inject = ['$timeout'];
function fullPage($timeout) {
var directive = {
restrict: 'A',
scope: { options: '=' },
link: link
};
return directive;
function link(scope, element) {
var pageIndex;
var slideIndex;
var afterRender;
var onLeave;
var onSlideLeave;
if (typeof scope.options === 'object') {
if (scope.options.afterRender) {
afterRender = scope.options.afterRender;
}
if (scope.options.onLeave) {
onLeave = scope.options.onLeave;
}
if (scope.options.onSlideLeave) {
onSlideLeave = scope.options.onSlideLeave;
}
} else if (typeof options === 'undefined') {
scope.options = {};
}
var rebuild = function () {
destroyFullPage();
$(element).fullpage(sanatizeOptions(scope.options));
if (typeof afterRender === 'function') {
afterRender();
}
};
var destroyFullPage = function () {
if ($.fn.fullpage.destroy) {
$.fn.fullpage.destroy('all');
}
};
var sanatizeOptions = function (options) {
options.afterRender = afterAngularRender;
options.onLeave = onAngularLeave;
options.onSlideLeave = onAngularSlideLeave;
function afterAngularRender() {
//We want to remove the HREF targets for navigation because they use hashbang
//They still work without the hash though, so its all good.
if (options && options.navigation) {
$('#fp-nav').find('a').removeAttr('href');
}
if (pageIndex) {
$timeout(function () {
$.fn.fullpage.silentMoveTo(pageIndex, slideIndex);
});
}
}
function onAngularLeave(page, next, direction) {
if (typeof onLeave === 'function' && onLeave(page, next, direction) === false) {
return false;
}
pageIndex = next;
}
function onAngularSlideLeave(anchorLink, page, slide, direction, next) {
if (typeof onSlideLeave === 'function' && onSlideLeave(anchorLink, page, slide, direction, next) === false) {
return false;
}
pageIndex = page;
slideIndex = next;
}
//if we are using a ui-router, we need to be able to handle anchor clicks without 'href="#thing"'
$(document).on('click', '[data-menuanchor]', function () {
$.fn.fullpage.moveTo($(this).attr('data-menuanchor'));
});
return options;
};
var watchNodes = function () {
return element[0].getElementsByTagName('*').length;
};
scope.$watch(watchNodes, rebuild);
scope.$watch('options', rebuild, true);
element.on('$destroy', destroyFullPage);
}
}
})();
Does the second depends on the first one? If does, are you compiling the directive one before embedding the second?
If you have 'collision' it's means that you're using some sort of 'use strict', would be more valuable if you show part of the code and see if transclude is part of the directive.

What is the downsides of using vanilla js in this angular directive using youtube Iframe Api?

I instinctly know this is wrong, but how to do this otherwise. It is not example that reflects something more complex what I am trying to do.
First tried to inject $document & $window. But $document did not have access to createElement method.
Nother thought is that I can use this type of logic if I wrap some of it in $scope.apply();
full code plnkr here
app.directive('videoPlayButton', function() {
return {
restrict: 'A',
link: linkFunction
};
function linkFunction(scope, el, att, controller) {
var player; // need this available for button eventlistenter
// Invoke on documet.ready(see bottom)
ready(loadPlayerFunction);
///// Directive Methods
// Inject iframe_ap if not available & configure/reference Iframe player
function loadPlayerFunction() {
if (typeof(YT) == 'undefined' || typeof(YT.Player) == 'undefined') {
// inject youtube iframe_apiscript as 1st script tage in the header
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
}
// onYouTubeIframeAPIReady: youtube iframe APi method
// Allows to config & reference your iframe to the api
window.onYouTubeIframeAPIReady = function() {
loadPlayer();
}
// Configure & check if ready(& worked)
function loadPlayer() {
player = new YT.Player('youtube-video', {
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
// Check if everything is functioning
function onPlayerReady() {
console.log("hey Im ready");
//do whatever you want here. Like, player.playVideo();
}
function onPlayerStateChange() {
console.log("my state changed");
}
} // loadPlayerFunction
// Logic for detached basic play/pause button
var playButton = document.querySelector('#playTheVideo');
console.log('playButton', playButton)
playButton.addEventListener('click', function() {
var state = player.getPlayerState();
if(state === 1) {
player.pauseVideo();
} else {
player.playVideo();
}
});
// Non jQuery on document.ready
// Source: http://youmightnotneedjquery.com/#ready
function ready(fn) {
if (document.readyState != 'loading') {
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
}
});
Using document.createElement is pretty common in Angular (as is stuff like querySelector when trying to get an element by class name); depending on what you're doing, it often gets wrapped in an angular.element call:
var elem = angular.element(document.createElement('script'));
// which I think is equivalent too...
var elem = angular.element('<script></script>');
So maybe something like this:
var theSource = 'http://something';
var tag = '<script src="' + theSource + '"></script>';
// Or this might go too...
// var tag = document.createElement ( 'script' );
// tag.src = theSource;
var elem = angular.element(tag);
var firstScriptTag = angular.element(document.getElementsByTagName('script')[0]);
firstScriptTag.prepend(elem); // or append, depending

Issue With MD-Dialog

I am having an MD-dialog controller like below.
var HomeController = function ($scope) {
$scope.demoNonLinear = function () {
var element = document.querySelector('.mdl-stepper#stepper-non-linear');
if (!element) return false;
var stepper = element.MaterialStepper;
var steps = element.querySelectorAll('.mdl-step');
var step;
// Upgrade the component.
if (typeof componentHandler === 'undefined') {
console.log('Missing componentHandler');
} else {
console.log('componentHandler is available');
componentHandler.upgradeAllRegistered();
}
for (var i = 0; i < steps.length; i++) {
step = steps[i];
step.addEventListener('onstepnext', function (e) {
setTimeout(function () {
stepper.next();
}, 4000);
});
}
};
};
The line
var element = document.querySelector('.mdl-stepper#stepper-non-linear');
is not working on the md-dialog html instead it works on the main document. Ho can I make it work on my md-dialog content ?
Plunker link
"https://plnkr.co/edit/ixMI8FKbhyTgL5sYieVa?p=preview"
Try this to select your element in angular.
var element = angular.element( document.querySelector( '#stepper-non-linear' ) );
EDIT FOR GOOD APPROACH
I've wrapped the function in a angular.element(document).ready event which will be executed when document is ready rather than the $timeout approach.
DEMO PLUNKER

$anchorScroll goes to wrong point if dynamically changing element position to fixed

I'm using a directive to fix my menu to the top of the page once my header scrolls past. I also would like my menu to use $anchorScroll to navigate to different elements of the page. The trouble I'm having is that the $anchorScroll goes past the anchor point unless you have scrolled past the header.
var myApp = angular.module('myApp', ['sticky']);
function IndexCtrl($scope, $location, $anchorScroll) {
$scope.gotoDiv = function (id) {
$scope.id = id;
$location.hash(id);
$anchorScroll();
}
}
angular.module('sticky', [])
.directive('sticky', [ function () {
return {
restrict: 'A',
link: function ($scope, $elem, $attrs) {
var offsetTop = 0,
$window = angular.element(window),
initialPositionStyle = $elem.css('position'),
stickyLine,
scrollTop;
// Set the top offset
$elem.css('top', '0');
$window.on('scroll', checkSticky);
setInitial();
function setInitial() {
stickyLine = $elem[0].offsetTop;
checkSticky();
}
function checkSticky() {
scrollTop = window.pageYOffset;
if (scrollTop >= stickyLine) {
$elem.css('position', 'fixed');
} else {
$elem.css('position', initialPositionStyle);
}
}
}
};
}]);
I created this plunker: http://plnkr.co/edit/7HfPtu4f1VoQ5vz4yVOJ?p=preview
-Click the menu the first time, the scroll goes to far.
-A second click takes you the correct position.
-scroll past the header, then click scroll goes to the correct position.
The header needs to be set to fixed before the scroll happens. like this:
var myApp = angular.module('myApp', ['sticky']);
function IndexCtrl($scope, $location, $anchorScroll) {
$scope.gotoDiv = function (id) {
$scope.stickyHeader();
$scope.id = id;
$location.hash(id);
$anchorScroll();
}
$scope.stickyHeader = function () {
service001.elem.css('position', 'fixed');
}
}
var service001 = {
}
angular.module('sticky', [])
.directive('sticky', [ function () {
return {
restrict: 'A',
link: function ($scope, $elem, $attrs) {
var offsetTop = 0,
$window = angular.element(window),
initialPositionStyle = $elem.css('position'),
stickyLine,
scrollTop;
// Set the top offset
$elem.css('top', '0');
$window.on('scroll', checkSticky);
setInitial();
service001.elem = $elem;
function setInitial() {
stickyLine = $elem[0].offsetTop;
checkSticky();
}
function checkSticky() {
scrollTop = window.pageYOffset;
if (scrollTop >= stickyLine) {
$elem.css('position', 'fixed');
} else {
$elem.css('position', initialPositionStyle);
}
}
}
};
}]);
There should be a service in here to share the elem.css position, but there it is.

AngularJS - bind to directive resize

How can i be notified when a directive is resized?
i have tried
element[0].onresize = function() {
console.log(element[0].offsetWidth + " " + element[0].offsetHeight);
}
but its not calling the function
(function() {
'use strict';
// Define the directive on the module.
// Inject the dependencies.
// Point to the directive definition function.
angular.module('app').directive('nvLayout', ['$window', '$compile', layoutDirective]);
function layoutDirective($window, $compile) {
// Usage:
//
// Creates:
//
var directive = {
link: link,
restrict: 'EA',
scope: {
layoutEntries: "=",
selected: "&onSelected"
},
template: "<div></div>",
controller: controller
};
return directive;
function link(scope, element, attrs) {
var elementCol = [];
var onSelectedHandler = scope.selected();
element.on("resize", function () {
console.log("resized.");
});
$(window).on("resize",scope.sizeNotifier);
scope.$on("$destroy", function () {
$(window).off("resize", $scope.sizeNotifier);
});
scope.sizeNotifier = function() {
alert("windows is being resized...");
};
scope.onselected = function(id) {
onSelectedHandler(id);
};
scope.$watch(function () {
return scope.layoutEntries.length;
},
function (value) {
//layout was changed
activateLayout(scope.layoutEntries);
});
function activateLayout(layoutEntries) {
for (var i = 0; i < layoutEntries.length; i++) {
if (elementCol[layoutEntries[i].id]) {
continue;
}
var div = "<nv-single-layout-entry id=slot" + layoutEntries[i].id + " on-selected='onselected' style=\"position:absolute;";
div = div + "top:" + layoutEntries[i].position.top + "%;";
div = div + "left:" + layoutEntries[i].position.left + "%;";
div = div + "height:" + layoutEntries[i].size.height + "%;";
div = div + "width:" + layoutEntries[i].size.width + "%;";
div = div + "\"></nv-single-layout-entry>";
var el = $compile(div)(scope);
element.append(el);
elementCol[layoutEntries[i].id] = 1;
}
};
}
function controller($scope, $element) {
}
}
})();
Use scope.$watch with a custom watch function:
scope.$watch(
function () {
return [element[0].offsetWidth, element[0].offsetHeight].join('x');
},
function (value) {
console.log('directive got resized:', value.split('x'));
}
)
You would typically want to watch the element's offsetWidth and offsetHeight properties. With more recent versions of AngularJS, you can use $scope.$watchGroup in your link function:
app.directive('myDirective', [function() {
function link($scope, element) {
var container = element[0];
$scope.$watchGroup([
function() { return container.offsetWidth; },
function() { return container.offsetHeight; }
], function(values) {
// Handle resize event ...
});
}
// Return directive definition ...
}]);
However, you may find that updates are quite slow when watching the element properties directly in this manner.
To make your directive more responsive, you could moderate the refresh rate by using $interval. Here's an example of a reusable service for watching element sizes at a configurable millisecond rate:
app.factory('sizeWatcher', ['$interval', function($interval) {
return function (element, rate) {
var self = this;
(self.update = function() { self.dimensions = [element.offsetWidth, element.offsetHeight]; })();
self.monitor = $interval(self.update, rate);
self.group = [function() { return self.dimensions[0]; }, function() { return self.dimensions[1]; }];
self.cancel = function() { $interval.cancel(self.monitor); };
};
}]);
A directive using such a service would look something like this:
app.directive('myDirective', ['sizeWatcher', function(sizeWatcher) {
function link($scope, element) {
var container = element[0],
watcher = new sizeWatcher(container, 200);
$scope.$watchGroup(watcher.group, function(values) {
// Handle resize event ...
});
$scope.$on('$destroy', watcher.cancel);
}
// Return directive definition ...
}]);
Note the call to watcher.cancel() in the $scope.$destroy event handler; this ensures that the $interval instance is destroyed when no longer required.
A JSFiddle example can be found here.
Here a sample code of what you need to do:
APP.directive('nvLayout', function ($window) {
return {
template: "<div></div>",
restrict: 'EA',
link: function postLink(scope, element, attrs) {
scope.onResizeFunction = function() {
scope.windowHeight = $window.innerHeight;
scope.windowWidth = $window.innerWidth;
console.log(scope.windowHeight+"-"+scope.windowWidth)
};
// Call to the function when the page is first loaded
scope.onResizeFunction();
angular.element($window).bind('resize', function() {
scope.onResizeFunction();
scope.$apply();
});
}
};
});
The only way you would be able to detect size/position changes on an element using $watch is if you constantly updated your scope using something like $interval or $timeout. While possible, it can become an expensive operation, and really slow your app down.
One way you could detect a change on an element is by calling
requestAnimationFrame.
var previousPosition = element[0].getBoundingClientRect();
onFrame();
function onFrame() {
var currentPosition = element[0].getBoundingClientRect();
if (!angular.equals(previousPosition, currentPosition)) {
resiszeNotifier();
}
previousPosition = currentPosition;
requestAnimationFrame(onFrame);
}
function resiszeNotifier() {
// Notify...
}
Here's a Plunk demonstrating this. As long as you're moving the box around, it will stay red.
http://plnkr.co/edit/qiMJaeipE9DgFsYd0sfr?p=preview
A slight variation on Eliel's answer worked for me. In the directive.js:
$scope.onResizeFunction = function() {
};
// Call to the function when the page is first loaded
$scope.onResizeFunction();
angular.element($(window)).bind('resize', function() {
$scope.onResizeFunction();
$scope.$apply();
});
I call
$(window).resize();
from within my app.js. The directive's d3 chart now resizes to fill the container.
Here is my take on this directive (using Webpack as bundler):
module.exports = (ngModule) ->
ngModule.directive 'onResize', ['Callback', (Callback) ->
restrict: 'A'
scope:
onResize: '#'
onResizeDebounce: '#'
link: (scope, element) ->
container = element[0]
eventName = scope.onResize || 'onResize'
delay = scope.onResizeDebounce || 1000
scope.$watchGroup [
-> container.offsetWidth ,
-> container.offsetHeight
], _.debounce (values) ->
Callback.event(eventName, values)
, delay
]

Resources