Prerequisites for custom fields

SVN:trunk[3913]
This commit is contained in:
Romain Quetiez
2016-02-19 08:49:14 +00:00
parent e0fad5e0e6
commit 21f0adb41b
13 changed files with 827 additions and 287 deletions

305
js/field_set.js Normal file
View File

@@ -0,0 +1,305 @@
//iTop Form handler
;
$(function()
{
// the widget definition, where 'itop' is the namespace,
// 'form_handler' the widget name
$.widget( 'itop.field_set',
{
// default options
options:
{
field_identifier_attr: 'data-field-id', // convention: fields are rendered into a div and are identified by this attribute
fields_list: null,
fields_impacts: {},
touched_fields: [],
is_valid: true,
form_path: '',
script_element: null,
style_element: null
},
buildData:
{
script_code: '',
style_code: ''
},
// the constructor
_create: function()
{
var me = this;
this.element
.addClass('field_set');
this.element
.bind('field_change', function(event, data){
console.log('field_set: field_change');
me._onFieldChange(event, data);
})
.bind('update_form', function(event, data){
console.log('field_set: update_form');
me._onUpdateForm(event, data);
})
.bind('get_current_values', function(event, data){
console.log('field_set: get_current_values');
return me._onGetCurrentValues(event, data);
})
.bind('validate', function(event, data){
if (data === undefined)
{
data = {};
}
console.log('field_set: validate');
return me._onValidate(event, data);
});
// Creating DOM elements if not using user's specifics
if(this.options.script_element === null)
{
this.options.script_element = $('<script type="text/javascript"></script>');
this.element.after(this.options.script_element);
}
if(this.options.style_element === null)
{
this.options.style_element = $('<style></style>');
this.element.before(this.options.style_element);
}
// Building the form
if(this.options.fields_list !== null)
{
this.buildForm();
}
},
// called when created, and later when changing options
_refresh: function()
{
},
// events bound via _bind are removed automatically
// revert other modifications here
_destroy: function()
{
this.element
.removeClass('field_set');
},
// _setOptions is called with a hash of all options that are changing
// always refresh when changing options
_setOptions: function()
{
this._superApply(arguments);
},
// _setOption is called for each individual option that is changing
_setOption: function( key, value )
{
this._super( key, value );
},
_getField: function (sFieldId)
{
return this.element.find('[' + this.options.field_identifier_attr + '="'+sFieldId+'"][data-form-path="'+this.options.form_path+'"]');
},
_onGetCurrentValues: function(event, data)
{
event.stopPropagation();
var result = {};
for(var i in this.options.fields_list)
{
var field = this.options.fields_list[i];
if(this._getField(field.id).hasClass('form_field'))
{
result[field.id] = this._getField(field.id).triggerHandler('get_current_value');
}
else
{
console.log('Field set : Cannot retrieve current value from field [' + this.options.field_identifier_attr + '="'+field.id+'"] as it seems to have no itop.form_field widget attached.');
}
}
return result;
},
_getRequestedFields: function(sourceFieldName)
{
var fieldsName = [];
if(this.options.fields_impacts[sourceFieldName] !== undefined)
{
for(var i in this.options.fields_impacts[sourceFieldName])
{
fieldsName.push(this.options.fields_impacts[sourceFieldName][i]);
}
}
return fieldsName;
},
_onFieldChange: function(event, data)
{
event.stopPropagation();
// Set field as touched so we know that we have to do checks on it later
if(this.options.touched_fields.indexOf(data.name) < 0)
{
this.options.touched_fields.push(data.name);
}
// Validate the field
var oRes = this._getField(data.name).triggerHandler('validate', {touched_fields_only: true});
if (!oRes.is_valid)
{
this.options.is_valid = false;
}
var requestedFields = this._getRequestedFields(data.name);
if(requestedFields.length > 0)
{
this.element.trigger('update_fields', {form_path: this.options.form_path, requested_fields: requestedFields});
}
},
_onUpdateForm: function(event, data)
{
event.stopPropagation();
this.buildData.script_code = '';
this.buildData.style_code = '';
for (var i in data.updated_fields)
{
var updated_field = data.updated_fields[i];
this.options.fields_list[updated_field.id] = updated_field;
this._prepareField(updated_field.id);
}
// Adding code to the dom
this.options.script_element.append('\n\n// Appended by update at ' + Date() + '\n' + this.buildData.script_code);
this.options.style_element.append('\n\n// Appended by update at ' + Date() + '\n' + this.buildData.style_code);
// Evaluating script code as adding it to dom did not executed it (only script from update !)
eval(this.buildData.script_code);
},
_onValidate: function(event, data)
{
event.stopPropagation();
this.options.is_valid = true;
var aFieldsToValidate = [];
if ((data.touched_fields_only !== undefined) && (data.touched_fields_only === true))
{
aFieldsToValidate = this.options.touched_fields;
}
else
{
// Requires IE9+ Object.keys(this.options.fields_list);
for (var sFieldId in this.options.fields_list)
{
aFieldsToValidate.push(sFieldId);
}
}
for(var i in aFieldsToValidate)
{
var oRes = this._getField(aFieldsToValidate[i]).triggerHandler('validate', data);
if (!oRes.is_valid)
{
this.options.is_valid = false;
}
}
return this.options.is_valid;
},
showOptions: function() // Debug helper
{
console.log(this.options);
return this.options;
},
_loadCssFile: function(url)
{
if (!$('link[href="'+url+'"]').length)
$('<link href="'+url+'" rel="stylesheet">').appendTo('head');
},
_loadJsFile: function(url)
{
if (!$('script[src="'+url+'"]').length)
$.getScript(url);
},
// Place a field for which no container exists
_addField: function(field_id)
{
$('<div ' + this.options.field_identifier_attr + '="'+field_id+'" data-form-path="' + this.options.form_path + '"></div>').appendTo(this.element);
},
_prepareField: function(field_id)
{
var field = this.options.fields_list[field_id];
if(this._getField(field.id).length === 1)
{
// We replace the node instead of just replacing the inner html so the previous widget is automatically destroyed.
this._getField(field.id).replaceWith( $('<div ' + this.options.field_identifier_attr + '="'+field.id+'" data-form-path="' + this.options.form_path + '"></div>') );
}
else
{
this._addField(field.id);
}
var field_container = this._getField(field.id);
// HTML
if( (field.html !== undefined) && (field.html !== '') )
{
field_container.html(field.html);
}
// JS files
if( (field.js_files !== undefined) && (field.js_files.length > 0) )
{
for(var j in field.js_files)
{
this._loadJsFile(field.js_files[i]);
}
}
// CSS files
if( (field.css_files !== undefined) && (field.css_files.length > 0) )
{
for(var j in field.css_files)
{
this._loadCssFile(field.css_files[i]);
}
}
// JS inline
if( (field.js_inline !== undefined) && (field.js_inline !== '') )
{
this.buildData.script_code += '; '+ field.js_inline;
}
// CSS inline
if( (field.css_inline !== undefined) && (field.css_inline !== '') )
{
this.buildData.style_code += ' '+ field.css_inline;
}
},
buildForm: function()
{
this.buildData.script_code = '';
this.buildData.style_code = '';
for(var i in this.options.fields_list)
{
var field = this.options.fields_list[i];
if(field.id === undefined)
{
console.log('Field set : An field must have at least an id property.');
return false;
}
this._prepareField(field.id);
}
this.options.script_element.text('$(document).ready(function(){ '+this.buildData.script_code+' });');
this.options.style_element.text(this.buildData.style_code);
eval(this.options.script_element.text());
}
});
});

View File

@@ -26,19 +26,21 @@ $(function()
this.element
.bind('set_validators', function(event, data){
event.stopPropagation();
me.options.validators = data;
});
this.element
.bind('validate get_current_value', function(event, data){
event.stopPropagation();
var callback = me.options[event.type+'_callback'];
if(typeof callback === 'string')
{
return me[callback]();
return me[callback](event, data);
}
else if(typeof callback === 'function')
{
return callback(me);
return callback(me, event, data);
}
else
{
@@ -72,29 +74,29 @@ $(function()
},
getCurrentValue: function()
{
var value = {};
var value = null;
this.element.find(':input').each(function(index, elem){
if($(elem).is(':hidden') || $(elem).is(':text') || $(elem).is('textarea'))
{
value[$(elem).attr('name')] = $(elem).val();
value = $(elem).val();
}
else if($(elem).is('select'))
{
value[$(elem).attr('name')] = [];
value = [];
$(elem).find('option:selected').each(function(){
value[$(elem).attr('name')].push($(this).val());
value.push($(this).val());
});
}
else if($(elem).is(':checkbox') || $(elem).is(':radio'))
{
if(value[$(elem).attr('name')] === undefined)
if(value === null)
{
value[$(elem).attr('name')] = [];
value = [];
}
if($(elem).is(':checked'))
{
value[$(elem).attr('name')].push($(elem).val());
value.push($(elem).val());
}
}
else
@@ -105,10 +107,10 @@ $(function()
return value;
},
validate: function()
validate: function(event, data)
{
var oResult = { is_valid: true, error_messages: [] };
// Doing data validation
if(this.options.validators !== null)
{
@@ -139,7 +141,7 @@ $(function()
oResult.is_valid = false;
oResult.error_messages.push(oValidator.message);
}
// ... In case of none empty array, we have to check is the value is not null
// ... In case of non empty array, we have to check if the value is not null
else if($.isArray(value))
{
for(var i in value)
@@ -190,7 +192,7 @@ $(function()
}
}
this.options.on_validation_callback();
this.options.on_validation_callback(this, oResult);
return oResult;
},

View File

@@ -11,25 +11,13 @@ $(function()
{
formmanager_class: null,
formmanager_data: null,
field_identifier_attr: 'data-field-id', // convention: fields are rendered into a div and are identified by this attribute
fields_list: null,
fields_impacts: {},
touched_fields: [],
submit_btn_selector: null,
cancel_btn_selector: null,
endpoint: null,
is_modal: false,
is_valid: true,
script_element: null,
style_element: null
field_set: null
},
buildData:
{
script_code: '',
style_code: ''
},
// the constructor
_create: function()
{
@@ -37,30 +25,11 @@ $(function()
this.element
.addClass('form_handler');
this.element
.bind('field_change', function(event, data){
me._onFieldChange(event, data);
this.element.bind('update_fields', function(event, data){
this._onUpdateFields(event, data);
});
// Creating DOM elements if not using user's specifics
if(this.options.script_element === null)
{
this.options.script_element = $('<script type="text/javascript"></script>');
this.element.after(this.options.script_element);
}
if(this.options.style_element === null)
{
this.options.style_element = $('<style></style>');
this.element.before(this.options.style_element);
}
// Building the form
if(this.options.fields_list !== null)
{
this.buildForm();
}
// Binding buttons
if(this.options.submit_btn_selector !== null)
{
@@ -97,41 +66,13 @@ $(function()
},
getCurrentValues: function()
{
var result = {};
for(var i in this.options.fields_list)
{
var field = this.options.fields_list[i];
if(this.element.find('[' + this.options.field_identifier_attr + '="'+field.id+'"]').hasClass('form_field'))
{
$.extend(true, result, this.element.find('[' + this.options.field_identifier_attr + '="'+field.id+'"]').triggerHandler('get_current_value'));
}
else
{
console.log('Form handler : Cannot retrieve current value from field [' + this.options.field_identifier_attr + '="'+field.id+'"] as it seems to have no itop.form_field widget attached.');
}
}
return result;
return this.options.field_set.triggerHandler('get_current_values');
},
_getRequestedFields: function(sourceFieldName)
{
var fieldsName = [];
if(this.options.fields_impacts[sourceFieldName] !== undefined)
{
for(var i in this.options.fields_impacts[sourceFieldName])
{
fieldsName.push(this.options.fields_impacts[sourceFieldName][i]);
}
}
return fieldsName;
},
_onFieldChange: function(event, data)
_onUpdateFields: function(event, data)
{
var me = this;
var sFormPath = data.form_path;
// Data checks
if(this.options.endpoint === null)
{
@@ -149,37 +90,23 @@ $(function()
return false;
}
// Set field as touched so we know that we have to do checks on it later
if(this.options.touched_fields.indexOf(data.name) < 0)
{
this.options.touched_fields.push(data.name);
}
var requestedFields = this._getRequestedFields(data.name);
if(requestedFields.length > 0)
{
this._disableFormBeforeLoading();
$.post(
this.options.endpoint,
{
operation: 'update',
formmanager_class: this.options.formmanager_class,
formmanager_data: JSON.stringify(this.options.formmanager_data),
current_values: this.getCurrentValues(),
requested_fields: requestedFields
},
function(data){
me._onUpdateSuccess(data);
}
)
.fail(function(data){ me._onUpdateFailure(data); })
.always(function(data){ me._onUpdateAlways(data); });
}
else
{
// Check self NOW as they are no ajax call
this.element.find('[' + this.options.field_identifier_attr + '="' + data.name + '"]').trigger('validate');
}
this._disableFormBeforeLoading();
$.post(
this.options.endpoint,
{
operation: 'update',
formmanager_class: this.options.formmanager_class,
formmanager_data: JSON.stringify(this.options.formmanager_data),
current_values: this.getCurrentValues(),
requested_fields: data.requested_fields,
form_path: sFormPath
},
function(data){
me._onUpdateSuccess(data, sFormPath);
}
)
.fail(function(data){ me._onUpdateFailure(data, sFormPath); })
.always(function(data){ me._onUpdateAlways(data, sFormPath); });
},
// Intended for overloading in derived classes
_onSubmitClick: function(event)
@@ -190,40 +117,22 @@ $(function()
{
},
// Intended for overloading in derived classes
_onUpdateSuccess: function(data)
_onUpdateSuccess: function(data, sFormPath)
{
if(data.form.updated_fields !== undefined)
{
this.buildData.script_code = '';
this.buildData.style_code = '';
for (var i in data.form.updated_fields)
{
var updated_field = data.form.updated_fields[i];
this.options.fields_list[updated_field.id] = updated_field;
this._prepareField(updated_field.id);
}
// Adding code to the dom
this.options.script_element.append('\n\n// Appended by update at ' + Date() + '\n' + this.buildData.script_code);
this.options.style_element.append('\n\n// Appended by update at ' + Date() + '\n' + this.buildData.style_code);
// Evaluating script code as adding it to dom did not executed it (only script from update !)
eval(this.buildData.script_code);
this.element.find('[data-form-path="'+sFormPath+'"]').trigger('update_form', {updated_fields: data.form.updated_fields});
}
},
// Intended for overloading in derived classes
_onUpdateFailure: function(data)
_onUpdateFailure: function(data, sFormPath)
{
},
// Intended for overloading in derived classes
_onUpdateAlways: function(data)
_onUpdateAlways: function(data, sFormPath)
{
// Check all touched AFTER ajax is complete, otherwise the renderer will redraw the field in the mean time.
for(var i in this.options.touched_fields)
{
this.element.find('[' + this.options.field_identifier_attr + '="' + this.options.touched_fields[i] + '"]').trigger('validate');
}
this.element.find('[data-form-path="'+sFormPath+'"]').trigger('validate');
this._enableFormAfterLoading();
},
// Intended for overloading in derived classes
@@ -234,91 +143,6 @@ $(function()
_enableFormAfterLoading: function()
{
},
_loadCssFile: function(url)
{
if (!$('link[href="'+url+'"]').length)
$('<link href="'+url+'" rel="stylesheet">').appendTo('head');
},
_loadJsFile: function(url)
{
if (!$('script[src="'+url+'"]').length)
$.getScript(url);
},
// Place a field for which no container exists
_addField: function(field_id)
{
$('<div ' + this.options.field_identifier_attr + '="'+field_id+'"></div>').appendTo(this.element);
},
_prepareField: function(field_id)
{
var field = this.options.fields_list[field_id];
if(this.element.find('[' + this.options.field_identifier_attr + '="'+field.id+'"]').length === 1)
{
// We replace the node instead of just replacing the inner html so the previous widget is automatically destroyed.
this.element.find('[' + this.options.field_identifier_attr + '="'+field.id+'"]').replaceWith( $('<div ' + this.options.field_identifier_attr + '="'+field.id+'"></div>') );
}
else
{
this._addField(field.id);
}
var field_container = this.element.find('[' + this.options.field_identifier_attr + '="'+field.id+'"]');
// HTML
if( (field.html !== undefined) && (field.html !== '') )
{
field_container.html(field.html);
}
// JS files
if( (field.js_files !== undefined) && (field.js_files.length > 0) )
{
for(var j in field.js_files)
{
this._loadJsFile(field.js_files[i]);
}
}
// CSS files
if( (field.css_files !== undefined) && (field.css_files.length > 0) )
{
for(var j in field.css_files)
{
this._loadCssFile(field.css_files[i]);
}
}
// JS inline
if( (field.js_inline !== undefined) && (field.js_inline !== '') )
{
this.buildData.script_code += '; '+ field.js_inline;
}
// CSS inline
if( (field.css_inline !== undefined) && (field.css_inline !== '') )
{
this.buildData.style_code += ' '+ field.css_inline;
}
},
buildForm: function()
{
this.buildData.script_code = '';
this.buildData.style_code = '';
for(var i in this.options.fields_list)
{
var field = this.options.fields_list[i];
if(field.id === undefined)
{
console.log('Form handler : An field must have at least an id property.');
return false;
}
this._prepareField(field.id);
}
this.options.script_element.text('$(document).ready(function(){ '+this.buildData.script_code+' });');
this.options.style_element.text(this.buildData.style_code);
eval(this.options.script_element.text());
},
showOptions: function() // Debug helper
{
console.log(this.options);

46
js/subform_field.js Normal file
View File

@@ -0,0 +1,46 @@
//iTop Form field
;
$(function()
{
// the widget definition, where 'itop' is the namespace,
// 'subform_field' the widget name
$.widget( 'itop.subform_field', $.itop.form_field,
{
// default options
options:
{
field_set: null
},
// the constructor
_create: function()
{
var me = this;
this.element
.addClass('subform_field');
this._super();
},
// events bound via _bind are removed automatically
// revert other modifications here
_destroy: function()
{
this.element
.removeClass('subform_field');
this._super();
},
getCurrentValue: function()
{
return this.options.field_set.triggerHandler('get_current_values');
},
validate: function(event, data)
{
return {
is_valid: this.options.field_set.triggerHandler('validate', data),
error_messages: []
}
},
});
});

View File

@@ -23,6 +23,7 @@
require_once APPROOT . 'sources/form/form.class.inc.php';
require_once APPROOT . 'sources/form/formmanager.class.inc.php';
require_once APPROOT . 'sources/form/field/field.class.inc.php';
require_once APPROOT . 'sources/form/field/subformfield.class.inc.php';
require_once APPROOT . 'sources/form/field/textfield.class.inc.php';
require_once APPROOT . 'sources/form/field/hiddenfield.class.inc.php';
require_once APPROOT . 'sources/form/field/stringfield.class.inc.php';

View File

@@ -36,6 +36,8 @@ abstract class Field
const DEFAULT_VALID = true;
protected $sId;
protected $sGlobalId;
protected $sFormPath;
protected $sLabel;
protected $bReadOnly;
protected $bMandatory;
@@ -52,6 +54,7 @@ abstract class Field
public function __construct($sId, Closure $onFinalizeCallback = null)
{
$this->sId = $sId;
$this->sGlobalId = 'field_'.$sId.uniqid();
$this->sLabel = static::DEFAULT_LABEL;
$this->bReadOnly = static::DEFAULT_READ_ONLY;
$this->bMandatory = static::DEFAULT_MANDATORY;
@@ -61,11 +64,33 @@ abstract class Field
$this->onFinalizeCallback = $onFinalizeCallback;
}
/**
* Get the field id within its container form
* @return string
*/
public function GetId()
{
return $this->sId;
}
/**
* Get a unique field id within the top level form
* @return string
*/
public function GetGlobalId()
{
return $this->sGlobalId;
}
/**
* Get the id of the container form
* @return string
*/
public function GetFormPath()
{
return $this->sFormPath;
}
public function GetLabel()
{
return $this->sLabel;
@@ -184,6 +209,14 @@ abstract class Field
return $this;
}
/**
* Called by the form when adding the field
*/
public function SetFormPath($sFormPath)
{
$this->sFormPath = $sFormPath;
}
public function AddValidator(Validator $oValidator)
{
$this->aValidators[] = $oValidator;
@@ -206,7 +239,7 @@ abstract class Field
* Note : Function is protected as aErrorMessages should not be add from outside
*
* @param string $sErrorMessage
* @return \Combodo\iTop\Field\Field
* @return \Combodo\iTop\Form\Field\Field
*/
protected function AddErrorMessage($sErrorMessage)
{
@@ -217,7 +250,7 @@ abstract class Field
/**
* Note : Function is protected as aErrorMessages should not be set from outside
*
* @return \Combodo\iTop\Field\Field
* @return \Combodo\iTop\Form\Field\Field
*/
protected function EmptyErrorMessages()
{
@@ -236,7 +269,7 @@ abstract class Field
{
// Note : We MUST have a temp variable to call the Closure. otherwise it won't work when the Closure is a class member
$callback = $this->onFinalizeCallback;
$callback();
$callback($this);
}
}

View File

@@ -0,0 +1,76 @@
<?php
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Form\Field;
use \Closure;
use \Combodo\iTop\Form\Field\Field;
use \Combodo\iTop\Form\Form;
/**
* Description of StringField
*
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
*/
class SubFormField extends Field
{
protected $oForm;
public function __construct($sId, $sParentFormId, Closure $onFinalizeCallback = null)
{
$this->oForm = new \Combodo\iTop\Form\Form($sParentFormId.'-subform_'.$sId);
parent::__construct($sId, $onFinalizeCallback);
}
public function GetForm()
{
return $this->oForm;
}
/**
* Checks the validators to see if the field's current value is valid.
* Then sets $bValid and $aErrorMessages.
*
* @return boolean
*/
public function Validate()
{
$this->oForm->Validate();
}
public function GetValid()
{
return $this->oForm->GetValid();
}
public function GetErrorMessages()
{
return $this->oForm->GetErrorMessages();
}
public function GetCurrentValue()
{
return $this->oForm->GetCurrentValues();
}
public function SetCurrentValue($value)
{
return $this->oForm->SetCurrentValues($value);
}
}

View File

@@ -60,6 +60,25 @@ class Form
return $this->aDependencies;
}
public function GetCurrentValues()
{
$aValues = array();
foreach ($this->aFields as $sId => $oField)
{
$aValues[$sId] = $oField->GetCurrentValue();
}
return $aValues;
}
public function SetCurrentValues($aValues)
{
foreach ($aValues as $sId => $value)
{
$oField = $this->GetField($sId);
$oField->SetCurrentValue($value);
}
}
/**
* Returns the current validation state of the form (true|false).
* It DOESN'T make the validation, see Validate() instead.
@@ -154,6 +173,7 @@ class Form
public function AddField(Field $oField, $aDependsOnIds = array())
{
$oField->SetFormPath($this->sId);
$this->aFields[$oField->GetId()] = $oField;
return $this;
}

View File

@@ -52,11 +52,17 @@ abstract class FormManager
// Overload in child class when needed
}
/**
* @return Form
*/
public function GetForm()
{
return $this->oForm;
}
/**
* @return FormRenderer
*/
public function GetRenderer()
{
return $this->oRenderer;

View File

@@ -0,0 +1,39 @@
<?php
// Copyright (C) 2016 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Renderer\Console;
use Combodo\iTop\Form\Form;
use Combodo\iTop\Renderer\FormRenderer;
use Combodo\iTop\Renderer\RenderingOutput;
use \Dict;
require_once('fieldrenderer/consolesimplefieldrenderer.class.inc.php');
require_once('fieldrenderer/consolesubformfieldrenderer.class.inc.php');
class ConsoleFormRenderer extends FormRenderer
{
const DEFAULT_RENDERER_NAMESPACE = 'Combodo\\iTop\\Renderer\\Console\\FieldRenderer\\';
public function __construct(Form $oForm)
{
parent::__construct($oForm);
$this->AddSupportedField('StringField', 'ConsoleSimpleFieldRenderer');
$this->AddSupportedField('SubFormField', 'ConsoleSubFormFieldRenderer');
}
}

View File

@@ -0,0 +1,139 @@
<?php
// Copyright (C) 2016 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Renderer\Console\FieldRenderer;
use \Dict;
use Combodo\iTop\Renderer\FieldRenderer;
use Combodo\iTop\Renderer\RenderingOutput;
class ConsoleSimpleFieldRenderer extends FieldRenderer
{
public function Render()
{
$oOutput = new RenderingOutput();
$sFieldClass = get_class($this->oField);
// TODO : Shouldn't we have a field type so we don't have to maintain FQN classname ?
// Rendering field in edition mode
if (!$this->oField->GetReadOnly())
{
switch ($sFieldClass)
{
case 'Combodo\\iTop\\Form\\Field\\StringField':
if ($this->oField->GetLabel() !== '')
{
$oOutput->AddHtml('<label for="' . $this->oField->GetGlobalId() . '" class="control-label">' . $this->oField->GetLabel() . '</label>');
}
$oOutput->AddHtml('<input type="text" id="'.$this->oField->GetGlobalId().'" value="' . $this->oField->GetCurrentValue() . '" size="30" />');
$oOutput->AddHtml('<span class="form_validation"></span>');
break;
}
}
// ... and in read-only mode
else
{
switch ($sFieldClass)
{
case 'Combodo\\iTop\\Form\\Field\\StringField':
if ($this->oField->GetLabel() !== '')
{
$oOutput->AddHtml('<label for="' . $this->oField->GetGlobalId() . '" class="control-label">' . $this->oField->GetLabel() . '</label>');
}
$oOutput->AddHtml('<div class="form-control-static">' . $this->oField->GetCurrentValue() . '</div>');
break;
}
}
switch ($sFieldClass)
{
case 'Combodo\\iTop\\Form\\Field\\StringField':
$oOutput->AddJs(
<<<EOF
$("#{$this->oField->GetGlobalId()}").off("change").on("change keyup", function(){
var me = this;
$(this).closest(".field_set").trigger("field_change", {
id: $(me).attr("id"),
name: $(me).closest(".form_field").attr("data-field-id"),
value: $(me).val()
});
});
EOF
);
break;
}
// JS Form field widget construct
$aValidators = array();
foreach ($this->oField->GetValidators() as $oValidator)
{
$aValidators[$oValidator::GetName()] = array(
'reg_exp' => $oValidator->GetRegExp(),
'message' => Dict::S($oValidator->GetErrorMessage())
);
}
$sValidators = json_encode($aValidators);
$sFormFieldOptions =
<<<EOF
{
validators: $sValidators,
on_validation_callback: function(me, oResult) {
var oValidationElement = $(me.element).find('span.form_validation');
if (oResult.is_valid)
{
oValidationElement.html('');
}
else
{
//TODO: escape html entities
var sExplain = oResult.error_messages.join(', ');
oValidationElement.html('<img src="../images/validation_error.png" style="vertical-align:middle" data-tooltip="'+sExplain+'"/>');
oValidationElement.tooltip({
items: 'span',
tooltipClass: 'form_field_error',
content: function() {
return $(this).find('img').attr('data-tooltip'); // As opposed to the default 'content' handler, do not escape the contents of 'title'
}
});
}
}
}
EOF
;
switch ($sFieldClass)
{
case 'Combodo\\iTop\\Form\\Field\\StringField':
case 'Combodo\\iTop\\Form\\Field\\TextAreaField':
case 'Combodo\\iTop\\Form\\Field\\SelectField':
case 'Combodo\\iTop\\Form\\Field\\HiddenField':
case 'Combodo\\iTop\\Form\\Field\\RadioField':
case 'Combodo\\iTop\\Form\\Field\\CheckboxField':
$oOutput->AddJs(
<<<EOF
$("[data-field-id='{$this->oField->GetId()}'][data-form-path='{$this->oField->GetFormPath()}']").form_field($sFormFieldOptions);
EOF
);
break;
}
return $oOutput;
}
}

View File

@@ -0,0 +1,52 @@
<?php
// Copyright (C) 2016 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
namespace Combodo\iTop\Renderer\Console\FieldRenderer;
use \Dict;
use Combodo\iTop\Renderer\Console\ConsoleFormRenderer;
use Combodo\iTop\Renderer\FieldRenderer;
use Combodo\iTop\Renderer\RenderingOutput;
class ConsoleSubFormFieldRenderer extends FieldRenderer
{
public function Render()
{
$oOutput = new RenderingOutput();
$oOutput->AddHtml('<div id="fieldset_'.$this->oField->GetGlobalId().'">');
$oOutput->AddHtml('</div>');
$oRenderer = new ConsoleFormRenderer($this->oField->GetForm());
$aRenderRes = $oRenderer->Render();
$aFieldSetOptions = array(
'fields_list' => $aRenderRes,
'fields_impacts' => $this->oField->GetForm()->GetFieldsImpacts(),
'form_path' => $this->oField->GetForm()->GetId()
);
$sFieldSetOptions = json_encode($aFieldSetOptions);
$oOutput->AddJs(
<<<EOF
$("#fieldset_{$this->oField->GetGlobalId()}").field_set($sFieldSetOptions);
$("[data-field-id='{$this->oField->GetId()}'][data-form-path='{$this->oField->GetFormPath()}']").subform_field({field_set: $("#fieldset_{$this->oField->GetGlobalId()}")});
EOF
);
return $oOutput;
}
}

View File

@@ -19,6 +19,7 @@
namespace Combodo\iTop\Renderer;
use \Exception;
use \Dict;
use \Combodo\iTop\Form\Form;
@@ -95,8 +96,7 @@ abstract class FormRenderer
}
else
{
// TODO : We might want to throw an exception.
return null;
throw new Exception('Field type not supported by the renderer: '.get_class($oField));
}
}
@@ -104,7 +104,7 @@ abstract class FormRenderer
* Returns the field identified by the id $sId in $this->oForm.
*
* @param string $sId
* @return Combodo\iTop\Renderer\FieldRenderer
* @return \Combodo\iTop\Renderer\FieldRenderer
*/
public function GetFieldRendererClassFromId($sId)
{
@@ -164,7 +164,7 @@ abstract class FormRenderer
* If $sMode = 'exploded', output is an has array with id / html / js_inline / js_files / css_inline / css_files / validators
* Else if $sMode = 'joined', output is a string with everything in it
*
* @param Combodo\iTop\Form\Field\Field $oField
* @param \Combodo\iTop\Form\Field\Field $oField
* @param string $sMode 'exploded'|'joined'
* @return mixed
*/
@@ -180,81 +180,78 @@ abstract class FormRenderer
);
$sFieldRendererClass = $this->GetFieldRendererClass($oField);
// TODO : We might want to throw an exception instead when there is no renderer for that field
if ($sFieldRendererClass !== null)
$oFieldRenderer = new $sFieldRendererClass($oField);
$oFieldRenderer->SetEndpoint($this->GetEndpoint());
$oRenderingOutput = $oFieldRenderer->Render();
// HTML
if ($oRenderingOutput->GetHtml() !== '')
{
$oFieldRenderer = new $sFieldRendererClass($oField);
$oFieldRenderer->SetEndpoint($this->GetEndpoint());
$oRenderingOutput = $oFieldRenderer->Render();
// HTML
if ($oRenderingOutput->GetHtml() !== '')
if ($sMode === static::ENUM_RENDER_MODE_EXPLODED)
{
if ($sMode === static::ENUM_RENDER_MODE_EXPLODED)
$output['html'] = $oRenderingOutput->GetHtml();
}
else
{
$output['html'] .= $oRenderingOutput->GetHtml();
}
}
// JS files
foreach ($oRenderingOutput->GetJsFiles() as $sJsFile)
{
if ($sMode === static::ENUM_RENDER_MODE_EXPLODED)
{
if (!in_array($sJsFile, $output['js_files']))
{
$output['html'] = $oRenderingOutput->GetHtml();
}
else
{
$output['html'] .= $oRenderingOutput->GetHtml();
$output['js_files'][] = $sJsFile;
}
}
else
{
$output['html'] .= '<script src="' . $sJsFile . '" type="text/javascript"></script>';
}
}
// JS inline
if ($oRenderingOutput->GetJs() !== '')
{
if ($sMode === static::ENUM_RENDER_MODE_EXPLODED)
{
$output['js_inline'] .= ' ' . $oRenderingOutput->GetJs();
}
else
{
$output['html'] .= '<script type="text/javascript">' . $oRenderingOutput->GetJs() . '</script>';
}
}
// JS files
foreach ($oRenderingOutput->GetJsFiles() as $sJsFile)
// CSS files
foreach ($oRenderingOutput->GetCssFiles() as $sCssFile)
{
if ($sMode === static::ENUM_RENDER_MODE_EXPLODED)
{
if ($sMode === static::ENUM_RENDER_MODE_EXPLODED)
if (!in_array($sCssFile, $output['css_files']))
{
if (!in_array($sJsFile, $output['js_files']))
{
$output['js_files'][] = $sJsFile;
}
}
else
{
$output['html'] .= '<script src="' . $sJsFile . '" type="text/javascript"></script>';
$output['css_files'][] = $sCssFile;
}
}
// JS inline
if ($oRenderingOutput->GetJs() !== '')
else
{
if ($sMode === static::ENUM_RENDER_MODE_EXPLODED)
{
$output['js_inline'] .= ' ' . $oRenderingOutput->GetJs();
}
else
{
$output['html'] .= '<script type="text/javascript">' . $oRenderingOutput->GetJs() . '</script>';
}
$output['html'] .= '<link href="' . $sCssFile . '" rel="stylesheet" />';
}
// CSS files
foreach ($oRenderingOutput->GetCssFiles() as $sCssFile)
}
// CSS inline
if ($oRenderingOutput->GetCss() !== '')
{
if ($sMode === static::ENUM_RENDER_MODE_EXPLODED)
{
if ($sMode === static::ENUM_RENDER_MODE_EXPLODED)
{
if (!in_array($sCssFile, $output['css_files']))
{
$output['css_files'][] = $sCssFile;
}
}
else
{
$output['html'] .= '<link href="' . $sCssFile . '" rel="stylesheet" />';
}
$output['css_inline'] .= ' ' . $oRenderingOutput->GetCss();
}
// CSS inline
if ($oRenderingOutput->GetCss() !== '')
else
{
if ($sMode === static::ENUM_RENDER_MODE_EXPLODED)
{
$output['css_inline'] .= ' ' . $oRenderingOutput->GetCss();
}
else
{
$output['html'] .= '<style>' . $oRenderingOutput->GetCss() . '</style>';
}
$output['html'] .= '<style>' . $oRenderingOutput->GetCss() . '</style>';
}
}