calling vue function from js on change - javascript

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.

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

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
}

Meteor template not updating in JQuery callback

I'm trying to get a div to fade out and then change a session variable which is being used in the template. The session variable is being successfully changed in the callback function but the template is not reactively updating.
The following does not reactively update the template. (These are trigger)
$(event.target.parentNode).find(subclass)
.fadeOut("slow", function() {
Session.set(this.valueOf() + "_show_exercise_fields", set_show_exercise_fields);
The following does reactively update the template.
Session.set(this.valueOf() + "_show_exercise_fields", set_show_exercise_fields);
$(event.target.parentNode).find(subclass)
.fadeOut("slow", function() {
// do nothing
});
Is there a way to force the template to re render or a better way to do what I am trying to do. Thanks
EDIT 1
Below is the entire function
Template.exercise.events({
'click .exercise-name': function(event) {
var subclass = ".exercise-fields-container";
var set_show_exercise_fields = false;
if (!Session.get(this.valueOf() + "_show_exercise_fields")) {
var subclass = ".exercise-options-container";
var set_show_exercise_fields = true;
}
// find the subclass (either the fields container or the options
// container) and fade out
$(event.target.parentNode).find(subclass)
.fadeOut("slow", function() {
Session.set(this.valueOf() + "_show_exercise_fields", set_show_exercise_fields);
});
}
});
Template.exercise.helpers({
show_fields: function() {
Session.setDefault(this.valueOf() + "_show_exercise_fields", true);
return Session.get(this.valueOf() + "_show_exercise_fields");
}
});
Below is the template
<template name="exercise">
<div class="exercise-name">
{{this.name}}
</div>
{{#if show_fields}}
Fields
{{else}}
Options
{{/if}}
</template>
Event handlers aren't reactive contexts. You can create a reactive context using Tracker.autorun().
If you use a session variable within the function you pass to autorun, the entire function will rerun whenever the session variable is changed. In this context you could fade in or out as you desire.
I had a scenario where a collection was being updated, so I had to re-build my select element using Materialize Select
Here's what my on rendered function looks like. The autorun knows that Channels is a reactive data source and re-runs the autorun function when this data source changes.
Channels = new Mongo.Collection("channels");
Template.channelSelectController.onRendered(function(){
var self = this;
this.autorun(function(){
var count = Channels.find().count();
self.$('select').material_select();
});
});

Binding sub-properties with Google Polymer

Is it possible to bind sub-properties automatically with Google Polymer? Something like AngularJS.
Here is a small example:
This is the element that contains the property prop1 and updates it after 2sec:
<dom-module is="test-elem">
<script>
TestElem = Polymer({
is: "test-elem",
properties: {
prop1: { type: String, value: "original value", notify: true }
},
factoryImpl: function() {
var that = this;
setTimeout(function(){
that.prop1 = "new value";
}, 2000);
}
});
</script>
</dom-module>
And this is the main page, which creates an element and show prop1 in the dom:
<template is="dom-bind" id="main-page">
<span>{{test.prop1}}</span>
</template>
<script>
var scope = document.getElementById('main-page');
var t = new TestElem();
scope.test = t;
</script>
Unfortunately, the page doesn't update with the new value.
Is there a way to bind this automatically?
Here is a jsfiddle: http://jsfiddle.net/xkqt00a7/
The easiest way I found was to add an event handler:
t.addEventListener('prop1-changed', function(e){
scope.notifyPath('test.prop1', e.currentTarget.prop1)
});
http://jsfiddle.net/83hjzoc3/
But it's not automatic like AngularJS.
I can't explain why, but switching your example from setTimeout to this.async makes it work.
Binding it as a sub property of the page scope doesn't work automatically because you need to manually call notifyPath('test.prop1', value) on the binding scope. One way to do that is to pass the binding scope to the TestElem constructor. Note that you don't need to worry about that if you bind to a parent element automatically. In that latter case the update is automatic.
The following works and shows two binding, and update, methods. One is automatic, the other requires a notifyPath call on the binding scope passed to the constructor.
TestElem = Polymer({
is: "test-elem",
properties: {
prop1: {
type: String,
value: "original value",
notify: true
}
},
ready: function() {
var that = this;
setTimeout(function () {
that.prop1 = "new value (via ready)";
}, 2000);
},
factoryImpl: function (app) {
setTimeout(function () {
app.notifyPath('test.prop1', 'new value (via app scope)');
}, 2000);
}
});
.. then reference the element one of two ways:
<test-elem prop1="{{prop1value}}">{{prop1value}}</test-elem>
<div>{{test.prop1}}</div>
<script>
app.test = new TestElem(app);
</script>
Where "app" is the main page's global binding scope (e.g. a as the a top level element in index.html, as is done in the polymer starter kit demo application).
After two seconds both properties update and the automatically updated html appears follows:
new value (via ready)
new value (via app scope)

Wrap a JavaScript function reference

I have the following JavaScript code, which works as expected...
/** #jsx React.DOM */
var TreeView = React.createClass({
render: function() {
return <div ref="treeview"></div>;
},
componentDidMount: function() {
console.log(this.props.onChange);
var tree = $(this.refs.treeview.getDOMNode()).kendoTreeView({
dataSource: ...,
dataTextField: "Name",
select: this.props.onChange
}
});
}
});
var MyApp = React.createClass({
onChange: function(e) {
console.log(e);
this.setState({key: e});
},
render: function() {
return (
<div>
<TreeView onChange={this.onChange}/>
<GridView />
</div>
);
}
});
However, with the kendo treeview, on selecting a tree node, the whole node is passed. To get at the underlying key, I would need to process the node as follows:
select: function(e) {
var id = this.dataItem(e.node).id;
this.props.onChange(id);
}
However I've obviously not quite got it right since, and here please excuse my noobness, it seems that in the working instance a reference to the function is being used, whereas in the non-working instance, the function is actually being executed... Or something like that: the error message being returned is:
Cannot call method 'onChange' of undefined.
So what would I need to do to be able to reference the function which extracts the key before calling the onChange method? Note that, if my understanding is correct, onChange needs to be executed in the context of the MyApp class so that any child components will get notified on the change.
EDIT: I've tried using partial application but am still not quite there. I've updated the onChange routine to take a function which returns the key from the node
onChange: function(getKey, e) {
this.setState({Key: getKey(e)});
},
But am not sure how to wire it all up.
Your code looks mostly right. I believe your only problem is that the select callback you're passing to the treeview is being called with the wrong scope. Note that you're using this to mean two different things within the function (once for the actual tree view object and the other for the React component). Easiest solution is probably to store a reference to the onChange function like so:
componentDidMount: function() {
var onChange = this.props.onChange;
var tree = $(this.refs.treeview.getDOMNode()).kendoTreeView({
dataSource: ...,
dataTextField: "Name",
select: function(e) {
var id = this.dataItem(e.node).id;
onChange(id);
}
});
}
Hope this helps.

Categories

Resources