Commit dc82d799 authored by Romain Loth's avatar Romain Loth

more profile scenarios (WIP) + COLS validation becomes shared + minor tidying

parent 29d937f7
...@@ -65,12 +65,12 @@ def connect_db(config=REALCONFIG): ...@@ -65,12 +65,12 @@ def connect_db(config=REALCONFIG):
""" """
Simple connection Simple connection
TODO decide if we'll use one or multiple (<= atm yes) TODO decide if we'll use one or multiple (<= atm multiple)
""" """
return connect( return connect(
host=config['SQL_HOST'], host=config['SQL_HOST'],
port=int(config['SQL_PORT']), port=int(config['SQL_PORT']),
user="root", # TODO change db ownership to a comexreg user user="root", # POSS change db ownership to a comexreg user
passwd="very-safe-pass", passwd="very-safe-pass",
db="comex_shared" db="comex_shared"
) )
...@@ -96,8 +96,7 @@ def get_field_aggs(a_field, hapax_threshold=int(REALCONFIG['HAPAX_THRESHOLD'])): ...@@ -96,8 +96,7 @@ def get_field_aggs(a_field, hapax_threshold=int(REALCONFIG['HAPAX_THRESHOLD'])):
sql_col = FIELDS_FRONTEND_TO_SQL[a_field] sql_col = FIELDS_FRONTEND_TO_SQL[a_field]
sql_tab = sql_col.split('.')[0] sql_tab = sql_col.split('.')[0]
mlog('DEBUG', "AGG API sql_col", sql_col) mlog('INFO', "AGG API sql_col", sql_col)
mlog('DEBUG', "AGG API sql_tab", sql_tab)
db = connect_db() db = connect_db()
db_c = db.cursor(DictCursor) db_c = db.cursor(DictCursor)
...@@ -321,6 +320,8 @@ def get_full_scholar(uid): ...@@ -321,6 +320,8 @@ def get_full_scholar(uid):
lkid_id = lkid_couple[1] lkid_id = lkid_couple[1]
urow_dict['linked_ids'][lkid_type] = lkid_id urow_dict['linked_ids'][lkid_type] = lkid_id
mlog("INFO", "get_full_scholar %s: OK" % uid)
# full user info as a dict # full user info as a dict
return urow_dict return urow_dict
...@@ -394,6 +395,7 @@ def save_scholar(uid, date, safe_recs, reg_db, uactive = True): ...@@ -394,6 +395,7 @@ def save_scholar(uid, date, safe_recs, reg_db, uactive = True):
reg_db.commit() reg_db.commit()
def save_pairs_sch_kw(pairings_list, comex_db): def save_pairs_sch_kw(pairings_list, comex_db):
""" """
Simply save all pairings (uid, kwid) in the list Simply save all pairings (uid, kwid) in the list
...@@ -440,7 +442,7 @@ def get_or_create_keywords(kw_list, comex_db): ...@@ -440,7 +442,7 @@ def get_or_create_keywords(kw_list, comex_db):
db_cursor.execute('INSERT INTO keywords(kwstr) VALUES ("%s")' % kw_str) db_cursor.execute('INSERT INTO keywords(kwstr) VALUES ("%s")' % kw_str)
comex_db.commit() comex_db.commit()
mlog("DEBUG", "Added keyword '%s'" % kw_str) mlog("INFO", "Added keyword '%s'" % kw_str)
found_ids.append(db_cursor.lastrowid) found_ids.append(db_cursor.lastrowid)
......
...@@ -23,17 +23,19 @@ __version__ = "1.4" ...@@ -23,17 +23,19 @@ __version__ = "1.4"
__email__ = "romain.loth@iscpif.fr" __email__ = "romain.loth@iscpif.fr"
__status__ = "Dev" __status__ = "Dev"
from flask import Flask, render_template, request, redirect, url_for from flask import Flask, render_template, request, \
from flask_login import login_required, current_user, login_user redirect, url_for, session
from flask_login import fresh_login_required, current_user, login_user
from re import sub from re import sub
from os import path from os import path
from traceback import format_tb from traceback import format_tb
from json import dumps from json import dumps
from datetime import timedelta
if __package__ == 'services': if __package__ == 'services':
# when we're run via import # when we're run via import
print("*** comex services ***") print("*** comex services ***")
from services.user import User, login_manager, doors_login from services.user import User, login_manager, doors_login, UCACHE
from services.text import keywords from services.text import keywords
from services.tools import restparse, mlog, re_hash, REALCONFIG from services.tools import restparse, mlog, re_hash, REALCONFIG
from services.db import connect_db, get_or_create_keywords, save_pairs_sch_kw, get_or_create_affiliation, save_scholar, get_field_aggs from services.db import connect_db, get_or_create_keywords, save_pairs_sch_kw, get_or_create_affiliation, save_scholar, get_field_aggs
...@@ -41,7 +43,7 @@ if __package__ == 'services': ...@@ -41,7 +43,7 @@ if __package__ == 'services':
else: else:
# when this script is run directly # when this script is run directly
print("*** comex services (dev server mode) ***") print("*** comex services (dev server mode) ***")
from user import User, login_manager, doors_login from user import User, login_manager, doors_login, UCACHE
from text import keywords from text import keywords
from tools import restparse, mlog, re_hash, REALCONFIG from tools import restparse, mlog, re_hash, REALCONFIG
from db import connect_db, get_or_create_keywords, save_pairs_sch_kw, get_or_create_affiliation, save_scholar, get_field_aggs from db import connect_db, get_or_create_keywords, save_pairs_sch_kw, get_or_create_affiliation, save_scholar, get_field_aggs
...@@ -57,6 +59,14 @@ app = Flask("services", ...@@ -57,6 +59,14 @@ app = Flask("services",
app.config['DEBUG'] = (config['LOG_LEVEL'] == "DEBUG") app.config['DEBUG'] = (config['LOG_LEVEL'] == "DEBUG")
app.config['SECRET_KEY'] = 'TODO fill secret key for sessions for login' app.config['SECRET_KEY'] = 'TODO fill secret key for sessions for login'
# for flask_login
app.config['REMEMBER_COOKIE_DURATION'] = timedelta(seconds=15)
app.config['REMEMBER_COOKIE_NAME'] = 'supercookie'
app.config['MY_VAR'] = 'incredible it works'
login_manager.login_view = "login"
login_manager.session_protection = "strong"
login_manager.init_app(app) login_manager.init_app(app)
########### PARAMS ########### ########### PARAMS ###########
...@@ -108,11 +118,29 @@ MIN_KW = 5 ...@@ -108,11 +118,29 @@ MIN_KW = 5
# /!\ Routes are not prefixed by nginx in prod so we do it ourselves /!\ # /!\ Routes are not prefixed by nginx in prod so we do it ourselves /!\
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
@login_manager.unauthorized_handler
def unauthorized():
return render_template(
"message.html",
message = """
Please <strong><a href="%(login)s">login here</a></strong>.
<br/><br/>
The page <span class='code'>%(tgt)s</span> is only available after login.
""" % {'tgt': request.path,
'login': url_for('login', next=request.path, _external=True)}
)
# /services/ # /services/
@app.route(config['PREFIX']+'/', methods=['GET']) @app.route(config['PREFIX']+'/', methods=['GET'])
def services(): def services():
return redirect(url_for('login', _external=True)) return redirect(url_for('login', _external=True))
# /services/test
@app.route(config['PREFIX'] + '/test', methods=['GET'])
def test_stuff():
print(UCACHE)
return render_template("message.html", message="UCACHE="+sub('<|&|>','::',repr(UCACHE)))
# /services/api/aggs # /services/api/aggs
@app.route(config['PREFIX'] + config['API_ROUTE'] + '/aggs') @app.route(config['PREFIX'] + config['API_ROUTE'] + '/aggs')
def aggs_api(): def aggs_api():
...@@ -166,6 +194,7 @@ def login(): ...@@ -166,6 +194,7 @@ def login():
doors_connect = config['DOORS_HOST']+':'+config['DOORS_PORT'] doors_connect = config['DOORS_HOST']+':'+config['DOORS_PORT']
) )
elif request.method == 'POST': elif request.method == 'POST':
# TODO check captcha
# TODO sanitize # TODO sanitize
email = request.form['email'] email = request.form['email']
pwd = request.form['password'] pwd = request.form['password']
...@@ -174,19 +203,72 @@ def login(): ...@@ -174,19 +203,72 @@ def login():
uid = doors_login(email, pwd, config) uid = doors_login(email, pwd, config)
if uid: if uid:
# £TODO usage ?
login_user(User(uid))
return redirect(url_for('profile', _external=True)) login_ok = login_user(User(uid))
# login_ok = login_user(User(uid), remember=True)
# -------------
# creates REMEMBER_COOKIE_NAME
# which is itself bound to session cookie
if login_ok:
# normal user
return redirect(url_for('profile', _external=True))
# POSS "next" request.args (useful when we'll have more pages)
# ---
else:
# user exists in doors but has no comex profile yet
# => TODO
# => we add him
# => status = "fresh_profile"
# => empty profile
# return redirect(url_for('fresh_profile', _external=True))
return redirect(url_for('register', _external=True))
else:
# user doesn't exist in doors nor comex_db
# (shouldn't happen since client-side blocks submit and displays same message, but still possible if user tweaks the js)
return render_template(
"message.html",
message = """
We're sorry but you don't exist in our database yet!
<br/>
However you can easily <strong><a href="%s">register here</a></strong>.
""" % url_for('register', _external=True)
)
# /services/user/profile/ # /services/user/profile/
@app.route(config['PREFIX'] + config['USR_ROUTE'] + '/profile/', methods=['GET']) @app.route(config['PREFIX'] + config['USR_ROUTE'] + '/profile/', methods=['GET'])
@login_required @fresh_login_required
def profile(): def profile():
"""
Entrypoint for users to load/re-save personal data
@login_required uses flask_login to relay User object current_user
"""
# login provides us current_user
if current_user.empty:
mlog("INFO", "PROFILE: empty current_user %s" % current_user.uid)
else:
mlog("INFO", "PROFILE: current_user %s\n -" % current_user.uid
+ '\n -'.join([current_user.info['email'],
current_user.info['initials'],
str(current_user.info['keywords']),
current_user.info['country']]
)
)
# debug session cookies
# print("[k for k in session.keys()]",[k for k in session.keys()])
mlog("DEBUG", "PROFILE view with flag session.new = ", session.new)
return render_template( return render_template(
"profile.html", "profile.html"
logged_in = True # NB we also got user info in {{current_user.info}}
# and {{current_user.json_info}}
) )
...@@ -247,12 +329,19 @@ def register(): ...@@ -247,12 +329,19 @@ def register():
# C) create record into the primary user table # C) create record into the primary user table
# --------------------------------------------- # ---------------------------------------------
# TODO class User method !!
save_scholar(duuid, rdate, clean_records, reg_db) save_scholar(duuid, rdate, clean_records, reg_db)
# D) read/fill each keyword and save the (uid <=> kwid) pairings # D) read/fill each keyword and save the (uid <=> kwid) pairings
kwids = get_or_create_keywords(kw_array, reg_db) kwids = get_or_create_keywords(kw_array, reg_db)
# TODO class User method !!
save_pairs_sch_kw([(duuid, kwid) for kwid in kwids], reg_db) save_pairs_sch_kw([(duuid, kwid) for kwid in kwids], reg_db)
# clear cache concerning this scholar
# TODO class User method !!
if uid in UCACHE: UCACHE.pop(uid)
# E) end connection # E) end connection
reg_db.close() reg_db.close()
......
...@@ -215,4 +215,4 @@ def mlog(loglvl, *args): ...@@ -215,4 +215,4 @@ def mlog(loglvl, *args):
print("WARNING: attempt to use mlog before read_config") print("WARNING: attempt to use mlog before read_config")
mlog("DEBUG", "conf\n "+"\n ".join(["%s=%s"%(k['var'],REALCONFIG[k['var']]) for k in CONFIGMENU])) mlog("INFO", "conf\n "+"\n ".join(["%s=%s"%(k['var'],REALCONFIG[k['var']]) for k in CONFIGMENU]))
...@@ -14,14 +14,16 @@ from flask_login import LoginManager ...@@ -14,14 +14,16 @@ from flask_login import LoginManager
if __package__ == 'services': if __package__ == 'services':
from services.db import connect_db, get_full_scholar from services.db import connect_db, get_full_scholar
from services.tools import mlog
else: else:
from db import connect_db, get_full_scholar from db import connect_db, get_full_scholar
from tools import mlog
# will be exported to main for initialization with app # will be exported to main for initialization with app
login_manager = LoginManager() login_manager = LoginManager()
# user cache # scholar User objects cache
ucache = {} UCACHE = {}
@login_manager.user_loader @login_manager.user_loader
def load_user(uid): def load_user(uid):
...@@ -29,12 +31,14 @@ def load_user(uid): ...@@ -29,12 +31,14 @@ def load_user(uid):
Used by flask-login to bring back user object from uid stored in session Used by flask-login to bring back user object from uid stored in session
""" """
u = None u = None
if uid in ucache: if uid in UCACHE:
u = ucache[uid] u = UCACHE[uid]
mlog("DEBUG", "load_user: user re-loaded by cache")
else: else:
try: try:
u = User(uid) u = User(uid)
ucache[uid] = u UCACHE[uid] = u
mlog("DEBUG", "load_user: user re-loaded from DB")
except Exception as err: except Exception as err:
print("User(%s) init error:" % str(uid), err) print("User(%s) init error:" % str(uid), err)
return u return u
...@@ -42,7 +46,7 @@ def load_user(uid): ...@@ -42,7 +46,7 @@ def load_user(uid):
def doors_login(email, password, config): def doors_login(email, password, config):
""" """
Query to Doors API to login a user Remote query to Doors API to login a user
Doors responses look like this: Doors responses look like this:
{'status': 'login ok', {'status': 'login ok',
...@@ -83,10 +87,12 @@ class User(object): ...@@ -83,10 +87,12 @@ class User(object):
if user_info is None: if user_info is None:
# user exists in doors but has nothing in DB yet # user exists in doors but has nothing in DB yet
self.info = {} self.info = {}
self.json_info = "{}"
self.empty = True self.empty = True
else: else:
# normal user has a nice info dict # normal user has a nice info dict
self.info = user_info self.info = user_info
self.json_info = dumps(user_info)
self.empty = False self.empty = False
def get_id(self): def get_id(self):
...@@ -97,7 +103,7 @@ class User(object): ...@@ -97,7 +103,7 @@ class User(object):
""" """
uses scholars.status uses scholars.status
""" """
return (self.info['record_status'] == "active") return (not self.empty and self.info['record_status'] == "active")
@property @property
def is_anonymous(self): def is_anonymous(self):
......
...@@ -73,6 +73,16 @@ ...@@ -73,6 +73,16 @@
margin: 0; margin: 0;
} }
/* for code blocks or urls */
.code {
font-family: "Fira Mono", "Droid Sans Mono", monospace;
font-weight: 500;
font-size: 75% ;
background-color: #ACA;
padding: .2em;
border-radius: .2em;
}
/* the picture preview */ /* the picture preview */
#box_show_pic { #box_show_pic {
......
/** /**
* @fileoverview * @fileoverview
* Profile 1/overview and 2/completing via Doors * Profile 1/overview and 2/completing
* @todo * @todo
* - package.json * - package.json
* *
...@@ -9,7 +9,27 @@ ...@@ -9,7 +9,27 @@
* @author romain.loth@iscpif.fr * @author romain.loth@iscpif.fr
* *
* @requires comex_user_shared * @requires comex_user_shared
* @requires comex_user_shared_auth *
* NB The uinfo variable should be set to template's user.json_info value.
*/ */
// TODO
// initialize form controllers
cmxClt.uform.initialize("comex_profile_form", completionAsYouGo)
var isProfileComplete = false
var pleaseCompleteMessage = document.selectById("please_complete")
var missingColumns = []
function completionAsYouGo() {
var valid = true
var mandatoryMissingFields = []
var optionalMissingFields = []
[ valid,
mandatoryMissingFields,
optionalMissingFields
] = cmxClt.uform.testFillField(cmxClt.uform.theForm)
}
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
* Validates the comex (communityexplorer.org) registration form * Validates the comex (communityexplorer.org) registration form
* + adds a timestamp in input#last_modified_date * + adds a timestamp in input#last_modified_date
* + adds autocompletes * + adds autocompletes
* + prepares DB save into COLS * + prepares DB save into cmxClt.COLS
* *
* @todo * @todo
* - harmonize var names (eg 'cmxClt.uauth.email' vs 'initialsInput' are both input elts) * - harmonize var names (eg 'cmxClt.uauth.email' vs 'initialsInput' are both input elts)
...@@ -49,39 +49,10 @@ function testAsYouGo() { ...@@ -49,39 +49,10 @@ function testAsYouGo() {
} }
// the target columns in DB: tuple (name, mandatoryBool, maxChars (or nChars))
var COLS = [ ["doors_uid", true, 36, 'exact'],
["last_modified_date", true, 24, 'exact'],
["email", true, 255],
["country", true, 60],
["first_name", true, 30],
["middle_name", false, 30],
["last_name", true, 50],
["initials", true, 7],
["position", false, 30],
["hon_title", false, 30],
["interests_text", false, 1200],
["community_hashtags", false, 350],
["gender", false, 1, 'exact'],
["job_looking_date", false, 24, 'exact'],
["home_url", false, 120],
["pic_url", false, 120],
["pic_file", false, null],
// ==> *scholars* table
["keywords", true, 350],
// ==> *keywords* table
["org", true, 120],
["org_type", true, 50],
["team_lab", false, 120],
["org_city", false, 50]]
// ==> *affiliations* table
var regTimestamp = document.getElementById('last_modified_date') var regTimestamp = document.getElementById('last_modified_date')
// dates up to 2049/12/31
var validDate = new RegExp( /^20[0-4][0-9]\/(?:0?[1-9]|1[0-2])\/(?:0?[1-9]|[1-2][0-9]|3[0-1])$/)
var subPage1Style = document.getElementById('subpage_1').style var subPage1Style = document.getElementById('subpage_1').style
var subPage2Style = document.getElementById('subpage_2').style var subPage2Style = document.getElementById('subpage_2').style
var teamCityDivStyle = document.getElementById('team_city_div').style var teamCityDivStyle = document.getElementById('team_city_div').style
...@@ -89,28 +60,6 @@ var otherInstDivStyle = document.getElementById('other_org_div').style ...@@ -89,28 +60,6 @@ var otherInstDivStyle = document.getElementById('other_org_div').style
var jobLookingDivStyle = document.getElementById('job_looking_div').style var jobLookingDivStyle = document.getElementById('job_looking_div').style
// NB using new route in doors api/userExists
// case true => Ok("""{"status":"login exists"}""")
// case false => Ok("""{"status":"login available"}""")
function testDoorsUserExists(emailValue) {
// /!\ async
cmxClt.uauth.callDoors(
"userExists",
[emailValue],
function(doorsResp) {
var doorsUid = doorsResp[0]
var doorsMsg = doorsResp[1]
// the global status can be true iff login is available and format ok
// £TODO fix scope (use uauth)
emailStatus = (doorsMsg == "login available")
displayDoorsStatusInRegisterBox(emailStatus, emailValue)
}
)
}
function registerDoorsAndSubmit(){ function registerDoorsAndSubmit(){
cmxClt.uform.mainMessage.innerHTML = "Registering with ISCPIF Doors..." cmxClt.uform.mainMessage.innerHTML = "Registering with ISCPIF Doors..."
...@@ -176,73 +125,13 @@ function validateAndMsg() { ...@@ -176,73 +125,13 @@ function validateAndMsg() {
cmxClt.uform.submitButton.disabled = true cmxClt.uform.submitButton.disabled = true
cmxClt.uform.mainMessage.style.display = 'block' cmxClt.uform.mainMessage.style.display = 'block'
var valid = true
cmxClt.uform.mainMessage.innerHTML = "Validating the form..." cmxClt.uform.mainMessage.innerHTML = "Validating the form..."
// also reformat the jobDate from "YYYY/MM/DD" to ISO var valid = true
if (jobDate.value.length) jobDate.value = (new Date(jobDate.value)).toISOString()
// objectify the form
cmxClt.uform.wholeFormData = new FormData(cmxClt.uform.theForm);
var missingFields = [] var missingFields = []
var toolongFields = []
for (var i in COLS) {
// console.warn("checking COLS["+i+"]", COLS[i])
var fieldName = COLS[i][0]
var mandatory = COLS[i][1]
var nChars = COLS[i][2]
var isExactN = (COLS[i][3] == 'exact')
// skip picture already done
if (fieldName == 'pic_file') continue ;
// skip doors_uid done afterwards if form ok
if (fieldName == 'doors_uid') continue ;
var actualValue = cmxClt.uform.wholeFormData.get(fieldName)
// console.log("actualValue", actualValue)
// test mandatory -----------------
if (mandatory && (actualValue == null || actualValue == "")) {
// todo human-readable fieldName here
missingFields.push(fieldName)
valid = false
console.log("missingField", fieldName)
}
// test length --------------------
else if (mandatory || (actualValue != null && actualValue != "")) {
if (isExactN) {
// should never happen => trigger error
if (actualValue.length != nChars) {
console.error("invalid value for field " + fieldName
+ "("+actualValue+")"
+ "should have exactly "+nChars+" chars")
valid = false
console.log("wrong value")
}
}
else {
if (actualValue.length > nChars) {
toolongFields.push([fieldName, nChars])
valid = false
console.log("tooShort")
}
}
}
// --------------------------------
} // end for val in COLS
[valid, missingFields] = cmxClt.uform.testFillField(cmxClt.uform.theForm)
// +++++++++++++
// RESULTS // RESULTS
if (valid) { if (valid) {
// adds the captchaCheck inside the form // adds the captchaCheck inside the form
...@@ -256,19 +145,16 @@ function validateAndMsg() { ...@@ -256,19 +145,16 @@ function validateAndMsg() {
else { else {
console.warn("form is not valid") console.warn("form is not valid")
// TODO highlight invalid fields
cmxClt.uform.submitButton.disabled = false cmxClt.uform.submitButton.disabled = false
var errorMessage = '' var errorMessage = ''
// TODO highlight invalid fields
if (missingFields.length) { if (missingFields.length) {
errorMessage += "Please fill the missing fields: " + cmxClt.ulListFromLabelsArray(missingFields, ["red"]) errorMessage += "Please fill the missing fields: " + cmxClt.ulListFromLabelsArray(missingFields, ["red"])
} }
// length is handled by each input's maxlength // length is handled by each input's maxlength
// re change the jobDate from ISO to YYYY/MM/DD
if (jobDate.value.length) jobDate.value = jobDate.value.slice(0,10).replace(/-/g,"/")
// display (TODO setTimeout and fade) // display (TODO setTimeout and fade)
cmxClt.uform.mainMessage.innerHTML = errorMessage cmxClt.uform.mainMessage.innerHTML = errorMessage
return false return false
...@@ -403,7 +289,7 @@ jobDate.onkeyup = checkJobDateStatus ...@@ -403,7 +289,7 @@ jobDate.onkeyup = checkJobDateStatus
jobDate.onchange = checkJobDateStatus jobDate.onchange = checkJobDateStatus
function checkJobDateStatus() { function checkJobDateStatus() {
jobLookingDateStatus = (jobBool.value == "No" || validDate.test(jobDate.value)) jobLookingDateStatus = (jobBool.value == "No" || cmxClt.uform.validDate.test(jobDate.value))
if (!jobLookingDateStatus) { if (!jobLookingDateStatus) {
jobDateMsg.style.color = cmxClt.colorRed jobDateMsg.style.color = cmxClt.colorRed
jobDateMsg.innerHTML = 'Date is not yet in the valid format YYYY/MM/DD' jobDateMsg.innerHTML = 'Date is not yet in the valid format YYYY/MM/DD'
......
...@@ -13,7 +13,6 @@ ...@@ -13,7 +13,6 @@
* *
*/ */
// initialize and export cmxClt module // initialize and export cmxClt module
var cmxClt = (function() { var cmxClt = (function() {
...@@ -25,6 +24,41 @@ var cmxClt = (function() { ...@@ -25,6 +24,41 @@ var cmxClt = (function() {
ccModule.colorGreen = '#161' ccModule.colorGreen = '#161'
ccModule.colorGrey = '#554' ccModule.colorGrey = '#554'
// the target columns in DB: tuple (name, mandatoryBool, type)
ccModule.COLS = [ ["doors_uid", true, "auto" ],
["last_modified_date", true, "auto" ],
["email", true, "plsfill"],
["country", true, "plsfill"],
["first_name", true, "plsfill"],
["middle_name", false, "plsfill"],
["last_name", true, "plsfill"],
["initials", true, "plsfill"],
["position", false, "plsfill"],
["hon_title", false, "plsfill"],
["interests_text", false, "plsfill"],
["community_hashtags", false, "plsfill"],
["gender", false, "plsfill"],
["job_looking_date", false, "pref" ],
["home_url", false, "plsfill"],
["pic_url", false, "pref" ],
["pic_file", false, "pref" ],
// ==> *scholars* table
["keywords", true, "plsfill"],
// ==> *keywords* table
["org", true, "plsfill"],
["org_type", true, "plsfill"],
["team_lab", false, "pref" ],
["org_city", false, "pref" ]]
// ==> *affiliations* table
// "type" is a complementary information to mandatory
// --------------------------------------------------
// type "auto" === filled by controllers
// type "plsfill" === filled by user, ideally needed for a complete profile
// type "pref" === filled by user but not needed at all
ccModule.makeRandomString = function (nChars) { ccModule.makeRandomString = function (nChars) {
var rando = "" var rando = ""
...@@ -61,7 +95,6 @@ var cmxClt = (function() { ...@@ -61,7 +95,6 @@ var cmxClt = (function() {
ccModule.uform = {} ccModule.uform = {}
ccModule.uform.theFormId = null ccModule.uform.theFormId = null
ccModule.uform.theForm = null ccModule.uform.theForm = null
ccModule.uform.wholeFormData = null
// vars that will be used during the interaction // vars that will be used during the interaction
ccModule.uform.submitButton = document.getElementById('formsubmit') ccModule.uform.submitButton = document.getElementById('formsubmit')
...@@ -77,9 +110,70 @@ var cmxClt = (function() { ...@@ -77,9 +110,70 @@ var cmxClt = (function() {
} }
// dates up to 2049/12/31
ccModule.uform.validDate = new RegExp( /^20[0-4][0-9]\/(?:0?[1-9]|1[0-2])\/(?:0?[1-9]|[1-2][0-9]|3[0-1])$/)
// checks if mandatory fields are filled
// checks if other plsfill ones are filled
ccModule.uform.testFillField = function (aForm) {
// "private" copy
var wholeFormData = new FormData(aForm)
// our return values
var valid = true
var mandatoryMissingFields = []
var otherMissingFields = []
// var toolongFields = []
// let's go
for (var i in ccModule.COLS) {
// console.warn("checking ccModule.COLS["+i+"]", ccModule.COLS[i])
var fieldName = ccModule.COLS[i][0]
var mandatory = ccModule.COLS[i][1]
var fieldType = ccModule.COLS[i][2]
// skip non-plsfill elements
if (fieldName != 'plsfill') continue ;
var actualValue = wholeFormData.get(fieldName)
// alternative null values
if (actualValue == "" || actualValue == "None") {
actualValue = null
}
// debug
// console.log(
// "cmxClt.testEachField: field", fieldName,
// "actualValue:", actualValue
// )
// test mandatory -----------------
if (mandatory && actualValue == null) {
// todo human-readable fieldName here
mandatoryMissingFields.push(fieldName)
valid = false
console.log("mandatoryMissingFields", fieldName)
}
// test benign --------------------
// may be missing but doesn't affect valid
else if (actualValue == null) {
otherMissingFields.push(fieldName)
console.log("otherMissingField", fieldName)
}
// --------------------------------
} // end for val in ccModule.COLS
// return full form diagnostic and field census
return [ valid,
mandatoryMissingFields,
otherMissingFields ]
}
return ccModule return ccModule
}()) ; }()) ;
console.log("shared load OK") console.log("shared load OK")
This diff is collapsed.
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