WIP on Date widget

Something's not right with the parser/formatter, they're not getting called.

Times not handled yet
This commit is contained in:
nathan 2021-08-13 15:26:18 -06:00
parent 35e5d57b2a
commit 4f225054f8
11 changed files with 3578 additions and 3381 deletions

View File

@ -9,18 +9,18 @@
*/ */
import {css, html, LitElement} from "@lion/core"; import {css, html, LitElement} from "../../../node_modules/@lion/core/index.js";
import {Et2Widget} from "./Et2Widget"; import {Et2Widget} from "./Et2Widget";
export class Et2Box extends Et2Widget(LitElement) export class Et2Box extends Et2Widget(LitElement)
{ {
static get styles() static get styles()
{ {
return [ return [
css` css`
:host { :host {
display: block; display: block;
width: 100%; width: 100%;
} }
:host > div { :host > div {
display: flex; display: flex;
@ -31,53 +31,53 @@ export class Et2Box extends Et2Widget(LitElement)
::slotted(*) { ::slotted(*) {
/* CSS for child elements */ /* CSS for child elements */
}`, }`,
]; ];
} }
render() render()
{ {
return html` return html`
<div class="et2_box" ${this.id ? html`id="${this.id}"` : ''}> <div class="et2_box" ${this.id ? html`id="${this.id}"` : ''}>
<slot><p>Empty box</p></slot> <slot><p>Empty box</p></slot>
</div> `; </div> `;
} }
_createNamespace(): boolean _createNamespace(): boolean
{ {
return true; return true;
} }
} }
customElements.define("et2-box", Et2Box); customElements.define("et2-box", Et2Box);
export class Et2HBox extends Et2Box export class Et2HBox extends Et2Box
{ {
static get styles() static get styles()
{ {
return [ return [
...super.styles, ...super.styles,
css` css`
:host > div { :host > div {
flex-direction: row; flex-direction: row;
}` }`
]; ];
} }
} }
customElements.define("et2-hbox", Et2HBox); customElements.define("et2-hbox", Et2HBox);
export class Et2VBox extends Et2Box export class Et2VBox extends Et2Box
{ {
static get styles() static get styles()
{ {
return [ return [
...super.styles, ...super.styles,
css` css`
:host > div { :host > div {
flex-direction: column; flex-direction: column;
}` }`
]; ];
} }
} }
customElements.define("et2-vbox", Et2VBox); customElements.define("et2-vbox", Et2VBox);

View File

@ -9,22 +9,22 @@
*/ */
import {css, html} from "@lion/core/index.js"; import {css, html} from "../../../node_modules/@lion/core/index.js";
import {LionButton} from "@lion/button/index.js"; import {LionButton} from "../../../node_modules/@lion/button/index.js";
import {Et2InputWidget} from "./et2_core_inputWidget"; import {Et2InputWidget} from "./et2_core_inputWidget";
import {Et2Widget} from "./Et2Widget"; import {Et2Widget} from "./Et2Widget";
export class Et2Button extends Et2InputWidget(Et2Widget(LionButton)) export class Et2Button extends Et2InputWidget(Et2Widget(LionButton))
{ {
protected _created_icon_node: HTMLImageElement; protected _created_icon_node: HTMLImageElement;
protected clicked: boolean = false; protected clicked: boolean = false;
private image: string; private image: string;
static get styles() static get styles()
{ {
return [ return [
...super.styles, ...super.styles,
css` css`
:host { :host {
padding: 1px 8px; padding: 1px 8px;
/* These should probably come from somewhere else */ /* These should probably come from somewhere else */
@ -36,114 +36,115 @@ export class Et2Button extends Et2InputWidget(Et2Widget(LionButton))
width: 20px; width: 20px;
padding-right: 3px; padding-right: 3px;
}`, }`,
]; ];
}
static get properties()
{
return {
...super.properties,
image: {type: String},
onclick: {type: Function}
}
}
constructor()
{
super();
// Property default values
this.image = '';
// Create icon Element since BXButton puts it as child, but we put it as attribute
this._created_icon_node = document.createElement("img");
this._created_icon_node.slot = "icon";
// Do not add this._icon here, no children can be added in constructor
// Define a default click handler
// If a different one gets set via attribute, it will be used instead
this.onclick = (typeof this.onclick === "function") ? this.onclick : () =>
{
return this.getInstanceManager().submit();
};
}
connectedCallback()
{
super.connectedCallback();
//this.classList.add("et2_button")
if (this.image)
{
this._created_icon_node.src = egw.image(this.image);
this.appendChild(this._created_icon_node);
} }
static get properties() this.addEventListener("click", this._handleClick.bind(this));
}
_handleClick(event: MouseEvent): boolean
{
debugger;
// ignore click on readonly button
if (this.disabled) return false;
this.clicked = true;
// Cancel buttons don't trigger the close confirmation prompt
if (this.classList.contains("et2_button_cancel"))
{ {
return { this.getInstanceManager()?.skip_close_prompt();
image: {type: String},
onclick: {type: Function}
}
} }
constructor() if (!super._handleClick(event))
{ {
super(); this.clicked = false;
return false;
// Property default values
this.image = '';
// Create icon Element since BXButton puts it as child, but we put it as attribute
this._created_icon_node = document.createElement("img");
this._created_icon_node.slot = "icon";
// Do not add this._icon here, no children can be added in constructor
// Define a default click handler
// If a different one gets set via attribute, it will be used instead
this.onclick = (typeof this.onclick === "function") ? this.onclick : () =>
{
return this.getInstanceManager().submit();
};
} }
connectedCallback() this.clicked = false;
{ this.getInstanceManager()?.skip_close_prompt(false);
super.connectedCallback(); return true;
}
//this.classList.add("et2_button") render()
{
if (this.image) return html`
{
this._created_icon_node.src = egw.image(this.image);
this.appendChild(this._created_icon_node);
}
this.addEventListener("click", this._handleClick.bind(this));
}
_handleClick(event: MouseEvent): boolean
{
debugger;
// ignore click on readonly button
if (this.disabled) return false;
this.clicked = true;
// Cancel buttons don't trigger the close confirmation prompt
if (this.classList.contains("et2_button_cancel"))
{
this.getInstanceManager()?.skip_close_prompt();
}
if (!super._handleClick(event))
{
this.clicked = false;
return false;
}
this.clicked = false;
this.getInstanceManager()?.skip_close_prompt(false);
return true;
}
render()
{
return html`
<div class="button-content et2_button" id="${this._buttonId}"> <div class="button-content et2_button" id="${this._buttonId}">
<slot name="icon"></slot> <slot name="icon"></slot>
<slot></slot> <slot></slot>
</div> `; </div> `;
} }
/** /**
* Implementation of the et2_IInput interface * Implementation of the et2_IInput interface
*/ */
/** /**
* Always return false as a button is never dirty * Always return false as a button is never dirty
*/ */
isDirty() isDirty()
{
return false;
}
resetDirty()
{
}
getValue()
{
if (this.clicked)
{ {
return false; return true;
} }
resetDirty() // If "null" is returned, the result is not added to the submitted
{ // array.
} return null;
}
getValue()
{
if (this.clicked)
{
return true;
}
// If "null" is returned, the result is not added to the submitted
// array.
return null;
}
} }
customElements.define("et2-button", Et2Button); customElements.define("et2-button", Et2Button);

163
api/js/etemplate/Et2Date.ts Normal file
View File

@ -0,0 +1,163 @@
/**
* EGroupware eTemplate2 - Date widget (WebComponent)
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
*/
import {css, html} from "../../../node_modules/@lion/core/index.js"
import {LionInputDatepicker} from "../../../node_modules/@lion/input-datepicker/index.js"
import {Et2InputWidget} from "./et2_core_inputWidget";
import {Et2Widget} from "./Et2Widget";
/**
* To parse a date into the right format
*
* @param {string} dateString
* @returns {Date | undefined}
*/
export function parseDate(dateString)
{
debugger;
let formatString = <string>(egw.preference("dateformat") || 'Y-m-d');
formatString = formatString.replaceAll(/-\/\./ig, '-');
let parsedString = "";
switch (formatString)
{
case 'd-m-Y':
parsedString = `${dateString.slice(6, 10)}/${dateString.slice(
3,
5,
)}/${dateString.slice(0, 2)}`;
break;
case 'm-d-Y':
parsedString = `${dateString.slice(6, 10)}/${dateString.slice(
0,
2,
)}/${dateString.slice(3, 5)}`;
break;
case 'Y-m-d':
parsedString = `${dateString.slice(0, 4)}/${dateString.slice(
5,
7,
)}/${dateString.slice(8, 10)}`;
break;
case 'd-M-Y':
parsedString = `${dateString.slice(6, 10)}/${dateString.slice(
3,
5,
)}/${dateString.slice(0, 2)}`;
default:
parsedString = '0000/00/00';
}
const [year, month, day] = parsedString.split('/').map(Number);
const parsedDate = new Date(Date.UTC(year, month - 1, day));
// Check if parsedDate is not `Invalid Date` or that the date has changed (e.g. the not existing 31.02.2020)
if (
year > 0 &&
month > 0 &&
day > 0 &&
parsedDate.getDate() === day &&
parsedDate.getMonth() === month - 1
)
{
return parsedDate;
}
return undefined;
}
/**
* Format dates according to user preference
*
* @param {Date} date
* @param {import('@lion/localize/types/LocalizeMixinTypes').FormatDateOptions} [options] Intl options are available
* set 'dateFormat': "Y-m-d" to specify a particular format
* @returns {string}
*/
export function formatDate(date: Date, options): string
{
debugger;
if (!date || !(date instanceof Date))
{
return "";
}
let _value = '';
// Add timezone offset back in, or formatDate will lose those hours
let formatDate = new Date(date.valueOf() + date.getTimezoneOffset() * 60 * 1000);
let dateformat = options.dateFormat || <string>egw.preference("dateformat") || 'Y-m-d';
var replace_map = {
d: "" + date.getUTCDate(),
m: "" + date.getUTCMonth() + 1,
Y: "" + date.getUTCFullYear()
}
var re = new RegExp(Object.keys(replace_map).join("|"), "gi");
_value = dateformat.replace(re, function (matched)
{
return replace_map[matched];
});
return _value;
}
export class Et2Date extends Et2InputWidget(Et2Widget(LionInputDatepicker))
{
static get styles()
{
return [
...super.styles,
css`
/* Custom CSS */
`,
];
}
static get properties()
{
return {
...super.properties,
value: {
attribute: true,
converter: {
toAttribute(value)
{
return value ? value.toJSON().replace(/\.\d{3}Z$/, 'Z') : "";
},
fromAttribute(value)
{
return new Date(value);
}
}
},
}
}
constructor()
{
super();
this.parser = parseDate;
this.formatter = formatDate;
}
connectedCallback()
{
super.connectedCallback();
}
getValue()
{
debugger;
return this.modelValue ? this.modelValue.toJSON().replace(/\.\d{3}Z$/, 'Z') : "";
}
}
customElements.define("et2-date", Et2Date);

View File

@ -1,5 +1,5 @@
/** /**
* EGroupware eTemplate2 - Button widget * EGroupware eTemplate2 - Textbox widget (WebComponent)
* *
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate * @package etemplate
@ -9,45 +9,45 @@
*/ */
import {css, html} from "@lion/core"; import {css, html} from "../../../node_modules/@lion/core/index.js"
import {LionInput} from "@lion/input"; import {LionInput} from "../../../node_modules/@lion/input/index.js"
import {Et2InputWidget} from "./et2_core_inputWidget"; import {Et2InputWidget} from "./et2_core_inputWidget";
import {Et2Widget} from "./Et2Widget"; import {Et2Widget} from "./Et2Widget";
export class Et2Textbox extends Et2InputWidget(Et2Widget(LionInput)) export class Et2Textbox extends Et2InputWidget(Et2Widget(LionInput))
{ {
static get styles() static get styles()
{ {
return [ return [
...super.styles, ...super.styles,
css` css`
/* Custom CSS */ /* Custom CSS */
`, `,
]; ];
}
static get properties()
{
return {
...super.properties,
value: {attribute: true},
onclick: {type: Function}
} }
}
static get properties() constructor()
{ {
return { debugger;
...super.properties, super();
value: {attribute: true},
onclick: {type: Function}
}
}
constructor() }
{
debugger;
super();
} connectedCallback()
{
super.connectedCallback();
connectedCallback() }
{
super.connectedCallback();
}
} }
customElements.define("et2-textbox", Et2Textbox); customElements.define("et2-textbox", Et2Textbox);

File diff suppressed because it is too large Load Diff

View File

@ -15,17 +15,19 @@
*/ */
import {et2_no_init} from "./et2_core_common"; import {et2_no_init} from "./et2_core_common";
import { ClassWithAttributes } from "./et2_core_inheritance"; import {ClassWithAttributes} from "./et2_core_inheritance";
import { et2_widget, WidgetConfig } from "./et2_core_widget"; import {et2_widget, WidgetConfig} from "./et2_core_widget";
import { et2_valueWidget } from './et2_core_valueWidget' import {et2_valueWidget} from './et2_core_valueWidget'
import {et2_IInput, et2_IInputNode, et2_ISubmitListener} from "./et2_core_interfaces"; import {et2_IInput, et2_IInputNode, et2_ISubmitListener} from "./et2_core_interfaces";
import {et2_compileLegacyJS} from "./et2_core_legacyJSFunctions"; import {et2_compileLegacyJS} from "./et2_core_legacyJSFunctions";
// fixing circular dependencies by only importing the type (not in compiled .js) // fixing circular dependencies by only importing the type (not in compiled .js)
import type {et2_tabbox} from "./et2_widget_tabs"; import type {et2_tabbox} from "./et2_widget_tabs";
export interface et2_input { export interface et2_input
getInputNode() : HTMLInputElement|HTMLElement; {
getInputNode(): HTMLInputElement | HTMLElement;
} }
/** /**
* et2_inputWidget derrives from et2_simpleWidget and implements the IInput * et2_inputWidget derrives from et2_simpleWidget and implements the IInput
* interface. When derriving from this class, call setDOMNode with an input * interface. When derriving from this class, call setDOMNode with an input
@ -33,9 +35,9 @@ export interface et2_input {
*/ */
export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_ISubmitListener, et2_input export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_ISubmitListener, et2_input
{ {
static readonly _attributes : any = { static readonly _attributes: any = {
"needed": { "needed": {
"name": "Required", "name": "Required",
"default": false, "default": false,
"type": "boolean", "type": "boolean",
"description": "If required, the user must enter a value before the form can be submitted" "description": "If required, the user must enter a value before the form can be submitted"
@ -78,7 +80,7 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
/** /**
* Constructor * Constructor
*/ */
constructor(_parent, _attrs? : WidgetConfig, _child? : object) constructor(_parent, _attrs?: WidgetConfig, _child?: object)
{ {
// Call the inherited constructor // Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_inputWidget._attributes, _child || {})); super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_inputWidget._attributes, _child || {}));
@ -105,7 +107,7 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
/** /**
* Make sure dirty flag is properly set * Make sure dirty flag is properly set
*/ */
doLoadingFinished() : boolean | JQueryPromise<unknown> doLoadingFinished(): boolean | JQueryPromise<unknown>
{ {
let result = super.doLoadingFinished(); let result = super.doLoadingFinished();
@ -141,10 +143,12 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
{ {
jQuery(node) jQuery(node)
.off('.et2_inputWidget') .off('.et2_inputWidget')
.bind("change.et2_inputWidget", this, function(e) { .bind("change.et2_inputWidget", this, function (e)
{
e.data.change.call(e.data, this); e.data.change.call(e.data, this);
}) })
.bind("focus.et2_inputWidget", this, function(e) { .bind("focus.et2_inputWidget", this, function (e)
{
e.data.focus.call(e.data, this); e.data.focus.call(e.data, this);
}); });
} }
@ -173,14 +177,16 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
if (valid && this.onchange) if (valid && this.onchange)
{ {
if(typeof this.onchange == 'function') if (typeof this.onchange == 'function')
{ {
// Make sure function gets a reference to the widget // Make sure function gets a reference to the widget
var args = Array.prototype.slice.call(arguments); var args = Array.prototype.slice.call(arguments);
if(args.indexOf(this) == -1) args.push(this); if (args.indexOf(this) == -1) args.push(this);
return this.onchange.apply(this, args); return this.onchange.apply(this, args);
} else { }
else
{
return (et2_compileLegacyJS(this.options.onchange, this, _node))(); return (et2_compileLegacyJS(this.options.onchange, this, _node))();
} }
} }
@ -189,11 +195,11 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
focus(_node) focus(_node)
{ {
if(typeof this.options.onfocus == 'function') if (typeof this.options.onfocus == 'function')
{ {
// Make sure function gets a reference to the widget // Make sure function gets a reference to the widget
var args = Array.prototype.slice.call(arguments); var args = Array.prototype.slice.call(arguments);
if(args.indexOf(this) == -1) args.push(this); if (args.indexOf(this) == -1) args.push(this);
return this.options.onfocus.apply(this, args); return this.options.onfocus.apply(this, args);
} }
@ -206,13 +212,13 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
* *
* @param {string} _value value to set * @param {string} _value value to set
*/ */
set_value(_value : any | null) set_value(_value: any | null)
{ {
var node = this.getInputNode(); var node = this.getInputNode();
if (node) if (node)
{ {
jQuery(node).val(_value); jQuery(node).val(_value);
if(this.isAttached() && this._oldValue !== et2_no_init && this._oldValue !== _value) if (this.isAttached() && this._oldValue !== et2_no_init && this._oldValue !== _value)
{ {
jQuery(node).change(); jQuery(node).change();
} }
@ -223,7 +229,7 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
set_id(_value) set_id(_value)
{ {
this.id = _value; this.id = _value;
this.dom_id = _value && this.getInstanceManager() ? this.getInstanceManager().uniqueId+'_'+this.id : _value; this.dom_id = _value && this.getInstanceManager() ? this.getInstanceManager().uniqueId + '_' + this.id : _value;
// Set the id of the _input_ node (in contrast to the default // Set the id of the _input_ node (in contrast to the default
// implementation, which sets the base node) // implementation, which sets the base node)
@ -249,9 +255,12 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
var node = this.getInputNode(); var node = this.getInputNode();
if (node) if (node)
{ {
if(_value && !this.options.readonly) { if (_value && !this.options.readonly)
{
jQuery(node).attr("required", "required"); jQuery(node).attr("required", "required");
} else { }
else
{
node.removeAttribute("required"); node.removeAttribute("required");
} }
} }
@ -273,8 +282,8 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
jQuery(node).addClass("invalid"); jQuery(node).addClass("invalid");
// If on a tab, switch to that tab so user can see it // If on a tab, switch to that tab so user can see it
let widget : et2_widget = this; let widget: et2_widget = this;
while(widget.getParent() && widget.getType() != 'tabbox') while (widget.getParent() && widget.getType() != 'tabbox')
{ {
widget = widget.getParent(); widget = widget.getParent();
} }
@ -319,26 +328,26 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
isDirty() isDirty()
{ {
let value = this.getValue(); let value = this.getValue();
if(typeof value !== typeof this._oldValue) if (typeof value !== typeof this._oldValue)
{ {
return true; return true;
} }
if(this._oldValue === value) if (this._oldValue === value)
{ {
return false; return false;
} }
switch(typeof this._oldValue) switch (typeof this._oldValue)
{ {
case "object": case "object":
if(typeof this._oldValue.length !== "undefined" && if (typeof this._oldValue.length !== "undefined" &&
this._oldValue.length !== value.length this._oldValue.length !== value.length
) )
{ {
return true; return true;
} }
for(let key in this._oldValue) for (let key in this._oldValue)
{ {
if(this._oldValue[key] !== value[key]) return true; if (this._oldValue[key] !== value[key]) return true;
} }
return false; return false;
default: default:
@ -392,58 +401,63 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_
*/ */
type Constructor<T = {}> = new (...args: any[]) => T; type Constructor<T = {}> = new (...args: any[]) => T;
export const Et2InputWidget = <T extends Constructor>(superClass: T) => { export const Et2InputWidget = <T extends Constructor>(superClass: T) =>
class Et2InputWidgetClass extends superClass implements et2_IInput, et2_IInputNode { {
class Et2InputWidgetClass extends superClass implements et2_IInput, et2_IInputNode
{
label: string = ''; label: string = '';
protected value: string | number | Object; protected value: string | number | Object;
protected _oldValue: string | number | Object; protected _oldValue: string | number | Object;
/** WebComponent **/ /** WebComponent **/
static get properties() { static get properties()
{
return { return {
...super.properties, ...super.properties,
value: {attribute: false} value: {attribute: false}
}; };
} }
constructor() { constructor()
{
super(); super();
} }
set_value(new_value) set_value(new_value)
{ {
this.modelValue=new_value; this.value = new_value;
} }
getValue() getValue()
{ {
return this._inputNode.value; return this.getInputNode().value;
} }
isDirty() isDirty()
{ {
let value = this.getValue(); let value = this.getValue();
if(typeof value !== typeof this._oldValue) if (typeof value !== typeof this._oldValue)
{ {
return true; return true;
} }
if(this._oldValue === value) if (this._oldValue === value)
{ {
return false; return false;
} }
switch(typeof this._oldValue) switch (typeof this._oldValue)
{ {
case "object": case "object":
if(typeof this._oldValue.length !== "undefined" && if (typeof this._oldValue.length !== "undefined" &&
this._oldValue.length !== value.length this._oldValue.length !== value.length
) )
{ {
return true; return true;
} }
for(let key in this._oldValue) for (let key in this._oldValue)
{ {
if(this._oldValue[key] !== value[key]) return true; if (this._oldValue[key] !== value[key]) return true;
} }
return false; return false;
default: default:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -29,7 +29,7 @@ use EGroupware\Api;
* &8 = dont show time for readonly and type date-time if time is 0:00, * &8 = dont show time for readonly and type date-time if time is 0:00,
* &16 = prefix r/o display with dow * &16 = prefix r/o display with dow
* &32 = prefix r/o display with week-number * &32 = prefix r/o display with week-number
* &64 = prefix r/o display with weeknumber and dow * &64 = prefix r/o display with weeknumber and dow
* &128 = no icon to trigger popup, click into input trigers it, also removing the separators to save space * &128 = no icon to trigger popup, click into input trigers it, also removing the separators to save space
* *
* @todo validation of date-duration * @todo validation of date-duration
@ -60,7 +60,7 @@ class Date extends Transformer
* @param string $cname * @param string $cname
* @param array $expand values for keys 'c', 'row', 'c_', 'row_', 'cont' * @param array $expand values for keys 'c', 'row', 'c_', 'row_', 'cont'
*/ */
public function beforeSendToClient($cname, array $expand=null) public function beforeSendToClient($cname, array $expand = null)
{ {
if($this->type == 'date-houronly') if($this->type == 'date-houronly')
{ {
@ -87,14 +87,20 @@ class Date extends Transformer
* @param array $expand * @param array $expand
* @param array $data Row data * @param array $data Row data
*/ */
public function set_row_value($cname, Array $expand, Array &$data) public function set_row_value($cname, array $expand, array &$data)
{ {
if($this->type == 'date-duration') return; if($this->type == 'date-duration')
{
return;
}
$form_name = self::form_name($cname, $this->id, $expand); $form_name = self::form_name($cname, $this->id, $expand);
$value =& $this->get_array($data, $form_name, true); $value =& $this->get_array($data, $form_name, true);
if (true) $value = $this->format_date($value); if(true)
{
$value = $this->format_date($value);
}
} }
/** /**
@ -104,14 +110,17 @@ class Date extends Transformer
*/ */
public function format_date($value) public function format_date($value)
{ {
if (!$value) return $value; // otherwise we will get current date or 1970-01-01 instead of an empty value if(!$value)
{
return $value;
} // otherwise we will get current date or 1970-01-01 instead of an empty value
// for DateTime objects (regular PHP and Api\DateTime ones), set user timezone // for DateTime objects (regular PHP and Api\DateTime ones), set user timezone
if ($value instanceof \DateTime) if($value instanceof \DateTime)
{ {
$date = Api\DateTime::server2user($value); $date = Api\DateTime::server2user($value);
} }
elseif ($this->attrs['data_format'] && $this->attrs['data_format'] !== 'object') elseif($this->attrs['data_format'] && $this->attrs['data_format'] !== 'object')
{ {
$date = Api\DateTime::createFromFormat($this->attrs['data_format'], $value, Api\DateTime::$user_timezone); $date = Api\DateTime::createFromFormat($this->attrs['data_format'], $value, Api\DateTime::$user_timezone);
} }
@ -142,58 +151,65 @@ class Date extends Transformer
* @param string $cname current namespace * @param string $cname current namespace
* @param array $expand values for keys 'c', 'row', 'c_', 'row_', 'cont' * @param array $expand values for keys 'c', 'row', 'c_', 'row_', 'cont'
* @param array $content * @param array $content
* @param array &$validated=array() validated content * @param array &$validated =array() validated content
* @return boolean true if no validation error, false otherwise * @return boolean true if no validation error, false otherwise
*/ */
public function validate($cname, array $expand, array $content, &$validated=array()) public function validate($cname, array $expand, array $content, &$validated = array())
{ {
$form_name = self::form_name($cname, $this->id, $expand); $form_name = self::form_name($cname, $this->id, $expand);
if (!$this->is_readonly($cname, $form_name) && $this->type != 'date-since') // date-since is always readonly if(!$this->is_readonly($cname, $form_name) && $this->type != 'date-since') // date-since is always readonly
{ {
$value = self::get_array($content, $form_name); $value = self::get_array($content, $form_name);
$valid =& self::get_array($validated, $form_name, true); $valid =& self::get_array($validated, $form_name, true);
if ($value && $this->type !== 'date-duration') if($value && $this->type !== 'date-duration')
{ {
try try
{ {
if (substr($value, -1) === 'Z') $value = substr($value, 0, -1); if(substr($value, -1) === 'Z')
{
$value = substr($value, 0, -1);
}
$date = new Api\DateTime($value); $date = new Api\DateTime($value);
} }
catch(\Exception $e) catch (\Exception $e)
{ {
unset($e); unset($e);
$date = null; $date = null;
$value = ''; $value = '';
// this is not really a user error, but one of the clientside engine // this is not really a user error, but one of the clientside engine
self::set_validation_error($form_name,lang("'%1' is not a valid date !!!", $value).' '.$this->data_format); self::set_validation_error($form_name, lang("'%1' is not a valid date !!!", $value) . ' ' . $this->data_format);
} }
} }
if ((string)$value === '' && $this->attrs['needed']) if((string)$value === '' && $this->attrs['needed'])
{ {
self::set_validation_error($form_name,lang('Field must not be empty !!!')); self::set_validation_error($form_name, lang('Field must not be empty !!!'));
} }
elseif (is_null($value)) elseif(is_null($value))
{ {
$valid = null; $valid = null;
} }
elseif ($this->type == 'date-duration') elseif($this->type == 'date-duration')
{ {
$valid = (string)$value === '' ? '' : (int)$value; $valid = (string)$value === '' ? '' : (int)$value;
} }
if (!empty($this->attrs['min']) && !empty($value)) if(!empty($this->attrs['min']) && !empty($value))
{ {
if(is_numeric($this->attrs['min'])) if(is_numeric($this->attrs['min']))
{ {
$min = new Api\DateTime(strtotime( $this->attrs['min'] . 'days')); $min = new Api\DateTime(strtotime($this->attrs['min'] . 'days'));
} }
elseif (preg_match('/[+-][[:digit:]]+[ymwd]/',$this->attrs['min'])) elseif(preg_match('/[+-][[:digit:]]+[ymwd]/', $this->attrs['min']))
{ {
// Relative date with periods // Relative date with periods
$min = new Api\DateTime(strtotime(str_replace(array('y','m','w','d'), array('years','months','weeks','days'), $this->attrs['min']))); $min = new Api\DateTime(strtotime(str_replace(array('y', 'm', 'w', 'd'), array('years', 'months',
'weeks',
'days'), $this->attrs['min'])
)
);
} }
else else
{ {
@ -201,23 +217,28 @@ class Date extends Transformer
} }
if($date < $min) if($date < $min)
{ {
self::set_validation_error($form_name,lang( self::set_validation_error($form_name, lang(
"Value has to be at least '%1' !!!", "Value has to be at least '%1' !!!",
$min->format($this->type != 'date') $min->format($this->type != 'date')
),''); ), ''
);
$value = $min; $value = $min;
} }
} }
if (!empty($this->attrs['max']) && !empty($value)) if(!empty($this->attrs['max']) && !empty($value))
{ {
if(is_numeric($this->attrs['max'])) if(is_numeric($this->attrs['max']))
{ {
$max = new Api\DateTime(strtotime( $this->attrs['max'] . 'days')); $max = new Api\DateTime(strtotime($this->attrs['max'] . 'days'));
} }
elseif (preg_match('/[+-][[:digit:]]+[ymwd]/',$this->attrs['max'])) elseif(preg_match('/[+-][[:digit:]]+[ymwd]/', $this->attrs['max']))
{ {
// Relative date with periods // Relative date with periods
$max = new Api\DateTime(strtotime(str_replace(array('y','m','w','d'), array('years','months','weeks','days'), $this->attrs['max']))); $max = new Api\DateTime(strtotime(str_replace(array('y', 'm', 'w', 'd'), array('years', 'months',
'weeks',
'days'), $this->attrs['max'])
)
);
} }
else else
{ {
@ -225,14 +246,15 @@ class Date extends Transformer
} }
if($date > $max) if($date > $max)
{ {
self::set_validation_error($form_name,lang( self::set_validation_error($form_name, lang(
"Value has to be at maximum '%1' !!!", "Value has to be at maximum '%1' !!!",
$max->format($this->type != 'date') $max->format($this->type != 'date')
),''); ), ''
);
$value = $max; $value = $max;
} }
} }
if ($this->type == 'date-duration') if($this->type == 'date-duration')
{ {
$valid = (string)$value === '' ? '' : (int)$value; $valid = (string)$value === '' ? '' : (int)$value;
} }
@ -241,22 +263,23 @@ class Date extends Transformer
// Not null, blank // Not null, blank
$value = ''; $value = '';
} }
elseif ($date && empty($this->attrs['data_format'])) // integer timestamp elseif($date && empty($this->attrs['data_format'])) // integer timestamp
{ {
$valid = $date->format('ts'); $valid = $date->format('ts');
} }
// string with formatting letters like for php's date() method // string with formatting letters like for php's date() method
elseif ($date && ($valid = $date->format($this->attrs['data_format']))) elseif($date && ($valid = $date->format($this->attrs['data_format'])))
{ {
// Nothing to do here // Nothing to do here
} }
else else
{ {
// this is not really a user error, but one of the clientside engine // this is not really a user error, but one of the clientside engine
self::set_validation_error($form_name,lang("'%1' is not a valid date !!!", $value).' '.$this->data_format); self::set_validation_error($form_name, lang("'%1' is not a valid date !!!", $value) . ' ' . $this->data_format);
} }
//error_log("$this : ($valid)" . Api\DateTime::to($valid)); //error_log("$this : ($valid)" . Api\DateTime::to($valid));
} }
} }
} }
\EGroupware\Api\Etemplate\Widget::registerWidget(__NAMESPACE__.'\\Date', array('time_or_date'));
\EGroupware\Api\Etemplate\Widget::registerWidget(__NAMESPACE__ . '\\Date', array('et2-date', 'time_or_date'));

View File

@ -165,7 +165,7 @@
</menulist> </menulist>
<description/> <description/>
<description value="Startdate" for="info_startdate"/> <description value="Startdate" for="info_startdate"/>
<date-time statustext="when should the ToDo or Phonecall be started, it shows up from that date in the filter open or own open (startpage)" id="info_startdate" class="et2_fullWidth"/> <et2-date statustext="when should the ToDo or Phonecall be started, it shows up from that date in the filter open or own open (startpage)" id="info_startdate" class="et2_fullWidth"></et2-date>
</row> </row>
<row class="dialogHeader3"> <row class="dialogHeader3">
<description value="Contact"/> <description value="Contact"/>

View File

@ -46,6 +46,11 @@
}, },
"dependencies": { "dependencies": {
"@andxor/jquery-ui-touch-punch-fix": "^1.0.2", "@andxor/jquery-ui-touch-punch-fix": "^1.0.2",
"@lion/button": "^0.14.2",
"@lion/core": "^0.18.2",
"@lion/input": "^0.15.4",
"@lion/input-date": "^0.12.6",
"@lion/input-datepicker": "^0.23.6",
"jquery-ui-dist": "^1.12.1", "jquery-ui-dist": "^1.12.1",
"jquery-ui-themes": "^1.12.0", "jquery-ui-themes": "^1.12.0",
"jquery-ui-timepicker-addon": "^1.6.3", "jquery-ui-timepicker-addon": "^1.6.3",