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?
Related
I'm currently learning basic JavaScript/JQuery but I'm in need of a quick fix to a problem which is slightly beyond what I'm capable of solving right now.
I'm looking to create a feature that allows me to click through images, and when I click on the last image it will return to the first image, in a loop.
(I'm trying to make something similar to this: https://www.antennebooks.com/product/east-end-of-europe/)
Any help would be greatly appreciated!
this is what I have so far:
<script>
var images = [
"images/img1.png",
"images/img2.png",
"images/img3.png",
"images/img4.png"
]
var step = 0;
changeImage();
function changeImage() {
document.getElementById('imgClickAndChange').src = images[step];
step++;
}
Assuming the function changeImage() is called when you click on the image displayed, you just have to change the step++; to step = (step + 1) % images.length; and place it as the first line of your function.
Each time step + 1 equals to images.length, step would be reset to 0.
We could use jQuery and manipulate the DOM via events, which would make more sense in this situation. A "click" event could be triggered when the user clicks on an image. You could store the images in a set, and when the number of clicks is greater than the length of the set, display the first card and start the process over again. Here is an example of the JavaScript code utilizing the jQuery libary:
$(document).ready(function (){
var card = $('.card'); //store each card with an image in it in a set
card.css('cursor', 'pointer'); //set the cursor to pointer to make a click obvious to user
card.not(':first').hide(); //hide all but the first image (card)
count = 1; //set the initial click count to one
card.on('click', function() { //listen for an image to be clicked
$(this).hide(); //hide the image that has been clicked
$(this).next().show(); //show the next image
count++; //increment the count
if(count > card.length) { //if the count is greater than the length of the set, you are out of images (cards)
count = 1; //reset the count
card.first().show(); //show the first card and start process over
}
});
});
This is the html code that I am using to simulate the events you are looking for:
<div class="card" style="max-width: 18rem;">
<img class="card-img-top" src="/path">
<div class="card-body">
<p>This is a random card and stuff</p>
</div>
</div>
<div class="card" style="max-width: 18rem;">
<img class="card-img-top" src="/path">
<div class="card-body">
<p>This is a random card and stuff</p>
</div>
</div>
<div class="card" style="max-width: 18rem;">
<img class="card-img-top" src="/path">
<div class="card-body">
<p>This is a random card and stuff</p>
</div>
</div>
As you can see, we have three cards that basically store our images. If you just want images and no cards, then do that, because that part of it is trivial. What matters here is the programming logic. I would simply set a counter and once that counter is greater than the length of the set (collection) of cards (a set/collection is an array-link object) then show the first card again and start the process over as I showed you in JavaScript.
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!
Hi I found lots of examples related to this question, but so far the examples that I see they don't match my needs.On my div at the moment I load all the list content retrieved from my function,My goal is I want to be able to only show 6 items and keep on appending the other 6 until the list is exhausted using the infinite scroll in jQuery.
This is how my list look within the div.
<div class="listOfAnything">
<div class="all">apple</div>
<div class="all">Banana</div>
<div class="all">Guava</div>
<div class="all">Pear</div>
<div class="all">mango</div>
<div class="all">Grapes</div>
<div class="all">Avocado</div>
<div class="all">Orange</div>
<div class="all">Lemon</div>
<div class="all">Nartjie</div>
<div class="all">Granadilla</div>
<div class="all">pawpaw</div>
<div class="all">Ginger</div>
<div class="all">Watermelon</div>
<div class="all">potato</div>
<div class="all">Sweet Potato</div>
<div class="all">Peach</div>
</div>
I've tried to follow the tutorials on http://scrollmagic.io/examples/advanced/infinite_scrolling.html but I had no luck because i got stuck here
function addBoxes (amount) {
for (i=1; i<=amount; i++) {
var randomColor = '#'+('00000'+ (Math.random()*0xFFFFFF<<0).toString(16)).slice(-6);
$("<div></div>")
.addClass("box1")
.css("background-color", randomColor)
.appendTo(".dynamicContent #content");
}
// "loading" done -> revert to normal state
scene.update(); // make sure the scene gets the new start position
$("#loader").removeClass("active");
}
// add some boxes to start with.
addBoxes(18);
Because I already have the content on my div.
Added the scroll function
function addBoxes (amount) {
for (i=1; i<=amount; i++) {
var randomColor = '#'+('00000'+ (Math.random()*0xFFFFFF<<0).toString(16)).slice(-6);
$("<div></div>")
.addClass("box1")
.css("background-color", randomColor)
.appendTo(".dynamicContent #content");
}
// "loading" done -> revert to normal state
scene.update(); // make sure the scene gets the new start position
$("#loader").removeClass("active");
}
// add some boxes to start with.
addBoxes(6);
// do things on mousescroll
$(window).bind('mousewheel DOMMouseScroll', function(event)
{
if (event.originalEvent.wheelDelta < 0 || event.originalEvent.detail > 0) {
setTimeout(function(){
addBoxes(6);
}, 1000);
}
});
I'm trying to create an experience on my personal site that allows users to click on a hero image of my work and watch a div open above the work in order to see more in depth info.
Here's a jsFiddle: https://jsfiddle.net/1zs7zomo/
I've tried matching the order of the info div to the order of the buttons using
$(function() {
var hero = $('.display-section__content-hero');
var treasure = $('.treasure-container');
var closeButton = $('.close-button');
treasure.hide();
hero.click(function() {
var el = $(this);
var elData = el.data('order');
var treasureData = treasure.data('order');
treasure.each(function(){
if (treasureData === elData) {
console.log(treasureData + " = " + elData);
$(this).slideDown(600);
}
});
})
closeButton.click(function() {
$(this).closest('.treasure-container').slideUp(600);
})
})
But when it opens the info div, it opens all of them instead of the one that matches.
I also tried matching based on .eq() position
$(function() {
var hero = $('.display-section__content-hero');
var treasure = $('.treasure-container');
var closeButton = $('.close-button');
treasure.hide();
hero.click(function() {
var el = $(this);
var elOrder = el.eq();
treasure.eq(elOrder).slideDown(600);
})
closeButton.click(function() {
$(this).closest('.treasure-container').slideUp(600);
})
})
But that doesn't seem to work at all. I supposed I could get into putting specific matching classes on each, but I'm trying to make this as modular as possible. I'm building on Node.js right now to learn and want to make this super modular so that down the road when I figure out how to load data via ajax I can just have them react without having to use any form of order or class, purely data.
Here's the HTML at a base level.. can provide more if necessary
<div class="display-section--build clearfix">
<div class="treasure-container" data-order="1">
<div class="treasure-hero">
<div class="close-button">X</div>
</div>
<div class="treasure-description-container">
<div class="description-head">
<h1 class="treasure-description__title">Spiffly.is</h1>
<a class="treasure-description__link" href="http://spiffly.is" target="_blank">Visit Site</a>
</div>
<h2 class="treasure-description__role">Front-End Engineer</h2>
<p class="treasure-description__details">Spiffly is a marketplace for good. A place for B2P -- that's business to prosumer -- transactions. Prosumers get products at wholesale cost, which takes the risk out of trying something new and gives them motivation to promote the product if it's good. Businesses still make money but get marketing from prosumers' social presence.
<br>
<br>
I built the front-end of Spiffly.is, an e-commerce site, in less than 100 hours despite never having worked with Swig, Node.js or Sass before.
</p>
</div>
</div>
<div class="display-section__content-hero" data-order="1"></div>
</div>
treasure.each(function(index, obj){
if ($(obj).data('order') === elData) {
Maybe that snippet will help you.
You should use a data attribute to index the box you need to target
<div class="click_me_to_open" data-order="1"></div>
<div class="hidden_div" data-order-details="1"></div>
this way in your JS you bind the click to .click_me_to_open get the value of data-order and make a selector to target the right box
$('.click_me_to_open').on('click', function () {
var target = $(this).data('order');
$('[data-order-details="' + target + '"]').slideDown();
})
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