/* Authors: * Endi Sukma Dewata * Adam Young * Pavel Zuna * Petr Vobornik * * Copyright (C) 2011 Red Hat * see file 'COPYING' for use and warranty information * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ define([ 'dojo/_base/array', 'dojo/_base/lang', './metadata', './builder', './ipa', './jquery', './navigation', './phases', './reg', './text'], function(array, lang, metadata_provider, builder, IPA, $, navigation, phases, reg, text) { /** * Field module * @class field * @singleton */ var exp = {}; /** * Field * @class IPA.field */ IPA.field = function(spec) { spec = spec || {}; var that = IPA.object(); /** * Entity * @property {entity.entity} */ that.entity = IPA.get_entity(spec.entity); /** * Facet * @property {facet.facet} */ that.facet = spec.facet; /** * Container * @property {facet.facet|IPA.dialog} */ that.container = null; /** * Name * @property {string} */ that.name = spec.name; /** * Entity param name * * - defaults to `name` * - can be change if multiple fields touches the same param * @property {string} */ that.param = spec.param || spec.name; /** * Entity param which provides access control rights * * - defaults to `param` * - some params might be virtual and thus actual rights might be * defined by other param. * @property {string} */ that.acl_param = spec.acl_param || that.param; /** * Label * @property {string} */ that.label = text.get(spec.label); /** * Tooltip * @property {string} */ that.tooltip = text.get(spec.tooltip); /** * Measurement unit * @property {string} */ that.measurement_unit = spec.measurement_unit; /** * Formatter * * - transforms field value to widget value * - only for read-only fields * @property {IPA.formatter} */ that.formatter = builder.build('formatter', spec.formatter); /** * Widget * @property {IPA.input_widget} */ that.widget = null; /** * Widget name within `container` * @property {string} */ that.widget_name = spec.widget; /** * Override the required flag in metadata * @property {boolean} */ that.required = spec.required; /** * read_only is set when widget is created * @readonly * @property {boolean} */ that.read_only = spec.read_only; /** * Writable is set during load * @readonly * @property {boolean} */ that.writable = true; /** * Enabled * @readonly * @property {boolean} */ that.enabled = spec.enabled === undefined ? true : spec.enabled; /** * Flags * @property {Array.} */ that.flags = spec.flags || []; /** * Undo is displayable * * - when false, undo button is not displayed even when the field is dirty * @property {boolean} */ that.undo = spec.undo === undefined ? true : spec.undo; /** * Metadata * @property {Object} */ that.metadata = spec.metadata; /** * Validators * @property {Array.} */ that.validators = builder.build('validator', spec.validators) || []; /** * Field priority * @property {number} */ that.priority = spec.priority; /** * Loaded values * @property {Array.} */ that.values = []; /** * Field is dirty (value is modified) * @readonly * @property {boolean} */ that.dirty = false; /** * Current value is valid - passes validators * @property {boolean} */ that.valid = true; /** * Dirty has changed * @event * @property {IPA.observer} */ that.dirty_changed = IPA.observer(); var init = function() { if (typeof that.metadata === 'string') { that.metadata = metadata_provider.get(that.metadata); } if (!that.metadata && that.entity) { that.metadata = IPA.get_entity_param(that.entity.name, that.param); } if (that.metadata) { if (!that.label) { that.label = that.metadata.label || ''; } if (!that.tooltip) { that.tooltip = that.metadata.doc || ''; } } that.validators.push(IPA.metadata_validator()); }; /** * Evaluate if field has to have some value * @return {boolean} */ that.is_required = function() { if (that.read_only) return false; if (!that.writable) return false; if (that.required !== undefined) return that.required; return that.metadata && that.metadata.required; }; /** * Required setter * @param {boolean} required */ that.set_required = function(required) { that.required = required; that.update_required(); }; /** * Update required state in widget to match field's * @protected */ that.update_required = function() { if(that.widget && that.widget.set_required) { that.widget.set_required(that.is_required()); } }; /** * Check if value is set when it has to be. Show error if not. * @return {boolean} */ that.validate_required = function() { var values = that.save(); if (IPA.is_empty(values) && that.is_required() && that.enabled) { that.valid = false; var message = text.get('@i18n:widget.validation.required', "Required field"); that.show_error(message); return false; } return true; }; /** * Returns true and clears the error message if the field value passes * the validation pattern. If the field value does not pass validation, * displays the error message and returns false. * @return {boolean} */ that.validate = function() { that.hide_error(); that.valid = true; if (!that.enabled) return that.valid; var values = that.save(); if (IPA.is_empty(values)) { return that.valid; } var value = values[0]; for (var i=0; i -1) { that.writable = false; } } if (record.attributelevelrights) { var rights = record.attributelevelrights[that.acl_param]; var oc_rights= record.attributelevelrights['objectclass']; var write_oc = oc_rights && oc_rights.indexOf('w') > -1; // Some objects in LDAP may not have set proper object class and // therefore server doesn't send proper attribute rights. Flag // 'w_if_no_aci' should be used when we want to ensure that UI // shows edit interface in such cases. Usable only when user can // modify object classes. // For all others, lack of rights means no write. if ((!rights && !(that.flags.indexOf('w_if_no_aci') > -1 && write_oc)) || (rights && rights.indexOf('w') < 0)) { that.writable = false; } } }; /** * Reset field and widget to loaded values */ that.reset = function() { that.set_widget_flags(); that.update_required(); that.update(); that.validate(); that.set_dirty(false); }; /** * Update widget with loaded values. */ that.update = function() { if (!that.widget || !that.widget.update) return; var formatted_values; // The formatter is currently only used on read-only fields only // because it cannot parse formatted values back to internal values. if (that.formatter && that.read_only) { formatted_values = []; for (var i=0; that.values && i Number(metadata.maxvalue)) { message = text.get('@i18n:widget.validation.max_value'); message = message.replace('${value}', metadata.maxvalue); return that.false_result(message); } } if (metadata.pattern) { var regex = new RegExp(metadata.pattern); if (!value.match(regex)) { return that.false_result(metadata.pattern_errmsg); } } return that.true_result(); }; return that; }; /** * Checks if value is supported * * @class IPA.unsupported_validator * @extends IPA.validator */ IPA.unsupported_validator = function(spec) { spec.message = spec.message ||'@i18n:widgets.validation.unsupported'; var that = IPA.validator(spec); /** * Unsupported values * @property {Array.} */ that.unsupported = spec.unsupported || []; /** * @inheritDoc */ that.validate = function(value, context) { if (IPA.is_empty(value)) return that.true_result(); if (that.unsupported.indexOf(value) > -1) return that.false_result(); return that.true_result(); }; return that; }; /** * Check if value is the same as in other field. * * - designed for password confirmation * * @class IPA.same_password_validator * @extends IPA.validator */ IPA.same_password_validator = function(spec) { spec = spec || {}; var that = IPA.validator(spec); /** * Other field name * @property {string} */ that.other_field = spec.other_field; that.message = text.get(spec.message || '@i18n:password.password_must_match', "Passwords must match"); /** * @inheritDoc */ that.validate = function(value, context) { var other_field = context.container.fields.get_field(that.other_field); var other_value = other_field.save(); var this_value = context.save(); if (IPA.array_diff(this_value, other_value)) return that.false_result(); return that.true_result(); }; return that; }; /** * Used along with checkbox widget * * @class IPA.checkbox_field * @extends IPA.field */ IPA.checkbox_field = function(spec) { spec = spec || {}; var that = IPA.field(spec); /** * Check value by default * @property {boolean} */ that.checked = spec.checked || false; /** * Boolean formatter for parsing loaded values. * @property {IPA.boolean_formatter} */ that.boolean_formatter = IPA.boolean_formatter(); /** * @inheritDoc */ that.load = function(record) { that.record = record; that.values = that.get_value(record, that.param); var value = that.boolean_formatter.parse(that.values); if (value === '') value = that.widget.checked; //default value that.values = [value]; that.load_writable(record); that.reset(); }; /** * @inheritDoc */ that.widgets_created = function() { that.field_widgets_created(); that.widget.checked = that.checked; }; /** * A checkbox will always have a value, so it's never required. * * @return {boolean} false */ that.is_required = function() { return false; }; that.checkbox_load = that.load; return that; }; /** * Used along with checkboxes widget * * @class IPA.checkboxes_field * @extends IPA.field */ IPA.checkboxes_field = function(spec) { spec = spec || {}; var that = IPA.field(spec); return that; }; /** * Used along with radio widget * * @class IPA.radio_field * @extends IPA.field */ IPA.radio_field = function(spec) { spec = spec || {}; var that = IPA.field(spec); /** * A radio will always have a value, so it's never required * @return {boolean} false */ that.is_required = function() { return false; }; /** * @inheritDoc */ that.widgets_created = function() { that.field_widgets_created(); }; return that; }; /** * Used along with multivalued widget * * @class IPA.multivalued_field * @extends IPA.field */ IPA.multivalued_field = function(spec) { spec = spec || {}; var that = IPA.field(spec); /** * @inheritDoc */ that.load = function(record) { that.field_load(record); }; /** * @inheritDoc */ that.test_dirty = function() { var dirty = that.field_test_dirty(); dirty = dirty || that.widget.test_dirty(); //also checks order return dirty; }; /** * @inheritDoc */ that.validate = function() { var values = that.save(); return that.validate_core(values); }; /** * Validate each value separately. * @protected * @param {Array} values * @return {boolean} valid */ that.validate_core = function(values) { that.hide_error(); that.valid = true; if (IPA.is_empty(values)) { return that.valid; } for (var i=0; i} */ that.get_fields = function() { return that.fields.values; }; /** * Add field * @param {IPA.field} field */ that.add_field = function(field) { field.container = that.container; that.fields.put(field.name, field); }; /** * Call each field's `widgets_created` method. */ that.widgets_created = function() { var fields = that.fields.values; for (var i=0; i} spec * @param {facet.facet|IPA.dialog} container */ that.build_fields = function(specs, container) { return that.build_field(specs, container); }; return that; }; /** * Field pre_op build operation * @member field * @return spec */ exp.pre_op = function(spec, context) { if (context.facet) spec.facet = context.facet; if (context.entity) spec.entity = context.entity; if (context.undo !== undefined) spec.undo = context.undo; return spec; }; /** * Field post_op build operation * @member field * @return obj */ exp.post_op = function(obj, spec, context) { if (context.container) context.container.add_field(obj); return obj; }; /** * Field builder with registry * @member field */ exp.builder = builder.get('field'); exp.builder.factory = IPA.field; exp.builder.string_mode = 'property'; exp.builder.string_property = 'name'; reg.set('field', exp.builder.registry); exp.builder.pre_ops.push(exp.pre_op); exp.builder.post_ops.push(exp.post_op); /** * Validator builder with registry * @member field */ exp.validator_builder = builder.get('validator'); reg.set('validator', exp.validator_builder.registry); /** * Register fields and validators to global registry * @member field */ exp.register = function() { var f = reg.field; var v = reg.validator; f.register('checkbox', IPA.checkbox_field); f.register('checkboxes', IPA.checkboxes_field); f.register('combobox', IPA.field); f.register('enable', IPA.enable_field); f.register('entity_select', IPA.field); f.register('field', IPA.field); f.register('link', IPA.link_field); f.register('multivalued', IPA.multivalued_field); f.register('password', IPA.field); f.register('radio', IPA.radio_field); f.register('select', IPA.select_field); f.register('sshkeys', IPA.sshkeys_field); f.register('textarea', IPA.field); f.register('text', IPA.field); f.register('value_map', IPA.field); v.register('metadata', IPA.metadata_validator); v.register('unsupported', IPA.unsupported_validator); v.register('same_password', IPA.same_password_validator); }; phases.on('registration', exp.register); return exp; });