I have this small project I'm working at, and it's my first time ever using KnockoutJS (and a long while since I used javascript).
Any javascript carousel works with foreach, until the array is updated. I have already tried using Glider, Slick and Owl plugins and they all end up doing the same thing:
Document starts, foreach initiates, populates the carousel with cards fetched from URL. OK
Using the <select>, I change the option which will return a different API URL based on it. OK
The foreach is restarted, the new content is thrown in the data-bind, but the last carousel remains in HTML, working (?)
The new content doesn't work well with the carousel, as the items show stacked on top of each other.
The fourth step actually happened long before I understood what happened with carousels and lifecycle from KO; I had to use a handleBinding to start the plugin function after the foreach was made. Problem is, when the foreach is updated, KO won't restart the whole view, just what's inside of it, so the handleBinding is ignored.
Also, I can't explain why, in the 3rd step, the last carousel keeps there.
Code:
Select
<div id="select-regiao" data-bind="with: localizacoes" class="form-group mb-0">
<select id="selected-option" data-bind="options: planosPorLocalizacao, value: planosSelecionados" class="custom-select select-oi mr-2">
</select>
</div>
Carousel, foreach, card...
<div class="owl-carousel owl-theme" data-bind="foreach: planos, carousel">
<!-- #region card do plano -->
<div class="mt-4 m-2">
<div class="card card-plano">
[... card content... ]
</div>
</div>
<!-- #endregion -->
</div>
Before </body>
<script src="assets/js/jquery-3.3.1.slim.min.js" type="text/javascript"></script>
<script src="assets/js/popper.min.js" type="text/javascript"></script>
<script src="assets/js/bootstrap.min.js" type="text/javascript"></script>
<script src='assets/bower_components/knockout/dist/knockout.js' type='text/javascript'></script>
<script src="assets/js/ko-models.js" type="text/javascript"></script>
<script src="assets/js/owl-2-2.3.4/dist/owl.carousel.min.js"></script>
My ko-models.js:
$(document).ready(function() {
apiUrl = "link";
localizacoes = {
planosPorLocalizacao: ["Todos", "Rio de Janeiro, RJ", "São Paulo, SP"],
planosSelecionados: ["Todos"]
};
planos = [];
var vm = null;
var select = document.getElementById("selected-option");
select.addEventListener("change", function() {
if (select.value == "Rio de Janeiro, RJ") {
apiUrl = "other link";
} else if (select.value == "São Paulo, SP") {
apiUrl = "other link";
} else if (select.value == "Todos") {
apiUrl = "link";
}
console.log(select.value);
MostraPlanos();
});
function MostraPlanos() {
fetch(apiUrl).then(function(next) {
next.json().then(function(res) {
// ko.cleanNode({planos: res});
res.forEach(el => {
el.dependentePreco = el["dependente-preco"];
el.precoReal = el["preco"].split(",")[0];
el.precoCentavo = el["preco"].split(",")[1];
});
planos = res;
if(vm == null) {
vm = {
planos: ko.observable(planos),
localizacoes
};
ko.applyBindings(vm);
}
else{
vm.planos(planos);
vm.gliderCarousel();
}
});
});
}
MostraPlanos();
ko.bindingHandlers.carousel = {
update: function() {
$(".owl-carousel").owlCarousel({
loop:true,
margin:10,
nav:true,
responsive:{
0:{
items:1
},
600:{
items:3
},
1000:{
items:5
}
}
});
}
}
});
I found out the problem was the way the plugins used to track the carousel. Glider, for instance, was the last one I tried and spent most time at.
From the possible options in Glider.js:
addTrack
Type: Boolean
Default: true
Whether or not Glider.js should wrap it's children with a
'glider-track' .
NOTE: If false, Glider.js will assume that the
'glider-track' element has been added manually. All slides must be
children of the track element.
Adding .glider-track to the outer div (parent of cards) and setting addTrack: false in JS solved the issue!
Related
We have an old ASPNET MVC application designed using Kendo(v4.0.30319). In the view I am using DropDownListFor to populate a list of items. The way the webpage works is after selecting an item from dropdown, there will be an ajax call to load data into grid.
My problem is when the page gets loaded for first time, dropdown is populated and I am able to select the item and load the grid. But, once it's loaded the dropdown is inactive/disabled and I am not able to select any other item.
Here is the snap of webpage
Here is the code block from my view:
<div class="col-sm-9 col-md-8 col-lg-9">
#Html.DropDownListFor(model => model.company_id, new System.Web.Mvc.SelectList(Model.CompanyList, "company_id", "company_name"), new { #class = "form-control", #id = "ddlCompany" })
</div>
The script block is:
$(document).ready(function () {
$('#ddlCompany').change(function () {
$("#CompanyConnectorPropertyGrid").data("kendoGrid").dataSource.read({ "companyConnectorID": 0 });
$('#dvGrid').hide();
$('#dvMessage').html('');
if ($('#ddlCompany').val()) {
BindConnectorDropdown();
}
else {
$('#dvGrid').hide();
}
});
$('#ddlConnector').change(function () {
$('#dvMessage').html('');
if ($('#ddlConnector').val() && $('#ddlConnector').val() > 0) {
$("#CompanyConnectorPropertyGrid").data("kendoGrid").dataSource.read({ "companyConnectorID": $('#ddlConnector').val() });
$('#dvGrid').show();
}
else {
$('#dvGrid').hide();
}
});
var connectorID = getParameterByName('cid');
if (connectorID) {
$("#ddlConnector").val(connectorID);
$('#ddlConnector').trigger('change');
}
});
There is nowhere it's mentioned to disable the dropdown.
I want the dropdown to be active so that I can select a diffrent item , bind it and get data. Any help to solve the problem would be appreciated. Thanks in advance.
<div class="owl-carousel">
<div ng-repeat="items in itemlist">
<img ng-src="{{items.imageUrl}}" />
</div>
<div>
<img src="http://placehold.it/350x150" />
</div>
</div>
View carousel here: Owl-carousel2
I'm running into an issue where whenever the ng-repeat directive is applied to carousel the items are stacked vertically instead of being layout horizontally.
If I leave out ng-repeat and use static items then it works as it should.
Is there a directive I can write and apply to owl-carousel to maintain the layout?
Also what is about ng-repeat that is causing the carousel to break?
Is angular somehow stripping the owl-carousel classes applied to the carousel?
Note* If build the list manually then iterate through and append the elements using :
var div = document.createElement('div');
var anchor = document.createElement('a');
var img = document.createElement('img');
.....
carousel.appendChild(div);
then call the owl.owlCarousel({..}) It works, not sure if this is the best work around because ng-repeat makes everything bit easier.
I discovered a hack , if I wrap the owl init in a timeout then ng-repat works.
setTimeout(function(){
...call owl init now
},1000);
<link rel="stylesheet" href="css/owl.carousel.css"/>
<link rel="stylesheet" href="css/owl.theme.default.min.css"/>
.....
<script src="/js/lib/owl.carousel.min.js"></script>
<script>
$(document).ready(function() {
var owl = $('.owl-carousel');
owl.owlCarousel({
.....
});
owl.on('mousewheel', '.owl-stage', function(e) {
if (e.deltaY > 0) {
owl.trigger('next.owl');
} else {
owl.trigger('prev.owl');
}
e.preventDefault();
});
})
</script>
Was able to modify a directive from DTing on another post to get it working with multiple carousels on the same page. Here is a working plnkr
-- Edit --
Have another plnkr to give an example on how to add an item. Doing a reinit() did not work cause any time the owl carousel is destroyed it loses the data- elements and can never initialize again.
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.items1 = [1,2,3,4,5];
$scope.items2 = [1,2,3,4,5,6,7,8,9,10];
}).directive("owlCarousel", function() {
return {
restrict: 'E',
transclude: false,
link: function (scope) {
scope.initCarousel = function(element) {
// provide any default options you want
var defaultOptions = {
};
var customOptions = scope.$eval($(element).attr('data-options'));
// combine the two options objects
for(var key in customOptions) {
defaultOptions[key] = customOptions[key];
}
// init carousel
$(element).owlCarousel(defaultOptions);
};
}
};
})
.directive('owlCarouselItem', [function() {
return {
restrict: 'A',
transclude: false,
link: function(scope, element) {
// wait for the last item in the ng-repeat then call init
if(scope.$last) {
scope.initCarousel(element.parent());
}
}
};
}]);
Here is the HTML
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/owl-carousel/1.3.3/owl.carousel.min.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/owl-carousel/1.3.3/owl.theme.min.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/owl-carousel/1.3.3/owl.transitions.min.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/owl-carousel/1.3.3/owl.carousel.min.js" />
<script data-require="angular.js#1.3.x" src="https://code.angularjs.org/1.3.15/angular.js" data-semver="1.3.15"></script>
<script data-require="jquery#2.1.3" data-semver="2.1.3" src="http://code.jquery.com/jquery-2.1.3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/owl-carousel/1.3.3/owl.carousel.min.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<data-owl-carousel class="owl-carousel" data-options="{navigation: true, pagination: false, rewindNav : false}">
<div owl-carousel-item="" ng-repeat="item in ::items1" class="item">
<p>{{::item}}</p>
</div>
</data-owl-carousel>
<data-owl-carousel class="owl-carousel" data-options="{navigation: false, pagination: true, rewindNav : false}">
<div owl-carousel-item="" ng-repeat="item in ::items2" class="item">
<p>{{::item}}</p>
</div>
</data-owl-carousel>
</body>
</html>
I tried fiddling around with different directives but had no luck until I discovered this, it might a bit of a hack fix, but it works nonetheless.
Here is my div setup:
<div ng-repeat="item in mediaitems">
<img ng-src="item.imageurl" />
</div>
$scope.mediaitems is generated using ajax call. I found that if I delayed the owl initialization until my list was populated then it would render it properly. Also if you decide you want update you list dynamically just call the setupcarousel function (look below) after the list has been populated and it will re-init the carousel.
Note that carousel init is in an external file within an anonymous function. That's just how I choosed to set it up, you can have yours in-line or however you please.
In my controller I had something like this :
$scope.populate = function(){
$timeout(function(){
$scope.mediaitems = returnedlist; //list of items retrun from server
utils.setupcarousel(); //call owl initialization
});
};
var utils = (function(){
var setupcarousel = function(){
console.log('setting up carousel..');
var owl = $('.owl-carousel');
if(typeof owl.data('owlCarousel') != 'undefined'){
owl.data('owlCarousel').destroy();
owl.removeClass('owl-carousel');
}
owl.owlCarousel({
loop: false,
nav: true,
margin: 10,
responsive: {
0: {items: 3 },
600: {items: 5},
960: { items: 8},
1200:{items: 10},
1600:{items: 12}
}
});
};
return{
....
}
})();
The Angular UI Team has put together a set of bootstrap components implemented as angular directives. They are super sleek and fast to implement, and because they are directives, you don't run into issues with using jquery in an angular project. One of the directives is a carousel. You can find it here and here. I messed around with carousels for a long time with angular. I got the owl to work after some annoying tinkering, but AngularUI's implementation is much easier.
This is the same answer as mentioned by JKOlaf. However I've added responsive behaviour to it which provides better UX.
2 major improvements:
1. Fully responsive code resulting better UX in different devices.
2. The "autoHeight" property is handled now resulting smaller thumbnail of the images.
Code goes below:
Was able to modify a directive from DTing on another post to get it working with multiple carousels on the same page. Here is a working plnkr
-- Edit -- Have another plnkr to give an example on how to add an item. Doing a reinit() did not work cause any time the owl carousel is destroyed it loses the data- elements and can never initialize again.
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.items1 = [1,2,3,4,5];
$scope.items2 = [1,2,3,4,5,6,7,8,9,10];
}).directive("owlCarousel", function() {
return {
restrict: 'E',
transclude: false,
link: function (scope) {
scope.initCarousel = function(element) {
// provide any default options you want
var defaultOptions = {
};
var customOptions = scope.$eval($(element).attr('data-options'));
// combine the two options objects
for(var key in customOptions) {
defaultOptions[key] = customOptions[key];
}
defaultOptions['responsive'] = {
0: {
items: 1
},
600: {
items: 3
},
1000: {
items: 6
}
};
// init carousel
$(element).owlCarousel(defaultOptions);
};
}
};
})
.directive('owlCarouselItem', [function() {
return {
restrict: 'A',
transclude: false,
link: function(scope, element) {
// wait for the last item in the ng-repeat then call init
if(scope.$last) {
scope.initCarousel(element.parent());
}
}
};
}]);
You can change the responsive item counts as per your requirement. Set it to a smaller value for larger thumbnail of images.
Hope this will help somebody and Thanks to the original answer provider.
I have "borrowed" the code below from other sources. As far as I can tell it is basically the same as
this MathJax demo page. The problem I'm having is that I don't see the results I have typed for odd numbered key presses. For example, when I type the first character I don't see anything in the MathPreview div. But, I see the first two characters after typing the second character. This pattern repeats so that it is as if MathJax toggles on for even key presses, but it turns off for odd numbered key presses. Any ideas why this is happening? This doesn't occur on the demo page I linked to above.
<!DOCTYPE html>
<html>
<head>
<title>Mathematics</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="bower_components/jquery/dist/jquery.min.js"></script>
<link rel="stylesheet" href="assets/css/styles.css">
<script src="bower_components/MathJax/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script>
var Preview = {
delay: 150, // delay after keystroke before updating
preview: null, // filled in by Init below
buffer: null, // filled in by Init below
timeout: null, // store setTimeout id
mjRunning: false, // true when MathJax is processing
oldText: null, // used to check if an update is needed
// Get the preview and buffer DIV's
Init: function () {
this.preview = document.getElementById("MathPreview");
this.buffer = document.getElementById("MathBuffer");
},
// Switch the buffer and preview, and display the right one.
// (We use visibility:hidden rather than display:none since
// the results of running MathJax are more accurate that way.)
SwapBuffers: function () {
var buffer = this.preview, preview = this.buffer;
this.buffer = buffer; this.preview = preview;
buffer.style.visibility = "hidden"; buffer.style.position = "absolute";
preview.style.position = ""; preview.style.visibility = "";
},
// This gets called when a key is pressed in the textarea.
// We check if there is already a pending update and clear it if so.
// Then set up an update to occur after a small delay (so if more keys
// are pressed, the update won't occur until after there has been
// a pause in the typing).
// The callback function is set up below, after the Preview object is set up.
Update: function () {
if (this.timeout) {clearTimeout(this.timeout)}
this.timeout = setTimeout(this.callback,this.delay);
},
// Creates the preview and runs MathJax on it.
// If MathJax is already trying to render the code, return
// If the text hasn't changed, return
// Otherwise, indicate that MathJax is running, and start the
// typesetting. After it is done, call PreviewDone.
CreatePreview: function () {
Preview.timeout = null;
if (this.mjRunning) return;
var text = document.getElementById("MathInput").value;
if (text === this.oldtext) return;
this.buffer.innerHTML = this.oldtext = text;
this.mjRunning = true;
MathJax.Hub.Queue(
["Typeset",MathJax.Hub,this.buffer],
["PreviewDone",this]
);
},
// Indicate that MathJax is no longer running,
// and swap the buffers to show the results.
PreviewDone: function () {
this.mjRunning = false;
this.SwapBuffers();
}
};
// Cache a callback to the CreatePreview action
Preview.callback = MathJax.Callback(["CreatePreview",Preview]);
Preview.callback.autoReset = true; // make sure it can run more than once
</script>
<script type="text/x-mathjax-config;executed=true">
MathJax.Hub.Config({
showProcessingMessages: false,
tex2jax: { inlineMath: [['$','$'],['\\(','\\)']] }
});
</script>
</head>
<body>
<div class="content">
<div id="MathJax_Message" style="display: none;"></div>
<div class="left_half_page">
<div class="content">
<div class="fill_with_padding">
<textarea class="content no_border" id="MathInput" onkeyup="Preview.Update()"></textarea>
</div>
</div>
</div>
<div class="right_half_page">
<div class="content">
<div class="fill_with_padding">
<div id="MathPreview" class="content">
<div id="MathBuffer">
<div>
<script>Preview.Init();</script>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
The problem is that your MathBuffer and MathPreview are nested. They should be siblings. The code uses a double-buffering technique that shows one buffer while the other is being typeset, and then switches the two. One is displayed while the other is hidden. If one is inside the other, you will only see the result every other keystroke.
Also, note that the contents of the buffers are replaced by the input, and so when you replace the MathPreview buffer, you remove the MathBuffer and the script it contains. Note that in the MathJax page that you link to, the two div's (the MathPreview and MathBuffer) are not nested, and the initialization script occurs after both of them (not nested within them).
If you fix the nesting problems, I think it will work for you.
trying to understand why its not working.
I have something like this.
<div class="carousel slide" id="new-prospect-container">
<div class="carousel-inner">
{{#each model}}
<div class="item">
...
</div>
{{/each}}
</div>
</div>
But Botostrap's first class api means that we don't need to execute any JS methods and their widgets will work automatically. The problem is I suspect Bootstrap would have executed this prior to my {{model}} being filled up by an Ajax requests. So this Carousel won't work.
What's annoying is i already tried turning off their data-api - $(document).off('.data-api'); and manually call their carousel method - still won't work.
The carousel works with hard coded data - but once I try to populate the carousel div items from my Ember model, it just stops working.
Any idea?
Why does this exist - https://github.com/emberjs-addons/ember-bootstrap ? does it exist to exactly solve my issue here? (although there's no carousel)
1 - I hope that this jsfiddle solve your problem.
App.CarouselView = Ember.View.extend({
templateName: 'carousel',
classNames: ['carousel', 'slide'],
init: function() {
this._super.apply(this, arguments);
// disable the data api from boostrap
$(document).off('.data-api');
// at least one item must have the active class, so we set the first here, and the class will be added by class binding
var obj = this.get('content.firstObject');
Ember.set(obj, 'isActive', true);
},
previousSlide: function() {
this.$().carousel('prev');
},
nextSlide: function() {
this.$().carousel('next');
},
didInsertElement: function() {
this.$().carousel();
},
indicatorsView: Ember.CollectionView.extend({
tagName: 'ol',
classNames: ['carousel-indicators'],
contentBinding: 'parentView.content',
itemViewClass: Ember.View.extend({
click: function() {
var $elem = this.get("parentView.parentView").$();
$elem.carousel(this.get("contentIndex"));
},
template: Ember.Handlebars.compile(''),
classNameBindings: ['content.isActive:active']
})
}),
itemsView: Ember.CollectionView.extend({
classNames: ['carousel-inner'],
contentBinding: 'parentView.content',
itemViewClass: Ember.View.extend({
classNames: ['item'],
classNameBindings: ['content.isActive:active'],
template: Ember.Handlebars.compile('\
<img {{bindAttr src="view.content.image"}} alt=""/>\
<div class="carousel-caption">\
<h4>{{view.content.title}}</h4>\
<p>{{view.content.content}}</p>\
</div>')
})
})
});
2 - I don't know why the carousel isn't include in ember-boostrap.
So I have a solution for this, but it's not for the squeamish.
Bootstrap isn't specific enough about what elements it looks for in the case of the Carousel. When the carousel function goes to inventory what elements it's to manipulate, it chokes on the metamorph tags that Ember injects into the DOM. Basically, when it goes to see how many images there are, it will always find 2 more than there actually are.
I made changes to the underlying code of the carousel in the bootstrap library, here's what I did.
Line 337, change:
this.$items = this.$active.parent().children()
TO
this.$items = this.$active.parent().children('.item')
Line 379, change:
var $next = next || $active[type]()
TO
var $next = next || $active[type]('.item')
Line 401, change:
var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()])
TO
var $nextIndicator = $(that.$indicators.children('li')[that.getActiveIndex()])
This helps the carousel plugin ignore the metamorph tags.
Hope this helps.
I had the same issue and solved it by using the following method. Note that I'm using ember-cli but it's fairly easy to adapt.
This is the templates/components/photo-carousel.hbs file:
<div id="my-carousel" class="carousel slide" data-ride="carousel">
<ol class="carousel-indicators">
{{#each photo in photos}}
<li data-target="#my-carousel" data-slide-to=""></li>
{{/each}}
</ol>
<div class="carousel-inner" role="listbox">
{{#each photo in photos}}
<div class="item">
<img {{bind-attr src="photo.completeUrl" title="photo.caption" alt="photo.caption"}} />
<div class="carousel-caption">
{{photo.caption}}
</div>
</div>
{{/each}}
</div>
<!-- removed right and left controls for clarity -->
</div>
This is the components/photo-carousel.js:
export default Ember.Component.extend({
didInsertElement: function () {
// Add the active classes (required by the carousel to work)
Ember.$('.carousel-inner div.item').first().addClass('active');
Ember.$('.carousel-indicators li').first().addClass('active');
// Set the values of data-slide-to attributes
Ember.$('.carousel-indicators li').each(function (index, li) {
Ember.$(li).attr('data-slide-to', index);
});
// Start the carousel
Ember.$('.carousel').carousel();
}
});
Note that setting the active classes manually will not be required with future versions of Ember since the each helper will provide the index of the current item.
I have a problem with the pull down refresh. It works the first time, but then if I change to a different view, then come back to the original view, the Pull to refresh and Release to refresh text seem to get duplicated and overlapped on itself. I am "hardcoding" the datasource's data here, I don't want to use the transport ajax.
I am trying to manually update the data in the setOptions pull method, instead of letting Kendo update it via ajax. The actual data update works. There are no Javascript errors and I get the same result in Chrome and Firefox.
First time works:
After moving to another view, then back to this view, then pulling down:
My view code is:
<div id="subitem-view" data-role="view" data-show="showSubItems">
<div data-role="header">
<div data-role="navbar">
</div>
</div>
<ul id="subItemList" class="itemList">
</ul>
<script id="subItemTemplate" type="text/x-kendo-template">
#:Name#
</script>
</div>
Javascript:
function showSubItems(e) {
var subItems = new kendo.data.DataSource({
data: [
{ Name : "Test1" },
{ Name : "Test2" }
]
});
e.view.element.find("#subItemList").kendoMobileListView({
dataSource: subItems,
pullToRefresh: true,
template: kendo.template($("#subItemTemplate").html())
});
if (typeof (e.view.scroller.pull) == "undefined") {
e.view.scroller.setOptions({
pull: function () {
console.log("pull event...");
subItems.data([
{ Name : "Test1 Updated" },
{ Name : "Test2 Updated" }
]);
setTimeout(function () { e.view.scroller.pullHandled(); }, 400);
}
});
}
}
You're initializing your Kendo UI Mobile ListView on every View show which leads to unpredictable results, like recreating the pull to refresh labels. You should do it in the Init event only.