introjs onafterchange fires before tooltip is positioned - javascript

While using introjs.js, I am trying to set the position of a tooltip (.introjs-tooltip) but, if I use the onafterchange event, my code runs, and then the position of the tooltip is set by introjs, and my values for top and left are overwritten. How can I make my change AFTER introjs has done it's calculations for the location of the tooltip?
<div>
<div class="divStep step1">
<span>Nothing much going on here</span>
</div>
<div id="step2" class="divStep step2">
<span>This is step 2</span>
</div>
<div id="step3" class="divStep step3">
<span>This is step 3</span>
</div>
</div>
body {
background-color: #00eeee;
}
.divStep {
display: block;
height: 100px;
width: 400px;
background-color: #fff;
margin: 10px;
padding: 10px;
}
.tt-step2 {
top: 0 !important;
left: 0 !important;
background-color: #ff0000;
}
var myIntro = {
tooltipPosition: 'bottom',
steps: [
{
intro: 'Howdy! This is step 1'
},
{
element: '#step2',
intro: 'This is step 2',
onbeforechange: function(){
console.log('onbeforechange step 2');
$('.introjs-tooltip').addClass('tt-step2');
console.log('has class? ' + $('.introjs-tooltip').hasClass('tt-step2'));
},
onchange: function(){
console.log('onchange step 2');
$('.introjs-tooltip').addClass('tt-step2');
console.log('has class? ' + $('.introjs-tooltip').hasClass('tt-step2'));
},
onafterchange: function(){
console.log('onafterchange step 2');
$('.introjs-tooltip').addClass('tt-step2');
console.log('has class? ' + $('.introjs-tooltip').hasClass('tt-step2'));
}
},
{
element: '#step3',
intro: 'This is step 3'
}
]
}
function launchIntro(){
var intro = introJs();
intro.setOptions(myIntro);
intro
.onbeforechange(function(){
currentStep = this._options.steps[this._currentStep];
if(currentStep.onbeforechange) {
currentStep.onbeforechange();
}
})
.onchange(function(){
currentStep = this._options.steps[this._currentStep];
if(currentStep.onchange) {
currentStep.onchange();
}
})
.onafterchange(function(){
currentStep = this._options.steps[this._currentStep];
if(currentStep.onafterchange) {
currentStep.onafterchange();
}
})
.start();
}
launchIntro();

I came across a similar issue. Currently IntroJS library has an open issue on github.
I had an assignment to heavily customize the elements styles and adjust some behavior.
I managed to handle the situation by using MutationObserver
Here's an example snippet:
const observer = new MutationObserver((mutations) => {
const { target } = mutations[0];
document.querySelector('.introjs-tooltip').style.top = `${
document.querySelector('.introjs-helperLayer').clientHeight
- document.querySelector('.introjs-tooltip').clientHeight - 10}px`;
return null; });
observer.observe(
document.querySelector('.introjs-tooltip'),
{ attributes: true, attributeOldValue: true, attributeFilter: ['style'] },
);

Related

Stop Event Propagation of Mapbox Layers

I have one layer and a basemap
mapboxgl.accessToken = '';
const coords = JSON.parse('{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"type":"Point","coordinates":[13.380550656438709,52.52208508665396]}},{"type":"Feature","properties":{},"geometry":{"type":"Point","coordinates":[13.380633221743006,52.52208172104466]}},{"type":"Feature","properties":{},"geometry":{"type":"Point","coordinates":[13.380686171093972,52.52208244564463]}},{"type":"Feature","properties":{},"geometry":{"type":"Point","coordinates":[13.380702060621635,52.5220511942754]}},{"type":"Feature","properties":{},"geometry":{"type":"Point","coordinates":[13.380527236009051,52.52205779286111]}}]}');
this.map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v9',
zoom: 19, // starting zoom
center: [13.380702060621635, 52.5220511942754]
});
this.map.on('load', async () => {
const controls = new mapboxgl.NavigationControl();
this.map.addControl(controls, 'top-right');
this.map.addSource('foo', {
type: 'geojson',
data: coords
});
this.map.addLayer({
id: 'points',
type: 'circle',
source: 'foo',
paint: {
'circle-radius': 5,
'circle-color': 'hotpink',
},
});
});
this.map.on('click', 'points', event => {
console.log('Layer click')
});
this.map.on('click', () => {
console.log('Basemap click')
});
body {
margin: 0;
padding: 0;
}
#map {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
#sidebar {
background-color: hotpink;
height: 100vh;
width: 200px;
position: relative;
z-index: 99999999999;
opacity: 0.5;
}
.popup-content {
display: inline-block
}
.pin-icon {
font-size: 20px;
color: blue
}
.vl {
border-left: 1px solid #bababa;
height: 15px;
padding: 0px;
margin: 10px;
}
<html>
<head>
<meta charset='utf-8' />
<title></title>
<meta name='viewport' content='initial-scale=1,maximum-;scale=1,user-scalable=no' />
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v1.2.0/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v1.2.0/mapbox-gl.css' rel='stylesheet' />
</head>
<body>
<div id='map'></div>
</body>
</html>
https://codepen.io/diesdasananas/pen/eqVLyj
Whenever I click on the circle layer, the event propagates through the basemap. Basemap click gets logged. I wonder how do I stop event propagation from the circle layer to the basemap? I cannto use event.stopPropagation() because Mapbox draws on the canvas...
One approach is to save the event coordinates of the click on the layer and then compare these coordinates with the underlying layer and its event coordinates.
let clickCoords = {};
this.map.on('click', 'points', event => {
clickCoords = event.point;
console.log('Layer click')
});
Now, detect a click on the map, not on the points layer
this.map.on('click', () => {
// check if coords are different, if they are different, execute whatever you need
if (clickCoords.x !== event.point.x && clickCoords.y !== event.point.y) {
console.log('Basemap click');
clickCoords = {};
}
});
Or, even better, use queryRenderedFeatures().
this.map.on('click', event => {
if (this.map.queryRenderedFeatures(event.point).filter(feature => feature.source === YOURLAYERNAME).length === 0) {
console.log('Basemap click');
}
});
Here is a Mapbox example.

Video.js Customizing player controls

Im Searching how to customize the player for live use with dash.js,
as browser player not work wel with live session i trying to delete seekbar and time indicators for now, in the future i will search for a seekbar that can manage live buffer.
But I can't find the correct attribute to set seekBar: false and every time indicator off,
I find this list https://docs.videojs.com/tutorial-components.html but the components seems to not work.
Which are the correct attribute to exclude that controls? Or maybe is a syntax problem?
http://jsbin.com/aheVeCOG/2/edit?js,output
Volume control to false work:
var video = videojs('my_video_1', {
children: {
controlBar: {
children: {
volumeControl: false
}
}
}
});
My try don't work
var video = videojs('my_video_1', {
children: {
controlBar: {
children: {
ProgressControl: false
}
}
}
});
Thanks!
Massimo
Just need to fix typo it works, use lowserCase at first character instead:
progressControl instead of ProgressControl
var video = videojs('my_video_1', {
children: {
controlBar: {
children: {
progressControl: false
}
}
}
});
Working example:
http://jsbin.com/damahev/2/edit?html,js,output
I was instructed to use the CSS side to show/hide any undesirable values.
So, I've been using the top four lines in style defn below for mine:
<style>
.video-js .vjs-current-time { display: block; }
.video-js .vjs-time-divider { display: block; }
.video-js .vjs-duration { display: block; }
.video-js .vjs-remaining-time { display: none; }
#.video-js .vjs-mute-control { display: none; }
#.video-js .vjs-volume-menu-button { display: none; }
#.video-js .vjs-volume-bar { display: none; }
#.video-js .vjs-progress-control { display: none; }
</style>
The bottom-four lines (after removing leading '#' char) should work for you.
(Note that you can peruse the linked file 'video-js.css' for current defns.
This worked for me:
this.video = videojs('some-id', {
bigPlayButton: true,
controlBar: {
fullscreenToggle: false,
pictureInPictureToggle: false,
remainingTimeDisplay: false,
volumePanel: false,
currentTimeDisplay: true,
timeDivider: true,
durationDisplay: true
}
})
To make currentTimeDisplay, timeDivider and durationDisplay to work you also need to add this CSS:
.vjs-current-time {
display: inline-block !important;
}
.vjs-time-divider {
display: inline-block !important;
}
.vjs-duration {
display: inline-block !important;
}

how to muliselect[at least two adjacent] items without manually pressing ctrl key and sort them with the rest of the items list?

well iam working on sortable angular ui-sortable plugin
https://github.com/angular-ui/ui-sortable
my goal :-muliselect item and sort them simultaneously.
well that can be done with muliselect library of this plugin but for that we have to manually first press and hold ctrl key and while selecting multiple item then release ctrl key now you can sort multiple items.
https://github.com/thgreasi/ui-sortable-multiselection
but i dont want user to manually press and hold ctrl key.
currently iam thinking i will trigger ctrl press key for some time and will trigger click on next item then sort the list.
i wasted lots of time on this idea but not seems working.am i doing it wrong?
Json data:-
var array = [
{
'item':1,
'superset':'true'
},
{
'item':2,
'superset':'false'
},
{
'item':3,
'superset':'true'
},
{
'item':4,
'superset':'false'
},
{
'item':5,
'superset':'true'
},
{
'item':6,
'superset':'false'},
{
'item':7,
'superset':'true'
},
{
'item':8,
'superset':'false'
},
{
'item':9,
'superset':'true'
},
{
'item':10,
'superset':'false'}
];
inside angular ng-repeat if i found superset key ==true for any item then i want its next adjacent item to be moved with it which have superset ==true.
I tried to implement your requirements with the plugins that you are using. But I couldn't get it to work. But I managed to get it working with this plugin: angular-drag-and-drop-lists.
It has a multiselection feature that was relatively easy to implement.
I copied the code from the multiselection demo(here) in their page and modified it according to your requirements.
Here is the working plunker: https://plnkr.co/edit/fq4ca0LlKbsfLtFifQ2s?p=preview.
Code:
HTML
<body ng-app="MyApp">
<div ng-controller="MyController">
<ul dnd-list dnd-drop="onDrop(model, val, index)">
<li ng-repeat="val in model.items"
dnd-draggable="getSelectedItemsIncluding(model, val)"
dnd-dragstart="onDragstart(model, event)"
dnd-moved="onMoved(model)"
dnd-dragend="model.dragging = false"
dnd-selected="val.selected = !val.selected"
ng-class="{'selected': val.selected}"
ng-hide="model.dragging && val.selected"
ng-init="val.selected=false">
{{ "Item " + val.item }}
</li>
</ul>
</div>
</body>
JS
var myAppModule = angular.module('MyApp', ['dndLists']);
myAppModule.controller('MyController', function($scope) {
$scope.model = {
items: [{
'item': 1,
'superset': true
}, {
'item': 2,
'superset': false
}, {
'item': 3,
'superset': true
}, {
'item': 4,
'superset': false
}, {
'item': 5,
'superset': true
}, {
'item': 6,
'superset': false
}, {
'item': 7,
'superset': true
}, {
'item': 8,
'superset': false
}, {
'item': 9,
'superset': true
}, {
'item': 10,
'superset': false
}],
dragging: false
};
/**
* dnd-dragging determines what data gets serialized and send to the receiver
* of the drop. While we usually just send a single object, we send the array
* of all selected items here.
*/
$scope.getSelectedItemsIncluding = function(list, item) {
item.selected = true;
if(item.superset) {
var index = list.items.indexOf(item);
list.items[index+1].selected = true;
}
return list.items.filter(function(item) {
return item.selected;
});
};
/**
* We set the list into dragging state, meaning the items that are being
* dragged are hidden. We also use the HTML5 API directly to set a custom
* image, since otherwise only the one item that the user actually dragged
* would be shown as drag image.
*/
$scope.onDragstart = function(list, event) {
list.dragging = true;
console.log(event);
if (event.dataTransfer.setDragImage) {
//event.dataTransfer.setDragImage(img, 0, 0);
}
};
/**
* In the dnd-drop callback, we now have to handle the data array that we
* sent above. We handle the insertion into the list ourselves. By returning
* true, the dnd-list directive won't do the insertion itself.
*/
$scope.onDrop = function(list, items, index) {
items = list.items.filter(function(item) {
return item.selected;
});
angular.forEach(items, function(item) {
item.selected = false;
list.items.splice(list.items.indexOf(item), 1);
});
index = index - items.length;
list.items = list.items.slice(0, index)
.concat(items)
.concat(list.items.slice(index));
$scope.$apply();
return true;
}
/**
* Last but not least, we have to remove the previously dragged items in the
* dnd-moved callback.
*/
$scope.onMoved = function(list) {
list.items = list.items.filter(function(item) {
return !item.selected;
});
};
});
CSS
/**
* The dnd-list should always have a min-height,
* otherwise you can't drop into it once it's empty
*/
.multiDemo ul[dnd-list] {
min-height: 42px;
padding-left: 0px;
}
/**
* An element with .dndPlaceholder class will be
* added to the dnd-list while the user is dragging
* over it.
*/
.multiDemo ul[dnd-list] .dndPlaceholder {
background-color: #ddd;
display: block;
min-height: 42px;
}
.multiDemo ul[dnd-list] li {
background-color: #fff;
border: 1px solid #ddd;
border-top-right-radius: 4px;
border-top-left-radius: 4px;
display: block;
margin-bottom: -1px;
padding: 10px 15px;
}
/**
* Show selected elements in green
*/
.multiDemo ul[dnd-list] li.selected {
background-color: #dff0d8;
color: #3c763d;
}
Hope this helps.

How to dynamically define the 'hero' in 'neon-animated-pages'?

I have two buttons in a page and I'd like the clicked one to play as hero in a transition to another page.
Say I have the first page:
<dom-module id="first-page">
<style>
.hero_from_1 {
position: fixed;
bottom: 0;
left: 0;
background-color: cornflowerblue;
}
.hero_from_2 {
position: fixed;
bottom: 0;
right: 0;
background-color: cornflowerblue;
}
</style>
<template>
<paper-button class="hero_from_1" raised on-tap="tapHandler_1">First Hero</paper-button>
<paper-button class="hero_from_2" raised on-tap="tapHandler_2">Second Hero</paper-button>
</template>
<script>
addEventListener('WebComponentsReady', function() {
Polymer({
is: 'first-page',
tapHandler_1: function(ev){
document.getElementsByClassName('hero_from_1')[0].setAttribute('id','hero_from');
document.getElementsByClassName('hero_from_2')[0].removeAttribute('id');
this.fire('second-page');
},
tapHandler_2: function(ev){
document.getElementsByClassName('hero_from_1')[0].removeAttribute('id');
document.getElementsByClassName('hero_from_2')[0].setAttribute('id','hero_from');
this.fire('second-page');
},
behaviors: [Polymer.NeonSharedElementAnimatableBehavior],
properties: {
animationConfig: {
value: function(){
return {
'entry':{
name: 'fade-in-animation',
node: this
},
'exit':[{
name: 'hero-animation',
id: 'hero',
fromPage: this
},{
name: 'fade-out-animation',
node: this
}]
}
}
},
sharedElements: {
value: function() {
return {
'hero': this.$.hero_from // changes in ids do not affect hero!!!
}
}
}
}
})
});
</script>
</dom-module>
with a standard sharedElements property, as documented in the project pages (https://elements.polymer-project.org/guides/using-neon-animations#page-transitions).
The problem is that even if I exchange the id of the buttons right after a click or tap event, the change does not propagate to the sharedElements function, and the animation does not work.
See fiddle:
http://jsfiddle.net/ferdinandkraft/qzxzeb1u/
The problem is that the sharedElements' function is evaluated at creation/attachment time, so it will not keep track of changing ids.
I've found a way to work aroud this, simply by setting the sharedElements property right after the tap event, just like any other property:
<dom-module id="first-page">
<style>
.hero_from_1 {
position: fixed;
bottom: 0;
left: 0;
background-color: cornflowerblue;
}
.hero_from_2 {
position: fixed;
bottom: 0;
right: 0;
background-color: cornflowerblue;
}
</style>
<template>
<paper-button class="hero_from_1" raised on-tap="tapHandler">First Hero</paper-button>
<paper-button class="hero_from_2" raised on-tap="tapHandler">Second Hero</paper-button>
</template>
<script>
addEventListener('WebComponentsReady', function() {
Polymer({
is: 'first-page',
tapHandler: function(ev){
this.sharedElements = {'hero': ev.target}; /////// here!!!
this.fire('second-page');
},
behaviors: [Polymer.NeonSharedElementAnimatableBehavior],
properties: {
animationConfig: {
value: function(){
return {
'entry':{
name: 'fade-in-animation',
node: this
},
'exit':[{
name: 'hero-animation',
id: 'hero',
fromPage: this
},{
name: 'fade-out-animation',
node: this
}]
}
}
},
/* sharedElements removed! */
}
})
});
</script>
</dom-module>
Note that ev.target will be the clicked button, so a single tapHandler will do the job!
See fiddle:
http://jsfiddle.net/ferdinandkraft/b1sw1q2o/

Change height of Highcharts with JavaScript. By default only re-sizes on window re-size

We're using HighCharts in our app, and I've added a function to expand the chart fullsize. I change the styles as well as use Javascript to change the height of the div.
However nothing changes until you actually resize the browser window. Anyone else run into this issue?
<section id="highchart-container" ng-class="{'high-chart-expanded' : highChartMax}">
<highchart id="chart1" config="chartConfig" style="height:auto"></highchart>
</section>
ChartHeader scope
function expandChartPanel() {
vm.chartMaxed = !vm.chartMaxed;
highChart = ScopeFactory.getScope('highChart');
if (vm.chartMaxed) {
highChart.highChartMax = true;
}
else {
highChart.highChartMax = false;
}
highChart.toggleChartSize();
}
HighChartDirective scope
function toggleChartSize() {
var chart1 = document.getElementById("chart1");
if (vs.highChartMax) {
chart1.style.height = "100%";
} else {
chart1.style.height = "400px";
}
}
Styles (SASS)
.high-chart-expanded {
min-height: 100% !important;
max-height: 100% !important;
width: 100% !important;
height: 100% !important;
#highcharts-6,
#chart1,
.highcharts-background,
.highcharts-container {
min-height: 100% !important;
max-height: 100% !important;
width: 100% !important;
height: 100% !important;
}
}
HighChart chartConfig
ApiFactory.quotes(buildFullUrl(url)).then(function (data) {
var quote_data = formatQuotes(data, 'quotes');
// Create the chart
vs.chartConfig = {
options: {
legend: {
itemStyle: {
color: "#333333",
cursor: "pointer",
fontSize: "10px",
fontWeight: "normal"
},
enabled: true,
floating: true,
align: 'left',
verticalAlign: 'top',
x: 60
},
chart : {
zoomType: 'x',
events: {
load: function () {
// HighChart loaded callback:
broadcastChartloaded();
}
}
},
This is what I see when I console out the chartConfig
console.log('highChart.chartConfig = ', highChart.chartConfig);
Try chart.setSize(width, height) ?
Here's a working example
UPDATE : for angular directive
To Pull out the chart object from directive you can just go the jQuery route:
var chart = $('#theChart').highcharts();
chart.setSize(width, height);
Specifically for ng-highcharts users, here's how its recommended to pull out the high-charts object by the author of the directive. The above method will work just fine too.
var chart = this.chartConfig.getHighcharts();
chart.setSize(width, height);
Although you can do it anywhere in your controller/directive/service, I would recommend you create a new service that returns this object , and then inject it in your controller if you are strict about following Angular design pattern, but if not just those two lines should work fine anywhere that you have access to chartsConfig object.
To reset the chart to being responsive again check this answer.

Categories