Programmatically style an input element's "focus" pseudo-class with Vue - css

So far I'm using event listeners to set the :focus pseudo-class of an input element:
const element = document.getElementById("myElementID");
element.addEventListener("focus", (e) => {
e.target.style.borderColor = "red";
});
element.addEventListener("blur", (e) => {
e.target.style.borderColor = "";
});
JSFiddle
Although this works, is there a more elegant or idiomatic way to achieve the same thing with Vue?

You don't have to listen to native events using vanilla JS syntax and getElementById when using Vue. You can specify the v-on-handler directly on the element in the template, like so:
// Vue SFC
<template>
<div>
<input #blur="doSomething" #focus="doSomethingElse" />
</div>
</template>
<script>
export default {
methods: {
// Both methods receive the same native dom event as a vanilla listener would
doSomething(e) {
e.target.style.borderColor = "red";
},
doSomethingElse(e) {
e.target.style.borderColor = "";
}
}
}
</script>
If you only want to apply this simple styling then a pure CSS solution, as provided by Manas Khandelwal, is sufficient and preferrable.

You can simply do this with CSS. Like this:
input {
outline: none;
}
input:focus {
border-color: red;
}
<input type="text" id="myElementID" />

Related

How to set up styleClass inside ts file?

I want to create css element inside ts file and pass it as a styleClass to PrimeNg Toast.
var style = document.createElement('style');
style.innerHTML = '::ng-deep .cssClass { background-color: #e62323; border: solid #8A427A; border-width: 0 0 0 6px; color: #2c1e30; }';
this.messageService.add({severity:'custom', summary:'Service Message', detail:'Via MessageService', styleClass: 'cssClass', sticky: true});
The above code is not working for me, style is not applied.
Could someone help me with that?
EDIT: I tried to catch the p-toast and append style but it's not applied still.
setTimeout(() => {
let message = document.getElementsByClassName('p-toast-message-custom')[0];
message.appendChild(style);
}, 100)
In Angular we should not set a style with directly DOM manipulation. The reason is:
Angular cannot detect changes if you do this by hand
Angular render components and create and update the DOM.
So its possible, but not a good way. In your case, set append the element to the DOM. Then it will works, I think.
The Angular Way
Each component has it one contained CSS/SCSS.
What you can do is to use Angulars board means, like ngStyle, ngClass and so on. example:
<div [ngStyle]="{'background-color':'green'}"></<div>
You can do it with property binding, too:
// Code
getColor(country) { (2)
switch (country) {
case 'UK':
return 'green';
case 'USA':
return 'blue';
case 'HK':
return 'red';
}
}
// Html
<div [ngStyle]="{'color':getColor(person.country)}">Test</div>
ngClass does the same but let you set a class flexible to any component.
// Code
val: number = 9;
// Html
<td [ngClass]="val > 10 ? 'red' : 'green'">{{ val }}</td>
Link to ngClass docu, link to ngStyle docu.
To add a class to a message using PrimeNG's MessageService, you can use the add method and provide an optional options object that includes a styleClass property.
Here's an example code snippet that demonstrates how to add a class to a message:
import { Component } from '#angular/core';
import { MessageService } from 'primeng/api';
#Component({
selector: 'app-my-component',
template: `
<button (click)="showSuccess()">Show Success Message</button>
`,
providers: [MessageService]
})
export class MyComponent {
constructor(private messageService: MessageService) {}
showSuccess() {
this.messageService.add({severity:'success', summary:'Success', detail:'Message Content',
options: {styleClass: 'my-custom-class'}});
}
}
.my-custom-class {
background-color: #b3ffb3;
color: #006600;
border: 1px solid #006600;
border-radius: 5px;
padding: 10px;
}

How to call a function inside a child component in `Vue3` created through a `v-for` loop?

I am currently building a form builder with vue3 composition API. The user can add in different types of inputs like text, radio buttons etc into the form before saving the form. The saved form will then render with the appropriate HTML inputs. The user can edit the name of the question, eg Company Name <HTML textInput.
Currently, when the user adds an input type eg,text, the type is saved into an ordered array. I run a v-for through the ordered array and creating a custom component formComponent, passing in the type.
My formComponent renders out a basic text input for the user to edit the name of the question, and a place holder string for where the text input will be displayed. My issue is in trying to save the question text from the parent.
<div v-if="type=='text'">
<input type="text" placeholder="Key in title"/>
<span>Input field here</span>
</div>
I have an exportForm button in the parent file that when pressed should ideally return an ordered array of toString representations of all child components. I have tried playing with $emit but I have issue triggering the $emit on all child components from the parent; if I understand, $emit was designed for a parent component to listen to child events.
I have also tried using $refs in the forLoop. However, when I log the $refs they give me the div elements.
<div v-for="item in formItems" ref="formComponents">
<FormComponent :type="item" />
</div>
The ideal solution would be to define a method toString() inside each of the child components and have a forLoop running through the array of components to call toString() and append it to a string but I am unable to do that.
Any suggestions will be greatly appreciated!
At first:
You don't really need to access the child components, to get their values. You can bind them dynamically on your data. I would prefer this way, since it is more Vue conform way to work with reactive data.
But I have also implemented the other way you wanted to achieve, with accessing the child component's methods getValue().
I would not suggest to use toString() since it can be confused with internal JS toString() function.
In short:
the wrapping <div> is not necessary
the refs should be applied to the <FormComponents> (see Refs inside v-for)
this.$refs.formComponents returns the Array of your components
FormComponent is used here as <form-components> (see DOM Template Parsing Caveats)
The values are two-way bound with Component v-model
Here is the working playground with the both ways of achieving your goal.
Pay attention how the values are automatically changing in the FormItems data array.
const { createApp } = Vue;
const FormComponent = {
props: ['type', 'modelValue'],
emits: ['update:modelValue'],
template: '#form-component',
data() {
return { value: this.modelValue }
},
methods: {
getValue() {
return this.value;
}
}
}
const App = {
components: { FormComponent },
data() {
return {
formItems: [
{ type: 'text', value: null },
{ type: 'checkbox', value: false }
]
}
},
methods: {
getAllValues() {
let components = this.$refs.formComponents;
let values = [];
for(var i = 0; i < components.length; i++) {
values.push(components[i].getValue())
}
console.log(`values: ${values}`);
}
}
}
const app = createApp(App)
app.mount('#app')
#app { line-height: 2; }
[v-cloak] { display: none; }
label { font-weight: bold; }
th, td { padding: 0px 8px 0px 8px; }
<div id="app">
<label>FormItems:</label><br/>
<table border=1>
<thead><tr><th>#</th><th>Item Type:</th><th>Item Value</th></tr></thead>
<tbody><tr v-for="(item, index) in formItems" :key="index">
<td>{{index}}</td><td>{{item.type}}</td><td>{{item.value}}</td>
</tr></tbody>
</table>
<hr/>
<label>FormComponents:</label>
<form-component
v-for="(item, index) in formItems"
:type="item.type" v-model="item.value" :key="index" ref="formComponents">
</form-component>
<button type="button" #click="getAllValues">Get all values</button>
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<script type="text/x-template" id="form-component">
<div>
<label>type:</label> {{type}},
<label>value:</label> <input :type='type' v-model="value" #input="$emit('update:modelValue', this.type=='checkbox' ? $event.target.checked : $event.target.value)" />
</div>
</script>

Responsive Props in Vue Component

I have a prop called src in a Vue Component that binds to a :style like this:
<template>
<section :class="color" class="hero" :style="{ backgroundImage: src && 'url(' + src + ')' }">
<slot></slot>
</section>
</template>
<script>
export default {
props: ['src', 'color']
}
</script>
What I would like to do is to create a list of responsive props that get used depending on the device or screen size of the site visitor.
For instance, I imagine a list of props like src-sm, src-md, src-lg, etc. The user would enter different image urls for different device sizes and the style attr would use the appropriate url depending on the screen/size.
Is this possible in VueJS. If so, any idea how?
Thanks.
Unfortuently what you are trying to do is not trivial. This is because inline style tags can not accept media queries.
The spec declares:
The value of the style attribute must match the syntax of the contents of a CSS declaration block
Solution 1:
This solution is the simplest, perhaps not entirely what you are looking for.
It works by including img elements, and showing an hiding them via CSS.
<template>
<div>
<img class="image--sm" :src="src.sm" />
<img class="image--md" :src="src.md" />
<img class="image--lg" :src="src.lg" />
</div>
</template>
<script>
export default {
props: {
src: Object
}
}
</script>
<style>
.image--md,
.image--lg {
display: none;
}
#media (min-width: 400px) {
.image--sm {
display: none;
}
.image--md {
display: block;
}
}
#media (min-width: 600px) {
.image--md {
display: none;
}
.image--lg {
display: block;
}
}
</style>
Example: https://jsfiddle.net/h3c5og08/1/
Solution 2:
Image tags may not be the desired effect you are trying to achieve. This solution creates a style tag in the head and injecting the css content to change the background images.
You can not have style tags in Vue template. It will throw an error like:
Templates should only be responsible for mapping the state to the UI. Avoid placing tags with side-effects in your templates, such as , as they will not be parsed.
As the error describes vue is designed the map state the UI. Using style tags in the template is prohibited because you can cause leaks to the outer world.
Although you can not declaratively styles in a template, we can use a bit of JS in the mounted hook of the component to add targetted and dynamic styles.
First we will need to constrain dynamic styles to this element. We can use the internal id of the created component this._uid, attaching to scope the css. (Note this is internal API so can be subject to change)
<template>
<div class="image" :data-style-scope="_uid">
</div>
</template>
The next part is to generate the style in a computed property, to later inject into a style block. You can expand on this computed property, to conditionaly assign properties ect. Note: keep the properties to the dynamic values only.
css () {
const selector = `.image[data-style-scope="${this._uid}"]`
const img = val => `${selector} { background-image: url("${val}"); }`
const sm = img(this.sm)
const md = img(this.md)
const lg = img(this.lg)
return `
${sm}
#media (min-width: 200px) { ${md} }
#media (min-width: 300px) { ${lg} }
`
}
This generated string from the css computed property is what we will now use when creating the style tag at mount. At mount we create a style node and append to the head. Assigning the nodes to the vm for references.
Using the references in the vm we can watch changes to the computed updating the style node.
Remember to clean up before destorying the component, removing the style node.
{
data () {
return {
// Reference data properties
style: null,
styleRef: null
}
},
mounted () {
// Create style node
let style = document.createElement('style')
style.type = "text/css"
style.appendChild(document.createTextNode(''))
// Assign references on vm
this.styleRef = style
this.style = style.childNodes[0]
// Assign css the the style node
this.style.textContent = this.css
// Append to the head
document.head.appendChild(style)
},
beforeDestroy () {
// Remove the style node from the head
this.style.parentElement.removeChild(this.style)
},
computed: {
css () {
// ...
}
},
watch: {
css (value) {
// On css value change update style content
this.style.textContent = this.css
}
}
}
Working Example: https://jsfiddle.net/bLkc51Lz/4/
You could also try the module described here: https://alligator.io/vuejs/vue-responsive-components/ which is called vue-responsive-components
It lets the component change its CSS depending on its own width (not on the entire browser's width)

How do I use ":nth-of-type" to select an element after the element is updated by jQuery?

I want to style the first element with a class that I've added through jQuery.
Unfortunately, my CSS styling is ignored when I use the :nth-of-type(1) selector.
Here is the Fiddle
When you click the button "World", the first word should be red but it isn't.
How do I use :nth-of-type to select an element after a jQuery updates the element?
You're using jQuery, fall back to it when CSS fails you. This doesn't mean inline styles, let's continue to use classes (modified fiddle):
Your new CSS:
.hidden {
display: none;
}
.seen {
display: inline-block;
}
.first {
color: red;
}
The new class .first replaces your attempt to match via CSS. We'll apply it with jQuery:
$( "button.1" ).click(function () {
$("span.1").toggleClass("seen hidden");
$("span").removeClass("first");
$(".seen:first").addClass("first");
});
$( "button.2" ).click(function () {
$("span.2").toggleClass("seen hidden");
$("span").removeClass("first");
$(".seen:first").addClass("first");
});
Now that things are working we've gotten to the point of "passing our test" (even though no test is written here, this is the point we'd be at). The next step is refactor. We've got some repetitive bits. Let's clean it up. Naively I may try and do this:
var selectFirst = function() {
$("span").removeClass("first");
$(".seen:first").addClass("first");
};
$( "button.1" ).click(function () {
$("span.1").toggleClass("seen hidden");
selectFirst();
});
$( "button.2" ).click(function () {
$("span.2").toggleClass("seen hidden");
selectFirst();
});
But in reality we can do much better by moving around some information in the HTML and changing our jQuery slightly (working fiddle):
Our new HTML looks like this:
<span class="hidden" data-number="1">Hello</span>
<span class="hidden" data-number="2">World</span>
<span class="hidden" data-number="1">Hello</span>
<span class="hidden" data-number="2">World</span>
<button data-target-number="1">Hello</button>
<button data-target-number="2">World</button>
Notice the usage of data- attributes. Much cleaner, the 1 and 2 as classes was really bogging down that attribute with useless information.
Let's see what effect that had on the jQuery:
$("button").click(function() {
var number = $(this).data("target-number"),
// This line could also be "span[data-number=" + number + "]"
targetSelector = ["span[data-number=", number, "]"].join("");
$(targetSelector).toggleClass("seen hidden");
$(".first").removeClass("first");
$(".seen:first").addClass("first");
});
That's it, only one function! No repeating ourself. The refactor was successful.
Try this:
.hidden:first-child + .seen, .seen:first-child {
color: red;
}
Working Fiddle
Updated to solve the issue represented in below comment:
.hidden:first-child ~ .seen, .seen:first-child {
color: red;
}
.hidden:first-child ~ span.seen ~ span.seen {
color: black;
}
Working Fiddle

show the tooltip only when ellipsis is active

I have the next div:
<div class="div-class" style="width:158px;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;" title=<%=myDesc%>
How can I show the tooltip only when ellipsis is active?
I find this function
function isEllipsisActive(e) {
return (e.offsetWidth < e.scrollWidth);
}
But I didn't know how to use it knowing I use jsp and struts
Try something like this:
Working DEMO
Working DEMO - with tooltip
$(function() {
$('div').each(function(i) {
if (isEllipsisActive(this))
//Enable tooltip
else
//Disable tooltip
});
});
function isEllipsisActive(e) {
return (e.offsetWidth < e.scrollWidth);
}
For anyone using qtip (being quite popular).
First, add a class to each of your overflowing elements.
<span class="ellipsis-text">Some very long text that will overflow</span>
Then, use the jQuery selector to select multiple such elements, and apply the qTip plugin (or any other tooltip that comes to mind) on to your elements as such:
$('.ellipsis-text').each(function() {
if (this.offsetWidth < this.scrollWidth) {
$(this).qtip({
content: {
text: $(this).text()
},
position: {
at: 'bottom center',
my: 'top center'
},
style: {
classes: 'qtip-bootstrap', //Any style you want
}
});
}
});

Resources