diff --git a/CHANGES b/CHANGES index 9504b1c1..9d843a3a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,54 @@ +0.47.9 2023-10-26 10:05:30 +0200 Tobias Oetiker + + - better fix, based on https://github.com/qooxdoo/qooxdoo/pull/10632 + +0.47.8 2023-10-24 11:23:11 +0200 Tobias Oetiker + + - force button to focus when it is touched ... this seems to + resolve the event order problem + +0.47.7 2023-10-24 11:08:14 +0200 Tobias Oetiker + + - force button to focus when it is clicked + +0.47.6 2023-10-24 10:50:48 +0200 Tobias Oetiker + + - fix event ordering problem on ios and android + - add longer timeout for downloads and views + +0.47.5 2023-10-19 14:06:22 +0200 Tobias Oetiker + + - ios compatiblity issue can not easily be fixed outside qx + https://github.com/qooxdoo/qooxdoo/pull/10629 is the right way + to go about it ... + +0.47.4 2023-10-18 22:53:53 +0200 Tobias Oetiker + + - it seems on ios there is no changeValue after blur, fix that by + resettting and setting the value explicitly after blur ... aargh + + - explicitly check for empty string in required field even when there + is no validator defined + +0.47.3 2023-08-29 16:20:30 +0200 Tobias Oetiker + + - make table columns with "flex" work on mobile devices + +0.47.2 2023-08-29 14:14:54 +0200 Tobias Oetiker + + - connect mmButton properties to button propperties + +0.47.1 2023-08-29 12:04:49 +0200 Tobias Oetiker + + - include callbackery.ui.form.FileSelectorMenuButton into distro + +0.47.0 2023-08-29 11:01:09 +0200 Tobias Oetiker + +* make callbackery work better on mobile + - collapse navbar buttons into menu on mobile + - move labels over form fields on mobile +* cleanup indentation + 0.46.5 2023-06-28 08:29:18 +0200 Tobias Oetiker - fixed regression in abstractform (missing signature) diff --git a/MANIFEST b/MANIFEST index 1380e2fc..85ab5527 100644 --- a/MANIFEST +++ b/MANIFEST @@ -48,8 +48,7 @@ lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/form/Auto.js lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/form/DateTime.js lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/form/renderer/HBox.js lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/form/renderer/NoteForm.js -lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/form/UploadButton.js -lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/form/UploadToolbarButton.js +lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/form/FileSelectorMenuButton.js lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/form/VirtualSelectBox.js lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/Header.js lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/__init__.js diff --git a/Makefile.PL b/Makefile.PL index 89da85b1..6bc764bf 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -12,15 +12,15 @@ WriteMakefile( AUTHOR => 'Tobias Oetiker ', LICENSE => 'gpl_3', PREREQ_PM => { - 'Mojolicious' => '9.12', + 'Mojolicious' => '9.33', 'Mojolicious::Plugin::Qooxdoo' => '1.0.14', 'Config::Grammar' => '1.13', - 'XS::Parse::Keyword' => '0.21', - 'Future::AsyncAwait' => '0.54', - 'Syntax::Keyword::Try' => '0.25', + 'XS::Parse::Keyword' => '0.38', + 'Future::AsyncAwait' => '0.65', + 'Syntax::Keyword::Try' => '0.29', 'Locale::PO' => '0.27', - 'JSON::Validator' => 5.03, - 'YAML::XS' => 0.83, + 'JSON::Validator' => '5.14', + 'YAML::XS' => '0.88', 'Text::CSV' => 0, 'Excel::Writer::XLSX' => 0, 'Test::Fatal' => 0, diff --git a/lib/CallBackery.pm b/lib/CallBackery.pm index 442c1da1..5a811074 100644 --- a/lib/CallBackery.pm +++ b/lib/CallBackery.pm @@ -37,7 +37,7 @@ use CallBackery::Plugin::Doc; use CallBackery::Database; use CallBackery::User; -our $VERSION = '0.46.5'; +our $VERSION = '0.47.9'; =head2 config diff --git a/lib/CallBackery/GuiPlugin/AbstractForm.pm b/lib/CallBackery/GuiPlugin/AbstractForm.pm index dcb830fe..6c1d3221 100644 --- a/lib/CallBackery/GuiPlugin/AbstractForm.pm +++ b/lib/CallBackery/GuiPlugin/AbstractForm.pm @@ -91,13 +91,19 @@ sub validateData { if (not ref $entry){ die mkerror(4095,trm("sorry, don't know the field you are talking about")); } - return if not $entry->{set}{required} and (not defined $formData->{$fieldName} or length($formData->{$fieldName}) == 0); + my $fieldIsEmpty = (not defined $formData->{$fieldName} or length($formData->{$fieldName}) == 0); + return if not $entry->{set}{required} and $fieldIsEmpty; if ($entry->{validator}){ my $start = time; my $data = $entry->{validator}->($formData->{$fieldName},$fieldName,$formData); $self->log->debug(sprintf("validator %s: %0.2fs",$fieldName,time-$start)); return $data; } + # if there is no validator but the field is required, complain + # if the content is empty + elsif ($entry->{set}{required} and $fieldIsEmpty){ + return trm('The %1 field is required',$fieldName); + } return; } diff --git a/lib/CallBackery/User.pm b/lib/CallBackery/User.pm index 9b9e78f2..6b7f4008 100644 --- a/lib/CallBackery/User.pm +++ b/lib/CallBackery/User.pm @@ -193,7 +193,7 @@ has cookieConf => sub { return {}; } - if ($paramCookie and gettimeofday() - $conf->{t} > 2.0){ + if ($paramCookie and gettimeofday() - $conf->{t} > 10.0){ $self->log->debug(qq{Cookie is expired}); die mkerror(38445,"cookie has expired"); } diff --git a/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/TabView.js b/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/TabView.js index 62b4dd12..e61af05e 100644 --- a/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/TabView.js +++ b/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/TabView.js @@ -5,8 +5,7 @@ Utf8Check: äöü ************************************************************************ */ /** - * Build the desktop. This is a singleton. So that the desktop - * object and with it the treeView and the searchView are universaly accessible + * Build the desktop. This is a singleton */ qx.Class.define("callbackery.ui.TabView", { extend : qx.ui.tabview.TabView, diff --git a/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/form/Auto.js b/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/form/Auto.js index 34ec4f8c..ad974259 100644 --- a/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/form/Auto.js +++ b/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/form/Auto.js @@ -275,8 +275,6 @@ qx.Class.define("callbackery.ui.form.Auto", { this); var formWgt = new (formRenderer)(form); - var fl = formWgt.getLayout(); - this._add(formWgt); }, diff --git a/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/form/FileSelectorMenuButton.js b/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/form/FileSelectorMenuButton.js new file mode 100644 index 00000000..cf04100a --- /dev/null +++ b/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/form/FileSelectorMenuButton.js @@ -0,0 +1,112 @@ +/* ************************************************************************ + + After Qooxdoo FileSelectorMenuButton + Copyright: + 2023 Oetiker+Partner AG + + License: + LGPL: http://www.gnu.org/licenses/lgpl.html + See the LICENSE file in the project's top-level directory for details. + + Authors: + * Tobias Oetiker + +************************************************************************ */ + +qx.Class.define("callbackery.ui.form.FileSelectorMenuButton", { + extend: qx.ui.menu.Button, + statics: { + _fileInputElementIdCounter: 0 + }, + properties: { + /** + * What type of files should be offered in the fileselection dialog. + * Use a comma separated list of [Unique file type specifiers](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#unique_file_type_specifiers). If you dont set anything, all files + * are allowed. + * + * *Example* + * + * `.doc,.docx,application/msword` + */ + accept: { + nullable: true, + check: "String", + apply: "_applyAttribute" + }, + + /** + * Specify that the camera should be used for getting the "file". The + * value defines which camera should be used for capturing images. + * `user` indicates the user-facing camera. + * `environment` indicates the camera facing away from the user. + */ + capture: { + nullable: true, + check: ["user", "environment"], + apply: "_applyAttribute" + }, + + /** + * Set to "true" if you want to allow the selection of multiple files. + */ + multiple: { + nullable: true, + check: "Boolean", + apply: "_applyAttribute" + }, + + /** + * If present, indicates that only directories should be available for + * selection. + */ + directoriesOnly: { + nullable: true, + check: "Boolean", + apply: "_applyAttribute" + } + }, + + members: { + __inputObjec: null, + _applyAttribute(value, old, attr) { + if (attr === "directoriesOnly") { + // while the name of the attribute indicates that this only + // works for webkit browsers, this is not the case. These + // days the attribute is supported by + // [everyone](https://caniuse.com/?search=webkitdirectory). + attr = "webkitdirectory"; + } + this.__inputObject.setAttribute(attr, value); + }, + setEnabled(value) { + this.__inputObject.setEnabled(value); + super.setEnabled(value); + }, + _createContentElement() { + let id = "qxMenuFileSelector_" + (++callbackery.ui.form.FileSelectorMenuButton._fileInputElementIdCounter); + let input = (this.__inputObject = new qx.html.Input( + "file", + null, + { id: id } + )); + + let label = new qx.html.Element("label", {}, { for: id }); + label.addListenerOnce("appear", e => { + label.add(input); + }); + + input.addListenerOnce("appear", e => { + let inputEl = input.getDomElement(); + // since qx.html.Node does not even create the + // domNode if it is not set to visible initially + // we have to quickly hide it after creation. + input.setVisible(false); + inputEl.addEventListener("change", e => { + this.fireDataEvent("changeFileSelection", inputEl.files); + inputEl.value = ""; + }); + }); + return label; + } + } +}); diff --git a/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/form/UploadButton.js b/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/form/UploadButton.js deleted file mode 100644 index 7123a268..00000000 --- a/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/form/UploadButton.js +++ /dev/null @@ -1,47 +0,0 @@ -var idCntr=0; -qx.Class.define("callbackery.ui.form.UploadButton", { - // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file - extend : qx.ui.form.Button, - events: { - changeFileSelection: "qx.event.type.Data" - }, - properties: { - accept: { - nullable: true, - apply : "_applyAttribute" - }, - multiple: { - nullable: true, - apply : "_applyAttribute" - }, - webkitdirectory: { - nullable: true, - apply : "_applyAttribute" - } - }, - members: { - __inputObject: null, - _applyAttribute: function(value,old,attr){ - this.__inputObject.setAttribute(attr,value); - }, - _createContentElement: function() { - var id = 'uploadId_'+(idCntr++); - var input = this.__inputObject - = new qx.html.Input("file",{display: 'none'},{id: id}); - var label = new qx.html.Element("label",{},{'for': id}); - label.addListenerOnce('appear',function(e){ - label.add(input); - qx.html.Element.flush(); - var inputEl = input.getDomElement(); - var that = this; - inputEl.addEventListener('change',function(e){ - that.fireDataEvent('changeFileSelection',inputEl.files); - inputEl.value = ""; - }); - },this); - return label; - } - } - }); - - \ No newline at end of file diff --git a/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/form/UploadToolbarButton.js b/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/form/UploadToolbarButton.js deleted file mode 100644 index 5cb4a72f..00000000 --- a/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/form/UploadToolbarButton.js +++ /dev/null @@ -1,99 +0,0 @@ -/* ************************************************************************ - - qooxdoo - the new era of web development - - http://qooxdoo.org - - Copyright: - 2007 Visionet GmbH, http://www.visionet.de - - License: - LGPL: http://www.gnu.org/licenses/lgpl.html - EPL: http://www.eclipse.org/org/documents/epl-v10.php - See the LICENSE file in the project's top-level directory for details. - - Authors: - * Dietrich Streifert (level420) - - Contributors: - * Petr Kobalicek (e666e) - * Tobi Oetiker (oetiker) - -************************************************************************ */ - -/* - The 'change' event on the input field requires that this handler be available: -*/ - -/** - * @use(qx.event.handler.Input) - */ - - - -/** - * An upload button to use in a toolbar. Like a normal - * {@link callbackery.ui.form.UploadButton} - * but with a style matching the toolbar and without keyboard support. - * - * After qx.ui.form.Button <> qx.ui.toolbar.Button - */ -qx.Class.define("callbackery.ui.form.UploadToolbarButton", - { - extend: callbackery.ui.form.UploadButton, - - // -------------------------------------------------------------------------- - // [Constructor] - // -------------------------------------------------------------------------- - - /** - * @param label {String} button label - * @param icon {String} icon path - * @param command {Command} command instance to connect with - */ - - construct: function (label, icon, command) { - this.base(arguments, label, icon, command); - - // Toolbar buttons should not support the keyboard events - this.removeListener("keydown", this._onKeyDown); - this.removeListener("keyup", this._onKeyUp); - }, - - // -------------------------------------------------------------------------- - // [Properties] - // -------------------------------------------------------------------------- - - properties: - { - appearance: - { - refine: true, - init: "toolbar-button" - }, - - focusable: - { - refine: true, - init: false - } - }, - - // -------------------------------------------------------------------------- - // [Members] - // -------------------------------------------------------------------------- - - members: - { - // overridden - _applyVisibility: function (value, old) { - this.base(arguments, value, old); - // trigger a appearance recalculation of the parent - var parent = this.getLayoutParent(); - if (parent && parent instanceof qx.ui.toolbar.PartContainer) { - qx.ui.core.queue.Appearance.add(parent); - } - } - } - - }); diff --git a/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/form/renderer/NoteForm.js b/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/form/renderer/NoteForm.js index f884d467..0858cf7c 100644 --- a/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/form/renderer/NoteForm.js +++ b/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/form/renderer/NoteForm.js @@ -10,15 +10,17 @@ * widget and next to the individual form widgets. */ qx.Class.define("callbackery.ui.form.renderer.NoteForm", { - extend : qx.ui.form.renderer.Single, + extend: qx.ui.form.renderer.Single, /** * create a page for the View Tab with the given title * * @param vizWidget {Widget} visualization widget to embed */ - construct: function(form) { + construct: function (form) { this._gotNote = false; - this.base(arguments,form); + this._mobileMode; + this._reflowQueue = []; + this.base(arguments, form); var fl = this._getLayout(); // have plenty of space for input, not for the labels fl.setColumnFlex(0, 0); @@ -26,54 +28,110 @@ qx.Class.define("callbackery.ui.form.renderer.NoteForm", { fl.setColumnFlex(1, 4); fl.setColumnMinWidth(1, 130); fl.setColumnFlex(2, this._gotNote ? 1 : 0); - fl.setColumnMaxWidth(2,250); + fl.setColumnMaxWidth(2, 250); fl.setSpacingY(0); + this.addListener('resize', (e) => { + this._updateForm(e.getData().width); + }); + }, + events: { + 'reflowForm': 'qx.event.type.Event' }, - members: { _gotNote: null, - addItems: function(items,names,title,itemOptions,headerOptions){ + _reflowQueue: null, + _updateForm(width) { + let update = true; + if (width < 400) { + if (this._mobileMode === true) { + update = false; + } + else { + this._mobileMode = true; + } + } else { + if (this._mobileMode === false) { + update = false; + } + else { + this._mobileMode = false; + } + } + if (update) { + this._reflowQueue.forEach((element) => { + element(); + }); + } + }, + addItems(items, names, title, itemOptions, headerOptions) { // add the header + let rfq = this._reflowQueue; if (title != null) { - if (headerOptions && headerOptions.widget){ - this._add( - headerOptions.widget.set({ - value: title, - paddingTop: this._row == 0 ? 0 :10 - }),{ - row: this._row++, - column: 0, - colSpan: 3 - } - ); + let widget = null; + if (headerOptions && headerOptions.widget) { + widget = headerOptions.widget.set({ + value: title, + paddingTop: this._row == 0 ? 0 : 10 + }); + } else { - this._add( - this._createHeader(title), { - row: this._row++, + widget = this._createHeader(title); + } + let row = this._row; + rfq.push(() => { + if (this._mobileMode) { + this._add( + widget, { + row: row, + column: 1, + colSpan: 2 + }); + } + else { + this._add( + widget, { + row: row, column: 0, colSpan: 3 - } - ); - } + }); + } + }); this._row++; - if (headerOptions - && headerOptions.note){ - var note = new qx.ui.basic.Label( + if (headerOptions + && headerOptions.note) { + let note = new qx.ui.basic.Label( headerOptions.note).set({ - rich: true, - alignX: 'left', - paddingBottom: 12, - paddingRight: 10 - }); - this._add(note,{ - row: this._row++, - column: 0, - colSpan: 3 + rich: true, + alignX: 'left', + paddingBottom: 12, + paddingRight: 10 + }); + let row = this._row; + rfq.push(() => { + if (this._mobileMode) { + this._add( + note, { + row: row, + column: 1, + colSpan: 2 + } + ); + } + else { + this._add( + note, { + row: row, + column: 0, + colSpan: 3 + } + ); + } }); + this._row++; if (headerOptions.widget) { this._connectVisibility( - headerOptions.widget,note); + headerOptions.widget, note); } } } @@ -81,73 +139,104 @@ qx.Class.define("callbackery.ui.form.renderer.NoteForm", { // add the items var msg = callbackery.ui.MsgBox.getInstance(); var that = this; - for (var i = 0; i < items.length; i++) { (function(){ // context - var label = that._createLabel(names[i], items[i]); - var item = items[i]; - item.set({ - marginTop: 2, - marginBottom: 2 - }); - var labelName = names[i]; - // allow form items without label - if (label) { - label.set({ + for (var i = 0; i < items.length; i++) { + (function () { // context + let label = that._createLabel(names[i], items[i]); + let item = items[i]; + item.set({ marginTop: 2, marginBottom: 2 }); - label.setBuddy(item); - that._add(label, {row: that._row, column: 0}); - } - that._add(item, {row: that._row, column: 1}); - if (itemOptions != null && itemOptions[i] != null) { - if ( itemOptions[i].note ){ - that._gotNote = true; - var note = new qx.ui.basic.Label( - itemOptions[i].note).set({ - rich: true, - paddingLeft: 20, - paddingRight: 20 + var labelName = names[i]; + // allow form items without label + if (label) { + + label.setBuddy(item); + let row = that._row; + rfq.push(() => { + let newLabel = label.getValue().replace(/\s*:\s*$/, ''); + if (that._mobileMode) { + label.set({ + value: newLabel, + marginTop: 2, + marginBottom: 0, + paddingBottom: 0, + font: 'small', + }); + that._add(label, { + row: row, + column: 1, + }); + } + else { + label.set({ + value: newLabel + ':', + marginTop: 2, + marginBottom: 2, + font: 'default', + }); + that._add(label, { + row: row + 1, + column: 0, + }); + } }); - that._add(note,{ - row: that._row, - column: 2 + } + that._add(item, { row: that._row + 1, column: 1 }); + if (itemOptions != null && itemOptions[i] != null) { + if (itemOptions[i].note) { + that._gotNote = true; + var note = new qx.ui.basic.Label( + itemOptions[i].note).set({ + rich: true, + paddingLeft: 20, + paddingRight: 20 + }); + that._add(note, { + row: that._row + 1, + column: 2 + }); + that._connectVisibility(item, note); + } + } + if (itemOptions[i].copyOnTap + && item.getReadOnly()) { + var copyFailMsg = itemOptions[i].copyFailMsg + ? that.xtr(itemOptions[i].copyFailMsg) + : that.tr("Select %1 and press [ctrl]+[c]", labelName); + var copySuccessMsg = itemOptions[i].copySuccessMsg + ? that.xtr(itemOptions[i].copySuccessMsg) + : that.tr("%1 copied", labelName); + + item.addListener('tap', function (e) { + try { + navigator.clipboard.writeText(item.getValue()) + .then(function (ret) { + msg.info(that.tr("Success"), copySuccessMsg); + }) + .catch(function (err) { + msg.info(that.tr("Copy failed"), copyFailMsg); + }) + } catch (err) { + msg.info(that.tr("Copy failed"), copyFailMsg); + } }); - that._connectVisibility(item, note); } - } - if ( itemOptions[i].copyOnTap - && item.getReadOnly()){ - var copyFailMsg = itemOptions[i].copyFailMsg - ? that.xtr(itemOptions[i].copyFailMsg) - : that.tr("Select %1 and press [ctrl]+[c]",labelName); - var copySuccessMsg = itemOptions[i].copySuccessMsg - ? that.xtr(itemOptions[i].copySuccessMsg) - : that.tr("%1 copied",labelName); + that._row += 2; + that._connectVisibility(item, label); - item.addListener('tap',function(e){ - try { - navigator.clipboard.writeText(item.getValue()) - .then(function(ret){ - msg.info(that.tr("Success"),copySuccessMsg); - }) - .catch(function(err){ - msg.info(that.tr("Copy failed"),copyFailMsg); - }) - } catch (err) { - msg.info(that.tr("Copy failed"),copyFailMsg); - } - }); - } - that._row++; - that._connectVisibility(item, label); - - // store the names for translation - if (qx.core.Environment.get("qx.dynlocale")) { - that._names.push({ - name: names[i], label: label, item: items[i] - }); - } - })(); } // end context + // store the names for translation + if (qx.core.Environment.get("qx.dynlocale")) { + that._names.push({ + name: names[i], label: label, item: items[i] + }); + } + })(); + } // end context + let bounds = this.getBounds(); + if (bounds) { + this._updateForm(bounds.width); + } } } }); diff --git a/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/plugin/Action.js b/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/plugin/Action.js index f1f2c9ea..0ab5fe3d 100644 --- a/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/plugin/Action.js +++ b/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/plugin/Action.js @@ -9,7 +9,7 @@ * Form Action Widget. */ qx.Class.define("callbackery.ui.plugin.Action", { - extend : qx.ui.container.Composite, + extend: qx.ui.container.Composite, /** * create a page for the View Tab with the given title * @@ -19,26 +19,26 @@ qx.Class.define("callbackery.ui.plugin.Action", { * @param getFormData {Function} method to get form data * @param plugin {Class} visualization widget to embedd */ - construct : function(cfg,buttonClass,layout,getFormData, plugin) { + construct(cfg, buttonClass, layout, getFormData, plugin) { this.base(arguments, layout); this._plugin = plugin; this._urlActions = []; this._buttonMap = {}; this._buttonSetMap = {}; this._cfg = cfg; - this._populate(cfg,buttonClass,getFormData); + this._populate(cfg, buttonClass, getFormData); plugin.addOwnedQxObject(this, 'Action'); - this.addListener('actionResponse',function(e){ + this.addListener('actionResponse', function (e) { var data = e.getData(); // ignore empty actions responses - if (!data){ + if (!data) { return; } - switch (data.action){ + switch (data.action) { case 'logout': - callbackery.data.Server.getInstance().callAsyncSmartBusy(function(ret) { - if (window.console){ - window.console.log('last words from the server "'+ret+'"'); + callbackery.data.Server.getInstance().callAsyncSmartBusy(function (ret) { + if (window.console) { + window.console.log('last words from the server "' + ret + '"'); } document.location.reload(true); }, 'logout'); @@ -83,13 +83,13 @@ qx.Class.define("callbackery.ui.plugin.Action", { console.error('Unknown action:', data.action); break; } - },this); + }, this); // process actions called via URL this.addListener('appear', () => { let config = callbackery.data.Config.getInstance(); this._urlActions.forEach(urlAction => { - let button = urlAction.button; + let button = urlAction.button; let urlValue = config.getUrlConfigValue(urlAction.key); if (urlValue && urlValue == urlAction.value) { button.execute(); @@ -111,59 +111,70 @@ qx.Class.define("callbackery.ui.plugin.Action", { members: { _cfg: null, - _plugin : null, + _plugin: null, _tableMenu: null, _defaultAction: null, _buttonMap: null, _urlActions: null, - _print: function(content, left, top) { + _mobileMenu: null, + _print(content, left, top) { var win = window.open('', '_blank'); var doc = win.document; doc.open(); doc.write(content); doc.close(); - win.onafterprint=function() { + win.onafterprint = function () { win.close(); } win.print(); }, - _populate: function(cfg,buttonClass,getFormData){ + _populate(cfg, buttonClass, getFormData) { var tm = this._tableMenu = new qx.ui.menu.Menu; + let mm = this._mobileMenu = new qx.ui.menu.Menu; var menues = {}; - let plugin = this._plugin; - cfg.action.forEach(function(btCfg){ - var button, menuButton; + var mmMenues = {}; + cfg.action.forEach(function (btCfg) { + var button, menuButton, mmButton; var label = btCfg.label ? this.xtr(btCfg.label) : null; switch (btCfg.action) { case 'menu': var menu = menues[btCfg.key] = new qx.ui.menu.Menu; + var mmMenu = mmMenues[btCfg.key] = new qx.ui.menu.Menu; if (btCfg.addToMenu != null) { // add submenu to menu button = new qx.ui.menu.Button(label, null, null, menu) + mmButton = new qx.ui.menu.Button(label, null, null, mmMenu) menues[btCfg.addToMenu].add(button); + mmMenues[btCfg.addToMenu].add(mmButton); } else { // add menu to form button = new qx.ui.form.MenuButton(label, null, menu); + mmButton = new qx.ui.menu.Button(label, null, null, mmMenu); if (btCfg.buttonSet) { var bs = btCfg.buttonSet; if (bs.label) { bs.label = this.xtr(bs.label); } button.set(bs); - if (btCfg.key){ - this._buttonSetMap[btCfg.key]=bs; + mmButton.set(bs); + if (btCfg.key) { + this._buttonSetMap[btCfg.key] = bs; } } this.add(button); + mm.add(mmButton); } if (btCfg.key) { let btnId = btCfg.key + (btCfg.testingIdPostfix ? btCfg.testingIdPostfix : '') + 'Button'; this.addOwnedQxObject(button, btnId); - this._buttonMap[btCfg.key]=button; + let mmBtnId = btCfg.key + + (btCfg.testingIdPostfix ? btCfg.testingIdPostfix : '') + + 'mmButton'; + this.addOwnedQxObject(mmButton, mmBtnId); } + this._bindButtonPropperties(button, mmButton); return; - break; case 'save': case 'submitVerify': case 'submit': @@ -173,20 +184,21 @@ qx.Class.define("callbackery.ui.plugin.Action", { case 'cancel': case 'download': case 'display': + mmButton = new qx.ui.menu.Button(label); if (btCfg.addToMenu != null) { button = new qx.ui.menu.Button(label); } else { button = new buttonClass(label); } - if (btCfg.key){ - this._buttonMap[btCfg.key]=button; + if (btCfg.key) { + this._buttonMap[btCfg.key] = button; let urlAction = btCfg.urlAction; if (urlAction) { this._urlActions.push({ - button : button, - value : urlAction.value, - key : urlAction.key + button: button, + value: urlAction.value, + key: urlAction.key }); } } @@ -196,12 +208,13 @@ qx.Class.define("callbackery.ui.plugin.Action", { bs.label = this.xtr(bs.label); } button.set(bs); - if (btCfg.key){ - this._buttonSetMap[btCfg.key]=bs; + mmButton.set(bs); + if (btCfg.key) { + this._buttonSetMap[btCfg.key] = bs; } } - if ( btCfg.addToContextMenu) { + if (btCfg.addToContextMenu) { menuButton = new qx.ui.menu.Button(label); if (btCfg.key) { let btnId = btCfg.key @@ -214,26 +227,26 @@ qx.Class.define("callbackery.ui.plugin.Action", { 'Visibility', 'Icon', 'Label' - ].forEach(function(Prop){ + ].forEach(function (Prop) { var prop = Prop.toLowerCase(); - button.addListener('change'+Prop,function(e){ - menuButton['set'+Prop](e.getData()); - },this); - if (btCfg.buttonSet && prop in btCfg.buttonSet){ - menuButton['set'+Prop](btCfg.buttonSet[prop]); + button.addListener('change' + Prop, function (e) { + menuButton['set' + Prop](e.getData()); + }, this); + if (btCfg.buttonSet && prop in btCfg.buttonSet) { + menuButton['set' + Prop](btCfg.buttonSet[prop]); } - },this); + }, this); } break; case 'refresh': var timer = qx.util.TimerManager.getInstance(); var timerId; - this.addListener('appear',function(){ - timerId = timer.start(function(){ - this.fireDataEvent('actionResponse',{action: 'reloadStatus'}); + this.addListener('appear', function () { + timerId = timer.start(function () { + this.fireDataEvent('actionResponse', { action: 'reloadStatus' }); }, btCfg.interval * 1000, this); }, this); - this.addListener('disappear',function(){ + this.addListener('disappear', function () { if (timerId) { timer.stop(timerId); timerId = null; @@ -243,17 +256,17 @@ qx.Class.define("callbackery.ui.plugin.Action", { case 'autoSubmit': var autoTimer = qx.util.TimerManager.getInstance(); var autoTimerId; - this.addListener('appear',function(){ + this.addListener('appear', function () { var key = btCfg.key; var that = this; - autoTimerId = autoTimer.start(function(){ + autoTimerId = autoTimer.start(function () { var formData = getFormData(); - callbackery.data.Server.getInstance().callAsync(function(ret){ - that.fireDataEvent('actionResponse',ret || {}); - },'processPluginData',cfg.name,{ "key": key, "formData": formData }); + callbackery.data.Server.getInstance().callAsync(function (ret) { + that.fireDataEvent('actionResponse', ret || {}); + }, 'processPluginData', cfg.name, { "key": key, "formData": formData }); }, btCfg.interval * 1000, this); }, this); - this.addListener('disappear',function(){ + this.addListener('disappear', function () { if (autoTimerId) { autoTimer.stop(autoTimerId); autoTimerId = null; @@ -261,10 +274,19 @@ qx.Class.define("callbackery.ui.plugin.Action", { }, this); break; case 'upload': - button = this._makeUploadButton(cfg,btCfg,getFormData); + button = this._makeUploadButton(cfg, btCfg, getFormData, + buttonClass == qx.ui.toolbar.Button + ? qx.ui.toolbar.FileSelectorButton + : qx.ui.form.FileSelectorButton); + mmButton = this._makeUploadButton(cfg, btCfg, getFormData, + callbackery.ui.form.FileSelectorMenuButton); + if (btCfg.key) { + this._buttonMap[btCfg.key] = button; + } break; case 'separator': - this.add(new qx.ui.core.Spacer(10,10)); + this.add(new qx.ui.core.Spacer(10, 10)); + mm.add(new qx.ui.menu.Separator()); break; default: this.debug('Invalid execute action:' + btCfg.action + ' for button', btCfg); @@ -275,23 +297,29 @@ qx.Class.define("callbackery.ui.plugin.Action", { + 'Button'; this.addOwnedQxObject(button, btnId); } - var action = function(){ + if (mmButton && btCfg.key) { + let mmBtnId = btCfg.key + + (btCfg.testingIdPostfix ? btCfg.testingIdPostfix : '') + + 'mmButton'; + this.addOwnedQxObject(mmButton, mmBtnId); + } + var action = function () { var that = this; - if (! button.isEnabled()) { + if (!button.isEnabled()) { return; } switch (btCfg.action) { case 'save': var formData = getFormData(); var key = btCfg.key; - callbackery.data.Server.getInstance().callAsync(function(ret){ - that.fireDataEvent('actionResponse',ret || {}); - },'processPluginData',cfg.name,{ "key": key, "formData": formData }); + callbackery.data.Server.getInstance().callAsync(function (ret) { + that.fireDataEvent('actionResponse', ret || {}); + }, 'processPluginData', cfg.name, { "key": key, "formData": formData }); break; case 'submitVerify': case 'submit': var formData = getFormData(); - if (formData === false){ + if (formData === false) { callbackery.ui.MsgBox.getInstance().error( this.tr("Validation Error"), this.tr("The form can only be submitted when all data fields have valid content.") @@ -299,23 +327,23 @@ qx.Class.define("callbackery.ui.plugin.Action", { return; } var key = btCfg.key; - var asyncCall = function(){ - callbackery.data.Server.getInstance().callAsyncSmartBusy(function(ret){ - that.fireDataEvent('actionResponse',ret || {}); - },'processPluginData',cfg.name,{ "key": key, "formData": formData }); + var asyncCall = function () { + callbackery.data.Server.getInstance().callAsyncSmartBusy(function (ret) { + that.fireDataEvent('actionResponse', ret || {}); + }, 'processPluginData', cfg.name, { "key": key, "formData": formData }); }; - if (btCfg.action == 'submitVerify'){ + if (btCfg.action == 'submitVerify') { var title = btCfg.label != null ? btCfg.label : btCfg.key; callbackery.ui.MsgBox.getInstance().yesno( this.xtr(title), this.xtr(btCfg.question) ) - .addListenerOnce('choice',function(e){ - if (e.getData() == 'yes'){ - asyncCall(); - } - },this); + .addListenerOnce('choice', function (e) { + if (e.getData() == 'yes') { + asyncCall(); + } + }, this); } else { asyncCall(); @@ -324,7 +352,7 @@ qx.Class.define("callbackery.ui.plugin.Action", { case 'download': case 'display': var formData = getFormData(); - if (formData === false){ + if (formData === false) { callbackery.ui.MsgBox.getInstance().error( this.tr("Validation Error"), this.tr("The form can only be submitted when all data fields have valid content.") @@ -332,21 +360,21 @@ qx.Class.define("callbackery.ui.plugin.Action", { return; } var key = btCfg.key; - callbackery.data.Server.getInstance().callAsyncSmart(function(cookie){ + callbackery.data.Server.getInstance().callAsyncSmart(function (cookie) { let url = 'download' - +'?name='+cfg.name - +'&key='+key - +'&xsc='+encodeURIComponent(cookie) - +'&formData='+encodeURIComponent(qx.lang.Json.stringify(formData)); + + '?name=' + cfg.name + + '&key=' + key + + '&xsc=' + encodeURIComponent(cookie) + + '&formData=' + encodeURIComponent(qx.lang.Json.stringify(formData)); if (btCfg.action == 'display') { - window.open(url + '&display=1','_blank'); + window.open(url + '&display=1', '_blank'); return; } var iframe = new qx.ui.embed.Iframe().set({ width: 100, height: 100 }); - iframe.addListener('load',function(e){ + iframe.addListener('load', function (e) { var response = { exception: { message: String(that.tr("No Data")), @@ -363,43 +391,43 @@ qx.Class.define("callbackery.ui.plugin.Action", { // If there is text left, it should be the json from the server. // JSON parsing an empty string is an error. if (innerHTML) { - response = qx.lang.Json.parse(innerHTML); + response = qx.lang.Json.parse(innerHTML); } // otherwise remove standard exception. else { response = ''; } - } catch (e){}; - if (response.exception){ + } catch (e) { }; + if (response.exception) { callbackery.ui.MsgBox.getInstance().error( that.tr("Download Exception"), - that.xtr(response.exception.message) + " ("+ response.exception.code +")" + that.xtr(response.exception.message) + " (" + response.exception.code + ")" ); } that.getApplicationRoot().remove(iframe); }); iframe.setSource(url); - that.getApplicationRoot().add(iframe,{top: -1000,left: -1000}); - },'getSessionCookie'); + that.getApplicationRoot().add(iframe, { top: -1000, left: -1000 }); + }, 'getSessionCookie'); break; case 'cancel': - this.fireDataEvent('actionResponse',{action: 'cancel'}); + this.fireDataEvent('actionResponse', { action: 'cancel' }); break; case 'wizzard': var parent = that.getLayoutParent(); - while (! parent.classname.match(/Page|Popup/) ) { + while (!parent.classname.match(/Page|Popup/)) { parent = parent.getLayoutParent(); } // This could in principal work for Page although. if (parent.classname.match(/Popup/)) { // parent already exists, replace content - parent.replaceContent(btCfg,getFormData); + parent.replaceContent(btCfg, getFormData); break; } - // fall through intended to create first popup content + // fall through intended to create first popup content case 'popup': - if (! btCfg.noValidation) { // backward incompatibility work around + if (!btCfg.noValidation) { // backward incompatibility work around var formData = getFormData(); - if (formData === false){ + if (formData === false) { callbackery.ui.MsgBox.getInstance().error( this.tr("Validation Error"), this.tr("The form can only be submitted when all data fields have valid content.") @@ -407,26 +435,26 @@ qx.Class.define("callbackery.ui.plugin.Action", { return; } } - var popup = new callbackery.ui.Popup(btCfg,getFormData, this); + var popup = new callbackery.ui.Popup(btCfg, getFormData, this); var appRoot = that.getApplicationRoot(); - - popup.addListenerOnce('close',function(){ + + popup.addListenerOnce('close', function () { // wait for stuff to happen before we rush into // disposing the popup - qx.event.Timer.once(function(){ + qx.event.Timer.once(function () { appRoot.remove(popup); popup.dispose(); this.fireEvent('popupClosed'); - },that,100); - if (!(btCfg.options && btCfg.options.noReload)){ - this.fireDataEvent('actionResponse',{action: ( btCfg.options && btCfg.options.reloadStatusOnClose ) ? 'reloadStatus' : 'reload'}); + }, that, 100); + if (!(btCfg.options && btCfg.options.noReload)) { + this.fireDataEvent('actionResponse', { action: (btCfg.options && btCfg.options.reloadStatusOnClose) ? 'reloadStatus' : 'reload' }); } - },that); + }, that); popup.open(); break; case 'logout': - that.fireDataEvent('actionResponse',{action: 'logout'}); + that.fireDataEvent('actionResponse', { action: 'logout' }); break; default: @@ -434,11 +462,21 @@ qx.Class.define("callbackery.ui.plugin.Action", { } }; // var action = function() { ... }; - if (btCfg.defaultAction){ + if (btCfg.defaultAction) { this._defaultAction = action; } - if (button){ - button.addListener('execute',action,this); + if (button) { + // in ios/android buttons do not necessarily get focus when clicked + // so we need to do it manually, since this happens on different + // browsers, chances are this is not a bug but by design :) + + // once https://github.com/qooxdoo/qooxdoo/pull/10632 is released + // this here can go away + button.addListenerOnce('appear',() => { + let el = button.getContentElement().getDomElement(); + button.addListener('touchstart', () => { el.focus(); }, this); + }); + button.addListener('execute', action, this); if (btCfg.addToMenu) { menues[btCfg.addToMenu].add(button); } @@ -448,23 +486,34 @@ qx.Class.define("callbackery.ui.plugin.Action", { } } } - if (menuButton){ - menuButton.addListener('execute',action,this); - this._tableMenu.add(menuButton); + if (mmButton) { + mmButton.addListener('execute', action, this); + if (btCfg.addToMenu) { + mmMenues[btCfg.addToMenu].add(mmButton); + } + else { + if (btCfg.addToToolBar !== false) { + mm.add(mmButton); + } + } + } + if (menuButton) { + menuButton.addListener('execute', action, this); + tm.add(menuButton); + } + if (button && menuButton) { + this._bindButtonProperties(button, mmButton); } - },this); + }, this); }, - _makeUploadButton: function(cfg,btCfg,getFormData){ + _makeUploadButton(cfg, btCfg, getFormData, buttonClass) { var button; var label = btCfg.label ? this.xtr(btCfg.label) : null; - if (btCfg.btnClass == 'toolbar') { - button = new callbackery.ui.form.UploadToolbarButton(label); + if (buttonClass) { + button = new buttonClass(label); } else { - button = new callbackery.ui.form.UploadButton(label); - } - if (btCfg.key){ - this._buttonMap[btCfg.key]=button; + button = new qx.ui.form.FileSelectorButton(label); } if (btCfg.buttonSet) { var bs = btCfg.buttonSet; @@ -472,62 +521,62 @@ qx.Class.define("callbackery.ui.plugin.Action", { bs.label = this.xtr(bs.label); } button.set(bs); - if (btCfg.key){ - this._buttonSetMap[btCfg.key]=bs; + if (btCfg.key) { + this._buttonSetMap[btCfg.key] = bs; } } var serverCall = callbackery.data.Server.getInstance(); var key = btCfg.key; var name = cfg.name; - button.addListener('changeFileSelection',function(e){ + button.addListener('changeFileSelection', function (e) { var fileList = e.getData(); var formData = getFormData(); - if(formData && fileList) { + if (formData && fileList) { var form = new FormData(); - form.append('name',name); - form.append('key',key); - form.append('file',fileList[0]); - form.append('formData',qx.lang.Json.stringify(formData)); + form.append('name', name); + form.append('key', key); + form.append('file', fileList[0]); + form.append('formData', qx.lang.Json.stringify(formData)); var that = this; - serverCall.callAsyncSmart(function(cookie){ - form.append('xsc',cookie); + serverCall.callAsyncSmart(function (cookie) { + form.append('xsc', cookie); that._uploadForm(form); - },'getSessionCookie'); + }, 'getSessionCookie'); } else { callbackery.ui.MsgBox.getInstance().error( this.tr("Upload Exception"), this.tr("Make sure to select a file and properly fill the form") ); } - },this); + }, this); + - return button; }, - _uploadForm: function(form){ - var req = new qx.io.request.Xhr("upload",'POST').set({ + _uploadForm(form) { + var req = new qx.io.request.Xhr("upload", 'POST').set({ requestData: form }); - req.addListener('success',function(e) { + req.addListener('success', function (e) { var response = req.getResponse(); - if (response.exception){ + if (response.exception) { callbackery.ui.MsgBox.getInstance().error( this.tr("Upload Exception"), - this.xtr(response.exception.message) - + " ("+ response.exception.code +")" + this.xtr(response.exception.message) + + " (" + response.exception.code + ")" ); } else { - this.fireDataEvent('actionResponse',response); + this.fireDataEvent('actionResponse', response); } req.dispose(); - },this); - req.addListener('fail',function(e){ + }, this); + req.addListener('fail', function (e) { var response = {}; try { response = req.getResponse(); } - catch(e){ + catch (e) { response = { exception: { message: e.message, @@ -537,35 +586,43 @@ qx.Class.define("callbackery.ui.plugin.Action", { } callbackery.ui.MsgBox.getInstance().error( this.tr("Upload Exception"), - this.xtr(response.exception.message) - + " ("+ response.exception.code +")" + this.xtr(response.exception.message) + + " (" + response.exception.code + ")" ); req.dispose(); }); req.send(); }, - getTableContextMenu: function(){ + getTableContextMenu() { return this._tableMenu; }, - - getDefaultAction: function(){ + getMobileMenu() { + return this._mobileMenu; + }, + getDefaultAction() { return this._defaultAction; }, - getButtonMap: function(){ + getButtonMap() { return this._buttonMap; }, - getButtonSetMap: function(){ + getButtonSetMap() { return this._buttonSetMap; + }, + _bindButtonProperties(button, mmButton) { + ['visibility', 'enabled', 'label', 'icon'].forEach((prop) => { + button.bind(prop, mmButton, prop); + }); } }, - destruct : function() { - if (! this._buttonMap) { + destruct() { + if (!this._buttonMap) { return; } for (const [key, btn] of Object.entries(this._buttonMap)) { btn.destroy(); } - } + }, + }); diff --git a/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/plugin/Form.js b/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/plugin/Form.js index 79c98dab..ac69d8c9 100644 --- a/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/plugin/Form.js +++ b/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/plugin/Form.js @@ -125,7 +125,7 @@ qx.Class.define("callbackery.ui.plugin.Form", { }, this ); - + this.add(action); }, diff --git a/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/plugin/Table.js b/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/plugin/Table.js index bd8321b3..44287d12 100644 --- a/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/plugin/Table.js +++ b/lib/CallBackery/qooxdoo/callbackery/source/class/callbackery/ui/plugin/Table.js @@ -9,7 +9,7 @@ * Table Visualization Widget. */ qx.Class.define('callbackery.ui.plugin.Table', { - extend : callbackery.ui.plugin.Form, + extend: callbackery.ui.plugin.Form, /** * create a page for the View Tab with the given title * @@ -21,23 +21,23 @@ qx.Class.define('callbackery.ui.plugin.Table', { } }, members: { - _populate: function(){ + _populate() { this.setLayout(new qx.ui.layout.VBox(0)); if (this._cfg.introText) { - this.add(new qx.ui.basic.Label(this.xtr(this._cfg.introText)).set({rich: true})); + this.add(new qx.ui.basic.Label(this.xtr(this._cfg.introText)).set({ rich: true })); } this.add(this._createToolbar()); - this.add(this._createTable(), {flex: 1}); + this.add(this._createTable(), { flex: 1 }); }, - _createToolbar: function(){ + _createToolbar() { var that = this; var cfg = this._cfg; var toolbar = new qx.ui.toolbar.ToolBar(); var action = this._action = new callbackery.ui.plugin.Action( - cfg,qx.ui.toolbar.Button, + cfg, qx.ui.toolbar.Button, new qx.ui.layout.HBox(0), - function(){ - if (that._form.validate()){ + function () { + if (that._form.validate()) { var rpcData = that._form.getData(); rpcData['selection'] = that.getSelection(); return rpcData; @@ -51,17 +51,23 @@ qx.Class.define('callbackery.ui.plugin.Table', { action.set({ paddingLeft: -10 }); + let mobileMenu = new qx.ui.form.MenuButton(this.tr("Menu"), null, action.getMobileMenu()); + toolbar.add(mobileMenu); + toolbar.setOverflowIndicator(mobileMenu); + toolbar.setOverflowHandling(true); + toolbar.add(action); + toolbar.setRemovePriority(action, 1); toolbar.addSpacer(); - var form = this._form = new callbackery.ui.form.Auto(cfg.form,null,callbackery.ui.form.renderer.HBox, this); + var form = this._form = new callbackery.ui.form.Auto(cfg.form, null, callbackery.ui.form.renderer.HBox, this); toolbar.add(form); - return toolbar; + return toolbar; }, - _createTable: function(){ + _createTable() { var cfg = this._cfg; - var model = this._model = new callbackery.data.RemoteTableModel(cfg,this._getParentFormData); - var table = this._table = new qx.ui.table.Table(model,{ - tableColumnModel : function(obj) { + var model = this._model = new callbackery.data.RemoteTableModel(cfg, this._getParentFormData); + var table = this._table = new qx.ui.table.Table(model, { + tableColumnModel: function (obj) { return new qx.ui.table.columnmodel.Resize(obj); } }).set({ @@ -69,58 +75,94 @@ qx.Class.define('callbackery.ui.plugin.Table', { }); table.getDataRowRenderer().setHighlightFocusRow(false); var ctxMenu = this._action.getTableContextMenu(); - if (ctxMenu){ + if (ctxMenu) { table.setContextMenu(ctxMenu); } var defaultAction = this._action.getDefaultAction(); - if (defaultAction){ - table.addListener('cellDbltap',defaultAction,this._action); + if (defaultAction) { + table.addListener('cellDbltap', defaultAction, this._action); } var tcm = table.getTableColumnModel(); var resizeBehavior = tcm.getBehavior(); - cfg.table.forEach(function(col,i){ + cfg.table.forEach(function (col, i) { var cr; switch (col.type) { case 'boolean': - cr = new qx.ui.table.cellrenderer.Boolean; + cr = new qx.ui.table.cellrenderer.Boolean; break; case 'html': - cr = new qx.ui.table.cellrenderer.Html; + cr = new qx.ui.table.cellrenderer.Html; break; case 'date': - cr = new qx.ui.table.cellrenderer.Date; + cr = new qx.ui.table.cellrenderer.Date; if (col.format != null) { cr.setDateFormat(new qx.util.format.DateFormat(col.format)); } break; case 'str': case 'string': - cr = new qx.ui.table.cellrenderer.String( - col.align,col.color,col.style,col.weight + cr = new qx.ui.table.cellrenderer.String( + col.align, col.color, col.style, col.weight ); break; case 'num': case 'number': - cr = new qx.ui.table.cellrenderer.Number( - col.align,col.color,col.style,col.weight + cr = new qx.ui.table.cellrenderer.Number( + col.align, col.color, col.style, col.weight ); if (col.format != null) { cr.setNumberFormat( new callbackery.util.format.NumberFormat() - .set(col.format) + .set(col.format) ); } break; } - if (cr){ + if (cr) { tcm.setDataCellRenderer(i, cr); } if ('visible' in col) { tcm.setColumnVisible(i, col.visible); } - if (col.width != null){ - resizeBehavior.setWidth(i, String(col.width)); + }); + + let setColumnResizeBehavior = () => { + let flexList = []; + let totalFlex = 0; + cfg.table.forEach((col, i) => { + if (tcm.isColumnVisible(i) && col.width) { + let match = String(col.width).match(/^(\d+)\*/); + if (match) { + let flex = parseInt(match[1]); + totalFlex += flex; + flexList[i] = flex; + } + } + }); + let width = this.getBounds().width; + let cols = tcm.getVisibleColumnCount(); + if (width / cols < 100) { + let oneFlex = cols * 100 / totalFlex; + cfg.table.forEach((col, i) => { + if (flexList[i]) { + resizeBehavior.setWidth(i, Math.round(oneFlex * flexList[i])); + } + }); + } + else { + cfg.table.forEach((col, i) => { + if (col.width) { + resizeBehavior.setWidth(i, col.width); + } + }); } + }; + this.addListener('resize', () => { + setColumnResizeBehavior(); + }); + + table.addListener('columnVisibilityChanged', () => { + setColumnResizeBehavior(); }); var selectionModel = table.getSelectionModel(); @@ -128,10 +170,10 @@ qx.Class.define('callbackery.ui.plugin.Table', { selectionModel.setSelectionMode(qx.ui.table.selection.Model.SINGLE_SELECTION); var buttonMap = this._action.getButtonMap(); var buttonSetMap = this._action.getButtonSetMap(); - - selectionModel.addListener('changeSelection',function(){ + + selectionModel.addListener('changeSelection', function () { var noSelection = true; - selectionModel.iterateSelection(function(index) { + selectionModel.iterateSelection(function (index) { var rowData = model.getRowData(index); if (rowData != null) { noSelection = false; @@ -141,54 +183,54 @@ qx.Class.define('callbackery.ui.plugin.Table', { for (var key in rowData._actionSet) { var as = rowData._actionSet[key] if (as.label) { - as.label = this.xtr(as.label) + as.label = this.xtr(as.label) } buttonMap[key].set(as); } } } - },this); - if (noSelection){ + }, this); + if (noSelection) { for (var key in buttonSetMap) { buttonMap[key].set(buttonSetMap[key]); } } - },this); + }, this); var processing = false; - model.addListener('dataChanged',function(e){ - if (processing){ + model.addListener('dataChanged', function (e) { + if (processing) { return; } processing = true; var lastData = this.getSelection(); - if (!qx.lang.Type.isObject(lastData)){ + if (!qx.lang.Type.isObject(lastData)) { return; } - new qx.util.DeferredCall(function(){ - if (currentRow !== null){ - for (var offset=-1;offset<=1;offset++){ - if (currentRow + offset < 0){ + new qx.util.DeferredCall(function () { + if (currentRow !== null) { + for (var offset = -1; offset <= 1; offset++) { + if (currentRow + offset < 0) { continue; } - var currentData = model.getRowData(currentRow+offset); - if (!qx.lang.Type.isObject(currentData)){ + var currentData = model.getRowData(currentRow + offset); + if (!qx.lang.Type.isObject(currentData)) { continue; } var gotPrimary = false; var equal = true; - cfg.table.forEach(function(col,i){ - if (col.primary){ + cfg.table.forEach(function (col, i) { + if (col.primary) { gotPrimary = true; - if (currentData[col.key] != lastData[col.key]){ + if (currentData[col.key] != lastData[col.key]) { equal = false; } } }); - if (gotPrimary && equal){ - if (offset != 0){ + if (gotPrimary && equal) { + if (offset != 0) { table.clearFocusedRowHighlight(); - table.setFocusedCell(0,currentRow+offset,false); - selectionModel.setSelectionInterval(currentRow+offset,currentRow+offset); + table.setFocusedCell(0, currentRow + offset, false); + selectionModel.setSelectionInterval(currentRow + offset, currentRow + offset); } this.setSelection(currentData); processing = false; @@ -200,23 +242,23 @@ qx.Class.define('callbackery.ui.plugin.Table', { table.clearFocusedRowHighlight(); this.setSelection({}); processing = false; - },this).schedule(); - },this); + }, this).schedule(); + }, this); - this.addListener('appear',function(e){ + this.addListener('appear', function (e) { model.reloadData(); }); - this._form.addListener('changeData',function(e){ + this._form.addListener('changeData', function (e) { model.setFormData(e.getData()); - if (this._loading == 0){ // only reload when data has been changed by human + if (this._loading == 0) { // only reload when data has been changed by human this._loadDataReadOnly(); model.reloadData(); } - },this); + }, this); - this._action.addListener('actionResponse',function(e){ + this._action.addListener('actionResponse', function (e) { var data = e.getData(); - switch (data.action){ + switch (data.action) { case 'reload': case 'dataModified': this._loadDataReadOnly();