How do I do a use an action like opening an email composer in odoo13 with only javascript outside of the odoo library environment? - odoo-13

I have a simple question.
I am using odoo13 and I utilize my own javascript libraries. In other words I don't rely only on the odoo libraries.
I want to add an action on a button and do simple stuff. For example: I want to click a button and open the mail composer just as it is done in "Sales Order / Send by email" button.
So I use jQuery for example and do something like this:
$('.something').on('click', function(){
var el = $(this);
var myConfig = {
'name': 'Compose Email',
'type': 'ir.actions.act_window',
'res_model': 'mail.compose.message',
'views': [(false, 'form')],
'view_id': false,
'target': 'new',
'context': {}
};
var action = new WhatMustIDoHere('?????');
action.run(myConfig);
// the config above is supposed to be passed to the action and cause mail composer to pop-up
});
It is completely outside of what it is typically associated with:
odoo.define('my_module.my_extended_action', function (require) {
'use strict';
...blah blah blah
});
... which requires a whole bunch of templating and what not.
The same code works as follows: You can add something like this inside a view:
<button type="object" name"action_myActionNameWhichIsInPython">Click me</button>
... and then inside python:
#api.model
def action_myActionNameWhichIsInPython(self):
return {
name': 'Compose Email',
'type': 'ir.actions.act_window',
'res_model': 'mail.compose.message',
'views': [(False, 'form')],
'view_id': False,
'target': 'new',
'context': {}
}
... and the whole lot works! But that is NOT what I want to do. I want to do the jQuery way because I have dynamically generated content with a whole bunch of libraries and what not which do not rely on the odoo environment.
If you look at my code and if someone can point me to the right direction to replace "WhatMustIDoHere('?????')" I would truly appreciate it.

So I managed to do something that seems to work. I am not sure if it is the right or nicest way to do it, but I would like to share it for other aspiring programmers.
I added an extension and I created a global function:
// set the action element functionality
odoo.define('my_module.whatever', function (require) {
"use strict";
var holder = require('web.SystrayMenu');
var obj = require('web.Widget');
var ext = obj.extend({
template:'my_module._odoo_action_element', // This template is added in the templates file and it will be used globally
events: {
"click": "on_click",
},
on_click: function (e) {
var el = $(e.currentTarget);
var action = el.data('_action'); // This is added by the function marked as "_odoo_action"
if (action){
this.do_action(action);
}
},
});
// add the element to the systray menu so that it can be available globally
holder.Items.push(_utils.var.erp_systray);
// create an external function to be used later
window._odoo_action = function(action){
$('._odoo_action_element').data('_action', action).click();
}
});
Then I added the template which will be injected inside the systrayMenu. It is hidden so it won't be visible.
<templates xml:space="preserve">
<t t-name="my_module._odoo_action_element">
<div class="_odoo_action_element" style="display: none !important;">
</div>
</t>
</templates>
Lastly, whenever I want to use it I can do something like this:
$('.some_element').on('click', function(){
window._odoo_action({
'name': 'My title',
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'mail.compose.message',
'views': [(false, 'form')],
'view_id': false,
'target': 'new',
'context': context,
});
});

Related

Vuetify v-form post does not send data

Forgive me for the English of the Translator :)
I created a basic form to see if I get data in my API using vuetify however, when submitting the data the v-select data is not sent and I can not understand the reason, since in general the examples of these forms do not really make a request POST, follows snippets of the code I'm using:
<v-form method="post" action="http://127.0.0.1:3000/produtos">
<v-text-field name="escola" v-model="name" required :rules="nameRules"></v-text-field>
<v-select
v-model="selectPessoa"
:items="pessoas"
:rules="[v => !!v || 'Item is required']"
item-value="id"
item-text="nome"
label="itens"
required
name="pessoa"
return-object
value="id"
></v-select>
<v-btn color="warning" type="submit">Submit</v-btn>
</v-form>
Excerpt from javascript code:
data(){
return { pessoas: [{ id: 1, nome: "sandro" },
{ id: 2, nome: "haiden" }],
name: '',
selectPessoa: null,
}
}
The information I type in the v-text-field I get in the API node, but the one in the v-select does not:
Form screen:
API log screen:
On the<v-select> component you have defined the return-object and item-value="id" props. Using the return-object is overriding the item-value by returning the entire object from the v-select component instead of just the id. In this case you could just remove the return-object prop from the <v-select> component and that will fix your issue.
<v-select
v-model="selectPessoa"
:items="pessoas"
:rules="[v => !!v || 'Item is required']"
item-value="id"
item-text="nome"
label="itens"
required
name="pessoa"
return-object <------REMOVE THIS!!!
value="id"
></v-select>
Vuetify v-select docs: https://vuetifyjs.com/en/components/selects
Another option instead of removing the return-object prop could be to modify your API endpoint to expect an object rather than an int.
Also, I would not recommend using the "method" and "action" attributes on the <v-form> component. Instead, put a click event handler on the submit button of the form that calls a method. The method should then grab the data and send it to the API endpoint via an AJAX call.
On the Form Component
Before: <v-form method="post" action="http://127.0.0.1:3000/produtos">
After: <form #submit.prevent>
On the Button Component
Before: <v-btn color="warning" type="submit">Submit</v-btn>
After: <v-btn color="warning" #click="submit">Submit</v-btn>
In the methods have a function do something like this (used axios in my example, not sure what your project is using):
methods: {
submit () {
let data = { name: this.name, selectPessoa: this.selectPessoa }
axios.post('http://127.0.0.1:3000/produtos', data)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
}

Best way to invoke a function in a child component from the parent?

I have a ractive component that needs to performs a specific function, when a user asks it to. I.e., not when the component is being initialised, but some time afterwards.
The ractive docs mention events are used to communicate between components and their parents but only in the context of events bubbling from the child to the parent. In this case, we want the parent to invoke something on the child.
Alternatively, there's ractive.findComponent() but the examples don't seem to use it much for ommunicating from the parent to the child.
I've made the following snippet which uses findComponent() and works (click 'Run Code Snippet' then 'Full Page'). However I'm not sure it's optimal:
var coolWidget = Ractive.extend({
data: {},
template: `<span>Your favorite fruit is {{ fruit }}</span>`,
data: function(){
return {
fruit: 'banana',
doCoolThing: function(){
var component = this;
console.log('Going to change fruit...')
setTimeout(function(){
component.set('fruit', 'avocado')
}, 3 * 1000)
}
}
}
})
var ui = new Ractive({
el: '.some-ui',
data: { name: 'Mike'},
components: {
coolWidget
},
template: `
<h1>Hello {{ name}}</h1>
<button>Press me</button>
<coolWidget />
`,
oncomplete: function(){
var widget = this.findComponent('coolWidget')
document.querySelector('button').addEventListener('click', function(){
widget.get('doCoolThing')();
})
}
})
<script src="http://cdn.ractivejs.org/latest/ractive.js"></script>
<div class="some-ui">
</div>
What's the best way invoke a function in a child component from the parent?
I was trying to expand on #rich-harris answer with a comment, but apparently this is too long.
If you want to have ractive manage your wiring a bit more, you could subscribe to and fire events from the root instance:
In widget onrender: this.coolThingListener = this.root.on('doCoolThing *.doCoolThing', function() { /* cool thing here */ })
In widget onunrender: this.coolThingListener.cancel()
In main instance template: <button on-click="doCoolThing">Press me</button>
When the button is pressed, the event will bubble to the root instance, which has a listener for any doCoolThing event (using the plain and starred version makes sure any nested component can trigger it in addition to events on the root instance). The only thing to watch out for is this in the component event handler (coolThingListener) will be the root instance, so you'll need to capture a component reference or use an arrow function.
There's an unsubscribe in onunrender in case you happen to have multiple instances of the component pop into existence over the life of the root.
Here's the whole thing together:
var coolWidget = Ractive.extend({
data: {},
template: `<span>Your favorite fruit is {{ fruit }}</span>`,
onrender: function(){
var component = this;
// if 'doCoolThing' might fire from another component, add '*.doCoolThing' to listener to matching child component events too
this.coolThingListener = this.root.on( 'doCoolThing', function(){
component.doCoolThing();
});
},
onunrender: function(){
this.coolThingListener.cancel();
},
data: function(){
return {
fruit: 'banana'
}
},
doCoolThing: function(){
console.log('Going to change fruit...')
setTimeout(function(){
this.set('fruit', 'avocado')
}, 3 * 1000)
}
})
var ui = new Ractive({
el: '.some-ui',
data: { name: 'Mike'},
components: {
coolWidget
},
template: `
<h1>Hello {{ name}}</h1>
<button on-click="doCoolThing">Press me</button>
<coolWidget />
`
})
If doCoolThing is a top-level option...
var coolWidget = Ractive.extend({
data: {},
template: `<span>Your favorite fruit is {{ fruit }}</span>`,
data: function(){
return {
fruit: 'banana'
}
},
doCoolThing: function(){
var component = this;
console.log('Going to change fruit...')
setTimeout(function(){
component.set('fruit', 'avocado')
}, 3 * 1000)
}
})
...then you can invoke the method like so:
oncomplete: function(){
var widget = this.findComponent('coolWidget')
document.querySelector('button').addEventListener('click', function(){
widget.doCoolThing();
})
}
This doesn't just feel better, it actually has a tangible benefit – doCoolThing sits on the coolWidget prototype, so only gets created once no matter how many widgets are instantiated. There's also no internal mucking around with context binding.

Setting default values with selectize api for listbox in selectize.js

I am trying to set default value upon page load in a dropdown listbox which loads value thru an ajax call. Not able to set values using setValue method as suggested in the docs. Please help. Following are code snippets from the project
HTML
<select name="country" placeholder="Please Select Country ..."></select>
Data population JS code
$('[name="country"]').selectize({
valueField: 'name',
labelField: 'name',
searchField: 'name',
preload: true,
create: false,
render: {
option: function(item, escape) {
return '<div>' + escape(item['name']) + '</div>';
}
},
load: function(query, callback) {
$.ajax({
url: '/countrydata',
type: 'GET',
error: function() {
callback();
},
success: function(res) {
callback(res);
}
});
},
});
Default Value Setting Code
$('[name="country"]').selectize();
$('[name="country"]')[0].selectize.setValue("Japan");
Is there a hack where I can circumvent the problem ? I am been trying to get this work for couple of days now.
Thank you for reading. Any help would be appreciated.
You're close, but I believe your issue is that you're attempting to set the value to an option that doesn't exist in the list. You need to add the option first:
$('[name="country"]').selectize();
var item = {};
item.name = "Japan"; //Based on your render function, which uses item['name'] and labelField, searchField, etc.
$('[name="country"]')[0].selectize.addOption(item);
$('[name="country"]')[0].selectize.setValue(item.name);

Does Knockout.mapping make ALL nested objects observable?

I am trying to map all possible nested objects of a JSON object so that each and every one is becomes an observable. I was under the impression that the use of ko.mapping.fromJS would result in all objects and their objects becoming observable. However, I am not seeing that happen.
If you look at the JSFiddle and code below you will see that the span initially displays the value "Test". My intention is for the button click to update the viewModel with the contents of stuff2, which should change the span's value to "Test2". However, the button click does not update anything.
http://jsfiddle.net/Eves/L5sgW/38/
HTML:
<p> <span>Name:</span>
<span data-bind="text: IntroData.Name"></span>
<button id="update" data-bind="click: Update">Update!</button>
</p>
JS:
var ViewModel = function (data) {
var me = this;
ko.mapping.fromJS(data, {}, me);
me.Update = function () {
ko.mapping.fromJS(stuff2, {}, windows.viewModel);
};
return me;
};
var stuff = {
IntroData: {
Name: 'Test'
}
};
var stuff2 = {
IntroData: {
Name: 'Test2'
}
};
window.viewModel = ko.mapping.fromJS(new ViewModel(stuff));
ko.applyBindings(window.viewModel);
Is it just that I have to make use of mapping options to have the nested objects be made observable? If so, what if the JSON object is so vast and complex (this one obviously isn't)? Can some recursive functionality be used to loop through each object's nested objects to make them all observable?
Modifying the Update function as below will work.
me.Update = function () {
ko.mapping.fromJS(stuff2, {}, windows.viewModel);
};

Access current request in Express/Jade view

I have a layout Jade view that has a menu via unordered list, and I want to set the <li> to be <li class="active">...</li> when the current page is rendered in the browser.
I assume I will have to access the current request to determine when to set the attribute on the <li>
I can't find any examples of how to do this so hoping someone can help
Thanks
Try this before your call res.render() in your route:
res.locals.path = req.path;
res.render('/page');
or
res.render('/page', { path: req.path });
Then you would have to do a bunch of if/else statements in your view (as the above solution suggests).
- if(currentUrl === '/')
li(class='active')
a(href='/') Current Driver Standings
- else
li
a(href='/') Current Driver Standings
I however, prefer to do this on client side instead, to keep my template files free from as much logic as possible:
In page template file (this is ejs, not sure how to echo in jade):
<body data-path="<%= path %>">
Then with jQuery you can grab the path from body and attach an active class:
$(function(){
var path = $('body').attr('data-path');
$('nav li a[href='+path+']').parents('li').addClass('active');
});
Update: You can also just use var path = window.location.pathname instead of saving it to an attribute on body
//no need to save path to <body> tag first:
$(function(){
var path = window.location.pathname;
$('nav li a[href='+path+']').parents('li').addClass('active');
});
Here's a much neater way of doing it, server-side:
In your routes.js (or wherever) define an array of objects representing your nav like such:
var navLinks = [
{ label: 'Home', key: 'home', path: '' },
{ label: 'About', key: 'about', path: '/about' },
{ label: 'Contact', key: 'contact', path: '/contact' }
]
Pass the navLinks variable to your view, as well as the key of the item you'd like hilighted:
res.render('home', { title: 'Welcome!', section: 'home', navLinks: navLinks });
You can also add the navLinks variable to app.locals and save yourself always having to provide it explicitly to views.
Then in your jade template loop through the array of links and set the active class on the one whose key matches the provided section:
ul(class='nav nav-list')
- navLinks.forEach(function(link){
- var isActive = (link.key == section ? 'active' : '')
li(class=isActive)
a(href=link.path)= link.label
- })
Pass the req.originalUrl in your routes file. example: in your
/routes/about.js
router.get('/', function(req, res) {
res.render('about', {
url: req.originalUrl
});
});
Then, write if else condition on your jade template
if(url==='/about-us')
li(class='active')
a(href='about-us') About Us
else
li
a(href='about-us') About Us
you can use global variables in app.js like :
// Global vars
app.use( function ( req, res, next ) {
// rest of your code ...
res.locals.current_url = req.path;
// rest of your code ...
next();
} );
// then in your .jade file:
ul.navbar-nav.mr-auto
li(class="nav-item #{ current_url === '/page1' ? 'active' : ''}")
a.nav-link(href='/page1') Page1
like this you are able to use "current_url" globally all around your view files
I came up with this which works however I'm not sure if its best practice. Please let me know either way:
response.render("current/currentSchedule", {
title: "Current Race Schedule",
currentUrl: req.path,
});
ul(class='nav nav-list')
li(class='nav-header') Current Season
- if(currentUrl === '/')
li(class='active')
a(href='/') Current Driver Standings
- else
li
a(href='/') Current Driver Standings
- if(currentUrl === '/constructor-standings')
li(class='active')
a(href='/constructor-standings') Current Constructor Standings
- else
li
a(href='/constructor-standings') Current Constructor Standings
- if(currentUrl === '/current-schedule')
li(class='active')
a(href='/current-schedule') Current Race Schedule
- else
li
a(href='/current-schedule') Current Race Schedule

Resources