How to create an auto positionable preview tooltip? Angular - html

I am creating an event calendar, I have created the event generation logic on a grid layout template and now I am working on an event preview, when I preview an event it should show me a tooltip with details of the event.
The problem is that the tooltip must be in different positions according to the space that has the event and the parent grid.
How can I place this tooltip according to the example image
Calendar image example cases
I did a lot of calculations and I still can't find how to do it since there are too many cases.
This is one of my last attempts. I share part of the code
Typescript:
confirmSelection() {
this.previewActive = !this.previewActive;
this.showEventTooltip = !this.showEventTooltip;
this.preview.positionTooltip = this.setTooltipPosition();
}
setTooltipPosition() {
// get size of subgrid-main-ui
const subgridMainUi = document.getElementsByClassName('subgrid-main-ui')[0];
const subgridMainUiRect = subgridMainUi.getBoundingClientRect();
// get size of event preview
const eventPreview = document.getElementsByClassName('event-preview')[0];
const eventPreviewRect = eventPreview.getBoundingClientRect();
// tooltip sizes
const widthTooltip = 289;
const heightTooltip = 473;
// calculate position the tooltip
let positionTooltip: any = {};
// calculate top position
let top = eventPreviewRect.top - subgridMainUiRect.top;
if (top + heightTooltip > subgridMainUiRect.height) {
top = subgridMainUiRect.height - heightTooltip;
}
positionTooltip.top = top + 'px';
// calculate left position
let left = eventPreviewRect.left - subgridMainUiRect.left;
if (left + widthTooltip > subgridMainUiRect.width) {
left = subgridMainUiRect.width - widthTooltip;
}
positionTooltip.left = left + 'px';
return positionTooltip;
}
Html:
<div class="subgrid-main-ui">
<!-- SLOTS -->
<ng-container *ngFor="let hour of hours">
<ng-container *ngFor="let place of places">
...
</ng-container>
</ng-container>
<ng-container *ngFor="let event of events">
...
</ng-container>
<ng-container *ngIf="preview.position">
<div class="event-preview" (click)="confirmSelection()" [ngStyle]="preview.position">
<p>{{ preview.location }}</p>
</div>
</ng-container>
<ng-container *ngIf="preview.positionTooltip">
<div class="tooltip-detail" [ngStyle]="preview.positionTooltip">
<p>{{ preview.location }}</p>
<p>Lorem...</p>
<button (click)="addEvent()">Ok</button>
</div>
</ng-container>
</div>
Css:
.event-preview {
box-sizing: border-box;
display: flex;
position: absolute;
border: 1px solid #dadce0;
height: 100%;
width: 100%;
z-index: 2;
background: rgba(54, 72, 85, 0.3);
}
.tooltip-detail {
position: absolute;
width: 289px;
height: 473px;
background-color: white;
border: 1px solid #dadce0;
z-index: 3;
padding: 10px;
}
I really don't know if it's a good idea to use the getBoundingClientRect(), since it's enough to reset the screen zoom to lose the calculations and stop making sense.
Also to position my events in the sub grid I use as position
{
'grid-row',
'grid-column',
'grid-row-end'
}
With this I get rid of responsive, but I don't know how to apply something similar to the tooltip since the size has to be fixed...

Related

On click, animate the div element to the top and make it sticky

I have a parent div, that contains multiple child divs inside it, On clicking any child element,I am trying to animate that child element from its current position to the top position according to its current parent, then after moving to the top with proper animation, I want that child element to be sticky, so that other child elements can be easily scrolled underneath it and then, if any other child element is clicked then it will animate to top and the recent child element will move back to its old position.
Any help using angular and/or html and css will be really appreciative.
I am also attaching stackblitz angular project link for my initial code.
https://stackblitz.com/edit/angular-ivy-abbnjo
Thanks
I find this question interesting, so I did a quick sample of how you can do this via jQuery. There are certainly libraries there that probably does this already, but with the interest of sharing the logic behind it, here's a quick demo in JSFiddle. It may need some more love though.
I hope this helps!
JSFiddle Link: https://jsfiddle.net/qo6x42za/1/
HTML
<div>
<div class="sticky"></div>
<div class="container">
<div class="box" data-order="1">Box1</div>
<div class="box" data-order="2">Box2</div>
<div class="box" data-order="3">Box3</div>
<div class="box" data-order="4">Box4</div>
<div class="box" data-order="5">Box5</div>
</div>
</div>
CSS
.container {
position: relative;
height: 250px;
overflow-y: scroll;
border: 1px solid #000;
}
.box {
width: 100px;
height: 50px;
padding: 20px;
background-color: #595959;
color: #fff;
border: 1px solid #c90000;
}
Javascript
$('.box').each(function(index) {
$(this).on('click', function() {
const target = $(this);
const sticky = $('.sticky');
const container = $('.container');
const position = $(sticky).position();
// after animation completes
const options = {
complete: () => {
// detach previous item from sticky container and place back to original position
if ($(sticky).children().length > 0) {
const firstChild = $(sticky).children().first();
const order = $(firstChild).data('order');
const previousChild = order - 1;
if (order > 1) {
$(firstChild).detach().insertAfter($(`[data-order=${previousChild}]`));
} else {
$(firstChild).detach();
$(container).prepend($(firstChild));
}
}
// attach item to sticky container
$(sticky).append($(target));
// remove the style attribute as we no longer need it
$(target).removeAttr('style');
}
};
// animate to position
$(target).css({ position: 'absolute'});
$(target).animate({
top: position.top
}, options);
});
})

Angular mat-chip-list size is incorrect, chips are sitting in input area

Apologies for the bad title, I have a myriad of issues that need work.
I am not a web dev, this is the first time working with Angular, and Typescript, so I am not sure even how to articulate the problems correctly, or even if I am giving you the right information.
So, for starters, my input area should be 15 px by 150px, but it is showing as 108px by 150px.
Here is my css.
.manual-work-entry {
margin-top: 1em;
.entry-row {
height: 40px;
}
.input-small {
width: 100px;
margin: 0;
padding: 1px;
mat-chip {
height: 22px;
font-size: 11px;
width: 85px;
div {
width: 100%;
}
}
}
::ng-deep .lower .mat-form-field-underline {
bottom: 0 !important;
}
::ng-deep .mat-chip-input {
.mat-form-field {
.mat-input-infix,
.mat-form-field-infix,
.mat-chip-input {
width: auto;
height: 15px !important;
}
.mat-chip-input {
flex: 1 1 auto;
}
}
}
}
Second, when I input a value for a chip, it shows above the input box, pushing it down. I want them to show below the box.
Third, when I press enter, I want the value I just typed in to vanish from the input box. I ho idea how to do that.
Fourth, when I hit backspace, I can keep going past the input values, and delete chips. Is this supposed to happen? I don't think it is, but I am not proficient enough in Angular to know.
Here is a chunk of the afflicted HTML code.
<mat-form-field class="input-small">
<mat-chip-list #additionalPartNumberCL
class="mat-chip-list-stacked">
<mat-chip *ngFor="let number of additionalPartNumbers"
[selectable]="true"
[removable]="true"
(removed)="removeMatChip($event, number, 'additionalPartNumbers')">
<div>
{{number}}
</div>
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
<input placeholder="Additional Part Numbers (optional)"
[matChipInputFor]="additionalPartNumberCL"
(matChipInputTokenEnd)="addMatChip($event, 'additionalPartNumbers')">
</mat-chip-list>
</mat-form-field>
And the corresponding typescript for addMatChip
public addMatChip(event: any, varName: string): void {
const value = (event.value || "").trim();
// this allows us to input the same value multiple times
switch (varName) {
case "additionalPartNumbers": {
this.additionalPartNumbers.push(value);
break;
}
//removing extra code
}
// reset the input value
if (event.value) {
event.value = "";
}
}
and the removeMatChip code
public removeMatChip(event: any, partNumber: string, varName: string): void {
const value = partNumber;
switch (varName) {
case "additionalPartNumbers": {
const index = this.additionalPartNumbers.indexOf(value);
if (index >= 0) {
this.additionalPartNumbers.splice(index, 1);
}
break;
//remove useless code
}
}
}
Any suggestions for improving my code?
I fixed issue 1 and 2 by moving the input to above the mat-chip-list.
Issue 3 was solved by adding an event.input.value = ""; to the if(event.value) in the add function.
I am still working on issue 4.
You can add custom css in the file style.css. first you should try to find out the class from dev tools then you can apply the css to that list.

How to keep a flex container all in one page without scroll

I am creating a web page which needs has to display some movie covers all in a single page, without scrolling it, becouse it will be on display. the problem is that i want to get the content to resize instead of making the web page scrollable. I also need to support n movies (they are dependent). I've tried using flexbox twice, but it doesnt work. Also, I am using tailwindcss framework, but i don't think that's a problem as it is just css in form of classes...
<html class='h-full m-0 p-0'>
<head>
<link href="https://unpkg.com/tailwindcss#next/dist/tailwind.min.css" rel="stylesheet">
</head>
<body class='h-full m-0 p-0'>
<div class='mx-10 mt-10 flex content-center items-center'>
<div class='flex flex-wrap'>
<!-- iterate over every movie -->
<div class='m-2 relative flex-grow h-full' style='flex-basis: 20%'>
<span class='px-2 py-1 rounded-full bg-blue-500 text-white absolute z-0' style='top: -0.5rem; right: -0.5rem'>0</span>
<img src='https://images.unsplash.com/photo-1525604803468-3064e402d70c' class: 'w-full' />
<span class='w-full opacity-75 bg-black text-white py-1 absolute z-0 inset-x-0 bottom-0 text-center px-2'>title</span>
</div>
<!-- end -->
</div>
</div>
</body>
</html>
EDIT: I added a the full example (with an example image take from unsplash) of what i want it to look like.
I don't think there is a way to do what you are asking without JavaScript. In the following example, I used CSS to maintain the ratio of the covers, and used JavaScript to calculate the maximum width that would keep the content from scrolling vertically.
Here is a fiddle.
function getRandomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
let numCovers = 10;
let coverList = document.querySelector(".cover-list");
// this function determines what percentage of the cover-list's width
// each cover should take up. It will maximize how large they can be
// without making the total grid size larger than cover-list.
// This should prevent scrolling.
function getBasis() {
let ratio = 1.35; // height/width
let width = coverList.clientWidth;
let height = coverList.clientHeight;
// this loop is really slow, you may want to find a faster way
let col = 0;
let accWidth, accHeight, numRows;
do {
col++;
if (col > numCovers) {
// maximize height
return (height / ratio) + "px";
}
accWidth = width / col;
accHeight = accWidth * ratio;
numRows = Math.ceil(numCovers / col);
} while (accHeight * numRows > height);
return (100 / col) + "%";
}
function generateCovers() {
// clear existing covers
coverList.innerHTML = "";
let basis = getBasis();
for (let i = 0; i < numCovers; i++) {
let cover = document.createElement("div");
cover.classList.add("cover");
cover.style.flexBasis = basis;
let inner = document.createElement("div");
inner.classList.add("inner");
inner.style.backgroundColor = getRandomColor();
cover.append(inner);
coverList.append(cover);
}
}
let numCoversInput = document.querySelector("#num-covers");
numCoversInput.addEventListener("change", function() {
numCovers = Math.min(Math.max(this.value, 1), 500);
this.value = numCovers;
generateCovers();
});
generateCovers();
window.addEventListener("resize", function() {
let basis = getBasis();
coverList.querySelectorAll(".cover").forEach(function(el) {
el.style.flexBasis = basis;
});
});
body {
/* set margin to computable value for cover-list calcs */
margin: 5px;
}
#controls {
height: 25px;
}
.cover-list {
/* account for margin and controls with calc */
width: calc(100vw - 10px);
height: calc(100vh - 35px);
/* use flex so the content will wrap as desired */
display: flex;
flex-wrap: wrap;
align-content: start;
}
.inner {
/* padding percentages are based on width, so setting
the height to 0 and padding-bottom to a percentage
allows us to maintain a ratio */
width: 100%;
height: 0;
padding-bottom: 135%;
}
<div id="controls">
<label>
Number of covers: <input id="num-covers" type="number" value="10" min="1" max="500"/>
</label>
</div>
<div class="cover-list">
</div>

How to make a fixed floating button to stop at footer in angularjs

I would like to create a button using that floats until footer and then stops
1) Button should be poisition: fixed; bottom: 0px when footer is not visible
2) When footer becomes visible, button should just sit on top of footer
The button should handle following cases.
when states change in angular, when we get data from server the footer is visible for a moment and then the page expands, what will happen then?
when the page has less content and footer is visible, button should sit on top of footer.
How can i do this?
Here is the plunker i started to play around with
http://plnkr.co/edit/SoCBjkUjFICiuTeTPxDB?p=preview
I came across this post when searching for a similar solution. Without a ready answer, this is what I ended up doing, based on this post https://ngmilk.rocks/2015/04/09/angularjs-sticky-navigation-directive/ .
Basicly you need a $scope.$watch to watch for scope change, and an event handler attached to the onscroll event.
angular.module('myApp')
.directive('stickyBottom', function($window) {
return {
restrict: 'A',
scope: {},
link: function (scope, elem, attrs) {
// the element box saved for later reference
var elemRect;
// element height
var height = elem[0].clientHeight;
// element top, will be changed as scope is updated
var top = 0;
// updates element's original position
scope.$watch(function(){
elemRect = elem[0].getBoundingClientRect();
return elemRect.top + $window.pageYOffset;
}, function(newVal, oldVal){
// this is the original element position, save it
if(!elem.hasClass('fixed-bottom')){
top = newVal;
}
// properly position the element even in `fixed` display
elem.css('width', elemRect.width);
elem.css('left', elemRect.left);
// check position
toggleClass();
});
// toggle `fixed-bottom` class based on element's position
var toggleClass = function() {
// the element is hidden
if (elem[0].getBoundingClientRect().top + height > $window.innerHeight) {
elem.addClass('fixed-bottom');
}
// the element is visible
else {
// the element is visible in its original position
if (top - $window.pageYOffset + height < $window.innerHeight && elem.hasClass('fixed-bottom')) {
elem.removeClass('fixed-bottom');
}
}
}
// bind to `onscroll` event
$window.onscroll = function() {
toggleClass();
};
}
};
})
;
And here's some css:
.fixed-bottom {
position: fixed;
top: auto;
bottom: 0;
}
You can accomplish this affect without using angular at all by modifying your style.css. The simplest solution in this case is just to set the bottom parameter of the #to-top element to be at minimum higher than the footer, for example:
#to-top {
position:fixed;
bottom: 60px;
right: 10px;
width: 100px;
padding: 5px;
border: 1px solid #ccc;
background: red;
color: white;
font-weight: bold;
text-align: center;
cursor: pointer;
}

ng repeat with relative position elements not sized correctly

I am using angularjs and zurb foundation. I have ng-repeat creating a row for each item in items. Each row itself has two rows within. I am using position relative to move the second inner row up behind the first inner row. The first row has z-index 1 to move it on top. The goal is to create a menu that is hidden behind a div initially.
Seems to work except for one flaw. The problem I am having is that it seems that the directive has its height set to the initial height of its content. So the directive is 12em tall while the content is only in the top half.
Forcing the directive to be 6em tall works for the first item but the subsequent ones have the content of the back row all jibbly wibbly! (I believe that is the scientific term)
Any help would be appreciated. Smashing my head against the keyboard is usually my 'go to' in these situations, but it hasn't helped in this case.
Sample
//index.html
<div ng-repeat='item in items'>
<div directive></div>//ends up 12em
</div>
//directive.html
<div class="row front">//6em tall
//content
</div>
<div class="row back">//moved 6em up
//Menu with buttons
</div>
//style.css
front: {
height: 6em;
z-index: 1;
}
back: {
height: 6em;
position: relative;
top: -6em;
}
The problem is that you are trying to position two divs with relative positioning. They don't need to be relative since they are intended to occupy the same space.
Make the parent div use relative positioning:
.parent { position: relative; }
.front { position: absolute; top: 0; height: 6em; }
.back { position: absolute: top:0; height: 6em }
https://docs.angularjs.org/api/ng/directive/ngRepeat
This is how I would do it:
app.directive('row front', function() {
return {
restrict: '6pm',
scope: {
collection: '=',
columnCount: '='
},
transclude: true,
template: '<div><div class="column" ng-repeat="col in cols">' +
'<div ng-repeat="item in col" ng-transclude></div>' +
'</div></div>',
link: function( scope ) {
var partition = function partition( size, items ) {
if ( items.length === 0 ) { return []; }
return ([items.slice( 0, size )]).concat( partition( size, items.slice( size )));
};
var relativenize = function() {
if ( !scope.columnCount ) { return; }
scope.cols = partition( scope.columnCount, scope.collection );
};
scope.$watch('columnCount', columnize );
scope.$watch('collection', columnize );
}
};
});