// setup namespace Zymonic.Form = {}; // base form // don't do anything here, makes it easy to establish inheritance Zymonic.Form.Form = function() {}; // setup some constants Zymonic.Field.Field.prototype.FIELD_CHANGED_CLASS = "ZymonicFieldChanged"; // setup some constants Zymonic.Form.Form.prototype.SYNC_UPDATE_TYPES = { "Update": true, "Insert": true, "UpdateOrInsert": true, "InsertIfNotFound": true, "Delete": true }; Zymonic.Form.Form.prototype.SYNC_UPDATE_TYPES_TO_ADD_RECORDS = { "Insert": true, }; Zymonic.Form.Form.prototype.SYNC_UPDATE_TYPES_TO_DELETE_RECORDS = { "Delete": true, }; Zymonic.Form.Form.prototype.SYNC_UPDATE_TYPES_TO_LOOKUP_RECORDS = { "Update": true, "UpdateOrInsert": true, "InsertIfNotFound": true, "Delete": true }; Zymonic.Form.Form.prototype.SYNC_UPDATE_TYPES_TO_ADD_RECORD_IF_NOT_FOUND = { "UpdateOrInsert": true, "InsertIfNotFound": true, }; Zymonic.Form.Form.prototype.SYNC_UPDATE_TYPES_TO_SKIP_RECORD_IF_FOUND = { "InsertIfNotFound": true, }; Zymonic.Form.Form.prototype.SYNC_UPDATE_TYPES_TO_DELETE_BEFORE_SYNC = { "Insert": true, }; Zymonic.Form.Form.prototype.SYNC_UPDATE_TYPES_TO_DELETE_AFTER_SYNC= { "Update": true, "UpdateOrInsert": true, }; Zymonic.Form.Form.prototype.PLACEHOLDER_ID = 'ZZNEW'; // init function Zymonic.Form.Form.prototype.init = function(args) { for (var key in args) { this[key] = args[key]; } if (this.ident != "") { Zymonic.add_to_lookup('Form', this.ident, this); } this.field_groups = {}; this.form_search_fields = []; this.resetHandlers(); // add placeholder record if needed, do this on ready to let everything get setup // can have a race condition here as we need this to run as soon as the form is fully // displayed, including any extra js rendering needed, e.g. fieldgroup tabs // so we trigger it from a custom form ready event, which gets triggered when all // records in form have completed rendering if ( this.use_js_adding && !this.no_add && this.multiple_records ) { var form = this; Zymonic.Utils.attach_event( this, 'ready_'+this.ident, 'ready_'+this.ident, function() { // find the existing placeholder and remove it var placeholder_record_html = form.getContainerDiv(" .ZymonicJSPlaceholderRecord"); placeholder_record_html.detach(); form.setupPlaceholder.call(form, placeholder_record_html); } ); } // confirm loading of any records on the GUI Zymonic.Utils.attach_event( this, 'record_ready_'+this.ident, 'record_ready_'+this.ident, function(event, form, record_ident) { var record = form.getRecord(record_ident); if (!record) { Zymonic.log("Unable to find ready record: "+record_ident); return; } if (record.ready) { return; } record.ready = true; // if all records are ready trigger the form as ready var records = form.getRecords(); var ready = true; for (var i = 0; i < records.length; i++) { if (!records[i].ready){ ready = false; break; } } if (ready) { Zymonic.Utils.trigger_event( form, 'ready_'+form.ident, null, [form] ); } } ); // horizontal forms load automatically, ready all records if (this.isHorizontal()) { var formobj = this; var records = this.getRecords(); for (var i = 0; i < records.length; i++) { Zymonic.Utils.trigger_event( formobj, 'record_ready_'+formobj.ident, null, [ formobj, records[i].record_ident ] ); } this.resizeobserver = new ResizeObserver( entries => { entries.forEach( entry => { /* Log left commented out just in case... */ /* console.log("RESULTSIZE: " + formobj.resultsize + " - clientwidth: " + entry.target.clientWidth + " - resultmode: " + formobj.resultmode); */ /* if(!formobj.resultsize || ( formobj.resultsize < entry.target.clientWidth && formobj.resultmode != 'normal' ) || ( formobj.resultsize > entry.target.clientWidth && formobj.resultmode != 'narrow' ) ) { formobj.resultSizeHandler(false); } */ /* Going to try a 'dwell' variant with reset - to deal with Safari not actually auto fitting columns when the layout shrinks (only grows?!?) */ clearTimeout(formobj.resizehandle); formobj.resizehandle = setTimeout(function() { formobj.resultSizeHandler(true); }, 300); } ); }); // Cache this first to ensure resultSizeHandler doesn't think its been changed. this.reportlayoutmode = $('input[name="' + this.ident + 'reportlayoutmode"]:checked').val(); if(div_to_observe) { var div_to_observe = document.getElementById( this.ident + "_div"); this.resizeobserver.observe(div_to_observe); this.resultSizeHandler(true); $('#' + this.ident + "reportlayoutmode_narrow").on('change', function() { formobj.resultSizeHandler(true); }); $('#' + this.ident + "reportlayoutmode_normal").on('change', function() { formobj.resultSizeHandler(true); }); $('#' + this.ident + "reportlayoutmode_auto").on('change', function() { formobj.resultSizeHandler(true); }); } } }; Zymonic.Form.Form.prototype.docReadyHandler = function() { if (this.isHorizontal()) { var formobj = this; this.resizeobserver = new ResizeObserver( entries => { entries.forEach( entry => { /* Log left commented out just in case... */ /* console.log("RESULTSIZE: " + formobj.resultsize + " - clientwidth: " + entry.target.clientWidth + " - resultmode: " + formobj.resultmode); */ /* if(!formobj.resultsize || ( formobj.resultsize < entry.target.clientWidth && formobj.resultmode != 'normal' ) || ( formobj.resultsize > entry.target.clientWidth && formobj.resultmode != 'narrow' ) ) { formobj.resultSizeHandler(false); } */ /* Going to try a 'dwell' variant with reset - to deal with Safari not actually auto fitting columns when the layout shrinks (only grows?!?) */ clearTimeout(formobj.resizehandle); formobj.resizehandle = setTimeout(function() { formobj.resultSizeHandler(true); }, 300); } ); }); // Cache this first to ensure resultSizeHandler doesn't think its been changed. this.reportlayoutmode = $('input[name="' + this.ident + 'reportlayoutmode"]:checked').val(); this.resizeobserver.observe(document.getElementById( this.ident + "_div")); this.resultSizeHandler(true); $('#' + this.ident + "reportlayoutmode_narrow").on('change', function() { formobj.resultSizeHandler(true); }); $('#' + this.ident + "reportlayoutmode_normal").on('change', function() { formobj.resultSizeHandler(true); }); $('#' + this.ident + "reportlayoutmode_auto").on('change', function() { formobj.resultSizeHandler(true); }); } }; // Report result size handler Zymonic.Form.Form.prototype.resultSizeHandler = function(reset) { var resultModeBefore = this.resultmode; this.reportlayoutmode = $('input[name="' + this.ident + 'reportlayoutmode"]:checked').val() var resultsElement = $('#' + this.ident + "_div .record_container").get(0); if (typeof resultsElement !== 'undefined') { // Reset the result block if freshly loading if(reset) { $('#' + this.ident + "_div .record_container").removeClass('narrowmode'); $('#' + this.ident + "_div .record_container").hide().show(0); // Force safari to redraw this.resultmode = 'normal'; } // Note in the remainder of this code we are updating all record_container class // elements (including MCLF selection area) - however we still rely on the // results element for detection. if( this.reportlayoutmode == 'narrow' ) { $('#' + this.ident + "_div .record_container").addClass('narrowmode'); this.resultmode = 'narrowmode'; } else if( this.reportlayoutmode == 'normal' ) { $('#' + this.ident + "_div .record_container").removeClass('narrowmode'); this.resultmode = 'normal'; } else { // Check if there is overflow to deal with - there seems to be a bit of variation in // clientWidth/scrollWidth - resulting in adding 10px to cover it if(resultsElement.scrollWidth > ( resultsElement.clientWidth + 10) ) { $('#' + this.ident + "_div .record_container").addClass('narrowmode'); this.resultmode = 'narrow'; } else { $('#' + this.ident + "_div .record_container").removeClass('narrowmode'); this.resultmode = 'normal'; } this.resultsize = resultsElement.scrollWidth; } } if(this.resultmode != resultModeBefore) { // Nothing records this change server side currently. // Resize the block if(this.block_id) { var block = Zymonic.lookup_object("Block", this.block_id); if(block) { block.autolength(); } } } } // called when form is reloaded to reset and handlers on new elements Zymonic.Form.Form.prototype.resetHandlers = function() { if (this.prefill_details) { this.setupPreFill(this.prefill_details); } if (this.sortable) { // function to autoscroll the screen if dragging near edge // needs to be custom function as built in scroll functionality // doesn't work nicely due to how desktop mode positioning works var sortable_autoscroll = function(event) { // is there a better way to get these magic numbers? // currently just hardcoded on what felt nice if (event.clientY < 150) { window.scrollBy({ top: -40, left: 0, behavior: "smooth", }); } else if (event.clientY > ( window.innerHeight - 30 - $(event.target).height() ) ) { window.scrollBy({ top: 40, left: 0, behavior: "smooth", }); } }; var form = this; this.getContainerDiv(" .HorizontalFormTable").sortable({ items: '.record', containment: 'parent', cursor: 'move', // disable scrolling, doesn't work in desktop mode // uses a custom autoscroll scroll: false, start: function(event, ui) { $(ui.item).addClass("ZymonicFormRecordBeingSorted"); document.addEventListener("mousemove", sortable_autoscroll); }, stop: function(event, ui) { $(ui.item).removeClass("ZymonicFormRecordBeingSorted"); document.removeEventListener("mousemove", sortable_autoscroll); }, update: function(event, ui) { if (form.sortable_field_zname) { // get onscreen order and update field var sequence = 1; $(ui.item).parent().children(".record").each(function(i, record_el) { var record_ident = $(record_el).attr('id'); var field = form.getRecordField(record_ident, form.sortable_field_zname); if (field) { // set the value, and force it to be flaged as changed // so its always sent back to the server field.setValue(sequence); field.setValueAsChanged(sequence, false, true); sequence++; } }); } }, }); } }; // get form container or contents within Zymonic.Form.Form.prototype.getContainerDiv = function(extra) { extra = extra || ""; return $("#"+this.ident+"_div"+extra); }; // register the form formats for this form // nothing is current doing with this, but i think it makes sense to store them Zymonic.Form.Form.prototype.addFormFormat = function(form_format) { if (!this.form_formats) { this.form_formats = []; } this.form_formats.push(form_format); }; // get form records Zymonic.Form.Form.prototype.getRecords = function(lookup_fields, lookup_record_containers) { // take copy of records var records = ( this.records ? this.records.slice() : [] ); // lookup fields and/or containers if (lookup_fields || lookup_record_containers) { for (var i=0; i0){ currentTab -= 1; tabObj.tabs('select', currentTab); // find record so we can then find fieldgroup within and finally the first field to focus var record; if (this.records && this.records.length == 1) { record = this.records[0]; } else { for (var i=0; i if it's a process, so if it doesn't exist just set it as an empty string navigation_node = (navigation_node ? (navigation_node.outerHTML || (new XMLSerializer().serializeToString(navigation_node))) : ''); // working transformation consists of - Filter > Record > Fieldgroup, so assemble the string as such record.append(field_group_xml.get(0)); record = record.get(0).outerHTML || (new XMLSerializer().serializeToString(record.get(0))); var xml_string = ''; xml_string += record + ''; // for some reason parseXML does not allow = in attributes // so try setting it directly after parsing var parsed_xml = jQuery.parseXML(xml_string); $(parsed_xml).find("Zymonic").attr("pageurl", xml.find("Zymonic").attr("pageurl")); return parsed_xml; } Zymonic.Form.Form.prototype.getPage = function() { return $('#' + this.ident + 'go_to_page').val(); }; Zymonic.Form.Form.prototype.getRecordsPerPage = function() { return $('#' + this.ident + 'records_per_page').val(); }; Zymonic.Form.Form.prototype.appendNavigationToForm = function(form_data) { // add page and records per page var page = this.getPage(); var records_per_page = this.getRecordsPerPage(); if (page) { form_data.append( this.ident+"go_to_page", page ); } if (records_per_page) { form_data.append( this.ident+"records_per_page", records_per_page ); } }; // callback to run when records change Zymonic.Form.Form.prototype.onRecordsChange = function(cb, caller_ident) { Zymonic.Utils.attach_event( $(this), 'records_changed_'+this.ident, 'records_changed_'+this.ident+(caller_ident ? caller_ident : ""), cb ); }; // trigger callback to run when records change Zymonic.Form.Form.prototype.triggerRecordsChanged = function(extras) { Zymonic.Utils.trigger_event( $(this), 'records_changed_'+this.ident, null, [ this, extras ] ); // Resize the block if(this.block_id && Zymonic.lookup_object("Block", this.block_id)) { Zymonic.lookup_object("Block", this.block_id).autolength(); } }; // sets up everything needs to run prefill via JS Zymonic.Form.Form.prototype.setupPreFill = function(prefill_details) { // find the button in the GUI and add handler, for each record var form = this; Zymonic.Utils.attach_event( this.getPreFillButton(), "click", this.ident+"prefill", function(event) { form.runPreFill.call(form, prefill_details); // prevent the normal click action event.preventDefault(); } ); }; // get the prefill button, if present Zymonic.Form.Form.prototype.getPreFillButton = function() { return $("#"+this.ident+"prefill"); }; // runs the prefill on the current form Zymonic.Form.Form.prototype.runPreFill = function(prefill_details) { // disable the GUI side temporarily if (false) { // load up the filter var source_filter ; if ( prefill_details.source_filter_class && Zymonic.Filter[prefill_details.source_filter_class] ) { source_filter = new Zymonic.Filter[prefill_details.source_filter_class](); } else { source_filter = new Zymonic.Filter.Filter(); } source_filter.init({ zname: prefill_details.source_filter_zname, ident: this.ident+"_"+prefill_details.source_filter_zname, // TODO: restrict fields to those needed for the prefill }); // set params for the search fields for (var i=0; i report > result").each(function(i, xml_result) { // result id and ident var result = { ident : $(xml_result).attr('ident'), id: {} }; $.each($(xml_result).find("ZZid").get(0).attributes, function(i, attribute) { result.id[attribute.name] = attribute.value; }); // fields in result $(xml_result).find("*[type='Field']").each(function(i, xml_field) { result[xml_field.tagName] = $(xml_field).find("Value").text(); }); results.push(result); }); // synchronize results form.synchronizeRecords( results, prefill_details.pre_fill_field_maps, 'UpdateOrInsert', true ); // all done unblock(); }, null, null, null, function() { Zymonic.Utils.block_gui_element( form.getContainerDiv() ) } ); return; } // block the GUI var unblock = Zymonic.Utils.block_gui_element( this.getContainerDiv() ); // otherwise reload just this form var form_data = Zymonic.new_form_data(); // as all this forms record data to form only this.appendRecordsDataToForm(form_data); // add dependent fields from linking field maps and parent form id field maps as extra fields var extra_fields = []; for (var i=0; i 0) { matching_records = matching_records.concat(found_records); } else if (Zymonic.Form.Form.prototype.SYNC_UPDATE_TYPES_TO_ADD_RECORD_IF_NOT_FOUND[update_type]) { matching_records.push(this.setupPlaceholder()); } else if (Zymonic.Form.Form.prototype.SYNC_UPDATE_TYPES_TO_SKIP_RECORD_IF_FOUND[update_type]) { return []; } } if (matching_records.length > 0 && !this.synchronize_multiple_records) { // currently not in user // TODO: resolve multiple records if needed } // if we are going to delete these records can return them now without any field updates if (Zymonic.Form.Form.prototype.SYNC_UPDATE_TYPES_TO_DELETE_RECORDS[update_type]) { return matching_records; } // grab the update field maps var update_field_maps = []; for (var i=0; i 0 ) { updated_records.push(updated_record); } } else { // NOTE: server side there are checks on zzlu for last updates // these have been left out here as we don't have DB access to check last updated values easily updated_records.push(updated_record); } } return updated_records; }; // assemble record key using field maps // this is essentially a copy of Zymonic::Table::synchronize_record_key so has functionality not current in use Zymonic.Form.Form.prototype.synchronizeRecordKey = function(record, field_maps) { var lookup_key = {}; for (var i=0; i"+short_description+""; var field_label = $(search+" .ZymonicField#"+new_ident+"_div label"); if (field_label.find('FieldLabelSpan').length) { field_label.find('FieldLabelSpan').replaceWith(new_label); } else { field_label.append(new_label); } return new_ident }; // function to create a new input field on the form and returns its new ident Zymonic.Form.Form.prototype.newEmptyField = function(search, field_ident, new_ident_suffix, display_name, short_description) { // new ident idents var new_ident = field_ident + new_ident_suffix; // create a basic input field and container, set class so its not treated as an unsaved change var new_html = $("
").attr("id", new_ident+"_div").addClass("ZymonicField ZymonicIgnoreAsUnsavedChange"); $("