Using Bootstrap UI for Angular, I need to gather the index of the slide being displayed, which I can then use to gather data in order to display content in a separate container.
Following this exact example I can log the index of the slide. And when I replicate it in my own environment, it works.
The issue I am having is that I have inserted the carousel within a tabset also provided by Bootstrap UI, like so:
<uib-tabset active="activePhaseTab" type="pills">
<uib-tab heading="Blue" index="3" ng-click="$ctrl.phaseSlide = 3;$ctrl.phaseSliderInfo($ctrl.phaseSlide)">
<div style="height: 305px">
<div uib-carousel active="activeSlide" interval="myInterval" no-wrap="noWrapSlides">
<div uib-slide ng-repeat="slide in slides track by slide.id" index="slide.id">
<img ng-src="{{slide.image}}" style="margin:auto;">
<div class="carousel-caption">
<h4>Slide {{slide.id}}</h4>
<p>{{slide.text}}</p>
</div>
</div>
</div>
</div>
</uib-tab>
</uib-tabset>
Without encapsulating the carousel within the tabs, I was able to use the $watch method on the activeSlide, but now the carousel is within the tabs, it is failing to do so.
Question
How do I $watch for change on 'activeSlide' within the tabset?
$scope.myInterval = 3000;
$scope.noWrapSlides = false;
var slides = $scope.slides = [];
var currIndex = 0;
$scope.addSlide = function() {
var newWidth = 600 + slides.length + 1;
slides.push({
image: '//unsplash.it/' + newWidth + '/300',
text: ['Nice image','Awesome photograph','That is so cool','I love that'][slides.length % 4],
id: currIndex++
});
};
for (var i = 0; i < 4; i++) {
$scope.addSlide();
}
$scope.$watch('activeSlide', function (active) { //SHOULD LOG THE ACTIVE SLIDE
if(active !== undefined) {
console.log(active)
console.log('slide ' + active + ' is active');
}
});
Your initial plunker link has this weird syntax error, so I made another one using the Bootstrap UI examples.
I fit in the carousel demo within the tabset pill demo.$scope.$watch('activeSlide'... did fail. But thankfully this specific scenario has been answered before in this stackoverflow question.
So I just followed their suggestions and tried out their directive. Now every slide change is monitored by on-carousel-change="onSlideChanged(nextSlide, direction)". You can check the console of the plunker below and play around with it however you like.
Here's the plunker
Hope that helps!
Related
Been trying various things, and searched around expecting others to have done the same, but getting nowhere fast .. the best result i've got is the first instance of the carousel working, but all the rest do not. Why is the .each() function not working?
Note: the main aim is to have multiple carousels with one block of .js control .. the script pulls the individual carousel instance variables such as number of items to show, delay, if lazy-load or not, theme classes, etc from each instance based on the data-variable="xyz" values ... and it works fine with individually identified (used unique classes or ID's) carousels on the page, but not this (more efficient) multi method.
As you see i've tried to find the carousels based on the common class ".galleryowlmulti", then find their parent container ID to uniquely identify it (as i thought this is the issue?) and then apply the variables to each carousel instance. Hope that makes sense?
Example HTML Snippet of one slider repeated in the same page but with unique ID's (please note that ".owl-carousel" is not needed where I use ".owlcarousel") :
<span id="unique-id1" class="slider">
<span class="titleh3">Title</span>
<div class="galleryowlmulti" data-owlitemshow="8" data-owlmargin="10" data-owltimeout="4000" data-owllazy="true" data-owldots="false" data-owlautoplay="false" data-owlslideby="page" data-owlthemes="owltheme-smallnav">
<div class="owlcarousel">
<div class="item">1st slide</div>
<div class="item">2nd slide</div>
</div>
</div>
</span>
<span id="unique-id2" class="slider">
<span class="titleh3">Title</span>
<div class="galleryowlmulti" data-owlitemshow="8" data-owlmargin="10" data-owltimeout="4000" data-owllazy="true" data-owldots="false" data-owlautoplay="false" data-owlslideby="page" data-owlthemes="owltheme-smallnav">
<div class="owlcarousel">
<div class="item">1st slide</div>
<div class="item">2nd slide</div>
</div>
</div>
</span>
The JavaScript:
$(function () {
$('.galleryowlmulti').each(function() {
// *** declare identifier? ***
var owl_id = $(this).closest('.slider').prop('id'); // .attr('id'); // .prop('id');
var owl_declare = $('#' + owl_id + ' .galleryowlmulti'); // owl_id.find('.galleryowlmulti'); // $('#' + owl_id + ' .galleryowlmulti'),
var owl_instance = $('#' + owl_id + ' .owlcarousel'); // $(".owlcarousel", this) // owl_id.find('.owlcarousel'); // $('#' + owl_id + ' .owlcarousel');
// pull variables from page
var owl_owlthemes = owl_declare.data('owlthemes'),
owl_owlitemshow = owl_declare.data('owlitemshow'),
owl_owllazy = owl_declare.data('owllazy'),
owl_owlmargin = owl_declare.data('owlmargin'),
owl_owldots = owl_declare.data('owldots'),
owl_owlautoplay = owl_declare.data('owlautoplay'),
owl_owltimeout = owl_declare.data('owltimeout'),
owl_slidebyf = owl_declare.data('owlslideby');
// calc the items to show breaks
var owl_owlitemshow75=Math.round(owl_owlitemshow*0.75),
owl_owlitemshow50=Math.round(owl_owlitemshow*0.5),
owl_owlitemshow25=Math.round(owl_owlitemshow*0.25);
// calculate item count
var item_count = parseInt(owl_instance.find('.item').length);
var true_false = 0;
if (item_count <=1) {true_false = false; owl_owldots = false;} else {true_false = true;}
//
// control nav visiblity thumbs shown vs thumbs allowed visible
// see: http://stackoverflow.com/a/33252395/3794783
// owl_instance.on('initialized.owl.carousel resized.owl.carousel', function(e) {
// $(e.target).toggleClass('owl-nonav', e.item.count <= e.page.size);
// });
owl_instance.owlCarousel({
themeClass: owl_owlthemes,
autoplay: owl_owlautoplay,
autoplayTimeout: owl_owltimeout,
items: owl_owlitemshow,
margin: owl_owlmargin,
responsive:{
0:{items:1,nav:true},
389:{items:owl_owlitemshow25},
605:{items:owl_owlitemshow50},
1023:{items:owl_owlitemshow75},
1289:{items:owl_owlitemshow}
},
loop: true_false,
nav: true_false,
slideBy: owl_slidebyf,
lazyLoad: owl_owllazy, // IMG markup (lazyOwl = V1 / owl-lazy = v2 ): class="owl-lazy" and data-src="url_to_img" src="" or/and data-src-retina="url_to_highres_img"
dots: owl_owldots,
//
// backport the classes to older used ones
navContainerClass: 'owl-buttons',
dotsClass: 'owl-pagination',
dotClass: 'owl-page',
autoplayHoverPause:true, //false
onInitialized: function() {
if(owl_slidebyf == 'page'){
owl_instance.owlCarousel({slideBy:page})
}
}
});
});
});
Fiddle example:
See JS Fiddle
UPDATE:
I got some way along with getting mutliple instances on page to fire and work (-ish as lazyload isnt happy) ..
In the fiddle, remove or comment out the following:
responsive:{
0:{items:1,nav:true},
389:{items:owl_owlitemshow25},
605:{items:owl_owlitemshow50},
1023:{items:owl_owlitemshow75},
1289:{items:owl_owlitemshow}
},
Ooops ... and this also needs commenting out or removing THEN it works? WTH?!
onInitialized: function() {
if(owl_slidebyf == 'page'){
owl_instance.owlCarousel({slideBy:page})
}
}
UPDATE 2:
Removing the "nav:true" in responsive seemed to fix .... seems to be the culprit for others reading ... if removed the original script code should work for you :)
Simply add some arrows (div, span, whatever) near the each carousel and assign to them class .carousel-arrow-left and .carousel-arrow-right. Next add this javascript to your js file.
The Javascript
// carousel arrows
$('.carousel-arrow-left').click(function(){
$(this).next(".carousel-product-list").trigger('prev.owl.carousel');
});
$('.carousel-arrow-right').click(function(){
$(this).prev(".carousel-product-list").trigger('next.owl.carousel');
});
Let's say every carousel is called .carousel-product-list. Now every right arrow on your page after click finds next class which name is .carousel-product-list and do a command next.owl.carousel, which means the carousel go forward by a one item.
I would like to rebuild an html tag structure to a new one on resizing the browser window. Have anyone an idea how can I get from the first structure to the second structure. I need this for an responsive personal project. Maybe with an JavaScript resize Event, I don't know...
<div class="wrapper">
<div class="slide">
<div class="item"></div>
<div class="item"></div>
</div>
<div class="slide">
<div class="item"></div>
<div class="item"></div>
</div>
</div>
<div class="wrapper">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
You could listen to the resize event on the window and then restructure your HTML when the window is below a certain size. Moving elements from the first structure to the second isn't a big problem, however the challenge lies in reverting that again. How will you know which of the .item's belonged to which .slide?
One way to do this is to keep track of the parent for each .item in a separate data- attribute when you make the list:
function makeList() {
var $slides = $('.slide'),
$items = $();
// For each slide set a data-attribute on all its child .item's
$slides.each(function(i) {
var $item = $(this).children('.item');
$item.attr('data-slide', i+1);
$items = $items.add($item);
});
// Append all items directly to the wrapper
$('.wrapper').html($items).attr('data-structure', 'list');
}
I set a data attribute to the .wrapper so we know that it's already converted to a list. Otherwise on every resize this would be fired, you only want it once (until it resizes back to where you want the slides).
When you want the slide again, loop through all the .items and keep a list of slide number's. For each new data-slide number you encounter make a new slide and add that to the total list;
function makeSlides() {
var $slides = $(),
slideNumbers = [],
$currentSlide = $();
$('.item[data-slide]').each(function() {
var $item = $(this),
slideNumber = $item.attr('data-slide');
// if the slide number wasn't in the array yet push the current slide into $slides and create a new one
if(slideNumbers.indexOf(slideNumber) < 0) {
$slides = $slides.add($currentSlide);
$currentSlide = $('<div class="slide" />');
slideNumbers.push(slideNumber);
}
$currentSlide.append($item);
});
// add the last currentSlide
$slides = $slides.add($currentSlide);
// place all slides in the wrapper
$('.wrapper').html($slides).attr('data-structure', 'slides');
}
Then finally you bind the resize event and fire these functions when needed:
$(window).resize(function(e) {
var currentStructure = $('.wrapper').attr('data-structure');
if(window.innerWidth < 600) {
if( currentStructure !== 'list') {
makeList();
}
} else {
if( currentStructure !== 'slides') {
makeSlides();
}
}
});
jsFiddle demo: http://jsfiddle.net/gktLnw31/3/
I do think this could be a bit more efficient, but this is just a proof of concept. Hopefully it'll give you some insights.
I would better take a look at foundation since it already includes the responsive design by default instead of trying to change it dynamically by code
I'm building a POS software with angularjs and I need to list thousands of products separated by pages of 30 products each, kind of like apps on an iPad. For that task I decided to use Swiperjs, an awesome library for creating slides, and since this angular app is designed for iPad, that library fits perfectly.
The problem is, I want to recycle slides so that I don't add 500 slides to the DOM, But it seems like the combination of Angularjs and Swiperjs is giving me problems.
First of all, I have an array with all my product data:
// In ProductListCtrl
$scope.productArray = [];
Then I separate the info in that array in 4 slides of 30 elements each:
var elements = $scope.productArray.slice(0, 120);
// slideGenerator is a function that seperates everything in slides
$scope.slides = Utilities.slideGenerator(elements, 30);
After that, I render the info in my DOM with a double ng-repeat:
<div id="bloque" class="swiper-container">
<!-- Ahora estoy desarrollando sin complicarme, repito la lista y la oculto -->
<div id="lista-productos" class="list oculto swiper-wrapper" ng-controller="ProductsListCtrl">
<div class="swiper-slide" ng-repeat="slide in slides">
<div class="elementos">
<div class="productos">
<div ng-repeat="element in slide">
<span ng-if="element.name != 'empty'">
<button product="{{element.name}}" hasAttribs="{{element.hasAttribs}}" class="btn-product" ng-click="showElementAttribs($parent.$parent.$index, $index)">
<span ng-if="textIsHidden == true && element.image">
<div class="elemImg" style="background-image: url({{element.image}}); background-size: contain;"></div>
</span>
<span ng-if="textIsHidden == false || !element.image">
<div class="elemName">{{element.name}}</div>
</span>
</button>
</span>
<span ng-if="element.name == 'empty'">
<button class="pro_vacio"></button>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
Until this point everything works perfectly. But then the user swipes, and swipes, and swipes again, and I try to load the new info like so:
if($scope.currentSlide >= 3) {
// Update the data on the slide, $scope.wheel is simply
// the slide I have to modify
if($scope.wheel < $scope.slides.length) {
$scope.slides[$scope.wheel] = Utilities.createSingleSlide($scope.productArray, $scope.currentSlide, 30);
console.log("New slide created");
$scope.wheel++;
//$scope.$apply();
}
else {
$scope.wheel=0;
}
swiper1.slides[0].clone().insertAfter(swiper1.slides.length - 1);
swiper1.removeSlide(0);
swiper1.swipeTo(swiper1.activeIndex-1, 0, false);
swiper1.reInit();
}
$scope.$apply();
console.log("Slides", $scope.slides);
To me this seems like it should work, but it doesn't. It adds new slides every time and outputs some funky behaviour, when it should always contain the same number of slides, 4.
If I comment out the angular code:
//if($scope.wheel < $scope.slides.length) {
// $scope.slides[$scope.wheel] = Utilities.createSingleSlide($scope.productArray, $scope.currentSlide, 30);
// console.log("New slide created");
// $scope.wheel++;
//$scope.$apply();
//}
//else {
// $scope.wheel=0;
//}
I sort of get the desired behaviour of the view recycling thing because I always get the same number of slides, 4, and I get a sort of infinite loop.
I've also tried this, after changing the info on the slide:
swiper1.slides[0].append()
But that changes the current slide that I'm viewing, which sucks, and doesn't work well.
So why does angular add new slides after modifying the data?
I'm building an app with Bootstrap and AngularJS. At some point I have an ng-repeat on a col-md-3, listing products. My problem is that I want to be able to insert a collapse into the grid, but as the columns are automatically generated, I don't really know how to do it.
Here's a diagram to understand it better:
First, the grid of .col-md-3 is populated from the ng-repeat.
And what I'm trying to achieve, is to add a .col-md-12 that appears right under the row of the .col-md-3 that gets clicked on.
My initial thought was to add an empty .col-md-12 dynamically after each group of 4 .col-md-3, but I wouldn't know how to do so, and it kinda seems to be that it would be a rather dull approach. Any ideas?
Here's the relevant html:
<div class="infinite" infinite-scroll="loadDetails()">
<div class="col-xs-3 col-md-3" ng-repeat="release in main.releases | filter:main.album">
<release release="release" artist="main.artist" class="dropdown"></release>
</div>
</div>
EDIT: Here's a working Plunker including tasseKATTs solution.
Place a custom directive on your inner element together with a position counter that starts with 1 and a marker describing if it's the last element:
<div ng-repeat="item in items" class="col-xs-3">
<div class="item" the-directive position="{{ $index + 1 }}" last="{{ $last }}">
</div>
</div>
Create the directive with an isolated scope, bind scope properties to the values of the position and last attributes and attach a click event handler to the element:
app.directive('theDirective', function() {
return {
restrict: 'A',
scope: { position: '#', last: '#' },
link: function(scope, element, attrs) {
element.bind('click', function() {
...
});
}
};
});
In the click handler first create the collapse element or select it if it already exists:
var collapseQuery = document.querySelector('#collapse');
var collapse = collapseQuery === null ?
angular.element('<div id="collapse" class="col-xs-12"><div class="twelve"></div></div>') :
angular.element(collapseQuery);
Based on the position of the clicked element calculate the rounded number up to the nearest multiple of four:
var calculatedPosition = Math.ceil(scope.position / 4) * 4;
Get the element at the calculated position or the last one if the position is out of range:
var calculatedQuery = document.querySelector('[position="' + calculatedPosition + '"]');
if (calculatedQuery === null) calculatedQuery = document.querySelector('[last="true"]');;
var calculatedElement = angular.element(calculatedQuery);
Insert the collapse element after the element at the calculated position:
calculatedElement.parent().after(collapse);
Could use some optimizations, but hopefully puts you on the right track.
Demo with some extra visuals: http://plnkr.co/edit/fsC51vS7Ily3X3CVmxSZ?p=preview
This question is easier to answer in an angular way if you follow the bootstrap convention using 12 columns per a row:
Grid columns are created by specifying the number of twelve available columns you wish to span. For example, three equal columns would use three .col-xs-4.
In your case, this means each row can have up to 4 .col-xs-3 columns, or just 1 .col-xs-12. You can prep your data to be displayed this way by splitting it into an array of smaller arrays.
$scope.getRows = function(array) {
var rows = [];
//https://stackoverflow.com/questions/8495687/split-array-into-chunks
var i,j,temparray,chunk = 4;
for (i=0,j=array.length; i<j; i+=chunk) {
temparray = array.slice(i,i+chunk);
rows.push(temparray);
}
return rows;
};
$scope.rows = $scope.getRows($scope.main.releases);
Then you can nest ngRepeat to achieve the desired layout, using ng-if to only create a col-xs-12 when a corresponding .col-xs-3 is clicked.
<div ng-repeat="row in rows">
<div class="row">
<div class="col-xs-3" ng-repeat="release in row" ng-click="main.releaseClicked=release">
<div class="release">{{release}}</div>
</div>
</div>
<div class="row" ng-repeat="release in row" ng-if="main.releaseClicked==release">
<div class="col-xs-12">
<div class="detail">Release detail: {{release}}</div>
</div>
</div>
</div>
This leaves you with a more declarative view that describes how the app works, and doesn't require jQuery to do DOM manipulation.
Here is a working demo: http://plnkr.co/ujlpq5iaX413fThbocSj
I have a container which animates its scrollTop to the bottom whenever a new item is added. The markup looks something like this:
<div class="scrolly">
<div class="item" ng-repeat="item in items" ng-animate=" 'scroll-to-bottom' ">
{{item.value}}
</div>
</div>
This works great when adding new items, but on the initial page load, the container is scrolled to the top. I'd like to figure out the right way to have the scrollTop set to the bottom on initial page load.
Example jsFiddle: http://jsfiddle.net/bkad/JnwCP/
The trick is to add some delay when you populate the data like this :)
$timeout(function () {
for (var i = 0; i < 20; i++)
$scope.items.push({
value: i
})
}, 10);
Demo
I ended up using scrollGlue which implements this as a directive.