Make field invisible via js code - ODOO 9 - javascript

I'm looking for a method which makes a field invisible on js (i'm making a custom widget 'InvisibleIfEmptry').
I tried to override _check_visibility method when extending FormWidget.AbstractField class :
var core = require('web.core'),
form_common = require('web.form_common');
var InvisibleIfEmpty = form_common.AbstractField.extend({
start: function() {
this.on("change:effective_readonly", this, function() {
this._toggle_label();
this._check_visibility();
});
this.render_value();
this._toggle_label();
},
_check_visibility: function() {
if (this.get("effective_readonly"))
this.$el.toggleClass('o_form_invisible',true);
}
this.$el.toggleClass('o_form_invisible',false);
}
}, .....
but this makes invisible only the field's value, not the label.
my guess is to alter some of field_manager's values but i can't figure out which one ?
Thank you for your help :)

Here is my JS code for doing that :
odoo.define('myCustomModule', function(require)
{
'use strict';
var core = require('web.core'),
form_common = require('web.form_common'),
form_view = require('web.FormView');
form_common.AbstractField.include({
start: function() {
this._super();
// Check visibility logic below when content
// changes or the form swich to view mode
this.field_manager.on("view_content_has_changed", this, function() {
this._check_visibility();
});
this.on("change:effective_readonly", this, function() {
this._toggle_label();
this._check_visibility();
});
},
_check_visibility: function() {
// If the form is in view mode and the field is empty,
// make the field invisible
window.alert(this.);
if (this.field_manager.get("actual_mode") === "view" ) {
if(this.get("value") == false){
this.$el.toggleClass('o_form_invisible',true);
this.$label.toggleClass('o_form_invisible',true);
}else{
this.$el.toggleClass('o_form_invisible',this.get("effective_invisible"));
this.$label.toggleClass('o_form_invisible',this.get("effective_invisible"));
}
}else{
this.$el.toggleClass('o_form_invisible',this.get("effective_invisible"));
this.$label.toggleClass('o_form_invisible',this.get("effective_invisible"));
}
},
});
});
But this applies to all my modules.
Does Any one know how to get module/model name from AbstractField ?

Related

Scope of variable in DOJO when created from within function

In a DOJO widget there is code in the postCreate and destroy method to create/start and stop a timer like you can see below. Depending on the value in a drop down box the timer is started or stopped. This works fine so far.
postCreate: function() {
var deferred = this.own(<...some action...>)[0];
deferred.then(
lang.hitch(this, function(result) {
this.t = new dojox.timing.Timer(result.autoRefreshInterval * 1000);
this.t.onTick = lang.hitch(this, function() {
console.info("get new data");
});
this.t.onStart = function() {
console.info("starting timer");
};
this.t.onStop = function() {
console.info("timer stopped");
};
})
);
this.selectAutoRefresh.on("change", lang.hitch(this, function(value) {
if (value == "Automatic") {
this.t.start();
} else {
this.t.stop();
}
}));
},
When leaving the page the timer is still active so I want to stop it when I leave the page using DOJOs destroy() method.
destroy: function() {
this.t.stop();
},
This however throws a this.t.stop is not a function exception. It seems like this.t is not created in the context of the widget although I use lang.hitch(this...
What am I missing here?
I solved that by just renaming the variable t to refreshTimer. Maybe t is some kind of reserved variable in Dojo?

Multiple click handlers for a single element

I've written a few events to handle opening and closing of a snap js drawer. This code below works, but I feel it could be written more efficiently. Any suggestions?
function openMobileMenu() {
event.preventDefault();
snapper.open('left');
$('#btn-menu').off('click', openMobileMenu);
$('#btn-menu').on('click', closeMobileMenu);
}
function closeMobileMenu() {
event.preventDefault();
snapper.close('left');
$('#btn-menu').on('click', openMobileMenu);
$('#btn-menu').off('click', closeMobileMenu);
}
$('#btn-menu').on('click', openMobileMenu);
Make your code modular and your concepts explicit.
You can start by creating a MobileMenu object which encapsulates the logic.
Note: The following code was not tested.
var MobileMenu = {
_snapper: null,
_$button: null,
_direction: 'left',
init: function (button, snapper, direction) {
this._$button = $(button);
this._snapper = snapper;
if (direction) this._direction = direction;
this._toggleSnapperVisibilityWhenButtonClicked();
},
_toggleSnapperVisibilityWhenbuttonClicked: function () {
this._$button.click($.proxy(this.toggle, this));
},
toggle: function () {
var snapperClosed = this._snapper.state().state == 'closed',
operation = snapperClosed? 'open' : 'closed';
this._snapper[operation](this._direction);
}
};
Then in your page you can just do the following to initialize your feature:
var mobileMenu = Object.create(MobileMenu).init('#btn-menu', snapper);
Modularizing your code will make it more maintainable and understandable in the long run, but also allow you to unit test it. You also gain a lot more flexibily because of the exposed API of your component which allows other code to interact with it.
E.g. you can now toggle the menu visibility with mobileMenu.toggle().
Use a variable to keep track of the state:
var menu_open = false;
$("#btn-menu").on('click', function(event) {
event.preventDefault();
if (menu_open) {
snapper.close('left');
} else {
snapper.open('left');
}
menu_open = !menu_open; // toggle variable
});
snap has a .state() method, which returns an object stuffed with properties, one of which is .state.
I think you want :
$('#btn-menu').on('click', function() {
if(snapper.state().state == "closed") {
snapper.open('left');
} else {
snapper.close('left');
}
});
Or, in one line :
$('#btn-menu').on('click', function() {
snapper[['close','open'][+(snapper.state().state == 'closed')]]('left');
});
Also, check How do I make a toggle button? in the documentation.

AngularJS : How to run JavaScript from inside Directive after directive is compiled and linked

I have a responsive template that I am trying to use with my Angularjs app. This is also my first Angular app so I know I have many mistakes and re-factoring in my future.
I have read enough about angular that I know DOM manipulations are suppose to go inside a directive.
I have a javascript object responsible for template re-sizes the side menu and basically the outer shell of the template. I moved all of this code into a directive and named it responsive-theme.
First I added all the methods that are being used and then I defined the App object at the bottom. I removed the function bodies to shorten the code.
Basically the object at the bottom is a helper object to use with all the methods.
var directive = angular.module('bac.directive-manager');
directive.directive('responsiveTheme', function() {
return {
restrict: "A",
link: function($scope, element, attrs) {
// IE mode
var isRTL = false;
var isIE8 = false;
var isIE9 = false;
var isIE10 = false;
var sidebarWidth = 225;
var sidebarCollapsedWidth = 35;
var responsiveHandlers = [];
// theme layout color set
var layoutColorCodes = {
};
// last popep popover
var lastPopedPopover;
var handleInit = function() {
};
var handleDesktopTabletContents = function () {
};
var handleSidebarState = function () {
};
var runResponsiveHandlers = function () {
};
var handleResponsive = function () {
};
var handleResponsiveOnInit = function () {
};
var handleResponsiveOnResize = function () {
};
var handleSidebarAndContentHeight = function () {
};
var handleSidebarMenu = function () {
};
var _calculateFixedSidebarViewportHeight = function () {
};
var handleFixedSidebar = function () {
};
var handleFixedSidebarHoverable = function () {
};
var handleSidebarToggler = function () {
};
var handleHorizontalMenu = function () {
};
var handleGoTop = function () {
};
var handlePortletTools = function () {
};
var handleUniform = function () {
};
var handleAccordions = function () {
};
var handleTabs = function () {
};
var handleScrollers = function () {
};
var handleTooltips = function () {
};
var handleDropdowns = function () {
};
var handleModal = function () {
};
var handlePopovers = function () {
};
var handleChoosenSelect = function () {
};
var handleFancybox = function () {
};
var handleTheme = function () {
};
var handleFixInputPlaceholderForIE = function () {
};
var handleFullScreenMode = function() {
};
$scope.App = {
//main function to initiate template pages
init: function () {
//IMPORTANT!!!: Do not modify the core handlers call order.
//core handlers
handleInit();
handleResponsiveOnResize(); // set and handle responsive
handleUniform();
handleScrollers(); // handles slim scrolling contents
handleResponsiveOnInit(); // handler responsive elements on page load
//layout handlers
handleFixedSidebar(); // handles fixed sidebar menu
handleFixedSidebarHoverable(); // handles fixed sidebar on hover effect
handleSidebarMenu(); // handles main menu
handleHorizontalMenu(); // handles horizontal menu
handleSidebarToggler(); // handles sidebar hide/show
handleFixInputPlaceholderForIE(); // fixes/enables html5 placeholder attribute for IE9, IE8
handleGoTop(); //handles scroll to top functionality in the footer
handleTheme(); // handles style customer tool
//ui component handlers
handlePortletTools(); // handles portlet action bar functionality(refresh, configure, toggle, remove)
handleDropdowns(); // handle dropdowns
handleTabs(); // handle tabs
handleTooltips(); // handle bootstrap tooltips
handlePopovers(); // handles bootstrap popovers
handleAccordions(); //handles accordions
handleChoosenSelect(); // handles bootstrap chosen dropdowns
handleModal();
$scope.App.addResponsiveHandler(handleChoosenSelect); // reinitiate chosen dropdown on main content resize. disable this line if you don't really use chosen dropdowns.
handleFullScreenMode(); // handles full screen
},
fixContentHeight: function () {
handleSidebarAndContentHeight();
},
setLastPopedPopover: function (el) {
lastPopedPopover = el;
},
addResponsiveHandler: function (func) {
responsiveHandlers.push(func);
},
// useful function to make equal height for contacts stand side by side
setEqualHeight: function (els) {
var tallestEl = 0;
els = jQuery(els);
els.each(function () {
var currentHeight = $(this).height();
if (currentHeight > tallestEl) {
tallestColumn = currentHeight;
}
});
els.height(tallestEl);
},
// wrapper function to scroll to an element
scrollTo: function (el, offeset) {
pos = el ? el.offset().top : 0;
jQuery('html,body').animate({
scrollTop: pos + (offeset ? offeset : 0)
}, 'slow');
},
scrollTop: function () {
App.scrollTo();
},
// wrapper function to block element(indicate loading)
blockUI: function (ele, centerY) {
var el = jQuery(ele);
el.block({
message: '<img src="./assets/img/ajax-loading.gif" align="">',
centerY: centerY !== undefined ? centerY : true,
css: {
top: '10%',
border: 'none',
padding: '2px',
backgroundColor: 'none'
},
overlayCSS: {
backgroundColor: '#000',
opacity: 0.05,
cursor: 'wait'
}
});
},
// wrapper function to un-block element(finish loading)
unblockUI: function (el) {
jQuery(el).unblock({
onUnblock: function () {
jQuery(el).removeAttr("style");
}
});
},
// initializes uniform elements
initUniform: function (els) {
if (els) {
jQuery(els).each(function () {
if ($(this).parents(".checker").size() === 0) {
$(this).show();
$(this).uniform();
}
});
} else {
handleUniform();
}
},
updateUniform : function(els) {
$.uniform.update(els);
},
// initializes choosen dropdowns
initChosenSelect: function (els) {
$(els).chosen({
allow_single_deselect: true
});
},
initFancybox: function () {
handleFancybox();
},
getActualVal: function (ele) {
var el = jQuery(ele);
if (el.val() === el.attr("placeholder")) {
return "";
}
return el.val();
},
getURLParameter: function (paramName) {
var searchString = window.location.search.substring(1),
i, val, params = searchString.split("&");
for (i = 0; i < params.length; i++) {
val = params[i].split("=");
if (val[0] == paramName) {
return unescape(val[1]);
}
}
return null;
},
// check for device touch support
isTouchDevice: function () {
try {
document.createEvent("TouchEvent");
return true;
} catch (e) {
return false;
}
},
isIE8: function () {
return isIE8;
},
isRTL: function () {
return isRTL;
},
getLayoutColorCode: function (name) {
if (layoutColorCodes[name]) {
return layoutColorCodes[name];
} else {
return '';
}
}
};
}
};
});
Originally the App.init() object method would be called at the bottom of any regular html page, and I have others that do certain things also that would be used on specific pages like Login.init() for the login page and so forth.
I did read that stackoverflow post
"Thinking in AngularJS" if I have a jQuery background? and realize that I am trying to go backwards in a sense, but I want to use this template that I have so I need to retro fit this solution.
I am trying to use this directive on my body tag.
<body ui-view="dashboard-shell" responsive-theme>
<div class="page-container">
<div class="page-sidebar nav-collapse collapse" ng-controller="SidemenuController">
<sidemenu></sidemenu>
</div>
<div class="page-content" ui-view="dashboard">
</div>
</div>
</body>
So here is my problem. This kinda sorta works. I don't get any console errors but when I try to use my side menu which the javascript for it is in the directive it doesn't work until I go inside the console and type App.init(). After that all of the template javascript works. I want to know how to do responsive theme stuff in these directives. I have tried using it both in the compile and link sections. I have tried putting the code in compile and link and calling the $scope.App.init() from a controller and also at the bottom after defining everything. I also tried putting this in jsfiddle but can't show a true example without having the console to call App.init().
My end design would be having some way to switch the pages through ui-router and when a route gets switched it calls the appropriate methods or re-runs the directive or something. The only method that will run on every page is the App.init() method and everything else is really page specific. And technically since this is a single page app the App.init() only needs to run once for the application. I have it tied to a parent template inside ui-router and the pages that will switch all use this shell template. There are some objects that need to access other to call their methods.
Im sorry in advance for maybe a confusing post. I am struggling right now trying to put together some of the ways that you do things from an angular perspective. I will continue to edit the post as I get responses to give further examples.
You said I have read enough about angular that I know DOM manipulations are suppose to go inside a directive but it sounds like you missed the point of a directive. A directive should handle DOM manipulation, yes, but not one directive for the entire page. Each element (or segment) of the page should have its own directive (assuming DOM manip needs to be done on that element) and then the $controller should handle the interactions between those elements and your data (or model).
You've created one gigantic directive and are trying to have it do way too much. Thankfully, you've kinda sorta designed your code in such a way that it shouldn't be too hard to break it up into several directives. Basically, each of your handle functions should be its own directive.
So you'd have something like:
.directive('sidebarMenu', function(){
return {
template: 'path/to/sidebar/partial.html',
link: function(scope, elem, attrs){
// insert the code for your 'handleSidebarMenu()' function here
}
};
})
.directive('horizontalMenu', function(){
return {
template: 'path/to/horizontal/partial.html',
link: function(scope, elem, attrs){
// insert the code for your 'handleHorizontalMenu()' function here
}
};
})
and then your view would look something like:
<body ui-view="dashboard-shell" responsive-theme>
<div class="page-container">
<div class="page-sidebar nav-collapse collapse">
<horizontal-menu></horizontal-menu>
<sidebar-menu></sidebar-menu>
</div>
<div class="page-content" ui-view="dashboard">
</div>
</div>
</body>
And then you don't need a SidebarmenuController because your controller functions shouldn't be handling DOM elements like the sidebar. The controller should just handling the data that you're going to display in your view, and then the view (or .html file) will handle the displaying and manipulation of that data by its use of the directives you've written.
Does that make sense? Just try breaking that huge directive up into many smaller directives that handle specific elements or specific tasks in the DOM.

Enter new values in ComboBox

I am trying to develop a combobox that allows the user to enter value which get lookuped and if it is missing in the list it get viualize as a new value (I'd thought about a image left to the field) and in additon causes the field to be bound to another property of the model where it is assigned to.
In addition the user should be able to clear the combo value.
I started by using a provided class from this guy and extended it with the required settings. I managed to prevent a issue which causes the 'x' trigger to hide continious, but I couldn't manage the difference between userinput - selection - set - clear (set value by form)
Here is a snippet from what I've got so far
Ext.define('Ext.ux.form.field.InputCombo', {
extend: 'Ext.form.field.ComboBox',
alias: 'widget.inputcombo',
trigger2Cls: 'x-form-clear-trigger',
newRecordFieldname: null, // the name if this value is new
originName: null,
initComponent: function () {
var me = this;
me.addEvents(
/**
* #event beforeclear
* #param {Combo} Combo The combo that triggered the event
*/
'beforeclear',
/**
* #event beforeclear
* #param {Combo} Combo The combo that triggered the event
*/
'clear'
);
me.callParent(arguments);
me.on('specialkey', this.onSpecialKeyDown, me);
me.on('afterrender', function () {
me.onShowClearTrigger(false);
}, me);
this.on('change', function() {
this.createdRecord(this);
});
this.on('select', function() {
this.onSetExisting(this);
});
},
createdRecord: function(ref) {
ref.newRecord = true;
ref.selectedRecord = false;
ref.originName = ref.name;
ref.name = ref.newRecordFieldname;
ref.style = 'background:url(../resources/themes/images/default/dd/drop-add.gif) no-repeat left center;'; // does not work
},
setExisting: function(ref) {
ref.newRecord = false;
ref.selectedRecord = true;
ref.style = '';
ref.name = ref.originName;
},
/**
* #private onSpecialKeyDown
* eventhandler for special keys
*/
onSpecialKeyDown: function (obj, e, opt) {
if ( e.getKey() == e.ESC ) {
this.clear();
}
},
onShowClearTrigger: function (show) {
var me = this;
if (show) {
me.triggerEl.each(function (el, c, i) {
if (i === 1) {
el.setWidth(el.originWidth, false);
el.setVisible(true);
me.active = true;
}
});
} else {
me.triggerEl.each(function (el, c, i) {
if (i === 1) {
el.originWidth = el.getWidth();
if (el.originWidth !== 0) { // prevent double hide
el.setWidth(0, false);
el.setVisible(false);
me.active = false;
}
}
});
}
me.updateLayout();
},
/* lines remoced. see linked answer */
});
What I haven't get managed to get working (and the point where I need help) is to show a image for a new record and that the field behaves in the same manner regardless if I first enter something and clear or set a value, select a value and so on. By know the field behaves quiete different or does not work at all.
In additon it should be compatible to the upcoming 4.2 release. Dunno if this important to mention.
Any help appreciated!

How do I add a function to an element via jQuery?

I want to do something like this:
$('.dynamicHtmlForm').validate = function() {
return true;
}
$('.dynamicHtmlForm .saveButton').click(function() {
if (!$(this).closest('.dynamicHtmlForm').validate()) {
return false;
}
return true;
});
And then when I have a form of class dynamicHtmlForm, I want to be able to provide a custom validate() function:
$('#myDynamicHtmlForm').validate = function() {
// do some validation
if (there are errors) {
return false;
}
return true;
}
But I get this when I do this:
$(this).closest(".dynamicHtmlForm").validate is not a function
Is what I've described even possible? If so, what am I doing wrong?
Yes, it is technically possible. You will need to reference the element itself, however, and not the jQuery collection. This should work:
$('.dynamicHtmlForm').each(function (ix,o) {
o.validate = function() {
return true;
}
});
$('.dynamicHtmlForm .saveButton').click(function() {
if ($(this).closest('.dynamicHtmlForm')[0].validate()) {
return false;
}
return true;
}
jQuery.fn.validate = function(options) {
var defaults = {
validateOPtions1 : '',
validateOPtions2 : ''
};
var settings = $.extend({}, defaults, options);
return this.each(function() {
// you validation code goes here
});
};
$(document).ready(function() {
$('selector').click(function() {
$('some selector').validate();
// or if you used any options in your code that you
// want the user to enter. then you go :
$('some selector').validate({
validateOPtions1: 'value1',
validateOPtions2: 'value2'
});
});
});
You're not adding the function to the element, you're adding it to the jQuery wrapper around the element. Every time you pass a selector to jQuery, it will create a new wrapper for the found elements:
$('#myEl'); // gives a jQuery wrapper object
$('#myEl'); // creates another jQuery wrapper object
If you save the wrapped element to a variable and use that later, it would be a different story because you're accessing the saved jQuery wrapper object.
var dynamicHtmlForm = $('.dynamicHtmlForm');
dynamicHtmlForm.validate = function() {
return true;
}
$('.dynamicHtmlForm .saveButton').click(function() {
if (dynamicHtmlForm.validate()) {
return false;
}
return true;
}
You could also add the function directly to the element using
$('.dynamicHtmlForm')[0].validate = function () { return true; }
// and later...
if (!$(this).closest('.dynamicHtmlForm')[0].validate())
Or you could look at extending jQuery properly by writing a plugin.

Categories

Resources