Javascript Odoo - javascript

i'm trying to learn the javascript of odoo ,and as you all know there are very few documentation about it
First i could finally create a view template with its client action ,add a button to excute a code from js
JS :
odoo.define('js_client_view.ClientReportWidget', function (require) {
'use strict';
console.log("module loaded");
var AbstractAction = require('web.AbstractAction');
var Widget = require('web.Widget');
var core = require('web.core');
require('web.dom_ready');
var ajax = require('web.ajax');
var button = $('#mybutton');
var JsClientView = Widget.extend({
//contentTemplate: 'UploadDocumentMainMenu',
template: 'JsClientView',
events: {
'click .mybutton': '_onButton',
},
init: function(){
this._super.apply(this, arguments);
data =ajax.jsonRpc('/get_clients', 'call', {}).then(function(data) {
console.log("in data")
console.log(data)
return data
});
console.log(data)
},
_onButton: function(event) {
console.log("in On Button");
console.log(event);
ajax.jsonRpc('/get_clients', 'call', {}).then(function(data) {
console.log("in data")
console.log(data)
});
},
});
core.action_registry.add('js_client_view', JsClientView);
return JsClientView;
});
My template :
<templates xml:space="preserve" >
<t t-name="JsClientView" >
<div class="container o_mrp_bom_report_page">
<span class="o_report_heading text-left">
<br/>
<button type="button" class= "btn btn-secondary mybutton"
>Test </button>
<h1>Relevé de compte client</h1>
<strong>
<t >
<span>Tous les client</span>
</t>
</strong>
</span>
</t>
</templates>
And the controller that return info to the button function:
class OdooControllers(http.Controller):
#http.route(['/get_clients'], type='json',auth='public', website=True)
def get_clients(self, **kw):
clients = http.request.env['hr.employee'].sudo().search([('department_id', '=',5)], limit=6)
c = []
for client in clients:
n = {
'name': client.name,
'id': client.id,
'certificate': client.certificate,
}
c.append(n)
return c
Now i want to send this information to the template and display it
How can i do that !

Odoo JS is the frontend of odoo. As such you cannot edit the template itself since you are not in the backend. Odoo is using a classical client-server architecture therefore, your JS is seperated from the server.
However you can edit the entire DOM tree by accessing normal jquery and JS features. You could do something like this:
Add a placeholder div somewhere:
<div id ="content">
</div>
Fill that div with data.
// On click find that div and inject content into it
ajax.jsonRpc('/get_clients', 'call', {}).then(function(data) {
$("#content").html("<p>Hello World<p/>");
// Later fill the content with data
});
You could probably use no JS at all for your example but as far as I understood, your goal is to specifically use JS to display that data for practice. So that's how you would inject data from the server into the DOM by using JS.

Related

How to add open file dialog using JS for custom button that inherited from ListView in Odoo 15 development

I'm new to Odoo. I currently develop my own custom module in Odoo 15.0. I want to make custom button in treeview. The button is already showed using QWEB bundled custom XML just like this:
But the functionality and the JS logic still doesn't work properly. Here is my code:
static/src/xml/import_csv_views.xml
<t t-extend="ListView.buttons">
<t t-extend="ListView.buttons">
<t t-jquery="button.o_list_button_add" t-operation="after">
<button t-if="widget.modelName == 'item.master'" class="btn btn-primary btn-sm o_list_button_upload" accesskey="f">
Import CSV
</button>
</t>
</t>
</t>
static/src/js/import_csv.js
odoo.define('component_creator.import_csv', function (require) {
"use strict";
var ListController = require('web.ListController');
var core = require('web.core');
var _t = core._t;
ListController.include({
renderButtons: function () {
var self = this;
this._super.apply(this, arguments);
if (this.modelName === 'item.master') {
this.$buttons.find('.o_list_button_upload').after(
$('<button>', {
class: 'btn btn-primary btn-sm o_list_button_upload',
accesskey: 'f',
text: _t("Import CSV"),
}).click(function () {
self.$('input[type=file]').click();
})
);
}
},
});
core.action_registry.add('component_creator.import_csv', ListController);
// return the object.
return ListController;
});
There's no error or exception raised but the button just doesn't open or doing anything.
Whats wrong in my code?
Thank you in advance.
Edit:
I've also upgraded my custom module everytime I make any changes to the .js/.xml files but the button still won't work.

Laravel - JS : search function without reloading page, return html

on a small project using laravel and javascript, I would like to implement a search functionality
For this, I would like that once the search is submitted, the page content changes without reloading
So I have a first method in my controller, which renders the page view complete with my data
In the page template, I included a file of partials, containing only my foreach loop and the associated html
Here is the controller method
public function __invoke(MyService $myService)
{
return view('posts.index', [
'posts' => $myService->getAll(),
]);
}
and my partials present in posts.index
#foreach($posts as $post)
<div class="">
{{ $post->name }}
<p class="my-4">
{{ str($post->data)->limit(150) }}
</p>
</div>
#endforeach
So, in my posts.index, I add this JS
var search = document.getElementById("search");
var by = document.getElementById("by");
var form = document.getElementById("form");
form.addEventListener("submit", function(evt) {
evt.preventDefault();
fetch('/search?search=' + search.value + '&by=' + by.value)
.then(response => response.json())
.then(data => {
var elem = document.querySelector('#result');
elem.innerHTML = JSON.stringify(data.html)
});
});
The #result element is where I'm including the partials
There is my search function
public function search(Request $request){
$by = $request->input('by');
switch ($by){
case 'name':
$service = new MyService();
$result = $service->getPostsForName($request->input('search');
$html = view('partials.list', ['posts' => compact('result')])->render();
return response()->json(compact('html'));
break;
}
}
The two methods of the controller return me an Array of Post (my model)
But when I run a search I always get the following error
attempt to read property "url" on array in file
I can't understand why, could you help me please ?

Child view model modying a different child viewmodel

I have a main View Model for my screen. It consists of 2 child view models.
One handles the registration section.
One handles the login section.
One handles the menu section (If authenticated and what menu items can appear, as well as the "Welcome "Username" type stuff).
$(document).ready(function () {
// Create the main View Model
var vm = {
loginVm: new LoginViewModel(),
registerVm: new RegisterViewModel(),
layoutVm: new LayoutViewModel()
}
// Get the Reference data
var uri = '/api/Reference/GetTimezones';
$.getJSON({ url: uri, contentType: "application/json" })
.done(function (data) {
vm.registerVm.Timezones(data);
});
// Bind.
ko.applyBindings(vm);
});
Once my Login model's "Login" method completes, I want to set the "IsAthenticated" value within the Menu model, as well as some other user info.
So in my login model, I have a SignIn method.
$.post({ url: uri, contentType: "application/json" }, logindata)
.done(function (data) {
toastr[data.StatusText](data.DisplayMessage, data.Heading);
if (data.StatusText == 'success') {
alert($parent.layoutVm.IsAuthenticated());
}
else {
}
})
.fail(function () {
toastr['error']("An unexpected error has occured and has been logged. Sorry about tbis! We'll resolve it as soon as possible.", "Error");
});
The alert code is my testing. I am hoping to access (and set) the IsAuthenticated property of the layoutVm model. That's one of the child models on my main View model.
However, "$parent" is not defined.
How can I update values in the layoutVm, from my loginVm?
$parent is part of the binding context, which is only available during the evaluation of the data-bind (i.e. to the binding handler).
In your viewmodel structure, you'll have to come up with a way to communicate between models yourself. For example, by passing parent view models, or by passing along shared observables. The problem you're describing can be solved by using data-bind="visible: $root.userVM.IsAuthenticated", like I answered in your previous question.
If you'd like to go with the other approach, here's an example on how to share an observable between viewmodels.
var ChildViewModel = function(sharedObs) {
this.myObs = sharedObs;
this.setObs = function() {
this.myObs(!this.myObs());
}.bind(this);
}
var RootViewModel = function() {
this.myObs = ko.observable(false);
this.vm1 = new ChildViewModel(this.myObs);
this.vm2 = new ChildViewModel(this.myObs);
this.vm3 = new ChildViewModel(this.myObs);
}
ko.applyBindings(new RootViewModel());
div { width: 25%; display: inline-block; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="with: vm1">
<h4>vm1</h4>
<p data-bind="text: myObs"></p>
<button data-bind="click: setObs">
flip
</button>
</div>
<div data-bind="with: vm2">
<h4>vm2</h4>
<p data-bind="text: myObs"></p>
<button data-bind="click: setObs">
flip
</button>
</div>
<div data-bind="with: vm3">
<h4>vm3</h4>
<p data-bind="text: myObs"></p>
<button data-bind="click: setObs">
flip
</button>
</div>
Note that each of the child view models also have write permission, so you'll have to be careful to not accidentally update the observable

Select File. Submit. Cannot select file again

I have a form with different fields and with input type="file". I use fileupload jQuery library.
Select file
Call
$('#some_id').fileupload().fileupload(
'send',
{
files: file,
url: widget.options.saveVideoUrl,
}
).success(
//...
(first fileupload called for init)
Try again to select file. Got: No files selected, clear console, etc..
Upd.1
The problem appear in E-commerce framework Magento2 in admin area.
The described form appear in such entity like 'slide-out panel'. It means that there is div block and this block wrapped in aside block using javascript.
<button onclick="jQuery('#new').modal('openModal')" .... >
<span>New</span>
</button>
Here is demo example:
Admin URL: https://iwdagency.com/magento2/admin
Username: admin
Password: admin123
Open Products / Catalog / select any product / click on New category
You should see following panel:
On such panel I've added by php constructor fields:
<div class="admin__field field field-new_video_screenshot " data-ui-id="product-tabs-tab-google-experiment-fieldset-element-form-field-new-video-screenshot">
<label class="label admin__field-label" for="..." data-ui-id="product-tabs-tab-google-experiment-fieldset-element-file-image-label"><span>Preview Image</span></label>
<div class="admin__field-control control">
<input id="...." name="image" data-ui-id="product-tabs-tab-google-experiment-fieldset-element-file-image" value="" title="Preview Image" type="file">
</div>
</div>
Script:
define([
'jquery',
'jquery/ui',
'Magento_Ui/js/modal/modal',
'mage/translate',
'mage/backend/tree-suggest',
'mage/backend/validation'
], function ($) {
'use strict';
$.widget('mage.newDialog', {
_create: function () {
var widget = this;
var newVideoForm = $('#new');
this.element.modal({
type: 'slide',
modalClass: 'mage-new-dialog form-inline',
title: $.mage.__('Create'),
buttons: [{
text: $.mage.__('Create'),
class: 'action-primary',
click: function (e) {
var file = $('#new_screenshot').get(0).files[0];
var result = $('#new_screenshot').fileupload().fileupload(
'send',
{
files: file,
url: widget.options.saveUrl,
}
).success(
function(result, textStatus, jqXHR)
{
var data = JSON.parse(result);
data['url'] = $('#new_url').val();
data['name'] = $('#new_name').val();
data['description'] = $('#new_description').val();
$('#media_gallery_content').trigger('addItem', data);
$('#new').modal('closeModal')
}
);
}
}],
});
}
});
return $.mage.newDialog;
});
I found the problem.
In my case the problem appear after initialization fileUpload library.
When I selected input:file, the library wasn't initialized (read as infected). When I pressed the button, initialization ran and any operations with this input become unavailable.
Solution is following: clone our input before it become infected, than do our operations and at the end replace existing infected input with its healthy copy, created before.

Dynamically adding items into view and posting back to controller (ASP.NET MVC 4)

I have a ASP.NET MVC 4 app with model, that contains and colection (IEnumerable<T> or IList<T>), i.e.:
class MyModel
{
public int Foo { get; set; }
public IList<Item> Bar { get; set; }
}
class Item
{
public string Baz { get; set; }
}
And I render the data in view with classic #for..., #Html.EditorFor... ad so on. Now there's a need to add on client side to add dynamically new items and then post it back to server.
I'm looking for an easy solution to handle the adding (in JavaScript), aka not manually creating all the inputs etc. Probably to get it somehow from editor template view. And to add it the way that when the form is submitted back to server the model binder will be able to properly create the IList<T> collection, aka some smart handling of inputs' names. I read a bunch of articles, but nothing that was easy and worked reliably (without magic strings like collection variable names, AJAX callbacks to server, ...).
So far this looks promising, but I'd like to rather rely on rendering (items known in advance) on server.
I'm not sure what do you mean 'collection variable names' and probably my solution is kind of magic you noticed.
My solution is based on copying existing editor for element and altering input names via Javascript.
First of all, we need to mark up our editor. This is a code of form outputs editor for collection
#for (var i = 0; i < Model.Count; i++)
{
<div class="contact-card">
#Html.LabelFor(c => Model[i].FirstName, "First Name")
#Html.TextBoxFor(c => Model[i].FirstName)
<br />
#Html.LabelFor(c => Model[i].LastName, "Last Name")
#Html.TextBoxFor(c => Model[i].LastName)
<br />
#Html.LabelFor(c => Model[i].Email, "Email")
#Html.TextBoxFor(c => Model[i].Email)
<br />
#Html.LabelFor(c => Model[i].Phone, "Phone")
#Html.TextBoxFor(c => Model[i].Phone)
<hr />
</div>
}
Our editor is placed into div with class contact-card. On rendering, ASP.NET MVC gives names like [0].FirstName, [0].LastName ... [22].FirstName, [22].LastName to inputs used as property editors. On submitting Model Binder converts this to collection of entities based both on indexes and property names.
Next we create javascript function that copies last editor and increases index in brackets by 1. On submitting it adds additional element to collection:
var lastContent = $("#contact-form .contact-card").last().clone();
$("#contact-form .contact-card").last().after(lastContent);
$("#contact-form .contact-card")
.last()
.find("input")
.each(function () {
var currentName = $(this).attr("name");
var regex = /\[([0-9])\]/;
var newName = currentName.replace(regex, '[' + (parseInt(currentName.match(regex)[1]) + 1) + ']');
$(this).val('');
$(this).attr('name', newName);
});
VOILA!! On submitting we will get one more element!
At the end I did similar stuff what STO was suggesting, but with the custom (non-linear) indices for collections suggested by Phil Haack.
This uses manual naming of elements (so I'm not binding directly to the model) and I can use custom instances (for empty element templates). I've also created some helper methods to generate me the code for the instance, so it's easier to generate code for actual instances from the model or empty ones.
I did this with help of Backbone (for file uploader) where i insert template whenever user click #addButton
View:
#using Telerik.Web.Mvc.UI
#{
ViewBag.Title = "FileUpload";
Layout = "~/Areas/Administration/Views/Shared/_AdminLayout.cshtml";
}
<div id="fileViewContainer" class="span12">
<h2>File upload</h2>
#foreach(var fol in (List<string>)ViewBag.Folders){
<span style="cursor: pointer;" class="uploadPath">#fol</span><br/>
}
#using (Html.BeginForm("FileUpload", "CentralAdmin", new { id = "FileUpload" }, FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<label for="file1">Path:</label>
<input type="text" style="width:400px;" name="destinacionPath" id="destinacionPath"/><br />
<div id="fileUploadContainer">
<input type="button" class="addButton" id="addUpload" value="Add file"/>
<input type="button" class="removeButton" id="removeUpload" value="Remove file"/>
</div>
<input type="submit" value="Upload" />
}
</div>
<script type="text/template" id="uploadTMP">
<p class="uploadp"><label for="file1">Filename:</label>
<input type="file" name="files" id="files"/></p>
</script>
#{
Html.Telerik().ScriptRegistrar().Scripts(c => c.Add("FileUploadInit.js"));
}
FileUploadInit.js
$(document).ready(function () {
var appInit = new AppInit;
Backbone.history.start();
});
window.FileUploadView = Backbone.View.extend({
initialize: function () {
_.bindAll(this, 'render', 'addUpload', 'removeUpload', 'selectPath');
this.render();
},
render: function () {
var tmp = _.template($("#uploadTMP").html(), {});
$('#fileUploadContainer').prepend(tmp);
return this;
},
events: {
'click .addButton': 'addUpload',
'click .removeButton': 'removeUpload',
'click .uploadPath': 'selectPath'
},
addUpload: function (event) {
this.render();
},
removeUpload: function (event) {
$($('.uploadp')[0]).remove();
},
selectPath: function (event) {
$('#destinacionPath').val($(event.target).html());
}
});
var AppInit = Backbone.Router.extend({
routes: {
"": "defaultRoute"
},
defaultRoute: function (actions) {
var fileView = new FileUploadView({ el: $("#fileViewContainer") });
}
});
In Controller you keep your code
I Hope this will help.

Categories