Set CSS class dynamically without template - vue.js

For each component with prefix mycomponent- I would like to add a class with the name of the component. I don't want to have to modify the component in order to do this.
My first thought was to use mixins and somehow add the class in beforeCreate but I haven't managed to add classes dynamically without using the template.
Do I have to use $el.classList.add(this.$options.name) in beforeUpdate or similar? Is there some more Vue-ish way to do it?

Here it is, wrapped up as plugin:
const addComponentNameAsClass = {
install(Vue, options) {
const fn = Vue.prototype.$mount;
Vue.prototype.$mount = function() {
fn.apply(this, arguments);
if (this.$options._componentTag?.startsWith("mycomponent-")) {
this.$el.classList.add(this.$options._componentTag);
}
}
}
}
Vue.use(addComponentNameAsClass);
// that's all you need
// see it working:
['a', 'b', 'foo', 'whatever'].forEach(type => {
Vue.component('mycomponent-' + type, {
template: '<div><slot /></div>'
})
});
new Vue({
el: '#app'
})
[class^="mycomponent-"] {
border: 1px solid;
margin-bottom: 4px;
padding: 1rem;
}
.mycomponent-a {
border-color: red;
}
.mycomponent-b {
border-color: blue;
}
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.12"></script>
<div id="app">
<mycomponent-a>I should get a red border.</mycomponent-a>
<mycomponent-b>I should get a blue one.</mycomponent-b>
<mycomponent-foo>bar</mycomponent-foo>
<mycomponent-whatever>Meh.</mycomponent-whatever>
</div>
Notes:
you should not expect this to work on Vue3. Why? Because whenever you're using internal props starting with _ Vue does not guarantee they'll still be there in the next major version update. But, on the other hand, the name of the component is not saved anywhere else (other than $options._componentTag).
the above won't work if you use components as <MycomponentA></MycomponentA>. However, you can swiftly get around it by running the value of $options._componentTag through a helper function (e.g: kebabCase from lodash).
note on note: if you want the added class to always be kebab-case, you'll have run the value passed to .classList.add() through kebabCase, as well). Otherwise, <MycomponentA> will add MycomponentA class and <mycomponent-a> will add mycomponent-a class, for obvious reasons.
Ref. "Vue-ish way": whatever the end goal of applying this "component" class is, chances are it can be achieved cleaner.
The very idea of placing classes denominating component type doesn't feel Vue-ish at all.
It feels WordPress-ish and Angular-ish. To me, at least.

Related

Custom directive

The documentation for custom directives demonstrates using a dynamic argument and a value together:
Directive arguments can be dynamic. For example, in v-mydirective:[argument]="value", the argument can be updated based on data properties in our component instance! This makes our custom directives flexible for use throughout our application.
If "value" doesn't contain a space, it works fine. But adding a space to the value (e.g. v-mydirective:[argument]="some value") causes Nuxt to choke:
invalid expression: Unexpected identifier in
some value
Raw expression: v-mydirective:[argument]="some value"
What is the problem, and how do I resolve it so that I can use a string with a space as the value to the custom directive?
Issue:
This happens because when we pass value with spaces, the expression is evaluated by vuejs and it tries to find the data options with property some & value. But as none exists with those property names, hence we get the mentioned error.
A simple example to explain this is when we pass value as:
v-mydirective:[argument]="2"
and if we do console.log inside bind function:
console.log(binding.value)
You will see the output displayed as 2. But, if we pass value as:
v-mydirective:[argument]="2 + 2"
and if we do console.log inside bind function, interestingly the output displayed this time is 4 instead of 2 + 2
Solutions:
There are two possible solutions for this:
Solution #1:
You can simply wrap some value in single quotes and pass it as a string like:
v-mydirective:[argument]="'some value'"
This way the expression will be directly evaluated as string instead.
Demo:
Vue.directive('pin', {
bind: function (el, binding, vnode) {
console.log(binding.value)
}
})
new Vue({
el: '#dynamicexample',
data: function () {
return {
direction: 'left',
}
}
})
#dynamicexample {
font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif;
color: #304455;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="dynamicexample">
<p v-pin:[direction]="'some value'">I am pinned onto the page at 200px to the left.</p>
</div>
Solution #2:
You can also create a separate data option for it like:
data: function () {
return {
myValue: 'some value'
}
}
and then you can use it in directive like:
v-mydirective:[argument]="myValue"
Demo:
Vue.directive('pin', {
bind: function (el, binding, vnode) {
console.log(binding.value)
}
})
new Vue({
el: '#dynamicexample',
data: function () {
return {
direction: 'left',
myValue: 'some value'
}
}
})
#dynamicexample {
font-family: "Source Sans Pro", "Helvetica Neue", Arial, sans-serif;
color: #304455;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="dynamicexample">
<p v-pin:[direction]="myValue">I am pinned onto the page at 200px to the left.</p>
</div>

how to update the height of a textarea in vue.js when updating the value dynamically?

Working with Vue.js, I do use a simple way to set dynamically the height of a text area that resizes when typing. But I am not able to do it when the component mounts or the value updates.
I have already try http://www.jacklmoore.com/autosize/, but it has the same problem.
I have created a sandbox that shows the problem, when typing the box it updates, but not when the value changes dynamically
Live example: https://codesandbox.io/s/53nmll917l
You need a triggerInput() method:
triggerInput() {
this.$nextTick(() => {
this.$refs.resize.$el.dispatchEvent(new Event("input"));
});
}
to use whenever you're changing the value programatically, triggering the resize logic used on <textarea> on "real" input events.
Updated codesandbox.
Note: Without the $nextTick() wrapper, the recently changed value will not have been applied yet and, even though the input is triggered, the element has not yet been updated and the resize happens before value has changed, resulting in the old height and looking like it didn't happen.
Not really feeling the answers posted here. Here is my simple solution:
<textarea
rows="1"
ref="messageInput"
v-model="message"
/>
watch: {
message: function(newItem, oldItem) {
let { messageInput } = this.$refs;
const lineHeightInPixels = 16;
// Reset messageInput Height
messageInput.setAttribute(
`style`,
`height:${lineHeightInPixels}px;overflow-y:hidden;`
);
// Calculate number of lines (soft and hard)
const height = messageInput.style.height;
const scrollHeight = messageInput.scrollHeight;
messageInput.style.height = height;
const count = Math.floor(scrollHeight / lineHeightInPixels);
this.$nextTick(() => {
messageInput.setAttribute(
`style`,
`height:${count*lineHeightInPixels}px;overflow-y:hidden;`
);
});
},
}
<style scoped>
textarea {
height: auto;
line-height: 16px;
}
</style>

Declare variable in sass for color web changer [duplicate]

This question already has an answer here:
Define variables in Sass based on classes
(1 answer)
Closed 6 years ago.
i am making color changer for my web, is it possible to make variable like this :
.red { $color: red; $background: red; }
.green { $color: green; $background: green; }
.blue { $color: blue; $background: blue; }
thanks
There's nothing inherently wrong with your SASS here - at least in principle - but syntatically it's a tad skewed. Also, what your trying to do though requires so client side run-time code for it to be implemented.
First up though you don't actually need the variables - but we'll run with it. So change your sass to
$red: #ff1a1a;
$green: #5cd65c;
$blue: #1a75ff;
.blue { background-color: $blue; }
.green { background-color: $green }
.red { background-color: $red }
assuming this generates a CSS file and your importing this into your HTML page you'll need a little bit of Javascript to apply the appropriate colour class to the element you want to take on this property.
Assuming you have 3 elements ( buttons ) with unique ID's, which when clicked will change the background colour of an element id=foo you could have something like
var changeColor = function(col) {
document.getElementById("foo").className = col
}
document.getElementById('buttonblue').addEventListener('click',
function() {
changeColor('blue');
}, false);
document.getElementById('buttongreen').addEventListener('click',
function() {
changeColor('green');
}, false);
// ... etc etc for each color button you have
This is far from clean or modularised code, but hopefully it outlines the principle of the process which you need to follow
Here's a working codePen with the example: http://codepen.io/anon/pen/rewoOY

Anyone Know a Way to Inherit the Parent of an Extended Class?

Background:
Im working on a framework that has browser classes applied to the HTML element.
Im trying to apply a cross browser fix (for safari5) whenever I extend to a mixin.
Example Markup:
<html class="safari5">
<div class="child"></div>
</html>
LESS:
.mixin{
content:"cool style mixin that breaks on safari";
}
.safari5{
.fix{content:"hacks safari5's bullshit and semi-fixes cool style mixin"!important;}
}
.child{
&:extend(.mixin);
&:extend(.fix);
}
/*
Expected CSS Output:
.mixin,
.child {
content: "cool style that breaks on safari";
}
.safari5 .fix,
.safari5 .child{
content:"hacks safari5's bullshit and semi-fixes cool style mixin"!important;
}
*/
Thanks!
See extend all. E.g.:
.mixin {
1: 1;
}
.safari5 {
.fix {2: 2}
}
.child {
&:extend(.mixin, .fix all);
}

Re-apply default element properties in class

LESS has mixins which make it easy to re-use properties from one class or ID ruleset in another. Is there a way to reference properties for an element (without class or ID) inside another ruleset? I'd like to do something like:
// Defined in a base .less file somewhere
a {
color: blue;
}
// Defined within a more specific file
.myClass a {
color: red;
}
// #myElement is used within .myClass, but I'd like to re-use the
// base styles.
#myElement a {
a();
}
You can use *:extend() pseudo-class for that:
#myElement a {
background: green;
&:extend(a);
}
See: Extend