How to add dynamic labels along the prev/next arrows in Swiper? - swiper.js

You've seen this on other sliders, where along with the nav arrows you'd have some label/title indicating the contents of the next/previous slide.
Hopefully someone has already done this, so I can copy and adapt the code. Failing that I guess I'll experiment with different events listeners, pulling the text from sibling slides, and changing the content of active slide's nav divs.
Though maybe it's better to save the label text as data attributes? I don't know. Just brainstorming the approach here...

I figured it out. Instead of tracking the active slide, I'm adding labels when the swiper is initialised. Sharing it here, in case anyone here has a similar question.
let homeSwiper = new Swiper('.home-swiper', {
loop: true,
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
on: {
init: function () {
addLabels();
},
},
});
Each slide has a hidden span that I use as its label. I then just pull those labels from the surrounding slides:
function addLabels() {
let homeSlides = document.querySelectorAll('.home-slider .swiper-slide');
homeSlides.forEach((slide, i) => {
let labelPrev, labelNext;
if (i === 0) {
labelPrev = homeSlides[homeSlides.length - 1].querySelector('span.label').textContent;
labelNext = homeSlides[i + 1].querySelector('span.label').textContent;
} else if (i === homeSlides.length - 1) {
labelPrev = homeSlides[i - 1].querySelector('span.label').textContent;
labelNext = homeSlides[0].querySelector('span.label').textContent;
} else {
labelPrev = homeSlides[i - 1].querySelector('span.label').textContent;
labelNext = homeSlides[i + 1].querySelector('span.label').textContent;
}
slide.querySelector('.slide-nav-label--prev').textContent = labelPrev;
slide.querySelector('.slide-nav-label--next').textContent = labelNext;
})
}

Related

How to prevent closing of cell edit mode on validation errors with custom vue components in ag-grid

I have succesfully rendered my own component as the cellEditor and would like and on-leave I would like it to try to validate the value and prevent the closing if it fails.
If I look at this then https://www.ag-grid.com/javascript-grid-cell-editing/#editing-api there's cancelable callback functions for editing. But in this callback function is there a way to access the current instantiated component? I would think that would be the easiest way to handle this.
I'm using vee-validate so the validation function is async, just to keep in mind.
Use Full row editing.
Create a global variable like
var problemRow = -1;
Then Subscribe to this events:
onRowEditingStarted: function (event) {
if (problemRow!=-1 && event.rowIndex!=problemRow) {
gridOptions.api.stopEditing();
gridOptions.api.startEditingCell({
rowIndex: problemRow,
colKey: 'the column you want to focus',
});
}
},
onRowEditingStopped: function (event) {
if (problemRow==-1) {
if (event.data.firstName != "your validation") {
problemRow = event.rowIndex
gridOptions.api.startEditingCell({
rowIndex: problemRow,
colKey: 'the column you want to focus',
});
}
}
if (problemRow == event.rowIndex) {
if (event.data.firstName != "your validation") {
problemRow = event.rowIndex
gridOptions.api.startEditingCell({
rowIndex: problemRow,
colKey: 'the column you want to focus',
});
}
else{
problemRow=-1;
}
}
},
I had a similar issue - albeit in AngularJS and the non-Angular mode for ag-grid - I needed to prevent the navigation when the cell editor didn't pass validation.
The documentation is not very detailed, so in the end I added a custom cell editor with a form wrapped around the input field (to handle the niceties such as red highlighting etc), and then used Angular JS validation. That got me so far, but the crucial part was trying to prevent the user tabbing out or away when the value was invalid so the user could at least fix the issue.
I did this by adding a value parser when adding the cell, and then within that if the value was invalid according to various rules, throw an exception. Not ideal, I know - but it does prevent ag-grid from trying to move away from the cell.
I tried loads of approaches to solving this - using the tabToNextCell events, suppressKeyboardEvent, navigateToNextCell, onCellEditingStopped - to name a few - this was the only thing that got it working correctly.
Here's my value parser, for what it's worth:
var codeParser = function (args) {
var cellEditor = _controller.currentCellEditor.children['codeValue'];
var paycodeId = +args.colDef.field;
var paycodeInfo = _controller.paycodes.filter(function (f) { return f.id === paycodeId; })[0];
// Check against any mask
if (paycodeInfo && paycodeInfo.mask) {
var reg = new RegExp("^" + paycodeInfo.mask + '$');
var match = args.newValue.match(reg);
if (!match) {
$mdToast.show($mdToast.simple().textContent('Invalid value - does not match paycode format.').position('top right').toastClass('errorToast'))
.then(function(r) {
_controller.currentCellEditor.children['codeValue'].focus();
});
throw 'Invalid value - does not match paycode format.';
}
}
return true;
};
The _controller.currentCellEditor value is set during the init of the cell editor component. I do this so I can then refocus the control after the error has been shown in the toast:
CodeValueEditor.prototype.init = function (params) {
var form = document.createElement('form');
form.setAttribute('id', 'mainForm');
form.setAttribute('name', 'mainForm');
var input = document.createElement('input');
input.classList.add('ag-cell-edit-input');
input.classList.add('paycode-editor');
input.setAttribute('name', 'codeValue');
input.setAttribute('id', 'codeValue');
input.tabIndex = "0";
input.value = params.value;
if (params.mask) {
input.setAttribute('data-mask', params.mask);
input.setAttribute('ng-pattern','/^' + params.mask + '$/');
input.setAttribute('ng-class',"{'pattern-error': mainForm.codeValue.$error.pattern}");
input.setAttribute('ng-model', 'ctl.currentValue');
}
form.appendChild(input);
this.container = form;
$compile(this.container)($scope);
_controller.currentValue = null;
// This is crucial - we can then reference the container in
// the parser later on to refocus the control
_controller.currentCellEditor = this.container;
$scope.$digest();
};
And then cleared in the grid options onCellEditingStopped event:
onCellEditingStopped: function (event) {
$scope.$apply(function() {
_controller.currentCellEditor = null;
});
},
I realise it's not specifically for your components (Vue.js) but hopefully it'll help someone else. If anyone has done it a better way, I'm all ears as I don't like throwing the unnecessary exception!

Performance issues with infinite scrolling and v-for

I’ve just recently started using Vue and so far so good, but I’ve ran into a bit of an issue that I can’t figure out a good solution to.
I have a photo gallery with a few different sections. I have an overall gallery component, a gallery section component and an image component. Essentially, I’m using a photos array for each section to store the photos data for that section. Within the sections I’m using v-for to display the photos. The gallery is infinitely scrolling so when you scroll to the bottom, more images load and the photos array for that section is updated.
Here’s my problem, currently the photos arrays are stored on the data of the overall gallery component, so when I update one of the photos arrays it seems to cause the entire gallery to rerender. The more images on the screen, the worse effect this has on the performance and the less responsive the page becomes.
I’m aware I could move the photos array to the data of the individual sections, but as far as I can tell this would still rerender that entire section.
I don’t really know if there’s any good solution that’ll do what I’m trying to do, having a certain amount of reactivity but only updating the things that changed. I don’t know if something like that is possible.
I’ve tried messing around with computed data, props, methods etc. but I can’t work out a better solution.
Here’s the code I’ve been working with in the overall gallery component:
<template>
<div class="photo-gallery">
<gallery-section v-for="(section, index) in sections" v-bind:section="section" class="photo-gallery__section" v-bind:key="index"></gallery-section>
</div>
</template>
<script>
import * as qb from "../queryBuilder";
let layout = [
{
title: "Highlighted Photos",
request: {
filters: qb.group([
qb.filter("rating", ">=", 4),
]),
options: {
offset: 0,
limit: 2,
order: ["rand()"],
size: 740
}
},
total: 2,
photoClass: "photo--highlighted",
loading: false,
photos: []
},
{
title: "More photos",
request: {
filters: qb.group([
qb.filter("rating", ">=", 2),
]),
options: {
offset: 0,
limit: 40,
order: ["rand()"]
}
},
total: Infinity,
loading: false,
photos: []
}
];
export default {
data() {
return {
sections: layout,
currentSection: 0
}
},
mounted() {
this.getPhotos(this.sections[0]);
this.getPhotos(this.sections[1]);
},
methods: {
getPhotos(section) {
section.loading = true;
let currentSection = this.currentSection;
fetch(`api/photos/search/filter/${JSON.stringify(section.request.filters)}/${JSON.stringify(section.request.options)}`)
.then(response => response.json())
.then(response => {
section.photos.push(...response.images);
// Set offset for next request
this.sections[this.currentSection].request.options.offset = section.photos.length;
// Check if current section is complete or if less than the requested amount of photos was returned
if (
this.sections[this.currentSection].total === section.photos.length ||
response.images.length < this.sections[this.currentSection].request.options.limit
) {
if (this.sections.length -1 != this.currentSection) {
// Move onto next section
this.currentSection++;
} else {
// Set currentSection to null if all sections have been fully loaded
this.currentSection = null;
}
}
})
.finally(() => {
section.loading = false;
});
},
scrollHandler() {
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 500) {
if (this.currentSection != null && !this.sections[this.currentSection].loading) {
this.getPhotos(this.sections[this.currentSection]);
}
}
}
},
created() {
window.addEventListener("scroll", this.scrollHandler);
},
destroyed() {
window.removeEventListener("scroll", this.scrollHandler);
}
}
</script>
One thing I've also noticed is that whenever more photos are loaded, the mount function for every photo component on the page runs.
Would anyone be able to point me in the right direction? Any advise would be very much appreciated.
Thank you, Jason.
The issue was the the way I was generating the keys for my photos component instances, which cannot be seen in the code I included above. I figured out that the random number being generated as the key meant Vue could not keep track of the element as the key would keep changing. I'm now generating unique keys on the server side and using them instead. It works as expected now.

dc.js composite chart toggle legend loses its translucence upon filtering

I have used this solution to get a toggle legend for a composite line chart and it works perfectly fine.
However, after i added a range chart to this composite chart, the deselected legend loses its translucence and becomes normal.
How can i keep the deselected legend object in faded state while filtering?
Here are screenshots for reference:
Before filter:
After filter:
This is the code I'm using for charts:
multiLineChart
.width(1000)
.height(300)
.transitionDuration(1000)
.margins({top: 30, right: 50, bottom: 40, left: 40})
.x(d3.time.scale().domain([startDate,endDate]))
.yAxisLabel("Data (Scaled)")
.xAxisLabel("Date And Time")
.rangeChart(timeSlider)
.legend(dc.legend().x(800).y(20).itemHeight(13).gap(5))
.renderHorizontalGridLines(true)
//.dimension(DateDim)
.compose([
dc.lineChart(multiLineChart)
.dimension(DateDim)
.colors('red')
.group(Line1Grp, 'Line1'),
dc.lineChart(multiLineChart)
.dimension(DateDim)
.colors('blue')
.group(Line2Grp, 'Line2')
])
.brushOn(false)
.on('pretransition.hideshow', function(chart) {
chart.selectAll('g.dc-legend .dc-legend-item')
.on('click.hideshow', function(d, i) {
var subchart = chart.select('g.sub._' + i);
var visible = subchart.style('visibility') !== 'hidden';
subchart.style('visibility', function() {
return visible ? 'hidden' : 'visible';
});
d3.select(this).style('opacity', visible ? 0.2 : 1);
});
});
//.xAxis().tickFormat(d3.time.format("%b %d %H:%M"));
timeSlider
.width(1000)
.height(50)
.margins({top: 0, right: 50, bottom: 20, left: 40})
.dimension(DateDim)
.group(Line1Grp)
.x(d3.time.scale().domain([startDate, endDate]))
.on("filtered", function (chart) {
dc.events.trigger(function () {
multiLineChart.focus(chart.filter());
dc.redrawAll(chart.chartGroup());
});
})
.xAxis().tickFormat(d3.time.format("%b %d"));
Here is a fiddle for the same.
Any help is appreciated.
Thanks for pointing this out - there was a bad practice in my earlier answer, and I went back and corrected it.
It's always better style, and more robust, to separate event handling and drawing, and always draw everything based on the data, not some event that is in flight.
If you follow these practices, then the code looks more like this:
function drawLegendToggles(chart) {
chart.selectAll('g.dc-legend .dc-legend-item')
.style('opacity', function(d, i) {
var subchart = chart.select('g.sub._' + i);
var visible = subchart.style('visibility') !== 'hidden';
return visible ? 1 : 0.2;
});
}
function legendToggle(chart) {
chart.selectAll('g.dc-legend .dc-legend-item')
.on('click.hideshow', function(d, i) {
var subchart = chart.select('g.sub._' + i);
var visible = subchart.style('visibility') !== 'hidden';
subchart.style('visibility', function() {
return visible ? 'hidden' : 'visible';
});
drawLegendToggles(chart);
})
drawLegendToggles(chart);
}
multiLineChart
.on('pretransition.hideshow', legendToggle);
Now, whenever we redraw the composite chart and its legend - no matter what the cause - all of the items in the legend will be updated based on whether the corresponding child chart has been hidden.
And the event handler is only concerned with hiding and showing charts, not drawing.
Fork of your fiddle.

How to access or get value of specific graph on chart plot by click event?

I use vue-chartjs to draw some chart like line, bar, etc.
In my project, there are many cases using specific value or lable of data in chart.
Using tooltip option of vue-chartjs, I can check that value or label of data item when hovered.
I want to know how to access or get information of specific data matched with point on graph when clicked(not hovered).
Here is my code about chart options.
chartOptions: {
responsive: false,
onClick: function(evt){
//Some logic to get value of label of specific data...(type, label, value, ...)
}
In my case, I use 'onclick' option to access specific data on point triggered 'click' event. In 'onClick' callback, I checked all of chart elements and dataset, etc.
How can I get value of label specific dataItem on point of graph(like line) or bar of graph(like bar) when triggered click event?
I was not able to find a solution that worked for me, but I dug a little bit and this is what I came up with.
onClick: function(evt, array) {
if (array.length != 0) {
var position = array[0]._index;
var activeElement = this.tooltip._data.datasets[0].data[position]
console.log(activeElement);
} else {
console.log("You selected the background!");
}
}
This will get the position in the array that you clicked and grab the data from what position you clicked. This may not be the prettiest or best example, but it worked for me.
This solution use the getElementAtEvent method of chartjs, but to use that you need reference to the Chart itself, not the Vue component. We can get that from the $data._chart property. To use this in a parent Vue component, we use the $refs as seen below`.
So parent defines the chart options
{
...
options: {
onClick: this.handleChartClick
}
...
}
and then parent method, using $refs with $data._chart to get the chart. We get the datasetIndex and value and also the tooltip
handleChartClick(evt, elements) {
var chart = this.$refs.periodChart.$data._chart;
const chartIndex = chart.getElementAtEvent(evt);
if (chartIndex.length !== 0) {
const datasetIndex = chartIndex[0]._datasetIndex;
const position = chartIndex[0]._index;
const info = {
datasetIndex: datasetIndex,
valueIndex: position,
label: chart.tooltip._data.labels[position],
value: chart.tooltip._data.datasets[datasetIndex].data[position]
};
console.log(info);
} else {
console.log("Background clicked");
}

How to add background-color to text navigation on image slider?

I have an image slider that is controlled by text navigation. The text is highlighted orange when it's relative slide is current in the gallery. I would like the other text to have an inactive state with a black background but cannot get this to work!
(In case that didn't make much sense! Basically, I want background-color orange when current, background-color black when inactive.) THANKS
$(document).ready(function(){
$('.slider').each(function(e){
if(e == 0){
$(this).addClass('current');
}
$(this).attr('id', 'handle' + e);
})
$('.tabs li').each(function(e){
if(e == 0){
$(this).addClass('current'); //adds class current to 1st li
}
$(this).wrapInner('<a class="title"></a>'); //wraps list items in anchor tag
$(this).children('a').attr('href', '#handle' + e);//adds href to the anchors
t = $(this).children('a').text();
$('#handle' + e).append('<h2>' + t + '</h2>'); //adds h2 and text to big images
})
$('.tabs li a').click(function(){
c = $(this).attr('href');
if($(c).hasClass('current')){
return false;
}else{
showImage($(c), 20);
$('.tabs li').removeClass('current');
$(this).parent().addClass('current');
return false;
}
})
runRotateImages();
$("#featured").hover(
function(){
clearTimeout(xx);
},
function(){
runRotateImages();
}
)
})
function showImage(img, duration){
$('.slider').removeClass('current').css({
"opacity" : 0.0,
"zIndex" : 2
});
img.animate({opacity:1.0}, duration, function(){
$(this).addClass('current').css({zIndex:1});
});
}
function rotateImages(){
var curPhoto = $("div.current");
var nxtPhoto = curPhoto.next();
var curTab = $(".tabs li.current");
var nxtTab = curTab.next();
if (nxtPhoto.length == 0) {
nxtPhoto = $('#featured div:first');
nxtTab = $('.tabs li:first-child');
}
curTab.removeClass('current');
nxtTab.addClass('current');
showImage(nxtPhoto, 300);
}
function runRotateImages(){
xx = setInterval("rotateImages()", 5000);
}
I have added a jsfiddle - http://jsfiddle.net/n5EPM/3/
However, on jsfiddle it does not seem to automatically cycle through the images, not sure why, have no problems in browser.
Try using not() method: http://api.jquery.com/not/
Basically, you need to create a new class disabled
.disabled{
background-color:#000000;
}
Then, add the following line to your tabs.li's each loop:
$(".tabs li").not(".current").addClass('disabled'); //add disabled class for non-current tabs
At last you need to remove disabled class in the rotateimage() function before assigning current and then disable non-current again. like this:
curTab.removeClass('current');
nxtTab.removeClass('disabled'); //remove diabled class
nxtTab.addClass('current');
$(".tabs li").not(".current").addClass('disabled'); // disable non-current again
Working jsfiddle here: http://jsfiddle.net/n5EPM/9/
This might not be the perfect solution but you will need to tweak it a little bit.
Hope this helps.