Positioning based on size of rendered component - vue.js

I have a Vue component which generates a part of an SVG.
Since text handling in SVG is a pain, I decided to use a foreignObject tag inside it in order to let the HTML and CSS inside it handle the layout.
The template part basically looks like this:
<template>
<g class="port">
<foreignObject ref="html">
<div class="port__wrapper" ref="wrapper">
<div class="port__label">{{ label }}</div>
<div class="port__content">{{ content }}</div>
</div>
</foreignObject>
</g>
</template>
Everything works fine until I try to position the part, based on its computed size. The size of my foreignObject has to be set manually. In order to do that, I need the computed size of my port__wrapper element.
Here is my current approach:
export default {
methods: {
calculateAndSetSize() {
const frame = this.$refs.html;
const wrapper = this.$refs.wrapper;
frame.setAttribute('width', `${wrapper.clientWidth}px`);
frame.setAttribute('height', `${wrapper.clientHeight}px`);
}
},
created() {
window.setTimeout(() => {
this.calculateAndSetSize();
// also some positioning stuff
}, 0);
}
}
I use the created lifecycle hook to ensure the component is created and use the setTimeout to move the calculation to the end of the render queue.
This works fine when I just move around inside my app, but if I refresh the page it only works 5/6 of the time. The calculation seems to be done just before the final render.
An example from Safari (even though it happens in all browsers):
I captured this, while my system was super slow. This usually happens in a fraction of a second
Before the component is fully rendered, this part is visible:
The component takes up a little bit less than double the size of the end-result. The calculation and positioning have happened and everything is aligned as expected (right side and vertically centered to the cross in the middle).
Almost correct, but then there is the final paint, which looks like this:
As you can see the positioning is totally off, since the element is way smaller and the calculations already happened.
The foreignObject sill has the wrong size:
(the last screenshot is from another build and looks a little different, but the problem stays the same)
I figured the problem might be the font loading and tried the execute the calculations after the document.fonts.ready promise, but that is even less consistent than the setTimeout approach.
Any hint what could cause this and how to work around it would be greatly appreciated.

Related

vuejs: mounted issues with coordinates of elements and svg

I have three columns, two with divs and the central one with an svg. I made a method that calculate the top() of each paragraph inside the divs to get the position and then draw an arrow in the svg. The problem is that when I use that method the first time I open my component, I get all zeroes, probably because the paragraph aren't really drawn (they have no coordinates) yet. I tried in mounted(), which should be the right place to do that. I use it also in updated() in case I reload my json with new data.
Am I missing something trivial?
The code I use to get the coordinates is like this:
drawLine(index1, index2) {
//var plist1 = this.$refs['p_list1'];
//var plist2 = this.$refs['p_list2'];
var plist1 = document.getElementsByClassName('p_list1');
var plist2 = document.getElementsByClassName('p_list2');
if (plist1.length == 0 && plist2.length == 0) return;
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'line');
//...
//const start = this.$refs['startpos'].getBoundingClientRect().top;
const start = document.getElementById('startpos').getBoundingClientRect().top;
const r1 = plist1[index1].getBoundingClientRect();
const r2 = plist2[index2].getBoundingClientRect();
index1 and index2 comes from a loop where I get which paragraph I have to connect with an arrow (also where the nextThick is)
Here is a simple example of the issue:
https://codesandbox.io/s/bootstrap-vue-test-bcozc
Note: it's badly shown, but if you press "DO" and then switch tab, you'll see that the arrow aren't correct. If you switch tab and then press DO, it will work.
Put your calculation methods in a $nextTick function to allow parents and children to fully render.
If that does not work, as a debug step, try using a setTimeout method to delay the calculation.
After understanding that the problem was linked to how tabs are built, I tried making that specific tab lazy and it worked.
<b-tab lazy ...
I don't know how tabs are normally built, but I suppose the dom is put together without having a real coordinate system and when it's made visible, I don't have any event to read to update the svg.

Vue lazyloading gets triggered when adding dynamic class to <main>

I am adding a 'sticky-header' class to my dynamically, but when its sticky i obviously also need to add padding to my so the content doesnt go behind the header.
The problem is that when adding the padding via class-binding(v-bind:class="{'fixed-header': stickyHeader}")on scroll, this reloads all the lazy-loaded images (that are lazy loaded using vue lazy)
This makes all images go from loading to loaded in a split second, but its very noticable.
stickyHeaderis a boolean that gets recalculated on scroll, if the window is scrolled past a certain element (with a eventlistener on scroll: checkHeader())
checkHeader() {
var elementTarget = document.getElementById("notice");
if(window.scrollY > (elementTarget.offsetTop + elementTarget.offsetHeight)){
this.stickyHeader = true;
}
else{
this.stickyHeader = false;
}
}
Anyone know what exactly triggers the images to go back to 'loading' for a split second?
To solve this instead of dynamically binding the class with v-bind i simply add it in the scroll listener method with basic javascript, now it doesnt rerender :) !

Vuetify breakpoints not working in Nuxt.js

I'm working on a project using Nuxt.js as SSR engine and Vuetify as styling framework. In one of my templates I have such code:
<v-layout row wrap align-center
:class="{ 'mb-4': $vuetify.breakpoint.smAndDown }">...</v-layout>
As you can see, I want to apply mb-4 class only if I am on small screens and smaller ones. But when I load this on desktop large screen and inspect element, this class is attached even though screen resolution does not match logic for applying this class. However, styling is back to expected when I resize browser window.
I've tried to manually dispatch 'resize' event in lifecycle hook:
mounted() {
window.dispatchEvent(new Event('resize'));
}
But it didn't help. Even if I wrap it in setTimeout, still no luck.
UPD: found a workaround, but still think it is not the best solution:
changed
:class="{ 'mb-4': $vuetify.breakpoint.smAndDown }"
to
:class="{ 'mb-4': isMounted && $vuetify.breakpoint.smAndDown }"
and in mounted lifecycle hook added: this.isMounted = true
UPDATE: while digging in Vuetify source code found out, that it checks window width with 200ms delay as window width check is costly operation. That is why we have delay.
you can give specific breakpoint for margin and padding attributes.
<v-layout row wrap align-center class="mb-sm-4">...</v-layout>
https://vuetifyjs.com/en/styles/spacing/#breakpoints

Is it possible to dynamically add a slide to an existing Materialize Carousel?

I am able to create a carousel as described here. The carousel appears and works properly.
Later on I would like to add an additional slide:
$(".carousel").append('<a class="carousel-item active" href="#six!"><img src="http://lorempixel.com/250/250/nature/6"></a>');
However, this does not appear to work as the slide does not get added to the carousel.
Is adding slides to an initialized carousel not supported or is this an issue on my end?
Sure it is. I haven't seen any solution in the internet. I believe that there might be a better and smoother solution but this one is mine.
All you need to do is after adding your new items remove the initialized class from already initialized main carousel div and reinitialize it.
//init carousel
var slider = $('.carousel');
slider.carousel();
//add a new item
slider.append('<a class="carousel-item active" href="#three!"><img src="http://lorempixel.com/250/250/nature/3"></a>');
//remove the 'initialized' class which prevents slider from initializing itself again when it's not needed
if (slider.hasClass('initialized')){
slider.removeClass('initialized')
}
//just reinit the carousel
slider.carousel();
full working fiddle: https://jsfiddle.net/8tq4ycm3/
I had the same problem, but I'm using the slider. (http://materializecss.com/media.html#slider)
To resolve this, it was necessary to clear all items, re-add items with the new one, and then re-bind the slider.
Follow example: https://codepen.io/troits/pen/QvdZov
let itens = [];
function addItem(){
$('.slides').empty();
$('.slider > .indicators').detach();
itens.push(createItem());
for(i in itens){
$('.slides').append(itens[i]);
}
$('.slider').slider();
showItem(i);
}
function showItem(i){
$('.slider > .indicators > .indicator-item')[i].click();
}

Polymer 1.0 Data binding when object changes

I'm having trouble understanding how data-binding works now.
On my index page I've got an object (obtained as JSON from a RESTful service), which works just fine when applied to a custom element like:
<main-menu class="tiles-container ofvertical flex layout horizontal start"
menuitems="{{menuitems}}">
</main-menu>
var maintemplate = document.querySelector('#fulltemplate');
maintemplate.menuitems = JSON.parse(data.GetDesktopResult);
This works as expected, and when I load my page with different users, main-menu changes as it should to show each user's desktop configuration. (This menuitems object reflects position and size of each desktop module for each user).
Now, users used to be able to change their configuration on the go, and on Polymer 0.5 I had no problem with that, just changed my maintemplate.menuitems object and that was that, it was reflected on the template instantly.
As I migrated to Polymer 1.0, I realized changes on an object wouldn't change anything visible, it's much more complicated than this, but just doing this doesn't work:
<paper-icon-button id="iconback" icon="favorite" onClick="testing()"></paper-icon-button>
function testing(){
debugger;
maintemplate = document.querySelector('#fulltemplate');
maintemplate.menuitems[0][0].ModuleSize = 'smamodule';
}
The object changes but nothing happens on the screen until I save it to DB and reload the page.
Am I missing something /Do I need to do something else on Polymer 1.0 to have elements update when I change an object passed as a property?
Before you ask, I've got those properties setted as notify: true, it was the inly thing I found different, but still doesn't work
Thanks for reading!
EDIT:
this is the code menuitems is used in:
<template is="dom-repeat" items="{{menuitems}}" as="poscol">
<div class="positioncolum horizontal layout wrap flex">
<template is="dom-repeat" items="{{poscol}}" as="mitem" index-as="j">
<main-menu-item class$="{{setitemclass(mitem)}}"
mitem="{{mitem}}"
index="{{mitem.TotalOrder}}"
on-click="itemclick"
id$="{{setitemid(index, j)}}">
</main-menu-item>
</template>
</div>
</template>
main-menu-item is just set of divs which changes size and color based on this object properties
You need to use the mutation helper functions if you want to modify elements inside an object or array otherwise dom-repeat won't be notified about the changes (check the docs):
function testing(){
debugger;
maintemplate = document.querySelector('#fulltemplate');
this.set('maintemplate.0.0.ModuleSize', 'smamodule');
}