show FormView inside a Widget in Odoo 10 - javascript

Can I create a Widget which gets model name as a parameter and renders model's form view?
My expectation is like:
When a user clicks a menu, the models form is displayed in edit mode ('mymodule.asset_category').
The user then selects a category dropdown. For example 'Asset'. ('Asset' asset_category's 'related_model' field is type of 'ir.model' and it's value would be 'mymodule.asset')
new form which makes the user able to create new related model's ('mymodule.asset') instance is created inside the parent form.
The user fills asset fields and then clicks the Save button.
Thus 'mymodule.asset_category', 'mymodule.asset' objects are saved to the database.
I think I can achieve these using wizards by showing forms sequentially. But I want to know at least if it's achievable in this way.
I tried following way:
class AssetCategory(models.Model):
_name = 'mymodule.asset_category'
name = fields.Char(string=u'Category', required="True")
related_model = fields.Many2one('ir.model', string=u'Related model')
class Asset(models.Model):
_name = 'mymodule.asset'
name = fields.Char(string=u'Asset Name', required="True")
amount = fields.Float(string=u'Amount', digit=[12, 2])
XML will be like:
<record model="ir.ui.view" id="asset_form_view">
<field name="name">mymodule_asset_form</field>
<field name="model">mymodule.asset_category</field>
<field name="arch" type="xml">
<form string="asset category">
<sheet>
<field name="name"></field>
<field name="related_model"></field>
<widget type="my_widget"></widget>
</sheet>
</form>
</field>
</record>
And the Javascript:
'mymodule.asset' parameter will be set dynamically onchange of "related_model" dropdown.
var MyWidget = Widget.extend({
start:function(){
self = this;
var dataset = new data.DataSet(this, 'mymodule.asset');
var v_id;
dataset._model.call('get_formview_id', [[0], {}]).then(function (view_id) {
v_id = view_id
});
var fields_view_def;
fields_view_def = data_manager.load_fields_view(dataset, v_id, 'form', false);
var form_view = new FormView(self, dataset, fields_view_def, {});
return form_view.appendTo(this.$el);
}
});
core.form_custom_registry.add('my_widget', MyWidget);
My current code throws following error:
http://localhost:8069/web/static/src/js/views/form_view.js:1239
Алдааны мөр:
TypeError: Cannot read property 'attrs' of undefined
at Class.set_fields_view (http://localhost:8069/web/static/src/js/views/form_view.js:1239:49)
at Class.start (http://localhost:8069/web/static/src/js/views/form_view.js:97:31)
at Class.prototype.<computed> [as start] (http://localhost:8069/web/static/src/js/framework/class.js:89:38)
at http://localhost:8069/web/static/src/js/framework/widget.js:193:25
at http://localhost:8069/web/static/lib/jquery/jquery.js:3276:89
at fire (http://localhost:8069/web/static/lib/jquery/jquery.js:3119:58)
at Object.add [as done] (http://localhost:8069/web/static/lib/jquery/jquery.js:3165:49)
at Array.<anonymous> (http://localhost:8069/web/static/lib/jquery/jquery.js:3275:77)
at Function.each (http://localhost:8069/web/static/lib/jquery/jquery.js:383:58)
at Object.<anonymous> (http://localhost:8069/web/static/lib/jquery/jquery.js:3272:56)

As I know you are familiar with Odoo, you have heard about hr_timesheet_sheet module.
for your reference you can check out this module.
hr_timesheet_sheet:-
https://github.com/odoo/odoo/blob/10.0/addons/hr_timesheet_sheet/static/src/js/timesheet.js

Related

Unable to read value from document

I'm a novice in MVC, Below is my code
I am unable to read the value of an ID and use that in an decision statement, I am getting "The name "Text" does not exist in current context", I need to work on the if statement based on the value I get from my document.getElementById
#{
var grid = new WebGrid(Model.Abc, canPage: true, canSort: true, rowsPerPage: 50);
}
#{
var gridColumnsNew = new List<WebGridColumn>();
gridColumnsNew.Add(grid.Column("Details", header: "Id"));
<text>
var obj = document.getElementById("NextAction").value;
</text>
if (#text.obj == "Start")
{
gridColumnsNew.Add(grid.Column("Temp"));
}
}
Try using
document.getElementsByName("NextAction").value;
I have seen in my case that Blazor changes Id to name.
Note: I am using DevexpressBlazor
Did you checked if you are able to see on the html generated that ID?
If yes, Did you have any JS error before?
Looks like the ID not was generated or the place where you are run the getElementById don't have visibility to your specific code.
You are mixing razor syntax and javascript. The line var obj = document.getElementById("NextAction").value; is javascript and should go inside <script> tag. You can't call javascript functions from razor code.
Solution:
Assuming you have a controller named GridController.cs and a view named Grid.cshtml. Inside your controller add a new HttpPost action:
[HttpPost]
public IActionResult NextAction(string nextAction)
{
ViewData["NextAction"] = nextAction;
return View("Grid");
}
Inside the view add a form that posts the nextAction value to the controller:
<form asp-action="NextAction" asp-controller="Grid">
<input type="hidden" value="Start" name="nextAction" />
<button type="submit">Start</button>
</form>
The controller added the NextAction value in the ViewData dictionary so now the view can access it:
#{
var gridColumnsNew = new List<WebGridColumn>();
gridColumnsNew.Add(grid.Column("Details", header: "Id"));
if (ViewData["NextAction"] == "Start")
{
gridColumnsNew.Add(grid.Column("Temp"));
}
}
You are getting that error because you are using #text.obj. In Razor, once you attached # before any identifier, it considers it a C# or VB variable.
Since we don't have your entire page, you may need to clarify where the source of the NextAction. It will be helpful. See a sample of something similar.
#if(item.Ward == "start")
{
gridColumnsNew.Add(grid.Column("Temp"));
}
The item is from the model I am iterating to form the grid.

Google Apps Script - Sheet - Menu Trigger not working

So I am building a mailmerge tool, and it works fine.
Testing the trigger with a hard coded input works fine:
function test(){
sendEmails("TEST MAILMERGE FROM DRAFT")
}
It also works fine if I prompt an input box (relevant section of the code shown).
function sendEmails(subjectLine,sheet=SpreadsheetApp.getActiveSheet()) {
if (!subjectLine) {
subjectLine = Browser.inputBox(
"Mail Merge",
"Type or copy/paste the subject line of the Gmail " +
"draft message you would like to use:",
Browser.Buttons.OK_CANCEL
);
if (subjectLine === "cancel" || subjectLine == "") {
// if no subject line finish up
return;
}
}
However, trying to be smarty pants and have the menu dynamically populated with Subject lines like this:
function onOpen() {
// get the UI for the Spreadsheet
const ui = SpreadsheetApp.getUi();
// add the menu
const menu = ui.createMenu("TEST");
// get the drafts from Gmail
let drafts = GmailApp.getDraftMessages();
// for each draft, create a new menu item
drafts.forEach((draft) => {
// add the drafts to be triggered using the following: addItem(caption: string, functionName: string)
menu
.addItem(
draft.getSubject().toString(),
'sendEmails("' + draft.getSubject().toString() + '")'
)
.addToUi();
});
}
However, this doesn't work. It comes up with the following error:
Error Script function not found: sendEmails(TEST MAILMERGE FROM DRAFT)
Which to me looks like it should work. As the testing trigger that is hardcoded above works.
Am I being daft here? As far as I can see, this should work? But it's not.
When / if I get it working, I will put a check in to account for 'trash' drafts that don't have a subject. Just trying to get it to actually work for now though.
Menu.addItem(caption, functionName)
Parameters:
caption: The label for the menu item, with only the first word capitalized.
functionName: The name of the function to invoke when the user selects the item. You can use functions from included libraries, such as Library.libFunction1.
Menu.addItem() expects a function name without arguments. It doesn't allow to pass arguments in the function.
Workaround:
Based on my understanding, your goal is to have a different menu item that could send emails on each draft messages available in your email account.
You might want to consider using Custom dialogs or Custom sidebars where in you can select your draft message subject that you want to pass as argument when you call your sendEmails() function. You can refer to the sample code as reference.
Sample Code:
(Code.gs)
function onOpen() {
// get the UI for the Spreadsheet
const ui = SpreadsheetApp.getUi();
// add the menu
const menu = ui.createMenu("TEST")
.addItem('Send Email', 'selectDraft')
.addToUi();
}
function selectDraft() {
var html = HtmlService.createHtmlOutputFromFile('draft');
SpreadsheetApp.getUi() // Or DocumentApp or SlidesApp or FormApp.
.showModalDialog(html, 'Select Draft Message');
}
function getDraftSubject(){
// get the drafts from Gmail
let drafts = GmailApp.getDraftMessages();
var subjects = [];
// for each draft, create a new menu item
drafts.forEach((draft) => {
subjects.push(draft.getSubject().toString());
});
Logger.log(subjects);
return subjects;
}
function sendEmails(subjectLine,sheet=SpreadsheetApp.getActiveSheet()) {
Logger.log(subjectLine);
}
(draft.html)
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<h2>Select Draft Subject</h2>
<form id="myForm" onsubmit="handleFormSubmit()">
<div class="form-group">
<label for="subject">Draft Subject</label>
<select class="form-control form-control-sm" id="subject" name="subject" required>
<option value="" selected>Choose...</option>
</select>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
<script>
google.script.run.withSuccessHandler(function(subj) {
let select = document.getElementById("subject");
subj.forEach(function(e, i) {
let option = document.createElement("option");
option.value = e;
option.text = e;
select.appendChild(option);
});
}).getDraftSubject();
function handleFormSubmit() {
var value = document.getElementById("subject").value;
google.script.run.sendEmails(value);
document.getElementById("myForm").reset();
}
</script>
</body>
What it does?
Create a custom menu that will show a dialog box based on the draft.html
In draft.html, We used google.script.run.withSuccessHandler(function) to call getDraftSubject() in our server side (apps script) which returned an array of draft message subjects. We then update the form's select class and add options based on each draft message subjects obtained.
When the form was submitted, it will call handleFormSubmit(), we get the selected draft message subject and pass that value using google.script.run.myFunction(...) (any server-side function).
google.script.run.sendEmails(value);
Output:

Form view in odoo 12

In my custom module, there is one relation field. After click on create and edit form view is pop up. But I didn’t want to open this form view as a popup. I want to open this form view in the current window or in the current form. This is possible in odoo 12 or by using javascript Please, anyone, help me.
Thanks in advance.
Edit
Model and view are defined as per odoo instruction as :
class SurveySurvey(models.Model):
_name = 'survey.survey'
pages_ids = fields.One2many('survey.page','survey_id', string='Pages', copy=True)
View:
<field name="pages_ids" mode="tree" context="{'default_survey_id': active_id}" options="{'no_open':False}">
<tree editable="bottom" >
<field name="title"/>
<field name="question_ids" class="question_ids_class" widget="many2many_tags" context="{'page_id': active_id}" domain="[('page_id','=','active_id')]" options="{'no_open':true,'no_quick_create':true}"/>
</tree>
</field>
question_id is a relation field after click on create and edit option available in relation field one popup is open javascript code for that pop is as follows:
_searchCreatePopup: function (view, ids, context) {
var self = this;
return new dialogs.SelectCreateDialog(this, _.extend({}, this.nodeOptions, {
res_model: this.field.relation,
domain: this.record.getDomain({fieldName: this.name}),
context: _.extend({}, this.record.getContext(this.recordParams), context || {}),
title: (view === 'search' ? _t("Search: ") : _t("Create: ")) + this.string,
initial_ids: ids ? _.map(ids, function (x) { return x[0]; }) : undefined,
initial_view: view,
disable_multiple_selection: true,
no_create: !self.can_create,
on_selected: function (records) {
self.reinitialize(records[0]);
self.activate();
}
})).open();
},
The above code is available in web/static/js/fields/relational_fields.js
For better understanding please see screenshots.
Screenshot after click on Create and edit

How to update data in Meteor using Reactivevar

I have a page with a form. In this form user can add multiple rows with key and values. There is a restriction that the customFields is created on the fly, not from any subscribed collection.
...html
<template name="main">
{{#each customFields}}
<div>
<input type="text" value="{{key}}"/>
<input type="text" style="width: 300px;" value="{{value}}"/>
</div>
{{/each}}
</template
.... router.js
Router.route 'products.add',
path: '/products/add/:_id'
data:
customFields:[]
....products.js
#using customFieldSet as Reactive Var from meteor package
Template.product.created = ->
#customFieldSet = new ReactiveVar([])
Template.product.rendered = ->
self = this
Tracker.autorun ->
arr = self.customFieldSet.get()
self.data.customFields = arr
Template.product.events(
'click .productForm__addField': (e)->
t = Template.instance()
m = t.customFieldSet.get()
console.log t
m.push(
key: ''
value: ''
)
t.customFieldSet.set m
....
The last event will be trigger when I click the button. And it add another row with key and value empty to the page.
Please advise me why I actually see the reactive variable customFieldSet updated, but there is nothing changed dynamically in html.
P/s: I guess customFields is not updated via Iron router.
Basically, you're doing the thing right. However, you shouldn't be assigning the new reactive data to your template's data context, but rather access it directly from your helpers:
Template.product.helpers({
customFileds: function () {
return Template.instance().customFiledsSet.get();
},
});
Now you can use {{customFields}} in your template code and it should work reactively. Just remember that {{this.customFileds}} or {{./customFileds}} will not work in this case.

Access value of viewModel property with knockout?

I have a three different Car Fuel type pages which they render a Partial View to load common functionality across all three. I am using knockout js to perform some dynamic calculation in that partial view.
So each of my Fuel type pages has a BeginForm and the id is different for each so DieselForm, PetrolForm, ElectricForm. The fuel type is set in my viewModel and is stored in the common partial view in a hidden field - #Html.HiddenFor(model => model.FuelType)
The following is some of my knockout js which is in the Partial View:
var mvcModel = #Html.Raw(Json.Encode(Model));
var viewModel = ko.mapping.fromJS(mvcModel);
var calculateTotal = function() {
var form = $('#DieselContent form');
if (!form.valid()) return;
var formData = form.serialize();
//ajax post to server method removed for brevity
Is there a way I can pull the FuelType value from the viewModel property and then dynamically change the form var based on which FuelType is set - i.e - it should be either var form = $('#DieselContent form'); or var form = $('#PetrolContent form'); or var form = $('#ElectricContent form');
I tried using var fuelType = viewModel.FuelType.val(); but that throws an undefined error.
There are lots of ways to achieve the result you want, so the one you choose comes down to architectural decisions. If you were strictly referencing only the ViewModel then you could select the form using
var form = $('#' + viewModel.FuelType() + 'Content form');
However, in this case CalculateTotal references a form (part of the presentation) so it doesn't really seem to be strictly associated with the ViewModel. So in this instance I'd be tempted just to get the form selected from the MVC model. I.e.
var calculateTotal = function() {
var form = $('#String.Format("#{0}Content form", Model.FuelType)');
if (!form.valid()) return;
var formData = form.serialize();

Categories