How to dynamically render videos in a vue carousel? - javascript

I am working with a video carousel using video.js and vueslickcarousel, these two library. The current code is as below:
<template>
<div>
<VueSlickCarousel
ref="carousel"
v-bind="settings"
#beforeChange="onSlideBeforeChange"
>
<carousel-video
v-for="video in visibleVideos"
....
></carousel-video>
</div>
</template>
....
methods: {
onSlideBeforeChange: function(currentIndex, nextIndex) {
...calculate lowerBound, upperBound for visibleVideos.
},
},
computed: {
visibleVideos: function() {
slice from a videoArray using lowerBound and upperBound
}
}
Here visibleVideos is a computed property. I want to increase/decrease the number of <carousel-video> rendered dynamically because the list is huge and can't render all of them at once.
However when the upperBound and lowerBound update is triggerd based on some condition, the internal state of VueSlickCarousel doesn't update.
The items are rendered in dom but these two class is missing in slick-list items - slick-current slick-active. That's why nothing is shown in the view. After that, If I click the next or previous button provided by VueSlickCarousel, it removes the whole component from dom.
Before updating upperBound and lowerBound
After update of upperBound/lowerBound
How can I fix this? Or is there any other library or way to efficiently render video.js videos in a carousel? Any similar implementation or opensource example will also be equally helpful.
Thanks in advance for any kind of help or suggestion.

Related

Combining Vue v-for with GSAP transitions

I have two components, one that generates a bunch of divs
<div
v-for="(app, index) in items.apps"
ref="appIcon"
:key="index"
>
<div #click="openApp(app)"></div>
</div>
with a method that pushes the clicked div into an array of openedApps
openApp(appName) {
this.items.openedApps.push(appName)
}
then, in a separate component, the array of apps is generated dynamically based on it's content
<div
v-for="(app, index) in items.openedApps"
ref="appWindow"
:key="index"
>
</div>
and I want to make transitions for all items in openedApps array, but separately, preferably using GSAP.
but when I do it like this
mounted() {
let { appWindow } = this.$refs
let timeline = new TimelineLite()
timeline.from(appWindow, 1, {opacity:0})
}
nothing happens and I get an error from GSAP saying "GSAP target undefined not found".
So I'm guessing there's something wrong with the usage of mounted() hook here, but I have no clue what to do. Been hitting my head against the wall on this one for two days trying to figure it out because I really want to use GSAP for this project instead of just the standard , but I just dont know how to succesfully target certain divs using GSAP and Vue.

When appending items dynamically, masonry does not layout them out

I am using a Masonry JavaScript grid layout library https://masonry.desandro.com/. I have an issue when appending new items to the grid directly from the DOM using Angular *ngfor which iterate through an a array as follows:
<div class="grid">
<div class="grid-item grid-item--height{{i}}" *ngFor="let i of array">
{{i}}
</div>
</div>
Because I am not appending them using the "appended masonry method", masonry does not layout them out. The array is getting new elements every time a user scroll down. So a JavaScript method is called:
onScrollDown() {
// add another 20 items
this.sum += 20;
for (let i = start; i < this.sum; ++i) {
this.array.push(i); }
}
When the array get more elements, it automatically creates new html items. So i need to find the way masonry layout them out again after the new items are being created. I tried calling $grid.masonry('layout') after the elements have being added to the array in the OnScrollMethod but it did not work.
I am trying $('.grid').masonry('reloadItems') as well, but calling this method the new items are overlapping the previous ones.
I would appreciate any help.
Update:
I am initializing masonry using my angular initialize component method as follows:
ngOnInit() {
this.$grid = jQuery('.grid').masonry({
// options
itemSelector: '.grid-item',
columnWidth: 384,
gutter: 24
});
UPDATED
You might try reloadItems. If your loading images, use imagesLoaded.js to avoid overlaps:
ngOnInit() {
this.$grid = jQuery('.grid').imagesLoaded( function() {
jQuery('.grid').masonry({
// options
itemSelector: '.grid-item',
columnWidth: 384,
gutter: 24
});
});
onScrollDown() {
// add another 20 items
this.sum += 20;
for (let i = start; i < this.sum; ++i) {
this.array.push(i); }
jQuery('.grid').imagesLoaded( function() {
jQuery('.grid').masonry('reloadItems');
});
}
It's been a lot of years since I had to do this, but basically:
Masonry runs a lot of JS, in order to figure out optimal bin-packing for the elements you are creating.
That means that it hard-codes a lot of style information on each "card" inside of your area, and after it calculates it, it sets it.
If you use Masonry to add new "cards" to that view, then it will continue to update the view, and change the layout and the sizes of those cards.
If you don't use the Masonry instance you had before (you use the element, not the Masonry object), then Masonry doesn't have a way to track the changes. At that point, you either have to add them to the DOM and then add them to the object, through addItems, and THEN call layout() (or masonry())... or you have to rewrap the DOM element in Masonry again, now that the element has updated.
This might have changed, but I doubt it.
There are things you could do by writing angular directives, I guess, but the cheap and cheerful way of accomplishing this would be to save the instance of your component's element (or the element that controller is bound to... whatever), and also save the reference to the Masonry view you made, and on update, add all of the items, and call layout again...
...or just, on each render, make a new Masonry view, using your root element.
PS: make sure to load a lot of new elements at once, because if you're just loading 3 at a time it'll be messy...
...also, make sure you pre-load the images in the elements, because if the images load afterwards your Masonry layout is going to get very, very messy.
I used masonry-layout in Vue.js and had the same problem. The way that solved my issue is:
onScrollDown() {
// ...
// after add another 20 items
this.msnry.reloadItems();
this.msnry.layout();
}
That this.msnry is my masonry-layout instance.

Polymer, evaluate element based off object

I am using the tile example from polymers neon elements - and I am trying to make each expanded tile unique. My first try on how to do this was to pass a string in with the grid items like
{
value: 1,
color: 'blue',
template: 'slide-1'
}
And have that element be evaluated when rendered in a new element something like this. (this is the card template itself)
<template>
<div id="fixed" class$="[[_computeFixedBackgroundClass(color)]]"></div>
<div id="card" class$="[[_computeCardClass(color)]]">
<[[item.template]]></[[item.template]]>
</div>
This does not work - however I am wondering if there is some way to do this so I can load custom elements for the content of each card. For reference -https://elements.polymer-project.org/elements/neon-animation?view=demo:demo/index.html&active=neon-animated-pages , it is the grid example and I am trying to replace the content of each card once it is clicked on ( the fullsize-page-with-card.html, here is all the html for it - https://github.com/PolymerElements/neon-animation/tree/master/demo/grid ). Is this the wrong way of approaching this? Or maybe I have some syntax wrong here. Thanks!
Edit : OK, So I can send it through if i add it to the click to open the card like so
scope._onTileClick = function(event) {
this.$['fullsize-card'].color = event.detail.data.color;
this.$['fullsize-card'].template = event.detail.data.template;
this.$.pages.selected = 1;
};
and in the card's properties like so
template: {
type: String
},
So I can then evaluate it as [[template]] , however - the question still remains how to call a custom element (dynamically) using this string. I could pass a couple of properties and fill in a card or form so they are unique, but i think I would have much more creative freedom if I could call custom elements inside each card.
I have an element that allows referenced templates. There are a couple of others other there, but this one also allows data bindings to work: https://github.com/Trakkasure/dom-bindref

polymer - how to get next corresponding element in a repeating template?

sorry for the sort of specific question. I'm trying to make an accordion using core-collapse and repeating templates and am facing some difficulties. Here is my accordion repeating template:
<template repeat="{{item, i in items}}">
<div class="accordheader" on-click="{{toggle}}">{{item}}</div>
<template repeat="{{content, i in contents}}">
<core-collapse class="collapse">
<p>{{content}}</p>
</core-collapse>
</template>
</template>
and here is my script:
toggle: function () {
//get whichever 'accordheader' clicked on
var collapseGetting = this.shadowRoot.querySelector('.accordheader');
console.log(collapseGetting);
//find the core-collapse that is directly underneath it
var collapse = $(collapseGetting).next('core-collapse');
console.log(collapse);
//toggle that particular core-collapse
collapse.toggle();
console.log('toggled');
}
And now my toggle is entirely broken and won't actually toggle anything. I still receive the 'toggled' console log but nothing is happening. I'm not sure if I'm targeting the next core-collapse correctly, or even nesting the repeating templates correctly. Basically I have 2 arrays, [items] and [contents] which I am retrieving my item for each .accordheader and my content for each core-collapse.
Any insight? I feel like this is a formatting or template issue..
Having a quick peek at the docs here:
http://www.w3schools.com/jquery/traversing_next.asp
(See the 'Try It Yourself' example')
I believe you're current code is getting the next '.accordheader' element instead of the next 'core-collapse'.
If I've interpreted the link properly then it should look something like this:
$('core-collapse').Next( console.log(collapse); );
Never used 'Next' before, but there's my 5 cents.

Reactjs append an element instead of replacing

I'm trying to iterate through a list/array/object of things: (I used coffeescript to keep it clear, jsfiddle of full JS here. but it's just a forEach)
pages = for page, each of #props.ids
$('#start').append("<div id='"+page+"' ></div>")
React.renderComponent page(title: each.title, text: each.text), $("#"+page)[0]
and append each of them, instead of replacing, leaving only the last item in the list.
Where the #start element is the starting container, and I want to populate it with multiple elements, but I need to give each their own container, otherwise they will all overwrite eachother, the default reactjs behaviour.
I'm wondering if there's a way to tell react to append instead of replacing the div.
I'd like to avoid using jquery if possible, and do it purely react-ly.
I though about giving the React.renderComponent page the initial list, and then iterate in the previously called template, however, then i'm facing a different problem, I have to return a reactjs element object, consisting of the whole list, which I really don't prefer.
I need for the initial call to create individual, independent react templates, appending eachother in a list, prefferably without any extra containers, or using jquery.
I think you're getting the concept wrong. React's renderComponent indeed renders a single component, somewhere. Doing this multiple times only re-renders the same component at that place (aka idempotent). There's no real "append" statement, at least not in the way you asked for.
Here's an example of what you're trying to achieve. Forgot about renderComponent in this. It's just to put the component somewhere.
/** #jsx React.DOM */
var pages = [{title: 'a', text: 'hello'}, {title: 'b', text: 'world'}];
var App = React.createClass({
render: function() {
return (
<div>
{
this.props.pages.map(function(page) {
return <div>Title: {page.title}. Text: {page.text}</div>;
})
}
</div>
);
}
});
React.renderComponent(<App pages={pages} />, whateverDOMNodeYouWantItToBeOn);
See what I did there? If I want multiple divs, I just create as many as I want to see. They represent the final look of your app, so making a same div be "appended" multiple times doesn't really make sense here.
Create a div with a class extradiv:
<div class="extradiv">
</div>
In CSS, Set It's display to none:
.extradiv {
display: none;
}
.extradiv * {
display: none;
}
In JS implement this function:
function GoodRender(thing, place) {
let extradiv = document.getElementsByClassName('extradiv')[0];
ReactDOM.render(thing, extradiv);
extradiv = document.getElementsByClassName('extradiv')[0];
place.innerHTML += extradiv.innerHTML;
}
Than you can use this in place of ReactDOM.render:
GoodRender(<Component>My Text</Component>, YourDOMObject)

Categories

Resources