Create a client action in Odoo 14 - odoo

I am trying to create a client action widget, but getting an error when I click the menu item.
Here is the error.
Traceback:
Error: widget.getTitle is not a function
_executeClientAction#http://localhost:5014/web/static/src/js/chrome/action_manager.js:449:27
_handleAction#http://localhost:5014/web/static/src/js/chrome/action_manager.js:670:29
*.js
odoo.define('mobile_basket_verification.BasketVerification', function (require){
'use strict';
var core = require('web.core');
var Widget = require('web.Widget');
// var ClientAction = require('stock_barcode.ClientAction');
// var ViewsWidget = require('stock_barcode.ViewsWidget');
var Qweb = core.qweb;
var _t = core._t;
var BasketWidget = Widget.extend({
// template:'BasketVerificationComponent',
contentTemplate:'BasketVerificationComponent',
init: function(parent,action){
this._super.apply(this, arguments);
},
start: function() {
this._super.apply(this, arguments);
console.log('Widget Start')
},
});
core.action_registry.add('basket_verification_client_action',BasketWidget);
return BasketWidget;
});
*static.xml
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="mobile_basket_verification.BasketVerificationComponent">
<div class="o_barcode_message d-flex flex-column justify-content-center align-items-center bg-800">
<div class="o_barcode_pic position-relative text-center mt-2 mb-1">
<i class="fa fa-5x mx-auto fa-exclamation-triangle text-white d-none"/>
<img class="o_barcode_icon" src="/stock_barcode/static/img/barcode.svg" alt="Barcode" height="40px"/>
</div>
<div class="basket_barcode d-flex" style="float:right;width:400px;margin-right:200px;">
<span>Basket </span>
<input type="text" class="o_field_char basket_barcode_input" style="width:100px"/>
<button class="btn btn-primary verify_basket">Add</button>
</div>
</div>
<div class="o_barcode_lines_header alert mb-0"></div>
<div class="o_barcode_lines list-group flex-grow-1 d-block position-relative"></div>
</t>
</templates>
*.Menuitem
<menuitem id="basket_verification_client_action_menu" name="Basket Verification11" parent=""
action="basket_verification_client_action"/>
clientaction.
<odoo>
<record id="basket_verification_client_action" model="ir.actions.client">
<field name="name">Basket Verification Client Action</field>
<field name="tag">basket_verification_client_action</field>
</record>
</odoo>
Please help me to resolve this error.

Extend the AbstractAction instead.
var BasketWidget = AbstractAction.extend({
});
Check the Client actions documentation:
from the perspective of the web client, it is a widget, which inherit from the class AbstractAction, and is supposed to be registered in the action registry under the corresponding key (from the field char)

Related

ASP.NET Core Razor Page, code behind method not being triggered

I have a C# Razor Pages project.
I created a Login view in the following structure:
- Pages
- Account
- Login.cshtml
This is the code for my Login view
#page "{handler?}"
#model HAL_WEB.Pages.LoginModel
#{
Layout = "_LayoutLogin";
}
<section class="section register min-vh-100 d-flex flex-column align-items-center justify-content-center py-4">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-4 col-md-6 d-flex flex-column align-items-center justify-content-center">
<div class="d-flex justify-content-center py-4">
<a href="index.html" class="logo d-flex align-items-center w-auto">
<img src="assets/img/teamtruetech_logo.png" alt="">
<span class="d-none d-lg-block">HAL Admin</span>
</a>
</div><!-- End Logo -->
<div class="card mb-3">
<div class="card-body">
<div class="pt-4 pb-2">
<h5 class="card-title text-center pb-0 fs-4">Login to Your Account</h5>
<p class="text-center small">Enter your username & password to login</p>
</div>
<form id="login-form" class="row g-3 needs-validation" novalidate>
<div class="col-12">
<label for="yourUsername" class="form-label">Username</label>
<div class="input-group has-validation">
<span class="input-group-text" id="inputGroupPrepend"></span>
<input type="text" name="username" class="form-control" id="yourUsername" required>
<div class="invalid-feedback">Please enter your username.</div>
</div>
</div>
<div class="col-12">
<label for="yourPassword" class="form-label">Password</label>
<input type="password" name="password" class="form-control" id="yourPassword" required>
<div class="invalid-feedback">Please enter your password!</div>
</div>
<div class="col-12">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="remember" value="true" id="rememberMe">
<label class="form-check-label" for="rememberMe">Remember me</label>
</div>
</div>
<div class="col-12">
<button class="btn btn-primary w-100" type="submit">Login</button>
</div>
#* <div class="col-12">
<p class="small mb-0">Don't have account? Create an account</p>
</div>*#
</form>
</div>
</div>
</div>
</div>
</div>
</section>
#section Scripts {
<script src="~/assets/js/loginpage.js"></script>
}
And this is the code behind:
using HAL_WEB.Data;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Security.Claims;
namespace HAL_WEB.Pages
{
public class LoginModel : PageModel
{
private readonly ApplicationDBContext _dbContext;
public LoginModel([FromServices] ApplicationDBContext dbContext)
{
_dbContext = dbContext;
}
public void OnGet()
{
}
public async Task<IActionResult> OnPostLoginAsync(string username, string password)
{
// Check if the provided credentials are valid
if (IsValidCredentials(username, password))
{
// If the credentials are valid, log the user in
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, username) }, CookieAuthenticationDefaults.AuthenticationScheme)),
new AuthenticationProperties
{
IsPersistent = true, // Set this to true if you want the user to stay logged in after closing the browser
ExpiresUtc = DateTime.UtcNow.AddDays(7) // Set the expiration time for the cookie
});
// Redirect the user to the home page
return RedirectToPage("/Home");
}
else
{
// If the credentials are invalid, show an error message
ModelState.AddModelError(string.Empty, "Invalid username or password.");
return Page();
}
}
private bool IsValidCredentials(string username, string password)
{
// Replace this with your own validation logic
return username == "admin" && password == "password";
}
public IActionResult OnPostLoginTestAsync()
{
return new JsonResult(true);
}
}
In my Javascript file I tried to call the method OnPostLoginTestAsync or OnPostLoginAsync without success.
I'm getting a "Bad Request 400" error:
This is my Javascript Axios code for calling the method:
// Use Axios to send a POST request to the server with the form data
axios.post('/Account/Login?handler=login', {
username,
password,
})
.then((response) => {
// If the request is successful, redirect the page
window.location.href = '/home';
})
.catch((error) => {
// If there is an error, log it to the console
console.error(error);
});
Any clue what am I doing wrong? I'm going to /Account/Login?handler=login because the call is a Post and what I think is that the method OnPostLoginAsync should be executed.
UPDATE
I found something interesting, I created the following Get method:
public IActionResult OnGetTestAsync()
{
return new JsonResult(true);
}
And in my Javascript, I changed the Axios url to be:
axios.get('/Account/Login?handler=test')
.then(function (response) {
})
.catch(function (error) {
// Handle the error response
});
And I could get the method executed! But when I change the method name back to:
OnPostTestAsync
and my Axios to:
axios.post('/Account/Login?handler=test')
.then(function (response) {
})
.catch(function (error) {
// Handle the error response
});
It never gets executed and I get 400 Bad Request. Any clue?

How to show error message in modal dialog from ASP.NET Core?

I have ASP.NET Core MVC project. In my core project, I am using fluent validation like this:
public class AddEntityViewModelValidator: AbstractValidator<AddEntityViewModel>
{
public AddEntityViewModelValidator()
{
RuleFor(x => x.Name)
.NotEmpty()
.WithMessage("You must enter name.");
}
}
My controller looks like this:
[HttpPost]
public async Task<IActionResult> CreateEntity(AddEntityViewModel addEntityViewModel)
{
try
{
if (!ModelState.IsValid)
{
var errors = ModelState.Values.SelectMany(v => v.Errors.Select(x => x.ErrorMessage)).ToList();
foreach(var error in errors)
{
ModelState.AddModelError("Error: ", error);
}
return View(addEntityViewModel);
}
await _businessLogic.CreateEntity(addEntityViewModel.Entity);
return View(addEntityViewModel);
}
catch (Exception)
{
return View(addEntityViewModel);
}
}
When user doesn't enter the name in model dialog, fluent validation do the work. This list of errors (var errors in controller) contains this error, so this part is working. But in my cshmtl modal-dialog this error message is not showing anywhere.
I have the the button for opening modal dialog:
<div id="PlaceHolderHere"></div>
<button type="button" class="btn btn-success" data-toggle="ajax-modal" data-target="#addEntity"
data-url="#Url.Action("AddEntity", "Entity", new { entityId = Model.Id})">
Add entity
</button>
and my jquery code:
$(function () {
var PlaceHolderElement = $('#PlaceHolderHere');
$('button[data-toggle="ajax-modal"]').click(function (event) {
var url = $(this).data('url');
var decodeUrl = decodeURIComponent(url);
$.get(decodeUrl).done(function (data) {
PlaceHolderElement.html(data);
PlaceHolderElement.find('.modal').modal('show');
})
})
PlaceHolderElement.on('click', '[data-save="modal"]', function (event) {
var form = $(this).parents('.modal').find('form');
var actionUrl = form.attr('action');
var sendData = form.serialize();
$.post(actionUrl, sendData).done(function (data) {
PlaceHolderElement.find('.modal').modal('hide');
location.reload(true);
})
})
})
Here is my modal-dialog:
#model AddEntityViewModel
<div class="modal fade" id="addEntity">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title" id="addEntityLabel">#Model.Title</h3>
</div>
<div class="modal-body">
<form action="CreateEntity">
<div asp-validation-summary="All" class="text-danger wrapper"></div>
<div class="form-group">
<label asp-for="#Model.Entity.Name">Name</label>
<input asp-for="#Model.Entity.Name" class="form-control" />
<span asp-validation-for="#Model.Entity.Name" class="text-danger"></span>
</div>
</form>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary" data-save="modal">Save</button>
<button type="button" class="btn btn-danger" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
So, i have this line:
<div asp-validation-summary="All" class="text-danger wrapper"></div>
but error is still now showing. Any idea how to solve this? Should I change something in my jquery function?

Files From Upload Modal Not Being Passed

I have a BootStrap Modal Popup that I want to use for selecting and uploading a file. The pop-up works in all respects EXCEPT it is not passing the selected file to the underlying controller. Here is the form:
<!--Modal Body Start-->
<div class="modal-content">
<!--Modal Header Start-->
<div class="modal-header">
<h4 class="modal-title">Upload File</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
</div>
<!--Modal Header End-->
<form asp-action="FileUpload" asp-controller="Attachment" method="post" enctype="multipart/form-data">
#Html.AntiForgeryToken()
<div class="modal-body form-horizontal">
<div>
<p>Upload a file using this form:</p>
<input type="file" name="file" />
</div>
<!--Modal Footer Start-->
<div class="modal-footer">
<button data-dismiss="modal" id="cancel" class="btn btn-default" type="button">Cancel</button>
<input type="submit" class="btn btn-success relative" id="btnSubmit" data-save="modal" value="Upload">
</div>
<div class="row">
</div>
</div> <!--Modal Footer End-->
</form>
</div>
<script type="text/javascript">
$(function () {
});
</script>
<!--Modal Body End-->
Here is the action in the controller:
[HttpPost]
public IActionResult FileUpload(IFormFile file)
{
//DO something with the file
return View();
}
[HttpGet]
public ActionResult UploadFile(string issueid)
{
ViewBag.id = issueid;
return PartialView("_UploadFile");
}
The action gets called but the "file" variable is NULL.
I have the following markup & script on the MAIN page the pop-up originates from:
<div id="modal-container" class="modal fade" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
</div>
</div>
</div>
Upload Files
<script>
$('body').on('click', '.modal-link', function () {
var actionUrl = $(this).attr('href');
$.get(actionUrl).done(function (data) {
$('body').find('.modal-content').html(data);
});
$(this).attr('data-target', '#modal-container');
$(this).attr('data-toggle', 'modal');
});
$('body').on('click', '.relative', function (e) {
e.preventDefault();
var form = $(this).parents('.modal').find('form');
var actionUrl = form.attr('action');
var dataToSend = form.serialize();
$.post(actionUrl, dataToSend).done(function (data) {
$('body').find('.modal-content').html(data);
});
})
$('body').on('click', '.close', function () {
$('body').find('#modal-container').modal('hide');
});
$('#CancelModal').on('click', function () {
return false;
});
$("form").submit(function () {
if ($('form').valid()) {
$("input").removeAttr("disabled");
}
});
</script>
To upload form data with a file you have to use a FormData object.
Also, you have to use $.ajax, as $.past cannot handle the FormData object
$('body').on('click', '.relative', function (e) {
e.preventDefault();
var form = $(this).parents('.modal').find('form');
var actionUrl = form.attr('action');
var dataToSend = new FormData(form[0]);
$.ajax({
url: actionUrl,
type: 'POST',
data: dataToSend,
processData: false, //prevent jQuery from trying to serialize the FormData object
contentType: false, // prevents jQuery from setting the default content type
success: function(data){
$('body').find('.modal-content').html(data);
}
});
})

Selenuim/Protractor can't find or click in textbox

I have a code from a website as follows and I want to use the 5th line from code segment below <input type="text" placeholder="Enter Workflow Name"
Code
<div class="workflow-container ng-scope" data-ng-controller="sourceCode.Designer.uiComponents.conciergeScreen.templates.NewWorkflowController">
<div class="input">
<div class="wrapper top" data-ng-class="{'fill': hosted === true}">
<label class="welcome">What should your workflow be called?</label>
<input type="text" placeholder="Enter Workflow Name" class="workflow-name-textbox ng-valid ng-not-empty ng-touched ng-dirty ng-valid-parse" data-ng-class="{'error': errors.error}" autofocus="" data-ng-focus="select($event)" data-ng-model="conciergetitle" data-ng-model-options="{ updateOn: 'default blur', debounce: { default: 300, blur: 300 } }" data-ng-change="inputchange(designeritems)" data-ng-keyup="$event.keyCode == 13 && createnewstudioitem(designerItems[0], conciergetitle, $event)" style="">
<div class="errogory">
<div class="summary">
<!-- ngIf: errors.error || errors.category -->
</div>
<div class="category" data-ng-click="categorypicker($event)">
<label>Folder</label>
<i class="icon icon-set-assetbrowser icon-size16 ic-categoryserver"></i>
Workflow
</div>
</div>
<div class="concierge-button-grid">
<div class="concierge-button-container">
<button id="createWorkflow" data-button-error="false" class="concierge-button button-command" data-ng-disabled="!newWorkflowReady" data-ng-class="{ 'error': errors.button, 'is-disabled error' : errors.button }" data-ng-click="createnewstudioitem(designerItems[0], conciergetitle, $event)" disabled="disabled">
<!-- ngIf: !errors.button --><span data-ng-bind="getString('new_workflow_create_button')" data-ng-if="!errors.button" class="ng-binding ng-scope">Create</span><!-- end ngIf: !errors.button -->
<!-- ngIf: errors.button -->
</button>
</div>
<div class="concierge-button-container">
<button id="discardWorkflow" class="concierge-button concierge-button-discard button-command tertiary" data-ng-click="discard()">
<span data-ng-bind="getString('discard_workflow_button')" class="ng-binding">Discard</span>
</button>
</div>
</div>
</div>
<!-- ngIf: showrecent -->
<!-- ngIf: showrecent -->
</div>
I want to click in the textbox so that I can clear the text. I have tried the following:
describe("New Screen", function () {
it("Should give textbox a new name", function () {
browser.sleep(10000);
console.log('Enter new name');
var editName = element.all(by.className('.workflow-name-textbox'));
editName.first().click().then(function () {
console.log('Clicked on Create');
})
browser.sleep(10000);
})
I get a error: Index out of bound. Trying to access element at index: 0 ...
if I change my code above to:
var editName = element.all(by.css('.workflow-name-textbox'));
editName.click().then(function () {
console.log('Clicked on Create');
I dont get errors but I dont see any clicking going on.
I know my protractor works because I have navigated to this page using similar code.
Do anyone have suggestions what else I could try.
I had to go two iFrames down:
//Parent
browser.switchTo().frame('Iframe1');
//Child
browser.switchTo().frame('Iframe2');
//var NewTextBox = browser.findElement(by.css('.name-textbox')).clear();
var NewTextBox = element.all(by.css('.name-textbox'));
NewTextBox.clear().then(function () {
console.log('Clear text');
Did you tried this way instead of element.all.
element.all return a list elemenet and element return only single element.
var NewTextBox = element(by.css('.workflow-name-textbox'));
or
var NewTextBox = element(by.xpath('//input[#placeholder='Enter Workflow Name']'));

Why I can not see the data in an input field when I use the knockout with: binding

I want to make a dynamic dialog box. For that I need some data like title, content and viewmodel. The title and the content will be shown. But the viewmodel not bind correctly. To see the problem, I prefilled the first_name with 'foo'. But it should be 'my first name'.
To make the modal dialog dynamic, we have a modalDialog object that must be filled with the specific data. The openDialog function fill this data and open it.
Normally the dynamic part will be load by requirejs. For demonstrate the problem, I have made it here much easier.
Here the html code:
<!-- the dynamic modal dialog -->
<div id="modal-dialog" class="modal fade" role="dialog" data-backdrop="static" data-bind="css: modalDialog.classes">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-bind="click: modalDialog.close">×</button>
<h4 class="modal-title" data-bind="html: modalDialog.title"></h4>
</div>
<div data-bind="with: modalDialog.viewModel">
<div class="modal-body" data-bind="html: $parent.modalDialog.content"></div>
</div>
<div class="modal-footer" data-bind="foreach: modalDialog.buttons">
<button type="button" class="btn btn-primary" data-bind="click: action, text: label"></button>
</div>
</div>
</div>
</div>
<!-- Test button for open the dialog -->
<button type="button" class="btn btn-info" data-bind="click: testDialog">Open modal</button>
Here the javascript code for the modal dialog viewmodel:
// the main viewmodel
var appVM = {
modalDialog:
{
title: ko.observable(''),
content: ko.observable(''),
viewModel: ko.observable(this),
buttons: ko.observableArray([]),
classes: ko.observable(''),
open: function()
{
$("#modal-dialog").modal('show');
},
close: function()
{
$("#modal-dialog").modal('hide');
}
},
openDialog: function(title, content, viewModel, buttons, classes)
{
if (!viewModel)
viewModel = appVM;
if (!buttons)
buttons = [{action: appVM.modalDialog.close, label: 'Close'}];
if (!classes)
classes = '';
appVM.modalDialog.title(title);
appVM.modalDialog.content(content);
appVM.modalDialog.buttons(buttons);
appVM.modalDialog.classes(classes);
appVM.modalDialog.viewModel(viewModel);
appVM.modalDialog.open();
},
testDialog: function()
{
var vm = new userViewModel();
var title = 'Test Title';
var html = dialogContent;
var buttons = [
{ action: vm.onSave, label: 'Apply' },
{ action: appVM.modalDialog.close, label: 'Cancel' }
];
appVM.openDialog(title, html, vm, buttons);
}
};
ko.applyBindings(appVM);
At least the code for the dynamic data:
// the user data
function User(data)
{
var self = this;
self.first_name = ko.observable(data.first_name).extend({required: true});
self.last_name = ko.observable(data.last_name).extend({required: true});
}
// the user viewmodel
function userViewModel()
{
var self = this;
self.user = ko.observable(new User(
{
first_name: 'my first name',
last_name: 'my last name'
}));
self.onSave = function()
{
alert('save data');
};
}
// The loaded content for the dialog
var dialogContent = ' \
<div class="clearfix"> \
<div class="col-xs-12"> \
<div class="row form-group"> \
<label class="col-xs-12 col-sm-4 col-md-3">Vorname:</label> \
<input class="col-xs-12 col-sm-8 col-md-9" type="text" data-bind="textInput: user().first_name" title="" value="foo"/> \
</div> \
<div class="row form-group"> \
<label class="col-xs-12 col-sm-4 col-md-3">Nachname:</label> \
<input class="col-xs-12 col-sm-8 col-md-9" type="text" data-bind="textInput: user().last_name" title=""/> \
</div> \
</div> \
</div> \
';
You can try it here:
http://jsfiddle.net/p8zbfw65/
Update
With the Tip from Roy it works like expected. I inserted the boundHtml binding
ko.bindingHandlers.boundHtml = {
update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
const contentHtml = ko.unwrap(valueAccessor());
element.innerHTML = contentHtml;
ko.applyBindingsToDescendants(bindingContext, element)
}
};
and changed the html: $parent.modalDialog.content
to boundHtml: $parent.modalDialog.content
The html binding inserts HTML but does not apply bindings to it. You will need a custom binding handler to do that. I wrote a simple one here.