');
- self.$('.oe_pad_readonly').html(data);
- },
- on_fail: function() {
- self.$('.oe_pad_content').text(_t('Unable to load pad'));
- }
- });
- } else {
- self.$('.oe_pad_content').addClass('oe_pad_loading').show().text(_t("This pad will be initialized on first edit"));
- }
- }
- });
+ /**
+ * @override
+ */
+ isSet: function () {
+ return true;
},
- render_edit: function() {
- var self = this;
- this._configured_deferred.then(function() {
- if (! self.value || !_.str.startsWith(self.value, 'http')) {
- self.trigger_up('perform_model_rpc', {
- method: 'pad_generate_url',
- model: self.model,
- context: {
- model: self.model,
- field_name: self.name,
- object_id: self.model
- },
- on_success: function(data) {
- if (! data.url) {
- self.set_configured(false);
- } else {
- self.set_value(data.url);
- }
- self._url_request_deferred.resolve();
- },
- });
- } else {
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * Note that this method has some serious side effects: performing rpcs and
+ * setting the value of this field. This is not conventional and should not
+ * be copied in other code, unless really necessary.
+ *
+ * @override
+ * @private
+ */
+ _renderEdit: function () {
+ if (_.str.startsWith(this.value, 'http')) {
+ // here, we have a valid url, so we can simply display an iframe
+ // with the correct src attribute
+ var userName = encodeURIComponent(this.getSession().userName);
+ var url = this.value + '?showChat=false&userName=' + userName;
+ var content = '
';
+ this.$('.oe_pad_content').html(content);
+ } else if (this.value) {
+ // it looks like the field does not contain a valid url, so we just
+ // display it (it cannot be edited in that case)
+ this.$('.oe_pad_content').text(this.value);
+ } else {
+ // It is totally discouraged to have a render method that does
+ // non-rendering work, especially since the work in question
+ // involves doing RPCs and changing the value of the field.
+ // However, this is kind of necessary in this case, because the
+ // value of the field is actually only the url of the pad. The
+ // actual content will be loaded in an iframe. We could do this
+ // work in the basic model, but the basic model does not know that
+ // this widget is in edit or readonly, and we really do not want to
+ // create a pad url everytime a task without a pad is viewed.
+ var self = this;
+ this.urlDef = this._rpc({
+ method: 'pad_generate_url',
+ model: this.model,
+ context: {
+ model: this.model,
+ field_name: this.name,
+ object_id: this.res_id
+ },
+ }, {
+ shadow: true
+ }).then(function (result) {
// We need to write the url of the pad to trigger
// the write function which updates the actual value
// of the field to the value of the pad content
- self.set_value(self.value);
- self._url_request_deferred.resolve();
- }
- self._url_request_deferred.then(function() {
- if (_.str.startsWith(self.value, 'http')) {
- var content = '
';
- self.$('.oe_pad_content').html(content);
- }
- else {
- self.$('.oe_pad_content').text(self.value);
- }
+ self._setValue(result.url);
});
- });
- },
- set_configured: function(configured) {
- this.configured = configured;
- if (!configured) {
- this.$(".oe_unconfigured").removeClass('hidden');
- this.$(".oe_configured").addClass('hidden');
}
},
- is_set: function() {
- return true;
+ /**
+ * @override
+ * @private
+ */
+ _renderReadonly: function () {
+ if (_.str.startsWith(this.value, 'http')) {
+ var self = this;
+ this.$('.oe_pad_content')
+ .addClass('oe_pad_loading')
+ .text(_t("Loading"));
+ this._rpc({
+ method: 'pad_get_content',
+ model: this.model,
+ args: [this.value]
+ }, {
+ shadow: true
+ }).then(function (data) {
+ self.$('.oe_pad_content')
+ .removeClass('oe_pad_loading')
+ .html('
');
+ self.$('.oe_pad_readonly').html(data);
+ }).fail(function () {
+ self.$('.oe_pad_content').text(_t('Unable to load pad'));
+ });
+ } else {
+ this.$('.oe_pad_content')
+ .addClass('oe_pad_loading')
+ .show()
+ .text(_t("This pad will be initialized on first edit"));
+ }
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ * @private
+ */
+ _onToggleFullScreen: function () {
+ this.$el.toggleClass('oe_pad_fullscreen mb0');
+ this.$('.oe_pad_switch').toggleClass('fa-expand fa-compress');
},
});
-field_registry.add('pad', FieldPad);
+fieldRegistry.add('pad', FieldPad);
+
+return FieldPad;
});
diff --git a/addons/pad/static/tests/pad_tests.js b/addons/pad/static/tests/pad_tests.js
new file mode 100644
index 00000000000..69342f5de6e
--- /dev/null
+++ b/addons/pad/static/tests/pad_tests.js
@@ -0,0 +1,151 @@
+odoo.define('pad.pad_tests', function (require) {
+"use strict";
+
+var FieldPad = require('pad.pad');
+var FormView = require('web.FormView');
+var testUtils = require('web.test_utils');
+
+var createView = testUtils.createView;
+
+QUnit.module('pad widget', {
+ beforeEach: function () {
+ this.data = {
+ task: {
+ fields: {
+ description: {string: "Description", type: "char"},
+ },
+ records: [
+ {id: 1, description: false},
+ {id: 2, description: "https://pad.odoo.pad/p/test-03AK6RCJT"},
+ ],
+ pad_is_configured: function () {
+ return true;
+ },
+ pad_generate_url: function (route, args) {
+ return {
+ url:'https://pad.odoo.pad/p/test/' + args.context.object_id
+ };
+ },
+ pad_get_content: function () {
+ return "we should rewrite this server in haskell";
+ },
+ },
+ };
+ },
+});
+
+ QUnit.test('pad widget display help if server not configured', function (assert) {
+ assert.expect(4);
+
+ var form = createView({
+ View: FormView,
+ model: 'task',
+ data: this.data,
+ arch:'',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (args.method === 'pad_is_configured') {
+ return $.when(false);
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+ assert.ok(form.$('p.oe_unconfigured').is(':visible'),
+ "help message should be visible");
+ assert.notOk(form.$('p.oe_pad_content').is(':visible'),
+ "content should not be visible");
+ form.$buttons.find('.o_form_button_edit').click();
+ assert.ok(form.$('p.oe_unconfigured').is(':visible'),
+ "help message should be visible");
+ assert.notOk(form.$('p.oe_pad_content').is(':visible'),
+ "content should not be visible");
+ form.destroy();
+ delete FieldPad.prototype.isPadConfigured;
+ });
+
+ QUnit.test('pad widget works, basic case', function (assert) {
+ assert.expect(5);
+
+ var form = createView({
+ View: FormView,
+ model: 'task',
+ data: this.data,
+ arch:'',
+ res_id: 1,
+ mockRPC: function (route, args) {
+ if (route === 'https://pad.odoo.pad/p/test/1?showChat=false&userName=batman') {
+ assert.ok(true, "should have an iframe with correct src");
+ return $.when(true);
+ }
+ return this._super.apply(this, arguments);
+ },
+ session: {
+ userName: "batman",
+ },
+ });
+ assert.notOk(form.$('p.oe_unconfigured').is(':visible'),
+ "help message should not be visible");
+ assert.ok(form.$('.oe_pad_content').is(':visible'),
+ "content should be visible");
+ assert.strictEqual(form.$('.oe_pad_content:contains(This pad will be)').length, 1,
+ "content should display a message when not initialized");
+
+ form.$buttons.find('.o_form_button_edit').click();
+
+ assert.strictEqual(form.$('.oe_pad_content iframe').length, 1,
+ "should have an iframe");
+
+ form.destroy();
+ delete FieldPad.prototype.isPadConfigured;
+ });
+
+ QUnit.test('pad widget works, with existing data', function (assert) {
+ assert.expect(2);
+
+ var contentDef = $.Deferred();
+
+ var form = createView({
+ View: FormView,
+ model: 'task',
+ data: this.data,
+ arch:'',
+ res_id: 2,
+ mockRPC: function (route, args) {
+ var result = this._super.apply(this, arguments);
+ if (args.method === 'pad_get_content') {
+ return contentDef.then(_.constant(result));
+ }
+ return result;
+ },
+ session: {
+ userName: "batman",
+ },
+ });
+ assert.strictEqual(form.$('.oe_pad_content').text(), "Loading",
+ "should display loading message");
+ contentDef.resolve();
+ assert.strictEqual(form.$('.oe_pad_content').text(), "we should rewrite this server in haskell",
+ "should display proper value");
+ form.destroy();
+ delete FieldPad.prototype.isPadConfigured;
+ });
+
+});
diff --git a/addons/pad/views/pad.xml b/addons/pad/views/pad.xml
index 56bc0b1a997..126f8c84f16 100644
--- a/addons/pad/views/pad.xml
+++ b/addons/pad/views/pad.xml
@@ -6,4 +6,10 @@
+
+
+
+
+
+