Cannot log DOM elements on the mounted hook of vue.js - javascript

For some reason, on the mounted hook I cant seem to log DOM elements on the browser while I am potentially lokking to loop through elements or , as my final resort just work on a specific index of a HTML Collection:
the following is the vue component I am having an issue with:
<template>
<full-page ref="fullpage" id="fullpage" :options="options">
<slider class="section" :auto="false">
<slider-item v-animate-css="'fadeIn'">
<h1 class="mainTitle">PROJECT GORILLA</h1>
</slider-item>
<slider-item v-for="bkg in bkgImg" :style="{backgroundSize:'cover',
backgroundImage: 'url(' + bkg + ')'}">
<h1 class="mainTitle">PROJECT GORILLA</h1>
</slider-item>
</slider>
</full-page>
</template>
<script>
import { Slider, SliderItem } from 'vue-easy-slider'
import pinkBkg from '#/assets/img/IMG_2473.jpg'
import redBkg from '#/assets/img/IMG_4674.jpg'
import blueBkg from '#/assets/img/IMG_4716.jpg'
import greenBkg from '#/assets/img/IMG_2013.jpg'
export default {
data(){
return {
options:{
licenseKey:null
},
bkgImg:[pinkBkg,redBkg,blueBkg,greenBkg]
}
},
components: {
Slider,
SliderItem
},
mounted(){
let slides = document.getElementsByClassName("slider-item");
console.log(slides[0]);
}
}
</script>
<style>
.slider-item:nth-of-type(1) { background-color:black;}
.slider-item > .wrap {
display:flex;
justify-content: center;
align-items:center;
}
h1.mainTitle {
position:fixed;
z-index:99;
color:white !important;
}
.slider-item {
z-index:98 !important;
}
</style>
Bare in mind that I am currently using the webpack template for vue-cli. To be honest jQuery has crossed my mind as a last resort but I really don't want to
resort to that because its is important that the application has a decent performance...
in this case console.log returns undefined. But If I copy and past the code on the browser then it would work fine.

When your component is mounted, it doesn't necessarily mean the child components within it are fully rendered.
As you can see in your template, there are no HTML elements with class="slider-item". I imagine these appear later when the SliderItem components are rendered.
What you can do is add a ref attribute to any element or component you want to reference.
For example
<slider-item ref="sliderItem" v-animate-css="'fadeIn'">
<h1 class="mainTitle">PROJECT GORILLA</h1>
</slider-item>
<slider-item ref="sliderItemRepeater" v-for="bkg in bkgImg"
:style="{backgroundSize:'cover', backgroundImage: 'url(' + bkg + ')'}">
<h1 class="mainTitle">PROJECT GORILLA</h1>
</slider-item>
Then in your mounted hook, you can access
this.$refs.sliderItem // the first, non-repeating component
this.$refs.sliderItemRepeater // an array of the repeated components
See https://v2.vuejs.org/v2/guide/components-edge-cases.html#Accessing-Child-Component-Instances-amp-Child-Elements

Related

Extending regular React components with styled-components

The docs for styled-components show its default styled export accepts a single argument:
styled
This is the default export. This is a low-level factory we use to create the styled.tagname helper methods.
Arguments
component / tagname
Description
Either a valid react component or a tagname like 'div'.
I've emphasized "valid react component" here because they explicitly aren't saying this has to be a React component created by styled, although traditionally that is how this is used (as well as documented under their Extending Styled section). An example of this is shown below:
const RedBox = styled.div`
border: 1px solid black;
color: red;
`;
// Traditionally, the argument you pass to `styled` is
// a react element *created by a previous `styled` call*
const BlueBox = styled(RedBox)`
color: blue;
`;
function Example() {
return (
<div>
<RedBox>I am a red box</RedBox>
<BlueBox>I am a blue box</BlueBox>
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
<script src="https://unpkg.com/react#17.0.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#17.0.1/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/react-is#17.0.1/umd/react-is.production.min.js"></script>
<script src="https://unpkg.com/styled-components#5.2.1/dist/styled-components.js"></script>
<div id="root"></div>
Nothing unsurprising with the above.
However, my question is what if you pass a non styled component as the argument to the styled call? Shouldn't that returned element also get the styles applied?
Consider the following simple example:
// Create two simple components, one functional, one class-based
const BoxFunctional = (props) => <div>{props.children}</div>;
class BoxClass extends React.Component {
render() {
return <div>{this.props.children}</div>
}
}
// Here, I pass a functional React Component to `styled`
const RedBoxFunctional = styled(BoxFunctional)`
color: red;
border: 1px solid black;
`;
// Again, passing another regular React component, this time a class component
const RedBoxClass = styled(BoxClass)`
color: red;
border: 1px solid black;
`;
function Example() {
return (
<div>
<p>The below two Boxes are regular react elements:</p>
<BoxFunctional>I am a functional box</BoxFunctional>
<BoxClass>I am a class box</BoxClass>
<hr />
<p>The below two boxes <em>should</em> be styled:</p>
<RedBoxFunctional>I am a functional red box</RedBoxFunctional>
<RedBoxClass>I am a class red box</RedBoxClass>
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
<script src="https://unpkg.com/react#17.0.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#17.0.1/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/react-is#17.0.1/umd/react-is.production.min.js"></script>
<script src="https://unpkg.com/styled-components#5.2.1/dist/styled-components.js"></script>
<div id="root"></div>
Running the above snippet you can see that the styled components are not styled, despite them extending a "valid" react component.
Is there something I'm missing? Are the docs just incorrect? Can styled only apply styles to an existing react component that is made by a previous styled call?
I don't really know where the confusion lies, you can pass any React component to the styled Higher Order Component.
The issue you have is that you aren't trying to style the BoxFunctional or BoxClass components, but rather you are trying to style the JSX they render. You just need to proxy the className prop through to what each renders.
Styling any Component
The styled method works perfectly on all of your own or any
third-party component, as long as they attach the passed className
prop to a DOM element.
const BoxFunctional = (props) => (
<div className={props.className}>{props.children}</div>
);
class BoxClass extends Component {
render() {
return <div className={this.props.className}>{this.props.children}</div>;
}
}
Demo

Override the style in one component A when the component B is present on the view - Angular

I have two components on same level:
<app-parent>
<app-component-a></app-component-a>
<app-component-b></app-component-b>
</app-parent>
The Component-A:
Has the below styles:
:host {
.container {
height: 10rem;
}
}
But I want to change the height of the container of the component-a, when the component-b is present on the view. So let's say the componen-b is hidden and the default height from above can be used, but when component-b is visible the style below should override the height given above.
height: 2rem;
Any suggestions on how to achieve this.
I have tried using the below on component-b but it doesn't help:
:host-context(.container) {
height: 2rem;
}
Have updated the code to make this work if there is a [ngClass] added to the code:
<div class="container" [ngClass]="{'area-height': showArea}">
Adding the Stackblitz code for easy understanding
Now I want to see if there is a way if we can connect the boolean value from the other component?
Added the random boolean generator, for now, to show/hide the component-b
Here is my stackblitz sample
https://stackblitz.com/edit/angular-components-styles-override-78a9ni
Actually all you need is to apply some class based on some compB property
Here are two possible solutions:
Solution A
REQUIREMENT: The display height of both components (when both are shown) is greater than or equal to 10rem
REQUIREMENT: When Component B is hidden, nothing is supposed to display in the area that it occupies when it is shown.
1) Wrap both components in a element with a fixed height equal to the height of Component B plus 2rem
2) Style Component A with a min-height of 2rem and a max-height of 10rem
RESULT: When Component B is hidden, Component A will take its full height of 10rem. When Component B shows, Component A will shrink to a minimum of 2rem.
Solution B
REQUIREMENT: There is (or could be) a property in the component that tracks the show/hide state of Component B
1) Use ngStyle on Component A in a manner similar to the following example:
<app-component-a [ngStyle]="{'height':isComponentBShown ? '2rem' : '10rem'}"><app-component-a>
<app-component-b [showArea]="isComponentBShown"><app-component-b>
RESULT: When the isComponentBShown boolean evaluates as true, the height of Component A is set to 2rem. It is set to 10rem otherwise.
Of the two, I would recommend Solution B.
you can definitely achieve this using :host-content provided by angular. Let say we have two components - component A and component B as we have in your requirement.
I am hiding component B from a button present in App component. Just toggle a css class to an ancestor of Component A in response to toggle of component B. I have created a stackblitz for you, please let me know if i am missing anything.
Stackblitz link- https://stackblitz.com/edit/angular-cfkqyt
Important code you need to look is below
// app component.ts
import {
Component
} from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
name = 'Angular';
isBVisible = true;
toggleB() {
this.isBVisible = !this.isBVisible;
}
}
/* Component A css */
.container-a {
border: 1px solid red;
height: 10rem;
}
/* apply css to component a if it finds bPresent css classname in its ancestor. */
:host-context(.bPresent) .container-a {
height: 4rem;
}
<!-- app component html -->
<button (click)="toggleB()">toggle b</button>
<div [class.bPresent]="isBVisible">
<comp-a></comp-a>
</div>
<div *ngIf="isBVisible">
<comp-b></comp-b>
</div>
:
Maybe you can handle it using simple CSS? For example, you can make different style in case if component-a is last-child(see your modified example here):
:host {
.container {
height: 10rem;
background: blueviolet;
}
.area-height{
height: 2rem;
}
}
// if component-b is present
:host:not(:last-child) {
.container {
height: 2rem;
}
}

Announcing information to a screen reader in vue.js using aria-live

I am trying to get a vue component to announce information dynamically to a screen reader when different events occur on my site.
I have it working to where clicking a button will populate a span that is aria-live="assertive" and role="alert" with text. This works decently the first time, however, clicking other buttons with similar behavior causes NVDA to read the previous text twice before reading the new text. This seems to be happening in vue, but not with a similar setup using jquery, so I'm guessing it has something to do with the way vue renders to the DOM.
I'm hoping there is some way to workaround this problem or perhaps a better way to read the text to the user that would not have this issue. Any help is greatly appreciated.
Here is a simple component I set up in a working code sandbox to show the problem I am having (navigate to components/HelloWorld.vue for the code) -- Note: This sandbox has changed per the answer below. Full code for the component is below:
export default {
name: "HelloWorld",
data() {
return {
ariaText: ""
};
},
methods: {
button1() {
this.ariaText = "This is a bunch of cool text to read to screen readers.";
},
button2() {
this.ariaText = "This is more cool text to read to screen readers.";
},
button3() {
this.ariaText = "This text is not cool.";
}
}
};
<template>
<div>
<button #click="button1">1</button>
<button #click="button2">2</button>
<button #click="button3">3</button><br/>
<span role="alert" aria-live="assertive">{{ariaText}}</span>
</div>
</template>
Ok so what I've found works way more consistently is instead of replacing the text in the element with new text, to add a new element to a parent container with the new text to be read. Instead of storing the text as a single string, I am storing it in an array of strings which will v-for onto the page within an aria-live container.
I have built a full component that will do this in various ways as an example for anyone looking to do the same:
export default {
props: {
value: String,
ariaLive: {
type: String,
default: "assertive",
validator: value => {
return ['assertive', 'polite', 'off'].indexOf(value) !== -1;
}
}
},
data() {
return {
textToRead: []
}
},
methods: {
say(text) {
if(text) {
this.textToRead.push(text);
}
}
},
mounted() {
this.say(this.value);
},
watch: {
value(val) {
this.say(val);
}
}
}
.assistive-text {
position: absolute;
margin: -1px;
border: 0;
padding: 0;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
}
<template>
<div class="assistive-text" :aria-live="ariaLive" aria-relevant="additions">
<slot></slot>
<div v-for="(text, index) in textToRead" :key="index">{{text}}</div>
</div>
</template>
This can be used by setting a variable on the parent to the v-model of the component, and any changes to that variable will be read to a screen reader once (as well as any time the parent container becomes tab-focused).
It can also be triggered by this.$refs.component.say(textToSay); -- note this will also be triggered again if the parent container becomes tab-focused. This behavior can be avoided by putting the element within a container that will not receive focus.
It also includes a slot so text can be added like this: <assistive-text>Text to speak</assistive-text> however, that should not be a dynamic/mustache variable or you will encounter the problem in the original question when the text changes.
I've also updated the sandbox posted in the question with a working example of this component.

How does <transition-group> work with Vue.components?

Suppose I want to use the components to make a list that would disappear if I click on it, and use the transition-group to do the animation part.
The following code can perform well:
HTML:
<transition-group name="testanim">
<p key="1" v-if='open1' #click='open1 = false'>Can You See Me?</p>
<p key="2" v-if='open2' #click='open2 = false'>Can You See Me?</p>
</transition-group>
CSS:
.testanim-enter-active, .testanim-leave-active {
transition: all .5s;
}
.testanim-enter, .testanim-leave-to {
transform: translateX(1rem);
opacity: 0;
}
.testanim-leave-active {
position: absolute;
}
.testanim-move {
transition: all .5s;
}
open1 and open2 are defined in data in Vue.js.
However, the following code would not perform the animation at all.
HTML:
<transition-group name="testanim">
<test-sth key="1"></test-sth>
<test-sth key="2"></test-sth>
</transition-group>
CSS: the same with above
JavaScript:
Vue.component ("test-sth", {
template: "<p v-if='open' #click='open = !open'>Can You See Me?</p>",
data: function () {
return {
open: true,
}
}
})
So the problem is that how I can animate the components inside the transition-group. I've searched for a few hours but did not find some question or documents related to it.
Update:
The key problem is that the animation in the former example that the second sentence move upwards smoothly when the first sentense disappear do not show in the latter one. Although I may put the transition inside the template, That do not solve the problem.
Should I write the whole transition-groupinside the template, or something else...?
When using Vue transitions, for internal reasons, the transition/transition-group components must be in the same template as the state that's being toggled.
Also, Vue components require that there always be a single root element for a component. A v-if breaks this rule because it gives the possibility of the element not being there, if the v-if happens to be false.
To solve your issue, move the transitioning to the test-sth component. Since it manages its own toggling, it should manage its own transitioning as well.
Vue.component("test-sth", {
template: `
<transition name='testanim'>
<p v-if='open' #click='open = !open'>Can You See Me?</p>
</transition>
`,
data: () => ({
open: true,
}),
})
new Vue({
el: "#app",
template: `
<div>
<test-sth></test-sth>
<test-sth></test-sth>
</div>
`,
})
See this fiddle for a working example.

How to control order of rendering in vue.js for sibling component

I have following kind of code:
<div>
<compA />
<compB />
</div>
How do I make sure that first compA is rendered only after it compB is rendered.
Why I want is I have some dependency on few elements of compA, and style of compB depends on presence of those elements.
Why in details:
I have some complex UI design, where one box will become fixed when you scroll. SO It will not go above the screen when you scroll, it will be fixed once you start scrolling and it start touching the header. So I am using jquery-visible to find if a div with a particular id is visible on the screen, if it is not visible, I change the style and make that box fixed. Following code should give the idea what I am doing:
methods: {
onScroll () {
if ($('#divId').visible(false, false, 'vertical')) { // This is div from the compA, so I want to make sure it is rendered first and it is visible
this.isFixed = false
} else {
this.isFixed = true
}
}
},
mounted () {
window.addEventListener('scroll', this.onScroll() }
},
destroyed () {
window.removeEventListener('scroll', this.onScroll)
}
I dont want to make those in same component as one reason is it dont make sense as the nature of these components, and other I use compA at many places, while compB is specific to only one page. Also layout of these does not allow me to make compB child of compA as suggested in comments.
Any suggestions are welcome.
An option with events:
<!-- Parent -->
<div>
<comp-a #rendered="rendered = true"></comp-a>
<component :is="compB"></component>
</div>
<script>
// import ...
export default {
components: { CompA, CompB },
watch: {
rendered: function (val) {
if (val) this.compB = 'comp-b';
}
},
data() {
return {
rendered: false,
compB: null
}
}
}
</script>
<!-- Component B -->
<script>
export default {
mounted() {
this.$emit('rendered');
}
}
</script>
After going through the edit I realised that the dependency is not data driven but event driven (onscroll). I have tried something and looks like it works (the setTimeout in the code is for demonstration).
My implementation is slightly different from that of Jonatas.
<div id="app">
RenderSwitch: {{ renderSwitch }} // for demonstration
<template v-if='renderSwitch'>
<comp-a></comp-a>
</template>
<comp-b #rendered='renderSwitchSet'></comp-b>
</div>
When the component-B is rendered it emits an event, which just sets a data property in the parent of both component-A and component-B.
The surrounding <template> tags are there to reduce additional markup for a v-if.
The moment renderSwitch is set to true. component-a gets created.

Categories