Unable to set focus to a textarea, when the page loads - vue.js

I am trying to set the focus on a syncfusion textarea but I am unable to do so. I have used the this.$nextTick when the component mounts as defined here but the system still does not focus on the textarea.
I have added the same "focus to textarea" code in the created event because somehow the created event is triggered after the mounted event.
I have re-created the issue here.
I also see that this.$refs.vocabularies.$el returns input#vocabularies.e-control.e-textbox.e-lib.
What am I doing wrong?
<template>
<ejs-textbox cssClass="height:500px;" id='vocabularies' :multiline="true" placeholder="Enter your vocabularies" floatLabelType="Auto" :input= "inputHandler" v-model="vocabularies" ref="vocabularies"/>
</template>
<script>
import '#syncfusion/ej2-base/styles/material.css';
import '#syncfusion/ej2-vue-inputs/styles/material.css';
export default
{
data() {
return {
vocabularies: '',
inputHandler: (args) =>
{
args.event.currentTarget.style.height = "auto";
args.event.currentTarget.style.height = (args.event.currentTarget.scrollHeight)+"px";
},
}
},
mounted()
{
this.$nextTick(function()
{
this.$refs.vocabularies.$el.style.height = "auto";
this.$refs.vocabularies.$el.style.height = (this.$refs.vocabularies.$el.scrollHeight)+"px";
this.$refs.vocabularies.$el.focus();
console.log(`mounted run`);
});
},
async created()
{
this.$nextTick(function()
{
this.$refs.vocabularies.$el.style.height = "auto";
this.$refs.vocabularies.$el.style.height = (this.$refs.vocabularies.$el.scrollHeight)+"px";
this.$refs.vocabularies.$el.focus();
console.log(`created run`);
});
},
</script>

So, here's how I've solved it. I am not so sure regarding how good of an approach this is as I haven't worked with syncfusion, so can't say if there might be a better way.
<ejs-textbox cssClass="test" id='vocabularies' :multiline="true" placeholder="Enter your vocabularies" floatLabelType="Auto" :input= "inputHandler" v-model="vocabularies" ref="vocabularies"/>
Then in mounted I did
mounted() {
let a = document.getElementsByClassName('test')[0];
a.children[1].focus();
}

I was able to fix the issue by using this.$refs.vocabularies.focusIn(); in the mounted() method, based on the documentation here

You can focus the text area by using the focusIn public method of the TextBox component in the created event. Kindly refer the below code,
<ejs-textbox cssClass="height:500px;" id='vocabularies' :multiline="true" placeholder="Enter your vocabularies" floatLabelType="Auto" :input= "inputHandler" v-model="vocabularies" ref="vocabularies" :created='onCreated' />
onCreated:function(){
this.$refs.vocabularies.ej2Instances.focusIn()
}
Please find the sample from the below link,
Sample Link:
https://www.syncfusion.com/downloads/support/directtrac/general/ze/quickstart1111977605

Related

odoo15: How can I load js in qweb template?

I need to add events in some buttons in qweb template or maybe make a widget for this template. But I can't load js in this template, even if I add js file in web.assets_backend or web.assets_frontend.
controller.py
from odoo import http
class LogData(http.Controller):
#http.route("/log_data", type="http", auth="user")
def log_data_view(self, **kwargs):
return http.request.render(
"table_relation.log_data_template"
)
log_data_template.xml
<odoo>
<template id="log_data_template" name="Log Data">
<t t-call="web.layout">
<t t-set="head">
<t t-call-asssets="web.assets_common" t-js="false"/>
<t t-call-asssets="web.assets_frontend" t-js="false"/>
</t>
<div id="wrap" class="container">
<h1>Log Data</h1>
<div class="o_log_data">
<button id="start-log">日志</button>
<button id="cancel-log">停止</button>
<div id="log-content" style="height:500px;overflow: scroll;"/>
</div>
<button type="button" class="demo-btn">demo button</button>
</div>
</t>
</template>
</odoo>
log_data.js
odoo.define('log_data', function (require){
'use strict';
var publicWidget = require('web.public.widget');
console.log('==========')
publicWidget.registry.LogData = publicWidget.Widget.extend({
selector: '.o_log_data',
events: {
'click #start-log': '_startLog',
'click #cancel-log': '_cancelLog',
},
init: function () {
console.log('o_log_data')
},
start: function () {
console.log('o_log_data')
},
_startLog: function () {
console.log('_startLog')
},
});
publicWidget.registry.DemoBtn = publicWidget.Widget.extend({
selector: '.demo-btn',
events: {
click: '_onClick'
},
_onClick: function (e) {
console.log('_onClick')
},
});
})
manifest.py
'assets': {
'web.assets_backend': [
...
'table_relation/static/src/js/log_data.js',
]
'web.assets_frontend': [
...
'table_relation/static/src/js/log_data.js',
]
...
}
enter image description here
It seems not to load assets_backend bundle on this page, and log_data.js is not working.
As per the code you have mentioned above, it seems like you are trying to create a controller i.e a route that can be accessed by the User only but from the website side like the portal or eCommerce part.
So if that is the case, then you need to add your js files to web.assets_frontend instead of web.assets_backend in manifest.py file.
The answer is late but in case you still need this you can try the following code. It works in Odoo14 and should definitely work in Odoo15 as well since class Widget still have this statement:
// Now this class can simply be used with the following syntax::
// var myWidget = new MyWidget(this);
// myWidget.appendTo($(".some-div"));
You can find the reference here:
https://github.com/odoo/odoo/blob/f1f3fcef6cc471fc01da574da26712e643315da6/addons/web/static/src/legacy/js/core/widget.js#L49-L52
And your code refactored by following those instructions.
odoo.define('log_data', function (require){
'use strict';
var publicWidget = require('web.public.widget');
console.log('==========')
publicWidget.registry.LogData = publicWidget.Widget.extend({
selector: '.o_log_data',
events: {
'click #start-log': '_startLog',
'click #cancel-log': '_cancelLog',
},
init: function () {
console.log('o_log_data')
},
start: function () {
console.log('o_log_data')
},
_startLog: function () {
console.log('_startLog')
},
});
var LogData = new publicWidget.registry.LogData(this);
LogData.appendTo($(".o_log_data"));
publicWidget.registry.DemoBtn = publicWidget.Widget.extend({
selector: '.demo-btn',
events: {
click: '_onClick'
},
_onClick: function (e) {
console.log('_onClick')
},
});
var DemoBtn = new publicWidget.registry.DemoBtn(this);
DemoBtn.appendTo($(".demo-btn"));
});
I think that publicWidget are only rendered, as Krutarth Patel said, in the scope of some layout like 'website.layout'. I cannot provide much info about this because I still have to figure out the layouts, for example 'portal.layout' scope was not rendering the publicWidget extension for me.
But the Widget class is designed in a way that allows to 'force' render of the widget by inserting it into the dom, render, bound to specific selector and events.
So you can (probably?) automatically render by wrapping your template with the proper t-call, otherwise you can use the 'appendTo' syntax and append the widget to the DOM, make an istance out of it and use that istance.
I've been struggling with this for a couple of days before I could make it work, and I found a couple of post like this.
I hope this will help you, or other users to figure out some of the use you can make of odoo widgets.

Keep props updated with programatically generated components

Imagine an empty virtual bulletin board where an unknown number of virtual notes will be placed. The board is the parent component and the note is the child.
When I click the board a new note appears on the board. When I move the mouse the note should follow the mouse cursor (weird UI I know, but I'm simplifying for the sake of this post).
I'm generating a new note by instancing it and then adding it to the dom like this:
let NoteClass = Vue.extend(Note);
let note = new NoteClass({
propsData: { x: this.clientX, y: this.clientY },
});
note.$mount();
this.$refs.board.appendChild(note.$el);
Notice the mouse x/y is passed to the note via props. This causes the note to appear at the position of the mouse cursor when I click. Great.
However, once the Note is instanced it no longer updates the x/y props. The Note does not continuously read the position of the mouse cursor from its parent.
Here's the full code:
https://codesandbox.io/s/boring-wiles-pru1y?file=/src/App.vue
For comparison, check out this version where the Note is NOT generated in code. A single note is placed the typical way. It follows the cursor just fine:
https://codesandbox.io/s/proud-tree-xnthc?file=/src/App.vue
I found a way better solution thanks to Michal Levý's comment -- the data driven way:
<template>
<div ref="board" class="board" #click.self="onClick" #mousemove.prevent="drag">
<Note v-for="(note, key) in notes" :key="key" :x="clientX" :y="clientY"></Note>
</div>
</template>
<script>
import Note from '#/components/Note.vue';
export default {
name: 'Board',
data() {
return {
clientX: 0,
clientY: 0,
notes: []
}
},
components: {
Note
},
methods: {
onClick() {
this.notes.push({});
},
drag(event) {
this.clientX = event.clientX;
this.clientY = event.clientY;
}
}
}
</script>

Vue.js - Highmaps - Redraw map on series change

I have a highmaps 'chart' and the only thing that I want is to redraw the whole map inside an external function. Let me explain better. The map draws itself immediatly when the page loads up but I fetch some data from an external service and set it to a variable. Then I would like to just redraw the chart so that the new data appears in the map itself. Below is my code.
<template>
<div>
<highmaps :options="chartOptions"></highmaps>
</div>
</template>
<script>
import axios from 'axios';
import HighCharts from 'vue-highcharts';
import json from '../map.json'
let regions = [];
export default {
data: function () {
return {
chartOptions: {
chart: {
map: json, // The map data is taken from the .json file imported above
},
map: {
/* hc-a2 is the specific code used, you can find all codes in the map.json file */
joinBy: ['hc-key', 'code'],
allAreas: false,
tooltip: {
headerFormat: '',
pointFormat: '{point.name}: <b>{series.name}</b>'
},
series: [
{
borderColor: '#a0451c',
cursor: 'pointer',
name: 'ERROR',
color: "red",
data: regions.map(function (code) {
return {code: code};
}),
}
],
}
},
created: function(){
let app = this;
/* Ajax call to get all parameters from database */
axios.get('http://localhost:8080/devices')
.then(function (response) {
region.push(response.parameter)
/* I would like to redraw the chart right here */
}).catch(function (error){
console.error("Download Devices ERROR: " + error);
})
}
}
</script>
As you can see I import my map and the regions variable is set to an empty array. Doing this results in the map having only the borders and no region is colored in red. After that there is the created:function() function that is used to make the ajax call and retrieve data. After that I just save the data pushing it into the array and then obviously nothing happens but I would like to redraw the map so that the newly imported data will be shown. Down here is the image of what I would like to create.
If you have any idea on how to implement a thing like this or just want to suggest a better way of handling the problem, please comment.
Thanks in advance for the help. Cheers!
After a few days without any answer I found some marginal help online and came to a pretty satisfying conclusion on this problem so I hope it can help someone else.
So the first thing I did was to understand how created and mounted were different in Vue.js. I used the keyword created at first when working on this project. Because of that, inside this function, I placed my ajax call that gave me data which I then loaded inside the 'chart' by using the .addSeries method of the chart itself.
To reference the chart itself I used this: let chart: this.$refs.highcharts.chart. This searches for the field refs in any of your components/html elements and links it to the variable. So in the html there was something like this:
<template>
<div>
<highmaps :options="chartOptions" ref="highcharts"></highmaps>
</div>
</template>
The real problem was that the chart didn't even start rendering while all this process was going on so I changed the created keyword with mounted which means that it executes all the code when all of the components are correctly mounted and so my chart would be already rendered.
To give you (maybe) a better idea of what I am talking about I will post some code down below
mounted: function(){
let errorRegions = [];
let chart = this.$refs.highcharts.chart;
axios.get('localhost:8080/foo').then(function(response)
{
/* Code to work on data */
response.forEach(function(device){
errorRegions.push(device);
}
chart.addSeries({
name: "ERROR",
color: "red",
data: errorRegions
}
/* ...Some more code... */
})
}
And this is the result (have been adding some more series in the same exact manner)
Really hoping I have been of help to someone else. Cheers!

Twitter typeahead.js not working in Vue component

I'm trying to use Twitter's typeahead.js in a Vue component, but although I have it set up correctly as tested out outside any Vue component, when used within a component, no suggestions appear, and no errors are written to the console. It is simply as if it is not there. This is my typeahead setup code:
var codes = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('code'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
prefetch: contextPath + "/product/codes"
});
$('.typeahead').typeahead({
hint: true,
highlight: true,
minLength: 3
},
{
name: 'codes',
display: 'code',
source: codes,
templates: {
suggestion: (data)=> {
return '<div><strong>' + data.code + '</strong> - ' + data.name + '</div>';
}
}
});
I use it with this form input:
<form>
<input id="item" ref="ttinput" autocomplete="off" placeholder="Enter code" name="item" type="text" class="typeahead"/>
</form>
As mentioned, if I move this to a div outside Vue.js control, and put the Javascript in a document ready block, it works just fine, a properly formatted set of suggestions appears as soon as 3 characters are input in the field. If, however, I put the Javascript in the mounted() for the component (or alternatively in a watch, I've tried both), no typeahead functionality kicks in (i.e., nothing happens after typing in 3 characters), although the Bloodhound prefetch call is made. For the life of me I can't see what the difference is.
Any suggestions as to where to look would be appreciated.
LATER: I've managed to get it to appear by putting the typeahead initialization code in the updated event (instead of mounted or watch). It must have been some problem with the DOM not being in the right state. I have some formatting issues but at least I can move on now.
The correct place to initialize Twitter Typeahead/Bloodhound is in the mounted() hook since thats when the DOM is completely built. (Ref)
Find below the relevant snippet: (Source: https://digitalfortress.tech/js/using-twitter-typeahead-with-vuejs/)
mounted() {
// configure datasource for the suggestions (i.e. Bloodhound)
this.suggestions = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('title'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
identify: item => item.id,
remote: {
url: http://example.com/search + '/%QUERY',
wildcard: '%QUERY'
}
});
// get the input element and init typeahead on it
let inputEl = $('.globalSearchInput input');
inputEl.typeahead(
{
minLength: 1,
highlight: true,
},
{
name: 'suggestions',
source: this.suggestions,
limit: 5,
display: item => item.title,
templates: {
suggestion: data => `${data.title}`;
}
}
);
}
You can also find a working example: https://gospelmusic.io/
and a Reference Tutorial to integrate twitter typeahead with your VueJS app.

Vue.JS value tied on input having the focus

Is there a way to change a value in the model when an input gets/loses focus?
The use case here is a search input that shows results as you type, these should only show when the focus is on the search box.
Here's what I have so far:
<input type="search" v-model="query">
<div class="results-as-you-type" v-if="magic_flag"> ... </div>
And then,
new Vue({
el: '#search_wrapper',
data: {
query: '',
magic_flag: false
}
});
The idea here is that magic_flag should turn to true when the search box has focus. I could do this manually (using jQuery, for example), but I want a pure Vue.JS solution.
Apparently, this is as simple as doing a bit of code on event handlers.
<input
type="search"
v-model="query"
#focus="magic_flag = true"
#blur="magic_flag = false"
/>
<div class="results-as-you-type" v-if="magic_flag"> ... </div>
Another way to handle something like this in a more complex scenario might be to allow the form to track which field is currently active, and then use a watcher.
I will show a quick sample:
<input
v-model="user.foo"
type="text"
name="foo"
#focus="currentlyActiveField = 'foo'"
>
<input
ref="bar"
v-model="user.bar"
type="text"
name="bar"
#focus="currentlyActiveField = 'bar'"
>
...
data() {
return {
currentlyActiveField: '',
user: {
foo: '',
bar: '',
},
};
},
watch: {
user: {
deep: true,
handler(user) {
if ((this.currentlyActiveField === 'foo') && (user.foo.length === 4)) {
// the field is focused and some condition is met
this.$refs.bar.focus();
}
},
},
},
In my sample here, if the currently-active field is foo and the value is 4 characters long, then the next field bar will automatically be focused. This type of logic is useful when dealing with forms that have things like credit card number, credit card expiry, and credit card security code inputs. The UX can be improved in this way.
I hope this could stimulate your creativity. Watchers are handy because they allow you to listen for changes to your data model and act according to your custom needs at the time the watcher is triggered.
In my example, you can see that each input is named, and the component knows which input is currently focused because it is tracking the currentlyActiveField.
The watcher I have shown is a bit more complex in that it is a "deep" watcher, which means it is capable of watching Objects and Arrays. Without deep: true, the watcher would only be triggered if user was reassigned, but we don't want that. We are watching the keys foo and bar on user.
Behind the scenes, deep: true is adding observers to all keys on this.user. Without deep enabled, Vue reasonably does not incur the cost of maintaining every key reactively.
A simple watcher would be like this:
watch: {
user() {
console.log('this.user changed');
},
},
Note: If you discover that where I have handler(user) {, you could have handler(oldValue, newValue) { but you notice that both show the same value, it's because both are a reference to the same user object. Read more here: https://github.com/vuejs/vue/issues/2164
Edit: to avoid deep watching, it's been a while, but I think you can actually watch a key like this:
watch: {
'user.foo'() {
console.log('user foo changed');
},
},
But if that doesn't work, you can also definitely make a computed prop and then watch that:
computed: {
userFoo() {
return this.user.foo;
},
},
watch: {
userFoo() {
console.log('user foo changed');
},
},
I added those extra two examples so we could quickly note that deep watching will consume more resources because it triggers more often. I personally avoid deep watching in favour of more precise watching, whenever reasonable.
However, in this example with the user object, if all keys correspond to inputs, then it is reasonable to deep watch. That is to say it might be.
You can use a flat by determinate a special CSS class, for example this a simple snippet:
var vm = new Vue({
el: '#app',
data: {
content: 'click to change content',
flat_input_active: false
},
methods: {
onFocus: function(event) {
event.target.select();
this.flat_input_active = true;
},
onBlur: function(event) {
this.flat_input_active = false;
}
},
computed: {
clazz: function() {
var clzz = 'control-form';
if (this.flat_input_active == false) {
clzz += ' only-text';
}
return clzz;
}
}
});
#app {
background: #EEE;
}
input.only-text { /* special css class */
border: none;
background: none;
}
<!-- libraries -->
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<!-- html template -->
<div id='app'>
<h1>
<input v-model='content' :class='clazz'
#focus="onFocus($event)"
#blur="onBlur"/>
</h1>
<div>
Good luck
You might also want to activate the search when the user mouses over the input - #mouseover=...
Another approach to this kind of functionality is that the filter input is always active, even when the mouse is in the result list. Typing any letters modifies the filter input without changing focus. Many implementations actually show the filter input box only after a letter or number is typed.
Look into #event.capture.