Ionic 4 Stencil PWA - Manipulating DOM elements - ionic4

I am trying out ionic 4 for the first time and I am trying to change the css style of an element on my page.
eg.
I have a div
<div id="foo"></div>
Now on mouseover I would like to move the div to a different position on my page.
How do I get the element #foo and change the position on mouseover in my component?
please note this is just an example of what I want to do and i have no interest in using CSS for this since it would not working for me.
I have done some reading on ionic 4 and the shadow dom but it still makes no sense to me.

Since you do not want to write any css and want to refer element in your controller, can use viewChild as shown below
In HTML
<div #foo></div>
In Controller
import { Component, ViewChild, ElementRef } from '#angular/core';
.
.
.
#ViewChild('foo') divRef: ElementRef;
constructor() {
}
ngAfterViewInit() {
this.divRef.nativeElement.style.background = "red";
}
Here is running code in stackblitz

Related

Scroll to bottom in Ionic Vue

I develop a chat app in Ionic Vue. To always see the latest messages, I have to scoll to the bottom automatically.
For web I use vue-chat-scroll, but this does not work with Ionic Vue.
I have tried a lot of different tactics but nothing works:
var objDiv = document.getElementById("your_div");
objDiv.scrollTop = objDiv.scrollHeight;
or
this.scrollIntoView(false);
or
var element = document.getElementById("scroll");
element.scrollIntoView();
or
vue-scroll-to with a hidden anchor at the end of the view
I have also tried different methods of listing the chat messages:
a combination of IonGrid with Rows
a normal ul / li list
just with divs
Take a look at "ion-content" component, https://ionicframework.com/docs/api/content
It has many methods and events for scrolling.
You only need to call the method .scrollToBottom() on the <ion-content> element.
You can use the following approach, refering the ion-content with a ref and calling the method scrollToBottom bellow:
<template>
<ion-content ref="content">
</ion-content>
</template>
export default defineComponent({
methods: {
scrollToBottom(){
this.$refs['content'].scrollToBottom()
}
},
});
</script>

Insert scopedSlot into d3 svg using foreignObject

I'm creating a chart component using d3js and Vue (v2). In some parts, I want to support custom user content using scoped slots (in this case, custom tooltips)
<my-chart>
<template slot="tooltip" slot-scope="{ data }">
<span>{{ data }}</span>
</template>
</my-chart>
But I'm struggling to render this using d3js on a vuejs component render function. I'm trying to do something like:
g?.append("foreignObject").html(
this.$scopedSlots["tooltip"]({
event,
}),
);
Obviously, the html method isn't appropriated. I can't find anything like this online. All other examples use the component template to insert the foreignObject and Vue component on the SVG. Nothing using d3js
EDIT
As user I refer to developers. This code is for a lib.
Just in case anyone wants to implement something like this, I manage to resolve my issue.
Background
SVG has a concept of foreignObject which allows me to inject HTML inside an SVG. The next step is, somehow, to render the Vue component to HTML.
I'm using vue2, Vuetify, and d3js v6
Rendering the component
this.$scopedSlots["tooltip"]({
event,
}),
returns a VNode[], so using Vue.extend I create a new component, instantiate it and mount it to a div inside the foreignObject like this:
// Call the scoped slot, which returns a vnode[]
const vnodes = this.$scopedSlots["tooltip"]({
event,
});
const id = "RANDOM_GENERATED_ID";
// Append a foreignObject to an g
const fo = g?.append("foreignObject");
// Append a div to the foreignObject, which will be the vue component mount point
fo?.append("xhtml:div").attr("id", id);
// Create a new component which only render our vnodes inside a div
const component = Vue.extend({
render: (h) => {
return h(
"div",
{staticClass: "foreign-object-container"}
vnodes,
);
},
});
// Create a instance of this new component
const c = new component();
// I'm using vuetify, so I inject it here
c.$vuetify = this.$vuetify;
// Mount the component. This call will render the HTML
c.$mount("#" + id);
// Get the component rect
const bbox = c?.$el.getBoundingClientRect();
// Resize the ForeignObject to match our injected component size
return fo
?.attr("width", bbox?.width ?? 0)
.attr("height", bbox?.height ?? 0);
This successfully renders our component inside an SVG using d3js. At least it appears inside the DOM.
Problems
At this point, I faced 2 new problems.
Invalid component size
After rendering the Vue component inside the foreignObject it reported width equals 0. So, based on this I used the next styles:
.foreign-object-container {
display: inline-flex;
overflow: hidden;
}
And voilá, habemus a visible Vue component.
Scroll, ForeignObject, and the old Webkit Bug
My use case is this: The chart is responsive, so it re-renders after every container resizes (with some debounce), but to prevent deformations I set a minimum width to every element. With some screen sizes, this provokes some overflow, which inserts a scrollbar (browser behavior).
This is exactly what I want. But I'm using some Vuetify components on my scopedSlot which have a position: relative style.
Enters, an old bug on WebKit (Google Chrome, Safari, and Edge Chromium). This is better explained here and here
The solution in my case is simple. As I stated before, my foreignObject was resized to match the rendered component. So, to prevent my components to be wrongly drawn, I change my styles a little bit.
.foreign-object-container {
display: inline-flex;
overflow: hidden;
position: sticky;
position: -webkit-sticky;
}
Now, my teammates can use a generic chart with scroll support and customize some pieces of it using any Vue component (at least for now )

nuxtjs $refs scrollTop not move top

I am using nuxtjs v2.11 ,
I just want to go to testing ref but seems it not move to testing div.
<button #click="goToRefsTesting()">
go to testing dev
</button>
<div
ref="testing"
id="testing"
>
just testing
</div>
in the methods as you expect
methods: {
goToRefsTesting() {
console.log('go to ref pls')
this.$refs.testing.scrollTop = 0
},
i got the console log message, but it still not move to testing div. any thought? is it nuxt issue or what.
Your page needs to have enough content for scrollTop or scrollIntoView to be able to work.
Most likely there's nothing after the div, so the page cannot scroll.
Add some more content and you'll see the content move.
ok, i found out it is not vue/nuxt problem. the container got auto scroll properties. so that is why it is not working.thanks
This worked for me in any component without ref (but it only scrolls to top), smooth scrolling is default behavior
<button #click=scrollToTop()>Jump to top of page
methods: {
scrollToTop() {
window.scrollTo({ top: 0 });
}
}

Lazy loading data from API in Vue.js

I want to lazy load content when the user scrolls down to the bottom of the Bootstrap modal, sort of like an infinite scroll, using Vue.js. I'm fetching all my data from a action method on API call. The data coming from this API is stored in array of objects on mounted and is used in the application.
So far so good. But now I want to implement that in the initial state, only the first 10 items are fetched from the API. When the user scrolls down to the bottom of the page I want to fetch the next 10 results and so on. I've looked at the documentation of the API that I'm using, and it has support for offsetting the items I'm fetching. Though I'm not sure where to start from there. Does anyone know any good resources on this subject? Thanks a ton!
after a while i solved the problem
here is my sample project for you reading this question
Here is my solution if you are trying to avoid using an npm package. You can use it in any scrollable div in vue. And in the if statement below is where you would handle your api call to fetch more results.
<template>
<div class="scroll" #scroll="scroll">
</div>
</template>
<script>
export default{
name: "Scroll",
data(){
return{
bottom: false
}
},
methods:{
scroll(e){
const {target} = e;
if (Math.ceil(target.scrollTop) >=
target.scrollHeight - target.offsetHeight) {
//this code will run when the user scrolls to the bottom of this div so
//you could do an api call here to implement lazy loading
this.bottom = true;
}
}
}
</script>

Problem with creating a div into the document (DOM)

I have some problems with this fragment, it creates a div into the DOM so that div appears in every page as it is normal, which is an image.
How can I change it so this div is only being created when I access at this custom component (Model.vue) and no longer being visible once I'm out of the component page.
Thanks!!
container = document.createElement( 'div' );
document.body.appendChild( container );
If your HTML is predictable you should probably add it in your template (as HTML or as a component) and then show/hide it with v-if or v-show
If you need static content, for example depending on the user input, then v-html is the way.
If you need dynamic content (e.g. with event bindings), then check out render functions.
There's also dynamic component like <component :is="someComponent">, where someComponent can be either a string name of the component, or the component itself. But you should probably try the other solution first.
First of all direct DOM manipulation(Add/Del elements) is not at all preferred in vuejs. You can use v-if instead to show the element conditionally.
Or else if you still want to do direct manipulation then you can write the above lines of code in mounted hook of your model.vue component.
mounted() {
container = document.createElement('div');
container.setAttribute("id", "divId");
}
And then in before destroyed hook you can remove this element from the DOM as below.
beforeDestroy() {
var elem = document.querySelector('#divId');
elem.parentNode.removeChild(elem);
}