diff --git a/EHR_ComplianceDB/api-src/org/labkey/ehr_compliancedb/api/EHR_ComplianceService.java b/EHR_ComplianceDB/api-src/org/labkey/ehr_compliancedb/api/EHR_ComplianceService.java
new file mode 100644
index 000000000..163762e73
--- /dev/null
+++ b/EHR_ComplianceDB/api-src/org/labkey/ehr_compliancedb/api/EHR_ComplianceService.java
@@ -0,0 +1,22 @@
+package org.labkey.ehr_compliancedb.api;
+
+import org.labkey.api.data.Container;
+
+/**
+ * Created by jon on 3/22/16.
+ */
+public abstract class EHR_ComplianceService {
+ static private EHR_ComplianceService _service = null;
+
+ static public EHR_ComplianceService get() {
+ return _service;
+ }
+
+ public static void setInstance(EHR_ComplianceService instance) {
+ _service = instance;
+ }
+
+ public abstract String getComplianceModuleName();
+
+ public abstract Container getEmployeeContainer(Container c);
+}
diff --git a/EHR_ComplianceDB/resources/module.xml b/EHR_ComplianceDB/resources/module.xml
index 8d95f5b68..ec54c55ca 100644
--- a/EHR_ComplianceDB/resources/module.xml
+++ b/EHR_ComplianceDB/resources/module.xml
@@ -14,6 +14,13 @@
This is the default view to show on the employees table
+
+ true
+
+ ADMIN
+
+ This is the containerPath to the folder holding the list of PDFs shared between public and private folder. The PDF list has a Linked schema between both folders, the actual files have to reside in the public folder Use of slashes is very important - it should be in the format '/myProject/compliance'
+
diff --git a/EHR_ComplianceDB/resources/queries/ehr_compliancedb/MostRecentCompletionDates.sql b/EHR_ComplianceDB/resources/queries/ehr_compliancedb/MostRecentCompletionDates.sql
new file mode 100644
index 000000000..a7ed96d66
--- /dev/null
+++ b/EHR_ComplianceDB/resources/queries/ehr_compliancedb/MostRecentCompletionDates.sql
@@ -0,0 +1,14 @@
+PARAMETERS (RequirementName VARCHAR DEFAULT 'SOP REVIEW-ANNUAL')
+
+SELECT t1.employeeid, t3.lastname, t3.firstname, t1.requirementname, t1.date
+FROM ehr_compliancedb.completiondates t1
+ JOIN ehr_compliancedb.employees t3
+ ON t1.employeeid = t3.employeeid
+WHERE EXISTS (SELECT 1
+ FROM ehr_compliancedb.completiondates t2
+ WHERE t1.employeeid = t2.employeeid
+ AND t1.requirementname = t2.requirementname
+ AND UPPER(t2.requirementname) = UPPER(RequirementName)
+ GROUP BY t2.employeeid, t2.requirementname
+ HAVING t1.date = MAX(t2.date))
+ORDER BY LOWER(t1.employeeid) ASC
diff --git a/EHR_ComplianceDB/resources/queries/ehr_compliancedb/SOPDateLastRead.query.xml b/EHR_ComplianceDB/resources/queries/ehr_compliancedb/SOPDateLastRead.query.xml
deleted file mode 100644
index e253428a4..000000000
--- a/EHR_ComplianceDB/resources/queries/ehr_compliancedb/SOPDateLastRead.query.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
'
- );return tpl.compile()}()
- });
-
- EHR.ext.ProjectField.superclass.initComponent.call(this, arguments);
-
- this.mon(this.ownerCt, 'participantchange', this.getProjects, this);
- },
-
- makeSql: function(id, date){
- var sql = "SELECT DISTINCT a.project, a.project.account, a.project.protocol as protocol FROM study.assignment a " +
- "WHERE a.id='"+id+"' " +
- //this protocol contains tracking projects
- "AND a.project.protocol != 'wprc00' ";
-
- if(!this.allowAllProtocols){
- sql += ' AND a.project.protocol IS NOT NULL '
- }
-
- if(date)
- sql += "AND cast(a.date as date) <= '"+date.format('Y-m-d')+"' AND (cast(a.enddate as date) >= '"+date.format('Y-m-d')+"' OR a.enddate IS NULL)";
- else
- sql += "AND a.enddate IS NULL ";
-
- if(this.defaultProjects){
- sql += " UNION ALL (SELECT project, account, project.protocol as protocol FROM ehr.project WHERE project IN ('"+this.defaultProjects.join("','")+"'))";
- }
-
- return sql;
- },
-
- getProjects : function(field, id){
- if(!id && this.ownerCt.boundRecord)
- id = this.ownerCt.boundRecord.get('Id');
-
- var date;
- if(this.ownerCt.boundRecord){
- date = this.ownerCt.boundRecord.get('date');
- }
-
- this.emptyText = 'Select project...';
- this.store.baseParams.sql = this.makeSql(id, date);
- this.store.load();
- }
-});
-Ext.reg('ehr-project', EHR.ext.ProjectField);
-
-
-/**
- * This field is used for EHR remarks. It automatically used the ResizableTextArea plugin. Also, if this field is created
- * with a storeCfg object, a combo will be created above the store that allows the user to pick from a set of pre-defined remarks
- * stored in the specified table
- * @param config The configuration object. Allows any parameters from the superclass as well as those defined here
- * @param {object} [config.storeCfg] This is a config object used to create a LABKEY.ext.Combobox displaying the saved remarks for this field.
- * @param {object} [config.storeCfg.schemaName] The schema from which to query saved remarks
- * @param {object} [config.storeCfg.queryName] The query from which to retrieve saved remarks
- * @param {object} [config.storeCfg.displayField] The field of this query to display in the combo
- * @param {object} [config.storeCfg.valueField] The value of the combo. When this combo option selected, the value of this field will be used as the value of the textarea
- */
-EHR.ext.RemarkField = Ext.extend(Ext.form.TextArea,
-{
- initComponent: function(){
- this.plugins = this.plugins || [];
- this.plugins.push('ehr-resizabletextarea');
-
- EHR.ext.RemarkField.superclass.initComponent.call(this);
- },
- onRender: function(){
- EHR.ext.RemarkField.superclass.onRender.apply(this, arguments);
-
- //if there is a storeCfg property, we render a combo with common remarks
- if(this.storeCfg){
- this.addCombo();
- }
-
- },
- addCombo: function(){
- var div = this.container.insertFirst({
- tag: 'div',
- style: 'padding-bottom: 5px;'
- });
-
- this.select = Ext.ComponentMgr.create({
- xtype: 'combo',
- renderTo: div,
- emptyText: 'Common remarks...',
- width: this.width,
- disabled: this.disabled,
- isFormField: false,
- boxMaxWidth: 200,
- valueField: this.storeCfg.valueField,
- displayField: this.storeCfg.displayField,
- triggerAction: 'all',
- store: new LABKEY.ext.Store({
- schemaName: this.storeCfg.schemaName,
- queryName: this.storeCfg.queryName,
- autoLoad: true
- }),
- listeners: {
- scope: this,
- select: function(combo, rec){
- var val = combo.getValue();
- this.setValue(val);
- this.fireEvent('change', this, val, this.startValue);
- combo.reset();
- }
- }
- });
- },
-
- setDisabled: function(val){
- EHR.ext.RemarkField.superclass.setDisabled.call(this, val);
-
- if(this.select)
- this.select.setDisabled(val);
- },
- setVisible: function(val){
- EHR.ext.RemarkField.superclass.setVisible.call(this, val);
-
- if(this.select)
- this.select.setVisible(val);
- }
-});
-Ext.reg('ehr-remark', EHR.ext.RemarkField);
-
-
-/**
- * This field is used for data entry on the Drug Administration dataset. It creates a numerfield with a trigger button.
- * When this trigger is selected, the weight of the current animal is identified, along with the concentration and dosage entered
- * into the current form. These values are combined (weight*dosage/conc) to find the volume and amount.
- */
-EHR.ext.DrugDoseField = Ext.extend(EHR.ext.TriggerNumberField,
-{
- initComponent: function(){
- this.triggerClass = 'x-form-search-trigger';
-
- EHR.ext.DrugDoseField.superclass.initComponent.call(this, arguments);
- },
- onTriggerClick: function(){
- var id, conc, dosage, conc_units;
- var parent = this.findParentByType('ehr-formpanel');
- var theForm = parent.getForm();
-
- var values = theForm.getFieldValues();
- conc = values.concentration;
- dosage = values.dosage;
-
- //note: if this form is an encounter, Id might be inherited, so we use the record as a fallback
- id = values.Id;
- if(!id && parent.boundRecord)
- id = parent.boundRecord.get('Id');
-
- if(!conc || !dosage || !id){
- Ext.Msg.alert('Error', 'Must supply Id, dosage and concentration');
- return
- }
-
- if(parent.importPanel.participantMap.get(id)){
- var weight;
- var showWeight = true;
- if(values.dosage_units && !values.dosage_units.match(/\/kg$/)){
- //console.log('using animal as unit');
- showWeight = false;
- weight = 1;
- }
- else {
- var weightStore = Ext.StoreMgr.find(function(s){
- if(s.queryName=='Weight'){
- var r = s.find('Id', id);
- if(r != -1){
- r = s.getAt(r);
- weight = r.get('weight');
- }
- }
- }, this);
-
- if(!weight){
- var record = parent.importPanel.participantMap.get(id);
- weight = record['Id/availBlood/MostRecentWeight'] || record['Id/mostRecentWeight/MostRecentWeight'] || record['Id/MostRecentWeight/MostRecentWeight'];
- }
-
- if(weight){
- var mt = Ext.form.MessageTargets.under;
- var msg;
- if(showWeight)
- msg = 'Weight: '+weight+' kg';
- else
- msg = null;
-
- if(mt){
- mt.mark(this, msg);
- function onBindChange(){
- mt.mark(this, null);
- this.ownerCt.doLayout();
- }
- parent.on('recordchange', onBindChange, this, {single: true});
- this.ownerCt.doLayout();
- }
- }
- else {
- alert('Unable to find weight');
- return;
- }
- }
-
- var vol = Ext4.util.Format.round(weight*dosage/conc, 2);
-
- //NOTE: calculated from volume to avoid errors due to rounding
- var amount = Ext4.util.Format.round(vol*conc, 2);
-
- theForm.findField('amount').setValue(amount);
- theForm.findField('volume').setValue(vol);
- theForm.findField('dosage').setValue(dosage);
-
- //we only fire 1 event b/c the databind plugin operates on the form as a whole
- var concField = theForm.findField('concentration');
- concField.setValue(conc);
- concField.fireEvent('change', conc, concField.startValue);
- }
- else {
- parent.importPanel.participantMap.on('add', this.onTriggerClick, this, {single: true})
- }
- }
-});
-Ext.reg('ehr-drugdosefield', EHR.ext.DrugDoseField);
-
-
-//this vtype is used in date range panels
-//@deprecated
-Ext.apply(Ext.form.VTypes, {
- daterange : function(val, field)
- {
- var date = field.parseDate(val);
- console.log('validating');
- if (!date)
- {
- console.log('returned');
- return;
- }
- if (field.startDateField && (!this.dateRangeMax || (date.getTime() != this.dateRangeMax.getTime())))
- {
- //var start = Ext.getCmp(field.startDateField);
- var start = field.startDateField;
- start.setMaxValue(date);
- //start.validate.defer(10);
- this.dateRangeMax = date;
-
- start.fireEvent('change', start, start.getValue(), start.startValue);
- }
- else if (field.endDateField && (!this.dateRangeMin || (date.getTime() != this.dateRangeMin.getTime())))
- {
- //var end = Ext.getCmp(field.endDateField);
- var end = field.endDateField;
- end.setMinValue(date);
- //end.validate.defer(10);
- this.dateRangeMin = date;
-
- end.fireEvent('change', end, end.getValue());
- }
- /*
- * Always return true since we're only using this vtype to set the
- * min/max allowed values (these are tested for after the vtype test)
- */
-
- return true;
- }
-});
-
diff --git a/ehr/resources/web/ehr/ext3/ExtContainers.js b/ehr/resources/web/ehr/ext3/ExtContainers.js
deleted file mode 100644
index 3079ded62..000000000
--- a/ehr/resources/web/ehr/ext3/ExtContainers.js
+++ /dev/null
@@ -1,2711 +0,0 @@
-/*
- * Copyright (c) 2013-2019 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
- */
-
-
-/*
- * This file contains miscellaneous Ext containers used throughout the EHR. These tend to be single-user Panels, such as the UI
- * that appears when a button is clicked or other popup windows.
- */
-
-
-/**
- * @class
- * This is the panel that appears when hitting the 'Add Batch' button on EHR grids. It provides a popup to find the set of
- * distinct animal IDs based on room, case, etc.
- */
-EHR.ext.AnimalSelectorPanel = Ext.extend(Ext.Panel, {
- initComponent: function()
- {
- Ext.applyIf(this, {
- layout: 'form'
- ,title: 'Choose Animals'
- ,bodyBorder: true
- ,border: true
- //,frame: true
- ,bodyStyle: 'padding:5px'
- ,width: 350
- ,defaults: {
- width: 200,
- border: false,
- bodyBorder: false
- }
- ,items: [
- {
- xtype: 'textarea',
- height: 100,
- ref: 'subjArea',
- fieldLabel: 'Id(s)'
- },
-// {
-// xtype: 'combo'
-// ,emptyText:''
-// ,fieldLabel: 'Area'
-// ,displayField:'area'
-// ,valueField: 'area'
-// ,typeAhead: true
-// ,editable: true
-// ,triggerAction: 'all'
-// ,store: new LABKEY.ext.Store({
-// schemaName: 'ehr_lookups',
-// queryName: 'areas',
-// sort: 'area',
-// autoLoad: true
-// }),
-// ref: 'areaField'
-//
-// },
- {
- emptyText:''
- ,fieldLabel: 'Room(s)'
- ,ref: 'roomField'
- ,xtype: 'textfield'
- ,listeners: {
- render: function(field){
- field.el.set({autocomplete: 'off'});
- },
- change: function(field, room){
- if(room){
- room = room.replace(/[,;]+/g, ';');
- room = room.replace(/(^;|;$)/g, '');
- room = room.toLowerCase();
- field.setValue(room);
- }
- }
- }
- },
- {
- xtype: 'textfield',
- ref: 'cageField',
- fieldLabel: 'Cage',
- listeners: {
- change: function(field, val){
- if(val && !isNaN(val)){
- var newVal = EHR.Utils.padDigits(val, 4);
- if(val != newVal)
- field.setValue(newVal);
- }
- },
- render: function(field){
- field.el.set({autocomplete: 'off'});
- }
- }
-// },
-// {
-// emptyText:''
-// ,fieldLabel: 'Project'
-// ,ref: 'projectField'
-// ,xtype: 'combo'
-// ,displayField:'project'
-// ,valueField: 'project'
-// ,typeAhead: true
-// ,mode: 'local'
-// ,triggerAction: 'all'
-// ,editable: true
-// ,store: new LABKEY.ext.Store({
-// schemaName: 'ehr',
-// queryName: 'project',
-// viewName: 'Projects With Active Assignments',
-// sort: 'project',
-// autoLoad: true
-// })
- }
- ],
- buttons: [{
- text:'Submit',
- disabled:false,
- ref: '../submit',
- scope: this,
- handler: function(s){
- this.getAnimals();
- }
- },{
- text: 'Close',
- scope: this,
- handler: function(){
- this.ownerCt.hide();
- }
- }]
- //buttonAlign: 'left'
- });
-
- EHR.ext.AnimalSelectorPanel.superclass.initComponent.call(this, arguments);
- },
-
- getFilterArray: function(button)
- {
- var room = this.roomField.getValue();
- var cage = this.cageField.getValue();
- var area = (this.areaField ? this.areaField.getValue() : null);
- var project = (this.projectField ? this.projectField.getValue() : null);
-
- var filterArray = [];
-
- if (area)
- filterArray.push(LABKEY.Filter.create('area', area, LABKEY.Filter.Types.STARTS_WITH));
-
- if (room)
- filterArray.push(LABKEY.Filter.create('room', room, LABKEY.Filter.Types.EQUALS_ONE_OF));
-
- if (cage)
- filterArray.push(LABKEY.Filter.create('cage', cage, LABKEY.Filter.Types.EQUAL));
-
- if (project)
- filterArray.push(LABKEY.Filter.create('Id/activeAssignments/projects', project, LABKEY.Filter.Types.CONTAINS));
-
- return filterArray;
- },
-
- getAnimals: function(button)
- {
- Ext.Msg.wait("Loading...");
-
- //we clean up, combine subjects
- var subjectList = this.subjArea.getValue();
- if(subjectList){
- subjectList = LDK.Utils.splitIds(subjectList);
- if(subjectList.length && this.targetStore){
- var records = [];
- Ext.each(subjectList, function(s){
- records.push({Id: s});
- }, this);
- this.targetStore.addRecords(records);
- }
- }
-
- var filterArray = this.getFilterArray();
- if (!subjectList && !subjectList.length && !filterArray.length)
- {
- Ext.Msg.hide();
- if(!subjectList.length)
- alert('Must Enter A Room or List of Animals');
- return;
- }
-
- this.ownerCt.hide();
-
- if (filterArray.length){
- //find distinct animals matching criteria
- LABKEY.Query.selectRows({
- schemaName: 'study',
- queryName: 'demographicsCurLocation',
- sort: 'room,cage,Id',
- filterArray: filterArray,
- scope: this,
- timeout: 30000,
- success: this.onSuccess,
- failure: EHR.Utils.onError
- });
- }
- else {
- Ext.Msg.hide();
- }
- },
-
- onSuccess: function(results)
- {
- if (!results.rows || !results.rows.length)
- {
- alert('No matching animals were found.');
- Ext.Msg.hide();
- return;
- }
-
- var ids = {};
- var records = [];
-
- Ext.each(results.rows, function(row)
- {
- var obj;
- if (!ids[row.Id]){
- obj = {Id: row.Id};
- if(row.room)
- obj['id/curlocation/location'] = row.room+'-'+row.cage;
- if(row.cond)
- obj['id/curlocation/cond'] = row.cond;
-
-
- records.push(obj);
- ids[row.Id] = 0;
- }
- }, this);
-
- if (this.targetStore){
- this.targetStore.addRecords(records);
- }
-
- Ext.Msg.hide();
- }
-
-});
-Ext.reg('ehr-animalselector', EHR.ext.AnimalSelectorPanel);
-
-
-/**
- * @class
- * This is the panel that appears when hitting the 'Add Series' button. It provides UI that allows the user to generate a consecutive
- * series of animal IDs, (ie. cy0114, cy0115, cy0116, ....). It is used on the arrival form (among others). In this workflow we receive
- * some number of animals and they are often assigned consecutive IDs. It is a helper function only, and does not validate these IDs.
- */
-EHR.ext.AddSeriesWin = Ext.extend(Ext.Panel, {
- initComponent: function()
- {
- Ext.applyIf(this, {
- layout: 'form'
- ,title: 'Enter Series of IDs'
- ,bodyBorder: true
- ,border: true
- //,frame: true
- ,bodyStyle: 'padding:5px'
- ,width: 350
- ,defaults: {
- width: 200,
- border: false,
- bodyBorder: false
- }
- ,items: [
- {
- xtype: 'textfield',
- ref: 'prefix',
- fieldLabel: 'Prefix'
- },{
- fieldLabel: 'Starting Number'
- ,ref: 'startNumber'
- ,xtype: 'numberfield'
- },{
- xtype: 'numberfield'
- ,ref: 'totalIds'
- ,fieldLabel: 'Total IDs'
- }
- ],
- buttons: [{
- text:'Submit',
- disabled:false,
- ref: '../submit',
- scope: this,
- handler: function(s){
- this.getAnimals();
- }
- },{
- text: 'Close',
- scope: this,
- handler: function(){
- this.ownerCt.hide();
- }
- }]
- //buttonAlign: 'left'
- });
-
- EHR.ext.AddSeriesWin.superclass.initComponent.call(this, arguments);
- },
-
- getAnimals: function(button)
- {
- var prefix = this.prefix.getValue();
- var startNumber = this.startNumber.getValue();
- var totalIds = this.totalIds.getValue();
-
- if (!prefix || !startNumber || !totalIds)
- {
- alert('Must Enter A Prefix and Number of Animals');
- return;
- }
-
- this.ownerCt.hide();
-
- var records = [];
- var length = 6 - prefix.length;
- for(var i=0;i]+/)){
- resultOORIndicator = result.match(/[<>]+/)[0];
- result = result.replace(/[<>]+/, '');
- }
- else
- resultOORIndicator = null;
-
- units = row[25];
- panelType = row[14];
-
- if(testId && panelType=='V19SR'){
- testId = testId.replace(/SR$/, '');
- }
-
- if(!id || !date || date=='Invalid Date' || !testId || !result){
- skippedRows.push(['ID: '+id, 'Date: '+(date && (date.format ? date.format(LABKEY.extDefaultDateFormat) : date)), 'TestId: '+testId, 'Result: '+result].join('; '));
- return;
- }
-
- //attempt to find this test ID. search aliases if not found
- var idx = testsStore.find('testid', new RegExp('^'+testId+'$'));
- if(idx==-1){
- var recIdx = testsStore.findBy(function(r){
- var alias = r.get('aliases');
- if(!r.phantom && alias){
- alias = alias.split(',');
- if(alias.indexOf(testId)!=-1){
- return true;
- }
- }
- }, this);
-
- if(recIdx!=-1){
- testId = testsStore.getAt(recIdx).get('testid');
- }
-
- //return false;
- }
- else {
- console.log('found test: '+testId);
- }
-
- rec = {
- Id: id,
- date: date,
- testid: testId,
- result: result,
- units: units
- };
-
- if(resultOORIndicator)
- rec.resultOORIndicator = resultOORIndicator;
-
- this.targetStore.addRecord(rec);
- }, this);
-
- if(skippedRows.length){
- alert('One or more rows were skipped:\n'+skippedRows.join('\n'));
- }
- }
-});
-Ext.reg('ehr-chemexcelwin', EHR.ext.ChemExcelWin);
-
-
-/**
- * @class
- * This window will allow clinpath staff to import hematology results from a text-based format. It is designed to directly
- * parse the output provided by the cytometer. It will parse/validate the incoming records and normalize their values
- * and Test IDs. It will also add units according to the data in ehr_lookups.hematology_tests. This panel is connected to the
- * 'Add Bulk' button in the hematology section in the ClinPath form.
- */
-EHR.ext.HematologyExcelWin = Ext.extend(Ext.Panel, {
- initComponent: function()
- {
- Ext.applyIf(this, {
- //layout: 'form'
- title: 'Enter Hematology From Sysmex Analyzer'
- ,bodyBorder: true
- ,border: true
- //,frame: true
- ,bodyStyle: 'padding:5px'
- ,width: '100%'
- ,defaults: {
- border: false,
- bodyBorder: false
- }
- ,items: [{
- xtype: 'displayfield',
- value: 'This allows bulk import of hematology results using the output of a Sysmex Hematology Analyzer. Cut/paste the contents of the output below.'
- },{
- xtype: 'textarea',
- ref: 'fileField',
- height: 350,
- width: 430
- }],
- buttons: [{
- text:'Submit',
- disabled:false,
- ref: '../submit',
- scope: this,
- handler: function(s){
- this.doSubmit();
- }
- },{
- text: 'Close',
- scope: this,
- handler: function(){
- this.ownerCt.hide();
- }
- }]
- //buttonAlign: 'left'
- });
-
- EHR.ext.HematologyExcelWin.superclass.initComponent.call(this, arguments);
- },
-
- doSubmit: function(button)
- {
- var fileContent = this.fileField.getValue();
-
- if (!fileContent)
- {
- alert('Must Paste Contents of File');
- return;
- }
-
- this.ownerCt.hide();
- Ext.Msg.wait('Loading...');
-
- fileContent = fileContent.replace(/[\x00-\x1F\x7F-\x9F]/g, "");
-
- Ext.Ajax.request({
- url: LABKEY.ActionURL.buildURL("assay", "assayFileUpload"),
- params: {
- fileName: 'HematologyUpload_'+(new Date()).format(LABKEY.extDefaultDateFormat + '_H:m:s.u')+'.tsv',
- fileContent: fileContent
- },
- success: this.onFileUpload,
- failure: EHR.Utils.onError,
- scope: this
- });
- },
- onFileUpload: function(response, options) {
- var data = new LABKEY.Exp.Data(Ext.util.JSON.decode(response.responseText));
- data.getContent({
- //format: 'jsonTSVExtended',
- format: 'jsonTSV',
- scope: this,
- successCallback: this.onGetContent,
- failureCallback: EHR.Utils.onError
- });
- },
- onGetContent: function (content, format){
- if (!content)
- {
- Ext.Msg.hide();
- Ext.Msg.alert("Upload Failed", "The data file has no content");
- return;
- }
- if (!content.sheets || content.sheets.length == 0)
- {
- // expected the data file to be parsed as jsonTSV
- Ext.Msg.hide();
- Ext.Msg.alert("Upload Failed", "The data file has no sheets of data");
- return;
- }
-
- // User 1st sheet unless there's a sheet named "Data"
- var sheet = content.sheets[0];
- for (var index = 0; index < content.sheets.length; index++)
- {
- if (content.sheets[index].name == "Data")
- sheet = content.sheets[index];
- }
-
- var data = sheet.data;
- if (!data.length)
- {
- Ext.Msg.hide();
- Ext.Msg.alert("Upload Failed", "The data file contains no rows");
- return;
- }
-
- Ext.Msg.hide();
- this.processData(data);
- },
- processData: function(data){
- var skippedRows = [];
- var runsStore = Ext.StoreMgr.get("study||Clinpath Runs||||");
- var unitStore = Ext.StoreMgr.get("ehr_lookups||hematology_tests||testid||testid");
-
- var result;
- var tests;
- var row1;
- var row2;
- var toAdd = [];
-
- if(!data.length || !data[0].length){
- alert('Something went wrong processing the file');
- console.log(data)
- return;
- }
-
- data = data[0][0].split(/D1U/i);
-
- Ext.each(data, function(row, idx){
- if(!row.match(/D2U/i))
- return;
-
- row = row.split(/D2U/i);
-
- row1 = row[0];
- row2 = row[1];
- row1 = row1.replace(/\s+/g, '');
- row2 = row2.split(/\s+/);
- row2 = row2.slice(2, row2.length-1);
- row2 = row2.join('');
-
- result = {};
- tests = {};
-
- //result.animalId = row1[2].substr(0,6);
- result.animalId = row1.substr(27,6);
- result.animalId = result.animalId.toLowerCase();
-
- var requestNumber = runsStore.find('Id',result.animalId)
- var record = runsStore.getAt(requestNumber);
-
- //Getting the collection time from the request itself, if it matches animalId
- if(requestNumber!= -1 && result.animalId == record.get('Id')){
-
- var collectionDate = record.get('date');
- }
-
- //result.sequenceNo = row1[1].substr(20,4);
- //result.date = new Date(row1[2].substr(6,4), row1[2].substr(10,2)-1, row1[2].substr(12,2));
- //result.date = new Date(row1.substr(33,4), row1.substr(37,2)-1, row1.substr(39,2));
- result.date= new Date(collectionDate);
-
- if(!result.animalId || runsStore.find('Id', result.animalId)==-1){
- //alert('ID: '+result.animalId+' not found in Clinpath Runs section. Records will not be added');
- skippedRows.push('Not found in Clinpath Runs: '+result.animalId);
- return;
- }
-
- tests['WBC'] = row2.substr(6,6);
- tests['RBC'] = row2.substr(12,5);
- tests['HGB'] = row2.substr(17,5);
- tests['HCT'] = row2.substr(22,5);
- tests['MCV'] = row2.substr(27,5);
- tests['MCH'] = row2.substr(32,5);
- tests['MCHC'] = row2.substr(37,5);
- tests['PLT'] = row2.substr(42,5);
- //tests['LYMPH%'] = row2.substr(47,5);
- tests['LY'] = row2.substr(47,5);
-
- //tests['MONO%'] = row2.substr(52,5);
- tests['MN'] = row2.substr(52,5);
-
- //tests['SEG%'] = row2.substr(57,5);
- tests['NE'] = row2.substr(57,5);
-
- //tests['EOSIN%'] = row2.substr(62,5);
- tests['EO'] = row2.substr(62,5);
-
- //tests['BASO%'] = row2.substr(67,5);
- tests['BS'] = row2.substr(67,5);
-
- //tests['LYMPH#'] = row2.substr(72,6);
- //tests['MONO#'] = row2.substr(78,6);
- //tests['SEG#'] = row2.substr(84,6);
- //tests['EOSIN#'] = row2.substr(90,6);
- //tests['BASO#'] = row2.substr(96,6);
- tests['RDW'] = row2.substr(102,5);
- //tests'RDW-CV'] = row2.substr(102,5);
- //tests['RDW-SD'] = row2.substr(107,5);
- //tests['PDW'] = row2.substr(112,5);
- tests['MPV'] = row2.substr(117,5);
- //tests['P-LCR'] = row2.substr(122,5);
-
- var value;
- for(var test in tests){
- var origVal = tests[test];
- value = tests[test];
-
- if (value.match(/^00(\d){4}$/)) {
- tests[test] = value.substr(2,3) / 100;
- }
- //note: at the moment WBC is the only test with 6 chars, so this test is possibly redundant
- else if (value.match(/^0(\d){4,}$/) && test=='WBC') {
- tests[test] = value.substr(1,4) / 100;
- }
- else if (value.match(/^0\d{4}$/)){
- if (test=='RBC') {
- tests[test] = value.substr(1,3) / 100;
- }
- else if (test=='PLT') {
- tests[test] = value.substr(1,3) / 1; //convert to number
- }
- else {
- tests[test] = value.substr(1,3) / 10;
- }
- }
- else if (test=='PLT') {
- tests[test] = value.substr(0,4);
- }
-
- //NOTE: the following is a possible replacement for the logic above
- //it attempts to more clearly define how the parsing works
- //so far as i can tell, specific tests return different sets of decimals
- //and there is no clear way to determine decimal number without knowing the test name
-// if(value.match(/^(\d){5,6}$/)){
-// //we drop the last digit in all cases
-// value = value.substr(0, value.length-1);
-//
-// var decimals = 1;
-// //WBC is output as 10^1/uL, but reported at 10^3/ul
-// //RBC is output as 10^4/ul, but reported at 10^6/ul
-// if(test=='WBC' || test=='RBC')
-// decimals = 2;
-// if(test=='PLT')
-// decimals = 0;
-//
-// value = value / Math.pow(10, decimals);
-//
-// value = Ext4.util.Format.round(value, decimals);
-// }
-// else {
-// //alert('Value: '+value+' is not a number');
-// return;
-// }
-//
-// if(value != tests[test]){
-// console.log('error: '+test+'/'+tests[test]+'/'+value+'/'+decimals);
-// }
-
- //find units
- var idx = unitStore.find('testid', test);
- var units = null;
- var sortOrder = null;
- if(idx!=-1){
- units = unitStore.getAt(idx).get('units');
- sortOrder = unitStore.getAt(idx).get('sort_order');
- }
-
- if(tests[test] && isNaN(tests[test])){
- skippedRows.push('Invalid Result for: '+result.animalId+', TestId: '+test+', '+tests[test]);
- tests[test] = null;
- }
-
- toAdd.push({
- Id: result.animalId,
- date: result.date,
- testid: test,
- result: tests[test],
- units: units,
- sortOrder: sortOrder
- });
- }
-
- }, this);
-
- if(toAdd.length){
- toAdd.sort(function(a, b){
- return a.Id < b.Id ? -1 :
- a.Id > b.Id ? 1 :
- a.date < b.date ? -1 :
- a.date > b.date ? 1 :
- a.sortOrder < b.sortOrder ? -1 :
- a.sortOrder > b.sortOrder ? 1 :
- 0
- });
- this.targetStore.addRecords(toAdd);
- }
-
- if(skippedRows.length){
- alert('One or more rows were skipped:\n'+skippedRows.join('\n'));
- }
- }
-});
-Ext.reg('ehr-hematologyexcelwin', EHR.ext.HematologyExcelWin);
-
-
-/**
- * @class
- * This window will allow users to query the treatment schedule and add records to a task based on the scheduled treatments
- * that match their criteria. It is connected to the 'Add Treatments' button in the treatments form.
- */
-EHR.ext.TreatmentSelector = function(config){
- EHR.ext.TreatmentSelector.superclass.constructor.call(this, config);
-};
-Ext.extend(EHR.ext.TreatmentSelector, Ext.Panel, {
- initComponent: function()
- {
- Ext.applyIf(this, {
- layout: 'form'
- ,title: 'Import Scheduled Treatments'
- ,bodyBorder: true
- ,border: true
- //,frame: true
- ,bodyStyle: 'padding:5px'
- ,width: 350
- ,defaults: {
- width: 200,
- border: false,
- bodyBorder: false
- }
- ,items: [{
- xtype: 'datefield',
- fieldLabel: 'Date',
- value: (new Date()),
- hidden: !EHR.Security.hasPermission('Completed', 'update', {queryName: 'Blood Draws', schemaName: 'study'}),
- maxValue: (new Date()),
- ref: 'dateField'
- },{
- emptyText:''
- ,fieldLabel: 'Room'
- ,ref: 'roomField'
- ,xtype: 'textfield'
- ,listeners: {
- render: function(field){
- field.el.set({autocomplete: 'off'});
- },
- change: function(field, room){
- if(room){
- room = room.replace(/[\s,;]+/g, ';');
- room = room.replace(/(^;|;$)/g, '');
- room = room.toLowerCase();
- field.setValue(room);
- }
- }
- }
- },{
- emptyText:''
- ,fieldLabel: 'Time of Day'
- ,ref: 'timeField'
- ,xtype: 'lovcombo'
- ,separator: ';'
- ,displayField:'time'
- ,valueField: 'time'
- ,typeAhead: true
- ,mode: 'local'
- ,triggerAction: 'all'
- ,editable: true
- ,store: new Ext.data.ArrayStore({
- fields: [
- 'time'
- ],
- idIndex: 0,
- data: [
- ['AM'],
- ['Any Time'],
- ['Noon'],
- ['PM'],
- ['Night']
- ]
- })
- }],
- buttons: [{
- text:'Submit',
- disabled:false,
- ref: '../submit',
- scope: this,
- handler: function(s){
- this.getTreatments();
- }
- },{
- text: 'Close',
- scope: this,
- handler: function(){
- this.ownerCt.hide();
- }
- }]
- });
-
- EHR.ext.TreatmentSelector.superclass.initComponent.call(this, arguments);
- },
-
- getFilterArray: function(button)
- {
- var room = (this.roomField ? this.roomField.getValue() : null);
- var area = (this.areaField ? this.areaField.getValue() : null);
- var time = (this.timeField ? this.timeField.getValue() : null);
- var date = (this.dateField ? this.dateField.getValue() : new Date());
-
- if (!room || !time.length)
- {
- alert('Must provide room and time of day');
- return;
- }
-
- var filterArray = [];
-
- filterArray.push(LABKEY.Filter.create('date', date, LABKEY.Filter.Types.DATE_EQUAL));
- filterArray.push(LABKEY.Filter.create('treatmentStatus', '', LABKEY.Filter.Types.ISBLANK));
-
- if (area)
- filterArray.push(LABKEY.Filter.create('CurrentRoom/area', area, LABKEY.Filter.Types.EQUAL));
-
- if (room)
- filterArray.push(LABKEY.Filter.create('CurrentRoom', room, LABKEY.Filter.Types.EQUALS_ONE_OF));
-
- if (time)
- filterArray.push(LABKEY.Filter.create('TimeOfDay', time, LABKEY.Filter.Types.EQUALS_ONE_OF));
-
- return filterArray;
- },
-
- getTreatments: function(button)
- {
- var filterArray = this.getFilterArray();
- if (!filterArray || !filterArray.length)
- {
- return;
- }
-
- Ext.Msg.wait("Loading...");
- this.ownerCt.hide();
-
- //find distinct animals matching criteria
- LABKEY.Query.selectRows({
- schemaName: 'study',
- queryName: 'treatmentSchedule',
- sort: 'date,CurrentRoom,CurrentCage,Id',
- columns: 'primaryKey,lsid,Id,date,CurrentRoom,CurrentCage,project,project.account,meaning,code,qualifier,route,concentration,conc_units,amount,amount_units,dosage,dosage_units,volume,vol_units,remark',
- filterArray: filterArray,
- scope: this,
- success: this.onSuccess,
- failure: EHR.Utils.onError
- });
-
- },
- onSuccess: function(results){
- if (!results.rows || !results.rows.length)
- {
- alert('No uncompleted treatments were found.');
- Ext.Msg.hide();
- return;
- }
-
- var ids = {};
- var records = [];
- var obj;
- var date;
- Ext.each(results.rows, function(row)
- {
- row.date = new Date(row.date);
- var date = new Date();
-
- //if retroactively entering, we take the record's date. otherwise we use the current time
- if(row.date.getDate()!=date.getDate() || row.date.getMonth()!=date.getMonth() || row.date.getFullYear()!=date.getFullYear())
- date = row.date;
-
- records.push({
- Id: row.Id,
- 'id/curlocation/location': row.CurrentRoom+'-'+row.CurrentCage,
- date: date,
- project: row.project,
- account: row.account,
- code: row.code,
- qualifier: row.qualifier,
- route: row.route,
- concentration: row.concentration,
- conc_units: row.conc_units,
- amount: row.amount,
- amount_units: row.amount_units,
- volume: row.volume,
- vol_units: row.vol_units,
- dosage: row.dosage,
- dosage_units: row.dosage_units,
- parentid: row.primaryKey,
- performedby: null,
- remark: row.remark,
- category: 'Treatments'
- });
- }, this);
-
- if (this.targetStore){
- this.targetStore.addRecords(records);
- }
-
- Ext.Msg.hide();
- }
-
-});
-Ext.reg('ehr-treatmentselector', EHR.ext.TreatmentSelector);
-
-/**
- * This should probably be deprecated. It was originally created to supplement EHR.ext.TreatmentSelector
- * by allowing admin users to pick a date (the normal one will only query treatments on the current date.
- * This was necessary to allow users to retroactively mark treatments as complete. However, EHR.ext.TreatmentSelector
- * was modified to conditionally show the date field, which is probably a better solution.
- */
-EHR.ext.TreatmentSelector2 = Ext.extend(EHR.ext.TreatmentSelector, {
- initComponent: function()
- {
- EHR.ext.TreatmentSelector2.superclass.initComponent.call(this, arguments);
- this.dateField.setVisible(true);
- }
-});
-Ext.reg('ehr-treatmentselector2', EHR.ext.TreatmentSelector2);
-
-
-/*
- * @class
- * This provides the UI to copy records from a previous necropsy into the current one. The users selects which dataset(s) to copy from
- * The code then selects all records from these datasets. It will only copy the fields specified in .onMultiLoad(), since it would
- * not make sense to copy Id/Date/Caseno, etc. It is connected to the 'Copy From Necropsy' button in the necropsy form.
- */
-EHR.ext.NecropsyCopyPanel = Ext.extend(Ext.Panel, {
- initComponent: function()
- {
- Ext.apply(this, {
- layout: 'form'
- //,title: 'Copy Necropsy'
- ,autoHeight: true
- ,bodyBorder: true
- ,border: true
- //,frame: true
- ,bodyStyle: 'padding:5px'
- ,width: 350
- ,defaults: {
- width: 200,
- border: false,
- bodyBorder: false
- }
- ,items: [{
- xtype: 'textfield',
- fieldLabel: 'Case No',
- ref: 'caseField'
- },{
- fieldLabel: 'Include Organ Weights?'
- ,ref: 'Organ Weights Field'
- ,xtype: 'checkbox'
- ,checked: true
- },{
- fieldLabel: 'Include Tissues?'
- ,ref: 'Tissue Samples Field'
- ,xtype: 'checkbox'
- ,checked: true
- },{
- fieldLabel: 'Include Histology?'
- ,ref: 'Histology Field'
- ,xtype: 'checkbox'
- ,checked: true
- }],
- buttons: [{
- text:'Submit',
- disabled:false,
- ref: '../submit',
- scope: this,
- handler: function(s){
- this.doQuery();
- }
- },{
- text: 'Close',
- scope: this,
- handler: function(){
- this.ownerCt.hide();
- }
- }]
- });
-
- EHR.ext.NecropsyCopyPanel.superclass.initComponent.call(this, arguments);
- },
-
- doQuery: function(){
- var caseno = this.caseField.getValue();
- if(!caseno){
- alert('Must enter case number');
- return;
- }
-
- caseno = caseno.toLowerCase();
-
- Ext.Msg.wait('Loading...');
- this.ownerCt.hide();
-
- LABKEY.Query.selectRows({
- schemaName: 'study',
- queryName: 'Necropsies',
- columns: 'objectid',
- filterArray: [
- LABKEY.Filter.create('caseno', caseno, LABKEY.Filter.Types.EQUAL)
- ],
- scope: this,
- success: this.onLoadNecropsy,
- failure: EHR.Utils.onError
- });
-
- },
- onLoadNecropsy: function(data){
- if(!data || !data.rows.length){
- Ext.Msg.hide();
- alert('Unable to find necropsy');
- return;
- }
-
- this.necropsyObjectId = data.rows[0].objectid;
-
- this.multi = new LABKEY.MultiRequest();
- this.requestedDatasets = [];
- Ext.each(['Histology', 'Tissue Samples', 'Organ Weights'], this.addDatasetQuery, this);
- this.multi.send(this.onMultiLoad, this);
-
-
- },
- addDatasetQuery: function(queryName){
- if(this[queryName+' Field'].checked){
- this.requestedDatasets.push(queryName);
- this.multi.add(LABKEY.Query.selectRows, {
- schemaName: 'study',
- queryName: queryName,
- filterArray: [
- LABKEY.Filter.create('parentid', this.necropsyObjectId, LABKEY.Filter.Types.EQUAL)
- ],
- scope: this,
- success: function(results){
- this[queryName+'Results'] = results;
- },
- failure: EHR.Utils.onError
- });
- }
- },
- onMultiLoad: function(){
- Ext.each(this.requestedDatasets, function(queryName){
- var results = this[queryName+'Results'];
- if(!results){
- alert('No records found for ')
- }
- else {
- var records = [];
- Ext.each(results.rows, function(row)
- {
- records.push({
- tissue: row.tissue,
- qualifier: row.qualifier,
- remark: row.remark,
-
- //tissues
- preservation: row.preservation,
- quantity: row.quantity,
- recipient: row.recipient,
-
- //histology
- slideNum: row.slideNum,
- stain: row.stain
- });
- }, this);
-
- if (records.length){
- var store = this.targetStore.get('study||'+queryName+'||||');
- if(store)
- store.addRecords(records);
- }
- }
- }, this);
-
- Ext.Msg.hide();
- }
-});
-Ext.reg('ehr-necropsycopy', EHR.ext.NecropsyCopyPanel);
-
-
-/**
- * @class
- * This is designed to be generic UI to sort a store client-side, based on multiple fields. At time of writing, this is
- * only used in necropsy and biopsy for the purpose of sorting histology, tissues, etc. However, it could in theory be added
- * as an option to any EHR form section.
- */
-EHR.ext.StoreSorterPanel = Ext.extend(Ext.Panel, {
- initComponent: function()
- {
- var storeData = [];
- this.targetStore.fields.each(function(field){
- if(!field.isHidden)
- storeData.push([field.name, field.fieldLabel]);
- }, this);
-
- this.sortFields = new Ext.data.ArrayStore({
- fields: [
- 'name',
- 'label'
- ],
- idIndex: 0,
- data: storeData
- });
-
- Ext.apply(this, {
- layout: 'form'
- ,ref: 'theForm'
- ,autoHeight: true
- ,bodyBorder: true
- ,border: true
- //,frame: true
- ,bodyStyle: 'padding:5px'
- //,width: 250
- ,defaults: {
- width: 200,
- border: false,
- bodyBorder: false
- }
- ,items: [{
- xtype: 'combo'
- ,emptyText:''
- ,fieldLabel: 'Sort 1'
- ,displayField:'label'
- ,valueField: 'name'
- ,typeAhead: true
- ,editable: true
- ,mode: 'local'
- ,triggerAction: 'all'
- ,store: this.sortFields
- ,ref: 'sortField'
- },{
- xtype: 'combo'
- ,emptyText:''
- ,fieldLabel: 'Sort 2'
- ,displayField:'label'
- ,valueField: 'name'
- ,typeAhead: true
- ,editable: true
- ,mode: 'local'
- ,triggerAction: 'all'
- ,store: this.sortFields
- ,ref: 'sortField2'
- },{
- xtype: 'combo'
- ,emptyText:''
- ,fieldLabel: 'Sort 3'
- ,displayField:'label'
- ,valueField: 'name'
- ,typeAhead: true
- ,editable: true
- ,mode: 'local'
- ,triggerAction: 'all'
- ,store: this.sortFields
- ,ref: 'sortField3'
- }],
- buttons: [{
- text:'Submit',
- disabled:false,
- ref: '../submit',
- scope: this,
- handler: function(s){
- this.doSort();
- }
- },{
- text: 'Close',
- scope: this,
- handler: function(){
- this.ownerCt.hide();
- }
- }]
- });
-
- EHR.ext.StoreSorterPanel.superclass.initComponent.call(this);
- },
- doSort: function(){
- var field1 = this.ownerCt.theForm.sortField.getValue();
- var field2 = this.ownerCt.theForm.sortField2.getValue();
- var field3 = this.ownerCt.theForm.sortField3.getValue();
-
- if(!field1){
- alert('Must pick a field');
- return;
- }
-
- this.ownerCt.hide();
-
- field1 = this.targetStore.fields.get(field1);
- field2 = this.targetStore.fields.get(field2);
- field3 = this.targetStore.fields.get(field3);
-
- var sortArray = [];
-
- sortArray.push({term: field1.dataIndex});
- if(field1.lookup){
- sortArray[0].storeId = LABKEY.ext.FormHelper.getLookupStoreId(field1);
- sortArray[0].displayField = field1.lookup.displayColumn;
- sortArray[0].valueField = field1.lookup.keyColumn;
- }
-
- if(field2){
- sortArray.push({term: field2.dataIndex});
- if(field2.lookup){
- sortArray[1].storeId = LABKEY.ext.FormHelper.getLookupStoreId(field2);
- sortArray[1].displayField = field2.lookup.displayColumn;
- sortArray[1].valueField = field2.lookup.keyColumn;
- }
- }
-
- if(field3){
- sortArray.push({term: field3.dataIndex});
- if(field3.lookup){
- sortArray[2].storeId = LABKEY.ext.FormHelper.getLookupStoreId(field3);
- sortArray[2].displayField = field3.lookup.displayColumn;
- sortArray[2].valueField = field3.lookup.keyColumn;
- }
- }
-
- this.targetStore.data.sort('ASC', EHR.Utils.sortStore(sortArray));
- this.targetStore.fireEvent('datachanged', this.targetStore);
-
- }
-});
-Ext.reg('ehr-storesorterpanel', EHR.ext.StoreSorterPanel);
-
-
-/**
- * @class
- * This provides the UI which allows the user to import scheduled blood draws to the current task. Note that this works slightly
- * differently than adding scheduled treatments. For blood, adding a request creates an actual record in study.blood draws. This request
- * does not initiall have a taskId. When the user attempts to add scheduled blood to a task, it will query all blood records
- * that are approved requests, but lack a taskId (aka not already assigned to a different task). It will update these records with the taskId of the
- * current task, and also change the date to reflect the current time. It then reloads the active blood draws store, which will cause it to
- * load these records (because we just updated their taskId field).
- */
-EHR.ext.BloodSelectorPanel = Ext.extend(Ext.Panel, {
- initComponent: function()
- {
- Ext.applyIf(this, {
- layout: 'form'
- ,title: 'Import Scheduled Blood Draws'
- ,bodyBorder: true
- ,border: true
- //,frame: true
- ,bodyStyle: 'padding:5px'
- ,width: 350
- ,defaults: {
- width: 200,
- border: false,
- bodyBorder: false
- }
- ,items: [{
- xtype: 'datefield'
- ,fieldLabel: 'Date'
- ,value: new Date()
- ,hidden: !EHR.Security.hasPermission('Completed', 'update', {queryName: 'Blood Draws', schemaName: 'study'})
- ,ref: 'dateField'
- },{
- xtype: 'combo'
- ,emptyText:''
- ,fieldLabel: 'Area (optional)'
- ,displayField:'area'
- ,valueField: 'area'
- ,typeAhead: true
- ,editable: true
- ,triggerAction: 'all'
- ,store: new LABKEY.ext.Store({
- schemaName: 'ehr_lookups',
- queryName: 'areas',
- sort: 'area',
- autoLoad: true
- }),
- ref: 'areaField'
- },{
- emptyText:''
- ,fieldLabel: 'Room'
- ,ref: 'roomField'
- ,xtype: 'textfield'
- ,listeners: {
- render: function(field){
- field.el.set({autocomplete: 'off'});
- },
- change: function(field, room){
- if(room){
- room = room.replace(/[\s,;]+/g, ';');
- room = room.replace(/(^;|;$)/g, '');
- room = room.toLowerCase();
- field.setValue(room);
- }
- }
- }
- },{
- xtype: 'textfield'
- ,ref: 'idField'
- ,fieldLabel: 'Id (optional)'
- },{
- emptyText:''
- ,fieldLabel: 'Assigned To'
- ,ref: 'billedbyField'
- ,xtype: 'combo'
- ,displayField:'description'
- ,valueField: 'value'
- ,typeAhead: true
- ,mode: 'local'
- ,triggerAction: 'all'
- ,editable: true
- ,value: 'a'
- ,store: new LABKEY.ext.Store({
- schemaName: 'ehr_lookups',
- queryName: 'blood_billed_by',
- sort: 'description',
- autoLoad: true
- })
- }],
- buttons: [{
- text:'Submit',
- disabled:false,
- ref: '../submit',
- scope: this,
- handler: function(s){
- this.getRecords();
- }
- },{
- text: 'Close',
- scope: this,
- handler: function(){
- this.ownerCt.hide();
- }
- }]
- });
-
- EHR.ext.BloodSelectorPanel.superclass.initComponent.call(this, arguments);
- },
-
- getFilterArray: function(button)
- {
- var room = (this.roomField ? this.roomField.getValue() : null);
- var area = (this.areaField ? this.areaField.getValue() : null);
- var billedby = (this.billedbyField ? this.billedbyField.getValue() : null);
- var Id = (this.idField ? this.idField.getValue() : null);
-
- if ((!room && !area && !Id) || !billedby)
- {
- alert('Must provide a room, area or Id and complete the \'assigned to\' field');
- return;
- }
-
- var filterArray = [];
-
- filterArray.push(LABKEY.Filter.create('date', this.dateField.getValue(), LABKEY.Filter.Types.DATE_EQUAL));
-
- filterArray.push(LABKEY.Filter.create('taskid', null, LABKEY.Filter.Types.ISBLANK));
- filterArray.push(LABKEY.Filter.create('drawStatus', 'Pending', LABKEY.Filter.Types.EQUAL));
-
- if (area)
- filterArray.push(LABKEY.Filter.create('Id/curLocation/area', area, LABKEY.Filter.Types.EQUAL));
-
- if (room)
- filterArray.push(LABKEY.Filter.create('Id/curLocation/room', room, LABKEY.Filter.Types.EQUALS_ONE_OF));
-
- if(billedby)
- filterArray.push(LABKEY.Filter.create('billedby', billedby, LABKEY.Filter.Types.EQUALS_ONE_OF));
-
- if(Id)
- filterArray.push(LABKEY.Filter.create('Id', Id, LABKEY.Filter.Types.EQUALS_ONE_OF));
-
- return filterArray;
- },
-
- getRecords: function(button)
- {
- var filterArray = this.getFilterArray();
- if (!filterArray || !filterArray.length)
- {
- return;
- }
-
- Ext.Msg.wait("Loading...");
- this.ownerCt.hide();
-
- //find distinct animals matching criteria
- LABKEY.Query.selectRows({
- schemaName: 'study',
- queryName: 'BloodSchedule',
- sort: 'Id/curLocation/Room,Id',
- columns: 'lsid,Id,date',
- filterArray: filterArray,
- scope: this,
- success: this.onSuccess,
- failure: EHR.Utils.onError
- });
-
- },
- onSuccess: function(results){
- if (!results.rows || !results.rows.length)
- {
- alert('No uncompleted blood draws were found.');
- Ext.Msg.hide();
- return;
- }
-
- var ids = {};
- var records = [];
- var obj;
- var dateVal = new Date();
- Ext.each(results.rows, function(row){
- obj = {
- lsid: row.lsid,
- taskid: this.parentPanel.formUUID,
- date: dateVal
- };
-
- if(EHR.Security.getQCStateByLabel('In Progress')){
- obj.qcState = EHR.Security.getQCStateByLabel('In Progress').RowId;
- }
-
- records.push(obj);
- }, this);
-
- if (this.targetStore){
- //we save the task to be sure there is a record of it
- var store = Ext.StoreMgr.get('ehr||tasks||||');
- var record = store.getAt(0);
- if(record.phantom){
- Ext.Msg.wait('Saving...');
- store.on('commitcomplete', function(c){
- doUpdate(this.targetStore);
- }, this, {single: true});
- store.commitRecords([record]);
- }
- else {
- doUpdate(this.targetStore);
- }
-
- function doUpdate(targetStore){
- LABKEY.Query.updateRows({
- schemaName: 'study',
- queryName: 'Blood Draws',
- scope: this,
- rows: records,
- success: function(data){
- targetStore.load();
- Ext.Msg.hide();
- },
- failure: EHR.Utils.onError
- });
- }
- }
- }
-
-});
-Ext.reg('ehr-bloodselector', EHR.ext.BloodSelectorPanel);
-
-
-/**
- * @class
- * This is a subclass of EHR.ext.FormPanel designed to be used by the top-most section of TaskPanels. The primary purpose was to allow distinct
- * buttons to be present in this form secion; however, many of these buttons have been disabled in favor of other mechanisms.
- */
-EHR.ext.HeaderFormPanel = Ext.extend(EHR.ext.FormPanel, {
- initComponent: function()
- {
- Ext.apply(this, {
- autoHeight: true
- //,autoWidth: true
- ,name: 'tasks'
- ,bodyBorder: false
- ,border: false
- ,bodyStyle: 'padding:5px'
- ,style: 'margin-bottom: 15px'
- ,plugins: ['databind']
- ,readOnly: false
- ,buttonAlign: 'left'
- ,tbar: {hidden: true}
-// ,buttons: [{
-// xtype: 'button',
-// text: 'Apply Template',
-// scope: this,
-// disabled: !this.canUseTemplates===false,
-// handler: this.applyTemplate
-// },{
-// xtype: 'button',
-// text: 'Save As Template',
-// scope: this,
-// disabled: !this.canUseTemplates===false,
-// handler: this.saveTemplate
-// },{
-// xtype: 'button',
-// text: 'Print Form',
-// scope: this,
-// handler: this.printFormHandler
-// }]
- });
-
- EHR.Utils.rApplyIf(this, {
- bindConfig: {
- disableUnlessBound: false
- ,bindOnChange: false
- ,autoBindRecord: true
- ,createRecordOnLoad: true
- ,showDeleteBtn: false
- }
- ,ref: 'importPanelHeader'
- ,defaults: {
- width: 160,
- border: false,
- bodyBorder: false,
- importPanel: this.importPanel || this
- }
- });
-
- EHR.ext.HeaderFormPanel.superclass.initComponent.call(this, arguments);
-
- },
- saveTemplate: function(){
- var theWindow = new EHR.ext.SaveTemplatePanel({
- width: 600,
- height: 300,
- importPanel: this.importPanel,
- formType: this.formType
- });
- theWindow.show();
- },
-
- applyTemplate: function(){
- var theWindow = new Ext.Window({
- closeAction:'hide',
- title: 'Apply Template To Form',
- width: 350,
- items: [{
- xtype: 'ehr-applytemplatepanel',
- ref: 'theForm',
- importPanel: this.importPanel,
- formType: this.formType
- }]
- });
- theWindow.show();
- },
- printFormHandler: function(){
- window.location = LABKEY.ActionURL.buildURL(
- 'ehr',
- 'printTask.view',
- (this.containerPath || LABKEY.ActionURL.getContainer()),
- {
- taskid: this.formUUID,
- formtype: this.formType,
- _print: 1
- }
- );
- }
-});
-Ext.reg('ehr-headerformpanel', EHR.ext.HeaderFormPanel);
-
-
-/**
- * @class
- * This panel is designed to display the animal's abstract in an ImportPanel. It listens for the participantchange event on the bound
- * ImportPanel and will fire participantloaded when the participant's information is available.
- *
- */
-EHR.ext.AbstractPanel = function(config){
- EHR.ext.AbstractPanel.superclass.constructor.call(this, config);
-};
-Ext.extend(EHR.ext.AbstractPanel, Ext.FormPanel, {
- initComponent: function()
- {
- var panelDefaults = {
- border: false,
- bodyBorder: false,
- labelStyle: 'padding: 0px;'
- };
-
- Ext.apply(this, {
- name: 'abstract'
- ,title: 'Animal Info'
- ,labelWidth: 120
- ,autoHeight: true
- ,border: true
- ,bodyBorder: false
- ,participantMap: new Ext.util.MixedCollection(null, function(f){return f.Id})
- ,defaults: panelDefaults
- ,defaultType: 'displayfield'
- ,style: 'margin-bottom: 15px'
- ,bodyStyle: 'padding:5px'
- ,ref: '../../abstract'
- ,items: [{
- xtype: 'panel',
- layout: 'form',
- labelWidth: 170,
- defaults: panelDefaults,
- ref: 'placeForAbstract',
- //275 is the height of the Clinical Summary view
- height: this.boxMinHeight || 300,
- items: [{html: 'No Animal Selected'}]
- },{
- xtype: 'panel',
- defaults: panelDefaults,
- border: false,
- bodyBorder: false,
- items: [{tag: 'div', ref: '../placeForQwp'}]
- }]
- });
- EHR.ext.AbstractPanel.superclass.initComponent.call(this, arguments);
-
- this.addEvents('participantchange', 'participantloaded');
- this.enableBubble('participantloaded');
-
- if (this.importPanel){
- this.mon(this.importPanel, 'participantchange', this.onParticipantChange, this, {delay: 50});
- this.importPanel.participantMap = this.participantMap;
- }
-
- },
- onParticipantChange: function(field)
- {
- field.participantMap = this.participantMap;
- this.participantField = field;
-
- var id = field.getValue();
-
- //no need to reload if ID is unchanged
- if (this.loadedId == id){
-// console.log('animal id is the same, no reload needed');
- return;
- }
-
- this.loadedId = id;
-
- this.placeForAbstract.removeAll();
- this.placeForQwp.body.update();
-
- if(!id){
- this.placeForAbstract.add({html: 'No Animal Selected'});
- this.doLayout();
- }
- else {
- this.participantMap.add(id, {loading: true});
-
- LABKEY.Query.selectRows({
- schemaName: this.schemaName || 'study',
- queryName: this.queryName || 'demographics',
- viewName: this.viewName || 'Clinical Summary',
- //columns: '',
- filterArray: [LABKEY.Filter.create('Id', id, LABKEY.Filter.Types.EQUAL)],
- scope: this,
- successCallback: function(data){
- this.renderAbstract(data, id, field)
- }
- });
-
- if(this.queryConfig){
- var qwpConfig = {
- allowChooseQuery: false,
- allowChooseView: true,
- showRecordSelectors: true,
- frame: 'none',
- showDeleteButton: false,
- timeout: 0,
- linkTarget: '_blank',
- renderTo: this.placeForQwp.body.id,
- scope: this,
- failure: EHR.Utils.onError
- };
- Ext.apply(qwpConfig, this.queryConfig);
-
- qwpConfig.filterArray = qwpConfig.filterArray || [];
- if(qwpConfig.filterColumn)
- qwpConfig.filterArray.push(LABKEY.Filter.create(qwpConfig.filterColumn, id, LABKEY.Filter.Types.EQUAL));
- else
- qwpConfig.filterArray.push(LABKEY.Filter.create('Id', id, LABKEY.Filter.Types.EQUAL));
-
- this.QWP = new LABKEY.QueryWebPart(qwpConfig);
- }
- }
- },
- renderAbstract: function(data, id, field)
- {
- this.placeForAbstract.removeAll();
- if (!data.rows.length)
- {
- this.participantMap.replace(id, {});
- this.placeForAbstract.add({html: 'Id not found: '+id});
- }
- else
- {
- var row = data.rows[0];
-// if (!this.allowDeadAnimals && row['calculated_status'] != 'Alive'){
-// alert('Animal: '+id+' is not currently alive and at the center');
-// }
-
- Ext.each(data.metaData.fields, function(c)
- {
- if (c.hidden)
- return false;
- var rawValue = row[c.name];
- if (Ext.isNumber(rawValue))
- {
- rawValue = Ext.util.Format.number(rawValue, "0.00");
- }
- var value = row['_labkeyurl_' + c.name] ? '' + rawValue + '' : rawValue;
- this.placeForAbstract.add({id: c.name, xtype: 'displayfield', fieldLabel: c.caption, value: value, submitValue: false});
- }, this);
-
- //this.loadedId = row['Id'];
- this.participantMap.replace(row['Id'], row);
- this.fireEvent('participantloaded', this, row['Id'], row);
- }
-
- this.doLayout();
- this.expand();
- }
-});
-Ext.reg('ehr-abstractpanel', EHR.ext.AbstractPanel);
-
-
-/**
- * @class
- * A subclass of EHR.ext.Abstract panel. Instead of simply displaying the abstract, this panel also displays information
- * about the selected protocol, including number assigned and distinct assignments by species
- */
-EHR.ext.AssignmentAbstractPanel = Ext.extend(EHR.ext.AbstractPanel, {
- initComponent: function(){
- EHR.ext.AssignmentAbstractPanel.superclass.initComponent.call(this, arguments);
-
- this.setTitle('Protocol Details');
-
- this.removeAll();
- this.add({
- xtype: 'panel',
- ref: 'placeForQwp',
- border: false,
- defaults: {border: false},
- items: [{
- html: 'No Protocol Selected'
- }]
- });
- },
- onParticipantChange: function(field){
- if(field){
- var theForm = field.findParentByType('ehr-formpanel').getForm();
- var projectField = theForm.findField('project');
-
- if(projectField){
- var proj = projectField.getValue();
- if(!proj){
- this.doUpdate();
- }
- else {
- var projectRec = projectField.store.find('project', proj);
- if(projectRec!=-1){
- projectRec = projectField.store.getAt(projectRec);
- this.doUpdate(projectRec.get('protocol'));
- }
- else {
- this.doUpdate();
- }
- }
- }
- }
- },
- doUpdate: function(protocol){
- if(this.loadedProtocol == protocol){
- return;
- }
-
- this.loadedProtocol = protocol;
-
- this.placeForQwp.removeAll();
- this.placeForQwp.body.update();
-
- if(protocol){
- this.QWP = new LABKEY.QueryWebPart({
- allowChooseQuery: false,
- allowChooseView: true,
- showRecordSelectors: true,
- frame: 'none',
- showDeleteButton: false,
- timeout: 0,
- linkTarget: '_blank',
- renderTo: this.placeForQwp.body.id,
- schemaName: 'ehr',
- queryName: 'protocolTotalAnimalsBySpecies',
- //viewName: 'With Animals',
- scope: this,
- failure: EHR.Utils.onError,
- filterArray: [LABKEY.Filter.create('protocol', protocol, LABKEY.Filter.Types.EQUAL)]
- });
- }
- else {
- this.placeForQwp.add({html: 'No Protocol Selected'});
- }
-
- this.doLayout();
- }
-});
-Ext.reg('ehr-assignmentabstractpanel', EHR.ext.AssignmentAbstractPanel);
-
-
-/**
- * @class
- * This is an ext panel designed to load a LABKEY QueryWebPart. The advantage of using this panel is that it will fit into a larger
- * Ext layout more easily and can be manipulated like a normal Ext container. In the EHR it is used primarily by the UI on the 'Enter Data'
- * or Manage Requests pages.
- */
-EHR.ext.QueryPanel = Ext.extend(Ext.Panel, {
- initComponent: function(){
- Ext.applyIf(this, {
- //width: 'auto',
- layout: 'fit',
- autoScroll: true,
- border: false,
- //headerStyle: 'background-color : transparent;background : transparent;',
- frame: false,
- autoHeight: true,
- listeners: {
- scope: this,
- activate: this.loadQuery,
- click: this.loadQuery
- },
- bodyStyle: 'padding:5px;'
- });
-
- EHR.ext.QueryPanel.superclass.initComponent.call(this, arguments);
-
- if(this.autoLoadQuery){
- this.on('render', this.loadQuery, this, {single: true});
- }
- },
- loadQuery: function(tab){
- tab = tab || this;
-
- if(tab.isLoaded)
- return;
-
- if(!tab.rendered){
- this.on('render', this.loadQuery, this, {single: true});
- return;
- }
-
- var target = tab.body;
- var qwpConfig = {
- schemaName: tab.schemaName,
- queryName: tab.queryName,
- filters: tab.filterArray,
- allowChooseQuery: false,
- allowChooseView: true,
- showRecordSelectors: true,
- showDetailsColumn: false,
-// showUpdateColumn: false,
- frame: 'none',
- showDeleteButton: false,
- timeout: 0,
- linkTarget: '_blank',
- renderTo: target.id,
- failure: EHR.Utils.onError,
- success: function(result){
- tab.isLoaded = true;
- },
- scope: this
- };
- Ext.apply(qwpConfig, tab.queryConfig);
-
- tab.QWP = new LABKEY.QueryWebPart(qwpConfig);
-
- }
-});
-Ext.reg('ehr-qwppanel', EHR.ext.QueryPanel);
-
-/**
- * @class
- * This provides the UI to duplicate one or more records from an EHR.ext.GridFormPanel. It allows the user to pick the number of copies
- * per record and which fields to copy
- */
-EHR.ext.RecordDuplicatorPanel = Ext.extend(Ext.FormPanel, {
- initComponent: function()
- {
- Ext.applyIf(this, {
- layout: 'form'
- ,bodyBorder: true
- ,border: true
- ,bodyStyle: 'padding:5px'
- ,defaults: {
- width: 200,
- border: false,
- bodyBorder: false
- }
- ,items: [{
- xtype: 'numberfield',
- fieldLabel: 'Number of Records',
- ref: 'newRecs',
- value: 1
- },{
- xtype: 'fieldset',
- fieldLabel: 'Choose Fields to Copy',
- items: []
- }]
- ,scope: this
- ,buttons: [{
- text:'Submit',
- disabled:false,
- ref: '../submit',
- scope: this,
- handler: function(s){
- this.duplicate();
- this.ownerCt.hide();
- }
- },{
- text: 'Close',
- scope: this,
- handler: function(){
- this.ownerCt.hide();
- }
- }]
- });
-
- this.populateForm();
-
- if(!this.records || !this.records.length){
- this.ownerCt.hide();
- }
- EHR.ext.RecordDuplicatorPanel.superclass.initComponent.call(this, arguments);
- },
-
- populateForm: function(){
- this.targetStore.fields.each(function(f){
- if (!f.hidden && f.shownInInsertView && f.allowDuplicateValue!==false){
- this.items[1].items.push({
- xtype: 'checkbox',
- dataIndex: f.dataIndex,
- name: f.dataIndex,
- fieldLabel: f.fieldLabel,
- checked: !f.noDuplicateByDefault
- })
- }
- }, this);
-
- },
-
- duplicate: function(){
- var newRec;
- for (var i=0;i 1){
- editor.xtype = 'displayfield';
- editor.store = null;
- editor.value = values.join('/');
- }
-
- toAdd.items.push(editor);
- }
- }, this);
-
- this.theWindow.theForm.add({
- bodyStyle: 'padding: 5px;',
- title: store.queryName,
- autoHeight: true,
- defaults: {
- border: false,
- bodyStyle: 'padding: 5px;'
- },
- items: [{
- html: ''+records.length+' Record'+(records.length==1 ? '' : 's')+' will be added. If you enter values below, these will be applied to all new records, overriding any saved values.'
- },
- toAdd
- ]
- });
- },
-
- loadTemplateData: function(toAdd){
- for (var i in toAdd){
- var store = Ext.StoreMgr.get(i);
- store.addRecords(toAdd[i])
- }
-
- Ext.Msg.hide();
- },
-
- onCustomize: function(){
- var toAdd = {};
- this.theWindow.theForm.items.each(function(tab){
- var values = tab.thePanel.getForm().getFieldValues(true);
- toAdd[tab.thePanel.storeId] = tab.thePanel.records;
- Ext.each(tab.thePanel.records, function(r){
- Ext.apply(r, values);
- }, this);
- }, this);
-
- this.loadTemplateData(toAdd);
- this.theWindow.hide();
- }
-});
-Ext.reg('ehr-applytemplatepanel', EHR.ext.ApplyTemplatePanel);
-
-
-/**
- * @class
- * This panel provides the UI which allows the user to save existing records in a Form or Form section as a template. It
- * gives the ability to choose which section(s) (ie. queries) should be saved and which field(s) per section to save.
- */
-EHR.ext.SaveTemplatePanel = Ext.extend(Ext.Window, {
- initComponent: function()
- {
- Ext.apply(this, {
- closeAction:'hide'
- ,title: 'Save As Template'
- ,xtype: 'panel'
- ,autoScroll: true
- ,autoHeight: true
- ,boxMaxHeight: 600
- ,defaults: {
- border: false
- ,bodyStyle: 'padding: 5px;'
- }
- ,items: [{
- layout: 'form',
- autoScroll: true,
- bodyStyle: 'padding: 5px;',
- monitorValid: true,
- defaults: {
- border: false
- },
- items: [{
- xtype: 'textfield',
- fieldLabel: 'Template Name',
- allowBlank: false,
- ref: '../templateName',
- listeners: {
- scope: this,
- change: function(f){
- this.buttons[0].setDisabled(!f.getValue())
- }
- }
- },{
- xtype: 'combo',
- displayField: 'DisplayName',
- valueField: 'UserId',
- triggerAction: 'all',
- mode: 'local',
- listWidth: 300,
- fieldLabel: 'User/Group',
- //value: 0,
- store: new LABKEY.ext.Store({
- schemaName: 'core',
- queryName: 'PrincipalsWithoutAdmin',
- columns: 'UserId,DisplayName',
- sort: 'Type,DisplayName',
- autoLoad: true
- }),
- ref: '../templateUser'
- },{
- xtype: 'displayfield',
- value: 'NOTE: You can choose a user or group in order to limit which users can see the template. Leave it blank to expose the template to everyone. Pick your own user to make it visible to you only. If you want to share it with a specific group (ie. vets or pathology), choose them from the list.'
- },{
- xtype: 'textarea',
- fieldLabel: 'Description',
- width: 300,
- ref: '../templateDescription'
- },{
- html: 'You can elect to save either all or some of the records in this form as a template. For each section, you can choose which fields to save.',
- style: 'padding-top:10px;'
- }]
- },{
- xtype: 'tabpanel',
- activeTab: 0,
- ref: 'theForm',
- autoScroll: true
- }]
- ,scope: this
- ,buttons: [{
- text:'Submit',
- scope: this,
- disabled: true,
- handler: function(s){
- this.onSubmit();
- this.hide();
- }
- },{
- text: 'Close',
- scope: this,
- handler: function(){
- this.hide();
- }
- }]
-
- });
-
- EHR.ext.SaveTemplatePanel.superclass.initComponent.call(this, arguments);
-
- //function differently depending on whether we're bound to a grid or import panel
- if(this.grid)
- this.targetStore = this.grid.store;
- else
- this.targetStore = this.importPanel.store;
-
- if(!this.targetStore)
- return;
- else if (this.targetStore instanceof EHR.ext.StoreCollection)
- this.populateFromStoreCollection();
- else if (this.targetStore instanceof LABKEY.ext.Store)
- this.populateFromStore();
-
- this.on('show', function(){
- this.templateName.focus(false, 50);
- }, this);
- },
- populateFromStoreCollection: function(){
- var hasRecords = false;
- this.targetStore.each(function(s){
- if(s.canSaveInTemplate === false)
- return;
-
- this.addStore(s);
- if(s.getCount())
- hasRecords = true;
- }, this);
-
- if(!hasRecords){
- this.on('beforeshow', function(){return false}, this, {single: true});
- this.hide();
- Ext.Msg.alert('Error', 'There are no records to save.');
- }
- },
- populateFromStore: function(){
- if(!this.targetStore.getCount()){
- this.on('beforeshow', function(){return false}, this, {single: true});
- this.hide();
- Ext.Msg.alert('Error', 'There are no records to save.');
- }
- else
- this.addStore(this.targetStore);
- },
- addStore: function(store){
- var count = store.getCount();
-
- if(!count){
- return
- }
- var panel = {
- xtype: 'panel',
- title: store.queryName + ': '+count+' Record' + (count==1 ? '' : 's'),
- border: false,
- style: 'padding-bottom:10px;',
- autoHeight: true,
- storeId: store.storeId,
- items: [{
- xtype: 'fieldset',
- title: 'Choose Records To Save',
- items: [{
- xtype: 'radiogroup',
- style: 'padding-bottom:10px;',
- //bodyStyle: 'padding: 5px;',
- ref: '../recordSelector',
- columns: 3,
- //width: 400,
- items: [{
- fieldLabel: 'Include All',
- inputValue: 'all',
- checked: true,
- name: store.storeId+'-radio'
- },{
- fieldLabel: 'Include None',
- inputValue: 'none',
- name: store.storeId+'-radio'
- }]
- }]
- }]
- };
-
- if(this.grid){
- panel.items[0].items[0].items.push({
- fieldLabel: 'Selected Only',
- inputValue: 'selected',
- name: store.storeId+'-radio'
- });
- }
-
- panel = this.theForm.add(panel);
-
- var toAdd = {
- xtype: 'checkboxgroup',
- ref: '../fieldSelector',
- name: store.storeId,
- columns: 3,
- items: []
- };
- store.fields.each(function(f){
- if(!f.hidden && f.shownInInsertView && f.allowSaveInTemplate!==false && f.allowDuplicate!==false){
- toAdd.items.push({
- xtype: 'checkbox',
- dataIndex: f.dataIndex,
- name: f.dataIndex,
- fieldLabel: f.fieldLabel || f.name,
- checked: !(f.noDuplicateByDefault || f.noSaveInTemplateByDefault)
- })
- }
- }, this);
-
- panel.add({
- xtype: 'fieldset',
- title: 'Choose Fields to Save',
- items: [toAdd]
- });
- },
- onSubmit: function(){
- this.hide();
- Ext.Msg.wait("Saving...");
-
- var tn = this.templateName.getValue();
- var rows = [];
-
- this.theForm.items.each(function(tab){
- var selections = tab.recordSelector.getValue().inputValue;
- var fields = tab.fieldSelector.getValue();
-
- if(!fields.length)
- return;
- if(selections == 'none')
- return;
-
- var store = Ext.StoreMgr.get(tab.storeId);
-
- var records = [];
- if(selections == 'selected'){
- records = this.grid.getSelectionModel().getSelections();
- if(!records.length){
- Ext.Msg.hide();
- Ext.Msg.alert('Error', 'No records were selected in the grid');
- }
- }
- else
- records = store.data.items;
-
- Ext.each(records, function(rec){
- var json = {};
- Ext.each(fields, function(chk){
- json[chk.dataIndex] = rec.get(chk.dataIndex);
- }, this);
-
- rows.push({
- templateId: null,
- storeId: store.storeId,
- json: Ext.util.JSON.encode(json),
- templateName: tn
- })
- }, this);
- }, this);
-
- if(!rows.length){
- Ext.Msg.hide();
- Ext.Msg.alert('Error', "No records selected");
- return;
- };
-
- this.saveTemplate(rows);
- },
-
- saveTemplate: function(rows){
- LABKEY.Query.insertRows({
- schemaName: 'ehr',
- queryName: 'formtemplates',
- scope: this,
- rows: [{
- title: this.templateName.getValue(),
- userid: this.templateUser.getValue(),
- description: this.templateDescription.getValue(),
- formType: this.formType
- }],
- success: function(rows){return function(data){
- Ext.each(rows, function(r){
- r.templateId = data.rows[0].entityid;
- }, this);
-
- LABKEY.Query.insertRows({
- schemaName: 'ehr',
- queryName: 'formTemplateRecords',
- rows: rows,
- failure: EHR.Utils.onError,
- success: function(){
- Ext.Msg.hide();
- }
- });
- }}(rows),
- failure: EHR.Utils.onError
- });
- }
-});
-Ext.reg('ehr-savetemplatepanel', EHR.ext.SaveTemplatePanel);
-
-
-/*
- * Although this has been commented out, this is designed to present a basic EHR.ext.Formpanel to the user in an Ext window. This
- * can be useful to allow fast insertion of records into a table. It was originally used to allow vets to enter clinical remarks;
- * however, this was removed in favor of all remarks going through the Task pathway. This code was note removed because the basic
- * mechanism was prove useful in other contexts.
- */
-//EHR.ext.FormWindow = Ext.extend(Ext.Window, {
-// initComponent: function()
-// {
-// Ext.apply(this, {
-// closeAction:'hide'
-// ,title: 'Enter Data'
-// ,xtype: 'panel'
-// ,autoScroll: true
-// ,autoHeight: true
-// ,boxMaxHeight: 600
-// ,defaults: {
-// border: false
-// ,bodyStyle: 'padding: 5px;'
-// }
-// ,items: [{
-// xtype: 'ehr-formpanel'
-// ,schemaName: this.schemaName
-// ,queryName: this.queryName
-// ,columns: EHR.Metadata.Columns[this.queryName]
-// ,metadata: EHR.Metadata.getTableMetadata(this.queryName, ['Task'])
-// }]
-// ,scope: this
-// });
-//
-// EHR.ext.FormWindow.superclass.initComponent.call(this, arguments);
-// }
-//});
-//Ext.reg('ehr-formwindow', EHR.ext.FormWindow);
-
-
diff --git a/ehr/resources/web/ehr/ext3/ExtOverrides.js b/ehr/resources/web/ehr/ext3/ExtOverrides.js
deleted file mode 100644
index 084710fd9..000000000
--- a/ehr/resources/web/ehr/ext3/ExtOverrides.js
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (c) 2013-2019 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
- */
-
-//This will contain ext overrides
-Ext.namespace('EHR.ext');
-
-
-
-//a css fix for Ext datepicker and tabpanel
-Ext.menu.DateMenu.prototype.addClass('extContainer');
-Ext.TabPanel.prototype.addClass('extContainer');
-
-
-//NOTE: In IE8, DatePickers render half-width
-//see: http://www.opendebug.com/article/533989
-Ext.override (Ext.menu.Menu, {
- autoWidth:function (){
- //only append if input is numeric
- if (!isNaN(this.width))
- this.width += 'px';
- }
-});
-
-//Ext's 3.1 documentation says this should be the code.
-// the purpose is to allow null values in numeric fields
-Ext.data.Types.INT.convert = function(v){
- return v !== undefined && v !== null && v !== '' ?
- parseInt(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
-};
-
-Ext.data.Types.FLOAT.convert = function(v){
- return v !== undefined && v !== null && v !== '' ?
- parseFloat(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
-};
-
-//if not set to true, null numeric values will automatically be coerced to zero
-Ext.data.Field.prototype.useNull = true;
-
-
-
-Ext.override(Ext.form.CheckboxGroup, {
- getValueAsString: function(delim)
- {
- delim = delim || ',';
- var vals = [];
- Ext.each(this.getValue(), function(c){
- vals.push(c.inputValue);
- }, this);
- return vals.join(delim);
- },
- removeAll: function () {
- this.panel.items.each(function(col){
- col.items.each(function(item){
- this.items.remove(item);
- col.remove(item);
- })
- }, this);
- }
-});
-
-//Ext is going to coerse the value into a string, which doesnt work well when it's null
-Ext.override(Ext.form.RadioGroup, {
- setValueForItem: function(val){
- val = (val===null ? '' : val);
- Ext.form.RadioGroup.superclass.setValueForItem.call(this, val);
- },
- blankText: 'This field is required'
-});
-
-
-Ext.override(Ext.Panel, {
- setReadOnly: function(val){
- if(!this.items){
- console.log(this)
- }
- else {
- this.items.each(function(item){
- if(item.setReadOnly){
- item.setReadOnly(val);
- }
- else {
- item.setDisabled(val)
- }
- }, this);
- }
- }
-});
-
-//overridden b/c if you try to set the value of a combo prior to store loading, it will display
-//the raw value, not display value
-Ext.override(LABKEY.ext.ComboBox, {
- setValue: function(v){
- if(this.store && !this.store.fields){
- this.initialValue = v;
- }
-
- LABKEY.ext.ComboBox.superclass.setValue.call(this, v);
- }
-});
-
-
-//overridden b/c readOnly has no effect on checkboxes and radios. this will disable the element, making it truly read only
-Ext.override(Ext.form.Checkbox, {
- setReadOnly: function(val){
- Ext.form.Checkbox.superclass.setReadOnly.apply(this, arguments);
- this.setDisabled(val);
- }
-});
-
-Ext.override(Ext.data.Store, {
- add : function(records) {
- var i, len, record, index;
-
- records = [].concat(records);
- if (records.length < 1) {
- return;
- }
-
- for (i = 0, len = records.length; i < len; i++) {
- record = records[i];
-
- record.join(this);
-
- if (record.dirty || record.phantom) {
- this.modified.push(record);
- }
- }
-
- index = this.data.length;
- this.data.addAll(records);
-
- if (this.snapshot) {
- this.snapshot.addAll(records);
- }
-
- this.fireEvent('add', this, records, index);
- },
- insert : function(index, records) {
- var i, len, record;
-
- records = [].concat(records);
- for (i = 0, len = records.length; i < len; i++) {
- record = records[i];
-
- this.data.insert(index + i, record);
- record.join(this);
-
- if (record.dirty || record.phantom) {
- this.modified.push(record);
- }
- }
-
- if (this.snapshot) {
- this.snapshot.addAll(records);
- }
-
- this.fireEvent('add', this, records, index);
- }
-});
-
-//only overridden to remove setting a default nullCaption. this is moved to the combo tpl
-Ext.override(LABKEY.ext.Store, {
- onLoad : function(store, records, options) {
- this.isLoading = false;
-
- //remember the name of the id column
- this.idName = this.reader.meta.id;
-
- if(this.nullRecord)
- {
- //create an extra record with a blank id column
- //and the null caption in the display column
- var data = {};
- data[this.reader.meta.id] = "";
- data[this.nullRecord.displayColumn] = this.nullRecord.nullCaption || this.nullCaption || null;
-
- var recordConstructor = Ext.data.Record.create(this.reader.meta.fields);
- var record = new recordConstructor(data, -1);
- this.insert(0, record);
- }
- }
-});
-
-//Ext.override(Ext.form.ComboBox, {
-// doQuery : function(q, forceAll){
-// q = Ext.isEmpty(q) ? '' : q;
-// var qe = {
-// query: q,
-// forceAll: forceAll,
-// combo: this,
-// cancel:false
-// };
-// if(this.fireEvent('beforequery', qe)===false || qe.cancel){
-// return false;
-// }
-// q = qe.query;
-// forceAll = qe.forceAll;
-// if(forceAll === true || (q.length >= this.minChars)){
-// if(this.lastQuery !== q){
-// this.lastQuery = q;
-// if(this.mode == 'local'){
-// this.selectedIndex = -1;
-// if(forceAll){
-// this.store.clearFilter();
-// }else{
-// this.store.filter(this.displayField, q, this.allowAnyValue, this.caseSensitive);
-// }
-// this.onLoad();
-// }else{
-// this.store.baseParams[this.queryParam] = q;
-// this.store.load({
-// params: this.getParams(q)
-// });
-// this.expand();
-// }
-// }else{
-// this.selectedIndex = -1;
-// this.onLoad();
-// }
-// }
-// }
-//});
-
-
diff --git a/ehr/resources/web/ehr/ext3/databind.js b/ehr/resources/web/ehr/ext3/databind.js
deleted file mode 100644
index fed48bf40..000000000
--- a/ehr/resources/web/ehr/ext3/databind.js
+++ /dev/null
@@ -1,425 +0,0 @@
-/*
- * Copyright (c) 2013-2019 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
- */
-
-Ext.namespace('EHR.ext.plugins');
-
-/**
- * @name EHR.ext.plugins
- * @namespace A namespace containing various Ext plugins used in the EHR.
- *
- */
-
-/**
- * A plugin for EHR.ext.FormPanel that will allow the panel to 'bind' to an Ext record, which allows changes on either the panel or record to alter the other.
- * @name EHR.ext.plugins.DataBind
- * @augments Ext.util.Observable
- * @description
- *
- * This plugin is adapted from the plugin found here.
- * It is used heavily throughout the EHR data entry UI. This allows the forms to consist of a store (which holds one or more records) and various
- * UI elements (FormPanel or GridPanel) which display them. Changes made in either the store record or through the form/grid will be reflected on
- * the other side. This allows a form to bind() or unbind() records, which lets the form toggle between many records.
- *
- * This plugin creates a new param in the FormPanel called bindConfig. This parameter is an object with the following options:
- *
disableUnlessBound: If true, inputs in the form will be disabled unless a record is bound
- *
bindOnChange: If true, a new record will be created and bound to the form (if no record is already bound) whenever the value of one of the fields changes
- *
showDeleteBtn: If true, a button will be added to the bbar of the form that will delete the bound record
- *
autoBindRecord: If true, when the store loads the form will automatically bind the first record of the store
- *
createRecordOnLoad: If true, when the store loads a new record will be created and bound unless the store already has records
- */
-
-//this sets up listeners to automatically update the record or form based on changes in the other
-
-EHR.ext.plugins.DataBind = Ext.extend(Ext.util.Observable, {
- init:function(o) {
- /*
- * This plugin adds a series of new methods to the bound form panel:
- */
- Ext.applyIf(o, {
- bindConfig: {
- disableUnlessBound: true,
- bindOnChange: false,
- showDeleteBtn: false,
- autoBindRecord: false,
- createRecordOnLoad: false
- },
- //private
- //returns the bound store
- getStore : function()
- {
- return this.store;
- },
- //private
- //removes the bound store and record
- removeStore: function(){
- if(!this.store) return;
-
- delete this.store.boundPanel;
- delete this.store;
- },
- //private
- // a helper to identify all fields that should have listeners added.
- // used because CheckboxGroups and RadioGroups are tricky to monitor correctly
- getFieldsToBind : function(){
- var fields = [];
- var findMatchingField = function(f) {
- if (f.isFormField) {
- if (f.dataIndex) {
- fields.push(f);
- } else if (f.isComposite) {
- f.items.each(findMatchingField);
- }
- }
- };
- this.getForm().items.each(findMatchingField, this);
- return fields;
- },
- //private
- //This will bind the supplied record to this form. If bound, changes in the record will be reflected in the form
- //and vise versa.
- bindRecord: function(record){
- if (!record || (this.boundRecord === record)){
- return;
- }
-
- //commit the old record before loading a new one
- if(this.boundRecord){
- this.unbindRecord();
- }
-
- this.boundRecord = record;
- this.boundRecord.store.boundPanel = this;
-
- Ext.each(this.getFieldsToBind(), function(f){
- if (this.bindConfig.disableUnlessBound){
- if(f.editable!==false)
- f.setDisabled(false);
- }
- f.setValue(record.get(f.dataIndex));
- }, this);
-// this.updateRecord();
-
- //allow changes in the record to update the form
- record.store.on('update', this.updateForm, this);
- //record.store.on('validation', this.updateForm, this);
- record.store.on('validation', this.markInvalid, this, {delay: 100});
-
- this.fireEvent('recordchange', this, record);
- },
-
- //private
- //unbinds a record from the store, if present. will disable fields if bindConfig.disableUnlessBound is true
- //fires the recordchange event
- unbindRecord: function(config)
- {
- var rec = this.boundRecord;
- if(rec){
- this.updateRecord();
- if(rec.store){
- rec.store.un('update', this.updateForm, this);
- rec.store.un('validation', this.markInvalid, this);
- }
- else if (config && config.store){
- config.store.un('update', this.updateForm, this);
- config.store.un('validation', this.markInvalid, this);
- }
-
- if(config && config.deleteRecord){
- rec.store.deleteRecords([rec]);
- }
- }
-
- this.boundRecord = undefined;
-
- Ext.each(this.getFieldsToBind(), function(f){
- if (this.bindConfig.disableUnlessBound){
- f.setDisabled(true);
- }
- }, this);
-
- this.form.reset();
- this.fireEvent('recordchange', this);
- },
-
- //private
- //when a field changes, this updates the bound record
- updateRecord : function()
- {
- var fields = this.getFieldsToBind();
-
- //create a record onChange if selected
- if(!this.boundRecord && this.bindConfig.bindOnChange && this.store){
- var values = {};
- Ext.each(fields, function(f){
- var val = this.getBoundFieldValue(f);
- if(!Ext.isEmpty(val))
- values[f.name] = val;
- }, this);
- var record = this.store.addRecord(values);
- record.markDirty();
- this.bindRecord(record);
- }
-
- if(!this.boundRecord)
- return;
-
- values = {};
- Ext.each(fields, function(f){
- var val = this.getBoundFieldValue(f);
- var oldVal = this.boundRecord.get(f.dataIndex);
- //TODO: better logic??
- if(!(val===oldVal || String(val) == String(oldVal)))
- values[f.dataIndex] = val;
- }, this);
-
- //we only fire the update event if we actually made changes
- //console.log(values);
- if(!EHR.Utils.isEmptyObj(values)){
- this.boundRecord.beginEdit();
- for (var i in values){
- this.boundRecord.set(i, values[i]);
- }
- //the following is to prevent
- this.awatingUpdateEvent = true;
- this.boundRecord.endEdit();
- }
-
- this.markInvalid();
- },
-
- //private
- //when a record changes, this updates the fields on the form
- updateForm: function(s, recs, idx)
- {
-
- if(this.awatingUpdateEvent){
- delete this.awatingUpdateEvent;
- return;
- }
-
- Ext.each(recs, function(r){
- if(r === this.boundRecord){
- this.getForm().loadRecord(r);
- }
- }, this);
- },
-
- //private
- //when configuring a form, this adds listeners to all fields to monitor for change events
- addFieldListeners: function()
- {
- Ext.each(this.getFieldsToBind(), function(f){
- this.addFieldListener(f);
- }, this);
- },
-
- //private
- //the handler for field onChange events. this is separated so that multiple fields in a single form are filtered into one event per panel
- onFieldChange: function(field){
- this.fireEvent('fieldchange', field);
- },
-
- //private
- //called to convert the value returned by the field to the value stored in the record. this is separated because complex field
- //types like RadioGroups and CheckboxGroups.
- getBoundFieldValue: function(f){
- if (f instanceof Ext.form.RadioGroup){
- //QUESTION: safe to make the assumption we only allow 1 checked at once?
- return (f.getValue() ? f.getValue().inputValue : null);
- }
- else if (f instanceof Ext.form.Radio){
- if(f.checked)
- return f.getValue();
- else
- return false;
- }
- else if (f instanceof Ext.form.CheckboxGroup){
- return f.getValueAsString();
- }
- else
- return f.getValue();
- },
-
- //private
- onRecordRemove: function(store, rec, idx){
- if(this.boundRecord && rec == this.boundRecord){
- this.unbindRecord({store: store});
- }
- },
-
- //private
- focusFirstField: function(){
- var firstFieldItem = this.getForm().items.first();
- if(firstFieldItem && firstFieldItem.focus){
- //delay the focus for 500ms to make sure the field is visible
- firstFieldItem.focus(false,500);
- }
- },
-
- //private
- addFieldListener: function(f){
- if(f.hasDatabindListener){
- //console.log('field already has listener');
- return;
- }
-
- if(f instanceof Ext.form.Checkbox){
- f.on('check', this.onFieldChange, this);
- f.on('change', this.onFieldChange, this);
- }
- else {
- //NOTE: use buffer so groups like checkboxgroup dont fire repeated events
- f.on('change', this.onFieldChange, this);
- }
-
- //NOTE: override getErrors(), so the field can be made to display server-side errors
- if(!f.oldGetErrors)
- f.oldGetErrors = f.getErrors;
-
- f.getErrors = function(value){
- var errors = this.oldGetErrors.apply(this, arguments);
- var panel;
- if(this.ownerCt.isXType('ehr-formpanel'))
- panel = this.ownerCt;
- else
- panel = this.findParentByType('ehr-formpanel');
-
- if(panel.boundRecord && panel.boundRecord.errors){
- Ext.each(panel.boundRecord.errors, function(error){
- if(error.field == this.name){
- errors.push((error.severity=='INFO' ? error.severity+': ' : '')+error.message);
- }
- }, this);
- }
-
- //if allowBlank==true, Ext.Form.TextField will return duplicate 'Field is required' messages
- errors = Ext.unique(errors);
-
- //the form will only display the first error, so we concat them
- if(errors.length)
- errors = [errors.join("; ")];
-
- return errors;
- };
-
- if(this.bindConfig.disableUnlessBound && !this.boundRecord && !this.bindConfig.bindOnChange)
- f.setDisabled(true);
-
- f.hasDatabindListener = true;
- },
-
- //private
- //Configures the store with listeners to monitor record changes
- configureStore: function(){
- if (this.store)
- {
- this.store = Ext.StoreMgr.lookup(this.store);
- Ext.apply(this.store, {
- boundPanel: this
- });
-
- this.store.on('load', function(store, records, options)
- {
- // Can only contain one row of data.
- if (records.length == 0){
- if(this.bindConfig.createRecordOnLoad){
- var values = {};
- Ext.each(this.getFieldsToBind(), function(item){
- values[item.name] = this.getBoundFieldValue(item);
- }, this);
- var rec = new this.store.recordType(values);
- rec.markDirty();
- this.store.addRecord(rec);
-
- //called to force record to store's modified list
- this.store.afterEdit(rec);
-
- this.bindRecord(rec);
- }
- }
- else {
- //NOTE: I disabled this behavior b/c it was potentially
- //confusing if the user loads a saved page and automatically
- //has a previous record bound.
- if(this.bindConfig.autoBindRecord){
- this.bindRecord(records[0]);
- }
- }
- }, this);
-
- //NOTE: this is called too late for this to matter
- //this.store.on('beforecommit', this.updateRecord, this);
-
- this.store.on('remove', this.onRecordRemove, this);
- }
- }
- });
-
- o.configureStore();
- o.addFieldListeners();
- o.addEvents('beforesubmit', 'recordchange', 'formchange', 'fieldchange');
-
- //we queue changes from all fields into a single event using buffer
- //this way batch updates of the form only trigger one record update/validation
- o.on('fieldchange', o.updateRecord, o, {delay: 20, buffer: 100});
-
- if(o.bindConfig.showDeleteBtn !== false){
- if(o.getBottomToolbar())
-
- o.getBottomToolbar().insert(0, {xtype: 'tbspacer', width: 100});
-
- o.getBottomToolbar().insert(0, {
- xtype: 'button',
- text: 'Clear Section',
- ref: 'recordDeleteBtn',
- scope: o,
- disabled: true,
- handler: function(){
- if(this.boundRecord){
- Ext.MessageBox.confirm(
- 'Confirm',
- 'You are about to clear this section. This will permanently delete these values. Are you sure you want to do this?',
- function(val){
- if(val=='yes')
- this.unbindRecord({deleteRecord: true});
- },
- this
- );
- }
- }
- });
-
- o.on('recordchange', function(form, record){
- this.getBottomToolbar().recordDeleteBtn.setDisabled(record===undefined)
- }, o);
- }
-
- o.on('beforesubmit', function(c){
- console.log('updating record from form beforesubmit');
- c.updateRecord();
- });
-
- o.on('add', function(o, c, idx){
- var findMatchingField = function(f) {
- if (f.isFormField) {
- if (f.dataIndex) {
- this.addFieldListener(c);
- } else if (f.isComposite) {
- f.items.each(findMatchingField, this);
- }
- }
- };
- findMatchingField.call(this, c);
- }, o, {delay: 300});
- }
-});
-Ext.preg('databind', EHR.ext.plugins.DataBind);
-
-
-
-
-
-
diff --git a/ehr/resources/web/ehr/ext3/datetime.js b/ehr/resources/web/ehr/ext3/datetime.js
deleted file mode 100644
index 0e4c808d5..000000000
--- a/ehr/resources/web/ehr/ext3/datetime.js
+++ /dev/null
@@ -1,753 +0,0 @@
- /**
- * @class Ext.ux.form.DateTime
- * @extends Ext.form.Field
- *
- * DateTime field, combination of DateField and TimeField
- *
- * @author Ing. Jozef Sakáloš
- * @copyright (c) 2008, Ing. Jozef Sakáloš
- * @version 2.0
- * @revision $Id: Ext.ux.form.DateTime.js 813 2010-01-29 23:32:36Z jozo $
- *
- * @license Ext.ux.form.DateTime is licensed under the terms of
- * the Open Source LGPL 3.0 license. Commercial use is permitted to the extent
- * that the code/component(s) do NOT become part of another Open Source or Commercially
- * licensed development library or toolkit without explicit permission.
- *
- *
- *
- * @constructor
- * @augments LABKEY.ext.EditorGridPanel
- * @param config Configuration properties. This may contain any of the configuration properties supported
- * by Ext.grid.EditorGridPanel
- * and LABKEY.ext.EditorGridPanel, plus those listed here.
- * @param {String} [config.store] An EHR.ext.AdvancedStore configured for a LabKey query.
- * @param {object} [config.sm] An Ext selection model. If not provided, this will default to using Ext.grid.CheckboxSelectionModel
- * @example <script type="text/javascript">
- var grid, store;
- Ext.onReady(function(){
-
- //create a Store bound to the 'Users' list in the 'core' schema
- var store = new EHR.ext.AdvancedStore({
- schemaName: 'core',
- queryName: 'users'
- });
-
- //create a grid using that store as the data source
- var grid = new EHR.ext.EditorGridPanel({
- store: store,
- renderTo: 'grid',
- width: 800,
- title: 'Example'
- });
- });
-
-
-</script>
-<div id='grid'/>
- */
-
-EHR.ext.EditorGridPanel = Ext.extend(LABKEY.ext.EditorGridPanel,
-{
- initComponent: function(){
-
- var sm = this.sm || new Ext.grid.CheckboxSelectionModel();
-
- Ext.applyIf(this, {
- viewConfig: {
- forceFit: true,
- scrollOffset: 0,
- getRowClass : function(record, rowIndex, rowParams, store){
- if(record.errors && record.errors.length){
- return 'x-grid3-row-invalid';
- }
- return '';
- }
- },
- autoHeight: true,
- autoWidth: true,
- pageSize: 200,
- autoSave: false,
- deferRowRender : true,
- editable: true,
- stripeRows: true,
- enableHdMenu: false,
- tbar: [
- {
- text: 'Add Record',
- tooltip: 'Click to add a blank record',
- name: 'add-record-button',
- handler: this.onAddRecord,
- scope: this
- },
- "-"
- ,{
- text: 'Delete Selected',
- tooltip: 'Click to delete selected row(s)',
- name: 'delete-records-button',
- handler: this.onDeleteRecords,
- scope: this
- }
- ]
- });
-
- EHR.ext.EditorGridPanel.superclass.initComponent.apply(this, arguments);
-
- this.store.on('validation', this.onStoreValidate, this, {delay: 100});
- }
-
- //private
- ,onStoreValidate: function(store, records){
- if(records && !Ext.isArray(records))
- records = [records];
-
- Ext.each(records, function(rec){
- if(this.rendered)
- this.getView().refreshRow(rec);
- }, this);
-
- }
-
- //NOTE: any methods marked as 'no longer needed' are methods inherited from LABKEY's EditorGridPanel
- //LABKEY's EditorGrid and this version take different routes to handle interpreting LabKey metadata
- //the non-needed methods have been explicitly overridden with empty functions to clarify differences in the classes
-
- //private
- ,populateMetaMap : function() {
- //not longer needed
- }
-
- //private
- ,getDefaultEditor: function(){
- //moved to EHR.ext.metaHelper
- }
-
- //private
- ,getLookupEditor: function(){
- //moved to EHR.ext.metaHelper
- }
-
- //private
- ,setLongTextRenderers : function() {
- //moved to EHR.ext.metaHelper
- }
-
- //private
- ,onLookupStoreError: function(){
- //moved to EHR.ext.metaHelper
- }
-
- //private
- ,onLookupStoreLoad: function(){
- //moved to EHR.ext.metaHelper
- }
-
- //private
- ,getLookupRenderer: function(){
- //moved to EHR.ext.metaHelper
- }
-
- //private
- //configures the Ext ColumnModel used by this grid, based on the fields and metadata supplied in the store
- ,setupColumnModel : function() {
- var columns = this.getColumnModelConfig();
-
- //if a sel model has been set, and if it needs to be added as a column,
- //add it to the front of the list.
- //CheckBoxSelectionModel needs to be added to the column model for
- //the check boxes to show up.
- //(not sure why its constructor doesn't do this automatically).
- if(this.getSelectionModel() && this.getSelectionModel().renderer)
- columns = [this.getSelectionModel()].concat(columns);
-
- //register for the rowdeselect event if the selmodel supports events
- //and if autoSave is on
- if(this.getSelectionModel().on && this.autoSave)
- this.getSelectionModel().on("rowselect", this.onRowSelect, this);
-
- //fire the "columnmodelcustomize" event to allow clients
- //to modify our default configuration of the column model
- //NOTE: I dont think this will be permissible b/c it's a public API,
- // but I would suggest changing the arguments on this event
- // might make more sense to pass 'this' and 'columns'. can use getColumnById() method
- this.fireEvent("columnmodelcustomize", columns);
-
- //reset the column model
- this.reconfigure(this.store, new Ext.grid.ColumnModel(columns));
-
- }
-
- //private
- ,getColumnModelConfig: function(){
- var config = {
- editable: this.editable,
- defaults: {
- sortable: false
- }
- };
-
- var columns = EHR.ext.metaHelper.getColumnModelConfig(this.store, config, this);
-
- Ext.each(columns, function(col, idx){
- var meta = this.store.findFieldMeta(col.dataIndex);
-
- //remember the first editable column (used during add record)
- if(!this.firstEditableColumn && col.editable)
- this.firstEditableColumn = idx;
-
- if(meta.isAutoExpandColumn && !col.hidden){
- this.autoExpandColumn = idx;
- }
-
- }, this);
-
- return columns;
- }
-
- //private
- ,getColumnById: function(colName){
- return this.getColumnModel().getColumnById(colName);
- }
-});
-Ext.reg('ehr-editorgrid', EHR.ext.EditorGridPanel);
-
diff --git a/ehr/resources/web/ehr/ext3/ehrFormPanel.js b/ehr/resources/web/ehr/ext3/ehrFormPanel.js
deleted file mode 100644
index 04e197e55..000000000
--- a/ehr/resources/web/ehr/ext3/ehrFormPanel.js
+++ /dev/null
@@ -1,355 +0,0 @@
-/*
- * Copyright (c) 2013-2019 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
- */
-
-Ext.namespace('EHR.ext');
-
-/**
- * Constructs a new LabKey FormPanel using the supplied configuration.
- * @class
- * EHR extension to the Ext.FormPanel class, which constructs a form panel, configured based on the query's metadata.
- * This class understands various LabKey metadata formats and can simplify generating basic forms. When a LABKEY.ext.FormPanel is created with additional metadata, it will try to intelligently construct fields of the appropriate type.
- * While the purpose of this class is similar to LABKEY.ext.FormPanel, it operates somewhat differently. Notably, the FormPanel will automatically
- * construct an Ext.data.Record and bind the form input to this record. This allows the Form to more easily communicate with an Ext Store and to operate on many records at once.
- * This class also delegates most of the work involved with interpreting metadata to EHR.ext.Metahelper, which allows the code of this class to be relatively basic.
- *
- *
If you use any of the LabKey APIs that extend Ext APIs, you must either make your code open source or
- * purchase an Ext license.
- *
- * @constructor
- * @augments Ext.FormPanel
- * @param config Configuration properties. This may contain any of the configuration properties supported
- * by the Ext.form.FormPanel,
- * plus those listed here.
- *
- * Note:
- *
- *
You may construct a FormPanel by either supplying a store or by supplying a schema/query, in which case a store will be automatically created.
- *
FormPanel automatically uses the plugin EHR.ext.plugins.DataBind to bind form entry to an Ext record. Please see documentation for this plugin for more information.
- *
- *
- * @param {String} [config.store] An EHR.ext.AdvancedStore configured for a LabKey query.
- * @param {String} [config.containerPath] The container path from which to query the server. If not specified, the current container is used. Will be ignored if a store is provided.
- * @param {String} [config.schemaName] The LabKey schema to query. Will be ignored if a store is provided.
- * @param {String} [config.queryName] The query name within the schema to fetch. Will be ignored if a store is provided.
- * @param {String} [config.viewName] A saved custom view of the specified query to use if desired. Will be ignored if a store is provided.
- * @param {Integer} [config.defaultFieldWidth] If provided, this will be used as the default width of fields in this form.
- * @param {Object} [config.metadata] A metadata object that will be applied to the default metadata returned by the server. See EHR.Metadata or EHR.ext.AdvancedStore for more information.
- * @param {Object} [config.storeConfig] A config object passed directly to the EHR.ext.AdvancedStore that will be used to create the store. Will only be used if no store is provided.
- * @param {Object} [config.bindConfig] A config object passed to EHR.ext.plugins.DataBind plugin. See documention on this plugin for more information.
- * @example <script type="text/javascript">
- var panel;
- Ext.onReady(function(){
- var panel = new EHR.ext.FormPanel({
- store: new EHR.ext.AdvancedStore({
- schemaName: 'core',
- queryName: 'users'
- }),
- renderTo: 'targetDiv',
- width: 800,
- autoHeight: true,
- title: 'Example'
- });
- });
-
-
-</script>
-<div id='targetDiv'/>
- */
-
-EHR.ext.FormPanel = function(config){
- EHR.ext.FormPanel.superclass.constructor.call(this, config);
-};
-
-Ext.extend(EHR.ext.FormPanel, Ext.FormPanel,
-{
- initComponent: function()
- {
- this.storeConfig = this.storeConfig || {};
- if(!this.storeConfig.filterArray){
- this.storeConfig.maxRows = 0;
- this.on('load', function(){
- delete this.maxRows;
- }, this, {single: true});
- }
-
- this.store = this.store || new EHR.ext.AdvancedStore(Ext.applyIf(this.storeConfig, {
- //xtype: 'ehr-store',
- containerPath: this.containerPath,
- schemaName: this.schemaName,
- queryName: this.queryName,
- viewName: this.viewName || '~~UPDATE~~',
- columns: this.columns || EHR.Metadata.Columns[this.queryName] || '',
- storeId: [this.schemaName,this.queryName,this.viewName].join('||'),
- filterArray: this.filterArray || [],
- metadata: this.metadata,
- autoLoad: true
- }));
-
- this.store.importPanel = this.importPanel || this;
-
- Ext.apply(this, {
- plugins: ['databind']
- ,trackResetOnLoad: true
- ,bubbleEvents: ['added']
- ,buttonAlign: 'left'
- ,monitorValid: false
- });
-
- Ext.applyIf(this, {
- autoHeight: true
- //,autoWidth: true
- ,labelWidth: 125
- ,defaultFieldWidth: 200
- ,items: {xtype: 'displayfield', value: 'Loading...'}
- //,name: this.queryName
- ,bodyBorder: false
- ,border: false
- ,bodyStyle: 'padding:5px'
- ,style: 'margin-bottom: 15px'
- ,bindConfig: {
- disableUnlessBound: true,
- autoBindRecord: true,
- bindOnChange: false,
- showDeleteBtn: true
- }
- //,deferredRender: true
- ,bbar: this.showStatus ? {
- xtype: 'statusbar',
- defaultText: 'Default text',
- text: 'No Records',
- statusAlign: 'left',
- buttonAlign: 'left',
- iconCls: 'x-status-valid'
- } : null
- });
-
- //a test for whether the store is loaded
- if(!this.store.fields){
- this.mon(this.store, 'load', this.loadQuery, this, {single: true});
- }
- else {
- this.loadQuery(this.store);
- }
-
- EHR.ext.FormPanel.superclass.initComponent.call(this);
-
- //NOTE: participantchange and participantrefres are likely not used and can be removed after testing
- this.addEvents('beforesubmit', 'participantchange','participantrefresh');
-
- if(this.showStatus){
- //NOTE: recordchange is described in the DataBind plugin
- this.on('recordchange', this.onRecordChange, this, {buffer: 100, delay: 100});
- this.mon(this.store, 'validation', this.onStoreValidate, this, {delay: 100});
- }
-
- this.on('recordchange', this.markInvalid, this, {delay: 100});
-
- },
-
- //private
- loadQuery: function(store)
- {
- this.removeAll();
- var toAdd = this.configureForm(store, this);
- Ext.each(toAdd, function(item){
- this.add(item);
- }, this);
-
- //create a placeholder for error messages
- this.add({
- tag: 'div',
- ref: 'errorEl',
- border: false,
- width: 350,
- style: 'padding:5px;text-align:center;'
- });
-
- if(this.rendered)
- this.doLayout();
-
- },
-
- //private
- //This method iterates the fields of the form and created the appropriate Ext field editor, based on the field's metadata
- configureForm: function(store, formPanel)
- {
- var toAdd = [];
- var compositeFields = {};
- store.fields.each(function(c){
- var config = {
- queryName: store.queryName,
- schemaName: store.schemaName
- };
-
- if (!c.hidden && c.shownInInsertView)
- {
- var theField = this.store.getFormEditorConfig(c.name, config);
-
- if(!c.width){
- theField.width = formPanel.defaultFieldWidth;
- }
-
- if (c.inputType == 'textarea' && !c.height){
- Ext.apply(theField, {height: 100});
- }
-
- if(theField.xtype == 'combo'){
- theField.lazyInit = false;
- theField.store.autoLoad = true;
- }
-
- if(this.readOnly){
- theField.xtype = 'displayfield';
- console.log('is read only: '+store.queryName)
- }
-
- if(!c.compositeField)
- toAdd.push(theField);
- else {
- theField.fieldLabel = undefined;
- if(!compositeFields[c.compositeField]){
- compositeFields[c.compositeField] = {
- xtype: 'panel',
- autoHeight: true,
- layout: 'hbox',
- border: false,
- //msgTarget: c.msgTarget || 'qtip',
- fieldLabel: c.compositeField,
- defaults: {
- border: false,
- margins: '0px 4px 0px 0px '
- },
- width: formPanel.defaultFieldWidth,
- items: [theField]
- };
- toAdd.push(compositeFields[c.compositeField]);
-
- if(compositeFields[c.compositeField].msgTarget == 'below'){
- //create a div to hold error messages
- compositeFields[c.compositeField].msgTargetId = Ext.id();
- toAdd.push({
- tag: 'div',
- fieldLabel: null,
- border: false,
- id: compositeFields[c.compositeField].msgTargetId
- });
- }
- else {
- theField.msgTarget = 'qtip';
- }
- }
- else {
- compositeFields[c.compositeField].items.push(theField);
- }
- }
- }
- }, this);
-
- //distribute width for compositeFields
- for (var i in compositeFields){
- var compositeField = compositeFields[i];
- var toResize = [];
- //this leaves a 2px buffer between each field
- var availableWidth = formPanel.defaultFieldWidth - 4*(compositeFields[i].items.length-1);
- for (var j=0;j
- * This class attempts to outsource as much to the GridFormPanel and FormPanel as possible. It does contain its own button bars (so that they will span both components),
- * and had to override the child components' handling of these.
- *
- *
If you use any of the LabKey APIs that extend Ext APIs, you must either make your code open source or
- * purchase an Ext license.
- *
- * @constructor
- * @augments Ext.Panel
- * @param {object} config Configuration properties. This may contain any of the configuration properties supported
- * by the Ext.Panel, plus those listed here.
- * @param {string} [config.formType] The type of this form, which will usually correspond to a row in ehr.formTypes
- * @param {string} [config.formUUID] The unique GUID associated with this form. This will usually be the taskId or requestId, depending on the type of form.
- * @param {boolean} [config.readOnly] If true, instead of rendering the children as FormPanels or GridPanels, they will be displayed using QueryWebParts
- * @param {array} [config.formHeaders] An array of Ext config objects used to create the sections appearing in the header portion of this ImportPanel. These will appear in order, starting at the top of the panel.
- * @param {array} [config.formSections] An array of Ext config objects used to create the sections appearing in the body of this form. These will appear in order, after all header items.
- * @param {array} [config.formTabs] An array of Ext config objects used to create tabs in this form. These will be added in order, after the formHeaders and formSections.
- * @param {array} [config.initialTemplates] An array of templates to apply to this form. Each item must be an object with the properties storeId and title. Each storeId/title provided must match a record in ehr.formtemplates.
- * @param {string} [config.allowableButtons] An array of strings corresponding to the button configs defined in EHR.ext.ImportPanel.Buttons. These buttons will not necessarily be displayed, if the current user does not have permissions for the button being requested.
- */
-
-EHR.ext.ImportPanel.Base = function(config){
- EHR.ext.ImportPanel.Base.superclass.constructor.call(this, config);
-};
-
-Ext.extend(EHR.ext.ImportPanel.Base, Ext.Panel, {
- initComponent: function()
- {
- Ext.QuickTips.init();
-
- Ext.applyIf(this, {
- autoHeight: true
- ,defaults: {
- bodyBorder: false
- ,border: false
- }
- ,items: []
- ,store: new EHR.ext.StoreCollection({
- monitorValid: true
- })
- ,fbar: []
- });
-
- EHR.ext.ImportPanel.Base.superclass.initComponent.call(this);
-
- this.addEvents('participantchange', 'participantloaded','participantrefresh');
-
- this.mon(this.store, 'validation', this.onStoreValidation, this);
- this.mon(this.store, 'commitcomplete', this.afterSubmit, this);
- this.mon(this.store, 'commitexception', this.afterSubmit, this);
-
- //monitor dirty state
- window.onbeforeunload = LABKEY.beforeunload(function (){
- if (this.isDirty())
- return this.warningMessage || 'we should set a warning message somewhere';
- }, this);
-
- EHR.Security.init({
- success: this.calculatePermissions,
- failure: EHR.Utils.onError,
- scope: this
- });
- },
-
- //@private
- //called when the permissionMap loads. calculates effective permissions based on queries represented in this form
- calculatePermissions: function()
- {
- this.populateItems();
-
- //add stores to StoreCollection
- //TODO: this seems like a bad method to perform this. Rather than iterating all stores, we
- //should use something more refined
- Ext.StoreMgr.each(this.addStore, this);
-
- this.populateButtons();
-
- if(this.initialTemplates && this.initialTemplates.length)
- this.applyTemplates(this.initialTemplates);
- },
-
- //@private
- //returns the set of unique queries represented in this form
- getQueries: function()
- {
- var queries = [];
-
- if(this.formHeaders)
- Ext.each(this.formHeaders, function(item){
- if(item.schemaName && item.queryName)
- queries.push({
- schemaName: item.schemaName,
- queryName: item.queryName
- })
- }, this);
- if(this.formSections)
- Ext.each(this.formSections, function(item){
- if(item.schemaName && item.queryName)
- queries.push({
- schemaName: item.schemaName,
- queryName: item.queryName
- })
- }, this);
- if(this.formTabs)
- Ext.each(this.formTabs, function(item){
- if(item.schemaName && item.queryName)
- queries.push({
- schemaName: item.schemaName,
- queryName: item.queryName
- });
- }, this);
-
- return queries;
- },
-
- //@private
- //configures the buttons shown in the bbar of this panel
- populateButtons: function()
- {
- if(this.allowableButtons){
- if(!Ext.isArray(this.allowableButtons))
- this.allowableButtons = this.allowableButtons.split(',');
- }
- else {
- this.allowableButtons = [
- 'VALIDATE',
- //'PRINT',
- 'SAVEDRAFT',
- //'SCHEDULE',
- 'REVIEW',
- 'SUBMIT',
- 'FORCESUBMIT',
- 'DISCARD',
- 'CLOSE'
- ];
- }
-
- var buttons = [];
- var buttonCfg;
- Ext.each(this.allowableButtons, function(b){
- var buttonCfg;
- if(Ext.isString(b) && EHR.ext.ImportPanel.Buttons[b])
- buttonCfg = EHR.ext.ImportPanel.Buttons[b].call(this);
- else
- buttonCfg = b;
-
- buttonCfg = this.configureButton(buttonCfg);
-
- if(buttonCfg)
- buttons.push(buttonCfg);
-
- }, this);
-
- if (EHR.debug){
- buttons.push([
- {text: 'Stores?', name: 'stores', handler: this.store.showStores, scope: this.store},
- {text: 'Errors?', name: 'errors', handler: this.store.showErrors, scope: this.store}
- ])
- }
-
- var button;
- Ext.each(buttons, function(b){
- if(this.rendered)
- button = this.getFooterToolbar().add(b);
- else
- button = this.addButton(b);
-
- if(b.ref)
- this[b.ref] = button;
- }, this);
- this.getFooterToolbar().doLayout();
- },
-
- //@private
- //This method configures each button, including determining whether it should be visible/enabled based on permissions
- configureButton: function(buttonCfg)
- {
- buttonCfg.scope = this;
- buttonCfg.xtype = 'button';
-
- //only show button if user can access this QCState
- if(buttonCfg.requiredQC){
- if(!EHR.Security.hasPermission(buttonCfg.requiredQC, (buttonCfg.requiredPermission || 'insert'), this.store.getQueries())){
- buttonCfg.hidden = true;
- }
- }
-
- return buttonCfg;
- },
-
- //@private
- //configures/adds all the sections of this form
- populateItems: function()
- {
- var toAdd = [];
- if(this.formHeaders){
- Ext.each(this.formHeaders, function(c){
- this.configureItem(c);
- this.configureHeaderItem(c);
- toAdd.push(c);
- }, this);
- }
-
- if(this.formSections){
- Ext.each(this.formSections, function(c){
- this.configureItem(c);
- toAdd.push(c);
- }, this);
- }
-
- if(this.formTabs && this.formTabs.length){
- var tabs = [];
- Ext.each(this.formTabs, function(c){
- this.configureItem(c);
- tabs.push(c);
- }, this);
-
- toAdd.push({
- xtype: 'tabpanel',
- activeTab: 0,
- width: 1110,
- ref: 'queryPanel',
- items: tabs,
- cls: 'extContainer'
- });
- }
-
- this.add(toAdd);
- this.doLayout();
- },
-
- //@private
- configureHeaderItem: function(c)
- {
- EHR.Utils.rApplyIf(c, {
- bindConfig: {
- createRecordOnLoad: true,
- autoBindRecord: true,
- showDeleteBtn: false
- }
- });
- },
-
- //@private
- configureItem: function(c)
- {
- EHR.Utils.rApplyIf(c, {
- collapsible: true,
- border: true,
- //uuid: this.uuid,
- formUUID: this.formUUID,
- formType: this.formType,
- readOnly: this.readOnly,
- bindConfig: {
- disableUnlessBound: false,
- bindOnChange: true
- },
- defaults: {
- bodyBorder: false
- ,border: false
- },
- showStatus: true,
- storeConfig: {
- //xtype: 'ehr-store',
- containerPath: c.containerPath,
- schemaName: c.schemaName,
- queryName: c.queryName,
- viewName: c.viewName || '~~UPDATE~~',
- columns: c.columns || EHR.Metadata.Columns[c.queryName] || '',
- //autoLoad: true,
- storeId: [c.schemaName,c.queryName,c.viewName,c.storeSuffix].join('||'),
- metadata: c.metadata,
- ignoreFilter: 1
- }
- });
- c.importPanel = this;
-
- if(c.xtype == 'ehr-formpanel'){
- c.bindConfig.autoBindRecord = true;
- }
-
- if(this.printFormat)
- c.xtype = 'ehr-printtaskpanel';
- if(this.readOnly){
- c.type = 'ehr-qwppanel';
- }
- },
-
- //@private
- applyTemplates: function(templates)
- {
- Ext.each(templates, function(obj){
- EHR.Ext3Utils.loadTemplateByName(obj.title, obj.storeId);
- }, this);
- },
-
- //@private
- //this was at one point used to add a loading mask to the panel prior to store loading. not currently used.
- setLoadMask: function()
- {
- if(!this.store.isLoading() && EHR.Security.hasLoaded()){
- Ext.Msg.hide();
- delete this.loadMsg;
- }
- else {
- if(!this.loadMsg)
- this.loadMsg = Ext.Msg.wait("Loading...");
-
- this.setLoadMask.defer(500, this);
- }
- },
-
- //@private
- //add a new store to the panel's store collection
- addStore: function(c)
- {
- if (c instanceof EHR.ext.AdvancedStore){
- this.store.add(c);
- }
- },
-
- //@private
- //if a section is removed that contains a store, also remove from the StoreCollection.
- onRemove: function(o)
- {
- if(o.store)
- this.store.remove(o.store);
- },
-
- //@private
- //called when the form is submitted. triggers UI changes, but the StoreCollection does most of the work
- onSubmit: function(o)
- {
- Ext.Msg.wait("Saving Changes...");
-
- //add a context flag to the request to saveRows
- var extraContext = {
- targetQC : o.targetQC,
- errorThreshold: o.errorThreshold,
- successURL : o.successURL,
- importPathway: 'ehr-ext3DataEntry'
- };
-
- //we delay this event so that any modified fields can fire their blur events and/or commit changes
- this.store.commitChanges.defer(300, this.store, [extraContext, true]);
- },
-
- //@private
- //similar to onSubmit, but provides extra notification prior to deleting a form.
- //NOTE: currently instead of deleting directly, the code marks the records with the QCState 'Delete Requested'.
- // A cron script runs periodically to delete these. This is for historical purposes and should be changed to a direct delete
- // at some point.
- requestDelete: function(o)
- {
- Ext.Msg.confirm(
- "Warning",
- "You are about to delete this form and all its data. Are you sure you want to do this?",
- function(v)
- {
- if (v == 'yes')
- {
- Ext.Msg.wait("Discarding Form...");
-
- var extraContext = {
- errorThreshold: o.errorThreshold,
- successURL : o.successURL,
- importPathway: 'ehr-ext3DataEntry'
- };
-
- this.store.requestDeleteAllRecords(extraContext);
- }
- }, this);
- },
-
- //@private
- //when a store validates records on the server, we toggle buttons based on validation status (ie. the most severe error)
- onStoreValidation: function(storeCollection, maxSeverity)
- {
- if(EHR.debug && maxSeverity)
- console.log('Error level: '+maxSeverity);
-
- this.getFooterToolbar().items.each(function(item){
- if(item.disableOn){
- if(maxSeverity && EHR.Utils.errorSeverity[item.disableOn] <= EHR.Utils.errorSeverity[maxSeverity])
- item.setDisabled(true);
- else
- item.setDisabled(false);
- }
- }, this);
- },
-
- //@private
- validateAll: function()
- {
- this.store.each(function(s){
- s.validateRecords(s, null, true, {operation: 'edit'});
- }, this);
- },
-
- //@private
- afterSubmit: function(o, e)
- {
- //console.log('after submit');
- Ext.Msg.hide();
- },
-
- //@private
- //similar to onSubmit, but provides extra confirmation before discarding a form
- discard: function(o)
- {
- Ext.Msg.confirm(
- "Warning",
- "You are about to discard this form. All data will be deleted. Are you sure you want to do this?",
- function(v)
- {
- if (v == 'yes')
- {
- Ext.Msg.wait("Deleting Records...");
-
- //add a context flag to the request to saveRows
- var extraContext = {
- targetQC : o.targetQC,
- errorThreshold: o.errorThreshold,
- successURL : LABKEY.ActionURL.getParameter('srcURL') || LABKEY.ActionURL.getParameter('returnUrl') || LABKEY.ActionURL.getParameter('returnURL') || LABKEY.ActionURL.buildURL("ehr", "dataEntry.view"),
- importPathway: 'ehr-ext3DataEntry'
- };
-
- //we delay this event so that any modified fields can fire their blur events and/or commit changes
- this.store.deleteAllRecords.defer(300, this.store, [extraContext]);
- }
- },
- this);
- },
-
- //@private
- //helper to test whether the stores on this form have any changes
- isDirty: function()
- {
- return this.store.isDirty();
- },
-
- //@private
- //helper to validate all records in this form
- isValid: function()
- {
- return this.store.isValid();
- }
-});
-
-/**
- * Constructs a new EHR.ext.ImportPanel.TaskPanel based on the supplied config
- * @class
- * An extension of EHR.ext.ImportPanel.Base that generates the outer panel used on the import page for Tasks.
- * Primrily configures the ehr.tasks store and configures each panel item with consistent sorts/filters (will only load records where the taskId matches the current form's UUID). See EHR.ext.ImportPanel.Base
- * for additional documentation.
- *
- * @constructor
- * @param {object} config Configuration properties. This may contain any of the configuration properties supported in EHR.ext.ImportPanel.Base in addition to those below.
- * @augments EHR.ext.ImportPanel.Base
- * @param {string} [config.taskHeaderMetaSources] A comma separated list of Metadata Sources (see EHR.Metadata) applied to the ehr.tasks store
- */
-EHR.ext.ImportPanel.TaskPanel = Ext.extend(EHR.ext.ImportPanel.Base, {
-
- addStore: function(c)
- {
- EHR.ext.ImportPanel.TaskPanel.superclass.addStore.apply(this, arguments);
-
- if (c.storeId == 'ehr||tasks||||'){
- this.mon(c, 'load', function(store){
-
-
- if(store.getCount()){
-
- var rec= store.getAt(0);
-
- LABKEY.NavTrail.setTrail(rec.get('title'));
- }
- },this)
- }
- },
-
- initComponent: function()
- {
-
- this.formUUID = this.formUUID || LABKEY.Utils.generateUUID();
- this.formHeaders = this.formHeaders || [];
- this.formHeaders.unshift({
- xtype: 'ehr-headerformpanel',
- formType: this.formType,
- schemaName: 'ehr',
- queryName: 'tasks',
- keyField: 'taskid',
- ref: 'importPanelHeader',
- //uuid: this.uuid,
- formUUID: this.formUUID,
- importPanel: this,
- readOnly: this.readOnly,
- metadata: EHR.Metadata.getTableMetadata('tasks', this.taskHeaderMetaSources || ['Task']),
- storeConfig: {
- canSaveInTemplate: false
- }
- });
-
- EHR.ext.ImportPanel.TaskPanel.superclass.initComponent.call(this, arguments);
- },
-
- //@override
- configureItem: function(c)
- {
- EHR.ext.ImportPanel.TaskPanel.superclass.configureItem.apply(this, arguments);
- c.storeConfig.filterArray = [
- LABKEY.Filter.create('taskId', this.formUUID, LABKEY.Filter.Types.EQUAL),
- LABKEY.Filter.create('qcstate/label', 'Delete Requested', LABKEY.Filter.Types.NEQ)
- ];
-
- var sortCol = [];
- if(c.storeConfig && c.storeConfig.columns){
- var cols = c.storeConfig.columns.split(',');
- Ext.each(['id', 'id/curlocation/location'], function(item){
- Ext.each(cols, function(o){
- if(o && o.match(new RegExp('^'+item+'$', 'i'))){
- sortCol.push(item)
- }
- }, this);
- }, this);
-
- }
- if(sortCol.length){
- sortCol = Ext.unique(sortCol);
- c.storeConfig.sort = sortCol.join(',');
- }
- }
-});
-
-/**
- * Constructs a new EHR.ext.ImportPanel.TaskDetailsPanel based on the supplied config.
- * @class
- * An extension of EHR.ext.ImportPanel.Base that generates the outer panel used on the import page for Tasks.
- * Unlike EHR.ext.ImportPanel.TaskPanel, this panel creates a read-only panel that uses QueryWebParts instead of EHR grids.
- * See EHR.ext.ImportPanel.Base for additional documentation.
- *
- * @constructor
- * @param {object} config Configuration properties. This may contain any of the configuration properties supported in EHR.ext.ImportPanel.Base.
- * @augments EHR.ext.ImportPanel.Base
- */
-EHR.ext.ImportPanel.TaskDetailsPanel = Ext.extend(EHR.ext.ImportPanel.Base, {
- initComponent: function()
- {
- this.formUUID = this.formUUID || LABKEY.Utils.generateUUID();
- this.formHeaders = this.formHeaders || [];
- this.formHeaders.unshift({
- xtype: 'ehr-qwppanel',
- title: '',
- collapsible: false,
- formType: this.formType,
- schemaName: 'ehr',
- queryName: 'tasks',
- keyField: 'taskid',
- ref: 'importPanelHeader',
- formUUID: this.formUUID,
- importPanel: this,
- readOnly: this.readOnly,
- storeConfig: {
- canSaveInTemplate: false
- }
- });
-
- EHR.ext.ImportPanel.TaskDetailsPanel.superclass.initComponent.call(this, arguments);
- },
-
- //@override
- configureItem: function(c)
- {
- if(!c.queryName || !c.schemaName){
- c.hidden = true;
- return;
- }
-
- EHR.ext.ImportPanel.TaskDetailsPanel.superclass.configureItem.apply(this, arguments);
- c.filterArray = [LABKEY.Filter.create('taskId', this.formUUID, LABKEY.Filter.Types.EQUAL)];
-
- if(c.xtype != 'ehr-detailsview'){
- c.xtype = 'ehr-qwppanel';
- c.autoLoadQuery = true;
- c.collapsed = false;
- }
-
- c.storeConfig = null;
- c.style = 'padding-bottom:20px;';
-
- }
-});
-
-/**
- * Constructs a new EHR.ext.ImportPanel.RequestPanel based on the supplied config.
- * @class
- * An extension of EHR.ext.ImportPanel.Base that generates the outer panel used on the import page for Requests.
- * The primary difference is that this class sets standardized sorts/filter on the component stores in the configureItem method (will only load records where the requestId matches the current form's UUID).
- * See EHR.ext.ImportPanel.Base for additional documentation.
- *
- * @constructor
- * @param {object} config Configuration properties. This may contain any of the configuration properties supported in EHR.ext.ImportPanel.Base.
- * @augments EHR.ext.ImportPanel.Base
- *
- */
-EHR.ext.ImportPanel.RequestPanel = Ext.extend(EHR.ext.ImportPanel.Base, {
- initComponent: function()
- {
- this.formUUID = this.formUUID || LABKEY.Utils.generateUUID();
- this.formHeaders = this.formHeaders || [];
-
- this.formHeaders.unshift({
- xtype: 'ehr-headerformpanel',
- formType: this.formType,
- schemaName: 'ehr',
- queryName: 'requests',
- keyField: 'requestid',
- ref: 'importPanelHeader',
- formUUID: this.formUUID,
- importPanel: this,
- readOnly: this.readOnly,
- metadata: EHR.Metadata.getTableMetadata('requests', ['Request']),
- storeConfig: {
- canSaveInTemplate: false
- }
- });
-
- this.allowableButtons = this.allowableButtons || 'VALIDATE,REQUEST';
-
- EHR.ext.ImportPanel.RequestPanel.superclass.initComponent.call(this, arguments);
- },
-
- //@override
- configureItem: function(c){
- EHR.ext.ImportPanel.RequestPanel.superclass.configureItem.apply(this, arguments);
- c.storeConfig.filterArray = [
- LABKEY.Filter.create('requestId', this.formUUID, LABKEY.Filter.Types.EQUAL),
- LABKEY.Filter.create('qcstate/label', 'Delete Requested', LABKEY.Filter.Types.NEQ)
- ];
- }
-});
-
-/**
- * Constructs a new EHR.ext.ImportPanel.RequestDetailsPanel based on the supplied config.
- * @class
- * An extension of EHR.ext.ImportPanel.Base that generates the outer panel used on the import page for Requests.
- * Unlike EHR.ext.ImportPanel.RequestPanel, this creates a read-only panel in which QueryWebParts are used instead of EHR grids.
- * See EHR.ext.ImportPanel.Base for additional documentation.
- *
- * @constructor
- * @param {object} config Configuration properties. This may contain any of the configuration properties supported in EHR.ext.ImportPanel.Base.
- * @augments EHR.ext.ImportPanel.Base
- */
-EHR.ext.ImportPanel.RequestDetailsPanel = Ext.extend(EHR.ext.ImportPanel.Base, {
- initComponent: function()
- {
- this.formUUID = this.formUUID || LABKEY.Utils.generateUUID();
- this.formHeaders = this.formHeaders || [];
- this.formHeaders.unshift({
- xtype: 'ehr-qwppanel',
- title: '',
- collapsible: false,
- formType: this.formType,
- queryConfig: {
- showDetailsColumn: true
- },
- schemaName: 'ehr',
- queryName: 'requests',
- keyField: 'requestid',
- ref: 'importPanelHeader',
- formUUID: this.formUUID,
- importPanel: this,
- readOnly: true,
- storeConfig: {
- canSaveInTemplate: false
- }
- });
-
- this.store = new Ext.util.MixedCollection();
- this.store.getQueries = function(){
- var queries = [];
- this.each(function(s){
- queries.push({
- schemaName: s.schemaName,
- queryName: s.queryName
- })
- }, this);
- return queries;
- };
-
- EHR.ext.ImportPanel.RequestDetailsPanel.superclass.initComponent.call(this, arguments);
- },
-
- //@override
- configureItem: function(c)
- {
- if(!c.queryName || !c.schemaName){
- c.hidden = true;
- return;
- }
-
- EHR.ext.ImportPanel.RequestDetailsPanel.superclass.configureItem.apply(this, arguments);
- c.filterArray = [LABKEY.Filter.create('requestId', this.formUUID, LABKEY.Filter.Types.EQUAL)];
-
- if(c.xtype != 'ehr-detailsview'){
- c.xtype = 'ehr-qwppanel';
- c.autoLoadQuery = true;
- c.collapsed = false;
- c.queryConfig = {
- showDetailsColumn: true
- }
- }
-
- c.storeConfig = null;
- c.style = 'padding-bottom:20px;';
-
- if(c.queryName && c.schemaName){
- this.store.add({
- schemaName: c.schemaName,
- queryName: c.queryName
- })
- }
- }
-});
-
-/**
- * Constructs a new EHR.ext.ImportPanel.SimpleImportPanel based on the supplied config.
- * @class
- * An extension of EHR.ext.ImportPanel.Base that generates a stripped down ImportPanel. It is used by manageRecord.html,
- * which is a generic page to allow editing of any record from a single query, assuming you provide the table and PK.
- * See EHR.ext.ImportPanel.Base for additional documentation.
- *
- * @constructor
- * @param {object} config Configuration properties. This may contain any of the configuration properties supported in EHR.ext.ImportPanel.Base.
- * @augments EHR.ext.ImportPanel.Base
- */
-EHR.ext.ImportPanel.SimpleImportPanel = Ext.extend(EHR.ext.ImportPanel.Base, {
- initComponent: function()
- {
- EHR.ext.ImportPanel.SimpleImportPanel.superclass.initComponent.call(this, arguments);
- },
-
- //@override
- configureItem: function(c)
- {
- EHR.ext.ImportPanel.SimpleImportPanel.superclass.configureItem.apply(this, arguments);
- c.bindConfig.showDeleteBtn = false;
- c.bindConfig.bindOnChange = true;
- c.bindConfig.autoBindRecord = true;
-
- if(c.keyField && c.keyValue){
- c.storeConfig.filterArray = [
- LABKEY.Filter.create(c.keyField, c.keyValue, LABKEY.Filter.Types.EQUAL)
- ];
- }
- else {
- delete c.storeConfig.filterArray;
- c.storeConfig.maxRows = 0;
- c.storeConfig.loadMetadataOnly = true;
- }
- }
-});
-
-/**
- * A static set of button config objects used by EHR.ext.ImportPanel.Base subclasses.
- * @class
- * These are a collection of static button config objects used by EHR.ext.ImportPanel
- * The intent is to place the code behind buttons in the single location, since these are often shared across multiple forms.
- *
- * Button configs are processed in ImportPanel.Base.configureButton. They support the following properties, in addition to standard Ext config:
- *
targetQC: If provided, when the form is submitted using this button it will set the QCState on all the records to this value. Must provide a QCStateLabel, not the numeric QCState
- *
requiredQC: If provided, the user must has insert privledges over this QCState on all tables in this form, or this button will be hidden. Must provide a QCStateLabel, not the numeric QCState
- *
errorThreshold: Either 'WARN', 'INFO' or 'ERROR'. If the max error severity of any record in this form is above this value, the button will be disabled
- *
successURL: On a successful submit, the page will navigate to this URL
- */
-EHR.ext.ImportPanel.Buttons = {
- /**
- * This button is the 'Save' button on most forms. Will save records using their current QCState, or 'In Progress' if none is provided
- */
- SAVEDRAFT: function(){return {
- text: 'Save Draft',
- name: 'saveDraft',
- //targetQC: 'In Progress',
- requiredQC: 'In Progress',
- errorThreshold: 'WARN',
- //successURL: LABKEY.ActionURL.getParameter('srcURL') || LABKEY.ActionURL.buildURL("ehr", "dataEntry.view"),
- disabled: true,
- ref: 'saveDraftBtn',
- handler: this.onSubmit,
- disableOn: 'ERROR',
- scope: this
- }
- },
- /**
- * A variation on the normal submit button, except will be hidden to non-admins. It was created so MPRs could have a submit button visible only to admins (permission-based logic was not a sufficient distinction otherwise)
- */
- SUBMITADMIN: function(){return {
- text: 'Submit Final',
- name: 'submit',
- requiredQC: 'Completed',
- requiredPermission: 'admin',
- targetQC: 'Completed',
- errorThreshold: 'INFO',
- successURL : LABKEY.ActionURL.getParameter('srcURL') || LABKEY.ActionURL.getParameter('returnUrl') || LABKEY.ActionURL.getParameter('returnURL') || LABKEY.ActionURL.buildURL("ehr", "dataEntry.view"),
- disabled: true,
- ref: 'submitBtn',
- handler: function(o){
- Ext.Msg.confirm('Finalize Form', 'You are about to finalize this form. Do you want to do this?', function(v){
- if(v=='yes')
- this.onSubmit(o);
- }, this);
- },
- disableOn: 'WARN',
- scope: this
- }
- },
- /**
- * The standard 'Submit Final' button. Will change the QCState of all records to 'Completed' and submit the form
- */
- SUBMIT: function(){return {
- text: 'Submit Final',
- name: 'submit',
- requiredQC: 'Completed',
- targetQC: 'Completed',
- errorThreshold: 'INFO',
- successURL : LABKEY.ActionURL.getParameter('srcURL') || LABKEY.ActionURL.getParameter('returnUrl') || LABKEY.ActionURL.getParameter('returnURL') || LABKEY.ActionURL.buildURL("ehr", "dataEntry.view"),
- disabled: true,
- ref: 'submitBtn',
- handler: function(o){
- Ext.Msg.confirm('Finalize Form', 'You are about to finalize this form. Do you want to do this?', function(v){
- if(v=='yes')
- this.onSubmit(o);
- }, this);
- },
- disableOn: 'WARN',
- scope: this
- }
- },
- /**
- * A button unique to deaths. This is the normal 'submit final' button, except it provides stronger warning language.
- */
- SUBMITDEATH: function(){return {
- text: 'Submit Final',
- name: 'submit',
- requiredQC: 'Completed',
- targetQC: 'Completed',
- errorThreshold: 'INFO',
- successURL : LABKEY.ActionURL.getParameter('srcURL') || LABKEY.ActionURL.getParameter('returnUrl') || LABKEY.ActionURL.getParameter('returnURL') || LABKEY.ActionURL.buildURL("ehr", "dataEntry.view"),
- disabled: true,
- ref: 'submitBtn',
- handler: function(o){
- Ext.Msg.confirm('Finalize Death', 'You are about to finalize this death record on this animal. This will end all treatments, problems, assignments and housing. BE ABSOLUTELY SURE YOU WANT TO DO THIS BEFORE CLICKING SUBMIT.', function(v){
- if(v=='yes')
- this.onSubmit(o);
- }, this);
- },
- disableOn: 'WARN',
- scope: this
- }
- },
- /**
- * An admin button that will force records to submit with a QCState of 'Completed', ignoring validation errors. Created for situations where there is a valid reason to override normal validation errors.
- */
- FORCESUBMIT: function(){return {
- text: 'Force Submit',
- name: 'submit',
- requiredQC: 'Completed',
- targetQC: 'Completed',
- requiredPermission: 'admin',
- errorThreshold: 'ERROR',
- successURL : LABKEY.ActionURL.getParameter('srcURL') || LABKEY.ActionURL.getParameter('returnUrl') || LABKEY.ActionURL.getParameter('returnURL') || LABKEY.ActionURL.buildURL("ehr", "dataEntry.view"),
- disabled: true,
- ref: 'foreceSubmitBtn',
- handler: function(o){
- Ext.Msg.confirm('Force Finalize Form', 'You are about to finalize this form. Do you want to do this?', function(v){
- if(v=='yes')
- this.onSubmit(o);
- }, this);
- },
- disableOn: 'SEVERE',
- scope: this
- }
- },
- /**
- * Necropsy-specific button that provides UI to copy records from an existing necropsy into the current one. The process is similar to using the earlier necropsy as a template.
- */
- COPYNECROPSY: function(){return {
- text: 'Copy Necropsy',
- name: 'copyNecropsy',
- requiredQC: 'Completed',
- disabled: false,
- ref: 'copyNecropsyBtn',
- handler: function(o){
- var theWindow = new Ext.Window({
- closeAction:'hide',
- title: 'Copy From Necropsy',
- //height: 900,
- items: [{
- width: 350,
- xtype: 'ehr-necropsycopy',
- targetStore: this.store
- }]
- }).show();
- },
- scope: this
- }
- },
- /**
- * Necrospy-specific button that will create/update a record (depending on whether one already exists) in either Deaths or Prenatal Deaths (depending on the ID prefix of the animal's name).
- */
- FINALIZEDEATH: function(){return {
- text: 'Finalize Death',
- name: 'finalizeDeath',
- requiredQC: 'Completed',
- //targetQC: 'Completed',
- errorThreshold: 'INFO',
- //successURL: LABKEY.ActionURL.getParameter('srcURL') || LABKEY.ActionURL.buildURL("ehr", "dataEntry.view"),
- disabled: true,
- ref: 'finalizeDeathBtn',
- handler: function(o){
- Ext.Msg.confirm('Finalize Death', 'You are about to finalize this death record on this animal. This will end all treatments, problems, assignments and housing. BE ABSOLUTELY SURE YOU WANT TO DO THIS BEFORE CLICKING SUBMIT.', function(v){
- if(v=='yes'){
- Ext.Msg.wait('Saving...');
-
- var store = Ext.StoreMgr.get('study||Necropsies||||');
- var record = store.getAt(0);
- var store2 = Ext.StoreMgr.get('study||Weight||||');
- var record2 = store2.getAt(0);
-
- if(!record ||
- !record.get('Id') ||
- !record.get('causeofdeath') ||
- !record.get('timeofdeath') ||
- !record.get('project')
- ){
- alert('Must provide Id, project, type of death, and time of death');
- return;
- }
-
- var Id = record.get('Id');
- var obj = {
- Id: Id,
- project: record.get('project'),
- date: record.get('timeofdeath'),
- cause: record.get('causeofdeath'),
- manner: record.get('mannerofdeath'),
- necropsy: record.get('caseno'),
- parentid: record.get('objectid'),
- weight: record2.get('weight'),
- necropsyDate: record.get('date')
- };
-
- var queryName;
- if(Id.match(/^pd/))
- queryName = 'Prenatal Deaths';
- else
- queryName = 'Deaths';
-
- //we look for a death record
- LABKEY.Query.selectRows({
- schemaName: 'study',
- queryName: queryName,
- scope: this,
- filterArray: [
- LABKEY.Filter.create('Id', Id, LABKEY.Filter.Types.EQUAL)
- ],
- success: function(data){
- if(data && data.rows && data.rows.length){
- obj.lsid = data.rows[0].lsid;
- LABKEY.Query.updateRows({
- schemaName: 'study',
- queryName: queryName,
- rows: [obj],
- scope: this,
- success: function(data){
- Ext.Msg.hide();
- alert('Success updating '+queryName+' from necropsy for '+Id);
- },
- failure: EHR.Utils.onError
- });
- }
- //otherwise we create a new record
- else {
-
- function doInsert(){
- LABKEY.Query.insertRows({
- schemaName: 'study',
- queryName: queryName,
- scope: this,
- rows: [obj],
- success: function(data){
- Ext.Msg.hide();
- alert('Success inserting into '+queryName+' from necropsy for '+Id)
- },
- failure: function(error){
- alert('ERROR: ' + (error.msg || error.exception));
- EHR.Utils.onError(error);
- }
- });
- }
-
- if(queryName=='Prenatal Deaths'){
- var selectorWin = new Ext.Window({
- closeAction:'hide',
- title: 'Prenatal Death',
- width: 450,
- items: [{
- xtype: 'form',
- style: 'padding: 5px;',
- items: [{
- emptyText:''
- ,fieldLabel: 'Species'
- ,ref: '../speciesField'
- ,xtype: 'combo'
- ,displayField:'common'
- ,valueField: 'common'
- ,typeAhead: true
- ,lazyInit: false
- ,mode: 'local'
- ,triggerAction: 'all'
- ,editable: true
- ,store: new LABKEY.ext.Store({
- schemaName: 'ehr_lookups',
- queryName: 'species',
- sort: 'common',
- autoLoad: true
- })
- },{
- emptyText:''
- ,fieldLabel: 'Gender'
- ,ref: '../genderField'
- ,xtype: 'combo'
- ,displayField:'meaning'
- ,valueField: 'code'
- ,typeAhead: true
- ,lazyInit: false
- ,mode: 'local'
- ,triggerAction: 'all'
- ,editable: true
- ,store: new LABKEY.ext.Store({
- schemaName: 'ehr_lookups',
- queryName: 'gender_codes',
- sort: 'meaning',
- autoLoad: true
- })
- },{
- emptyText:''
- ,fieldLabel: 'Dam'
- ,ref: '../damField'
- ,xtype: 'combo'
- ,displayField:'Id'
- ,valueField: 'Id'
- ,typeAhead: true
- ,lazyInit: false
- ,mode: 'local'
- ,triggerAction: 'all'
- ,editable: true
- ,store: new LABKEY.ext.Store({
- schemaName: 'study',
- sql: "SELECT * FROM study.demographics WHERE gender.code = 'f'",
- //queryName: 'demographics',
- sort: 'id',
- autoLoad: true
- //maxRows: 100,
-
- })
- }]
- }],
- buttons: [{
- text:'Submit',
- disabled:false,
- ref: '../submit',
- scope: this,
- handler: function(s){
- obj.gender = s.ownerCt.ownerCt.genderField.getValue();
- obj.species = s.ownerCt.ownerCt.speciesField.getValue();
- obj.dam = s.ownerCt.ownerCt.damField.getValue();
-
- doInsert();
-
- s.ownerCt.ownerCt.hide();
- }
- },{
- text: 'Close',
- scope: this,
- handler: function(s){
- s.ownerCt.ownerCt.hide();
- return;
- }
- }]
- });
-
- selectorWin.show();
- }
- else {
- doInsert();
- }
- }
- },
- failure: function(error){
- alert('ERROR: ' + (error.msg || error.exception));
- EHR.Utils.onError(error);
- }
- });
- }
- }, this);
- },
- disableOn: 'ERROR',
- scope: this
- }
- },
- /**
- * Will attempt to convert the QCState of all records to 'Completed' and submit the form. Similar to the other SUBMIT button; however, this button does not require a confirmation prior to submitting.
- */
- BASICSUBMIT: function(){return {
- text: 'Submit',
- name: 'basicsubmit',
- requiredQC: 'Completed',
- errorThreshold: 'INFO',
- successURL : LABKEY.ActionURL.getParameter('srcURL') || LABKEY.ActionURL.getParameter('returnUrl') || LABKEY.ActionURL.getParameter('returnURL') || LABKEY.ActionURL.buildURL("ehr", "dataEntry.view"),
- disabled: true,
- ref: 'submitBtn',
- handler: function(o){
- this.onSubmit(o);
- },
- disableOn: 'WARN',
- scope: this
- }
- },
- /**
- * Will attempt to convert the QCState of all records to 'Completed', submit the current form then reload with a blank new one. Designed for single-record entry.
- */
- SUBMITANDNEXT: function(){return {
- text: 'Submit And Next',
- name: 'submit',
- requiredQC: 'Completed',
- targetQC: 'Completed',
- errorThreshold: 'INFO',
- successURL: LABKEY.ActionURL.buildURL("ehr", LABKEY.ActionURL.getAction(), null, {
- schemaName: LABKEY.ActionURL.getParameter('schemaName'),
- queryName: LABKEY.ActionURL.getParameter('queryName'),
- formtype: LABKEY.ActionURL.getParameter('formtype')
- }),
- disabled: true,
- ref: 'submitNextBtn',
- handler: this.onSubmit,
- disableOn: 'WARN',
- scope: this
- }
- },
- /**
- * Will attempt to convert all records to the QCState 'Review Required' and submit the form.
- */
- REVIEW: function(){return {
- text: 'Submit for Review',
- name: 'review',
- requiredQC: 'Review Required',
- targetQC: 'Review Required',
- errorThreshold: 'WARN',
- successURL : LABKEY.ActionURL.getParameter('srcURL') || LABKEY.ActionURL.getParameter('returnUrl') || LABKEY.ActionURL.getParameter('returnURL') || LABKEY.ActionURL.buildURL("ehr", "dataEntry.view"),
- disabled: true,
- ref: 'reviewBtn',
- disableOn: 'ERROR',
- handler: function(o){
- var theWindow = new Ext.Window({
- closeAction:'hide',
- title: 'Submit For Review',
- width: 350,
- scope: this,
- buttons: [{
- text:'Submit',
- disabled:false,
- ref: '../submit',
- scope: this,
- handler: function(btn){
- var assignedTo = btn.ownerCt.ownerCt.theForm.assignedTo.getValue();
- if(!assignedTo){
- alert('Must assign this task to someone');
- return;
- }
- var taskStore = Ext.StoreMgr.get('ehr||tasks||||');
- taskStore.getAt(0).set('assignedto', assignedTo);
- theWindow.hide();
- this.onSubmit(o);
- }
- },{
- text: 'Cancel',
- scope: this,
- handler: function(){
- theWindow.hide();
- }
- }],
- items: [{
- xtype: 'form',
- ref: 'theForm',
- bodyStyle: 'padding:5px;',
- items: [{
- xtype: 'combo',
- fieldLabel: 'Assign To',
- width: 200,
- triggerAction: 'all',
- mode: 'local',
- store: new LABKEY.ext.Store({
- xtype: 'labkey-store',
- schemaName: 'core',
- queryName: 'PrincipalsWithoutAdmin',
- columns: 'UserId,DisplayName',
- sort: 'Type,DisplayName',
- autoLoad: true,
- listeners: {
- //scope: this,
- load: function(s){
- var recIdx = s.find('DisplayName', (o.ownerCt.ownerCt.reviewRequiredRecipient || 'dataentry (LDAP)'));
- if(recIdx!=-1){
- theWindow.theForm.assignedTo.setValue(s.getAt(recIdx).get('UserId'));
- }
- }
- }
- }),
- displayField: 'DisplayName',
- valueField: 'UserId',
- ref: 'assignedTo',
- tpl: '
{DisplayName:htmlEncode}
'
- }]
- }]
- });
- theWindow.show();
- },
- scope: this
- }
- },
- /**
- * Will attempt to convert all records to a QCState of 'scheduled' and submit the form.
- */
- SCHEDULE: function(){return {
- text: 'Schedule',
- name: 'review',
- requiredQC: 'Scheduled',
- targetQC: 'Scheduled',
- errorThreshold: 'WARN',
- successURL : LABKEY.ActionURL.getParameter('srcURL') || LABKEY.ActionURL.getParameter('returnUrl') || LABKEY.ActionURL.getParameter('returnURL') || LABKEY.ActionURL.buildURL("ehr", "dataEntry.view"),
- disabled: true,
- ref: 'scheduledBtn',
- disableOn: 'ERROR',
- handler: this.onSubmit,
- scope: this
- }
- },
- /**
- * Re-runs server-side validation on all records. Primarily useful if something goes wrong in the normal validation process and an error will not disappear as it should
- */
- VALIDATE: function(){return {
- text: 'Validate',
- name: 'validate',
- //targetQC: 'In Progress',
- //errorThreshold: 'WARN',
- //successURL: LABKEY.ActionURL.buildURL("ehr", "dataEntry.view"),
- disabled: false,
- ref: 'validateBtn',
- handler: this.validateAll,
- //disableOn: 'ERROR',
- scope: this
- }
- },
- /**
- * Will attempt to convert all records to a QCState of 'Delete Requested' and submit the form. NOTE: this button and the requestDelete method should really be converted to perform a true delete
- */
- DISCARD: function(){return {
- text: 'Discard',
- name: 'discard',
- ref: 'discardBtn',
- targetQC: 'Delete Requested',
- requiredQC: 'Delete Requested',
- successURL : LABKEY.ActionURL.getParameter('srcURL') || LABKEY.ActionURL.getParameter('returnUrl') || LABKEY.ActionURL.getParameter('returnURL') || LABKEY.ActionURL.buildURL("ehr", "dataEntry.view"),
- handler: this.requestDelete,
- //handler: this.onSubmit,
- scope: this
- }
- },
- /**
- * Similar to DISCARD, but the button has a different label. In some situations 'Discard' was interpreted more like 'discard changes'. NOTE: this button and the requestDelete method should really be converted to perform a true delete
- */
- DELETEBTN: function(){return {
- text: 'Delete',
- name: 'delete',
- ref: 'deleteBtn',
- targetQC: 'Delete Requested',
- requiredQC: 'Delete Requested',
- successURL : LABKEY.ActionURL.getParameter('srcURL') || LABKEY.ActionURL.getParameter('returnUrl') || LABKEY.ActionURL.getParameter('returnURL') || LABKEY.ActionURL.buildURL("ehr", "dataEntry.view"),
- handler: this.requestDelete,
- //handler: this.onSubmit,
- scope: this
- }
- },
- /**
- * Will submit/save the form and then return to the dataEntry page. It does not alter the QCState of records.
- */
- CLOSE: function(){return {
- text: 'Save & Close',
- name: 'closeBtn',
- //targetQC: 'In Progress',
- requiredQC: 'In Progress',
- errorThreshold: 'WARN',
- successURL : LABKEY.ActionURL.getParameter('srcURL') || LABKEY.ActionURL.getParameter('returnUrl') || LABKEY.ActionURL.getParameter('returnURL') || LABKEY.ActionURL.buildURL("ehr", "dataEntry.view"),
- disabled: true,
- ref: 'closeBtn',
- handler: this.onSubmit,
- disableOn: 'ERROR',
- scope: this
- }
- },
- /**
- * This button will open an EHR.ext.PrintTaskPanel with this form; however, this class is not fully completed at time of writing. The intent was to provide a print-friendly view of the records. This button is not currently used.
- */
- PRINT: function(){return {
- text: 'Print',
- name: 'print',
- ref: 'printBtn',
- handler: function(){
- this.mon(this.store, 'commitcomplete', function(){
- window.open(LABKEY.ActionURL.buildURL('ehr','printTask.view', null, {_print: 1, formtype:this.formType, taskid: this.formUUID}))
- }, this, {single: true});
-
- this.onSubmit({
- text: 'Print',
- name: 'print'
- });
- },
- requiredQC: 'In Progress',
- errorThreshold: 'WARN',
- disabled: true,
- disableOn: 'ERROR',
- scope: this
- }
- },
- /**
- * This button will attempt to convert the QCState of records to 'Request: Pending' and submit the form. Default button for request pages.
- */
- REQUEST: function(){return {
- text: 'Request',
- name: 'request',
- targetQC: 'Request: Pending',
- requiredQC: 'Request: Pending',
- errorThreshold: 'WARN',
- successURL : LABKEY.ActionURL.getParameter('srcURL') || LABKEY.ActionURL.getParameter('returnUrl') || LABKEY.ActionURL.getParameter('returnURL') || LABKEY.ActionURL.buildURL("ehr", "requestServices.view"),
- disabled: true,
- ref: 'requestBtn',
- handler: this.onSubmit,
- disableOn: 'WARN',
- scope: this
- }
- },
- /**
- * This button will convert the QCState on records to 'Request: Approved' and submit. Not currently used in the EHR, because requests are better managed through the tabbed UI on the dataEntry page.
- */
- APPROVE: function(){return {
- text: 'Approve Request',
- name: 'approve',
- targetQC: 'Request: Approved',
- requiredQC: 'Request: Approved',
- errorThreshold: 'WARN',
- successURL : LABKEY.ActionURL.getParameter('srcURL') || LABKEY.ActionURL.getParameter('returnUrl') || LABKEY.ActionURL.getParameter('returnURL') || LABKEY.ActionURL.buildURL("ehr", "dataEntry.view"),
- disabled: true,
- ref: 'approveBtn',
- handler: this.onSubmit,
- disableOn: 'ERROR',
- scope: this
- }
- },
- /**
- * This button will navigate the user to the non-read only manageTask page, showing the current task. It was designed to be used on a read-only TaskDetailsPanel.
- */
- EDIT: function(){return {
- text: 'Edit',
- name: 'edit',
- targetQC: 'Completed',
- requiredQC: 'Completed',
- errorThreshold: 'WARN',
- handler: function(){
- window.onbeforeunload = Ext.emptyFn;
- window.location = LABKEY.ActionURL.buildURL('ehr','manageTask.view', null, {formtype:this.formType, taskid: this.formUUID});
- },
- disabled: false,
- ref: 'editBtn',
- disableOn: 'ERROR'
- }
- },
- /**
- * This button will only appear for RequestAdmins and is designed to allow them to edit the ehr.request record associated with the request for the purpose of editing the notification recipients. It was designed to be added to a RequestDetailsPanel.
- */
- EDITCONTACTS: function(){return {
- text: 'Edit Contacts',
- name: 'edit',
- requiredQC: 'Request: Approved',
- requiredPermission: 'admin',
- errorThreshold: 'WARN',
- handler: function(){
- window.onbeforeunload = Ext.emptyFn;
- window.location = LABKEY.ActionURL.buildURL('ehr','manageRecord.view', null, {schemaName: 'ehr', queryName: 'Requests', keyField: 'requestid', key: this.formUUID, update: 1});
- },
- disabled: false,
- ref: 'editBtn',
- disableOn: 'ERROR'
- }
- }
-};
diff --git a/ehr/resources/web/ehr/ext3/ehrMetaHelper.js b/ehr/resources/web/ehr/ext3/ehrMetaHelper.js
deleted file mode 100644
index f772d1332..000000000
--- a/ehr/resources/web/ehr/ext3/ehrMetaHelper.js
+++ /dev/null
@@ -1,555 +0,0 @@
-/*
- * Copyright (c) 2013-2019 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
- */
-
-
-/**
- * @class
- * @name EHR.ext.metaHelper
- * @description
- * EHR.ext.metaHelper provides a number of static helpers designed to assist
- * with the process of converting metadata config objects into Ext configuration.
- */
-
-
-EHR.ext.metaHelper = new function(){
- return {
-
- /**
- * Creates a new Ext editor, tailored for a form, based on a supplied LabKey metadata object. This will utilize a meta.formConfig object, if provided.
- * @param {Object} meta The LabKey metadata object
- * @param {Object} config An optional extra config object to be merged onto the Ext config object
- * @returns An ext editor
- */
- getFormEditor: function(meta, config){
- var editorConfig = EHR.ext.metaHelper.getFormEditorConfig(meta, config);
- return Ext.ComponentMgr.create(editorConfig);
- },
-
- /**
- * Creates a new Ext editor, tailored for a grid, based on a supplied LabKey metadata object. This will utilize the meta.gridConfig object, if provided.
- * @param {Object} meta The LabKey metadata object
- * @param {Object} config An optional extra config object to be merged onto the Ext config object
- * @returns An ext editor
- */
- getGridEditor: function(meta, config){
- var editorConfig = EHR.ext.metaHelper.getGridEditorConfig(meta, config);
- return Ext.ComponentMgr.create(editorConfig);
- },
-
- /**
- * Returns the config object to create a new Ext editor, tailored for a grid, based on a supplied LabKey metadata object. This will utilize the meta.gridConfig object, if provided.
- * @param {Object} meta The LabKey metadata object
- * @param {Object} config An optional extra config object to be merged onto the Ext config object
- * @returns {Object} An ext config object
- */
- getGridEditorConfig: function(meta, config){
- //this produces a generic editor
- var editor = EHR.ext.metaHelper.getDefaultEditorConfig(meta);
-
- //for multiline fields:
- if(editor.editable && meta.inputType == 'textarea'){
- editor = new LABKEY.ext.LongTextField({
- columnName: editor.dataIndex
- });
- }
-
- //now we allow overrides of default behavior, in order of precedence
- if(meta.editorConfig)
- EHR.Utils.rApply(editor, meta.editorConfig);
- if(meta.gridEditorConfig)
- EHR.Utils.rApply(editor, meta.gridEditorConfig);
- if(config)
- EHR.Utils.rApply(editor, config);
-
- return editor;
- },
-
- /**
- * Returns the config object to create a new Ext editor, tailored for a form, based on a supplied LabKey metadata object. This will utilize the meta.formConfig object, if provided.
- * @param {Object} meta The LabKey metadata object
- * @param {Object} config An optional extra config object to be merged onto the Ext config object
- * @returns {Object} An ext config object
- */
- getFormEditorConfig: function(meta, config){
- var editor = EHR.ext.metaHelper.getDefaultEditorConfig(meta);
-
- //now we allow overrides of default behavior, in order of precedence
- if(meta.editorConfig)
- EHR.Utils.rApply(editor, meta.editorConfig);
- if(meta.formEditorConfig)
- EHR.Utils.rApply(editor, meta.formEditorConfig);
- if(config)
- EHR.Utils.rApply(editor, config);
-
- return editor;
- },
-
- //private
- //this is designed to be called through either .getFormEditorConfig or .getGridEditorConfig
- getDefaultEditorConfig: function(meta){
- var h = Ext.util.Format.htmlEncode;
- var lc = function(s){return !s?s:Ext.util.Format.lowercase(s);};
-
- var field =
- {
- //added 'caption' for assay support
- fieldLabel: h(meta.label || meta.caption || meta.header || meta.name),
- originalConfig: meta,
- //we assume the store's translateMeta() will handle this
- allowBlank: meta.allowBlank!==false,
- disabled: meta.editable===false,
- name: meta.name,
- dataIndex: meta.dataIndex || meta.name,
- value: meta.value || meta.defaultValue,
- helpPopup: ['Type: ' + meta.friendlyType],
- width: meta.width,
- height: meta.height
- };
-
- if(field.description)
- field.helpPopup.push('Description: '+meta.description);
-
- field.helpPopup = {html: field.helpPopup.join(' ')};
-
- if (meta.hidden)
- {
- field.xtype = 'hidden';
- }
- else if (meta.lookup && false !== meta.lookups)
- {
- var l = meta.lookup;
-
- if (Ext.isObject(meta.store) && meta.store.events)
- field.store = meta.store;
- else
- field.store = EHR.ext.metaHelper.getLookupStoreConfig(meta);
-
- if (field.store && meta.lazyCreateStore === false){
- field.store = EHR.ext.metaHelper.getLookupStore(field);
- }
-
- Ext.apply(field, {
- //this purpose of this is to allow other editors like multiselect, checkboxGroup, etc.
- xtype: (meta.xtype || 'combo'),
- forceSelection: true,
- typeAhead: true,
- mode: 'local',
- hiddenName: meta.name,
- hiddenId : Ext.id(),
- triggerAction: 'all',
- lazyInit: false,
- //NOTE: perhaps we do translation of the following names in store's translateMeta() method
- displayField: l.displayColumn,
- valueField: l.keyColumn,
- //NOTE: supported for non-combo components
- initialValue: field.value,
- showValueInList: meta.showValueInList,
- listClass: 'labkey-grid-editor',
- lookupNullCaption: meta.lookupNullCaption
- });
-
- if(!meta.hasOwnTpl){
- //TODO: it would be better to put this in LABKEY.ext.Combo, so this tpl doesnt conflict with non-combo editors
- field.tpl = function(){var tpl = new Ext.XTemplate(
- '' +
- '
{[LABKEY.Utils.encodeHtml(values["' + l.keyColumn + '"]!==null ? values["' + l.displayColumn + '"] : "'+ (Ext.isDefined(this.lookupNullCaption) ? this.lookupNullCaption : '[none]') +'")]}' +
- //allow a flag to display both display and value fields
- '{[LABKEY.Utils.encodeHtml(values["' + l.keyColumn + '"] ? " ("+values["' + l.keyColumn + '"]+")" : "")]}'+
- '
'
- );return tpl.compile()}() //FIX: 5860
- }
- }
- else
- {
- switch (meta.jsonType)
- {
- case "boolean":
- field.xtype = meta.xtype || 'checkbox';
- break;
- case "int":
- field.xtype = meta.xtype || 'numberfield';
- field.allowDecimals = false;
- break;
- case "float":
- field.xtype = meta.xtype || 'numberfield';
- field.allowDecimals = true;
- break;
- //TODO: account for datetime vs date?
- case "date":
- field.xtype = meta.xtype || 'datefield';
- field.format = meta.extFormat || Date.patterns.ISO8601Long;
- field.altFormats = Date.patterns.ISO8601Short +
- 'n/j/y g:i:s a|n/j/Y g:i:s a|n/j/y G:i:s|n/j/Y G:i:s|' +
- 'n-j-y g:i:s a|n-j-Y g:i:s a|n-j-y G:i:s|n-j-Y G:i:s|' +
- 'n/j/y g:i a|n/j/Y g:i a|n/j/y G:i|n/j/Y G:i|' +
- 'n-j-y g:i a|n-j-Y g:i a|n-j-y G:i|n-j-Y G:i|' +
- 'j-M-y g:i a|j-M-Y g:i a|j-M-y G:i|j-M-Y G:i|' +
- 'n/j/y|n/j/Y|' +
- 'n-j-y|n-j-Y|' +
- 'j-M-y|j-M-Y|' +
- 'Y-n-d H:i:s|Y-n-d|' +
- 'j M Y G:i:s O|' + // 10 Sep 2009 11:24:12 -0700
- 'j M Y H:i:s'; // 10 Sep 2009 01:24:12
- break;
- case "string":
- if (meta.inputType=='textarea')
- {
- field.xtype = meta.xtype || 'textarea';
- field.width = meta.width || 500;
- field.height = meta.height || 60;
- if (!this._textMeasure)
- {
- this._textMeasure = {};
- var ta = Ext.DomHelper.append(document.body,{tag:'textarea', rows:10, cols:80, id:'_hiddenTextArea', style:{display:'none'}});
- this._textMeasure.height = Math.ceil(Ext.util.TextMetrics.measure(ta,"GgYyJjZ==").height * 1.2);
- this._textMeasure.width = Math.ceil(Ext.util.TextMetrics.measure(ta,"ABCXYZ").width / 6.0);
- }
- if (meta.rows && !meta.height)
- {
- if (meta.rows == 1)
- field.height = undefined;
- else
- {
- // estimate at best!
- var textHeight = this._textMeasure.height * meta.rows;
- if (textHeight)
- field.height = textHeight;
- }
- }
- if (meta.cols && !meta.width)
- {
- var textWidth = this._textMeasure.width * meta.cols;
- if (textWidth)
- field.width = textWidth;
- }
-
- }
- else
- field.xtype = meta.xtype || 'textfield';
- break;
- default:
- field.xtype = meta.xtype || 'textfield';
- }
- }
-
- return field;
- },
-
- //private
- //creates or returns an Ext store suitable for a lookup, based on metadata
- getLookupStore: LABKEY.ext.FormHelper.getLookupStore,
-
- //private
- //creates a unique storeId for a lookup sotrestore, based on metadata
- getLookupStoreId: LABKEY.ext.FormHelper.getLookupStoreId,
-
- //private
- //similar to getLookupStore(), except returns the config object only
- getLookupStoreConfig : function(c)
- {
- // UNDONE: avoid self-joins
- // UNDONE: core.UsersData
- // UNDONE: container column
- var l = c.lookup;
- // normalize lookup
- l.queryName = l.queryName || l.table;
- l.schemaName = l.schemaName || l.schema;
-
- if (l.schemaName == 'core' && l.queryName =='UsersData')
- l.queryName = 'Users';
-
- var config = {
- xtype: "labkey-store",
- storeId: LABKEY.ext.FormHelper.getLookupStoreId(c),
- schemaName: l.schemaName,
- queryName: l.queryName,
- containerPath: l.container || l.containerPath || LABKEY.container.path,
- autoLoad: true
- };
-
- if (l.viewName)
- config.viewName = l.viewName;
-
- if (l.filterArray)
- config.filterArray = l.filterArray;
-
- if (l.columns)
- config.columns = l.columns;
- else
- {
- var columns = [];
- if (l.keyColumn)
- columns.push(l.keyColumn);
- if (l.displayColumn && l.displayColumn != l.keyColumn)
- columns.push(l.displayColumn);
- if (columns.length == 0){
- columns = ['*'];
- }
- config.columns = columns;
- }
-
- if (l.sort)
- config.sort = l.sort;
- else if (l.sort !== false)
- config.sort = l.displayColumn;
-
- if (!c.required && c.includeNullRecord !== false)
- {
- if(c.lookupNullCaption===undefined && l.keyColumn==l.displayColumn)
- c.lookupNullCaption = '';
-
- config.nullRecord = c.nullRecord || {
- displayColumn: l.displayColumn,
- nullCaption: (l.displayColumn==l.keyColumn ? null : (c.lookupNullCaption!==undefined ? c.lookupNullCaption : '[none]'))
- };
- }
-
- return config;
- },
-
- //private
- //not in use, perhaps could be deprecated
- setLongTextRenderer: function(col, meta){
- if(col.multiline || (undefined === col.multiline && col.scale > 255 && meta.jsonType === "string"))
- {
- col.renderer = function(data, metadata, record, rowIndex, colIndex, store)
- {
- //set quick-tip attributes and let Ext QuickTips do the work
- metadata.attr = "ext:qtip=\"" + Ext.util.Format.htmlEncode(data || '') + "\"";
- return data;
- };
- }
- },
-
- //private
- //returns an array of Ext column objects, suitable to create an Ext columnModel
- getColumnModelConfig: function(store, config, grid){
- config = config || {};
-
- var columns = store.reader.jsonData.columnModel;
- var cols = new Array();
-
- Ext.each(columns, function(col, idx){
- cols.push(EHR.ext.metaHelper.getColumnConfig(store, col, config, grid));
- }, this);
-
- return cols;
- },
-
- //private
- //returns an Ext column config object, based on metadata
- getColumnConfig: function(store, col, config, grid){
- var meta = store.findFieldMeta(col.dataIndex);
- col.customized = true;
-
- if((meta.hidden || meta.shownInGrid===false) && !meta.shownInGrid){
- col.hidden = true;
- }
-
- switch(meta.jsonType){
- case "boolean":
- if(col.editable){
- col.xtype = 'booleancolumn';
- //if column type is boolean, substitute an Ext.grid.CheckColumn
- // if(col.editable)
- // col.init(this);
- // col.editable = false; //check columns apply edits immediately, so we don't want to go into edit mode
- //NOTE: The original EditorGrid defines its own Ext.grid.CheckColumn
- //Ext 3.1 has a booleancolumn class. we just use this instead
- }
- break;
- // case "int":
- // col.xtype = 'numbercolumn';
- // col.format = '0';
- // break;
- // case "float":
- // col.xtype = 'numbercolumn';
- // break;
- // case "date":
- // col.xtype = 'datecolumn';
- // col.format = meta.format || Date.patterns.ISO8601Long;
- }
-
- //this.updatable can override col.editable
- col.editable = config.editable && col.editable && meta.userEditable;
-
- //will use custom renderer
- if(meta.lookup && meta.lookups!==false)
- delete col.xtype;
-
- if(col.editable && !col.editor)
- col.editor = EHR.ext.metaHelper.getGridEditorConfig(meta);
-
- if(meta.getRenderer)
- col.renderer = meta.getRenderer(col, meta, grid);
-
- col.renderer = EHR.ext.metaHelper.getDefaultRenderer(col, meta, col.renderer, grid);
-
- //HTML-encode the column header
- col.header = Ext.util.Format.htmlEncode(meta.header || col.header);
-
- if(meta.ignoreColWidths)
- delete col.width;
-
- if(meta.colModel) {
- EHR.Utils.rApply(col, meta.colModel);
- }
-
- //allow override of defaults
- if(config && config.defaults)
- EHR.Utils.rApply(col, config.defaults);
-
- if(config && config[col.dataIndex])
- EHR.Utils.rApply(col, config[col.dataIndex]);
-
- return col;
-
- },
-
- //private
- //creates the default renderer function suitable for Ext grids. This will interpret metadata and create the appropriate display string and qtip
- getDefaultRenderer : function(col, meta, renderer, grid) {
- return function(data, cellMetaData, record, rowIndex, colIndex, store)
- {
- EHR.ext.metaHelper.buildQtip(data, cellMetaData, record, rowIndex, colIndex, store, col, meta);
-
- var displayType = meta.type;
-
- //NOTE: this is substantially changed over FormHelper
- if(meta.lookup && meta.lookups!==false){
- data = EHR.ext.metaHelper.lookupRenderer(meta, data, grid, record, rowIndex);
- displayType = 'string';
- }
-
- if(null === data || undefined === data || data.toString().length == 0)
- return data;
-
- //format data into a string
- var displayValue;
- if(renderer){
- displayValue = renderer(data, cellMetaData, record, rowIndex, colIndex, store);
- }
- else {
- switch (displayType)
- {
- case "date":
- var date = new Date(data);
- var format = meta.extFormat;
- if(!format){
- if (date.getHours() == 0 && date.getMinutes() == 0 && date.getSeconds() == 0)
- format = LABKEY.extDefaultDateFormat;
- else
- format = LABKEY.extDefaultDateTimeFormat;
- }
- displayValue = date.format(format);
- break;
- case "int":
- displayValue = (Ext.util.Format.numberRenderer(this.format || '0'))(data);
- break;
- case "boolean":
- var t = this.trueText || 'true', f = this.falseText || 'false', u = this.undefinedText || ' ';
- if(data === undefined){
- displayValue = u;
- }
- else if(!data || data === 'false'){
- displayValue = f;
- }
- else {
- displayValue = t;
- }
- case "float":
- displayValue = (Ext.util.Format.numberRenderer(this.format || '0,000.00'))(data);
- case "string":
- default:
- displayValue = data.toString();
- }
- }
-
- //if meta.file is true, add an for the file icon
- if(meta.file){
- displayValue = " " + displayValue;
- //since the icons are 16x16, cut the default padding down to just 1px
- cellMetaData.attr = "style=\"padding: 1px 1px 1px 1px\"";
- }
-
- //wrap in if url is present in the record's original JSON
- if(col.showLink !== false && record.json && record.json[meta.name] && record.json[meta.name].url)
- return "" + displayValue + "";
- else
- return displayValue;
- };
- },
-
- //private
- //builds the Ext qtip for a cell in a grid
- buildQtip: function(data, cellMetaData, record, rowIndex, colIndex, store, col, meta){
- var qtip = [];
- if(record.json && record.json[meta.name] && record.json[meta.name].qcValue)
- {
- var qcValue = record.json[meta.name].qcValue;
-
- //get corresponding message from qcInfo section of JSON and set up a qtip
- if(store.reader.jsonData.qcInfo && store.reader.jsonData.qcInfo[qcValue])
- {
- qtip.push(store.reader.jsonData.qcInfo[qcValue]);
- cellMetaData.css = "labkey-mv";
- }
- return qcValue;
- }
-
- if(record.errors && record.errors.length){
-
- Ext.each(record.errors, function(e){
- if(e.field==meta.name){
- qtip.push((e.severity || 'ERROR') +': '+e.message);
- cellMetaData.css += ' x-grid3-cell-invalid';
- }
- }, this);
- }
-
- if(meta.qtipRenderer)
- meta.qtipRenderer(qtip, data, cellMetaData, record, rowIndex, colIndex, store);
-
- if(qtip.length)
- cellMetaData.attr = "ext:qtip=\"" + Ext.util.Format.htmlEncode(qtip.join(' ')) + "\"";
- },
-
- //private
- //this creates the renderer used to display cells if that column uses a lookup
- lookupRenderer : function(meta, data, grid, record, rowIndex) {
- var lookupStore = EHR.ext.metaHelper.getLookupStore(meta);
- if(!lookupStore){
- return '';
- }
-
- var lookupRecord = lookupStore.getById(data);
- if (lookupRecord)
- return lookupRecord.data[meta.lookup.displayColumn];
- else {
- //if store not loaded yet, retry rendering on store load
- if(grid && !lookupStore.fields){
- this.lookupStoreLoadListeners = this.lookupStoreLoadListeners || [];
- if(this.lookupStoreLoadListeners.indexOf(lookupStore.storeId) == -1){
- lookupStore.on('load', function(store){
- this.lookupStoreLoadListeners.remove(store.storeId);
- //console.log('refresh: '+store.storeId);
-
- grid.getView().refresh();
-
- }, this, {single: true});
- this.lookupStoreLoadListeners.push(lookupStore.storeId);
- }
- }
- if (data!==null){
- return "[" + data + "]";
- }
- else {
- return Ext.isDefined(meta.lookupNullCaption) ? meta.lookupNullCaption : "[none]";
- }
- }
- }
- }
-};
diff --git a/ehr/resources/web/ehr/ext3/ehrPrintTaskPanel.js b/ehr/resources/web/ehr/ext3/ehrPrintTaskPanel.js
deleted file mode 100644
index 6dceb9365..000000000
--- a/ehr/resources/web/ehr/ext3/ehrPrintTaskPanel.js
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright (c) 2013-2019 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
- */
-
-Ext.namespace('EHR.ext');
-
-/**
- * This class was never completed. The intent was to try to create a variant of a normal Task ImportPanel which would
- * display records in a read-only manner suitable for printing. The thought was that in some cases paper was going to be
- * necessary. The user could start the task in LabKey, fill out as much as possible, then print. The printout should have
- * space to fill in necessary values. The user could take this printout back to the computer and fill in missing values.
- *
- * There may be use for this concept; however, other items were prioritized higher.
- */
-
-EHR.ext.PrintTaskPanel = Ext.extend(Ext.Panel, {
- initComponent: function(){
- this.storeConfig = this.storeConfig || {};
- if(!this.storeConfig.filterArray){
- this.storeConfig.maxRows = 0;
- this.on('load', function(){
- delete this.maxRows;
- }, this, {single: true});
- }
-
- this.store = this.store || new EHR.ext.AdvancedStore(Ext.applyIf(this.storeConfig, {
- //xtype: 'ehr-store',
- containerPath: this.containerPath,
- schemaName: this.schemaName,
- queryName: this.queryName,
- viewName: this.viewName || '~~UPDATE~~',
- columns: this.columns || EHR.Metadata.Columns[this.queryName] || '',
- storeId: [this.schemaName,this.queryName,this.viewName].join('||'),
- filterArray: this.filterArray || [],
- metadata: Ext.apply(this.metadata, {fieldDefaults: {lookupNullCaption: ''}}),
- timeout: 0
- }));
-
- if(this.store && this.queryName && this.schemaName){
- this.store.load();
-
- //a test for whether the store is loaded
- if(!this.store.fields){
- this.mon(this.store, 'load', this.loadQuery, this, {single: true});
- }
- else {
- this.loadQuery(this.store);
- }
- }
- else {
- //NOTE: inelegant
- this.hidden = true;
- }
-
- Ext.applyIf(this, {
- autoHeight: true
- //,autoWidth: true
- //,width: '7 in'
- ,bodyBorder: false
- ,border: false
- ,bodyStyle: 'padding:5px'
- ,style: 'margin-bottom: 15px'
- ,items: [{
- html: 'Loading...'
- }]
- ,tbar: [{
- xtype: 'button',
- text: 'Add Rows',
- scope: this,
- handler: function(b){
- Ext.Msg.prompt('Number of Rows', 'How many rows do you want to add?', function(r, newRows){
- var width = this.theTable.layout.columns;
- if(newRows){
- for(var i=0;iMany internal methods of this store have been divded to permit a collection of stores to be more easily controlled by an EHR.ext.StoreCollection
- *
This store accepts a metadata object that will be applied directly to the server-supplied metadata. This metadata is interpreted by other EHR/Ext components such as FormPanel to EditorGrid
- *
This store supports the concept of 'validation' which submits a record to the server through the normal save pathway. This record will enter the validation script; however, there is a flag to force it to fail. This allows server-side validation / error generation to be performed and for a single code path to be used for commits and validation.
- *
This store is designed to parse the response from the server to allow two-way communication between validation scripts and the store.
- *
- *
If you use any of the LabKey APIs that extend Ext APIs, you must either make your code open source or
- * purchase an Ext license.
- *
- * @constructor
- * @augments LABKEY.ext.Store
- * @param config Configuration properties. This may contain any of the configuration properties supported by the LABKEY.ext.Store plus those listed here.
- * @param {String} [config.containerPath] The container path from which to query the server. If not specified, the current container is used.
- * @param {String} [config.schemaName] The LabKey schema to query.
- * @param {String} [config.queryName] The query name within the schema to fetch.
- * @param {String} [config.viewName] A saved custom view of the specified query to use if desired.
- * @param {Object} [config.metadata] A metadata object that will be applied to the default metadata returned by the server. See EHR.Metadata for more information.
- * @param {Boolean} [config.doServerValidation] If true, whenever a record is modified it will be validated on the server.
- */
-EHR.ext.AdvancedStore = Ext.extend(LABKEY.ext.Store, {
- constructor: function(config){
- Ext.apply(this, {
- pruneModifiedRecords: true,
- errors: new Ext.util.MixedCollection(),
- doServerValidation: true
- });
-
- EHR.ext.AdvancedStore.superclass.constructor.apply(this, arguments);
-
- /**
- * @memberOf EHR.ext.AdvancedStore#
- * @name beforemetachange
- * @event
- * @description Fires after the HTTP proxy loads metadata from the server, but before it is applied to the store
- * @param {Object} this The store object
- * @param {Object} meta The metadata object that will be applied to the store
- */
- /**
- * @memberOf EHR.ext.AdvancedStore#
- * @name validation
- * @event
- * @description This will fire whenever the store's records are validated.
- * @param {Object} this The store object
- * @param {Array} records Array of the records that were validated
- */
- this.addEvents('beforemetachange', 'validation');
- this.proxy.on("load", this.onProxyLoad, this);
-
- //@deprecated
- if(this.monitorPermissions){
- EHR.Security.init({
- success: Ext.emptyFn,
- failure: EHR.Utils.onError,
- scope: this
- });
- this.on('update', this.verifyPermission, this);
- }
-
- //TODO: remove once added to labkey.ext.store
- if(config.maxRows !== undefined){
- this.baseParams['query.maxRows'] = config.maxRows;
- }
- if(config.ignoreFilter)
- this.baseParams['query.ignoreFilter'] = 1;
-
- if(this.monitorValid)
- this.initMonitorValid();
- },
-
- //private
- //creates the listeners needed to validate records on the server
- initMonitorValid: function(){
- this.on('update', this.validateRecord, this);
- this.on('add', this.validateRecords, this);
-// this.on('load', this.validateRecords, this);
- this.on('remove', this.validationOnRemove, this);
-
- this.validateRecords(this);
- },
-
- //private
- stopMonitorValid: function(){
- this.un('update', this.validateRecord, this);
- this.un('add', this.validateRecords, this);
-// this.un('load', this.validateRecords, this);
- this.on('remove', this.validationOnRemove, this);
-
-// this.un('validation', this.validateRecordOnServer, this);
- },
-
- //private
- validationOnRemove: function(store, rec){
- //we need to remove all errors from this record
- this.errors.each(function(error){
- if(error.record==rec){
- this.errors.remove(error);
- }
- }, this);
- this.fireEvent('validation', this);
- },
-
- //private
- onProxyLoad: function(proxy, response, options){
- var meta = this.reader.meta;
-
- //provide a standardized translation
- this.translateMetaData(meta);
-
- Ext.each(meta.fields, function(f){
- //allow defaults for all fields
- //NOTE: perhaps move to this.fieldDefaults?
- if(this.metadata){
- if(this.metadata['fieldDefaults']){
- EHR.Utils.rApply(f, this.metadata['fieldDefaults']);
- }
-
- //allow more complex metadata, per field
- if(this.metadata[f.name]){
- EHR.Utils.rApply(f, this.metadata[f.name]);
- }
- }
- }, this);
-
- if(this.colModel){
- var colModel = this.reader.jsonData.columnModel;
- Ext.each(colModel, function(col){
- if(this.colModel[col.dataIndex]){
- EHR.Utils.rApply(col, this.colModel[col.dataIndex]);
- }
- }, this);
- }
-
- //console.log('beforemetachange:'+this.storeId);
- if(false===this.fireEvent('beforemetachange', this, meta))
- return;
-
- //console.log('metachange:'+this.storeId);
- this.reader.onMetaChange(meta);
- },
-
- //private
- changeMetadata: function(meta){
- console.log('metachange:'+this.storeId);
- this.reader.onMetaChange(meta);
- },
-
- //@deprecated
- //the original intent was to enforce QCState permissions in the store, but it was never fully implemented
- verifyPermission: function(store, rec, operation){
- var qc;
- if(EHR.Security.hasLoaded() && rec.fields.get('QCState')){
- qc = rec.get('QCState');
- if(!qc)
- return;
-
- qc = EHR.Security.getQCStateByRowId(qc).Label;
-
- var command = 'insert';
- //TODO: it would be nice to be smarter about whether we test for insert or update.
- //the server will enforce it, but stopping it on the client would be more friendly
-
- if(!EHR.Security.hasPermission(qc, command, {schemaName: this.schemaName, queryName: this.queryName})){
- alert('You do not have permission to change this record');
- rec.reject();
- }
- /*
- else {
- //config option in store
- if(!this.allowOthersToEditRecords){
- //one of the following must be true in order to edit
- if(
- //these dont exist on the server, so anyone can edit
- !rec.phantom &&
- //you can edit recs you created
- rec.get('created') != LABKEY.Security.currentUser.id &&
- //you can edit recs you modified
- rec.get('modified') != LABKEY.Security.currentUser.id &&
- //data admins can always edit
- !EHR.Security.hasPermission(qc, 'admin', {schemaName: this.schemaName, queryName: this.queryName})
- ){
- //also allow editing of tasks assigned to you:
- //TODO
-
- alert('You do not have permission to change this record');
- rec.reject();
- }
- }
- }
- */
- }
- },
-
- //private
- //the intention of this method was to provide a standard, low-level way to translating Labkey metadata names into ext ones.
- translateMetaData: function(meta){
- Ext.each(meta.fields, function(field){
- var h = Ext.util.Format.htmlEncode;
- var lc = function(s){return !s?s:Ext.util.Format.lowercase(s);};
-
- //field.type = lc(field.jsonType) || lc(field.type) || lc(field.typeName) || 'string';
- field.fieldLabel = h(field.label) || h(field.caption) || h(field.header) || h(field.name);
- field.dataIndex = field.dataIndex || field.name;
- field.editable = field.userEditable && !field.readOnly;
-
- field.allowBlank = field.nullable;
-
- }, this);
- },
-
- /**
- * @memberOf EHR.ext.AdvancedStore#
- * @name newRecord
- * @description This method creates a new record to add to this store. It exists (as opposed to using "new this.recordType()") such
- * that fields with an initial value generator function (see EHR.Metadata.FieldMetadata.setInitialValue()). Records created
- * for this store should use this method. At a future point, it might be worth investigating whether this.recordType could be
- * overridden instead.
- * @param {object} data The initial values to use for this record
- */
- //TODO: would be better to just override this.recordType
- newRecord: function(data){
- var rec;
- if(data instanceof Ext.data.Record)
- rec = data;
- else
- rec = new this.recordType(data);
-
- this.fields.each(function(f) {
- rec.data[f.name] = Ext.isDefined(rec.data[f.name]) ? f.convert(rec.data[f.name]) :
- Ext.isDefined(f.defaultValue) ? f.defaultValue :
- null;
- if(!rec.data[f.name] && Ext.isFunction(f.setInitialValue)){
- rec.data[f.name] = f.setInitialValue.call(this, rec.data[f.name], rec, f);
- }
- }, this);
-
- return rec;
- },
-
- //private
- addRecords: function(records, idx){
- if(undefined === idx)
- idx = this.getCount();
-
- if(!Ext.isArray(records))
- records = [records];
-
- Ext.each(records, function(r, idx){
- records[idx] = this.newRecord(r);
- }, this);
-
- //NOTE: when you add an array of records, they get added in reverse order.
- //therefore I reverse it here so it's added in the order submitted.
- //records.reverse();
-
- this.insert(idx, records);
- this.validateRecords(this, records);
-
- return records
- },
-
- //private
- addRecord: function(r, idx){
- if(undefined === idx)
- idx = this.getCount();
-
- r = this.newRecord(r);
- this.insert(idx, r);
-
- this.validateRecords(this, [r]);
- return r;
- },
-
- //private
- duplicateRecord: function(record, defaults){
- defaults = defaults || {};
- //NOTE: this is deliberate such that defaults are applied first. if you want the new record to have a null value, this should work
- var newData = Ext.apply({}, r.data);
- Ext.apply(newData, defaults);
- this.store.addRecord(newData);
- },
-
- //private
- duplicateRecords: function(records, defaults){
- Ext.each(records, function(r){
- this.duplicateRecord(r, defaults);
- }, this);
- },
-
- //private
- maxErrorSeverity: function(){
- var maxSeverity;
- this.errors.each(function(e){
- maxSeverity = EHR.Utils.maxError(maxSeverity, e.severity);
- }, this);
-
- return maxSeverity;
- },
-
- //private
- validateRecords: function(store, recs, fireEvent, config){
- config = config || {};
-
- if(recs)
- Ext.each(recs, function(r){
- this.validateRecord(this, r, config.operation, {fireEvent: false});
- }, this);
- else
- this.each(function(r){
- this.validateRecord(this, r, config.operation, {fireEvent: false});
- }, this);
-
- if(fireEvent !== false){
- this.fireEvent('validation', this, recs);
- }
- },
-
- //private
- //this method performs simple checks client-side then will submit the record for server-validation if selected
- validateRecord: function(store, r, operation, config){
- config = config || {};
-
- r.errors = r.errors || [];
-
- //remove other client-side errors for this record
- for(var i = r.errors.length-1; i >= 0; i--){
- if(!r.errors[i].fromServer){
- this.errors.remove(r.errors[i]);
- r.errors.splice(i, 1);
- }
- }
-
- r.fields.each(function(f) {
- //apparently the store's metadata can be updated w/o the record's, so we use that
- var meta = store.fields.map[f.name];
-
- //NOTE: we're drawing a distinction between LABKEY's nullable and ext's allowBlank.
- // This allows fields to be set to 'allowBlank: false', which throws a warning
- // nullable:false will throw an error when null.
- // also, if userEditable==false, we assume will be set server-side so we ignore it here
- if(meta.userEditable!==false && Ext.isEmpty(r.data[f.name])){
- if(meta.nullable === false || meta.allowBlank === false){
- r.errors.push({
- id: LABKEY.Utils.generateUUID(),
- field: f.name,
- message: 'This field is required',
- record: r,
- errorValue: r.get(f.dataIndex),
- severity: (f.nullable === false ? 'ERROR' : 'WARN'),
- fromServer: false
- });
- }
- }
-
- //NOTE: if we had a reference to the field editor, i could hook into getErrors() to find other validation errors
- //should revisit in Ext 4 when validation is moved to dataModel
- },store);
-
- this.errors.addAll(r.errors);
-
- //this is designed such that validateRecords() can call validateRecord() multiple times without events firing
- if(config.fireEvent!==false){
- this.fireEvent('validation', this, [r], config);
- }
-
- //if(this.doServerValidation){
- if(operation=='edit' && this.doServerValidation){
- this.validateRecordOnServer.defer(500, this, [this, [r], config]);
- }
- },
-
- //private
- validateRecordOnServer: function(store, records, config){
- if(!records || !records.length)
- return;
-
- if(config && config.noServerValidation){
- console.log('skipping server validation');
- return;
- }
-
- var commands = this.getChanges(records);
- if (!commands.length){
- return false;
- }
-
- Ext.each(records, function(record){
- //NOTE: we remove saveOperationInProgress b/c this transaction is going to fail
- //therefore we do not want this flag to block a legitimate future save attempt.
- delete record.saveOperationInProgress;
- record.serverValidationInProgress = true;
- }, this);
-
- //add a flag per command to make sure this record fails
- this.sendRequest(records, commands, {validateOnly: true, silent: true, targetQCState: null});
- },
-
- /**
- * @memberOf EHR.ext.AdvancedStore#
- * @name getFormEditorConfig
- * @description This is a convenience wrapper for EHR.ext.metaHelper.getFormEditorConfig().
- * @param {string} fieldName The name of the field
- * @param {object} config Optional. This config object will be merged with the output of getFormEditorConfig()
- */
- getFormEditorConfig: function(fieldName, config){
- var meta = this.findFieldMeta(fieldName);
- return EHR.ext.metaHelper.getFormEditorConfig(meta, config);
- },
-
- /**
- * @memberOf EHR.ext.AdvancedStore#
- * @name getGridEditorConfig
- * @description This is a convenience wrapper for EHR.ext.metaHelper.getGridEditorConfig().
- * @param {string} fieldName The name of the field
- * @param {object} config Optional. This config object will be merged with the output of getGridEditorConfig()
- */
- getGridEditorConfig: function(fieldName, config){
- var meta = this.findFieldMeta(fieldName);
- return EHR.ext.metaHelper.getGridEditorConfig(meta, config);
- },
-
- //private
- getAllRecords: function(){
- var recs = [];
- this.each(function(r){
- recs.push(r);
- }, this);
- return recs;
- },
-
- //NOTE: returns true to remove unwanted checking contained in LABKEY store
- readyForSave: function(){
- return true;
- },
-
- //private
- //@override
- //the only reason this method is overridden is to delete record.phantom near the bottom (see note). we also delete lastTransactionId
- processResponse : function(rows){
- var idCol = this.reader.jsonData.metaData.id;
- var row;
- var record;
- for(var idx = 0; idx < rows.length; ++idx)
- {
- row = rows[idx];
-
- if(!row || !row.values)
- return;
-
- //find the record using the id sent to the server
- record = this.getById(row.oldKeys[this.reader.meta.id]);
- if(!record)
- return;
-
- //apply values from the result row to the sent record
- for(var col in record.data)
- {
- //since the sent record might contain columns form a related table,
- //ensure that a value was actually returned for that column before trying to set it
- if(undefined !== row.values[col]){
- if(!record.fields.get(col)){
- console.log('ERROR: column not found: '+col);
- console.log(record.fields);
- continue;
- }
-
- var x = record.fields.get(col);
- record.set(col, record.fields.get(col).convert(row.values[col], row.values));
- }
-
- //clear any displayValue there might be in the extended info
- if(record.json && record.json[col])
- delete record.json[col].displayValue;
- }
-
- //if the id changed, fixup the keys and map of the store's base collection
- //HACK: this is using private data members of the base Store class. Unfortunately
- //Ext Store does not have a public API for updating the key value of a record
- //after it has been added to the store. This might break in future versions of Ext.
- if(record.id != row.values[idCol])
- {
- record.id = row.values[idCol];
- this.data.keys[this.data.indexOf(record)] = row.values[idCol];
-
- delete this.data.map[record.id];
- this.data.map[row.values[idCol]] = record;
- }
-
- //reset transitory flags and commit the record to let
- //bound controls know that it's now clean
- delete record.saveOperationInProgress;
- delete record.lastTransactionId;
-
- //NOTE: this could probably be removed in favor of the Ext param
- //phantom is the built-in Ext version of labkey's isNew. I delete here so we know the record exists on the server
- delete record.isNew;
- delete record.phantom;
- record.commit();
- }
- },
-
- //@deprecated
- getLookupStore: function(){
- //not needed . used in editorgridpanel. should be shifted to metaHelper.
- },
-
- //@override
- // overridden to remove setting a default nullCaption. this is moved to the combo tpl
- onLoad : function(store, records, options) {
- this.isLoading = false;
-
- //remember the name of the id column
- this.idName = this.reader.meta.id;
-
- if(this.nullRecord)
- {
- //create an extra record with a blank id column
- //and the null caption in the display column
- var data = {};
- data[this.reader.meta.id] = "";
- data[this.nullRecord.displayColumn] = this.nullRecord.nullCaption || this.nullCaption || null;
-
- var recordConstructor = Ext.data.Record.create(this.reader.meta.fields);
- var record = new recordConstructor(data, -1);
- this.insert(0, record);
- }
- },
-
- //private
- //NOTE: split this method in two so they can be called independently
- commitChanges : function(extraContext){
- var records = this.getModifiedRecords();
- this.commitRecords(records, extraContext);
- },
-
- //private
- //see above
- commitRecords : function(records, extraContext){
- var commands = this.getChanges(records);
-
- if (!commands.length){
- if(extraContext && extraContext.silent!==true)
- Ext.Msg.alert('Alert', 'There are no changes to submit.');
-
- return;
- }
-
- for(var i=0;i 0 && false === this.fireEvent("beforecommit", records, commands[i].rows))
- return [];
- }
-
- this.sendRequest(records, commands, extraContext);
- },
-
- //private
- getChanges: function(records){
- records = records || this.getModifiedRecords();
-
- if(!records || records.length == 0)
- return [];
-
- if (!this.updatable)
- throw "this LABKEY.ext.Store is not updatable!";
-
- //build the json to send to the server
- var record;
- var skippedRecords = [];
- var insertCommand =
- {
- schemaName: this.schemaName,
- queryName: this.queryName,
- command: "insertWithKeys",
- rows: [],
- extraContext: this.getExtraContext(records)
- };
- var updateCommand =
- {
- schemaName: this.schemaName,
- queryName: this.queryName,
- command: "updateChangingKeys",
- rows: [],
- extraContext: this.getExtraContext(records)
- };
- for(var idx = 0; idx < records.length; ++idx)
- {
- record = records[idx];
-
- //if we are already in the process of saving this record, just continue
- if(record.saveOperationInProgress){
- skippedRecords.push(record);
- continue;
- }
-
- //NOTE: this check could possibly be eliminated since the form/server should do the same thing
- if(!this.readyForSave(record)){
- skippedRecords.push(record);
- continue;
- }
-
- record.saveOperationInProgress = true;
- //NOTE: modified since ext uses the term phantom for any record not saved to server
- if (record.isNew || record.phantom)
- {
- insertCommand.rows.push({
- values: this.getRowData(record),
- oldKeys : this.getOldKeys(record)
- });
- }
- else
- {
- updateCommand.rows.push({
- values: this.getRowData(record),
- oldKeys : this.getOldKeys(record)
- });
- }
- }
-
- var commands = [];
- if (insertCommand.rows.length > 0)
- {
- commands.push(insertCommand);
- }
- if (updateCommand.rows.length > 0)
- {
- commands.push(updateCommand);
- }
-
- if(skippedRecords.length){
- console.log('Skipping records');
- console.log(skippedRecords)
- }
-// else {
-// console.log('sending');
-// }
-
- return commands;
- },
-
- getExtraContext: function(records){
- var ret = {
- storeId: this.storeId,
- queryName: this.queryName,
- schemaName: this.schemaName,
- keyField: this.reader.meta.id
- };
-
- // NOTE: this should really be left to subclasses of ehrStore to handle, rather
- // then account for each query here. we also might want extraContext attached to the
- // top-level request, rather than only per-blood. in this instance only Blood Draws.js
- // is using the data, but weights are a good example of something that might be
- // globally useful.
- if (this.queryName == 'Blood Draws'){
- var bloodDrawMap = {};
- var allRecords = this.getAllRecords();
- for (var idx = 0; idx < allRecords.length; ++idx){
- var record = allRecords[idx];
- if (record.get('quantity') > 0)
- {
- var id = record.get('Id');
- var date = record.get('date');
- if (!id || !date)
- continue;
-
- date = date.format(LABKEY.extDefaultDateFormat);
-
- if (!bloodDrawMap[id])
- bloodDrawMap[id] = [];
-
- bloodDrawMap[id].push({
- objectid: record.get('objectid'),
- date: date,
- qcstate: record.get('QCState'),
- quantity: record.get('quantity')
- });
- }
- }
-
- if (!LABKEY.Utils.isEmptyObj(bloodDrawMap)){
- ret.bloodInTransaction = bloodDrawMap
- }
- }
-
- if (this.queryName == 'Housing'){
- var map = {};
- var allRecords = this.getRange();
- for (var idx = 0; idx < allRecords.length; ++idx){
- var record = allRecords[idx];
-
- var date = record.get('date');
- var id = record.get('Id');
- var room = record.get('room');
- if (!id || !date)
- continue;
-
- if (!map[id])
- map[id] = [];
-
- map[id].push({
- Id: id,
- objectid: record.get('objectid'),
- date: date,
- enddate: record.fields.get('enddate') ? record.get('enddate') : null,
- qcstate: record.get('QCState'),
- room: record.get('room'),
- cage: record.get('cage'),
- divider: record.fields.get('divider') ? record.get('divider') : null
- });
- }
-
- if (!LABKEY.Utils.isEmptyObj(map)){
- ret.housingInTransaction = map;
- }
- }
-
- return ret;
- },
-
- //private
- //NOTE: we append the value of the key field to the record
- getRowData : function(record) {
- var values = EHR.ext.AdvancedStore.superclass.getRowData.apply(this, arguments);
- values._recordid = record.id;
- return values;
- },
-
- //private
- //NOTE: split this into a separate method so validateOnServer() can call it separately
- sendRequest: function(records, commands, extraContext){
- extraContext = extraContext || {};
- extraContext.isLegacyFormat = true;
-
- var request = Ext.Ajax.request({
- url : LABKEY.ActionURL.buildURL("query", "saveRows", this.containerPath),
- method : 'POST',
- success: this.onCommitSuccess,
- failure: this.getOnCommitFailure(records),
- scope: this,
- jsonData : {
- containerPath: this.containerPath,
- commands: commands,
- extraContext: extraContext
- },
- headers : {
- 'Content-Type' : 'application/json'
- }
- });
-
- Ext.each(records, function(rec){
- rec.lastTransactionId = request.tId;
- }, this);
- },
-
- //private
- //NOTE: overridden support different processing of exceptions and validation errors
- getOnCommitFailure : function(records) {
- return function(response, options) {
-
- for(var idx = 0; idx < records.length; ++idx)
- delete records[idx].saveOperationInProgress;
-
- var serverError = this.getJson(response);
- var msg = '';
- if(serverError && serverError.errors){
- //each error should represent 1 row. there can be multiple errors per row
- Ext.each(serverError.errors, function(rowError){
- //handle validation script errors and exceptions differently
- if(rowError.errors && rowError.errors.length && rowError.row){
- this.handleValidationErrors(rowError, response, serverError.extraContext);
- msg = "Could not save changes due to errors. Please check the form for fields marked in red.";
- }
- else {
- //if an exception was thrown, I believe we automatically only have one error returned
- //this means this can only be called once
- msg = 'Could not save changes due to the following error:\n' + (serverError && serverError.exception) ? serverError.exception : response.statusText;
- }
- }, this);
-
- if(!serverError.errors){
- msg = 'Could not save changes due to the following error:\n' + serverError.exception;
- }
- }
-
- if(false !== this.fireEvent("commitexception", msg) && (options.jsonData.extraContext && !options.jsonData.extraContext.silent)){
- Ext.Msg.alert("Error", "Error During Save. "+msg);
- }
- };
- },
-
- //private
- handleValidationErrors: function(serverError, response, extraContext){
- var record = this.getById(serverError.row._recordid);
- if(record){
- this.handleValidationError(record, serverError, response, extraContext);
- }
- else {
- console.log('ERROR: Record not found');
- console.log(serverError);
- }
- },
-
- //private
- //this will process the errors associated with 1 record. there might be more than 1 error per record
- handleValidationError: function(record, serverError, response, extraContext){
- //verify transaction Id matches the most recent request
- if(record.lastTransactionId != response.tId){
- console.log('There has been a more recent transaction for this record. Ignoring this one.');
- return;
- }
-
- //allow the server to return values for fields
- this.fields.each(function(field){
- if(field.updateValueFromServer &&
- serverError.row[field.dataIndex] &&
- serverError.row[field.dataIndex] != record.get(field.dataIndex)
- ){
- record.set(field.dataIndex, serverError.row[field.dataIndex]);
- }
- }, this);
-
- //remove all old errors for this record
- record.errors = [];
- this.errors.each(function(error){
- if(error.record==record){
- this.errors.remove(error);
- }
- }, this);
-
- Ext.each(serverError.errors, function(e){
- this.processValidationError(record, e)
- }, this);
-
- if(extraContext && extraContext.skippedErrors && extraContext.skippedErrors[record.id]){
- Ext.each(extraContext.skippedErrors[record.id], function(e){
- this.processValidationError(record, e, true)
- }, this);
- }
- //re-run client-side validation.
- this.validateRecord(this, record, null, {fireEvent: true, noServerValidation: true});
- },
-
- processValidationError: function(record, error, noParse){
- //this is a flag used by server-side validation scripts
- if(error.field=='_validateOnly')
- return;
-
- if(!noParse){
- var msg = error.message.split(': ');
- if(msg.length>1){
- error.severity = msg.shift();
- error.message = msg.join(': ');
- }
- }
-
- record.errors.push({
- id: LABKEY.Utils.generateUUID(),
- field: error.field,
- message: error.message,
- record: record,
- severity: error.severity || 'ERROR',
- errorValue: record.get(error.field),
- fromServer: true
- });
- },
-
- //private
- //NOTE: instead of directly deleting the records, this method converts them to a QCState of 'Delete Requested'. This is for historic reasons
- //that no longer apply. At some point this code should probably be changed to directly delete the records.
- requestDeleteRecords: function(records){
- if (!this.updatable)
- throw "this LABKEY.ext.Store is not updatable!";
-
- if(!records || records.length == 0)
- return;
-
- var extraContext = {
- //targetQC : 'Delete Requested',
- errorThreshold: 'SEVERE',
- importPathway: 'ehr-ext3DataEntry'
- };
-
- var recs = [];
- Ext.each(records, function(r){
- if(r.phantom){
- this.remove(r);
- delete r;
- }
- else {
- r.beginEdit();
- if(r.get('requestid') || r.get('requestId')){
- //note: we reject changes since we dont want to retain modifications made in this form
- r.reject();
-
- //reset the date
- if(r.get('daterequested'))
- r.set('date', r.get('daterequested'));
-
- //remove from this task
- if(this.queryName!='tasks')
- r.set('taskid', null);
-
- r.set('QCState', EHR.Security.getQCStateByLabel('Request: Approved').RowId);
- }
- else {
- r.set('QCState', EHR.Security.getQCStateByLabel('Delete Requested').RowId);
- }
- r.commit();
-
- recs.push(r);
- }
- }, this);
-
- if(recs.length){
- function onComplete(response){
- this.un('commitcomplete', onComplete);
- this.remove(recs);
- }
- this.on('commitcomplete', onComplete, this, {single: true});
- this.commitRecords(recs, extraContext);
- }
- else {
- this.fireEvent("commitcomplete");
- }
- },
-
- //private
- //NOTE: the following 2 methods are overridden because the old approach causes uncommitted client-side records to get destroyed on store load
- //also added QCState permission checking
- deleteRecords : function(records, extraContext) {
- if (!this.updatable)
- throw "this LABKEY.ext.Store is not updatable!";
-
- if(!records || records.length == 0)
- return;
-
- var deleteRowsKeys = [];
- var canDelete = true;
- var key;
- var qc;
- for(var idx = 0; idx < records.length; ++idx)
- {
- //server delete not required for phantom records
- //we dont worry about permissions, since we assume the current user created it
- if(records[idx].phantom){
- this.remove(records[idx]);
- delete records[idx];
- }
- else {
- if(EHR.Security.hasLoaded() && records[idx].fields.get('QCState')){
- qc = records[idx].get('QCState');
- qc = EHR.Security.getQCStateByRowId(qc).Label;
- if(!EHR.Security.hasPermission(qc, 'delete', {schemaName: this.schemaName, queryName: this.queryName})){
- canDelete = false;
- }
- else {
- key = {};
- key[this.idName] = records[idx].id;
- deleteRowsKeys[idx] = key;
- }
- }
- }
- }
-
- if(!canDelete)
- alert('You do not have permission to delete one or more of the records');
-
- if(deleteRowsKeys.length){
- //send the delete
- LABKEY.Query.deleteRows({
- schemaName: this.schemaName,
- queryName: this.queryName,
- containerPath: this.containerPath,
- extraContext: extraContext,
- rows: deleteRowsKeys,
- scope: this,
- successCallback: this.getDeleteSuccessHandler(records),
- failure: this.getOnCommitFailure(records),
- action: "deleteRows" //hack for Query.js bug
- });
- }
- else {
- //Question: should this really count as commitcomplete since we never hit the server?
- this.fireEvent("commitcomplete");
- }
- },
-
- //private
- getDeleteSuccessHandler : function(records) {
- var store = this;
- return function(results) {
- store.fireEvent("commitcomplete");
- this.remove(records);
- Ext.each(records, function(rec){
- delete rec;
- }, this);
- };
- },
-
- //private
- removePhantomRecords: function(){
- this.each(function(r){
- if(r.phantom){
- this.remove(r);
- delete r;
- }
- }, this);
- }
-
-});
-
-Ext.reg('ehr-store', EHR.ext.AdvancedStore);
-
diff --git a/ehr/resources/web/ehr/ext3/ehrStoreCollection.js b/ehr/resources/web/ehr/ext3/ehrStoreCollection.js
deleted file mode 100644
index 27fcdba1b..000000000
--- a/ehr/resources/web/ehr/ext3/ehrStoreCollection.js
+++ /dev/null
@@ -1,705 +0,0 @@
-/*
- * Copyright (c) 2013-2019 LabKey Corporation
- *
- * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
- */
-
-Ext.namespace('EHR.ext', 'EHR.ext.plugins');
-
-
-/**
- * This class will manage a collection of child EHR.ext.AdvancedStores. When a store is registered with the StoreCollection then StoreCollection will
- * manage commit records and interpret the response. Where possible, this class seeks to delegate processing to the child stores. An instance of
- * this class is automatically created by EHR.ext.ImportPanels, which is the primary use case; however, it could be created directly.
- * @class
- * @name EHR.ext.StoreCollection
- * @aguments Ext.util.MixedCollection
- * @param {object} config Configuration object.
- * @param {boolean} [config.monitorValid] If true, this store will monitor the valid status of record in child stores. See EHR.ext.AdvancedStore for more on validation.
- *
- */
-EHR.ext.StoreCollection = Ext.extend(Ext.util.MixedCollection, {
- constructor: function(config){
- Ext.apply(this, config);
-
- //inheritance code separated for now
- Ext.apply(this, EHR.ext.StoreInheritance);
-
- EHR.ext.StoreCollection.superclass.constructor.call(this, false, function(item){return item.storeId;});
- this.addEvents('beforecommit', 'commitcomplete', 'commitexception', 'update', 'validation');
- },
-
- /**
- * Add a store to this collection
- * @memberOf EHR.ext.StoreCollection
- * @param store The store to add.
- */
- add: function(store){
- store = Ext.StoreMgr.lookup(store);
- if (this.contains(store)){
- //console.log('Store already added: '+store.queryName);
- return;
- }
-
- if (!this.containerPath)
- this.containerPath = store.containerPath;
-
- //check whether container path matches
- if (store.containerPath && store.containerPath != this.containerPath)
- console.log('possible problem: container doesn\'t match');
-
- EHR.ext.StoreCollection.superclass.add.call(this, store.storeId, store);
-
- Ext.apply(store, {
- parentStore: this,
- monitorValid: this.monitorValid
- //allowOthersToEditRecords: this.allowOthersToEditRecords
- });
-
- if(this.monitorValid){
- store.on('validation', this.onValidation, this);
- store.initMonitorValid();
- }
-
- this.initInheritance(store);
-
- this.relayEvents(store, ['update']);
-
- },
-
- //private
- initMonitorValid: function(){
- this.monitorValid = true;
- this.each(function(store){
- store.on('validation', this.onValidation, this);
- }, this);
- },
-
- //private
- stopMonitorValid: function(){
- this.each(function(store){
- this.store.un('validation', this.onValidation, this);
- }, this);
- this.monitorValid = false;
- },
-
- /**
- * Removes a child store from this collection
- * @memberOf EHR.ext.StoreCollection
- * @param store The store to remove.
- */
- remove: function(store){
- //TODO: this is done to undo relayEvents() set above.
- if (store.hasListener('update')) {
- store.events['update'].clearListeners();
- }
-
- store.un('validation', this.onValidation, this);
- delete store.parentStore;
-
- EHR.ext.StoreCollection.superclass.remove.call(store);
- },
-
- //private
- getChanged: function(commitAll){
- var allCommands = [];
- var allRecords = [];
-
- this.each(function(s){
- var records;
- if(commitAll)
- records = s.getAllRecords();
- else
- records = s.getModifiedRecords();
-
- var commands = s.getChanges(records);
-
- if (commands.length){
- allCommands = allCommands.concat(commands);
- allRecords = allRecords.concat(records);
- }
- else if (commands.length && !records.length){
- console.log('ERROR: there are modified records but no commands');
- }
- }, this);
-
- return {
- commands: allCommands,
- records: allRecords
- }
- },
-
- commitChanges : function(extraContext, commitAll) {
- var changed = this.getChanged(commitAll);
- this.commit(changed.commands, changed.records, extraContext);
- },
-
- commitRecord: function(record, extraContext){
- record.store.commitRecords([record], extraContext);
- },
-
- //private
- commit: function(commands, records, extraContext){
- extraContext = extraContext || {};
-
- if (!commands || !commands.length){
-// if(extraContext && extraContext.silent!==true)
-// Ext.Msg.alert('Alert', 'There are no changes to submit.');
-
- this.onComplete(extraContext);
- return;
- }
-
- if(this.fireEvent('beforecommit', records, commands, extraContext)===false)
- return;
-
- extraContext = extraContext || {};
- extraContext.isLegacyFormat = true;
-
- var request = Ext.Ajax.request({
- url : LABKEY.ActionURL.buildURL('query', 'saveRows', this.containerPath),
- method : 'POST',
- success: this.onCommitSuccess,
- failure: this.getOnCommitFailure(records),
- scope: this,
- timeout: this.timeout || 0,
- jsonData : {
- containerPath: this.containerPath,
- commands: commands,
- extraContext: extraContext
- },
- headers : {
- 'Content-Type' : 'application/json'
- }
- });
-
- Ext.each(records, function(rec){
- rec.lastTransactionId = request.tId;
- }, this);
- },
- /**
- * Will test whether all records in this store collection pass validation or not.
- * @memberOf EHR.ext.StoreCollection
- * @returns {Boolean} True/false depending on whether all records in this StoreCollection pass validation
- */
- isValid: function(){
- var valid = true;
- this.each(function(s){
- if(!s.isValid()){
- valid=false
- }
- }, this);
- return valid;
- },
- /**
- * Tests whether any records in this store collection are dirty
- * @returns {boolean} True/false depending on whether any records in the collection are dirty.
- */
- isDirty: function()
- {
- var dirty = false;
- this.each(function(s){
- if(s.getModifiedRecords().length)
- dirty=true;
- }, this);
- return dirty;
- },
-
- //private
- //tests whether any store are loading or not
- isLoading: function(){
- var isLoading = false;
- this.each(function(s){
- if(s.isLoading){
- isLoading = true;
- }
- }, this);
-
- return isLoading;
- },
-
- //private
- //returns an array of the queries represented in this store
- getQueries: function(){
- var queries = [];
- this.each(function(s){
- queries.push({
- schemaName: s.schemaName,
- queryName: s.queryName
- })
- }, this);
- return queries;
- },
-
- //private
- onValidation: function(store, records){
- //check all stores
- var maxSeverity = '';
- this.each(function(store){
- maxSeverity = EHR.Utils.maxError(maxSeverity, store.maxErrorSeverity());
- }, this);
-
- this.fireEvent('validation', this, maxSeverity);
- },
-
- //private
- getErrors: function(){
- var errors = [];
- this.each(function(store){
- store.errors.each(function(error){
- errors.push(error);
- }, this);
- }, this);
-
- return errors;
- },
-
- //private
- getOnCommitFailure : function(records) {
- return function(response, options) {
- //note: should not matter which child store they belong to
- for(var idx = 0; idx < records.length; ++idx)
- delete records[idx].saveOperationInProgress;
-
- var serverError = this.getJson(response);
- var msg = '';
- if(serverError && serverError.errors){
- //each error should represent 1 row. there can be multiple errors per row
- Ext.each(serverError.errors, function(rowError){
- //handle validation script errors and exceptions differently
- if(rowError.errors && rowError.errors.length && rowError.row){
- this.handleValidationErrors(rowError, response, serverError.extraContext);
- msg = rowError.exception || "Could not save changes due to errors. Please check the form for fields marked in red.";
- }
- else {
- //if an exception was thrown, I believe we automatically only have one error returned
- //this means this can only be called once
- msg = 'Could not save changes due to the following error:\n' + (serverError && serverError.exception) ? serverError.exception : response.statusText;
- }
- }, this);
-
- if(!serverError.errors){
- msg = 'Could not save changes due to the following error:\n' + serverError.exception;
- }
- }
-
- if(false !== this.fireEvent("commitexception", msg, serverError) && (options.jsonData.extraContext && !options.jsonData.extraContext.silent)){
- Ext.Msg.alert("Error", "Error During Save. "+msg);
- console.log(serverError);
- }
- };
- },
-
- //private
- handleValidationErrors: function(serverError, response, extraContext){
- var store = this.get(extraContext.storeId);
- var record = store.getById(serverError.row._recordid);
- if(record){
- store.handleValidationError(record, serverError, response, extraContext);
- }
- else {
- console.log('ERROR: Record not found');
- console.log(serverError);
- }
- },
-
- //private
- onCommitSuccess : function(response, options){
- var json = this.getJson(response);
- if(!json || !json.result)
- return;
-
- for (var i=0;i= 0)
- ? Ext.util.JSON.decode(response.responseText)
- : null;
- },
-
- //private
- deleteAllRecords: function(extraContext){
- //NOTE: we delegate the deletion to each store, and track progress here so we can fire a single event
- var storesPerformingDeletes = [];
- var failures = 0;
-
- this.each(function(s){
- var records = [];
- s.each(function(r){
- records.push(r);
- }, this);
-
- function onComplete(response){
- s.un('commitcomplete', onComplete);
- s.un('commitexception', onComplete);
-
- if(storesPerformingDeletes.indexOf(s.storeId)!=-1){
- storesPerformingDeletes.remove(s.storeId)
- }
-
- if(!storesPerformingDeletes.length){
- if(failures == 0){
- this.onComplete(extraContext);
- }
- else {
- this.fireEvent('commitexception');
- }
- }
- }
- s.on('commitcomplete', onComplete, this, {single: true});
- s.on('commitexception', onComplete, this, {single: true});
-
- storesPerformingDeletes.push(s.storeId);
- s.deleteRecords(records, extraContext);
- }, this);
- },
-
- //private
- //this is distinct from deleteAllRecords. instead of deleting the records, it changes the QCState to 'Delete Requested'. these
- //records are deleted periodically by a separate cron script. this exists b/c historically delete performance was extrememly poor.
- //this has been fixed and the data entry code should probably be refactored to directly delete the records.
- requestDeleteAllRecords: function(options){
- options = options || {};
-
- //add a context flag to the request to saveRows
- var extraContext = Ext.apply({
- importPathway: 'ehr-ext3DataEntry'
- //,targetQC : 'Delete Requested'
- }, options);
-
- var commands = [];
- var records = [];
- this.each(function(s){
- s.removePhantomRecords();
- s.each(function(r){
- var recs = [];
- r.beginEdit();
- if(r.get('requestid') || r.get('requestId')){
- //note: we reject changes since we dont want to retain modifications made in this form
- r.reject();
-
- //reset the date
- if(r.get('daterequested'))
- r.set('date', r.get('daterequested'));
-
- //remove from this task
- if(s.queryName!='tasks')
- r.set('taskid', null);
-
- r.set('QCState', EHR.Security.getQCStateByLabel('Request: Approved').RowId);
- r.set('qcstate', EHR.Security.getQCStateByLabel('Request: Approved').RowId);
- }
- else {
- r.set('QCState', EHR.Security.getQCStateByLabel('Delete Requested').RowId);
- r.set('qcstate', EHR.Security.getQCStateByLabel('Delete Requested').RowId);
- }
- recs.push(r);
-
-
- if(recs.length){
- var changes = s.getChanges(recs);
- if(changes.length){
- commands = commands.concat(changes);
- records = records.concat(recs);
- }
- }
- }, this);
- }, this);
-
- //NOTE: since this will navigate away from this page, we dont need to bother removing
- //these records from the store
- if(commands.length){
- this.commit(commands, records, extraContext);
- }
- else {
- this.onComplete(extraContext);
- }
- },
-
- //private
- //returns all records in all child stores
- getAllRecords: function(){
- var records = [];
- this.each(function(s){
- s.each(function(r){
- records.push(r)
- }, this);
- }, this);
- return records;
- },
-
- //private
- //NOTE: used for development. should get removed eventually
- showStores: function(){
- this.each(function(s){
- if(s.getCount()){
- console.log(s.storeId);
- console.log(s);
- console.log('Num Records: '+s.getCount());
- console.log('Total Records: '+s.getTotalCount());
- console.log('Modified Records:');
- console.log(s.getModifiedRecords());
- s.each(function(rec)
- {
- console.log('record ID: '+rec.id);
- console.log('record is dirty?: '+rec.dirty);
- console.log('record is phantom?: '+rec.phantom);
- console.log('saveOperationInProgress? '+rec.saveOperationInProgress);
- Ext.each(rec.fields.keys, function(f){
- console.log(f + ': ' + rec.get(f));
- }, s);
- }, s)
- }
- }, this);
- },
-
- //private
- //for debugging purposes
- showErrors: function(){
- console.log(this.getErrors());
- }
-});
-
-/*
- * A plugin applied to EHR.ext.StoreCollection that allows field metadata to specificy parent/child relationships between records
- * See EHR.Metadata.FieldMetadata.parentConfig
- */
-EHR.ext.StoreInheritance = {
- initInheritance: function(store) {
- store.on('beforemetachange', this.addInheritanceListeners, this);
-
- //if the store is already loaded, we need to refresh metadata
- if(store.reader.meta.fields){
- this.addInheritanceListeners(store);
- }
- },
- relationships: new Ext.util.MixedCollection(false, function(s){return s.key}),
- addInheritanceListeners: function(store, meta, field){
- meta = meta || store.reader.meta;
-
- if(!field){
- Ext.each(meta.fields, function(f){
- this.handleField(store, meta, f);
- }, this);
- }
- else {
- this.handleField(store, meta, field);
- }
- //b/c store.fields is read only, we need to manually reload metadata
- //this is necessary for setInitialValue() to be copied
- //console.log('refreshing metadata for store: '+store.storeId);
- store.reader.onMetaChange(meta);
- },
- handleField: function(store, meta, field){
- if(field.parentConfig){
- if(!field.parentConfig.parent)
- this.findParent(store, meta, field);
-
- if(field.parentConfig.parent)
- this.addInheritanceListener(store, field);
- }
- },
- findParent: function(store, meta, field){
- var targetStore;
- if(Ext.isFunction(field.parentConfig.storeIdentifier)){
- targetStore = field.parentConfig.storeIdentifier();
- }
- else {
- targetStore = this.find(function(s){
- for (var i in field.parentConfig.storeIdentifier){
- if(s[i] != field.parentConfig.storeIdentifier[i])
- return false;
- }
- return true;
- });
- }
- if(!targetStore){
- console.log('ERROR: target store not found');
- console.log(field.parentConfig);
- this.on('add', function(){
- this.addInheritanceListeners(store, meta, field)
- }, this, {single: true});
- return;
- }
-
- if(targetStore == store){
- //console.log('target store is parent, skipping')
- return;
- }
-
- //this function is used to retry a store when records are not found
- function retryStore(){
- //console.log('retrying store: '+targetStore.storeId+' for field :'+field.name);
- this.addInheritanceListeners(store, meta, field)
- }
-
- //in this case the store has not loaded yet
- if(!targetStore.fields){
- targetStore.on('load', retryStore, this, {single: true});
- return;
- }
-// //the store is loaded, but has no records
-// else if (!targetStore.getCount()){
-// //console.log('no records in store: '+targetStore.storeId);
-// targetStore.on('add', retryStore, this, {single: true});
-// return;
-// }
-//
-// //console.log('parent found: '+targetStore.storeId+" for table "+store.storeId+' for field '+field.name);
-// if(field.parentConfig.recordIdentifier){
-// field.parentConfig.parent = field.parentConfig.recordIdentifier(targetStore);
-// if(!field.parentConfig.parent){
-// targetStore.on('add', retryStore, this, {single: true});
-// }
-// }
-// else {
-// //if recordIdentifier is not provided, we always take the first record
-// field.parentConfig.parent = targetStore.getAt(0);
-// }
- field.parentConfig.parent = targetStore;
-
- },
- addInheritanceListener: function(store, field){
- var key = [store.storeId, field.name].join(':');
- var config = {
- key: key,
- store: store,
- field: field,
- listeners: {},
- listenerTarget: null,
- parent: null
- };
-
- field.oldSetInitialValue = field.setInitialValue;
- var parent = field.parentConfig.parent;
- config.parent = parent;
-
- if (parent instanceof Ext.data.Store){
- config.listenerTarget = parent;
-
- function findParentRec(field, parent, childRecord){
- var parentRec;
- if(!field.parentConfig.recordIdentifier){
- parentRec = parent.getAt(0);
- }
- else {
- var idx = parent.findBy(function(record){return field.parentConfig.recordIdentifier.call(this, record, childRecord)}, this);
- if(idx != -1)
- parentRec = parent.getAt(idx);
- }
-
- return parentRec;
- }
-
- //console.log('adding '+field.name+' from : '+parent.store.storeId + '/to: '+store.storeId);
- config.listeners.update = function(parent, childStore){return function(store, rec, idx){
- var parentRec;
- childStore.each(function(rec){
- parentRec = findParentRec(field, parent, rec);
-
- if(!parentRec){
- console.log('No matching record');
- return;
- }
-
- //console.log('inheritance listener called on '+field.dataIndex+'/childStore: '+childStore.storeId+' /parentStore: '+store.storeId+'/rec: '+rec.id);
- //console.log('setting value to: '+parentRec.get(field.parentConfig.dataIndex));
- rec.set(field.dataIndex, parentRec.get(field.parentConfig.dataIndex));
- }, config);
- }}(parent, store);
-
- //TODO: we might need to account for the possibility of head records being deleted
-// config.listeners.remove = function(store, rec){
-// if(rec === field.parentConfig.parent){
-// this.removeInheritanceListeners(store);
-// this.addInheritanceListeners(store);
-// }
-// };
-
- field.setInitialValue = function(v, rec, f){
- var parentRec = findParentRec(field, parent, rec);
- if(!parentRec){
- return;
- }
-
- //console.log('setting initial val for: '+f.name + ' to ' +parentRec.get(f.parentConfig.dataIndex));
- return parentRec.get(f.parentConfig.dataIndex);
- }
- }
- else {
- console.log('problem with parent');
- console.log(field.parentConfig.parent);
- return;
- }
-
- if(this.relationships.contains(key)){
- var oldConfig = this.relationships.get(key);
- console.log('key already exists: '+key);
- if(oldConfig.parent === config.parent){
- console.log('same parent - aborting');
- return;
- }
- else
- this.removeInheritanceListener(key);
- }
-
- this.relationships.add(config);
- for (var l in config.listeners){
- //console.log('adding '+l+' listener from: '+config.store.storeId+' to: '+config.listenerTarget.storeId+'/'+config.field.name)
- config.listenerTarget.on(l, config.listeners[l], this);
- };
-
- //update any pre-existing records
- //NOTE: if called before render the grid throws an error
- store.each(function(rec){
- var parentRec = findParentRec(field, parent, rec);
- if(!parentRec)
- return;
-
- var initialVal = parentRec.get(field.parentConfig.dataIndex);
-
- rec.beginEdit();
- if(rec.get(field.dataIndex) != initialVal){
- rec.set(field.dataIndex, initialVal);
- }
- rec.endEdit();
- }, this);
-
- },
- removeInheritanceListeners: function(store){
- store.fields.each(function(f){
- this.removeInheritanceListener([store.storeId, f.name].join(':'));
- }, this);
- },
- removeInheritanceListener: function(key){
- var config = this.relationships.get(key);
- if(config){
- Ext.each(config.listeners, function(l){
- config.listenerTarget.un(l, config.listeners[l], this);
- }, this);
- }
- }
-};
diff --git a/ehr/resources/web/ehr/ext3/ext.ux.datetimefield.js b/ehr/resources/web/ehr/ext3/ext.ux.datetimefield.js
deleted file mode 100644
index f01b26935..000000000
--- a/ehr/resources/web/ehr/ext3/ext.ux.datetimefield.js
+++ /dev/null
@@ -1,226 +0,0 @@
-/**
- * @author Andrew Pleshkov
- */
-//http://www.sencha.com/forum/showthread.php?98292-DateTime-field-again-and-again-%29&highlight=time+picker
-//new Ext.ux.form.DateTimeField({
-// fieldLabel: 'date & time',
-// dateFormat: 'd.m.Y',
-// timeFormat: 'H:i'
-//});
-Ext.namespace('Ext.ux.form');
-
-(function () {
-
- var Form = Ext.ux.form;
- Ext.menu.DateMenu.prototype.addClass('extContainer');
- //
-
- function doSomeAlchemy(picker) {
- Ext.apply(picker, {
-
- _getDateTime: function (value) {
- if (this.timeField != null) {
- var timeval = this.timeField.getValue();
- value = Date.parseDate(value.format(this.dateFormat) + ' ' + timeval, this.format);
- }
- return value;
- },
-
- _initTimeField: function () {
- if (null == this.timeField) {
- var config = Ext.apply({}, this.timeFieldConfig, {
- width: 100
- });
- var timeField = this.timeField = Ext.ComponentMgr.create(config, 'timefield');
-
- if (timeField instanceof Ext.form.ComboBox) {
- timeField.getListParent = function () {
- return this.el.up('.x-menu');
- }.createDelegate(timeField);
-
- if (Ext.isIE7) {
- timeField.maxHeight = 190;
- }
- }
- }
- },
-
- setValue: function (value) {
- if (null == this.timeField) {
- this._initTimeField();
- this.timeField.setValue(value);
- }
-
- this.value = this._getDateTime(value);
- this.update(this.value, true);
- },
-
- update: function (date, forceRefresh) {
- var d = date.clone().clearTime();
- Ext.DatePicker.prototype.update.call(this, d, forceRefresh);
- },
-
- _handleTimeButtonClick: function (e) {
- e.stopEvent();
- var t = this.el.child('table.x-date-inner td.x-date-selected a', true);
- this.handleDateClick(e, t);
- },
-
- onRender: function () {
- Ext.DatePicker.prototype.onRender.apply(this, arguments);
-
- var cls = 'ux-form-datetimefield';
- var timeBtnCls = cls + '-timeButton';
-
- var table = this.el.child('table');
-
- Ext.DomHelper.insertBefore(table.child('tr:first'), {
- tag: 'tr',
- children: [
- {
- tag: 'td',
- colspan: '3',
- cls: 'x-date-bottom',
- style: 'border-top: 0',
- children: [
- {
- tag: 'table',
- cellspacing: 0,
- cls: 'x-date-picker',
- style: 'background: transparent',
- children: [
- {
- tag: 'tbody',
- children: [
- {
- tag: 'tr',
- children: [
- {
- tag: 'td',
- style: 'padding-right: 5px',
- html: this.timeFieldLabel
- },
- {
- tag: 'td',
- cls: cls
- },
- {
- tag: 'td',
- cls: 'x-date-right',
- style: 'text-align: left; background: transparent; padding-left: 5px',
- html: ''
- }
- ]
- }
- ]
- }
- ]
- }
- ]
- }
- ]
- }, true);
-
- var selBtn = table.child('a.' + timeBtnCls);
- selBtn.on('click', this._handleTimeButtonClick, this);
-
- var ct = table.child('td.' + cls);
- this.timeField.render(ct);
- },
-
- beforeDestroy: function () {
- if (this.timeField) {
- Ext.destroy(this.timeField);
- delete this.timeField;
- }
-
- Ext.DatePicker.prototype.beforeDestroy.call(this);
- },
-
- fixIE: function () {
- var el = this.timeField.el;
- el.repaint();
- }
-
- });
- }
-
- //
-
- var DateMenu = Ext.extend(Ext.menu.DateMenu, {
-
- initComponent: function () {
- DateMenu.superclass.initComponent.call(this);
-
- if (Ext.isStrict && Ext.isIE7) {
- this.on('show', function () {
- var h = this.picker.el.getComputedHeight();
- h += this.el.getFrameWidth('tb');
- this.setHeight(h);
- }, this, { single: true });
- }
-
- // Using of Ext.DatePicker as this.picker is hardcoded in Ext.menu.DateMenu,
- // so we need to do some alchemy to provide additional functionality and avoid copypasta
- doSomeAlchemy(this.picker);
- },
-
- onShow: function () {
- DateMenu.superclass.onShow.apply(this, arguments);
-
- this.picker.fixIE();
- }
-
- });
-
- //
-
- Form.DateTimeField = Ext.extend(Ext.form.DateField, {
-
- defaultAutoCreate : {
- tag: "input",
- type: "text",
- size: "20",
- autocomplete: "off"
- },
-
- timeFieldLabel: 'Time',
-
- initComponent: function () {
- var tfc = this.timeFieldConfig || {};
-
- if (this.timeFormat != null && tfc.format != null) {
- throw 'What time format do you prefer?';
- }
- var timeFormat = this.timeFormat || tfc.format || Ext.form.TimeField.prototype.format;
- this.timeFormat = tfc.format = timeFormat;
-
- this.timeFieldConfig = tfc;
-
- this.dateFormat = this.dateFormat || Ext.form.DateField.prototype.format;
- this.format = this.dateFormat + ' ' + this.timeFormat;
-
- Form.DateTimeField.superclass.initComponent.call(this);
- },
-
- onTriggerClick: function () {
- if (null == this.menu) {
- this.menu = new DateMenu({
- hideOnClick: false,
- focusOnSelect: false,
- //
- timeFieldLabel: this.timeFieldLabel,
- timeFieldConfig: this.timeFieldConfig,
- dateFormat: this.dateFormat,
- format: this.format
- });
- }
-
- Form.DateTimeField.superclass.onTriggerClick.call(this);
- }
-
- });
-
- Ext.reg('datetimefield', Form.DateTimeField);
-
-})();
diff --git a/ehr/resources/web/ehr/ext3/ext.ux.multiselect.js b/ehr/resources/web/ehr/ext3/ext.ux.multiselect.js
deleted file mode 100644
index b2c16e9b4..000000000
--- a/ehr/resources/web/ehr/ext3/ext.ux.multiselect.js
+++ /dev/null
@@ -1,681 +0,0 @@
-/*
- * ! Ext JS Library 3.2.0 Copyright(c) 2006-2010 Ext JS, Inc.
- * licensing@extjs.com http://www.extjs.com/license
- */
-Ext.ns('Ext.ux.form');
-
-/**
- * @class Ext.ux.form.MultiSelect
- * @extends Ext.form.Field A control that allows selection and form submission
- * of multiple list items.
- *
- * @history 2008-06-19 bpm Original code contributed by Toby Stuart (with
- * contributions from Robert Williams) 2008-06-19 bpm Docs and demo
- * code clean up
- *
- * @constructor Create a new MultiSelect
- * @param {Object}
- * config Configuration options
- * @xtype multiselect
- */
-Ext.ux.form.MultiSelect = Ext.extend(Ext.form.Field, {
- /**
- * @cfg {String} legend Wraps the object with a fieldset and specified legend.
- */
- /**
- * @cfg {Ext.ListView} view The {@link Ext.ListView} used to render the
- * multiselect list.
- */
- /**
- * @cfg {String/Array} dragGroup The ddgroup name(s) for the MultiSelect
- * DragZone (defaults to undefined).
- */
- /**
- * @cfg {String/Array} dropGroup The ddgroup name(s) for the MultiSelect
- * DropZone (defaults to undefined).
- */
- /**
- * @cfg {Boolean} ddReorder Whether the items in the MultiSelect list are
- * drag/drop reorderable (defaults to false).
- */
- ddReorder : false,
- /**
- * @cfg {Object/Array} tbar The top toolbar of the control. This can be a
- * {@link Ext.Toolbar} object, a toolbar config, or an array of
- * buttons/button configs to be added to the toolbar.
- */
- /**
- * @cfg {String} appendOnly True if the list should only allow append drops
- * when drag/drop is enabled (use for lists which are sorted, defaults to
- * false).
- */
- appendOnly : false,
- /**
- * @cfg {Number} width Width in pixels of the control (defaults to 100).
- */
- width : 100,
- /**
- * @cfg {Number} height Height in pixels of the control (defaults to 100).
- */
- height : 100,
- /**
- * @cfg {String/Number} displayField Name/Index of the desired display field
- * in the dataset (defaults to 0).
- */
- displayField : 0,
- /**
- * @cfg {String/Number} valueField Name/Index of the desired value field in
- * the dataset (defaults to 1).
- */
- valueField : 1,
- /**
- * @cfg {Boolean} allowBlank False to require at least one item in the list to
- * be selected, true to allow no selection (defaults to true).
- */
- allowBlank : true,
- /**
- * @cfg {Number} minSelections Minimum number of selections allowed (defaults
- * to 0).
- */
- minSelections : 0,
- /**
- * @cfg {Number} maxSelections Maximum number of selections allowed (defaults
- * to Number.MAX_VALUE).
- */
- maxSelections : Number.MAX_VALUE,
- /**
- * @cfg {String} blankText Default text displayed when the control contains no
- * items (defaults to the same value as
- * {@link Ext.form.TextField#blankText}.
- */
- blankText : Ext.form.TextField.prototype.blankText,
- /**
- * @cfg {String} minSelectionsText Validation message displayed when
- * {@link #minSelections} is not met (defaults to 'Minimum {0} item(s)
- * required'). The {0} token will be replaced by the value of
- * {@link #minSelections}.
- */
- minSelectionsText : 'Minimum {0} item(s) required',
- /**
- * @cfg {String} maxSelectionsText Validation message displayed when
- * {@link #maxSelections} is not met (defaults to 'Maximum {0} item(s)
- * allowed'). The {0} token will be replaced by the value of
- * {@link #maxSelections}.
- */
- maxSelectionsText : 'Maximum {0} item(s) allowed',
- /**
- * @cfg {String} delimiter The string used to delimit between items when set
- * or returned as a string of values (defaults to ',').
- */
- delimiter : ',',
- /**
- * @cfg {Ext.data.Store/Array} store The data source to which this MultiSelect
- * is bound (defaults to undefined). Acceptable values for
- * this property are:
- *
- *
any {@link Ext.data.Store Store} subclass
- *
an Array : Arrays will be converted to a
- * {@link Ext.data.ArrayStore} internally.
- *
- *
1-dimensional array : (e.g., ['Foo','Bar'])
A 1-dimensional array will automatically be expanded
- * (each array item will be the combo {@link #valueField value} and
- * {@link #displayField text})
For a
- * multi-dimensional array, the value in index 0 of each item will be
- * assumed to be the combo {@link #valueField value}, while the value at
- * index 1 is assumed to be the combo {@link #displayField text}.
- *
- *
- *
- *
- */
-
- // private
- defaultAutoCreate : {
- tag : 'div'
- },
-
- // private
- initComponent : function() {
- Ext.ux.form.MultiSelect.superclass.initComponent.call(this);
-
- if (Ext.isArray(this.store)) {
- if (Ext.isArray(this.store[0])) {
- this.store = new Ext.data.ArrayStore({
- fields : [
- 'value', 'text'
- ],
- data : this.store
- });
- this.valueField = 'value';
- } else {
- this.store = new Ext.data.ArrayStore({
- fields : [
- 'text'
- ],
- data : this.store,
- expandData : true
- });
- this.valueField = 'text';
- }
- this.displayField = 'text';
- } else {
- this.store = Ext.StoreMgr.lookup(this.store);
- }
-
- this.addEvents({
- 'dblclick' : true,
- 'click' : true,
- 'change' : true,
- 'drop' : true
- });
- },
-
- // private
- onRender : function(ct, position) {
- Ext.ux.form.MultiSelect.superclass.onRender.call(this, ct, position);
-
- var fs = this.fs = new Ext.form.FieldSet({
- renderTo : this.el,
- title : this.legend,
- height : this.height,
- width : this.width,
- //NOTE: Following CSS added by bbimber
- style : 'padding:0;margin-left:0px',
- tbar : this.tbar,
- autoScroll: true
- });
- fs.body.addClass('ux-mselect');
-
- this.view = new Ext.ListView({
- multiSelect : true,
- store : this.store,
- //NOTE: Added class to fix labkey CSS
- cls: 'extContainer',
- columns : [
- {
- header : 'Value',
- width : 1,
- dataIndex : this.displayField
- }
- ],
- hideHeaders : true
- });
-
- fs.add(this.view);
-
- this.view.on('click', this.onViewClick, this);
- this.view.on('beforeclick', this.onViewBeforeClick, this);
- this.view.on('dblclick', this.onViewDblClick, this);
-
- this.hiddenName = this.name || Ext.id();
- var hiddenTag = {
- tag : 'input',
- type : 'hidden',
- value : '',
- name : this.hiddenName
- };
- this.hiddenField = this.el.createChild(hiddenTag);
- this.hiddenField.dom.disabled = this.hiddenName != this.name;
- fs.doLayout();
-
- //value cannot be set unless rendered. this is a quick way around that
- if(this.value || this.initialValue){
- this.setValue(this.value || this.initialValue);
- delete this.initialValue;
- }
- },
-
- // private
- afterRender : function() {
- Ext.ux.form.MultiSelect.superclass.afterRender.call(this);
-
- if (this.ddReorder && !this.dragGroup && !this.dropGroup) {
- this.dragGroup = this.dropGroup = 'MultiselectDD-' + Ext.id();
- }
-
- if (this.draggable || this.dragGroup) {
- this.dragZone = new Ext.ux.form.MultiSelect.DragZone(this, {
- ddGroup : this.dragGroup
- });
- }
- if (this.droppable || this.dropGroup) {
- this.dropZone = new Ext.ux.form.MultiSelect.DropZone(this, {
- ddGroup : this.dropGroup
- });
- }
- },
-
- // private
- onViewClick : function(vw, index, node, e) {
- this.fireEvent('change', this, this.getValue(), this.hiddenField.dom.value);
- this.hiddenField.dom.value = this.getValue();
- this.fireEvent('click', this, e);
- this.validate();
- },
-
- // private
- onViewBeforeClick : function(vw, index, node, e) {
- if (this.disabled) {
- return false;
- }
- },
-
- // private
- onViewDblClick : function(vw, index, node, e) {
- return this.fireEvent('dblclick', vw, index, node, e);
- },
-
- /**
- * Returns an array of data values for the selected items in the list. The
- * values will be separated by {@link #delimiter}.
- *
- * @return {Array} value An array of string data values
- */
- getValue : function(valueField) {
- //if called prior to render
- if(!this.rendered){
- return this.value || this.initialValue;
- }
- var returnArray = [];
- var selectionsArray = this.view.getSelectedIndexes();
- if (selectionsArray.length == 0) {
- return '';
- }
- for (var i = 0; i < selectionsArray.length; i++) {
- returnArray.push(this.store.getAt(selectionsArray[i]).get((valueField != null) ? valueField : this.valueField));
- }
- return returnArray.join(this.delimiter);
- },
-
- /**
- * Sets a delimited string (using {@link #delimiter}) or array of data values
- * into the list.
- *
- * @param {String/Array}
- * values The values to set
- */
- setValue : function(values) {
- var index;
- var selections = [];
- values = values || [];
-
- if (!Ext.isArray(values)) {
- values = values.split(this.delimiter);
- }
-
-
- if(!this.rendered){
- this.initialValue = values.join(this.delimiter);
- return;
- };
-
- this.view.clearSelections();
- this.hiddenField.dom.value = '';
-
- if (!values || (values == '')) {
- return;
- }
-
- for (var i = 0; i < values.length; i++) {
- index = this.view.store.indexOf(this.view.store.query(this.valueField, new RegExp('^' + values[i] + '$', 'i')).itemAt(0));
- selections.push(index);
- }
- this.view.select(selections);
- this.hiddenField.dom.value = this.getValue();
- this.validate();
- },
-
- // inherit docs
- reset : function() {
- this.setValue('');
- },
-
- // inherit docs
- getRawValue : function(valueField) {
- var tmp = this.getValue(valueField);
- if (tmp.length) {
- tmp = tmp.split(this.delimiter);
- } else {
- tmp = [];
- }
- return tmp;
- },
-
- // inherit docs
- setRawValue : function(values) {
- setValue(values);
- },
-
- // inherit docs
- validateValue : function(value) {
- if (value.length < 1) { // if it has no value
- if (this.allowBlank) {
- this.clearInvalid();
- return true;
- } else {
- this.markInvalid(this.blankText);
- return false;
- }
- }
- if (value.length < this.minSelections) {
- this.markInvalid(String.format(this.minSelectionsText, this.minSelections));
- return false;
- }
- if (value.length > this.maxSelections) {
- this.markInvalid(String.format(this.maxSelectionsText, this.maxSelections));
- return false;
- }
- return true;
- },
-
- // inherit docs
-// disable : function() {
-// this.disabled = true;
-// this.hiddenField.dom.disabled = true;
-// this.fs.disable();
-// },
-//
-// // inherit docs
-// enable : function() {
-// this.disabled = false;
-// this.hiddenField.dom.disabled = false;
-// this.fs.enable();
-// },
-
- // inherit docs
- destroy : function() {
- Ext.destroy(this.fs, this.dragZone, this.dropZone);
- Ext.ux.form.MultiSelect.superclass.destroy.call(this);
- }
-});
-
-Ext.reg('multiselect', Ext.ux.form.MultiSelect);
-
-// backwards compat
-Ext.ux.Multiselect = Ext.ux.form.MultiSelect;
-
-Ext.ux.form.MultiSelect.DragZone = function(ms, config) {
- this.ms = ms;
- this.view = ms.view;
- var ddGroup = config.ddGroup || 'MultiselectDD';
- var dd;
- if (Ext.isArray(ddGroup)) {
- dd = ddGroup.shift();
- } else {
- dd = ddGroup;
- ddGroup = null;
- }
- Ext.ux.form.MultiSelect.DragZone.superclass.constructor.call(this, this.ms.fs.body, {
- containerScroll : true,
- ddGroup : dd
- });
- this.setDraggable(ddGroup);
-};
-
-Ext.extend(Ext.ux.form.MultiSelect.DragZone, Ext.dd.DragZone, {
- onInitDrag : function(x, y) {
- var el = Ext.get(this.dragData.ddel.cloneNode(true));
- this.proxy.update(el.dom);
- el.setWidth(el.child('em').getWidth());
- this.onStartDrag(x, y);
- return true;
- },
-
- // private
- collectSelection : function(data) {
- data.repairXY = Ext.fly(this.view.getSelectedNodes()[0]).getXY();
- var i = 0;
- this.view.store.each(function(rec) {
- if (this.view.isSelected(i)) {
- var n = this.view.getNode(i);
- var dragNode = n.cloneNode(true);
- dragNode.id = Ext.id();
- data.ddel.appendChild(dragNode);
- data.records.push(this.view.store.getAt(i));
- data.viewNodes.push(n);
- }
- i++;
- }, this);
- },
-
- // override
- onEndDrag : function(data, e) {
- var d = Ext.get(this.dragData.ddel);
- if (d && d.hasClass('multi-proxy')) {
- d.remove();
- }
- },
-
- // override
- getDragData : function(e) {
- var target = this.view.findItemFromChild(e.getTarget());
- if (target) {
- if (!this.view.isSelected(target) && !e.ctrlKey && !e.shiftKey) {
- this.view.select(target);
- this.ms.setValue(this.ms.getValue());
- }
- if (this.view.getSelectionCount() == 0 || e.ctrlKey || e.shiftKey)
- return false;
- var dragData = {
- sourceView : this.view,
- viewNodes : [],
- records : []
- };
- if (this.view.getSelectionCount() == 1) {
- var i = this.view.getSelectedIndexes()[0];
- var n = this.view.getNode(i);
- dragData.viewNodes.push(dragData.ddel = n);
- dragData.records.push(this.view.store.getAt(i));
- dragData.repairXY = Ext.fly(n).getXY();
- } else {
- dragData.ddel = document.createElement('div');
- dragData.ddel.className = 'multi-proxy';
- this.collectSelection(dragData);
- }
- return dragData;
- }
- return false;
- },
-
- // override the default repairXY.
- getRepairXY : function(e) {
- return this.dragData.repairXY;
- },
-
- // private
- setDraggable : function(ddGroup) {
- if (!ddGroup)
- return;
- if (Ext.isArray(ddGroup)) {
- Ext.each(ddGroup, this.setDraggable, this);
- return;
- }
- this.addToGroup(ddGroup);
- }
-});
-
-Ext.ux.form.MultiSelect.DropZone = function(ms, config) {
- this.ms = ms;
- this.view = ms.view;
- var ddGroup = config.ddGroup || 'MultiselectDD';
- var dd;
- if (Ext.isArray(ddGroup)) {
- dd = ddGroup.shift();
- } else {
- dd = ddGroup;
- ddGroup = null;
- }
- Ext.ux.form.MultiSelect.DropZone.superclass.constructor.call(this, this.ms.fs.body, {
- containerScroll : true,
- ddGroup : dd
- });
- this.setDroppable(ddGroup);
-};
-
-Ext.extend(Ext.ux.form.MultiSelect.DropZone, Ext.dd.DropZone, {
- /**
- * Part of the Ext.dd.DropZone interface. If no target node is found, the
- * whole Element becomes the target, and this causes the drop gesture to
- * append.
- */
- getTargetFromEvent : function(e) {
- var target = e.getTarget();
- return target;
- },
-
- // private
- getDropPoint : function(e, n, dd) {
- if (n == this.ms.fs.body.dom) {
- return 'below';
- }
- var t = Ext.lib.Dom.getY(n), b = t + n.offsetHeight;
- var c = t + (b - t) / 2;
- var y = Ext.lib.Event.getPageY(e);
- if (y <= c) {
- return 'above';
- } else {
- return 'below';
- }
- },
-
- // private
- isValidDropPoint : function(pt, n, data) {
- if (!data.viewNodes || (data.viewNodes.length != 1)) {
- return true;
- }
- var d = data.viewNodes[0];
- if (d == n) {
- return false;
- }
- if ((pt == 'below') && (n.nextSibling == d)) {
- return false;
- }
- if ((pt == 'above') && (n.previousSibling == d)) {
- return false;
- }
- return true;
- },
-
- // override
- onNodeEnter : function(n, dd, e, data) {
- return false;
- },
-
- // override
- onNodeOver : function(n, dd, e, data) {
- var dragElClass = this.dropNotAllowed;
- var pt = this.getDropPoint(e, n, dd);
- if (this.isValidDropPoint(pt, n, data)) {
- if (this.ms.appendOnly) {
- return 'x-tree-drop-ok-below';
- }
-
- // set the insert point style on the target node
- if (pt) {
- var targetElClass;
- if (pt == 'above') {
- dragElClass = n.previousSibling ? 'x-tree-drop-ok-between' : 'x-tree-drop-ok-above';
- targetElClass = 'x-view-drag-insert-above';
- } else {
- dragElClass = n.nextSibling ? 'x-tree-drop-ok-between' : 'x-tree-drop-ok-below';
- targetElClass = 'x-view-drag-insert-below';
- }
- if (this.lastInsertClass != targetElClass) {
- Ext.fly(n).replaceClass(this.lastInsertClass, targetElClass);
- this.lastInsertClass = targetElClass;
- }
- }
- }
- return dragElClass;
- },
-
- // private
- onNodeOut : function(n, dd, e, data) {
- this.removeDropIndicators(n);
- },
-
- // private
- onNodeDrop : function(n, dd, e, data) {
- if (this.ms.fireEvent('drop', this, n, dd, e, data) === false) {
- return false;
- }
- var pt = this.getDropPoint(e, n, dd);
- if (n != this.ms.fs.body.dom)
- n = this.view.findItemFromChild(n);
-
- if (this.ms.appendOnly) {
- insertAt = this.view.store.getCount();
- } else {
- insertAt = n == this.ms.fs.body.dom ? this.view.store.getCount() - 1 : this.view.indexOf(n);
- if (pt == 'below') {
- insertAt++;
- }
- }
-
- var dir = false;
-
- // Validate if dragging within the same MultiSelect
- if (data.sourceView == this.view) {
- // If the first element to be inserted below is the target node, remove it
- if (pt == 'below') {
- if (data.viewNodes[0] == n) {
- data.viewNodes.shift();
- }
- } else { // If the last element to be inserted above is the target node,
- // remove it
- if (data.viewNodes[data.viewNodes.length - 1] == n) {
- data.viewNodes.pop();
- }
- }
-
- // Nothing to drop...
- if (!data.viewNodes.length) {
- return false;
- }
-
- // If we are moving DOWN, then because a store.remove() takes place first,
- // the insertAt must be decremented.
- if (insertAt > this.view.store.indexOf(data.records[0])) {
- dir = 'down';
- insertAt--;
- }
- }
-
- for (var i = 0; i < data.records.length; i++) {
- var r = data.records[i];
- if (data.sourceView) {
- data.sourceView.store.remove(r);
- }
- this.view.store.insert(dir == 'down' ? insertAt : insertAt++, r);
- var si = this.view.store.sortInfo;
- if (si) {
- this.view.store.sort(si.field, si.direction);
- }
- }
- return true;
- },
-
- // private
- removeDropIndicators : function(n) {
- if (n) {
- Ext.fly(n).removeClass([
- 'x-view-drag-insert-above', 'x-view-drag-insert-left', 'x-view-drag-insert-right', 'x-view-drag-insert-below'
- ]);
- this.lastInsertClass = '_noclass';
- }
- },
-
- // private
- setDroppable : function(ddGroup) {
- if (!ddGroup)
- return;
- if (Ext.isArray(ddGroup)) {
- Ext.each(ddGroup, this.setDroppable, this);
- return;
- }
- this.addToGroup(ddGroup);
- }
-});
diff --git a/ehr/resources/web/ehr/form/field/ProjectEntryField.js b/ehr/resources/web/ehr/form/field/ProjectEntryField.js
index 70ec02334..b67be7a37 100644
--- a/ehr/resources/web/ehr/form/field/ProjectEntryField.js
+++ b/ehr/resources/web/ehr/form/field/ProjectEntryField.js
@@ -10,351 +10,356 @@
* @cfg includeDefaultProjects defaults to true
*/
Ext4.define('EHR.form.field.ProjectEntryField', {
- extend: 'Ext.form.field.ComboBox',
- alias: 'widget.ehr-projectentryfield',
-
- fieldLabel: 'Project',
- typeAhead: true,
- forceSelection: true, //NOTE: has been re-enabled, but it is important to let the field get set prior to loading
- emptyText:'',
- disabled: false,
- matchFieldWidth: false,
- includeDefaultProjects: true,
- invesLastNameCol: 'lastName',
- autoSelectFirstProjectOnLoad: false,
-
- initComponent: function(){
- this.allProjectStore = EHR.DataEntryUtils.getProjectStore();
-
- this.trigger2Cls = Ext4.form.field.ComboBox.prototype.triggerCls;
- this.onTrigger2Click = Ext4.form.field.ComboBox.prototype.onTriggerClick;
-
- Ext4.apply(this, {
- displayField: 'displayName',
- valueField: 'project',
- queryMode: 'local',
- plugins: [Ext4.create('LDK.plugin.UserEditableCombo', {
- createWindow: function(){
- this.window = Ext4.create('Ext.window.Window', {
- modal: true,
- closeAction: 'destroy',
- bodyStyle: 'padding: 5px',
- title: 'Choose Project',
- items: [{
- xtype: 'ehr-projectfield',
- width: 400,
- fieldLabel: 'Project',
- itemId: 'projectField',
- listeners: {
- specialkey: function(field, e){
- if (e.getKey() === e.ENTER && !field.isExpanded){
- var btn = field.up('window').down('button[text=Submit]');
- btn.handler.apply(btn.scope, [btn]);
- }
- }
- }
- },{
- xtype: 'ldk-linkbutton',
- linkTarget: '_blank',
- text: '[View All Projects]',
- href: LABKEY.ActionURL.buildURL('query', 'executeQuery', null, {schemaName: 'ehr', 'query.queryName': 'project', 'query.viewName': 'Active Projects'}),
- style: 'padding-top: 5px;padding-bottom: 5px;padding-left: 100px;'
- }],
- buttons: [{
- scope: this,
- text: 'Submit',
- handler: function(btn){
- var win = btn.up('window');
- var field = win.down('#projectField');
- var project = field.getValue();
- if (!project){
- Ext4.Msg.alert('Error', 'Must enter a project');
- return;
- }
-
- var rec = field.findRecord('project', project);
- LDK.Assert.assertTrue('Project record not found for id: ' + project, !!rec);
-
- if (rec){
- this.onPrompt('ok', {
- project: project,
- displayName: rec.get('displayName'),
- protocolDisplayName: rec.get('protocol/displayName'),
- protocol: rec.get('protocol'),
- title: rec.get('title'),
- shortname: rec.get('shortname'),
- investigator: rec.get('investigatorId/' + this.invesLastNameCol)
- });
-
- win.close();
- }
- else {
- Ext4.Msg.alert('Error', 'Unknown Project');
- }
- }
- },{
- text: 'Cancel',
- handler: function(btn){
- btn.up('window').close();
- }
- }],
- listeners: {
- show: function(win){
- var field = win.down('combo');
- Ext4.defer(field.focus, 100, field);
- }
- }
- }).show();
- },
-
- onBeforeComplete: function(){
- return !this.window || !this.window.isVisible();
+ extend: 'Ext.form.field.ComboBox',
+ alias: 'widget.ehr-projectentryfield',
+
+ fieldLabel: 'Project',
+ typeAhead: true,
+ forceSelection: true, //NOTE: has been re-enabled, but it is important to let the field get set prior to loading
+ emptyText:'',
+ disabled: false,
+ matchFieldWidth: false,
+ includeDefaultProjects: true,
+ invesLastNameCol: 'lastName',
+ autoSelectFirstProjectOnLoad: false,
+
+ initComponent: function(){
+ this.allProjectStore = EHR.DataEntryUtils.getProjectStore();
+
+ this.trigger2Cls = Ext4.form.field.ComboBox.prototype.triggerCls;
+ this.onTrigger2Click = Ext4.form.field.ComboBox.prototype.onTriggerClick;
+
+ Ext4.apply(this, {
+ displayField: 'displayName',
+ valueField: 'project',
+ queryMode: 'local',
+ plugins: [Ext4.create('LDK.plugin.UserEditableCombo', {
+ createWindow: function(){
+ this.window = Ext4.create('Ext.window.Window', {
+ modal: true,
+ closeAction: 'destroy',
+ bodyStyle: 'padding: 5px',
+ title: 'Choose Project',
+ items: [{
+ xtype: 'ehr-projectfield',
+ width: 400,
+ fieldLabel: 'Project',
+ itemId: 'projectField',
+ listeners: {
+ specialkey: function(field, e){
+ if (e.getKey() === e.ENTER && !field.isExpanded){
+ var btn = field.up('window').down('button[text=Submit]');
+ btn.handler.apply(btn.scope, [btn]);
+ }
}
- })],
- validationDelay: 500,
- //NOTE: unless i have this empty store an error is thrown
- store: {
- type: 'labkey-store',
- schemaName: 'study',
- sql: this.makeSql(),
- sort: 'sort_order,project',
- autoLoad: false,
- loading: true,
- listeners: {
- scope: this,
- delay: 50,
- load: function(store){
- // allow for auto-select of the project, if not already selected, for quick data entry
- if (this.autoSelectFirstProjectOnLoad && !this.getValue()) {
- var storeProjects = Ext4.Array.filter(store.collect('project'), function(proj) {
- return LABKEY.Utils.isNumber(proj);
- });
-
- if (storeProjects.length > 0) {
- this.setValue(storeProjects[0]);
- }
- }
-
- this.resolveProjectFromStore();
- this.getPicker().refresh();
- }
+ }
+ },{
+ xtype: 'ldk-linkbutton',
+ linkTarget: '_blank',
+ text: '[View All Projects]',
+ href: LABKEY.ActionURL.buildURL('query', 'executeQuery', null, {schemaName: 'ehr', 'query.queryName': 'project', 'query.viewName': 'Active Projects'}),
+ style: 'padding-top: 5px;padding-bottom: 5px;padding-left: 100px;'
+ }],
+ buttons: [{
+ scope: this,
+ text: 'Submit',
+ handler: function(btn){
+ var win = btn.up('window');
+ var field = win.down('#projectField');
+ var project = field.getValue();
+ if (!project){
+ Ext4.Msg.alert('Error', 'Must enter a project');
+ return;
}
- },
- listeners: {
- scope: this,
- beforerender: function(field){
- var target = field.up('form');
- if (!target)
- target = field.up('grid');
-
- LDK.Assert.assertNotEmpty('Unable to find form or grid', target);
- if (target) {
- field.mon(target, 'animalchange', field.getProjects, field);
- }
- else {
- console.error('Unable to find target');
- }
- //attempt to load for the bound Id
- this.getProjects();
+ var rec = field.findRecord('project', project);
+ LDK.Assert.assertTrue('Project record not found for id: ' + project, !!rec);
+
+ if (rec){
+ this.onPrompt('ok', {
+ project: project,
+ displayName: rec.get('displayName'),
+ protocolDisplayName: rec.get('protocol/displayName'),
+ protocol: rec.get('protocol'),
+ title: rec.get('title'),
+ shortname: rec.get('shortname'),
+ investigator: rec.get('investigatorId/' + this.invesLastNameCol)
+ });
+
+ win.close();
}
+ else {
+ Ext4.Msg.alert('Error', 'Unknown Project');
+ }
+ }
+ },{
+ text: 'Cancel',
+ handler: function(btn){
+ btn.up('window').close();
+ }
+ }],
+ listeners: {
+ show: function(win){
+ var field = win.down('combo');
+ Ext4.defer(field.focus, 100, field);
+ }
}
- });
-
- this.listConfig = this.listConfig || {};
- Ext4.apply(this.listConfig, {
- innerTpl: this.getInnerTpl(),
- getInnerTpl: function(){
- return this.innerTpl;
- },
- style: 'border-top-width: 1px;' //this was added in order to restore the border above the boundList if it is wider than the field
- });
-
- this.callParent(arguments);
-
- this.on('render', function(){
- this.triggerEl.set({'data-qtip': 'Click to recalculate allowable projects'});
- }, this);
- },
+ }).show();
+ },
- getInnerTpl: function(){
+ onBeforeComplete: function(){
+ return !this.window || !this.window.isVisible();
+ }
+ })],
+ validationDelay: 500,
+ //NOTE: unless i have this empty store an error is thrown
+ store: {
+ type: 'labkey-store',
+ schemaName: 'study',
+ sql: this.makeSql(),
+ sort: 'sort_order,project',
+ autoLoad: false,
+ loading: true,
+ listeners: {
+ scope: this,
+ delay: 50,
+ load: function(store){
+ if (this.autoSelectFirstProjectOnLoad && !this.getValue()) {
+ var storeProjects = Ext4.Array.filter(store.collect('project'), function(proj) {
+ return LABKEY.Utils.isNumber(proj);
+ });
+ if (storeProjects.length > 0) {
+ this.setValue(storeProjects[0]);
+ }
+ }
+ this.resolveProjectFromStore();
+ this.getPicker().refresh();
+ }
+ }
+ },
+ listeners: {
+ scope: this,
+ beforerender: function(field){
+ var target = field.up('form');
+ if (!target)
+ target = field.up('grid');
+
+ LDK.Assert.assertNotEmpty('Unable to find form or grid', target);
+ if (target) {
+ field.mon(target, 'animalchange', field.getProjects, field);
+ }
+ else {
+ console.error('Unable to find target');
+ }
+
+ //attempt to load for the bound Id
+ this.getProjects();
+ }
+ }
+ });
+
+ this.listConfig = this.listConfig || {};
+ Ext4.apply(this.listConfig, {
+ innerTpl: this.getInnerTpl(),
+ getInnerTpl: function(){
+ return this.innerTpl;
+ },
+ style: 'border-top-width: 1px;' //this was added in order to restore the border above the boundList if it is wider than the field
+ });
+
+ this.callParent(arguments);
+
+ this.addEvents('projectchange');
+ this.enableBubble('projectchange');
+
+ this.on('change', function(field, val, oldVal){
+ this.fireEvent('projectchange', val);
+ }, this, {buffer: 200});
+
+ this.on('render', function(){
+ Ext4.QuickTips.register({
+ target: this.triggerEl.elements[0],
+ text: 'Click to recalculate allowable projects'
+ });
+ }, this);
+ },
+
+ getInnerTpl: function(){
return ['{[LABKEY.Utils.encodeHtml(values["displayName"] + " " + (values["shortname"] ? ("(" + values["shortname"] + ")") : (values["investigator"] ? "(" + (values["investigator"] ? values["investigator"] : "") : "") + (values["account"] ? ": " + values["account"] : "") + (values["investigator"] ? ")" : "")))]} '];
- },
+ },
- trigger1Cls: 'x4-form-search-trigger',
+ trigger1Cls: 'x4-form-search-trigger',
- onTrigger1Click: function(){
- var boundRecord = EHR.DataEntryUtils.getBoundRecord(this);
- if (!boundRecord){
- Ext4.Msg.alert('Error', 'Unable to locate associated animal Id');
- return;
- }
-
- var id = boundRecord.get('Id');
- if (!id){
- Ext4.Msg.alert('Error', 'No animal Id provided');
- return;
- }
+ onTrigger1Click: function(){
+ var boundRecord = EHR.DataEntryUtils.getBoundRecord(this);
+ if (!boundRecord){
+ Ext4.Msg.alert('Error', 'Unable to locate associated animal Id');
+ return;
+ }
- this.getProjects(id);
- },
+ var id = boundRecord.get('Id');
+ if (!id){
+ Ext4.Msg.alert('Error', 'No Animal Id Provided');
+ return;
+ }
- getDisallowedProtocols: function(){
- return null;
- },
+ this.getProjects(id);
+ },
- makeSql: function(id, date){
- if (!id && !this.includeDefaultProjects)
- return;
+ getDisallowedProtocols: function(){
+ return null;
+ },
- //avoid unnecessary reloading
- var key = id + '||' + date;
- if (this.loadedKey == key){
- return;
- }
- this.loadedKey = key;
-
- var sql = "SELECT DISTINCT t.project, t.displayName, t.account, t.protocolDisplayName, t.protocol, t.investigator, t.title, t.shortname, false as fromClient, min(sort_order) as sort_order, max(isAssigned) as isAssigned FROM (";
-
- if (id){
- //NOTE: show any actively assigned projects, or projects under the same protocol. we also only show projects if either the animal is assigned, or that project is active
- sql += "SELECT p.project as project, p.displayName as displayName, p.account as account, p.protocol.displayName as protocolDisplayName, " +
- " p.protocol as protocol, p.title, p.shortname, p.investigatorId." + this.invesLastNameCol + " as investigator, " +
- " CASE WHEN (a.project = p.project AND p.use_category = 'Research') THEN 0 WHEN (a.project = p.project) THEN 1 ELSE 2 END as sort_order, " +
- " CASE WHEN (a.project = p.project) THEN 1 ELSE 0 END as isAssigned " +
- " FROM ehr.project p JOIN study.assignment a ON (a.project.protocol = p.protocol) " +
- " WHERE a.id='"+id+"' AND (a.project = p.project) "; //TODO: restore this OR p.enddate IS NULL OR p.enddate >= curdate()
-
- //NOTE: if the date is in the future, we assume active projects
- if (date){
- sql += "AND cast(a.date as date) <= '"+date.format('Y-m-d')+"' AND ((a.enddateCoalesced >= '"+date.format('Y-m-d')+"') OR ('"+date.format('Y-m-d')+"' >= now() and a.enddate IS NULL))";
- }
- else {
- sql += "AND a.isActive = true ";
- }
+ makeSql: function(id, date){
+ if (!id && !this.includeDefaultProjects)
+ return;
- if (this.getDisallowedProtocols()){
- sql += " AND p.protocol NOT IN ('" + this.getDisallowedProtocols().join("', '") + "') ";
- }
- }
+ //avoid unnecessary reloading
+ var key = id + '||' + date;
+ if (this.loadedKey == key){
+ return;
+ }
+ this.loadedKey = key;
+
+ var sql = "SELECT DISTINCT t.project, t.displayName, t.account, t.protocolDisplayName, t.protocol, t.title, t.shortname, false as fromClient, min(sort_order) as sort_order, max(isAssigned) as isAssigned FROM (";
+
+ if (id){
+ //NOTE: show any actively assigned projects, or projects under the same protocol. we also only show projects if either the animal is assigned, or that project is active
+ sql += "SELECT p.project as project, p.displayName as displayName, p.account as account, p.protocol.displayName as protocolDisplayName, " +
+ "p.protocol as protocol, p.title, p.shortname, p.investigatorId." + this.invesLastNameCol + " as investigator, " +
+ "CASE WHEN (a.project = p.project AND p.use_category = 'Research') THEN 0 WHEN (a.project = p.project) THEN 1 ELSE 2 END as sort_order, CASE WHEN (a.project = p.project) THEN 1 ELSE 0 END as isAssigned " +
+ " FROM ehr.project p JOIN study.assignment a ON (a.project.protocol = p.protocol) " +
+ " WHERE a.id='"+id+"' AND (a.project = p.project) "; //TODO: restore this OR p.enddate IS NULL OR p.enddate >= curdate()
+
+ //NOTE: if the date is in the future, we assume active projects
+ if (date){
+ sql += "AND cast(a.date as date) <= '"+Ext4.Date.format(date, 'Y-m-d')+"' AND ((a.enddateCoalesced >= '"+Ext4.Date.format(date, 'Y-m-d')+"') OR ('"+Ext4.Date.format(date, 'Y-m-d')+"' >= now() and a.enddate IS NULL))";
+ }
+ else {
+ sql += "AND a.isActive = true ";
+ }
+
+ if (this.getDisallowedProtocols()){
+ sql += " AND p.protocol NOT IN ('" + this.getDisallowedProtocols().join("', '") + "') ";
+ }
+ }
- if (this.includeDefaultProjects){
- if (id)
- sql += ' UNION ALL ';
+ if (this.includeDefaultProjects){
+ if (id)
+ sql += ' UNION ALL ';
- sql += " SELECT p.project, p.displayName, p.account, p.protocol.displayName as protocolDisplayName, p.protocol as protocol, " +
- " p.title, p.shortname, p.investigatorId." + this.invesLastNameCol + " as investigator, 3 as sort_order, " +
- " 0 as isAssigned FROM ehr.project p WHERE p.alwaysavailable = true"; //TODO: restore this: and p.enddateCoalesced >= curdate()
- }
+ sql += " SELECT p.project, p.displayName, p.account, p.protocol.displayName as protocolDisplayName, p.protocol as protocol, " +
+ "p.title, p.shortname, p.investigatorId." + this.invesLastNameCol + " as investigator, 3 as sort_order, 0 as isAssigned FROM ehr.project p WHERE p.alwaysavailable = true"; //TODO: restore this: and p.enddateCoalesced >= curdate()
+ }
- sql+= " ) t GROUP BY t.project, t.displayName, t.account, t.protocolDisplayName, t.protocol, t.investigator, t.title, t.shortname";
+ sql+= " ) t GROUP BY t.project, t.displayName, t.account, t.protocolDisplayName, t.protocol, t.title, t.shortname";
- return sql;
- },
+ return sql;
+ },
- getProjects : function(id){
- var boundRecord = EHR.DataEntryUtils.getBoundRecord(this);
- if (!boundRecord){
- console.warn('no bound record found');
- }
+ getProjects : function(id){
+ var boundRecord = EHR.DataEntryUtils.getBoundRecord(this);
+ if (!boundRecord){
+ console.warn('no bound record found');
+ }
- if (boundRecord && boundRecord.store){
- LDK.Assert.assertNotEmpty('ProjectEntryField is being used on a store that lacks an Id field: ' + boundRecord.store.storeId, boundRecord.fields.get('Id'));
- }
+ if (boundRecord && boundRecord.store){
+ LDK.Assert.assertNotEmpty('ProjectEntryField is being used on a store that lacks an Id field: ' + boundRecord.store.storeId, boundRecord.fields.get('Id'));
+ }
- if (!id && boundRecord)
- id = boundRecord.get('Id');
+ if (!id && boundRecord)
+ id = boundRecord.get('Id');
- var date;
- if (boundRecord){
- date = boundRecord.get('date');
- }
+ var date;
+ if (boundRecord){
+ date = boundRecord.get('date');
+ }
- this.emptyText = 'Select project...';
- var sql = this.makeSql(id, date);
- if (sql){
- this.store.loading = true;
- this.store.sql = sql;
- this.store.removeAll();
- this.store.load();
- }
- },
+ this.emptyText = 'Select project...';
+ var sql = this.makeSql(id, date);
+ if (sql){
+ this.store.loading = true;
+ this.store.sql = sql;
+ this.store.removeAll();
+ this.store.load();
+ }
+ },
- setValue: function(val){
- var rec;
- if (Ext4.isArray(val)){
- val = val[0];
- }
+ setValue: function(val){
+ var rec;
+ if (Ext4.isArray(val)){
+ val = val[0];
+ }
- if (val && Ext4.isPrimitive(val)){
- rec = this.store.findRecord('project', val);
- if (!rec){
- rec = this.store.findRecord('displayName', val, null, false, false, true);
+ if (val && Ext4.isPrimitive(val)){
+ rec = this.store.findRecord('project', val);
+ if (!rec){
+ rec = this.store.findRecord('displayName', val, null, false, false, true);
- if (rec)
- console.log('resolved project entry field by display value')
- }
+ if (rec)
+ console.log('resolved project entry field by display value')
+ }
- if (!rec){
- rec = this.resolveProject(val);
- }
- }
+ if (!rec){
+ rec = this.resolveProject(val);
+ }
+ }
- if (rec){
- val = rec;
- }
+ if (rec){
+ val = rec;
+ }
- // NOTE: if the store is loading, Combo will set this.value to be the actual model.
- // this causes problems downstream when other code tries to convert that into the raw datatype
- if (val && val.isModel){
- val = val.get(this.valueField);
- }
+ // NOTE: if the store is loading, Combo will set this.value to be the actual model.
+ // this causes problems downstream when other code tries to convert that into the raw datatype
+ if (val && val.isModel){
+ val = val.get(this.valueField);
+ }
- this.callParent([val]);
- },
+ this.callParent([val]);
+ },
- resolveProjectFromStore: function(){
- var val = this.getValue();
- if (!val || this.isDestroyed)
- return;
+ resolveProjectFromStore: function(){
+ var val = this.getValue();
+ if (!val || this.isDestroyed)
+ return;
- LDK.Assert.assertNotEmpty('Unable to find store in ProjectEntryField', this.store);
- var rec = this.store ? this.store.findRecord('project', val) : null;
- if (rec){
- return;
- }
+ LDK.Assert.assertNotEmpty('Unable to find store in ProjectEntryField', this.store);
+ var rec = this.store ? this.store.findRecord('project', val) : null;
+ if (rec){
+ return;
+ }
- rec = this.allProjectStore.findRecord('project', val);
- if (rec){
- var newRec = this.store.createModel({});
- newRec.set({
- project: rec.data.project,
- account: rec.data.account,
- displayName: rec.data.displayName,
- protocolDisplayName: rec.data['protocol/displayName'],
- protocol: rec.data.protocol,
- title: rec.data.title,
- investigator: rec.data['investigatorId/' + this.invesLastNameCol],
- isAssigned: 0,
- fromClient: true
- });
-
- this.store.insert(0, newRec);
+ rec = this.allProjectStore.findRecord('project', val);
+ if (rec){
+ var newRec = this.store.createModel({});
+ newRec.set({
+ project: rec.data.project,
+ account: rec.data.account,
+ displayName: rec.data.displayName,
+ protocolDisplayName: rec.data['protocol/displayName'],
+ protocol: rec.data.protocol,
+ title: rec.data.title,
+ investigator: rec.data['investigatorId/' + this.invesLastNameCol],
+ isAssigned: 0,
+ fromClient: true
+ });
+
+ this.store.insert(0, newRec);
return newRec;
}
},
- resolveProject: function(val){
- if (this.allProjectStore.isLoading()){
- this.allProjectStore.on('load', function(store){
- var newRec = this.resolveProjectFromStore();
- if (newRec)
- this.setValue(val);
- }, this, {single: true});
- }
- else {
- this.resolveProjectFromStore();
- }
+ resolveProject: function(val){
+ if (this.allProjectStore.isLoading()){
+ this.allProjectStore.on('load', function(store){
+ var newRec = this.resolveProjectFromStore();
+ if (newRec)
+ this.setValue(val);
+ }, this, {single: true});
+ }
+ else {
+ this.resolveProjectFromStore();
}
+ }
});
\ No newline at end of file
diff --git a/ehr/resources/web/ehr/grid/Panel.js b/ehr/resources/web/ehr/grid/Panel.js
index e6e0353db..0b43418f7 100644
--- a/ehr/resources/web/ehr/grid/Panel.js
+++ b/ehr/resources/web/ehr/grid/Panel.js
@@ -49,18 +49,7 @@ Ext4.define('EHR.grid.Panel', {
this.addEvents('animalchange', 'storevalidationcomplete');
this.enableBubble('animalchange');
- this.getSelectionModel().on('selectionchange', function(sm, models){
- if (models.length != 1)
- return;
-
- //checks the extra property for a non dataset that does not have Id column
- var byPassId = models[0].store.sectionCfg.extraProperties.BY_PASS_ANIMAL_ID;
- if (byPassId || byPassId === 'true')
- return;
-
- var id = models[0].get('Id');
- this.fireEvent('animalchange', id);
- }, this);
+ this.getSelectionModel().on('selectionchange', this.handleSectionChangeEvent, this);
// the intention of the following is to avoid redrawing the entire grid, which is expensive, when we have
// single row changes, or more importantly single row changes that only involve validation/tooltip error message differences
@@ -91,6 +80,19 @@ Ext4.define('EHR.grid.Panel', {
pendingChanges: {},
+ handleSectionChangeEvent: function(sm, models){
+ if (models.length != 1)
+ return;
+
+ //checks the extra property for a non dataset that does not have Id column
+ var byPassId = models[0].store.sectionCfg.extraProperties.BY_PASS_ANIMAL_ID;
+ if (byPassId || byPassId === 'true')
+ return;
+
+ var id = models[0].get('Id');
+ this.fireEvent('animalchange', id);
+ },
+
onStoreValidation: function(store, record){
var key = store.storeId + '||' + record.internalId;
diff --git a/ehr/resources/web/ehr/lib/classify.js b/ehr/resources/web/ehr/lib/classify.js
deleted file mode 100644
index a51fb42c8..000000000
--- a/ehr/resources/web/ehr/lib/classify.js
+++ /dev/null
@@ -1,98 +0,0 @@
-define([], function() {
- var Classify = Classify || {};
-
- Classify.newClass = function(config) {
- var newObject = function(constructorConfig) {
- var self = this;
-
- // First, call the parent constructor, if one exists.
- if (('parent' in this) && ('_innerConstructor' in this.parent)) {
- this.parent._innerConstructor.call(this, constructorConfig);
- }
-
- // Now, call the passed in constructor for this object
- if ('_innerConstructor' in this) {
- this._innerConstructor.call(this, constructorConfig);
- }
-
- // Only support computed methods if knockout has been loaded
- if (!_.isUndefined(ko)) {
- jQuery.each(self._ko_computeds, function (key, value) {
- if (typeof value === 'function') {
- var wrappedMethod = function () {
- var returnVal;
- try {
- returnVal = value.call(self, arguments);
- }
- catch (err) {
- return undefined;
- }
- return returnVal;
- };
- self[key] = ko.computed(wrappedMethod, self);
- }
- });
- }
- };
-
- // Apply the parent
- if ('parent' in config) {
- newObject.prototype = Object.create(config.parent.prototype);
- newObject.prototype.parent = Object.create(config.parent.prototype);
- }
-
- // Define the constructor
- newObject.prototype.constructor = newObject;
- if (typeof config.constructor === 'function') {
- newObject.prototype._innerConstructor = config.constructor;
- }
-
- // Handle computed methods, including merging with parent computed methods.
- var computeds = {};
- if ('computeds' in config) {
- computeds = config.computeds;
- }
- if (('parent' in newObject.prototype) && ( '_ko_computeds' in newObject.prototype.parent )) {
- computeds = _.extend(newObject.prototype.parent._ko_computeds, computeds);
- }
- newObject.prototype._ko_computeds = computeds;
-
- // Give the prototype a method to add methods and add it to the new object directly, so that it
- // can be accessed later in this function.
- newObject.addMethod = function(name, method) {
- this.prototype[name] = method;
- };
- newObject.prototype.addMethod = newObject.addMethod;
-
- // Add all of the configured methods to the object prototype.
- if ('methods' in config) {
- jQuery.each(config.methods, function(key, value) {
- newObject.addMethod(key, value);
- });
- }
-
- return newObject;
- };
-
- Classify.IInterfacify = Classify.newClass({
- constructor: function(config) {
- //TODO: Implement a requiement property for the config object, to allow
- // Interfaces to specify methods or properties that they need.
- },
- methods: {
- applyTo: function(thingToInterfacify) {
- var self = this;
-
- /* Apply all the properties of this interface to the new object */
- jQuery.each(_.keys(self), function(index, property) {
- // TODO: Add error checking/warning to see if we're clobbering anything on the object
- if ( (!property.match(/^_/)) && (property !== 'applyTo') ) {
- thingToInterfacify.prototype[property] = self[property];
- }
- });
- }
- }
- });
-
- return Classify;
-});
\ No newline at end of file
diff --git a/ehr/resources/web/ehr/lib/knockout-mapping.js b/ehr/resources/web/ehr/lib/knockout-mapping.js
deleted file mode 100644
index 4ffc7225d..000000000
--- a/ehr/resources/web/ehr/lib/knockout-mapping.js
+++ /dev/null
@@ -1,809 +0,0 @@
-/// Knockout Mapping plugin v2.4.1
-/// (c) 2013 Steven Sanderson, Roy Jacobs - http://knockoutjs.com/
-/// License: MIT (http://www.opensource.org/licenses/mit-license.php)
-(function (factory) {
- // Module systems magic dance.
-
- if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
- // CommonJS or Node: hard-coded dependency on "knockout"
- factory(require("knockout"), exports);
- } else if (typeof define === "function" && define["amd"]) {
- // AMD anonymous module with hard-coded dependency on "knockout"
- define(["knockout", "exports"], factory);
- } else {
- //