I try to recreate the ellipsis components in naive-ui css library for vue3, here are the source code and demo codesandbox
https://github.com/tusen-ai/naive-ui/blob/main/src/ellipsis/src/Ellipsis.tsx
https://codesandbox.io/s/pne1kq
I spent a lot of time watching the source code, for now I figure out most of the source code.
But one thing I can't trace is where
s the logic they judge how long should make the tooltip appeared and make the text to have ... at the end.
Can someone help trace which part the logic exist in the above link ?
The ... is set through style declarations on the parent element. Look at the computed property ellipsisStyleRef, particularly where the value of textOverflow is set (line 66):
export default defineComponent({
...
setup (props, { slots, attrs }) {
...
const ellipsisStyleRef = computed(() => {
const { lineClamp } = props
const { value: expanded } = expandedRef
if (lineClamp !== undefined) {
return {
textOverflow: '',
'-webkit-line-clamp': expanded ? '' : lineClamp
}
} else {
return {
textOverflow: expanded ? '' : 'ellipsis', // <------------- here
'-webkit-line-clamp': ''
}
}
})
As to length, I am not sure if you mean width or duration. But it looks like there is no duration, the tooltip is shown as long as mouse hovers over trigger, and width does not seem to be restricted.
The store data can be accessed in a component embeded in the HTML directly as:
{{$store.state.notificationArea.cart.total;}}
This works fine, However, it doesn't work in the computed attribute of the same controller as:
computed: {
total: function () {
return this.$store.state.notificationArea.cart.total;
}
}
Have been trying to resolve it for three days, please help.
A computed property is a function that returns a value it should be declared like total:function(){}, total:()=>{} or total(){} :
computed: {
total:()=>{
return this.$store.state.notificationArea.cart.total;
}
}
And that property which you're referencing should be initialized like :
const state={
notificationArea:{
cart:{
total:0,
}
}
}
....
I've been searching for a good solution to dynamically modify the class that can be attached to the bodyAttrs, with no success. I have found no posts that specifically address/answer my situation. I hope someone can help.
I have a project I am working on and in the project I am using Nuxt with SSR functionality, The site has properties that can be manipulated with user configuration. Setting the scene... the user can manipulate the body tag, and can change background colors.
I have set up the app.html page defined in the documentation (https://nuxtjs.org/guide/views#document). I have then set the head like so:
head() {
return {
bodyAttrs: {
class: this.dataLoaded ? "bodyAttr" : ""
}
};
}
Here is what the bodyAttr class looks like. This is a default value at startup:
.bodyAttr {
background: linear-gradient(#0098db, #0046ad);
}
When the data is loaded, I need to dynamically change the background property colors to the values selected by the user configuration.
Is there a way to do this... or am I approaching this from the wrong direction?
Thanks.
I would consider adding and/or removing classes from the body tag and then in your CSS, define styles for those classes.
export default {
data() {
return {
darkMode: false
}
},
head() {
return {
bodyAttrs: {
class: this.darkMode ? 'my-gradient' : 'normal-mode'
}
}
},
}
Then somewhere in your CSS:
.my-gradient {
background: linear-gradient(#0098db, #0046ad);
}
.normal-mode {
background: none;
}
In the above examples, setting darkMode to true will apply the my-gradient class to the body tag and setting it to false will apply normal-mode.
I've got form with dynamic number of input fields and i need to transliterate data, passed to this fields in 'live'. I wrote custom directive which do all job, but there is an a error -> it converts all chars except last one (should be привет->privet, while привет->priveт). This is my source code
directives: {
transliterate: {
update(element, binding) {
element.value = tr(element.value)
}
}
}
This is PUG (Jade)
input(v-model='requestHotels.travellers[index].first_name', v-transliterate='true')
tr - just function, which transliterate from ru to en
I knew why this happening, but i can't solve it by myself. Any ideas?
1) Consider using computed property instead of directive. Personally, I don't like directives because they can add alot of useless complexity to your code. But there are some complex cases where they can be really useful. But this one is not one of them.
export default {
data: () => ({
tranliteratedValue: ""
}),
computed: {
vModelValue: {
get() {
return this.tranliteratedValue;
},
set(value) {
this.tranliteratedValue = transl.transform(value);
}
}
}
};
Full example: https://codesandbox.io/s/039vvo13yv?module=%2Fsrc%2Fcomponents%2FComputedProperty.vue
2) You can use filter and transliterate during render
filters: {
transliterate(value) {
return transl.transform(value);
}
}
Then in your template:
<p>{{ value | transliterate }}</p>
Full example: https://codesandbox.io/s/039vvo13yv?module=%2Fsrc%2Fcomponents%2FFilter.vue
3) Transparent wrapper technique (using custom component)
The idea behind transparent wrapper is that you should create custom component that behave as build-in input (and accepts the same arguments) but you can intercept events and change behaviour as you'd like. In your example - tranliterate input text.
<textarea
v-bind="$attrs"
:value="value"
v-on="listeners"
/>
computed: {
listeners() {
return {
...this.$listeners,
input: event => {
const value = transl.transform(event.target.value + "");
this.$emit("input", value);
}
};
}
}
Full example: https://codesandbox.io/s/039vvo13yv?module=%2Fsrc%2Fcomponents%2Finc%2FTransliteratedInput.vue
Read more about Transparent wrapper technique here https://github.com/chrisvfritz/7-secret-patterns/blob/master/slides-2018-03-03-spotlight-export.pdf
You can check all 3 working approaches here https://codesandbox.io/s/039vvo13yv
How do I access $refs inside computed? It's always undefined the first time the computed property is run.
Going to answer my own question here, I couldn't find a satisfactory answer anywhere else. Sometimes you just need access to a dom element to make some calculations. Hopefully this is helpful to others.
I had to trick Vue to update the computed property once the component was mounted.
Vue.component('my-component', {
data(){
return {
isMounted: false
}
},
computed:{
property(){
if(!this.isMounted)
return;
// this.$refs is available
}
},
mounted(){
this.isMounted = true;
}
})
I think it is important to quote the Vue js guide:
$refs are only populated after the component has been rendered, and they are not reactive. It is only meant as an escape hatch for direct child manipulation - you should avoid accessing $refs from within templates or computed properties.
It is therefore not something you're supposed to do, although you can always hack your way around it.
If you need the $refs after an v-if you could use the updated() hook.
<div v-if="myProp"></div>
updated() {
if (!this.myProp) return;
/// this.$refs is available
},
I just came with this same problem and realized that this is the type of situation that computed properties will not work.
According to the current documentation (https://v2.vuejs.org/v2/guide/computed.html):
"[...]Instead of a computed property, we can define the same function as a method. For the end result, the two approaches are indeed exactly the same. However, the difference is that computed properties are cached based on their reactive dependencies. A computed property will only re-evaluate when some of its reactive dependencies have changed"
So, what (probably) happen in these situations is that finishing the mounted lifecycle of the component and setting the refs doesn't count as a reactive change on the dependencies of the computed property.
For example, in my case I have a button that need to be disabled when there is no selected row in my ref table.
So, this code will not work:
<button :disabled="!anySelected">Test</button>
computed: {
anySelected () {
if (!this.$refs.table) return false
return this.$refs.table.selected.length > 0
}
}
What you can do is replace the computed property to a method, and that should work properly:
<button :disabled="!anySelected()">Test</button>
methods: {
anySelected () {
if (!this.$refs.table) return false
return this.$refs.table.selected.length > 0
}
}
For others users like me that need just pass some data to prop, I used data instead of computed
Vue.component('my-component', {
data(){
return {
myProp: null
}
},
mounted(){
this.myProp= 'hello'
//$refs is available
// this.myProp is reactive, bind will work to property
}
})
Use property binding if you want. :disabled prop is reactive in this case
<button :disabled="$refs.email ? $refs.email.$v.$invalid : true">Login</button>
But to check two fields i found no other way as dummy method:
<button :disabled="$refs.password ? checkIsValid($refs.email.$v.$invalid, $refs.password.$v.$invalid) : true">
{{data.submitButton.value}}
</button>
methods: {
checkIsValid(email, password) {
return email || password;
}
}
I was in a similar situation and I fixed it with:
data: () => {
return {
foo: null,
}, // data
And then you watch the variable:
watch: {
foo: function() {
if(this.$refs)
this.myVideo = this.$refs.webcam.$el;
return null;
},
} // watch
Notice the if that evaluates the existence of this.$refs and when it changes you get your data.
What I did is to store the references into a data property. Then, I populate this data attribute in mounted event.
data() {
return {
childComps: [] // reference to child comps
}
},
methods: {
// method to populate the data array
getChildComponent() {
var listComps = [];
if (this.$refs && this.$refs.childComps) {
this.$refs.childComps.forEach(comp => {
listComps.push(comp);
});
}
return this.childComps = listComps;
}
},
mounted() {
// Populates only when it is mounted
this.getChildComponent();
},
computed: {
propBasedOnComps() {
var total = 0;
// reference not to $refs but to data childComps array
this.childComps.forEach(comp => {
total += comp.compPropOrMethod;
});
return total;
}
}
Another approach is to avoid $refs completely and just subscribe to events from the child component.
It requires an explicit setter in the child component, but it is reactive and not dependent on mount timing.
Parent component:
<script>
{
data() {
return {
childFoo: null,
}
}
}
</script>
<template>
<div>
<Child #foo="childFoo = $event" />
<!-- reacts to the child foo property -->
{{ childFoo }}
</div>
</template>
Child component:
{
data() {
const data = {
foo: null,
}
this.$emit('foo', data)
return data
},
emits: ['foo'],
methods: {
setFoo(foo) {
this.foo = foo
this.$emit('foo', foo)
}
}
}
<!-- template that calls setFoo e.g. on click -->