I created a custom module that defines a widget. here are the .js file as well as the QWeb template:
bh_sidebar.js
odoo.define('sidebar_widget.bh_sidebar', function (require) {
'use strict';
var Widget = require('web.Widget');
var widgetRegistry = require('web.widget_registry');
var core = require('web.core');
var QWeb = core.qweb;
var _t = core._t;
var bh_sidebar = Widget.extend({
init: function () {
var self = this;
this._super(parent);
console.log('Widget initialized!');
},
events: {},
start: function () {
this.$el.append(QWeb.render('bh_sidebar_template'));
console.log('Widget started!');
}
});
widgetRegistry.add(
'bh_sidebar', bh_sidebar
)
})
bh_sidebar.xml
<?xml version="1.0"?>
<templates id="template" xml:space="preserve">
<t t-name="bh_sidebar_template">
<div style="background-color:red;" class="myClass">
Button 1
Button 2
</div>
</t>
</templates>
I've added both to the backend assets using:
<odoo>
<data>
<template id="assets_backend" inherit_id="web.assets_backend">
<xpath expr="script[last()]" position="after">
<script type="text/javascript" src="/sidebar_widget/static/src/js/bh_sidebar.js"></script>
<!-- <script type="text/javascript" src="/sidebar_widget/static/src/js/renderer.js"></script> -->
<link rel="stylesheet" type="text/scss" href="/sidebar_widget/static/src/scss/widget.scss"></link>
</xpath>
</template>
</data>
</odoo>
I've also added the templates to the manifest file.
This widget is supposed to display a red rectangle with two links, button 1 and button 2. I want this widget to be independent of any type of view, and just show up at the top of the screen beneath Odoo's navigation bar. How do I that? if I try to run my code as is, nothing happens. I've been at this for a week. Nothing I found on the internet helped. The documentation is criminally outdated, the ressources scarce.
I would appreciate any help.
Thank you.
Your widget will not be instantiated and inserted into the DOM automatically. To do this, you can hook into any of the already existing high-level widgets. For example, using the show_application method on the WebClient:
var WebClient = require('web.WebClient');
WebClient.include({
show_application: function () {
var sup = this._super.apply(this, arguments);
// Instantiate
var sidebar = new bh_sidebar();
// Insert in DOM
sidebar.insertAfter(this.$('.o_main'));
return sup;
},
});
I think u should render this view by using controller mechanism this will render this view independent from others view.
Related
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.
This question already has answers here:
Event binding on dynamically created elements?
(23 answers)
Closed 4 years ago.
I have this script in my view file, the purpose of it is to populate a section of the same view, but only a small section of it, which is a HTML div with Bootstrap panel classes:
<script type="text/javascript">
function GetVehicles() {
$.get('#Context.Request.Scheme://#hostname/#controller/GetVehicles', {id: #Model.Id}, function (response) {
$("#tabOutVehicles").html(response);
});
}
function GetMedInfo() {
$.get('#Context.Request.Scheme://#hostname/#controller/GetMedInfo', {id: #Model.Id}, function (response) {
$("#tabOutMedInfo").html(response);
});
}
</script>
My complete view, which will display the output generated by the script above:
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
<script src="~/js/site.js"></script>
<div class="panel panel-primary">
<div class="panel-heading">
Vehicles
</div>
<div id="collapseVehicles" class="panel-collapse collapse">
<div id="tabOutVehicles">
</div>
</div>
</div>
<div class="panel panel-primary">
<div class="panel-heading">
<strong style="color:white">Medical Info</strong>
</div>
<div id="collapseMedInfo" class="panel-collapse collapse">
<div id="tabOutMedInfo">
</div>
</div>
</div>
<script type="text/javascript">
function GetVehicles() {
$.get('#Context.Request.Scheme://#hostname/#controller/GetVehicles', {id: #Model.Id}, function (response) {
$("#tabOutVehicles").html(response);
});
}
function GetMedInfo() {
$.get('#Context.Request.Scheme://#hostname/#controller/GetMedInfo', {id: #Model.Id}, function (response) {
$("#tabOutMedInfo").html(response);
});
}
</script>
By clicking on the hyperlinks inside the Bootstrap panel divs, the jQuery method hits my controller action, returns the response, and then puts the response in the applicable div (#tabOutMedInfo / #tabOutVehicles). Both these actions return partial views. Here is what my partial view looks like, both look the same except for the model properties that differ:
#model MyViewModel
<div class="panel-body">
<a data-url="#Url.Action("EditMedInfo", "Controller", new { id = Model.Id })" data-toggle="ajax-modal" data-target="#EditMedInfo" class="btn btn-default">Edit</a>
<div class="form-horizontal">
<div class="">
<div class="form-group">
<div class="col-md-5 col-sm-5 col-xs-5"><label asp-for="Variable" class="control-label"></label></div>
<div class="col-md-7 col-sm-7 col-xs-7">
#Html.DisplayFor(item => item.Variable)
</div>
</div>
</div>
</div>
</div>
When the above hyperlink is clicked, it is supposed to execute JavaScript code that loads a modal for editing, which is not happening, instead it does not open the modal. The JavaScript code is located in my site.js file, which is being imported in my main view.
What I've tried:
I moved the script import tag to both my partial views and then removed it from my view, it then causes the JavaScript code to run and display my modal, but this solution only worked for a while.
New problem:
Then a new problem started occurring, if the first partial view is loaded, and you were to load the second partial view without closing the web page, it causes site.js to be loaded twice into the view, once by the first partial view, and a second time by the second partial view. With site.js loaded twice, it somehow causes my post action to be hit twice, resulting in data being inserted twice for one post action.
I then decided to move site.js to my _Layout.cshtml (ideally how it should be) and tried again, this way around caused the partial views to render like normal, but once again, the modals didn't show when clicking on the hyperlinks found in the partial views.
My theory:
The way I understand it, when the jQuery get methods loads the partial views, it prevents the partial view from seeing site.js, even though it was loaded in by my _Layout.cshtml.
What I preferrably don't want to change:
I don't want to get rid of my small get actions, I built it like this to keep my users as much as possible on one page, and calling the get actions seperately reduces their data usage.
Am I loading site.js correctly? Why doesn't my partial views see site.js?
Edit:
This is my _Layout.cshtml
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>#ViewBag.Title</title>
<environment names="Development">
#*Other scripts omitted *#
<script src="~/js/site.js" asp-append-version="true" defer></script>
</environment>
<environment names="Staging,Production">
#*Other scripts omitted *#
<script src="~/js/site.js" asp-append-version="true" defer></script>
</environment>
</head>
<body>
<div id="modal-placeholder"></div>
#RenderBody()
#RenderSection("scripts", required: false)
</body>
</html>
And here is the JavaScript code located inside site.js:
$(function () {
var placeholderElement = $('#modal-placeholder');
var redirectUrl = "";
$('a[data-toggle="ajax-modal"]').click(function (event) {
var url = $(this).data('url');
redirectUrl = window.location.href;
$.get(url).done(function (data) {
placeholderElement.html(data);
placeholderElement.find('.modal').modal('show');
});
});
placeholderElement.on('click', '[data-save="modal"]', function (event) {
event.preventDefault();
var form = $(this).parents('.modal').find('form');
var actionUrl = form.attr('action');
var dataToSend = form.serialize();
$.post(actionUrl, dataToSend).done(function (data) {
var newBody = $('.modal-body', data);
placeholderElement.find('.modal-body').replaceWith(newBody);
var isValid = newBody.find('[name="IsValid"]').val() == 'True';
if (isValid) {
placeholderElement.find('.modal').modal('hide');
window.location.assign(redirectUrl);
}
}
});
});
});
The JavaScript was written like above to handle server side validation on modals, see the brilliant article here: https://softdevpractice.com/blog/asp-net-core-mvc-ajax-modals/
When you 'paste' the HTML for the panel into the page once returned from the ajax call, it breaks all existing event handlers. So you need to re-bind the handler anchor elements. I would wrap your event handler inside a function, and call that function both on initial page load and also in the ajax success handler (after you've pasted the HTML). Assuming you're using jQuery, it would look something like this:
function bindAjaxModalButtons() {
$('[data-toggle="ajax-modal"]').click(function() {
// ... your event handler code here
});
}
$(function() {
bindAjaxModalButtons(); // attaches event handlers on initial page load
});
Then change your ajax functions like so:
function GetMedInfo() {
$.get('#Context.Request.Scheme://#hostname/#controller/GetMedInfo', {id: #Model.Id}, function (response) {
$("#tabOutMedInfo").html(response);
bindAjaxModalButtons(); // <-- this will attach the event handler to the new buttons
});
}
EDIT: now that I can see your JS file, here is what it should look like:
$(function () {
var placeholderElement = $('#modal-placeholder');
var redirectUrl = "";
bindAjaxModalButtons();
placeholderElement.on('click', '[data-save="modal"]', function (event) {
event.preventDefault();
var form = $(this).parents('.modal').find('form');
var actionUrl = form.attr('action');
var dataToSend = form.serialize();
$.post(actionUrl, dataToSend).done(function (data) {
var newBody = $('.modal-body', data);
placeholderElement.find('.modal-body').replaceWith(newBody);
var isValid = newBody.find('[name="IsValid"]').val() == 'True';
if (isValid) {
placeholderElement.find('.modal').modal('hide');
window.location.assign(redirectUrl);
}
}
});
});
});
function bindAjaxModalButtons() {
var btns = $('a[data-toggle="ajax-modal"]');
btns.unbind(); // <-- so that existing buttons don't get double-bound
btns.click(function (event) {
var url = $(this).data('url');
redirectUrl = window.location.href;
var placeholderElement = $('#modal-placeholder');
$.get(url).done(function (data) {
placeholderElement.html(data);
placeholderElement.find('.modal').modal('show');
});
});
}
I have a Master-Detail application and I would like the first list item to be selected automatically once the application is loaded. I have tried the following solution but it does not work:
onAfterRendering: function() {
var oList = this.getView().byId("bankList");
var aItems = oList.getItems("listItems");
console.log(aItems);
if (aItems.length) {
aItems[0].setSelected(true);
}
}
The strange thing is that aItems seems to be empty even though it contains the correct details. Below is what is printed in the console when I console.log(aItems):
The result of console.log(aItems)
Guessing that you are using a sap.m.List, You should use the setSelectedItem() function, that receives the Item object as parameter.
Furthermore I recommend you to avoid using the onAfterRendering lifecycle method, to keep the lifecycle clean. There are usually many items that you can use, for example updateFinished for sap.m.List
Here the snippet
<!DOCTYPE html>
<html>
<head>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<meta charset="utf-8">
<title>MVC with XmlView</title>
<!-- Load UI5, select "blue crystal" theme and the "sap.m" control library -->
<script id='sap-ui-bootstrap' src='https://sapui5.hana.ondemand.com/resources/sap-ui-core.js' data-sap-ui-theme='sap_belize_plus' data-sap-ui-libs='sap.m' data-sap-ui-xx-bindingSyntax='complex'></script>
<!-- DEFINE RE-USE COMPONENTS - NORMALLY DONE IN SEPARATE FILES -->
<!-- define a new (simple) View type as an XmlView
- using data binding for the Button text
- binding a controller method to the Button's "press" event
- also mixing in some plain HTML
note: typically this would be a standalone file -->
<script id="view1" type="sapui5/xmlview">
<mvc:View xmlns="sap.m" xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc" controllerName="my.own.controller">
<List items="{/options}" mode="SingleSelect" updateFinished="onUpdateFinished">
<StandardListItem title="{value}"></StandardListItem>
</List>
</mvc:View>
</script>
<script>
// define a new (simple) Controller type
sap.ui.controller("my.own.controller", {
onUpdateFinished: function(oEvent) {
var oList = oEvent.getSource();
var aItems = oList.getItems();
oList.setSelectedItem(aItems[0])
}
});
/*** THIS IS THE "APPLICATION" CODE ***/
// create some dummy JSON data
var data = {
options: [{
key: '1',
value: 'option 1'
}, {
key: '2',
value: 'option 2'
}, {
key: '3',
value: 'option 3'
}]
};
var oJSONModel = new sap.ui.model.json.JSONModel();
oJSONModel.setData(data);
// instantiate the View
var myView = sap.ui.xmlview({
viewContent: jQuery('#view1').html()
}); // accessing the HTML inside the script tag above
myView.setModel(oJSONModel);
// put the View onto the screen
myView.placeAt('content');
</script>
</head>
<body id='content' class='sapUiBody'>
</body>
</html>
So I've been porting my app over to ractive. I'm currently serving up each page from Express using Swig... to render out a ractive template client side. This seems a bit nuts when I could serve up one page and use ractive to do all the client side rendering.
I understand that Ractive doesn't ship with a router, and indeed leaves one out by design (to give flexibility etc - it makes sense). I've googled, and trawled through Stack overflow and see a number of third party router libraries are recommended...
However, I can't find any tutorials or advise on best practice regarding routing with rative. So my question is - are there any available?
Thanks
** EDIT **
Following on from martypdx comment - here are the routes I need:
/home <!-- a static page -->
/list <!-- a list of items -->
/list/:itemID <!-- a link to an items detail page -->
/contact <!-- a static contact page -->
In express I've built a simple api that handles all the db stuff.. all the basic CRUD stuff. I'm using socket.io to send all the data back and forth.
Ractive.js and routing? It's pretty simple actually, no magic needed.
<!-- main.js -->
{{# route('/') }}<home />{{/}}
{{# route('/about') }}<about />{{/}}
{{# route('/404') }}<not-found />{{/}}
<script>
component.exports = {
data: {
route: function(path){
// Your pattern matching algorithm here. Return true if match.
// You can use location.pathname, location.search and location.hash
}
},
components: {
home: /* home constructor */,
about: /* about constructor */,
'not-found': /* not-found constructor */,
}
};
</script>
You have other options, like computed properties:
{{# isHome }}<home />{{/}}
{{# isAbout }}<about />{{/}}
{{# isNotFound }}<not-found />{{/}}
<script>
function router(path){
return function(){
// Your pattern matching algorithm here. Return true if match.
// You can use location.pathname, location.search and location.hash
}
}
component.exports = {
computed: {
isHome: router('/'),
isAbout: router('/about'),
isNotFound: router('/404'),
},
components: {
home: /* home constructor */,
about: /* about constructor */,
'not-found': /* not-found constructor */,
}
};
</script>
As for passing down the data, you also have a lot of options. You can use oninit that runs when the component is created and ready to render (or in this case, when a section becomes truthy, ie. {{# isHome }} when isHome is true). Here's an example of <home /> fetching data oninit:
<!-- home.js -->
<h1>Home</h1>
<div>{{someDynamicData}}</div>
<script>
var SomeDataStore = require('stores/some-data-store');
component.exports = {
oninit: function(){
// Let's say your data comes from an AJAX call
$.get(...).then(function(response){
this.set('someDynamicData', response);
}.bind(this));
// Or say it's from a listenable data store (like Reflux)
SomeDataStore.listen(function(){
this.set('someDynamicData', SomeDataStore.getSomeDynamicData());
});
}
}
</script>
Or you can have the routing component fetch and pass it down (and the routing component "owns" the data). This works well with the computed approach since you can observe computed values and fetch when the appropriate view appears.
<!-- main.js -->
{{# isHome }}<home data="{{homeData}}" />{{/}}
{{# isAbout) }}<about data="{{aboutData}}" />{{/}}
{{# isNotFound }}<not-found data="{{notFoundData}}" />{{/}}
<script>
component.exports = {
...
oninit: function(){
this.observe('isHome', function(isHome){
if(!isHome) return;
// still the same here, you can use any data source, as long as you
// set to a data property in the end
this.get(...).then(function(response){
this.set('homeData', response);
}.bind(this));
});
this.observe('isAbout', function(isAbout){
if(!isAbout) return;
...
});
}
};
</script>
I would like to customize the dnd-upload.js file. For example I want to override _adjustGuiIfFinished method. I've tried several times to do it, but all of them failed (I was using some tutorials/examples:
http://blogs.alfresco.com/wp/wabson/2011/08/12/custom-admin-console-components-in-share/
https://forums.alfresco.com/forum/developer-discussions/alfresco-share-development/javascrip-autocompete-yahoo-library-03152012)
Here are my steps:
create .js file, let's say dnd-upload-ep.js with following contents:
(function ()
{
Alfresco.EpDNDUpload = function Alfresco_EpDNDUpload(htmlId)
{
Alfresco.EpDNDUpload.superclass.constructor.call(this, htmlId);
return this;
};
YAHOO.extend(Alfresco.EpDNDUpload, Alfresco.DNDUpload,{
_adjustGuiIfFinished: function EpDNDUpload_adjustGuiIfFinished()
{
...
// function body with some modifications
...
})
});
include just created file in .ftl. Override dnd-upload.get.html.ftl and include file in js dependencies:
...
<#markup id="js">
<#-- JavaScript Dependencies -->
<#script type="text/javascript" src="${url.context}/res/components/upload/dnd-upload.js" group="upload"/>
<#script type="text/javascript" src="${url.context}/res/components/upload/dnd-upload-ep.js" group="upload"/>
</#>
...
instead of original DNDUpload instantiate recently created EpDNDUpload. To do it override dnd-upload.get.js changing 'Widget instantiation metadata':
...
//Widget instantiation metadata...
var dndUpload = {
id : "EpDNDUpload",
name : "Alfresco.EpDNDUpload",
assignTo : "dndUpload"
};
model.widgets = [dndUpload];
...
Seems that it should work, but it doesn't. When I upload document I got error that Alfresco.EpDNDUpload not created and could't be instantiated.
Is there is something I do wrong?