Commit 3077c398 authored by Ryan Herbert's avatar Ryan Herbert

add multiple patients in one form.

makes the refactored patient form extendable. With the serializeObject
method, form names can be used like array/object accessors and enables
complexe json objects to be built from the form and sent to the
controller.

See #2878
parent c30fd2c2
......@@ -72,6 +72,7 @@ function loadAfterConf() {
"../tokeniser",
// Speed test
"../speed_test",
"../form_builder",
"../../test/QUnit/testFiles/data_test",
], function(){
if (typeof main == "undefined"){
......
......@@ -457,6 +457,35 @@ Database.prototype = {
}
});
}
if ( document.getElementById('object_form') ) {
$('#object_form').on('submit', function(e) {
e.preventDefault();
$.ajax({
type: "POST",
cache: false,
timeout: DB_TIMEOUT_CALL,
crossDomain: true,
url : $('#object_form').attr('action'),
data : {'data': JSON.stringify($('#object_form').serializeObject())},
xhrFields: {withCredentials: true},
success: function (result) {
self.display_result(result, $(this).attr('action'))
self.connected = true;
},
error: function (request, status, error) {
self.connected = false;
if (status === "timeout") {
console.log({"type": "flash", "default" : "database_timeout", "priority": 2});
} else {
console.log({"type": "popup", "msg": request.responseText})
}
}
});
return false;
});
}
//login_form
if ( document.getElementById('login_form') ){
......
function FormBuilder() {
if (typeof FormBuilder.instance === 'object') {
return FormBuilder.instance;
}
FormBuilder.instance = this
this.indexes = {
'patient': 0,
'run': 0,
'generic': 0
};
}
FormBuilder.prototype = {
patient: function(index) {
this.indexes['patient'] = index;
var fieldset = this.fieldset('patient');
fieldset.appendChild(this.build_input('id', 'text', 'id', 'hidden', 'patient'));
fieldset.appendChild(this.patient_id());
fieldset.appendChild(this.first_name());
fieldset.appendChild(this.last_name());
fieldset.appendChild(this.birth());
fieldset.appendChild(this.info());
return fieldset;
},
build_label: function(txt, stype, tgt) {
var l = document.createElement('label');
l.htmlFor = tgt + "_" + this.indexes[stype];
l.id = stype + "_" + tgt + "__label_" + this.indexes[stype];
l.innerText = txt + ":";
return l;
},
build_input: function(id, className, name, input_type, set_type, placeholder) {
var i = document.createElement('input');
i.id = set_type + "_" + id + "_" + this.indexes[set_type];
i.className = className;
i.type = input_type;
i.name = set_type + "[" + this.indexes[set_type] + "][" + name + "]";
if (typeof placeholder !== "undefined") {
i.placeholder = placeholder;
}
return i;
},
build_textarea: function(id, className, name, set_type) {
var t = document.createElement('textarea');
t.id = set_type + "_" + id + "_" + this.indexes[set_type];
t.className = className;
t.name = set_type + "[" + this.indexes[set_type] + "][" + name + "]";
return t;
},
fieldset: function(type) {
var f = document.createElement('fieldset');
f.name = type + this.indexes[type];
var l = document.createElement('legend');
l.innerText = type.charAt(0).toUpperCase() + type.slice(1) + " " + (this.indexes['patient']+1);
f.appendChild(l);
return f;
},
patient_id: function() {
var d = document.createElement('div');
var id = 'id_label';
d.appendChild(this.build_label('Patient ID', 'patient', id));
d.appendChild(this.build_input(id, 'date', 'id_label', 'text', 'patient'));
return d;
},
first_name: function() {
var d = document.createElement('div');
var id = 'first_name';
d.appendChild(this.build_label('First Name', 'patient', id));
d.appendChild(this.build_input(id, 'string', id, 'text', 'patient'));
return d;
},
last_name: function() {
var d = document.createElement('div');
var id = 'last_name';
d.appendChild(this.build_label('Last Name', 'patient', id));
d.appendChild(this.build_input(id, 'string', id, 'text', 'patient'));
return d;
},
birth: function() {
var d = document.createElement('div');
var id = 'birth';
d.appendChild(this.build_label('Birth', 'patient', id));
d.appendChild(this.build_input(id, 'date', id, 'text', 'patient', 'yyyy-mm-dd'));
return d;
},
info: function() {
var d = document.createElement('div');
var id = 'info';
d.appendChild(this.build_label('Info', 'patient', id));
var txt = this.build_textarea('info', "text", 'info', 'patient');
$(txt).data('needs-atwho', true);
$(txt).on('focus', function() {
$(this).data('keys', [$('#group_select option:selected').val()]);
new VidjilAutoComplete().setupTags(this);
});
txt.cols = 40;
txt.rows = 10;
d.appendChild(txt);
return d;
}
}
......@@ -130,3 +130,69 @@ db.ajax_indicator_stop();
db.loadNotifications();
setTimeout(worker, NOTIFICATION_PERIOD);
})();
// This function was taken from https://gist.github.com/Rad1calDreamer/46407ccdef492511e80e
$.fn.serializeObject = function(){
var self = this,
json = {},
push_counters = {},
patterns = {
"validate": /^[a-zA-Z][a-zA-Z0-9_]*(?:\[(?:\d*|[a-zA-Z0-9_]+)\])*$/,
"key": /[a-zA-Z0-9_]+|(?=\[\])/g,
"push": /^$/,
"fixed": /^\d+$/,
"named": /^[a-zA-Z0-9_]+$/
};
this.build = function(base, key, value){
base[key] = value;
return base;
};
this.push_counter = function(key){
if(push_counters[key] === undefined){
push_counters[key] = 0;
}
return push_counters[key]++;
};
$.each($(this).serializeArray(), function(){
// skip invalid keys
if(!patterns.validate.test(this.name)){
return;
}
var k,
keys = this.name.match(patterns.key),
merge = this.value,
reverse_key = this.name;
while((k = keys.pop()) !== undefined){
// adjust reverse_key
reverse_key = reverse_key.replace(new RegExp("\\[" + k + "\\]$"), '');
// push
if(k.match(patterns.push)){
merge = self.build([], self.push_counter(reverse_key), merge);
}
// fixed
else if(k.match(patterns.fixed)){
merge = self.build([], k, merge);
}
// named
else if(k.match(patterns.named)){
merge = self.build({}, k, merge);
}
}
json = $.extend(true, json, merge);
});
return json;
};
......@@ -3,6 +3,7 @@ import gluon.contrib.simplejson, datetime
import vidjil_utils
import time
import datetime
import json
if request.env.http_origin:
response.headers['Access-Control-Allow-Origin'] = request.env.http_origin
......@@ -32,7 +33,7 @@ def form():
return dict(message=T(message),
groups=groups,
master_group=max_group,
patient=db.patient[request.vars["id"]])
patients=[db.patient[request.vars["id"]]])
......@@ -41,86 +42,78 @@ def form():
## redirect to patient list if success
## return a flash error message if fail
def submit():
error = ""
if request.vars["first_name"] == "" :
error += "first name needed, "
if request.vars["last_name"] == "" :
error += "last name needed, "
if request.vars["birth"] != "" :
try:
datetime.datetime.strptime(""+request.vars['birth'], '%Y-%m-%d')
except ValueError:
error += "date (wrong format)"
if error != "":
res = {"success" : "false",
"message" : error}
log.error(res)
return gluon.contrib.simplejson.dumps(res, separators=(',',':'))
data = json.loads(request.vars['data'], encoding='utf-8')
mf = ModelFactory()
helper = mf.get_instance('patient')
messages = []
p = dict(first_name=request.vars["first_name"],
last_name=request.vars["last_name"],
birth=request.vars["birth"],
info=request.vars["info"],
id_label=request.vars["id_label"]
)
error = False
for p in data['patient']:
p['error'] = helper.validate(p)
if len(p['error']) > 0:
error = True
continue
register = False
reset = False
register = False
reset = False
# edit patient
log.debug("id present: " + "id" in request.vars)
log.debug("id: " + request.vars["id"] + request.vars['id'] == "" + request.vars['id'] is None)
if (request.vars['id'] != "" and auth.can_modify_patient(request.vars["id"])):
patient = db.patient[request.vars["id"]]
db.patient[request.vars["id"]] = p
patient_name = "%s %s" % (p['first_name'], p['last_name'])
if (patient.info != request.vars["info"]):
group_id = get_set_group(defs.SET_TYPE_PATIENT, request.vars["id"])
register = True
reset = True
# edit patient
if (p['id'] != "" and auth.can_modify_patient(p['id'])):
id = p["id"]
patient = db.patient[id]
db.patient[id] = p
res = {"redirect": "back",
"message": "%s %s (%s): patient edited" % (request.vars["first_name"], request.vars["last_name"], request.vars["id"])}
id = request.vars["id"]
if (patient.info != p['info']):
group_id = get_set_group(defs.SET_TYPE_PATIENT, id)
register = True
reset = True
# add patient
elif (auth.can_create_patient()):
messages.append("%s (%s): patient edited" % (patient_name, id))
id_sample_set = db.sample_set.insert(sample_type=defs.SET_TYPE_PATIENT)
# add patient
elif (auth.can_create_patient()):
p['creator'] = auth.user_id
p['sample_set_id'] = id_sample_set
id = db.patient.insert(**p)
id_sample_set = db.sample_set.insert(sample_type=defs.SET_TYPE_PATIENT)
user_group = int(request.vars["patient_group"])
admin_group = db(db.auth_group.role=='admin').select().first().id
p['creator'] = auth.user_id
p['sample_set_id'] = id_sample_set
id = db.patient.insert(**p)
group_id = user_group
register = True
group_id = int(data["group"])
#patient creator automaticaly has all rights
auth.add_permission(user_group, PermissionEnum.access.value, db.patient, id)
register = True
patient_name = request.vars["first_name"] + ' ' + request.vars["last_name"]
#patient creator automaticaly has all rights
auth.add_permission(group_id, PermissionEnum.access.value, db.patient, id)
res = {"redirect": "sample_set/index",
"args" : { "id" : id_sample_set },
"message": "(%s) patient %s added" % (id_sample_set, patient_name) }
messages.append("(%s) patient %s added" % (id_sample_set, patient_name))
if (id % 100) == 0:
mail.send(to=defs.ADMIN_EMAILS,
subject="[Vidjil] %d" % id,
message="The %dth patient has just been created." % id)
if (id % 100) == 0:
mail.send(to=defs.ADMIN_EMAILS,
subject="[Vidjil] %d" % id,
message="The %dth patient has just been created." % id)
else :
res = {"message": ACCESS_DENIED}
log.error(res)
else :
p['error'].append("permission denied")
error = True
if register:
register_tags(db, defs.SET_TYPE_PATIENT, p["id"], p["info"], group_id, reset=reset)
if register:
register_tags(db, defs.SET_TYPE_PATIENT, request.vars["id"], request.vars["info"], group_id, reset=reset)
log.info(res, extra={'user_id': auth.user.id, 'record_id': id, 'table_name': 'patient'})
return gluon.contrib.simplejson.dumps(res, separators=(',',':'))
if not error:
# TODO proper logging of all events
#log.info(res, extra={'user_id': auth.user.id, 'record_id': id, 'table_name': 'patient'})
res = {"redirect": "sample_set/all",
"message": "successfully added/edited patient(s)"}
return gluon.contrib.simplejson.dumps(res, separators=(',',':'))
else:
response.view = 'patient/form.html'
return dict(message=T("an error occured"),
groups=[{'name': 'foobar', 'id': int(data['group'])}],
master_group=data['group'],
patients=data['patient'])
def download():
return response.download(request, db)
......
......@@ -47,3 +47,16 @@ class Patient(SampleSet):
name = self.get_name(data)
ident = "(%d)" % data.id
return ":p %s %s" % (name, ident)
def validate(self, data):
error = []
if data["first_name"] == "" :
error.append("first name needed")
if data["last_name"] == "" :
error.append("last name needed")
if data["birth"] != "" :
try:
datetime.datetime.strptime(""+data['birth'], '%Y-%m-%d')
except ValueError:
error.append("date (wrong format)")
return error
......@@ -48,7 +48,7 @@ class TagExtractor(TagManager):
db.commit()
def parse_text(self, text):
return list(set(re.findall(self.expression(), unicode(text, 'utf-8'), re.UNICODE)))
return list(set(re.findall(self.expression(), text, re.UNICODE)))
def execute(self, table, record_id, text, group_id, reset=False):
if (reset):
......
{{extend 'db_layout.html'}}
<form id="data_form" action="DB_ADDRESS/patient/submit" enctype="multipart/form-data" method="post">
<input type="hidden" id="id" name="id" value="{{if patient is not None:}}{{=patient.id}}{{pass}}">
<form id="object_form" action="DB_ADDRESS/patient/submit" enctype="multipart/form-data" method="post">
<div {{ if len(groups) <= 1: }} class="hiddenCheckBox" {{pass}}>
<label for="group_select" id="patient_group__label">Owner Group: </label>
{{= SELECT(*[OPTION(g['name'], _value=g['id']) for g in groups], _id="group_select", _name="patient_group", _value=master_group, value=master_group) }}
{{= SELECT(*[OPTION(g['name'], _value=g['id']) for g in groups], _id="group_select", _name="group", _value=master_group, value=master_group) }}
</div>
<div id="fieldset_container">
<fieldset name="patient0">
<legend>Patient 1</legend>
<div>
<label for="patient_id_label_0" id="patient_id_label__label_0">Patient ID: </label>
<input class="date" id="patient_id_label_0" name="id_label" type="text"
value="{{if patient is not None and patient.id_label is not None:}}{{=patient.id_label}}{{pass}}"><span></span>
</div>
<div>
<label for="patient_first_name_0" id="patient_first_name__label_0">First Name: </label>
<input class="string" id="patient_first_name_0" name="first_name" type="text"
value="{{if patient is not None:}}{{=patient.first_name}}{{pass}}"><span>*</span>
</div>
<div>
<label for="patient_last_name_0" id="patient_last_name__label_0">Last Name: </label>
<input class="string" id="patient_last_name_0" name="last_name" type="text"
value="{{if patient is not None:}}{{=patient.last_name}}{{pass}}"><span>*</span>
</div>
<div>
<label for="patient_birth_0" id="patient_birth__label_0">Birth: </label>
<input class="date" id="patient_birth_0" name="birth" type="text"
value="{{if patient is not None and patient.birth is not None:}}{{=patient.birth}}{{pass}}" placeholder="yyyy-mm-dd">
</div>
<div>
<label for="patient_info_0" id="patient_info__label_0">Info: </label>
<textarea
onfocus="$(this).data('keys', [$('#group_select option:selected').val()]);
new VidjilAutoComplete().setupTags(this);"
data-needs-atwho="true"
class="text"
cols="40"
id="patient_info_0"
name="info"
rows="10">{{if patient is not None and patient.info is not None:}}{{=patient.info}}{{pass}}</textarea>
</div>
</fieldset>
{{ for i, p in enumerate(patients): }}
<fieldset name="patient{{=i}}">
<legend>Patient {{=i+1}}</legend>
<input type="hidden" id="patient_id_{{=i}}" name="patient[{{=i}}][id]" value="{{if p is not None and 'id' in p:}}{{=p['id']}}{{pass}}">
{{ if p is not None and 'error' in p: }}
<div class="error">error: {{=", ".join(p['error'])}}</div>
{{ pass }}
<div>
<label for="patient_id_label_{{=i}}" id="patient_id_label__label_{{=i}}">Patient ID: </label>
<input class="date" id="patient_id_label_{{=i}}" name="patient[{{=i}}][id_label]" type="text"
value="{{if p is not None and p['id_label'] is not None:}}{{=p['id_label']}}{{pass}}"><span></span>
</div>
<div>
<label for="patient_first_name_{{=i}}" id="patient_first_name__label_{{=i}}">First Name: </label>
<input class="string" id="patient_first_name_{{=i}}" name="patient[{{=i}}][first_name]" type="text"
value="{{if p is not None:}}{{=p['first_name']}}{{pass}}"><span>*</span>
</div>
<div>
<label for="patient_last_name_{{=i}}" id="patient_last_name__label_{{=i}}">Last Name: </label>
<input class="string" id="patient_last_name_{{=i}}" name="patient[{{=i}}][last_name]" type="text"
value="{{if p is not None:}}{{=p['last_name']}}{{pass}}"><span>*</span>
</div>
<div>
<label for="patient_birth_{{=i}}" id="patient_birth__label_{{=i}}">Birth: </label>
<input class="date" id="patient_birth_{{=i}}" name="patient[{{=i}}][birth]" type="text"
value="{{if p is not None and p['birth'] is not None:}}{{=p['birth']}}{{pass}}" placeholder="yyyy-mm-dd">
</div>
<div>
<label for="patient_info_{{=i}}" id="patient_info__label_{{=i}}">Info: </label>
<textarea
onfocus="$(this).data('keys', [$('#group_select option:selected').val()]);
new VidjilAutoComplete().setupTags(this);"
data-needs-atwho="true"
class="text"
cols="40"
id="patient_info_{{=i}}"
name="patient[{{=i}}][info]"
rows="10">{{if p is not None and p['info'] is not None:}}{{=p['info']}}{{pass}}</textarea>
</div>
</fieldset>
{{ pass }}
</div>
<a class="btn" onclick="document.getElementById('fieldset_container').appendChild(new FormBuilder().patient(this.dataset.index++));" data-index="{{=len(patients)}}">Add more</a>
<input type="submit" value="save" class="btn"></td>
</form>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment