I want to use contenteditable attribute of HTML for my component to make a area editable.
For this, I use #input directive but it does not work as I expect.
I expect the caret keeps the end of the entire input value but it moves to the head position.
Animation Gif Image
Demo
https://codesandbox.io/s/oj9p82kvp6
Code
<template>
<div>
<p contenteditable="true" #input="update">{{ text }}</p>
</div>
</template>
<script>
export default {
name: 'ReadWrite',
data () {
return {
text: 'edit here!'
}
},
methods: {
update (e) {
this.text = e.target.textContent
}
}
}
</script>
<style scoped>
:read-write {
border: solid 1px #cccccc;
padding: 5px;
}
</style>
So upon checking your sandbox code.
Whenever you edit the text inside the contenteditable p tag. The cursor moves to the first position. Assuming you don't really need to always show the cursor at the bottom and just want to fix this quirk. It's 2018 yet there is not yet existing a neat way of handling this. My two cents to solve this is to use the focusout event.
You may use the focusout directive instead.
<template>
<div>
<p contenteditable="true" #focusout ="update">{{ text }}</p>
</div>
</template>
<script>
export default {
name: 'ReadWrite',
data () {
return {
text: 'edit here!'
}
},
methods: {
update (e) {
this.text = e.target.textContent
console.log(this.text);
}
}
}
</script>
See it working here
https://codesandbox.io/s/0o9qow6zvp
Unless you need a two way binding here, this should do the work without a lot of nitty gritty codes just for something simple
Now the value will be bind to the text variable and the cursor will not move at the first position. Hence the jankiness will be gone.
Related
In the following code, codePen demo here
child component emits a custom event changedMsg to parent which changes msg data property on the parent component. Not sure, why changedMsg does not work. It does not modify msg property of parent.
Note: In a single file setup this works, but not in the setup below which is using template tag. Not sure, why?
VueJS
var child = {
template: '#child',
props: ['parentMsg'],
methods: {
changeParentMsg() {
console.log(this.parentMsg)
this.parentMsg = 'Message was changed by CHILD'
this.$emit('changedMsg', this.parentMsg)
}
}
}
new Vue({
el: '#parent',
data() {
return {
msg: 'Hello World'
}
},
components: {
child
},
methods: {
changeMsg() {
this.msg = 'Changed Own Msg'
}
},
})
HTML
<div>
<h4>Parent</h4>
<p>{{ msg }}</p>
<button #click="changeMsg">Change Own Message</button>
<br>
<div class="child">
<h4>Child</h4>
<child :parentMsg="msg" #changedMsg= "msg = $event"></child>
</div>
</div>
</div>
<template id="child">
<div>
<button #click="changeParentMsg">Change Parnets msg</button>
</div>
</template>
CSS
#parent {
background-color: lightblue;
border: 2px solid;
width: 300px;
}
.child {
background-color: lightgray;
border: 2px solid;
margin-top: 20px
}
Thanks
Note: In a single file setup this works, but not in the setup below which is using template tag. Not sure, why?
This is explained in the docs:
Event Names
Unlike components and props, event names will never be used as variable or property names in JavaScript, so there’s no reason to use camelCase or PascalCase. Additionally, v-on event listeners inside DOM templates will be automatically transformed to lowercase (due to HTML’s case-insensitivity), so v-on:myEvent would become v-on:myevent – making myEvent impossible to listen to.
For these reasons, we recommend you always use kebab-case for event names.
So, it's not a good practice to make the logic on the listem event directive, i made this way and worked:
<child :parentMsg="msg" #changed-msg="msgChanged"></child>
</div>
</div>
on the child tag i changed the #changedMsg to kebab-case so now it's #changed-msg and made it call a function instead of do the logic on the child tag, function called msgChanged, so you need to create it on your methods section:
methods: {
changeMsg() {
this.msg = 'Changed Own Msg'
},
msgChanged(param) {
console.log(param)
}
}
hope that helps
I am trying to add a hint or error message or some kind of validation (anything is acceptable) to a text field, using Vuetify.js. The hint should only appear if the user clicks on a "Search" button while the search field beside it is empty. I'm stuck trying to find a way to do it. My current code is below:
new Vue({
el: '#app',
data: () => ({
customSearchText: '',
validate: null
}),
computed: {
form() {
return {
validate: this.validate
}
}
},
watch : {
},
methods: {
searchCustomText() {
}
}
})
.v-input.v-text-field.theme--light {
width: 50%;
margin-left: 120px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.js"></script>
<div id="app">
<v-text-field hint="Fill the field to view results" append-icon="search"
v-model="customSearchText" #click:append="searchCustomText">
</v-text-field>
</div>
Link to Codepen : https://codepen.io/anon/pen/jJjZNQ
Hints only show when the text field is focused. So you were on the right track, you just need to focus the text field in the method searchCustomText which is called when the search icon is clicked. You will need to add a ref to the v-text-field so you can reference it in the method.
I also assumed you didn't want the hint when there is something in the text field, so I added an else to set the isFull to true.
Here is the codepen https://codepen.io/anon/pen/GeVQLG
My example linked below shows a very large list of a grid of divs (1,000 x 20)
and when clicking on one div, it will highlight only that one element. However,
it seems there is significant overhead by VueJS in the rerendering which introduces a lag when clicking.
<div class="row" v-for="row in rows">
<div v-for="column in row.columns" :class="{red: isHighlighted(row,column)}" #click.prevent="setHighlighted({row: row.id, column: column.id})">
<div>Value: {{column['value']}}</div>
</div>
</div>
Code Pen Example
Sure, you can speed it up by not doing something that requires an evaluation on every update. In your case, the class setting has to call a function for every box every time row or column changes.
I added the style as a member of the column, so that on highlighting, it can be found directly, rather than requiring each cell to notice the change to highlighted. However, this still didn't remove the lag.
After working on this for a while, I surmised that the :class setting was being re-evaluated on every update, even if it was not a function call. My earlier solution handled class-setting explicitly, and that avoided the :class issue. This solution uses a component for each cell, which avoids re-calculation because components only re-evaluate when their props change.
const store = new Vuex.Store({
state: {
rows: [],
rowCount: 2000,
highlighted: {
row: null,
column: null
}
},
getters: {
rows(state) {
return state.rows;
},
rowCount(state) {
return state.rowCount;
},
highlighted(state) {
return state.highlighted;
}
},
mutations: {
setRows(state, rows) {
state.rows = rows;
},
setHighlighted(state, highlighted) {
state.highlighted = highlighted;
}
}
});
new Vue({
el: "#app",
store,
data() {
return {
highlightedEntry: null,
highlightedEl: null
};
},
created() {
this.setRows(
Array.from(Array(this.rowCount).keys()).map(i => {
return {
id: i,
columns: Array.from(Array(parseInt(20)).keys()).map(j => {
return {
id: j,
value: Math.random().toPrecision(4),
isHighlighted: false
};
})
};
})
);
},
computed: {
...Vuex.mapGetters(["rows", "rowCount", "highlighted"])
},
components: {
aCell: {
props: ['rowId', 'column'],
template: '<div :class="{red: column.isHighlighted}" #click="highlight">Value: {{column.value}}</div>',
computed: {
style() {
return this.column.style;
}
},
methods: {
highlight() {
this.$emit('highlight', this.rowId, this.column);
}
}
}
},
methods: {
...Vuex.mapMutations(["setRows", "setHighlighted"]),
highlight(rowId, column) {
if (this.highlightedEntry) {
this.highlightedEntry.isHighlighted = false;
}
this.highlightedEntry = column;
column.isHighlighted = true;
this.setHighlighted({
row: rowId,
column: column.id
});
}
}
});
.row {
display: flex;
justify-content: space-between;
}
.row>* {
border: 1px solid black;
}
.red {
background-color: red;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.js"></script>
<div id="app">
<div>
Cells: {{rowCount * 20}}
</div>
<div class="row" v-for="row in rows" :key="row.id">
<div v-for="column in row.columns">
<a-cell :row-id="row.id" :column="column" #highlight="highlight"></a-cell>
</div>
</div>
</div>
If each of your items has a unique property like an id, pass that to :key="item.id":
<div v-for="column in row.columns" :key="column.id" ...
When Vue is updating a list of elements rendered with v-for, by default it uses an “in-place patch” strategy. If the order of the data items has changed, instead of moving the DOM elements to match the order of the items, Vue will patch each element in-place and make sure it reflects what should be rendered at that particular index. This is similar to the behavior of track-by="$index" in Vue 1.x.
This default mode is efficient, but only suitable when your list render output does not rely on child component state or temporary DOM state (e.g. form input values).
To give Vue a hint so that it can track each node’s identity, and thus reuse and reorder existing elements, you need to provide a unique key attribute for each item. An ideal value for key would be the unique id of each item. This special attribute is a rough equivalent to track-by in 1.x, but it works like an attribute, so you need to use v-bind to bind it to dynamic values (using shorthand here):
<div v-for="item in items" :key="item.id">
<!-- content -->
</div>
It is recommended to provide a key with v-for whenever possible, unless the iterated DOM content is simple, or you are intentionally relying on the default behavior for performance gains.
Since it’s a generic mechanism for Vue to identify nodes, the key also has other uses that are not specifically tied to v-for, as we will see later in the guide.
https://v2.vuejs.org/v2/guide/list.html#key
I am thinking how to implement validation on vue.js component.
The initial idea is that component validates and return an error code, like: "require", "min", "max" etc. Another component will display full text message according to this error code.
Because error message might not always display inside component's template. I need two separated components.
pseudo code is like this.
<div>
<mycomponent ref="salary" errcode="ErrorCode"></mycomponent>
</div>
.....
<div>
<errormessage watchcomponent="salary" message="salaryErrorMessages"></errormessage>
</div>
salaryErrorMessages is a hash of codes and messages. for example:
{"require":"please enter salary",
"min": "minimum salary value is 10000"}
Vue has ref attribute on component, . I don't know if I can use ref to reference a component in attribute.
Other solutions I considered:
add an error object in model of the page, and pass into using :sync binding. can monitor same object.
This requires to declare error messages in model.
If I consider the requirement that page also needs to know if there is an error before post back. a global error object might be necessary.
use event bus or Vuex.
This seems official solution, but I don't know .
When a page has multiple instances of , they will trigger same event. all instances will monitor same event.
You should definitely use Vuex. Not only it solves your problem, but it gives you huge scalability in your project.
You're working literally backwards. Vue is about passing data from parent to child, not exposing child properties to parent components.
add an error object in model of the page, and pass into using :sync binding. can monitor same object. This requires to declare error messages in model.
sync is gone from Vue v2, but the idea is fundamentally sort of correct: a parent component holds one object, child components get passed chunks of it as props, parent object automagically gets updated in the parent when it is changed in a child. It doesn't have to be the root component.
use event bus or Vuex. This seems official solution, but I don't know .
Vuex is pretty much always the Correct solution if you have an application that needs to manage a lot of state-related shenanigans.
When a page has multiple instances of , they will trigger same event. all instances will monitor same event.
Vue will very frequently pollute your console with warnings that data must be a function. This is why!
Here is an abridged version really shameful hackjob I inflicted on innocen made for an acquaintance recently:
In my defense, putting things in __proto__ is a very quick way to make them non-enumerable.
Vue.component('error', {
template: '#error',
props: ['condition', 'errorMessage']
})
Vue.component('comp', {
template: '#comp',
props: ['errorMessage', 'model'],
})
Vue.component('app', {
template: '#app',
data() {
return {
models: {
nameForm: {
firstName: '',
lastName: '',
__proto__: {
validator: function(value) {
return _(value).every(x => x.length > 2)
}
}
},
otherForm: {
notVeryInterestingField: 'There are words here',
veryImportantField: 0,
__proto__: {
validator: function(value) {
return value.veryImportantField > 20
}
}
},
__proto__: {
validator: function(value) {
return _(value).every(x => x.validator(x))
}
}
}
}
}
})
const vm = new Vue({
el: '#root'
})
.error {
background: orange
}
.alright {
background: mediumaquamarine
}
section > div, pre {
padding: 6px;
}
section {
flex: 1;
}
#root {
display: flex;
flex-direction: column;
}
<script src="https://unpkg.com/vue#2.1.6/dist/vue"></script>
<script src="https://unpkg.com/lodash"></script>
<template id="comp">
<div :class="[model.validator(model) ? 'alright' : 'error']" style="display: flex; border-bottom: 2px solid rgba(0,0,0,0.4)">
<section>
<div v-for="(field, fieldName) in model">
{{_.startCase(fieldName)}}:
<input v-model="model[fieldName]">
<br>
</div>
<error :condition="!model.validator(model)" :error-message="errorMessage"></error>
</section>
<section>
<pre>props: {{$options.propsData}}</pre>
</section>
</div>
</template>
<template id="error">
<div v-if="condition">
{{ errorMessage || 'Oh no! An error!' }}
</div>
</template>
<template id="app">
<div :class="[models.validator(models) ? 'alright' : 'error']">
<comp :model="model" error-message="Mistakes were made." v-for="model in models"></comp>
<pre>data: {{$data}}</pre>
</div>
</template>
<div id="root">
<app></app>
</div>
Is it possible to set a v-on:keyup.enter on the whole page, not only for an input element in javascript framework Vue.js ?
Perhaps a better way to do this is with a Vue component. This would allow you to control when you listen to events by including or not including the component. Then you could attach event listeners to Nuxt using the no-ssr component.
Here is how you create the component:
<template>
<div></div>
</template>
<script>
export default {
created() {
const component = this;
this.handler = function (e) {
component.$emit('keyup', e);
}
window.addEventListener('keyup', this.handler);
},
beforeDestroy() {
window.removeEventListener('keyup', this.handler);
}
}
</script>
<style lang="stylus" scoped>
div {
display: none;
}
</style>
Then on the page you want to use that component you'd add this HTML:
<keyboard-events v-on:keyup="keyboardEvent"></keyboard-events>
And then you'll have to add your event handler method:
methods: {
keyboardEvent (e) {
if (e.which === 13) {
// run your code
}
}
}
Short answer is yes, but how depends on your context. If you are using vue-router as I am on a current project, you would want to add to the outer-most element you want that applied to. In my case I'm using the actual app.vue entry point's initial div element.
There is one catch that I believe is a hard requirement, the element has to be within the potentially focusable elements. The way I'm dealing with that is setting a -1 tabindex and just declaring my super-hotkeys (mostly for debug purposes right now) on the parent element in my app.
<template>
<div
id="app-main"
tabindex="-1"
#keyup.enter="doSomething"
>
<everything-else></everything-else>
</div>
</template>
EDIT:
As a side note, I also added a touch of additional configuration to my vue-router to make sure the right element is focused when I transition pages. This allows the pageup/pagedown scrolling to already be in the right section based on the content area being the only scrollable section. You'd also have to add the tabindex="-1" to the app-content element as well.
router.afterEach(function (transition) {
document.getElementById('app-content').focus();
});
and the basis of my app-content component:
<template>
<div id="app-content" tabindex="-1">
<router-view
id="app-view"
transition="app-view__transition"
transition-mode="out-in"
></router-view>
</div>
</template>
I created a small npm module that takes care of global keypress events in Vue, hope it makes someone's life easier:
https://github.com/lupas/vue-keypress
My simplest approach:
Add into your root Vue component (or any other component):
new Vue({
//...
created() {
window.addEventListener('keypress', this.onKeyPress);
},
beforeDestroy() {
window.removeEventListener('keypress', this.onKeyPress);
},
methods: {
onKeyPress(e) {
console.log('KEYPRESS EVENT', e)
//... your code
}
}
//...
})
In Vue 3 composition API, you can do it with a composable:
import { onMounted, onUnmounted } from "vue";
export function useKeyupEvent(handler) {
onMounted(() => document.addEventListener("keyup", handler));
onUnmounted(() => document.removeEventListener("keyup", handler));
}
and then in your component setup:
useKeyupEvent( event => console.log(event))