Intialize Vue data with the result of an AJAX call - javascript

I need to initialize a Vue component's data with the result of an AJAX call. I tried the following:
data: function () {
return {
supplierCount: 0
}
},
created: function () {
axios.get("/supplier/list").then(response => {
this.supplierCount = response.data.length;
});
}
However, this approach doesn't work, because the template can access the data before the AJAX handler updates supplierCount.
What's the correct way to initialize the data with the result of an asynchronous call? For example, if I return a promise (instead of an object) from data, will Vue wait until the promise is rejected/resolved before exposing the data to the template?

I don't think you can force your component to initialize only after the ajax call, but you can configure it to be hidden before the ajax data is loaded, either by hiding it with css (using v-show) or by simply preventing its rendering (using v-if).
For example, you can add a property hasLoaded to your component, and bind either v-show or v-if to it, like this:
On your js:
data: function () {
return {
supplierCount: 0,
hasLoaded: false
}
},
created: function () {
axios.get("/supplier/list").then(response => {
this.supplierCount = response.data.length;
this.hasLoaded = true;
});
}
On your template:
<!-- The top element is your root element, and you should always render it, so the v-show is appended to the immediate child -->
<div>
<div v-show="hasLoaded">
<!-- the rest of your template goes here -->
</div>
</div>

Render your template html conditionally.
That's the best way I believe - Nothing get added to DOM if condition fails.
<div v-if="supplierCount">
</div>

Related

Odoo field doesn't update when changed from javascript with an RPC call

I want to be able to change the context of a one2many field (work_unit) programatically to modify the default value of one of its fields (product_id).
Ideally I would like to change the o2m context directly from my widget, but I haven't had any success doing that, the view doesn't acknowledge any changes I make from javascript.
Current approach: I have another field selected_chapter which I pass through context as the default for work_unit.product_id. This works fine: when I change selected_chapter manually, the o2m context picks up the new default for the field product_id.
Now, I want to be able to modify selected_chapter programatically from a widget in javascript.
I do this by calling a python method with an _rpc() call from my widget, and it works, but the view doesn't update selected_chapter until I save the record which defeats the purpose of the call.
Widget code:
ListRenderer.include({
...
_setSelectedChapter: function () {
var self = this;
this.trigger_up('mutexify', {
action: function () {
return self._rpc({
model: 'sale.order',
method: 'set_selected_chapter',
args: [
[self.res_id]
],
kwargs: {
chapter_id: self.filter.getSelected()
},
}).then(function (result) {
console.log("res", result);
self._render();
});
},
});
},
...
})
Model code:
selected_chapter = fields.Many2one('product.product')
#api.multi
def set_selected_chapter(self, chapter_id):
chapter = self.env['product.product'].browse(chapter_id)
if not chapter.exists():
return
# I've also tried using self.update(), same results
self.selected_chapter = chapter
View code:
<field name="work_unit" mode="tree,kanban" filter_field="product_id" context="{'default_product_id': selected_chapter}">
First, rename work_unit to work_unit_ids.
Then, on the server side write an onchange method. See https://www.odoo.com/documentation/12.0/reference/orm.html#onchange-updating-ui-on-the-fly

calling vue function from js on change

I'm trying to make a call from a on.("change") event to a vue method and that works fine but trying to give the received data from the change event to a Vue variable, the console log says that the variable has the new data, but it doesn't really change the variable correctly, it changes the last variable when you duplicate the components.
here is some of my code:
Vue.component('text-ceditor',{
props:['id'],
data: function (){
return {
dataText: "this is something for example"
}
},
template: '#text-ceditor',
methods: {
setData: function(data){
console.log(data)
this.dataText = data
console.log(this.dataText)
}
},
mounted: function(){
CKEDITOR.replace(this.$refs.text);
self = this;
CKEDITOR.instances.texteditor.on('change', function() {
self.setData(this.getData())
})
}
})
the component works correctly but the variable just change the last one
here is the fiddle: https://jsfiddle.net/labradors_/3snmcu84/1/
Your problem isn't with Vue but with CKEDITOR and its instances (with the ids you defined in the template and the way you reference them).
First problem is that you're duplicating ids in the text-ceditor component:
<textarea ref="text" v-model="dataText" id="texteditor" rows="10" cols="80"></textarea>
Why do we need to fix this? Because CKEDITOR instances in Javascript are id-based.
So now we need to change the id attribute to use the one passed in the component's props, like this:
<textarea ref="text" v-model="dataText" :id="id" rows="10" cols="80"></textarea>
Once we took care of that, let's reference the correct CKEDITOR instance from within the mounted method in the component.
We want to reference the one that matches with the id in our component.
From:
mounted: function(){
CKEDITOR.replace(this.$refs.text);
self = this;
CKEDITOR.instances.texteditor.on('change', function() {
self.setData(this.getData())
})
}
To:
mounted: function () {
CKEDITOR.replace(this.$refs.text);
var self = this;
var myCKEInstance = CKEDITOR.instances[self.id]
myCKEInstance.on('change', function () {
self.dataText = myCKEInstance.getData()
})
}
Notice that I also removed the call to setData as there is no need to have it and also declared self as a variable avoiding the global scope (which would overwrite it everytime and reference the latest one in all different components).
Now everything is updating correctly, here's the working JSFiddle.

Execute a nested function in vue

I have two functions that are nested in vue, the parent function is supposed to get the value of an attribute, while the child is supposed to use the value of the attribute to make an api call. How can I execute this function once to ensure I get this attribute and make the api call at once?
//button with the attribute I want
<button :data-post-id="My id">Click Me</button>
//Here I'm calling the parent function
<button #click="getPostId">Submit to api</button>
Javascript
getPostId: function (evt) {
const postId = evt.target.getAttribute('data-postid');
//console.log(postId);
function usePostId(){
console.log("I am accessible here " +postId)//null
}
return usePostId()
}
Your approach will create function multiple time, Just start with the simple function and keep separate.
new Vue({
el: '#app',
data: {
postid: ''
},
methods:{
setPostId: function (id){
this.postid = id;
},
getPostId: function () {
console.log(this.postid);
}
}
})
<script src="https://npmcdn.com/vue/dist/vue.js"></script>
<div id="app">
<button #click="setPostId(11)">Set 11</button>
<button #click="setPostId(22)">Set 22</button>
<button #click="setPostId(33)">Set 33</button>
<button #click="getPostId">Get postid</button>
<div>{{postid}}</div>
</div>
I am no vue expert but I can spot one inconsistency.
You are binding your callback to child but set the attr data-post-id on parent and then expecting child to have that attr. Also, it seems the attribute name doesn't match i.e. what you have set and what you are trying to get.
As for the original problem, i am not sure why you didn't add the attribute to child element as well and in case you can't do that you will need to find the desired parent through DOM.
#mots you could do something like the below,
usePostId: function(id) {
console.log("I am accessible here " + id)
},
getPostId: function(evt) {
const postId = evt.target.getAttribute('data-post-id')
const output = this.usePostId(postId)
return output
}

Knockout component dispose not called

I have a custom KO component address-input
ko.components.register('address-input', {
viewModel: { createViewModel: function ({}, componentInfo) {
var self = {};
self.dispose = function() {
// When removed by KO, dispose computeds and subscriptions
};
return self;
}},
template: 'address-input'
});
The corresponding template is address-input.html
<div class`enter code here`="clearfix row">
<!-- elements come here -->
</div>
My application is an SPA one whose basic layout will be like below
A main.html will contain section.html which inturn holds address-input,html. On page nav , section.html will be replaced by another html and so on.
The section htmls are loaded through AJAX
$j.ajax({
url: url,
success: function(htmlText) {
var $el = $j(element);
$el.html(htmlText);
ko.applyBindingsToDescendants(bindingContext, $el[0]);
},
cache: false,
mimeType: 'text/html-ko'
});
I might have some observables subscribed in the address-input component in future. When that happens i would like the dispose method called when navigating away from the page. But it is not happening now. What is wrong here? Is it a case of DOM not getting removed from memory? If it is so , why?
You're using jQuery to replace a part of the DOM tree. Knockout has no way of knowing which elements are removed and cannot call dispose on the bound models.
Use knockout's html binding to add/remove the new section or (not recommended) call ko.cleanNode(element) before calling $el.html.
An example that shows:
When you manually remove a component from the DOM, knockout isn't notified and cannot call dispose
When you use a regular binding to alter the DOM (e.g. foreach, if, with) knockout does call dispose when stuff has to be removed
When you call ko.cleanNode, knockout detaches all nodes from their models, calls dispose, and let's you do what you want with the remaining DOM nodes.
ko.components.register('mycomponent', {
viewModel: function(params) {
this.dispose = () => console.log("Dispose called");
},
template: "<li>My Component</li>"
});
// Some example data to render a list
const comps = ko.observableArray([1, 2, 3, 4]);
// Remove straigt from the DOM without knockout...
const badRemove = () => document
.querySelector("mycomponent:last-child")
.remove();
const manualDetach = () => ko.cleanNode(document.querySelector("div"));
// Use knockout to alter the DOM
const goodRemove = () => comps.shift();
ko.applyBindings({ comps, badRemove, goodRemove, manualDetach });
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div data-bind="foreach: comps">
<mycomponent></mycomponent>
</div>
<button data-bind="click: badRemove">bad remove</button>
<button data-bind="click: goodRemove">good remove</button>
<button data-bind="click: manualDetach">clean node</button>

jquery.get function url parameter

in the jquery.get() function, the first parameter is URL, is that the url to the content I want to retrieve or to the Controller/action method.
The problem is I'm building an asp.net-mvc application and I'm not sure what to pass as this parameter. Right now I'm passing my partialview.cshtml but nothing is being returned, not sure if I'm doing this right or wrong.
Here's what I got
<div id="editor_box"></div>
<button id="add_button">add</button>
<script>
var inputHtml = null;
var appendInput = function () {
$("#add_button").click(function() {
if (!inputHtml) {
$.get('AddItem.cshtml', function (data) {
inputHtml = data;
$('#editor_box').append(inputHtml);
});
} else {
$('#editor_box').append(inputHtml);
}
})
};
</script>
also, what is the second parameter "function (data)" is this the action method?
You need to remove var appendInput = function () { from the script. You are defining a function but never calling it. Just use the following (update you action and controller) names
<script>
var inputHtml = null;
$("#add_button").click(function() {
if (!inputHtml) {
$.get('#Url.Action("SomeAction", "SomeController")'', function (data) {
inputHtml = data;
$('#editor_box').append(inputHtml);
});
} else {
$('#editor_box').append(inputHtml);
}
});
</script>
Edit
Based on your script you appear to be requiring the content only once (you then cache it and add it again on subsequent clicks. Another alternative would be to render the contents initially inside a hidden <div> element, then in the script clone the contents of the <div> and append it to the DOM
<div id="add style="display:none">#Html.Partial("AddItem")</div>
$("#add_button").click(function() {
$('#editor_box').append($('add').clone());
});
The first argument to $.get is the URL which will respond with the expected data. jQuery/JavaScript don't care what kind of server side architecture you have or the scripting language. Whether the URL looks like a file AddItem.cshtml or a friendly route like /User/Sandeep, it doesn't matter as far as the client side is concerned.
In the case of ASP.NET, your URL endpoint can be generated like so:
$.get('#Url.Action("SomeAction", "SomeController")', function (data) {

Categories

Resources