// setup namespace Zymonic.Field = {}; // setup cache and lookup // TODO: replace with Zymonic.add_to_lookup and Zymonic.lookup_object Zymonic.FieldLookup = {}; function getZymonicField(ident) { return Zymonic.FieldLookup[ident]; } // setup caches on record, so can find fields in filter results // TODO: replace with Zymonic.add_to_lookup and Zymonic.lookup_object Zymonic.FieldsInRecord = {}; function getZymonicRecordField(record_id, zname) { if (Zymonic.FieldsInRecord[record_id]) { return Zymonic.FieldsInRecord[record_id][zname]; } return null; } function getZymonicFieldsInRecord(record_id) { if (Zymonic.FieldsInRecord[record_id]) { var fields = []; for (var zname in Zymonic.FieldsInRecord[record_id]) { fields.push(Zymonic.FieldsInRecord[record_id][zname]) } return $(fields); } return $([]); } // There is a potential issue here is that if a block refreshes and some // fields disappear then they will still be in the FieldLookupByBlock // cache - TODO consider clearing down on block load. Zymonic.FieldLookupByWrapperIdent = {}; function getZymonicFieldsByWrapperIdent(wIdent) { var fArray = new Array; if( Object.prototype.toString.call( Zymonic.FieldLookupByWrapperIdent[wIdent] ) === '[object Object]' ) { for(var i in Zymonic.FieldLookupByWrapperIdent[wIdent]) { if (Zymonic.FieldLookupByWrapperIdent[wIdent][i].onPage()) { fArray.push(Zymonic.FieldLookupByWrapperIdent[wIdent][i]); } else { delete Zymonic.FieldLookupByWrapperIdent[wIdent][i]; } } } return fArray; } // base field // don't do anything here, makes it easy to establish inheritance Zymonic.Field.Field = function() {}; Zymonic.Field.Field.prototype.errorClass = function() { return ""; }; // add constants Zymonic.Field.Field.prototype.FIELD_CHANGED_CLASS = "ZymonicFieldChanged"; Zymonic.Field.Field.prototype.FIELD_CHANGED_SHOW_CLASS = "ZymonicFieldChangedShow"; Zymonic.Field.Field.prototype.FIELD_FORCE_CHANGED = "ZymonicFieldForceChanged"; // init function Zymonic.Field.Field.prototype.init = function(args) { for (var key in args) { this[key] = args[key]; } this.validation_function = "validate_"+this.ident; // clear validation errors this.clearErrors(); // add to the lookups if (this.ident != "") { Zymonic.FieldLookup[this.ident] = this; this.addToWrapperLookup(); } if (this.js_ident && this.js_ident != "") { Zymonic.FieldLookup[this.js_ident] = this; } if (this.record) { this.addToRecordLookup(this.record.ident); } // Create a generic DefaultHandler event var field = this; if(this.ident != '') { this.input_elements().each(function(index) { field.addDefaultHandler.call(field, this); }); } // placeholder for = in reload extra_params this.equals_placeholder = "ZZEQUALS"; // handlers this.handlers = { "change": [], "change_seen": {}, }; // field fills this.field_fill_calculations = []; // setup handler for adding on blur if (this.add_on_blur && this.parent_form_ident && this.record && this.inPlaceholder()) { var parent_form = this.getParentForm(); if (parent_form && parent_form.canAddRecords()) { // handler to focus this field on any new records which is added var field_zname = this.zname; parent_form.onRecordsChange( function(event, form, records_changed) { // find new placeholder record, find this field on it and focus it if (records_changed && records_changed.new_placeholder) { var new_placeholder_field = records_changed.new_placeholder.find("[zname='"+field_zname+"']"); if (new_placeholder_field) { new_placeholder_field.focus(); } else { Zymonic.log("Unable to find field "+field_zname+" in record "+records_changed.new_placeholder.attr('id')); } } }, this.ident+"_add_on_blur" ); // add blur handler to input elements to trigger the add var field = this; var record_ident = this.record.ident; Zymonic.Utils.attach_event( this.input_elements().last(), "blur", this.ident, function(event) { // don't do anything if its already being added or if there is no value if (field.adding_on_blur ||!field.getValue()) { return; } field.adding_on_blur = true; // disable input so it doesn't accidentally trigger the add again // before the result has come back field.input_elements().prop( 'disabled', true ); var add_button = parent_form.getAddButton(record_ident); if (add_button) { add_button.click(); // if the add fails for some reason the input will still be disabled // so add a check for that here and re-enable the input // if global var is set then adding is happening, nothing to do // if not then add failed, enable the input again if (!window.adding_record || field.hasErrors()) { field.input_elements().prop( 'disabled', false ); } } else { Zymonic.log("Unable to find add button for form "+parent_form.ident); } field.adding_on_blur = false; event.preventDefault(); } ); } } }; // generic function to add settings to a field object Zymonic.Field.Field.prototype.setExtraArgs = function(args) { for (var key in args) { this[key] = args[key]; } } // return true if this field is still on the current page Zymonic.Field.Field.prototype.onPage = function() { return this.container_div().length > 0; } Zymonic.Field.Field.prototype.addDefaultHandler = function(element) { var field_container = this.container_div(); var ident = this.getIdent(); var jquery_element = $(element); jquery_element.bind(this.defaultHandler, {}, function(event) { $(Zymonic).trigger(ident + 'defaultHandler', [event]); }); jquery_element.focus(function() { field_container.addClass('hasFocus'); }); jquery_element.blur(function() { field_container.removeClass('hasFocus'); }); }; Zymonic.Field.Field.prototype.getZName = function() { return this.zname; }; Zymonic.Field.Field.prototype.getIdent = function() { return this.ident; }; Zymonic.Field.Field.prototype.getClass = function() { return this.class_name; }; Zymonic.Field.Field.prototype.addToWrapperLookup = function(ident) { if( !Zymonic.FieldLookupByWrapperIdent[this.parent_form_ident] || !(Object.prototype.toString.call( Zymonic.FieldLookupByWrapperIdent[this.parent_form_ident] ) === '[object Object]') ) Zymonic.FieldLookupByWrapperIdent[this.parent_form_ident] = new Object; Zymonic.FieldLookupByWrapperIdent[this.parent_form_ident][this.ident] = this; }; Zymonic.Field.Field.prototype.getValueFromInputs = function() { var value; if (!this.hidden && (this.display_only || this.report_mode == "true")) { value = this.display_only_element().text(); } else { value = this.jquery().val(); } return value; } Zymonic.Field.Field.prototype.getValue = function(raw) { var value = this.getValueFromInputs(); // Trim field of any leading/trailing whitespace if ( value && typeof(value) == 'string' ) { value = value.replace(/^\s+/, "").replace(/\s+$/, ""); } // for numeric types, strip any commas if ( value && this.field_type && this.field_type.match(/int|float|double|decimal/) ) { value = value.replace(/,/g, ""); } // apply any field fills var ffc_value = this.runFieldFillCalculations(value); // if value has changed then // set the changed value on the field itself so the user can see it if (ffc_value != null && ffc_value != value) { value = ffc_value; this.setValue(value, true); } // if raw value wanted then see if any comma stripping is needed if ( raw && value && this.field_type && this.field_type.match(/int|float|double|decimal/) ) { value = value.replace(/,/g, ""); } return value; }; Zymonic.Field.Field.prototype.runFieldFillCalculations = function(value) { var ret_value = null; var ffc_set = false; for (var i=0; i< this.field_fill_calculations.length; ++i) { var ffc = this.field_fill_calculations[i]; var ffc_value = ffc.getValue(value); if ( ffc_value != null && ( ffc_value !== '' || ffc.allowEmpty() ) ) { ffc_value = this.roundValue(ffc_value); ret_value = ffc_value; // stop now, we got a value break; } } return ret_value; }; Zymonic.Field.Field.prototype.getValueURIEncoded = function() { return encodeURI(this.getValue()); }; Zymonic.Field.Field.prototype.setValue = function(value, not_as_changed) { // be careful firing change event here // see linked field reverse fill for example of it var changed = false; if (!this.hidden && (this.display_only || this.report_mode == "true")) { if ( this.display_only_element().text() != value ) { this.display_only_element().text(value); changed = true; } } else { if ( this.jquery().val() != value ) { this.jquery().val(value); changed = true; } } // do value changed checks this.setValueAsChanged(value, not_as_changed); // track when value is changed if ( !this.last_value || value != this.last_value ) { this.last_value = value; this.value_last_changed_timestamp = Zymonic.Utils.current_timestamp(); } }; Zymonic.Field.Field.prototype.clearValue = function() { this.setValue(''); }; // set a field as having its value changed // for hidden/readonly fields ensure they are never marked as changed Zymonic.Field.Field.prototype.setValueAsChanged = function(value, not_as_changed, force) { // use flag to ensure we don't recurse through this if (this.setting_value_as_changed) { return; } this.setting_value_as_changed = true; if (!not_as_changed) { if ( !force && ( this.hidden || this.display_only || this.report_mode == "true" || ( !this.any_change && this.original_value != null && this.original_value == (value || this.getValue()) ) ) ) { this.container_div().removeClass(this.FIELD_CHANGED_CLASS); } else { this.container_div().addClass(this.FIELD_CHANGED_CLASS); } } // if forcing then set an extra param to make it use this value server side if (force) { this.addExtraFormData(this.ident+"_force_get_user_value", "true"); this.input_elements().addClass(this.FIELD_FORCE_CHANGED); } // call handlers, regardless of status of the field itself if ( this.any_change || this.original_value == null || this.original_value != (value || this.getValue()) ) { for (var i=0; i< this.handlers.change.length; ++i) { this.handlers.change[i](); } } this.setting_value_as_changed = false; }; // has a field had its value changed Zymonic.Field.Field.prototype.hasValueChanged = function(value) { if (this.container_div().hasClass(this.FIELD_CHANGED_CLASS)) { return true; } else if ( ( this.original_value == null && this.getValue() ) || ( this.original_value != null && this.original_value != this.getValue() ) ) { // this can be called before other handlers which set the CSS class set above // so add this as a sanity check return true; } else if ( this.search_field == "true" && (( this.check_empty == "Y" && !this.isCheckEmptySet() ) || ( this.check_empty != "Y" && this.isCheckEmptySet() )) ) { this.check_empty = this.isCheckEmptySet() ? "Y" : "N"; return true; } else { return false; } }; // checks whether a field has a block value saved which differs from the original Zymonic.Field.Field.prototype.hasUnsavedBlockValue = function() { if ( this.block_value == null || ( this.block_value != null && this.original_value != null && this.original_value == this.block_value ) ) { return false; } else { return true; } }; // updates original and block values Zymonic.Field.Field.prototype.setOriginalValue = function(value) { this.setOriginalValue = value; }; Zymonic.Field.Field.prototype.setBlockValue = function(value) { this.block_value = value; }; // checks if this field is in a placeholder record Zymonic.Field.Field.prototype.inPlaceholder = function() { if (this.record && this.record.is_placeholder) { return true; } return false; }; //checks if this field is in a record on a multiple record form Zymonic.Field.Field.prototype.onMultipleRecordForm = function() { if (this.record && this.record.on_multiple_record_form) { return true; } return false; }; //checks if this field is in a placeholder record Zymonic.Field.Field.prototype.isBeingAdded = function() { if (this.record && this.record.being_added) { return true; } return false; }; // returns if this field needs validation as part of a submit Zymonic.Field.Field.prototype.needsValidationOnSubmit = function() { if ( this.onMultipleRecordForm() && this.inPlaceholder() ) { if ( Zymonic.transition_being_run && Zymonic.transition_being_run.add_on_save ) { var form = this.getParentForm(); var fields_to_check = Zymonic.transition_being_run.add_on_save[form["zname"]] if (form && form["zname"] && fields_to_check) { var record = form.getRecord(this.record.ident, true); if (record) { for (var i=0; i 0) { Zymonic.log("Found deleted record in result: "+form_record.record_ident); return; } // remove it from the form object form.removeRecord(form_record); // remove the existing record record_replacement_element.remove(); } else { Zymonic.log("Invalid handle_record_replacement: "+handle_record_replacement); } }; // Append to form data Zymonic.Field.Field.prototype.appendToForm = function(form_data, search) { var val = this.getValue(); // Send empty string instead of null if(!val) { val = ''; } if(search) { if(this.jquery('_check_empty').is(':checked')) { form_data.append(this.getIdent() + '_check_empty', this.jquery('_check_empty').val()); } else { form_data.append(this.getIdent() + '_check_empty', ''); } } if(!search || val !== null) { form_data.append(this.getIdent(), val); } this.appendOtherFieldsToForm(form_data, search); this.setExtraFormParams(form_data); }; // Append other fields to form data Zymonic.Field.Field.prototype.appendOtherFieldsToForm = function(form_data, search) { // need to add any contained field values which have changed this.getFields().forEach(function(field) { if (field.hasValueChanged()){ field.appendToForm(form_data, search); } }); }; // Add a mechanism to force a record ident - initially added so that subfilter // fields can be give a record ident to allow reload to work Zymonic.Field.Field.prototype.setForceParentIdents = function(form_ident, record_ident) { this.force_record_ident = record_ident; this.force_parent_form_ident = form_ident; } // reloads the current field via call to the server and put the results in callbacks Zymonic.Field.Field.prototype.reload = function(error_cb, success_cb, extra_params, use_parent_form, save, validate, save_block_params, get_block_params, full_update, loading_function, record_being_added, add_new_placeholder, placeholder_to_copy) { // if not saving, check for unsaved changes before continuing if (!save && !this.skip_unsaved_changes_checks) { if (this.checkForUnsavedChanges()) { // get confirmation before proceeding var cont = confirm("There are unsaved changes here, which will be highlighted. Do you wish to leave them unsaved?"); if (!cont) { // success, but without the can_continue flag success_cb(this, false); return false; } } } // build list of params var form_data = Zymonic.new_form_data(); form_data.record_being_added = record_being_added || null; this.appendToForm(form_data); // add field reload values form_data.append("ZZwebservicemode", "field"); form_data.append("ZZno_display_attributes","false"); form_data.append("ZZBzname", this.zname); form_data.append("ZZFident", this.ident); form_data.append("ZZFblock_id", this.block_id); form_data.append("ZZFparent_fap_from_block", 'true'); form_data.append("ZZFpage_id", this.page_id); form_data.append("ZZBprocess_id", this.process_id); form_data.append("ZZFform", this.parent_form_zname); if(this.force_parent_form_ident) { form_data.append("ZZFform_ident", this.force_parent_form_ident); } else { form_data.append("ZZFform_ident", this.parent_form_ident); } if(this.force_record_ident) { form_data.append("ZZFrecord_ident", this.zymoforce_record_ident); } else if (this.record) { form_data.append("ZZFrecord_ident", this.record.ident); } if (this.field_group_ident) { form_data.append("ZZFfield_group_ident", this.field_group_ident); } if (this.report_mode == 'true') { form_data.append("ZZFsubtype", "Report"); } else if (this.search_field == 'true') { form_data.append("ZZFsubtype", "Search"); } // set flags if (use_parent_form && !this.no_parent_form) { form_data.append("ZZFuse_parent_form", "true"); } if (save) { form_data.append("ZZFsave", "true"); } if (validate) { form_data.append("ZZFvalidate", "true"); } if (save_block_params) { form_data.append("ZZFsave_block_params","true"); } if (get_block_params) { form_data.append("ZZFget_block_params","true"); } // add any extra params on the object itself // same for lookup fields and extra fields this.setExtraParams(form_data); this.setFieldLookupParams(form_data); this.setExtraFieldsParam(form_data); // add extra params if (extra_params) { for (var i=0; i 0) { form_data.append('ZZFextra_fields', extra_fields.join(",")); } }; //attach UI tab event to field object Zymonic.Field.Field.prototype.setLastInFieldGroup = function(field_group_ident){ var field = this; var inputs = field.input_elements(); inputs.last().bind('keydown',function(e){ if((e.which||e.keyCode)==9 && !e.shiftKey){ var form = Zymonic.lookup_object('Form', field.parent_form_ident); form.nextFieldGroup(field.record.ident); // prevent the default tab behaviour e.preventDefault(); } }); } Zymonic.Field.Field.prototype.setFirstInFieldGroup = function(field_group_ident){ var field = this; var inputs = field.input_elements(); inputs.first().bind('keydown',function(e){ if((e.which||e.keyCode)==9 && e.shiftKey){ var form = Zymonic.lookup_object('Form', field.parent_form_ident); form.prevFieldGroup(field.record.ident); // prevent the default tab behaviour e.preventDefault(); } }); } Zymonic.Field.Field.prototype.basicValidation = function(display_name, min_length, lls) { if(this.noBasicValidation()) return 'OK'; this.clearErrors(); var field = getFieldValue(this.ident); var type_ok = false; var required_ok = false; var length_ok = false; var duplicates_ok = false; // Check for correct type var digit_type = false; if ( ! field ) { // need to check if type is a digit var re = new RegExp('int|float|double',"i"); if ( this.field_type.match(re) ) { digit_type = true; } type_ok = true; } else { var re = new RegExp('char|text|blob|enum',"i"); if ( this.field_type.match(re) ) { // most things are ok here type_ok = true; } else { var re = new RegExp('int',"i"); if ( this.field_type.match(re) ) { digit_type = true; var re = new RegExp("^-?[\\d]*(?:e[-+]?\\d+)?$"); if(field.match(re)) type_ok = true; } else { var re = new RegExp('float|double',"i"); if ( this.field_type.match(re) ) { digit_type = true; var re = new RegExp('^-?[\\d,]*\\.{0,1}[\\d]*(?:e[-+]?\\d+)?$'); if(field.match(re)) type_ok = true; } else { if ( this.field_type.toLowerCase == 'datetime' ) { var re = new RegExp('(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d) (\\d\\d):(\\d\\d):(\\d\\d)'); if(field.match(re)) type_ok = true; } else { type_ok = true; } } } } } // Check if required field if ( !this.required || field ) { required_ok = true; } // Check length if ( (!field) || field.length >= min_length ) { length_ok = true; } // if flag set for duplicates check on other records on the form // can only do this if we have parent form and a record if ( this.no_duplicate_on_form && this.parent_form_ident && this.record && this.record.ident ) { var form = this.getParentForm(); if (form) { var found_duplicate = false; var value = this.getValue(); var records = form.getRecords(true); for (var i=0; i < records.length; ++i) { // skip this record if ( this.record.ident == records[i].record_ident ) { continue; } for (var j=0; j