diff --git a/web_widget_boolean_switch/README.rst b/web_widget_boolean_switch/README.rst new file mode 100644 index 00000000..b9ddb18b --- /dev/null +++ b/web_widget_boolean_switch/README.rst @@ -0,0 +1,132 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +========================= +Web widget boolean switch +========================= + +This module add a widget ``boolean_switch`` to render boolean fields. One +of it's main features is to quick edit that field without enter in edit mode +from list view or form view. + + +Configuration +============= + +In the view (test on tree view and form view), you can declare any boolean +field using this widget. + +Example +------- + +``` + +``` + +Options +------- + +quick_edit +~~~~~~~~~~ + +extra +~~~~~ +``extra`` is used to set +`bootstrap-switch `_ options. + +*Available options*: + + * **size**: The checkbox size - default: `null` - values: null, 'mini', + 'small', 'normal', 'large' + * **animate**: Animate the switch - default: `true` + * **inverse**: Inverse switch direction - default: `false` + * **onColor**: Color of the left side of the switch - default: `"primary"` - + values: 'primary', 'info', 'success', 'warning', 'danger', 'default' + * **offColor**: Color of the right side of the switch - default: `default` - + values: 'primary', 'info', 'success', 'warning', 'danger', 'default' + * **onText**: Text of the left side of the switch - default: `"ON"` + * **offText**: Text of the right side of the switch - default: `"OFF"` + * **labelText**: Text of the center handle of the switch - default: + `" "`, + * **handleWidth**: Width of the left and right sides in pixels - default: + `"auto"`, + * **labelWidth**: Width of the center handle in pixels - default: `"auto"` + * **baseClass**: Global class prefix - default: `"bootstrap-switch"` + * **wrapperClass**: Container element class(es) - default: `"wrapper"` + + +.. warning:: + + Those parameters are overwritten by this module or highly discouraged: + + * **radioAllOff**: Allow this radio button to be unchecked by the user - + default: `false` + * **state**: The checkbox state - default: `true` + * **disabled**: Disable state - default: `false` + * **readonly**: Readonly state - default: `false` + * **onInit**: Callback function to execute on initialization - default: + `function() {}`, + * **onSwitchChange**: Callback function to execute on switch state change - + default: `function() {}` + * **indeterminate**: Indeterminate state - default: `false` + +attrs +----- + +This attribute is supported as expected! + +context +------- + +``context`` is sent to the ``write`` method of the field model in case of +special needs with the quick edition. + + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/162/7.0 + +Known issues / Roadmap +====================== + +* Manage Null values +* + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and +welcomed feedback `here `_. + + +Credits +======= + +Contributors +------------ + +* Pierre Verkest + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit http://odoo-community.org. diff --git a/web_widget_boolean_switch/__init__.py b/web_widget_boolean_switch/__init__.py new file mode 100644 index 00000000..87ba95e2 --- /dev/null +++ b/web_widget_boolean_switch/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +# © 2015 +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). diff --git a/web_widget_boolean_switch/__openerp__.py b/web_widget_boolean_switch/__openerp__.py new file mode 100644 index 00000000..f4aeb8cb --- /dev/null +++ b/web_widget_boolean_switch/__openerp__.py @@ -0,0 +1,177 @@ +# -*- coding: utf-8 -*- +# © +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +{ + "name": "Web widget boolean switch", + "summary": "This module add widget to render boolean fields", + "version": "7.0.1.0.0", + "category": "web", + "website": "https://odoo-community.org/", + "author": ", Odoo Community Association (OCA)", + "license": "AGPL-3", + "application": False, + "installable": True, + "external_dependencies": { + "python": [], + "bin": [], + }, + "depends": [ + "base", + "web", + ], + "data": [ + ], + "js": [ + 'static/lib/bootstrap-switch/bootstrap-switch.js', + 'static/src/js/web_widget_boolean_switch.js', + ], + 'test': [ + 'static/test/web_widget_boolean_switch.js', + ], + "css": [ + 'static/lib/bootstrap-switch/bootstrap-switch.css', + ], + 'qweb': [ + 'static/src/xml/web_widget_boolean_switch.xml', + ], + "demo": [ + 'demo/res_users_view.xml', + ], + 'description': """ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +========================= +Web widget boolean switch +========================= + +This module add a widget ``boolean_switch`` to render boolean fields. One +of it's main features is to quick edit that field without enter in edit mode +from list view or form view. + + +Configuration +============= + +In the view (test on tree view and form view), you can declare any boolean +field using this widget. + +Example +------- + +``` + +``` + +Options +------- + +quick_edit +~~~~~~~~~~ + +extra +~~~~~ +``extra`` is used to set +`bootstrap-switch `_ options. + +*Available options*: + + * **size**: The checkbox size - default: `null` - values: null, 'mini', + 'small', 'normal', 'large' + * **animate**: Animate the switch - default: `true` + * **indeterminate**: Indeterminate state - default: `false` + * **inverse**: Inverse switch direction - default: `false` + * **onColor**: Color of the left side of the switch - default: `"primary"` - + values: 'primary', 'info', 'success', 'warning', 'danger', 'default' + * **offColor**: Color of the right side of the switch - default: `default` - + values: 'primary', 'info', 'success', 'warning', 'danger', 'default' + * **onText**: Text of the left side of the switch - default: `"ON"` + * **offText**: Text of the right side of the switch - default: `"OFF"` + * **labelText**: Text of the center handle of the switch - default: + `" "`, + * **handleWidth**: Width of the left and right sides in pixels - default: + `"auto"`, + * **labelWidth**: Width of the center handle in pixels - default: `"auto"` + * **baseClass**: Global class prefix - default: `"bootstrap-switch"` + * **wrapperClass**: Container element class(es) - default: `"wrapper"` + + +.. warning:: + + Those parameters are overwritten by this module or highly discouraged: + + * **radioAllOff**: Allow this radio button to be unchecked by the user - + default: `false` + * **state**: The checkbox state - default: `true` + * **disabled**: Disable state - default: `false` + * **readonly**: Readonly state - default: `false` + * **onInit**: Callback function to execute on initialization - default: + `function() {}`, + * **onSwitchChange**: Callback function to execute on switch state change - + default: `function() {}` + +attrs +----- + +This attribute is supported as expected! + +context +------- + +``context`` is sent to the ``write`` method of the field model in case of +special needs with the quick edition. + + +.. image:: https://odoo-community.org/website/image/ir.attachment/ + 5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/162/7.0 + +Known issues / Roadmap +====================== + +* Manage Null values +* + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and +welcomed feedback `here `_. + + +Credits +======= + +Contributors +------------ + +* Pierre Verkest + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit http://odoo-community.org. + + """, +} diff --git a/web_widget_boolean_switch/demo/res_users_view.xml b/web_widget_boolean_switch/demo/res_users_view.xml new file mode 100644 index 00000000..d1c201e6 --- /dev/null +++ b/web_widget_boolean_switch/demo/res_users_view.xml @@ -0,0 +1,79 @@ + + + + + + res.users.search + res.users + + + + + + + + + + res.users.tree + res.users + + + + + + + + + res.users.form + res.users + + + + + + + + + + res.users.tree + res.users + + + + + + + + + + + + Tree Editable Users + ir.actions.act_window + res.users + form + + + + + + tree + + + + + + + diff --git a/web_widget_boolean_switch/static/description/icon.png b/web_widget_boolean_switch/static/description/icon.png new file mode 100644 index 00000000..3a0328b5 Binary files /dev/null and b/web_widget_boolean_switch/static/description/icon.png differ diff --git a/web_widget_boolean_switch/static/description/icon.svg b/web_widget_boolean_switch/static/description/icon.svg new file mode 100644 index 00000000..a7a26d09 --- /dev/null +++ b/web_widget_boolean_switch/static/description/icon.svg @@ -0,0 +1,79 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/web_widget_boolean_switch/static/lib/bootstrap-switch/README.md b/web_widget_boolean_switch/static/lib/bootstrap-switch/README.md new file mode 100644 index 00000000..af265be5 --- /dev/null +++ b/web_widget_boolean_switch/static/lib/bootstrap-switch/README.md @@ -0,0 +1,12 @@ +Bootstrap-switch +================ + +from: https://github.com/nostalgiaz/bootstrap-switch + +``bootstrap-switch.js`` and ``bootstrap-switch.css`` files came from ``dist/`` +directories at this [revision](https://github.com/nostalgiaz/bootstrap-switch/ +tree/a39a1e9d6059822235bf4a9c0e7700a7e6a5ad19/dist) the september 30th 2015. + +The default library doesn't allow to change value through javascript if +switcher is set `readonly` or `disabled`. We add a small change to +allow it when ``skip`` is ``true``. diff --git a/web_widget_boolean_switch/static/lib/bootstrap-switch/bootstrap-switch.css b/web_widget_boolean_switch/static/lib/bootstrap-switch/bootstrap-switch.css new file mode 100644 index 00000000..6c0cc1dd --- /dev/null +++ b/web_widget_boolean_switch/static/lib/bootstrap-switch/bootstrap-switch.css @@ -0,0 +1,195 @@ +/* ======================================================================== + * bootstrap-switch - v3.3.2 + * http://www.bootstrap-switch.org + * ======================================================================== + * Copyright 2012-2013 Mattia Larentis + * + * ======================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ======================================================================== + */ + +.bootstrap-switch { + display: inline-block; + direction: ltr; + cursor: pointer; + border-radius: 4px; + border: 1px solid; + border-color: #cccccc; + position: relative; + text-align: left; + overflow: hidden; + line-height: 8px; + z-index: 0; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + vertical-align: middle; + -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; +} +.bootstrap-switch .bootstrap-switch-container { + display: inline-block; + top: 0; + border-radius: 4px; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); +} +.bootstrap-switch .bootstrap-switch-handle-on, +.bootstrap-switch .bootstrap-switch-handle-off, +.bootstrap-switch .bootstrap-switch-label { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + cursor: pointer; + display: inline-block !important; + height: 100%; + padding: 6px 12px; + font-size: 14px; + line-height: 20px; +} +.bootstrap-switch .bootstrap-switch-handle-on, +.bootstrap-switch .bootstrap-switch-handle-off { + text-align: center; + z-index: 1; +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary { + color: #fff; + background: #337ab7; +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info { + color: #fff; + background: #5bc0de; +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success { + color: #fff; + background: #5cb85c; +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning { + background: #f0ad4e; + color: #fff; +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger { + color: #fff; + background: #d9534f; +} +.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default, +.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default { + color: #000; + background: #eeeeee; +} +.bootstrap-switch .bootstrap-switch-label { + text-align: center; + margin-top: -1px; + margin-bottom: -1px; + z-index: 100; + color: #333333; + background: #ffffff; +} +.bootstrap-switch .bootstrap-switch-handle-on { + border-bottom-left-radius: 3px; + border-top-left-radius: 3px; +} +.bootstrap-switch .bootstrap-switch-handle-off { + border-bottom-right-radius: 3px; + border-top-right-radius: 3px; +} +.bootstrap-switch input[type='radio'], +.bootstrap-switch input[type='checkbox'] { + position: absolute !important; + top: 0; + left: 0; + margin: 0; + z-index: -1; + opacity: 0; + filter: alpha(opacity=0); +} +.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-on, +.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-off, +.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-label { + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; +} +.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-on, +.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-off, +.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-label { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; +} +.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-on, +.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-off, +.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-label { + padding: 6px 16px; + font-size: 18px; + line-height: 1.3333333; +} +.bootstrap-switch.bootstrap-switch-disabled, +.bootstrap-switch.bootstrap-switch-readonly, +.bootstrap-switch.bootstrap-switch-indeterminate { + cursor: default !important; +} +.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-on, +.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-on, +.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-on, +.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-off, +.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-off, +.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-off, +.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-label, +.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-label, +.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-label { + opacity: 0.5; + filter: alpha(opacity=50); + cursor: default !important; +} +.bootstrap-switch.bootstrap-switch-animate .bootstrap-switch-container { + -webkit-transition: margin-left 0.5s; + -o-transition: margin-left 0.5s; + transition: margin-left 0.5s; +} +.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-on { + border-bottom-left-radius: 0; + border-top-left-radius: 0; + border-bottom-right-radius: 3px; + border-top-right-radius: 3px; +} +.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-off { + border-bottom-right-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 3px; + border-top-left-radius: 3px; +} +.bootstrap-switch.bootstrap-switch-focused { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6); + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6); +} +.bootstrap-switch.bootstrap-switch-on .bootstrap-switch-label, +.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-off .bootstrap-switch-label { + border-bottom-right-radius: 3px; + border-top-right-radius: 3px; +} +.bootstrap-switch.bootstrap-switch-off .bootstrap-switch-label, +.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-on .bootstrap-switch-label { + border-bottom-left-radius: 3px; + border-top-left-radius: 3px; +} diff --git a/web_widget_boolean_switch/static/lib/bootstrap-switch/bootstrap-switch.js b/web_widget_boolean_switch/static/lib/bootstrap-switch/bootstrap-switch.js new file mode 100644 index 00000000..804c586e --- /dev/null +++ b/web_widget_boolean_switch/static/lib/bootstrap-switch/bootstrap-switch.js @@ -0,0 +1,744 @@ +/* ======================================================================== + * bootstrap-switch - v3.3.2 + * http://www.bootstrap-switch.org + * ======================================================================== + * Copyright 2012-2013 Mattia Larentis + * + * ======================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ======================================================================== + */ + +(function() { + var slice = [].slice; + + (function($, window) { + "use strict"; + var BootstrapSwitch; + BootstrapSwitch = (function() { + function BootstrapSwitch(element, options) { + if (options == null) { + options = {}; + } + this.$element = $(element); + this.options = $.extend({}, $.fn.bootstrapSwitch.defaults, { + state: this.$element.is(":checked"), + size: this.$element.data("size"), + animate: this.$element.data("animate"), + disabled: this.$element.is(":disabled"), + readonly: this.$element.is("[readonly]"), + indeterminate: this.$element.data("indeterminate"), + inverse: this.$element.data("inverse"), + radioAllOff: this.$element.data("radio-all-off"), + onColor: this.$element.data("on-color"), + offColor: this.$element.data("off-color"), + onText: this.$element.data("on-text"), + offText: this.$element.data("off-text"), + labelText: this.$element.data("label-text"), + handleWidth: this.$element.data("handle-width"), + labelWidth: this.$element.data("label-width"), + baseClass: this.$element.data("base-class"), + wrapperClass: this.$element.data("wrapper-class") + }, options); + this.prevOptions = {}; + this.$wrapper = $("
", { + "class": (function(_this) { + return function() { + var classes; + classes = ["" + _this.options.baseClass].concat(_this._getClasses(_this.options.wrapperClass)); + classes.push(_this.options.state ? _this.options.baseClass + "-on" : _this.options.baseClass + "-off"); + if (_this.options.size != null) { + classes.push(_this.options.baseClass + "-" + _this.options.size); + } + if (_this.options.disabled) { + classes.push(_this.options.baseClass + "-disabled"); + } + if (_this.options.readonly) { + classes.push(_this.options.baseClass + "-readonly"); + } + if (_this.options.indeterminate) { + classes.push(_this.options.baseClass + "-indeterminate"); + } + if (_this.options.inverse) { + classes.push(_this.options.baseClass + "-inverse"); + } + if (_this.$element.attr("id")) { + classes.push(_this.options.baseClass + "-id-" + (_this.$element.attr("id"))); + } + return classes.join(" "); + }; + })(this)() + }); + this.$container = $("
", { + "class": this.options.baseClass + "-container" + }); + this.$on = $("", { + html: this.options.onText, + "class": this.options.baseClass + "-handle-on " + this.options.baseClass + "-" + this.options.onColor + }); + this.$off = $("", { + html: this.options.offText, + "class": this.options.baseClass + "-handle-off " + this.options.baseClass + "-" + this.options.offColor + }); + this.$label = $("", { + html: this.options.labelText, + "class": this.options.baseClass + "-label" + }); + this.$element.on("init.bootstrapSwitch", (function(_this) { + return function() { + return _this.options.onInit.apply(element, arguments); + }; + })(this)); + this.$element.on("switchChange.bootstrapSwitch", (function(_this) { + return function(e) { + if (false === _this.options.onSwitchChange.apply(element, arguments)) { + if (_this.$element.is(":radio")) { + return $("[name='" + (_this.$element.attr('name')) + "']").trigger("previousState.bootstrapSwitch", true); + } else { + return _this.$element.trigger("previousState.bootstrapSwitch", true); + } + } + }; + })(this)); + this.$container = this.$element.wrap(this.$container).parent(); + this.$wrapper = this.$container.wrap(this.$wrapper).parent(); + this.$element.before(this.options.inverse ? this.$off : this.$on).before(this.$label).before(this.options.inverse ? this.$on : this.$off); + if (this.options.indeterminate) { + this.$element.prop("indeterminate", true); + } + this._init(); + this._elementHandlers(); + this._handleHandlers(); + this._labelHandlers(); + this._formHandler(); + this._externalLabelHandler(); + this.$element.trigger("init.bootstrapSwitch", this.options.state); + } + + BootstrapSwitch.prototype._constructor = BootstrapSwitch; + + BootstrapSwitch.prototype.setPrevOptions = function() { + return this.prevOptions = $.extend(true, {}, this.options); + }; + + BootstrapSwitch.prototype.state = function(value, skip) { + if (typeof value === "undefined") { + return this.options.state; + } + if (!skip && (this.options.disabled || this.options.readonly)) { + return this.$element; + } + if (this.options.state && !this.options.radioAllOff && this.$element.is(":radio")) { + return this.$element; + } + if (this.$element.is(":radio")) { + $("[name='" + (this.$element.attr('name')) + "']").trigger("setPreviousOptions.bootstrapSwitch"); + } else { + this.$element.trigger("setPreviousOptions.bootstrapSwitch"); + } + if (this.options.indeterminate) { + this.indeterminate(false); + } + value = !!value; + this.$element.prop("checked", value).trigger("change.bootstrapSwitch", skip); + return this.$element; + }; + + BootstrapSwitch.prototype.toggleState = function(skip) { + if (this.options.disabled || this.options.readonly) { + return this.$element; + } + if (this.options.indeterminate) { + this.indeterminate(false); + return this.state(true); + } else { + return this.$element.prop("checked", !this.options.state).trigger("change.bootstrapSwitch", skip); + } + }; + + BootstrapSwitch.prototype.size = function(value) { + if (typeof value === "undefined") { + return this.options.size; + } + if (this.options.size != null) { + this.$wrapper.removeClass(this.options.baseClass + "-" + this.options.size); + } + if (value) { + this.$wrapper.addClass(this.options.baseClass + "-" + value); + } + this._width(); + this._containerPosition(); + this.options.size = value; + return this.$element; + }; + + BootstrapSwitch.prototype.animate = function(value) { + if (typeof value === "undefined") { + return this.options.animate; + } + value = !!value; + if (value === this.options.animate) { + return this.$element; + } + return this.toggleAnimate(); + }; + + BootstrapSwitch.prototype.toggleAnimate = function() { + this.options.animate = !this.options.animate; + this.$wrapper.toggleClass(this.options.baseClass + "-animate"); + return this.$element; + }; + + BootstrapSwitch.prototype.disabled = function(value) { + if (typeof value === "undefined") { + return this.options.disabled; + } + value = !!value; + if (value === this.options.disabled) { + return this.$element; + } + return this.toggleDisabled(); + }; + + BootstrapSwitch.prototype.toggleDisabled = function() { + this.options.disabled = !this.options.disabled; + this.$element.prop("disabled", this.options.disabled); + this.$wrapper.toggleClass(this.options.baseClass + "-disabled"); + return this.$element; + }; + + BootstrapSwitch.prototype.readonly = function(value) { + if (typeof value === "undefined") { + return this.options.readonly; + } + value = !!value; + if (value === this.options.readonly) { + return this.$element; + } + return this.toggleReadonly(); + }; + + BootstrapSwitch.prototype.toggleReadonly = function() { + this.options.readonly = !this.options.readonly; + this.$element.prop("readonly", this.options.readonly); + this.$wrapper.toggleClass(this.options.baseClass + "-readonly"); + return this.$element; + }; + + BootstrapSwitch.prototype.indeterminate = function(value) { + if (typeof value === "undefined") { + return this.options.indeterminate; + } + value = !!value; + if (value === this.options.indeterminate) { + return this.$element; + } + return this.toggleIndeterminate(); + }; + + BootstrapSwitch.prototype.toggleIndeterminate = function() { + this.options.indeterminate = !this.options.indeterminate; + this.$element.prop("indeterminate", this.options.indeterminate); + this.$wrapper.toggleClass(this.options.baseClass + "-indeterminate"); + this._containerPosition(); + return this.$element; + }; + + BootstrapSwitch.prototype.inverse = function(value) { + if (typeof value === "undefined") { + return this.options.inverse; + } + value = !!value; + if (value === this.options.inverse) { + return this.$element; + } + return this.toggleInverse(); + }; + + BootstrapSwitch.prototype.toggleInverse = function() { + var $off, $on; + this.$wrapper.toggleClass(this.options.baseClass + "-inverse"); + $on = this.$on.clone(true); + $off = this.$off.clone(true); + this.$on.replaceWith($off); + this.$off.replaceWith($on); + this.$on = $off; + this.$off = $on; + this.options.inverse = !this.options.inverse; + return this.$element; + }; + + BootstrapSwitch.prototype.onColor = function(value) { + var color; + color = this.options.onColor; + if (typeof value === "undefined") { + return color; + } + if (color != null) { + this.$on.removeClass(this.options.baseClass + "-" + color); + } + this.$on.addClass(this.options.baseClass + "-" + value); + this.options.onColor = value; + return this.$element; + }; + + BootstrapSwitch.prototype.offColor = function(value) { + var color; + color = this.options.offColor; + if (typeof value === "undefined") { + return color; + } + if (color != null) { + this.$off.removeClass(this.options.baseClass + "-" + color); + } + this.$off.addClass(this.options.baseClass + "-" + value); + this.options.offColor = value; + return this.$element; + }; + + BootstrapSwitch.prototype.onText = function(value) { + if (typeof value === "undefined") { + return this.options.onText; + } + this.$on.html(value); + this._width(); + this._containerPosition(); + this.options.onText = value; + return this.$element; + }; + + BootstrapSwitch.prototype.offText = function(value) { + if (typeof value === "undefined") { + return this.options.offText; + } + this.$off.html(value); + this._width(); + this._containerPosition(); + this.options.offText = value; + return this.$element; + }; + + BootstrapSwitch.prototype.labelText = function(value) { + if (typeof value === "undefined") { + return this.options.labelText; + } + this.$label.html(value); + this._width(); + this.options.labelText = value; + return this.$element; + }; + + BootstrapSwitch.prototype.handleWidth = function(value) { + if (typeof value === "undefined") { + return this.options.handleWidth; + } + this.options.handleWidth = value; + this._width(); + this._containerPosition(); + return this.$element; + }; + + BootstrapSwitch.prototype.labelWidth = function(value) { + if (typeof value === "undefined") { + return this.options.labelWidth; + } + this.options.labelWidth = value; + this._width(); + this._containerPosition(); + return this.$element; + }; + + BootstrapSwitch.prototype.baseClass = function(value) { + return this.options.baseClass; + }; + + BootstrapSwitch.prototype.wrapperClass = function(value) { + if (typeof value === "undefined") { + return this.options.wrapperClass; + } + if (!value) { + value = $.fn.bootstrapSwitch.defaults.wrapperClass; + } + this.$wrapper.removeClass(this._getClasses(this.options.wrapperClass).join(" ")); + this.$wrapper.addClass(this._getClasses(value).join(" ")); + this.options.wrapperClass = value; + return this.$element; + }; + + BootstrapSwitch.prototype.radioAllOff = function(value) { + if (typeof value === "undefined") { + return this.options.radioAllOff; + } + value = !!value; + if (value === this.options.radioAllOff) { + return this.$element; + } + this.options.radioAllOff = value; + return this.$element; + }; + + BootstrapSwitch.prototype.onInit = function(value) { + if (typeof value === "undefined") { + return this.options.onInit; + } + if (!value) { + value = $.fn.bootstrapSwitch.defaults.onInit; + } + this.options.onInit = value; + return this.$element; + }; + + BootstrapSwitch.prototype.onSwitchChange = function(value) { + if (typeof value === "undefined") { + return this.options.onSwitchChange; + } + if (!value) { + value = $.fn.bootstrapSwitch.defaults.onSwitchChange; + } + this.options.onSwitchChange = value; + return this.$element; + }; + + BootstrapSwitch.prototype.destroy = function() { + var $form; + $form = this.$element.closest("form"); + if ($form.length) { + $form.off("reset.bootstrapSwitch").removeData("bootstrap-switch"); + } + this.$container.children().not(this.$element).remove(); + this.$element.unwrap().unwrap().off(".bootstrapSwitch").removeData("bootstrap-switch"); + return this.$element; + }; + + BootstrapSwitch.prototype._width = function() { + var $handles, handleWidth; + $handles = this.$on.add(this.$off); + $handles.add(this.$label).css("width", ""); + handleWidth = this.options.handleWidth === "auto" ? Math.max(this.$on.width(), this.$off.width()) : this.options.handleWidth; + $handles.width(handleWidth); + this.$label.width((function(_this) { + return function(index, width) { + if (_this.options.labelWidth !== "auto") { + return _this.options.labelWidth; + } + if (width < handleWidth) { + return handleWidth; + } else { + return width; + } + }; + })(this)); + this._handleWidth = this.$on.outerWidth(); + this._labelWidth = this.$label.outerWidth(); + this.$container.width((this._handleWidth * 2) + this._labelWidth); + return this.$wrapper.width(this._handleWidth + this._labelWidth); + }; + + BootstrapSwitch.prototype._containerPosition = function(state, callback) { + if (state == null) { + state = this.options.state; + } + this.$container.css("margin-left", (function(_this) { + return function() { + var values; + values = [0, "-" + _this._handleWidth + "px"]; + if (_this.options.indeterminate) { + return "-" + (_this._handleWidth / 2) + "px"; + } + if (state) { + if (_this.options.inverse) { + return values[1]; + } else { + return values[0]; + } + } else { + if (_this.options.inverse) { + return values[0]; + } else { + return values[1]; + } + } + }; + })(this)); + if (!callback) { + return; + } + return setTimeout(function() { + return callback(); + }, 50); + }; + + BootstrapSwitch.prototype._init = function() { + var init, initInterval; + init = (function(_this) { + return function() { + _this.setPrevOptions(); + _this._width(); + return _this._containerPosition(null, function() { + if (_this.options.animate) { + return _this.$wrapper.addClass(_this.options.baseClass + "-animate"); + } + }); + }; + })(this); + if (this.$wrapper.is(":visible")) { + return init(); + } + return initInterval = window.setInterval((function(_this) { + return function() { + if (_this.$wrapper.is(":visible")) { + init(); + return window.clearInterval(initInterval); + } + }; + })(this), 50); + }; + + BootstrapSwitch.prototype._elementHandlers = function() { + return this.$element.on({ + "setPreviousOptions.bootstrapSwitch": (function(_this) { + return function(e) { + return _this.setPrevOptions(); + }; + })(this), + "previousState.bootstrapSwitch": (function(_this) { + return function(e) { + _this.options = _this.prevOptions; + if (_this.options.indeterminate) { + _this.$wrapper.addClass(_this.options.baseClass + "-indeterminate"); + } + return _this.$element.prop("checked", _this.options.state).trigger("change.bootstrapSwitch", true); + }; + })(this), + "change.bootstrapSwitch": (function(_this) { + return function(e, skip) { + var state; + e.preventDefault(); + e.stopImmediatePropagation(); + state = _this.$element.is(":checked"); + _this._containerPosition(state); + if (state === _this.options.state) { + return; + } + _this.options.state = state; + _this.$wrapper.toggleClass(_this.options.baseClass + "-off").toggleClass(_this.options.baseClass + "-on"); + if (!skip) { + if (_this.$element.is(":radio")) { + $("[name='" + (_this.$element.attr('name')) + "']").not(_this.$element).prop("checked", false).trigger("change.bootstrapSwitch", true); + } + return _this.$element.trigger("switchChange.bootstrapSwitch", [state]); + } + }; + })(this), + "focus.bootstrapSwitch": (function(_this) { + return function(e) { + e.preventDefault(); + return _this.$wrapper.addClass(_this.options.baseClass + "-focused"); + }; + })(this), + "blur.bootstrapSwitch": (function(_this) { + return function(e) { + e.preventDefault(); + return _this.$wrapper.removeClass(_this.options.baseClass + "-focused"); + }; + })(this), + "keydown.bootstrapSwitch": (function(_this) { + return function(e) { + if (!e.which || _this.options.disabled || _this.options.readonly) { + return; + } + switch (e.which) { + case 37: + e.preventDefault(); + e.stopImmediatePropagation(); + return _this.state(false); + case 39: + e.preventDefault(); + e.stopImmediatePropagation(); + return _this.state(true); + } + }; + })(this) + }); + }; + + BootstrapSwitch.prototype._handleHandlers = function() { + this.$on.on("click.bootstrapSwitch", (function(_this) { + return function(event) { + event.preventDefault(); + event.stopPropagation(); + _this.state(false); + return _this.$element.trigger("focus.bootstrapSwitch"); + }; + })(this)); + return this.$off.on("click.bootstrapSwitch", (function(_this) { + return function(event) { + event.preventDefault(); + event.stopPropagation(); + _this.state(true); + return _this.$element.trigger("focus.bootstrapSwitch"); + }; + })(this)); + }; + + BootstrapSwitch.prototype._labelHandlers = function() { + return this.$label.on({ + "click": function(e) { + return e.stopPropagation(); + }, + "mousedown.bootstrapSwitch touchstart.bootstrapSwitch": (function(_this) { + return function(e) { + if (_this._dragStart || _this.options.disabled || _this.options.readonly) { + return; + } + e.preventDefault(); + e.stopPropagation(); + _this._dragStart = (e.pageX || e.originalEvent.touches[0].pageX) - parseInt(_this.$container.css("margin-left"), 10); + if (_this.options.animate) { + _this.$wrapper.removeClass(_this.options.baseClass + "-animate"); + } + return _this.$element.trigger("focus.bootstrapSwitch"); + }; + })(this), + "mousemove.bootstrapSwitch touchmove.bootstrapSwitch": (function(_this) { + return function(e) { + var difference; + if (_this._dragStart == null) { + return; + } + e.preventDefault(); + difference = (e.pageX || e.originalEvent.touches[0].pageX) - _this._dragStart; + if (difference < -_this._handleWidth || difference > 0) { + return; + } + _this._dragEnd = difference; + return _this.$container.css("margin-left", _this._dragEnd + "px"); + }; + })(this), + "mouseup.bootstrapSwitch touchend.bootstrapSwitch": (function(_this) { + return function(e) { + var state; + if (!_this._dragStart) { + return; + } + e.preventDefault(); + if (_this.options.animate) { + _this.$wrapper.addClass(_this.options.baseClass + "-animate"); + } + if (_this._dragEnd) { + state = _this._dragEnd > -(_this._handleWidth / 2); + _this._dragEnd = false; + _this.state(_this.options.inverse ? !state : state); + } else { + _this.state(!_this.options.state); + } + return _this._dragStart = false; + }; + })(this), + "mouseleave.bootstrapSwitch": (function(_this) { + return function(e) { + return _this.$label.trigger("mouseup.bootstrapSwitch"); + }; + })(this) + }); + }; + + BootstrapSwitch.prototype._externalLabelHandler = function() { + var $externalLabel; + $externalLabel = this.$element.closest("label"); + return $externalLabel.on("click", (function(_this) { + return function(event) { + event.preventDefault(); + event.stopImmediatePropagation(); + if (event.target === $externalLabel[0]) { + return _this.toggleState(); + } + }; + })(this)); + }; + + BootstrapSwitch.prototype._formHandler = function() { + var $form; + $form = this.$element.closest("form"); + if ($form.data("bootstrap-switch")) { + return; + } + return $form.on("reset.bootstrapSwitch", function() { + return window.setTimeout(function() { + return $form.find("input").filter(function() { + return $(this).data("bootstrap-switch"); + }).each(function() { + return $(this).bootstrapSwitch("state", this.checked); + }); + }, 1); + }).data("bootstrap-switch", true); + }; + + BootstrapSwitch.prototype._getClasses = function(classes) { + var c, cls, i, len; + if (!$.isArray(classes)) { + return [this.options.baseClass + "-" + classes]; + } + cls = []; + for (i = 0, len = classes.length; i < len; i++) { + c = classes[i]; + cls.push(this.options.baseClass + "-" + c); + } + return cls; + }; + + return BootstrapSwitch; + + })(); + $.fn.bootstrapSwitch = function() { + var args, option, ret; + option = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : []; + ret = this; + this.each(function() { + var $this, data; + $this = $(this); + data = $this.data("bootstrap-switch"); + if (!data) { + $this.data("bootstrap-switch", data = new BootstrapSwitch(this, option)); + } + if (typeof option === "string") { + return ret = data[option].apply(data, args); + } + }); + return ret; + }; + $.fn.bootstrapSwitch.Constructor = BootstrapSwitch; + return $.fn.bootstrapSwitch.defaults = { + state: true, + size: null, + animate: true, + disabled: false, + readonly: false, + indeterminate: false, + inverse: false, + radioAllOff: false, + onColor: "primary", + offColor: "default", + onText: "ON", + offText: "OFF", + labelText: " ", + handleWidth: "auto", + labelWidth: "auto", + baseClass: "bootstrap-switch", + wrapperClass: "wrapper", + onInit: function() {}, + onSwitchChange: function() {} + }; + })(window.jQuery, window); + +}).call(this); diff --git a/web_widget_boolean_switch/static/src/js/web_widget_boolean_switch.js b/web_widget_boolean_switch/static/src/js/web_widget_boolean_switch.js new file mode 100644 index 00000000..82191e25 --- /dev/null +++ b/web_widget_boolean_switch/static/src/js/web_widget_boolean_switch.js @@ -0,0 +1,246 @@ +openerp.web_widget_boolean_switch = function(instance){ + "use strict"; + + instance.web.form.widgets.add('boolean_switch', + 'instance.web.form.FieldBooleanSwitch'); + + $.fn.bootstrapSwitch.defaults.size = 'mini'; + $.fn.bootstrapSwitch.defaults.onColor = 'success'; + + instance.web.BooleanSwitchWidget = instance.web.Class.extend({ + + init: function(checkboxes, opts, quick_edit_callback){ + var options = _.extend({}, opts ? opts : {}); + this.checkboxes = checkboxes; + + this.quick_edit = options.hasOwnProperty('quick_edit') ? + options.quick_edit : false; + var switchOptions = options.hasOwnProperty('extra') ? + options.extra : {}; + + + if(options.hasOwnProperty('onSwitchChange')){ + switchOptions.onSwitchChange = options.onSwitchChange; + } + this.checkboxes.bootstrapSwitch(switchOptions); + this.set_disabled(options.hasOwnProperty('disabled') ? + options.disabled : !this.quick_edit); + if(this.quick_edit && quick_edit_callback){ + this.checkboxes.on('switchChange.bootstrapSwitch', + quick_edit_callback); + } + }, + set_value: function(value){ + // the third parameter tell if we should skip to fire events + // and force change the state whatever it's readonly or disabled + this.checkboxes.bootstrapSwitch('state', value, true); + }, + set_readonly: function(value){ + this.checkboxes.bootstrapSwitch('readonly', value); + }, + set_disabled: function(value){ + this.checkboxes.bootstrapSwitch('disabled', value); + }, + }); + + // Form view + instance.web.form.FieldBooleanSwitch = instance.web.form.AbstractField.extend({ + + template: 'FieldBooleanSwitch', + + start: function(){ + this.$checkbox = $("input", this.$el); + var options = { + onSwitchChange: _.bind(function(event, state) { + // Test effective_readonly in case we are using quick_edit, + // and we are not in edit mode. + // We could use this.view.get('actual_mode') which sons + // semantically better, possible values are + // at least `view`, `edit`, `create`... ? to avoid doupt + // using bool seems safer! + if(!this.get('effective_readonly')){ + this.internal_set_value(state); + event.preventDefault(); + } + }, this), + }; + _.extend(options, this.modifiers ? this.modifiers : {}); + _.extend(options, this.options ? this.options : {}); + + this.switcher = new openerp.instances.instance0.web.BooleanSwitchWidget( + this.$checkbox, options, _.bind(function(event, state) { + // keep in mind that in case of view list editable + // actual_mode is undefined... + if(this.view.get('actual_mode') === 'view'){ + var id = this.view.dataset.ids[this.view.dataset.index]; + var values = {}; + values[this.name] = state; + var context = openerp.instances.instance0.web.pyeval.eval( + 'contexts', this.build_context()); + var model = new openerp.instances.instance0.web.Model(this.view.model); + model.call('write', [[id], values], + {'context': this.build_context()}); + this.internal_set_value(state, {'silent': true}); + this.getParent().datarecord[this.name] = state; + } + }, this)); + this.on("change:readonly", this, this.switcher_states); + this.setupFocus(this.$checkbox); + this._super(); + this.switcher_states.call(this); + }, + internal_set_value: function(value_, options) { + var tmp = this.no_rerender; + this.no_rerender = true; + this.set({'value': value_}, options); + this.no_rerender = tmp; + }, + switcher_states: function () { + this.switcher.set_readonly(this.get('readonly')); + if (!this.switcher.quick_edit){ + this.switcher.set_disabled(this.get('effective_readonly')); + } + }, + render_value: function() { + this.switcher.set_value(this.get('value')); + }, + focus: function() { + var input = this.$checkbox && this.$checkbox[0]; + return input ? input.focus() : false; + }, + }); + + // Helper methods + function apply_switcher(view, columns){ + var switch_fields = columns.filter(function(c){ + return c.widget === 'boolean_switch'; + }); + switch_fields.forEach(function(field){ + var checkboxes; + + var options = field.options ? py.eval(field.options) : {}; + + if(view.grouped){ + //Manage if it's grouped by boolean_switch widget field + var opt = {}; + _.extend(opt, options); + opt.disabled = true; + checkboxes = view.$el.find( + 'th.oe_list_group_name input[type="checkbox"]'); + new openerp.instances.instance0.web.BooleanSwitchWidget( + checkboxes, opt, null); + } + + _.extend(options, field.modifiers ? field.modifiers : {}); + checkboxes = view.$el.find('td[data-field=' + field.name + + '].oe_list_field_boolean_switch > input[type="checkbox"]'); + new openerp.instances.instance0.web.BooleanSwitchWidget( + checkboxes, options, _.bind(function(event, state) { + var id = $(event.target).data('rowid'); + var values = {}; + values[this.field.name] = state; + var context = field.context ? py.eval(field.context) : {}; + _.extend(context, view.session.user_context); + var model = new openerp.instances.instance0.web.Model(this.view.model); + model.call('write', [[id], values], + {'context': context}).then(_.bind(function(){ + if(!this.view.grouped){ + this.view.records._byId[this.id].attributes[ + this.field.name] = this.state; + } + }, {'view': this.view, 'field': this.field, + 'id': id, 'state': state})); + }, {'view': view, 'field': field}) + ); + }); + } + + // List view + instance.web.list.columns.add('field.boolean_switch', 'instance.web.list.FieldBooleanSwitch'); + + instance.web.list.FieldBooleanSwitch = instance.web.list.Column.extend({ + format: function (row_data, options) { + options = options || {}; + var attrs = {}; + if (options.process_modifiers !== false) { + attrs = this.modifiers_for(row_data); + } + if (attrs.invisible) { return ''; } + + if (!row_data[this.id]) { + return options.value_if_empty === undefined ? + '' : options.value_if_empty; + } + var readonly = attrs.hasOwnProperty('readonly') ? attrs.readonly : false; + return this._format(row_data, options, readonly); + }, + + _format: function (row_data, options, readonly) { + options = options ? options : {}; + var autofocus = this.hasOwnProperty('autofocus') ? + this.autofocus : false; + var tabindex = this.hasOwnProperty('tabindex') ? + parseInt(this.tabindex) : 0; + return _.str.sprintf('', + row_data[this.id].value ? 'checked="checked"' : '', + readonly ? 'readonly' : '', + row_data.hasOwnProperty('id') && _.isNumber(row_data.id.value) ? + row_data.id.value : -1, + autofocus ? 'autofocus' : '', + 'tabindex="' + tabindex + '"'); + } + }); + + instance.web.ListView.Groups.include({ + render: function(post_render){ + var self = this; + var prender = function(){ + apply_switcher(self.view, self.columns); + if (post_render) { post_render(); } + }; + return this._super(prender); + }, + }); + + instance.web.ListView.List.include({ + init: function(){ + //This is the case when on each line, a request to name_get + //id done, this cause a record to be re-render though on change. + //So we have to overide the events as I haven't found any good hooks + this._super.apply(this, arguments); + var self = this; + this.records.unbind('change'); + this.record_callbacks.change = function (event, record, attribute, value, old_value) { + var $row; + if (attribute === 'id') { + if (old_value) { + throw new Error(_.str.sprintf( _t("Setting 'id' attribute on existing record %s"), + JSON.stringify(record.attributes) )); + } + self.dataset.add_ids([value], self.records.indexOf(record)); + // Set id on new record + $row = self.$current.children('[data-id=false]'); + } else { + $row = self.$current.children( + '[data-id=' + record.get('id') + ']'); + } + $row.replaceWith(self.render_record(record)); + self.afterRowChanged($row, record, attribute); + }; + this.records.bind('change', this.record_callbacks.change); + }, + afterRowChanged: function($row, record, attribute){ + apply_switcher(this.view, this.columns, $row); + }, + }); + instance.web.ListView.include({ + reload_record: function(record){ + // in case of editable list, only update record is reloaded + // after edition + var self = this; + return this._super(record).then(function(){ + apply_switcher(self, self.columns); + }); + }, + }); +}; diff --git a/web_widget_boolean_switch/static/src/xml/web_widget_boolean_switch.xml b/web_widget_boolean_switch/static/src/xml/web_widget_boolean_switch.xml new file mode 100644 index 00000000..809fef2c --- /dev/null +++ b/web_widget_boolean_switch/static/src/xml/web_widget_boolean_switch.xml @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/web_widget_boolean_switch/static/test/web_widget_boolean_switch.js b/web_widget_boolean_switch/static/test/web_widget_boolean_switch.js new file mode 100644 index 00000000..645a597a --- /dev/null +++ b/web_widget_boolean_switch/static/test/web_widget_boolean_switch.js @@ -0,0 +1,205 @@ +openerp.testing.section('BooleanSwitchWidget.behaviors', + {dependences: ['web.web_widget_boolean_switch'], + rpc: 'mock', + templates: true, + }, function(test){ + test('BooleanSwitchWidget quick edit - enter in edit mode - cancel', + {asserts: 30}, function (instance, $fix, mock) { + + mock('demo:read', function () { + console.log("read data"); + return [{ id: 1, a: true, b: 'bar', c: 'baz' }]; + }); + mock('demo:fields_view_get', function () { + return { + type: 'form', + fields: { + a: {type: 'boolean', string: "A"}, + b: {type: 'char', string: "B"}, + c: {type: 'char', string: "C"} + }, + arch: '
' + + ' ' + + ' ' + + ' ' + + '', + }; + }); + var ds = new instance.web.DataSetStatic(null, 'demo', null, [1]); + ds.index = 0; + var form = new instance.web.FormView({}, ds, false, { + initial_mode: 'view', + disable_autofocus: false, + $buttons: $(), + $pager: $() + }); + var numTest = 1; + form.appendTo($fix); + var promise = form.do_show(); + check_values(form.$el.find(".oe_form_field_boolean_switch"), + true, false, false, + numTest++ + " - Init state: quick editable with true value"); + form.fields.a.switcher.checkboxes.click(); + check_values(form.$el.find('.oe_form_field_boolean_switch'), + false, false, false, + numTest++ + " - Quick edit to `false` value"); + form.to_edit_mode(); + check_values(form.$el.find('.oe_form_field_boolean_switch'), + false, false, false, + numTest++ + " - Move to edit mode value still false"); + form.fields.a.switcher.checkboxes.click(); + check_values(form.$el.find('.oe_form_field_boolean_switch'), + true, false, false, + numTest++ + " - edit to True"); + // remove class oe_form_dirty to avoid popup in unit test while canceling + form.$el.removeClass('oe_form_dirty'); + form.on_button_cancel.call(form); + check_values(form.$el.find('.oe_form_field_boolean_switch'), + false, false, false, + numTest++ + " - Cancel edit mode return still False"); + return promise; + + }); + +}); + +var check_values = function (scratchpad, value, readonly, disabled, + message){ + var $container = scratchpad.children(); + var $input = $container.find('input'); + strictEqual($input[0].checked, value, message + " - Input value"); + strictEqual($input[0].readOnly && $input[0].readOnly ? true : false, + readonly, message + " - Input readonly"); + strictEqual($input[0].disabled && $input[0].disabled ? true : false, disabled, + message + " - Input disabled"); + + var prefix = 'bootstrap-switch-'; + ok($container[0].classList.contains(prefix + (value ? 'on' : 'off')), + message + " - Bootstrap-switch value class"); + strictEqual($container[0].classList.contains(prefix + "readonly"), + readonly, message + " - Bootstrap-switch readonly class"); + strictEqual($container[0].classList.contains(prefix + "disabled"), + disabled, message + " - Bootstrap-switch disabled class"); + +}; + +openerp.testing.section('BooleanSwitchWidget.tests', + {'dependences': ['web.web_widget_boolean_switch'], + }, function(test){ + "use strict"; + + var init_check_values = function(instance, scratchpad, html, options, + checked, readonly, disabled, message){ + scratchpad.html(html); + var widget = new instance.web.BooleanSwitchWidget( + scratchpad.find('input'), options, null); + check_values(scratchpad, checked, readonly, disabled, message); + return widget; + }; + + test('BooleanSwitchWidget Class test method', function(instance, $scratchpad){ + + var numTest = 1; + var widget = init_check_values( + instance, $scratchpad, '', {}, + false, false, true, numTest++ + " - init values"); + + ok($scratchpad.children()[0].classList.contains('bootstrap-switch'), + "Basic bootstrap-switch init using BooleanSwitchWidget class"); + + widget.set_disabled(false); + check_values($scratchpad, false, false, false, + numTest++ + " - Enable"); + + widget.set_value(true); + check_values($scratchpad, true, false, false, + numTest++ + " - Set true"); + + widget.set_readonly(true); + check_values($scratchpad, true, true, false, + numTest++ + " - Set readonly"); + + widget.set_disabled(true); + check_values($scratchpad, true, true, true, + numTest++ + " - Disabled"); + + widget.set_value(false); + check_values($scratchpad, false, true, true, + numTest++ + " - set value whatever its Disabled and readonly"); + widget.set_readonly(false); + widget.set_value(true); + check_values($scratchpad, true, false, true, + numTest++ + " - set value whatever its Disabled"); + widget.set_disabled(false); + widget.set_readonly(true); + widget.set_value(false); + check_values($scratchpad, false, true, false, + numTest++ + " - set value whatever its readonly"); + }); + + test('BooleanSwitchWidget Class test init', function(instance, $scratchpad){ + var numTest = 1; + + init_check_values(instance, $scratchpad, + '', + {}, true, true, true, + numTest++ + " - init values disabled readonly checked"); + + init_check_values(instance, $scratchpad, + '', + {}, false, true, true, + numTest++ + " - init values disabled readonly"); + + init_check_values(instance, $scratchpad, + '', + {}, true, false, true, + numTest++ + " - init values disabled checked"); + + init_check_values(instance, $scratchpad, + '', + {'disabled': false}, true, true, false, + numTest++ + " - init values checked readonly"); + + init_check_values(instance, $scratchpad, + '', + {}, false, false, true, + numTest++ + " - By default input is disabled without any parameter"); + + init_check_values(instance, $scratchpad, + '', + {'disabled': false}, true, false, false, + numTest++ + " - init values checked"); + + init_check_values(instance, $scratchpad, + '', + {'disabled': false, 'quick_edit': true}, + false, true, false, + numTest++ + " - init values disabled readonly"); + + init_check_values(instance, $scratchpad, + '', + {'disabled': false}, false, false, false, + numTest++ + " - every thing is false"); + + init_check_values(instance, $scratchpad, + '', + {'quick_edit': true}, true, false, false, + numTest++ + " - quick edit enable widget"); + + init_check_values(instance, $scratchpad, + '', + {'quick_edit': true}, true, false, false, + numTest++ + " - quick edit enable widget case initial element is disabled"); + + init_check_values(instance, $scratchpad, + '', + {}, true, false, true, + numTest++ + " - By default widget is disabled test with checked"); + + init_check_values(instance, $scratchpad, + '', + {}, true, true, true, + numTest++ + " - By default widget is disabled test with checked and readonly"); + }); + +});