Is there a way to make an ion-modal-sheet to snap the header in Ionic V6 - ionic-framework

I'm currently developing an app using Ionic V6 and i'm figured out if it was possible to make an ion-modal-sheet snap the header when it fully swiped like the Uber eats application ?
Thanks in advance for your answers and have a nice day !

For those who wondering how to do it, here's how i managed to get something similar:
first of all i've added an id to my header (my ion-header is wrapped inside a component called header) if your header is always in the same page of where you're creating the modal, use a template reference variable instead of an id:
<header
id="getHeight"
[title]="'Solutions disponibles'"
[displayGoBack]="true"></header>
then i've created a helper to:
Get the height of the header (can be different between iOS and Android)
Get the body height
Use the body height and the header height to return the height my modal should have in px and in %
export const getContentHeight = (): any => {
const header = document.querySelector('#getHeight');
const body = document.querySelector('body');
if (body && header) {
const percent = (100 - (header.clientHeight * 100 / body.clientHeight));
return {
height: body.clientHeight - header.clientHeight,
percent: percent
}
}
}
After that i've created another helper to change the visual aspect of my modal when breakpoint reach 0.9:
export const setModalSheetContentHeight = (modal: HTMLIonModalElement, breakpoint: number, expand: boolean): any => {
if (breakpoint >= 0.9 && !expand) {
modal.classList.add('no-radius');
modal.setCurrentBreakpoint(1);
expand = true;
} else if (breakpoint <= 0.9 && expand) {
modal.classList.remove('no-radius');
expand = false;
}
return expand;
}
The boolean "expand" avoid the helper to always set the modal breakpoint to 1.
Then i've created a method that add some listeners on the modal:
initModalListener(): void {
let expand = false;
// Define if the modal is fully expand
const content = getContentHeight();
// Return an object with content height in px and in %
addEventListener('ionModalWillPresent', () => {
this.modal.setAttribute('style', `--height: ${content.height}px`);
// Set the modal height before it shown to avoid visual bug
});
addEventListener('ionBreakpointDidChange', (e: any) => {
const breakpoint = e.detail.breakpoint;
expand = setModalSheetContentHeight(this.modal, breakpoint, expand);
});
}
And after all of that i call this method inside of the method that create my modal:
private async presentModal(): Promise<void> {
this.modal = await this.modalCtrl.create({
component: YourModalComponent,
breakpoints: [0.3, 0.65, 1],
initialBreakpoint: 0.3,
backdropBreakpoint: 1,
showBackdrop: false,
canDismiss: false,
keyboardClose: true,
id: 'modalSheet',
}
});
this.initModalListener();
await this.modal.present();
}
This is a first shot so i'm sure that there is plenty of thing to adjust but for now, it work pretty well with Ionic 6.

Related

How to make fixed header availabel at the focus an focusable element?

I have a fixed header, when user scroll to an offset 100px, a class add to header to make it fixed:
fixed-header {
position: fixed:
top: 0,
left: 0,
right: 0,
width: 100%;
}
$( window ).on( 'scroll', function() {
if ( $( window ).scrollTop() >= 100 ) {
navigation.classList.add('.fixed-header');
document.body.style.paddingTop = "100px" // prevent page jump when fixed header
} else {
navigation.classList.remove('.fixed-header');
document.body.style.paddingTop = "0"
}
});
On top of page, when user focus a link via tab key, I use focus event listener to check the fixed class in DOM to scroll focusable element to re-position it below fixed header:
const elements = [...document.querySelectorAll(
"a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled])"
)];
header = document.body.querySelector('.header-top'); // Fixed header height: 100px
const handleFocus = (e) => {
console.log(header.classList.contains('.fixed-header')); // This will false
if (header.classList.contains('.fixed-header')) {
var windowScrollTop = $( window ).scrollTop(),
focusableScrollTop = $(e.target).offset().top;
if (focusableScrollTop - windowScrollTop < 100) {
window.scrollTo({ top: focusableScrollTop - 100, behavior: 'smooth' });
}
}
};
for (const element of elements) {
element.addEventListener('focus', handleFocus, { once: true });
}
But focus callback handler fire before DOM repaint (when fixed class added to header), so it can not detect fixed class in DOM:
Wrap focus handler inside requestAnimationFrame()', don't work, if I wrap focus handler inside setTimeout()' function with 300ms, it will detect correct fixed class, but cause page scroll jump (focusable element scroll up and down).
for (const element of elements) {
element.addEventListener('focus', setTimeout(() => {
handleFocus()
}, 300), { once: true });
}
Any way to make header.classList.contains('.fixed-header') come true at the focusable link focused?
I hope someone know this issue and help to fix this problem. Thank you very much.

Position the dialog at the center of the screen in Fiori

I have a SAPUI5 Fiori application.
I use theme sap_fiori_3 as the base theme.
I customized this theme and only attached a background image to the theme.
The interesting part is when I activate this customized theme (that only has an extra background image in comparison to original sap_fiori_3 theme), the dialog are not centered in my app anymore.
The dialog are made with sap.m.dialog class.
I wrote a small snippet of code to center the dialog like following:
onAfterDialogOpen: function(oEvent){
var oDialog = oEvent.getSource(),
$Dialog = oDialog.$(),
oPosition = $Dialog.position(),
iTop = oPosition.top,
iLeft = oPosition.left,
iDialogWidth = $Dialog.width(),
iDialogHeight = $Dialog.height(),
iScreenWidth = sap.ui.Device.resize.width,
iScreenHight = sap.ui.Device.resize.height,
iNewTop = Math.floor((iScreenHight-iDialogHeight)/2),
iNewLeft = Math.floor((iScreenWidth-iDialogWidth)/2);
if(Math.abs(iNewLeft-iLeft) > 10 & Math.abs(iNewTop-iTop) > 10){
$Dialog.offset({top: iNewTop, left: iNewLeft});
}
},
But it is not a good solution. Why? Because it makes a motion on my screen like following:
Now the question is, how can I center the dialog without Java Script and by settings or some other tricks that when the dialog is opened, it be already centered.
Please note that using onBeforeOpen event is not possible as I need the size and position of the dialog!
I finally found out what is the source of the problem. It seems the Theme Designer of SAP is buggy and some part of the theme code does not transfer to the exported file.
When I use the theme designer to customize the theme it not only made the mentioned error, but also some other strange behavior appear in the deployed applications in the fiori launchpad which use the customized theme. However, we don't have those errors in the development time in the WEB IDE.
Therefore as I only needed to customize the following items:
background image
logo
favicon
I tried to use the standard theme like sap_fiori_3 and work around for setting these properties.
So for the first 2 issues I used the CSS hack:
div.sapUShellFullHeight {
background-image: url(../images/myBackgroundImage.svg);
background-repeat: no-repeat;
background-size: contain;
background-position: right;
}
a#shell-header-logo > img#shell-header-icon {
content:url(../images/logo.svg);
}
And for the favicon I used the promise solution. Please notice in the fiori launchpad each time that you switch between the applications fiori will reset the favicon, so I used the JavaScript promise to set it.
// Set the favicon dynamically to get read of blinking
function waitForElementAppear(selector) {
return new Promise(function(resolve, reject) {
var element = document.querySelector(selector);
if(element) {
resolve(element);
return;
}
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
var nodes = Array.from(mutation.addedNodes);
for(var node of nodes) {
if(node.matches && node.matches(selector)) {
observer.disconnect();
resolve(node);
return;
}
};
});
});
observer.observe(document.documentElement, { childList: true, subtree: true });
});
}
//
function waitForElementRemoved(selector) {
return new Promise((resolve) => {
var element = document.querySelector(selector);
if(!element) {
resolve();
return;
}
var observer = new MutationObserver((mutations, observer) => {
for (const mutation of mutations) {
for (const removedNode of mutation.removedNodes) {
if (removedNode === element) {
observer.disconnect();
resolve();
}
}
}
});
observer.observe(element.parentElement, { childList: true });
});
}
//
function changeFavIcon(selector) {
waitForElementAppear(selector).then(function(element) {
element.setAttribute("href", "icon/favicon.ico");
waitForElementRemoved(selector).then(function() {
changeFavIcon(selector);
});
});
};
changeFavIcon("link[rel='shortcut icon']");
It recursively checks when the favicon is injected then it will set its href and as soon as it is removed, this function will observe the next injection!
As I know somebody may says why not used sapui5 its original solution for setting the favicon, like this:
jQuery.sap.setIcons({
'phone': '/images/cimt-logo.png',
'phone#2': '/images/cimt-logo.png',
'tablet': '/images/cimt-logo.png',
'tablet#2': '/images/cimt-logo.png',
'favicon': '/icon/favicon.ico',
'precomposed': true
});
I must say it was not working in my case!

Display values outside of pie chart in chartjs

When I hover on pie chart, the values are displayed in tooltip. However, I want to display values outside of pie chart. I want to make chart like this image:
How to do this?
I was able to get something similar working using chart.js v2.3.0 using both the plugin API and extending chart types API. You should be able to take this as a starting point and tweak it to your needs.
Here is how it looks after being rendered.
Note, this requires digging deep into chart.js internals and could break if they change the way tooltips are positioned or rendered in the future. I also added a new configuration option called showAllTooltips to enable selectively using the plugin on certain charts. This should work for all chart types, but I am currently only using it for pie, doughnut, bar, and line charts so far.
With that said, here is a working solution for the image above.
Chart.plugins.register({
beforeRender: function (chart) {
if (chart.config.options.showAllTooltips) {
// create a namespace to persist plugin state (which unfortunately we have to do)
if (!chart.showAllTooltipsPlugin) {
chart.showAllTooltipsPlugin = {};
}
// turn off normal tooltips in case it was also enabled (which is the global default)
chart.options.tooltips.enabled = false;
// we can't use the chart tooltip because there is only one tooltip per chart which gets
// re-positioned via animation steps.....so let's create a place to hold our tooltips
chart.showAllTooltipsPlugin.tooltipsCollection = [];
// create a tooltip for each plot on the chart
chart.config.data.datasets.forEach(function (dataset, i) {
chart.getDatasetMeta(i).data.forEach(function (sector, j) {
// but only create one for pie and doughnut charts if the plot is large enough to even see
if (!_.contains(['doughnut', 'pie'], sector._chart.config.type) || sector._model.circumference > 0.1) {
var tooltip;
// create a new tooltip based upon configuration
if (chart.config.options.showAllTooltips.extendOut) {
// this tooltip reverses the location of the carets from the default
tooltip = new Chart.TooltipReversed({
_chart: chart.chart,
_chartInstance: chart,
_data: chart.data,
_options: chart.options.tooltips,
_active: [sector]
}, chart);
} else {
tooltip = new Chart.Tooltip({
_chart: chart.chart,
_chartInstance: chart,
_data: chart.data,
_options: chart.options.tooltips,
_active: [sector]
}, chart);
}
// might as well initialize this now...it would be a waste to do it once we are looping over our tooltips
tooltip.initialize();
// save the tooltips so they can be rendered later
chart.showAllTooltipsPlugin.tooltipsCollection.push(tooltip);
}
});
});
}
},
afterDraw: function (chart, easing) {
if (chart.config.options.showAllTooltips) {
// we want to wait until everything on the chart has been rendered before showing the
// tooltips for the first time...otherwise it looks weird
if (!chart.showAllTooltipsPlugin.initialRenderComplete) {
// still animating until easing === 1
if (easing !== 1) {
return;
}
// animation is complete, let's remember that fact
chart.showAllTooltipsPlugin.initialRenderComplete = true;
}
// at this point the chart has been fully rendered for the first time so start rendering tooltips
Chart.helpers.each(chart.showAllTooltipsPlugin.tooltipsCollection, function (tooltip) {
// create a namespace to persist plugin state within this tooltip (which unfortunately we have to do)
if (!tooltip.showAllTooltipsPlugin) {
tooltip.showAllTooltipsPlugin = {};
}
// re-enable this tooltip otherise it won't be drawn (remember we disabled all tooltips in beforeRender)
tooltip._options.enabled = true;
// perform standard tooltip setup (which determines it's alignment and x, y coordinates)
tooltip.update(); // determines alignment/position and stores in _view
tooltip.pivot(); // we don't actually need this since we are not animating tooltips, but let's be consistent
tooltip.transition(easing).draw(); // render and animate the tooltip
// disable this tooltip in case something else tries to do something with it later
tooltip._options.enabled = false;
});
}
},
});
// A 'reversed' tooltip places the caret on the opposite side from the current default.
// In order to do this we just need to change the 'alignment' logic
Chart.TooltipReversed = Chart.Tooltip.extend({
// Note: tooltipSize is the size of the box (not including the caret)
determineAlignment: function(tooltipSize) {
var me = this;
var model = me._model;
var chart = me._chart;
var chartArea = me._chartInstance.chartArea;
// set caret position to top or bottom if tooltip y position will extend outsite the chart top/bottom
if (model.y < tooltipSize.height) {
model.yAlign = 'top';
} else if (model.y > (chart.height - tooltipSize.height)) {
model.yAlign = 'bottom';
}
var leftAlign, rightAlign; // functions to determine left, right alignment
var overflowLeft, overflowRight; // functions to determine if left/right alignment causes tooltip to go outside chart
var yAlign; // function to get the y alignment if the tooltip goes outside of the left or right edges
var midX = (chartArea.left + chartArea.right) / 2;
var midY = (chartArea.top + chartArea.bottom) / 2;
if (model.yAlign === 'center') {
leftAlign = function(x) {
return x >= midX;
};
rightAlign = function(x) {
return x < midX;
};
} else {
leftAlign = function(x) {
return x <= (tooltipSize.width / 2);
};
rightAlign = function(x) {
return x >= (chart.width - (tooltipSize.width / 2));
};
}
overflowLeft = function(x) {
return x - tooltipSize.width < 0;
};
overflowRight = function(x) {
return x + tooltipSize.width > chart.width;
};
yAlign = function(y) {
return y <= midY ? 'bottom' : 'top';
};
if (leftAlign(model.x)) {
model.xAlign = 'left';
// Is tooltip too wide and goes over the right side of the chart.?
if (overflowLeft(model.x)) {
model.xAlign = 'center';
model.yAlign = yAlign(model.y);
}
} else if (rightAlign(model.x)) {
model.xAlign = 'right';
// Is tooltip too wide and goes outside left edge of canvas?
if (overflowRight(model.x)) {
model.xAlign = 'center';
model.yAlign = yAlign(model.y);
}
}
}
});

How to disable animation in sap.m.ProgressIndicator on "percentValue" change?

As the title says, how to make the sap.m.ProgressIndicator not animated when changing the percent value of it?
I cannot find a method for it, and extending would probably be the way to go, but maybe somebody has already figured it out and done it?
My Google search was not successful though.
interesting question, below is the sap.m.ProgressIndication.prototype.setPercentValue function, you can see when the percent value changes the bars values is changed via an linear animation
My suggestion, the easiest way to change this behavior is to extend the control to your own control and to redefine the setPercentValue, either remove the animate function on the bar or set time to null so there is no animation
sap.m.ProgressIndicator.prototype.setPercentValue = function(fPercentValue) {
var that = this;
...
if (that.getPercentValue() != fPercentValue) {
// animation without rerendering
this.$().addClass("sapMPIAnimate");
var time = Math.abs(that.getPercentValue() - fPercentValue) * 20;
this.setProperty("percentValue", fPercentValue, true);
var $Bar = this.$("bar");
$Bar.animate({
width : fPercentValue + "%"
}, time, "linear", function() {
that._setText.apply(that);
that.$().removeClass("sapMPIAnimate");
});
}
something like
jQuery.sap.declare("my.ProgressIndicator");
jQuery.sap.require("sap.m.ProgressIndicator");
sap.m.ProgressIndicator.extend("my.ProgressIndicator", {
renderer: {}
});
my.ProgressIndicator.prototype.setPercentValue = function(fPercentValue) {
var that = this;
// validation of fPercentValue
if (typeof (fPercentValue) == "number") {
if (that.getPercentValue() != fPercentValue) {
// animation without rerendering
this.$().addClass("sapMPIAnimate");
//var time = Math.abs(that.getPercentValue() - fPercentValue) * 20;
var time = 0;
this.setProperty("percentValue", fPercentValue, true);
var $Bar = this.$("bar");
$Bar.animate({
width : fPercentValue + "%"
}, time, "linear", function() {
that._setText.apply(that);
that.$().removeClass("sapMPIAnimate");
});
}
return this;
};
There is no convenient method to suppress this behavior.
You can only extend the control and overwrite the method setPercentValue to you desired behavior.
As of UI5 1.73, the animation on percantageValue-change can be turned off by setting the property displayAnimation to false.
Determines whether a percentage change is displayed with animation.
Since: 1.73.
<ProgressIndicator displayAnimation="false" />

Fancybox Positioning Inside Facebook Canvas iFrame

OK so I have a iframe canvas app with its height set to "Settable" with the facebook javascrip sdk calls to FB.Canvas.setSize(); and FB.Canvas.setAutoGrow();. These are working perfectly, as the iframe gets set to a certain pixel height based on its content.
The problem is that when I make a call to Fancybox, it positions itself based on this height. I know that's exactly what its supposed to do as the fancybox jQuery returns the viewport by:
(line 673 of latest version of jquery.fancybox-1.3.4.js):
_get_viewport = function() {
return [
$(window).width() - (currentOpts.margin * 2),
$(window).height() - (currentOpts.margin * 2),
$(document).scrollTop() + currentOpts.margin
];
},
But the problem is the iframe will, for a lot of viewers, be longer than their browser window. So the Fancybox centers itself in the iframe and ends up only partly visible to the majority of viewers. (i.e. iframe height is 1058px and users browser is say only 650px).
Is there a way to have fancybox just calculate the physical browser height? Or do I need to change some settings in my Facebook canvas app to make it work?
I like how the only scrollbar is the one on Facebook (the parent, if you will).
All suggestions GREATLY appreciated!
For fancybox 2 try:
find:
_start: function(index) {
and replace with:
_start: function(index) {
if ((window.parent != window) && FB && FB.Canvas) {
FB.Canvas.getPageInfo(
function(info) {
window.canvasInfo = info;
F._start_orig(index);
}
);
} else {
F._start_orig(index);
}
},
_start_orig: function (index) {
Then in function getViewport replace return rez; with:
if (window.canvasInfo) {
rez.h = window.canvasInfo.clientHeight;
rez.x = window.canvasInfo.scrollLeft;
rez.y = window.canvasInfo.scrollTop - window.canvasInfo.offsetTop;
}
return rez;
and finally in _getPosition function replace line:
} else if (!current.locked) {
with:
} else if (!current.locked || window.canvasInfo) {
As facebook js api provides page info, then we could use it, so
find
_start = function() {
replace with
_start = function() {
if ((window.parent != window) && FB && FB.Canvas) {
FB.Canvas.getPageInfo(
function(info) {
window.canvasInfo = info;
_start_orig();
}
);
} else {
_start_orig();
}
},
_start_orig = function() {
and also modify _get_viewport function
_get_viewport = function() {
if (window.canvasInfo) {
console.log(window.canvasInfo);
return [
$(window).width() - (currentOpts.margin * 2),
window.canvasInfo.clientHeight - (currentOpts.margin * 2),
$(document).scrollLeft() + currentOpts.margin,
window.canvasInfo.scrollTop - window.canvasInfo.offsetTop + currentOpts.margin
];
} else {
return [
$(window).width() - (currentOpts.margin * 2),
$(window).height() - (currentOpts.margin * 2),
$(document).scrollLeft() + currentOpts.margin,
$(document).scrollTop() + currentOpts.margin
];
}
},
I had the same problem, i used 'centerOnScroll' :true, and now it works fine...
Had the same problem. Thankfully fancybox is accessable through CSS. My solution was to overwrite fancybox's positioning in my CSS file:
#fancybox-wrap {
top: 20px !important;
}
This code places the fancybox always 20px from top of the iframe. Use a different size if you like. The !important sets this positioning even though fancybox sets the position dynamically at runtime.
Here's one way to do it by positioning the Fancybox relative to the position of another element, in my case an Uploadify queue complete div that displays a view link after the user uploads an image.
Have a style block with a set ID like so:
<style id="style-block">
body { background-color: #e7ebf2; overflow: hidden; }
</style>
Then the link to open the Fancybox calls a function with the image name, width, and height to set the content and sizes. The important part is the positioning. By getting the position of the queue complete div, generating a new class declaration (fancy-position), appending it to the style block BEFORE the fancybox loads (!important in class will override positioning from fancybox), then adding the new class using the wrapCSS parameter in the fancybox options, it positions the fancybox exactly where I want it.
function viewImage(image, width, height) {
var complete_pos = $('#image_queue_complete').position();
var css_code = '.fancy-position { top: ' + complete_pos.top.toString() + 'px !important; }';
$('#style-block').append(css_code);
var img_src = '<img src="images/' + image + '" width="' + width.toString() + '" height="' + height.toString() + '" />';
$.fancybox({
content: img_src,
type: 'inline',
autoCenter: false,
wrapCSS: 'fancy-position'
});
}