allow to submit partial content by passing a container(-widget) to etemplate2.submit()

- not send content is not validated and therefore not passed to server-side callback (currently only implemented for text-, select- and checkbox)
- new method et2_grid.getRow(_widget) to return a fake row container to pass it etemplate2.submit() --> implemented a real row-container for et2_grid
- new output_mode=4 for etemplate_new::exec() to force a json response, like form was submitted from client-side
--> allows to use full server-side validation for ajax like calls submitting only partial content
This commit is contained in:
Ralf Becker 2015-03-20 15:39:35 +00:00
parent d4c0992f45
commit 8c8e61630d
8 changed files with 85 additions and 15 deletions

View File

@ -95,6 +95,7 @@ class etemplate_new extends etemplate_widget_template
* -1 = first time return html, after use 0 (echo html incl. navbar), eg. for home * -1 = first time return html, after use 0 (echo html incl. navbar), eg. for home
* 2 = echo without navbar (eg. for popups) * 2 = echo without navbar (eg. for popups)
* 3 = return eGW independent html site * 3 = return eGW independent html site
* 4 = json response
* @param string $ignore_validation if not empty regular expression for validation-errors to ignore * @param string $ignore_validation if not empty regular expression for validation-errors to ignore
* @param array $changes change made in the last call if looping, only used internaly by process_exec * @param array $changes change made in the last call if looping, only used internaly by process_exec
* @return string html for $output_mode == 1, else nothing * @return string html for $output_mode == 1, else nothing
@ -106,6 +107,11 @@ class etemplate_new extends etemplate_widget_template
if (!$this->rel_path) throw new egw_exception_assertion_failed("No (valid) template '$this->name' found!"); if (!$this->rel_path) throw new egw_exception_assertion_failed("No (valid) template '$this->name' found!");
if ($output_mode == 4)
{
$output_mode = 0;
self::$response = egw_json_response::get();
}
self::$request->output_mode = $output_mode; // let extensions "know" they are run eg. in a popup self::$request->output_mode = $output_mode; // let extensions "know" they are run eg. in a popup
self::$request->content = self::$cont = $content; self::$request->content = self::$cont = $content;
self::$request->changes = $changes; self::$request->changes = $changes;

View File

@ -717,6 +717,22 @@ class etemplate_widget
return $pos; return $pos;
} }
/**
* return a reference to $arr[$idx]
*
* This works for non-trival indexes like 'a[b][c]' too: it returns &$arr[a][b][c]
* $sub = get_array($arr,'a[b]'); $sub = 'c'; is equivalent to $arr['a']['b'] = 'c';
*
* @param array& $_arr the array to search, referenz as a referenz gets returned
* @param string $_idx the index, may contain sub-indices like a[b], see example below
* @param mixed $_value value to set
*/
static function set_array(&$_arr, $_idx, $_value)
{
$ref =& self::get_array($_arr, $_idx, true);
if (true) $ref = $_value;
}
/** /**
* Checks if a widget is readonly: * Checks if a widget is readonly:
* - readonly attribute set * - readonly attribute set

View File

@ -52,6 +52,7 @@ class etemplate_widget_checkbox extends etemplate_widget
if (!$this->is_readonly($cname, $form_name)) if (!$this->is_readonly($cname, $form_name))
{ {
$value = self::get_array($content, $form_name); $value = self::get_array($content, $form_name);
if (!isset($value)) return; // value not transmitted --> nothing to validate
$valid =& self::get_array($validated, $form_name, true); $valid =& self::get_array($validated, $form_name, true);
if (!$value && $this->attrs['needed']) if (!$value && $this->attrs['needed'])

View File

@ -174,9 +174,11 @@ class etemplate_widget_menupopup extends etemplate_widget
} }
} }
} }
$valid =& self::get_array($validated, $form_name, true); if (isset($value))
if (true) $valid = $value; {
//error_log(__METHOD__."() $form_name: ".array2string($value_in).' --> '.array2string($value).', allowed='.array2string($allowed)); self::set_array($validated, $form_name, $value);
//error_log(__METHOD__."() $form_name: ".array2string($value_in).' --> '.array2string($value).', allowed='.array2string($allowed));
}
} }
else else
{ {

View File

@ -131,7 +131,6 @@ class etemplate_widget_textbox extends etemplate_widget
} }
$value = $value_in = self::get_array($content, $form_name); $value = $value_in = self::get_array($content, $form_name);
$valid =& self::get_array($validated, $form_name, true);
// passwords are not transmitted back to client (just asterisks) // passwords are not transmitted back to client (just asterisks)
// therefore we need to replace it again with preserved value // therefore we need to replace it again with preserved value
@ -185,8 +184,11 @@ class etemplate_widget_textbox extends etemplate_widget
} }
} }
} }
if (true) $valid = $value; if (isset($value))
//error_log(__METHOD__."() $form_name: ".array2string($value_in).' --> '.array2string($value)); {
self::set_array($validated, $form_name, $value);
//error_log(__METHOD__."() $form_name: ".array2string($value_in).' --> '.array2string($value));
}
} }
} }
} }

View File

@ -1026,6 +1026,47 @@ var et2_grid = et2_DOMWidget.extend([et2_IDetachedDOM, et2_IAligned, et2_IResize
} }
} }
},
/**
* Get a dummy row object containing all widget of a row
*
* This is only a temp. solution until rows are implemented as eT2 containers and
* _sender.getParent() will return a real row container.
*
* @param {et2_widget} _sender
* @returns {Array|undefined}
*/
getRow: function(_sender)
{
if (!_sender || !this.cells) return;
for(var r=0; r < this.cells.length; ++r)
{
var row = this.cells[r];
for(var c=0; c < row.length; ++c)
{
if (!row[c].widget) continue;
var found = row[c].widget === _sender;
if (!found) row[c].widget.iterateOver(function(_widget) {if (_widget === _sender) found = true;});
if (found)
{
// return a fake row object allowing to iterate over it's children
var row_obj = new et2_widget(this, {});
for(var c=0; c < row.length; ++c)
{
if (row[c].widget) row_obj._children.push(row[c].widget);
}
row_obj.isInTree = jQuery.proxy(this.isInTree, this);
// we must not free the children!
row_obj.destroy = function(){
delete row_obj._children;
};
return row_obj;
}
}
}
} }
}); });
et2_register_widget(et2_grid, ["grid"]); et2_register_widget(et2_grid, ["grid"]);

View File

@ -498,7 +498,7 @@ etemplate2.prototype.load = function(_name, _url, _data, _callback)
$this = $j(this); $this = $j(this);
return !$this.attr('tabindex') || $this.attr('tabIndex')>=0; return !$this.attr('tabindex') || $this.attr('tabIndex')>=0;
}).first(); }).first();
// mobile device, focus only if the field is empty (usually means new entry) // mobile device, focus only if the field is empty (usually means new entry)
// should focus always for non-mobile one // should focus always for non-mobile one
if (egwIsMobile() && $input.val() == "" || !egwIsMobile()) $input.focus(); if (egwIsMobile() && $input.val() == "" || !egwIsMobile()) $input.focus();
@ -616,23 +616,25 @@ etemplate2.prototype.autocomplete_fixer = function ()
* @param {(et2_button|string)} button button widget or string with id * @param {(et2_button|string)} button button widget or string with id
* @param {boolean} async true: do an asynchronious submit, default is synchronious * @param {boolean} async true: do an asynchronious submit, default is synchronious
* @param {boolean} no_validation - Do not do individual widget validation, just submit their current values * @param {boolean} no_validation - Do not do individual widget validation, just submit their current values
* @param {et2_widget|undefined} _container container to submit, default whole template
* @return {boolean} true if submit was send, false if eg. validation stoped submit * @return {boolean} true if submit was send, false if eg. validation stoped submit
*/ */
etemplate2.prototype.submit = function(button, async, no_validation) etemplate2.prototype.submit = function(button, async, no_validation, _container)
{ {
if(typeof no_validation == 'undefined') if(typeof no_validation == 'undefined')
{ {
no_validation = false; no_validation = false;
} }
var container = _container || this.widgetContainer;
// Get the form values // Get the form values
var values = this.getValues(this.widgetContainer); var values = this.getValues(container);
// Trigger the submit event // Trigger the submit event
var canSubmit = true; var canSubmit = true;
if(!no_validation) if(!no_validation)
{ {
this.widgetContainer.iterateOver(function(_widget) { container.iterateOver(function(_widget) {
if (_widget.submit(values) === false) if (_widget.submit(values) === false)
{ {
canSubmit = false; canSubmit = false;

View File

@ -13,15 +13,15 @@
<description value="Header" span="all"/> <description value="Header" span="all"/>
<description/> <description/>
</row> </row>
<row part="footer"> <row class="th" part="header">
<description value="Footer" span="all"/>
<description/>
</row>
<row class="th">
<grid-sortheader label="Col 1 Header" id="first" custom-sort="app.method"/> <grid-sortheader label="Col 1 Header" id="first" custom-sort="app.method"/>
<grid-sortheader label="Col 2 Header" id="second"/> <grid-sortheader label="Col 2 Header" id="second"/>
<description value="Col 3 Header"/> <description value="Col 3 Header"/>
</row> </row>
<row part="footer">
<description value="Footer" span="all"/>
<description/>
</row>
<row class="row"> <row class="row">
<description id="${row}[first]"/> <description id="${row}[first]"/>
<description id="${row}[second]"/> <description id="${row}[second]"/>