ad1ac33c20a213b386adbc58e69bad6b75fb08c0 larrym Thu Apr 26 11:42:05 2012 -0700 cleanup per review items from tim diff --git src/hg/lib/jsHelper.c src/hg/lib/jsHelper.c index a478a9c..14e87af 100644 --- src/hg/lib/jsHelper.c +++ src/hg/lib/jsHelper.c @@ -1,945 +1,949 @@ /* javascript - some little helper routines to manage our javascript. * We don't do much javascript - just occassionally use it so that * when they select something from a pull-down, it will go hit the server to * figure out how to reload other control options based on the choice. * (For instance if they change the group, which items in the track * drop-down need to change). * * We accomplish this by maintaining two forms - a mainForm and a * hiddenForm. The hiddenForm maintains echo's of all the variables * in the main form, which get updated onChange of controls that need * to 'ripple' to other controls. The onChange also submits the * control. */ #include "common.h" #include #include "dystring.h" #include "cheapcgi.h" #include "cart.h" #include "hPrint.h" #include "hash.h" #include "jsHelper.h" #include "web.h" #include "hui.h" #include "hgConfig.h" #include "portable.h" static boolean jsInited = FALSE; struct jsonHashElement *jsonGlobalsHash = NULL; void jsInit() /* If this is the first call, set window.onload to the operations * performed upon loading a page and print supporting javascript. * Currently this just sets the page vertical position if specified on * CGI, and also calls jsWriteFunctions. * Subsequent calls do nothing, so this can be called many times. */ { if (! jsInited) { puts( "\n" "\n"); int pos = cgiOptionalInt("jsh_pageVertPos", 0); if (pos > 0) printf("\n\n", pos); jsWriteFunctions(); jsInited = TRUE; } } void jsWriteFunctions() /* Write out Javascript functions. */ { hPrintf("\n\n"); } struct dyString *jsOnChangeStart() /* Start up an onChange string */ { struct dyString *dy = dyStringNew(1024); dyStringAppend(dy, "onChange=\""); return dy; } char *jsOnChangeEnd(struct dyString **pDy) /* Finish up javascript onChange command. */ { dyStringAppend(*pDy, "document.hiddenForm.submit();\""); return dyStringCannibalize(pDy); } void jsDropDownCarryOver(struct dyString *dy, char *var) /* Add statement to carry-over drop-down item to dy. */ { dyStringPrintf(dy, "document.hiddenForm.%s.value=", var); dyStringPrintf(dy, "document.mainForm.%s.options", var); dyStringPrintf(dy, "[document.mainForm.%s.selectedIndex].value; ", var); } void jsTextCarryOver(struct dyString *dy, char *var) /* Add statement to carry-over text item to dy. */ { dyStringPrintf(dy, "document.hiddenForm.%s.value=document.mainForm.%s.value; ", var, var); } void jsTrackingVar(char *jsVar, char *val) /* Emit a little Javascript to keep track of a variable. * This helps especially with radio buttons. */ { hPrintf("\n"); } void jsMakeTrackingRadioButton(char *cgiVar, char *jsVar, char *val, char *selVal) /* Make a radio button that also sets tracking variable * in javascript. */ { hPrintf(""); } void jsMakeTrackingCheckBox(struct cart *cart, char *cgiVar, char *jsVar, boolean usualVal) /* Make a check box filling in with existing value and * putting a javascript tracking variable on it. */ { char buf[256]; boolean oldVal = cartUsualBoolean(cart, cgiVar, usualVal); hPrintf("\n", jsVar, oldVal); hPrintf(""); safef(buf, sizeof(buf), "%s%s", cgiBooleanShadowPrefix(), cgiVar); cgiMakeHiddenVar(buf, "0"); } void jsTrackedVarCarryOver(struct dyString *dy, char *cgiVar, char *jsVar) /* Carry over tracked variable (radio button?) to hidden form. */ { dyStringPrintf(dy, "document.hiddenForm.%s.value=%s; ", cgiVar, jsVar); } char *jsRadioUpdate(char *cgiVar, char *jsVar, char *val) /* Make a little javascript to check and uncheck radio buttons * according to new value. To use this you must have called * jsWriteFunctions somewhere, and also must use jsMakeTrackingRadioButton * to make the buttons. */ { static char buf[256]; safef(buf, sizeof(buf), "setRadioCheck('%s', '%s'); %s='%s'", cgiVar, val, jsVar, val); return buf; } void jsCreateHiddenForm(struct cart *cart, char *scriptName, char **vars, int varCount) /* Create a hidden form with the given variables */ { int i; hPrintf( "
\n", scriptName); cartSaveSession(cart); for (i=0; i\n", vars[i]); puts(""); } char *jsSetVerticalPosition(char *form) /* Returns a javascript statement for storing the vertical position of the * page; typically this would go just before a document submit. * jsInit must be called first. * Do not free return value! */ { if (! jsInited) errAbort("jsSetVerticalPosition: jsInit must be called first."); static char vertPosSet[2048]; safef(vertPosSet, sizeof(vertPosSet), "document.%s.jsh_pageVertPos.value = f_scrollTop(); ", form); return vertPosSet; } void jsMakeSetClearButton(struct cart *cart, char *form, char *buttonVar, char *buttonLabel, char *cartVarPrefix, struct slName *cartVarSuffixList, char *anchor, boolean currentPos, boolean isSet) /* Make a button for setting or clearing all of a list of boolean * cart variables (i.e. checkboxes). If this button was just pressed, * set or clear those cart variables. * Optional html anchor is appended to the form's action if given. * If currentPos, anchor is ignored and jsSetVerticalPosition is used so * that the new page gets the same vertical offset as the current page. */ { struct slName *suffix; char javascript[2048]; char *vertPosJs = ""; if (currentPos) { anchor = NULL; jsInit(); vertPosJs = jsSetVerticalPosition(form); } cgiMakeHiddenVar(buttonVar, ""); safef(javascript, sizeof javascript, "document.%s.action = '%s%s%s'; document.%s.%s.value='%s'; %s" "document.%s.submit();", form, cgiScriptName(), (isNotEmpty(anchor) ? "#" : ""), (isNotEmpty(anchor) ? anchor : ""), form, buttonVar, buttonLabel, vertPosJs, form); cgiMakeOnClickButton(javascript, buttonLabel); if (isNotEmpty(cgiOptionalString(buttonVar))) { char option[1024]; if (cartVarPrefix == NULL) cartVarPrefix = ""; for (suffix = cartVarSuffixList; suffix != NULL; suffix = suffix->next) { safef(option, sizeof(option), "%s%s", cartVarPrefix, suffix->name); cartSetBoolean(cart, option, isSet); } } } void jsMakeCheckboxGroupSetClearButton(char *buttonVar, boolean isSet) /* Make a button for setting or clearing a set of checkboxes with the same name. * Uses only javascript to change the checkboxes, no resubmit. */ { char javascript[256]; safef(javascript, sizeof(javascript), "var list = document.getElementsByName('%s'); " "for (var ix = 0; ix < list.length; ix++) {list[ix].checked = %s}", buttonVar, isSet ? "true" : "false"); cgiMakeOnClickButton(javascript, isSet ? JS_SET_ALL_BUTTON_LABEL : JS_CLEAR_ALL_BUTTON_LABEL); } char *jsPressOnEnter(char *button) /* Returns a javascript statement that clicks button when the Enter key * has been pressed; typically this would go in a text input. * jsInit must be called first. * Do not free return value! */ { if (! jsInited) errAbort("jsPressOnEnter: jsInit must be called first."); static char poe[2048]; safef(poe, sizeof(poe), "return pressOnEnter(event, %s);", button); return poe; } void jsIncludeFile(char *fileName, char *noScriptMsg) { /* Prints out html to include given javascript file from the js directory; suppresses redundant * \n"); } } void jsonErrPrintf(struct dyString *ds, char *format, ...) // Printf a json error to a dyString for communicating with ajax code; format is: // {"error": error message here} { va_list args; va_start(args, format); dyStringPrintf(ds, "{\"error\": \""); struct dyString *buf = newDyString(1000); dyStringVaPrintf(buf, format, args); dyStringAppend(ds, javaScriptLiteralEncode(dyStringCannibalize(&buf))); dyStringPrintf(ds, "\"}"); va_end(args); } static void skipLeadingSpacesWithPos(char *s, int *posPtr) /* skip leading white space. */ { for (;;) { char c = s[*posPtr]; if (!isspace(c)) return; (*posPtr)++; } } static void getSpecificChar(char c, char *str, int *posPtr) { // get specified char from string or errAbort if(str[*posPtr] != c) errAbort("Unexpected character '%c' (expected '%c') - string position %d\n", str[*posPtr], c, *posPtr); (*posPtr)++; } static char *getString(char *str, int *posPtr) { // read a double-quote delimited string; we handle backslash escaping. // returns allocated string. boolean escapeMode = FALSE; int i; struct dyString *ds = dyStringNew(1024); getSpecificChar('"', str, posPtr); for(i = 0;; i++) { char c = str[*posPtr + i]; if(!c) errAbort("Premature end of string (missing trailing double-quote); string position '%d'", *posPtr); else if(escapeMode) { // We support escape sequences listed in http://www.json.org, // except for Unicode which we cannot support in C-strings switch(c) { case 'b': c = '\b'; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'u': errAbort("Unicode in JSON is unsupported"); break; default: // we don't need to convert \,/ or " break; } dyStringAppendC(ds, c); escapeMode = FALSE; } else if(c == '"') break; else if(c == '\\') escapeMode = TRUE; else { dyStringAppendC(ds, c); escapeMode = FALSE; } } *posPtr += i; getSpecificChar('"', str, posPtr); return dyStringCannibalize(&ds); } static struct jsonElement *jsonParseExpression(char *str, int *posPtr); static struct jsonElement *jsonParseObject(char *str, int *posPtr) { struct hash *h = newHash(0); getSpecificChar('{', str, posPtr); while(str[*posPtr] != '}') { // parse out a name : val pair skipLeadingSpacesWithPos(str, posPtr); char *name = getString(str, posPtr); skipLeadingSpacesWithPos(str, posPtr); getSpecificChar(':', str, posPtr); skipLeadingSpacesWithPos(str, posPtr); hashAdd(h, name, jsonParseExpression(str, posPtr)); skipLeadingSpacesWithPos(str, posPtr); if(str[*posPtr] == ',') (*posPtr)++; else break; } skipLeadingSpacesWithPos(str, posPtr); getSpecificChar('}', str, posPtr); return (struct jsonElement *) newJsonHash(h); } static struct jsonElement *jsonParseList(char *str, int *posPtr) { struct slRef *list = NULL; getSpecificChar('[', str, posPtr); while(str[*posPtr] != ']') { struct slRef *e; AllocVar(e); skipLeadingSpacesWithPos(str, posPtr); e->val = jsonParseExpression(str, posPtr); slAddHead(&list, e); skipLeadingSpacesWithPos(str, posPtr); if(str[*posPtr] == ',') (*posPtr)++; else break; } skipLeadingSpacesWithPos(str, posPtr); getSpecificChar(']', str, posPtr); slReverse(&list); return (struct jsonElement *) newJsonList(list); } static struct jsonElement *jsonParseString(char *str, int *posPtr) { return (struct jsonElement *) newJsonString(getString(str, posPtr)); } static struct jsonElement *jsonParseBoolean(char *str, int *posPtr) { struct jsonBooleanElement *ele = NULL; int i; for(i = 0; str[*posPtr + i] && isalpha(str[*posPtr + i]); i++); ; char *val = cloneStringZ(str + *posPtr, i); -if(sameWord(val, "true")) +if(sameString(val, "true")) ele = newJsonBoolean(TRUE); -else if(sameWord(val, "false")) +else if(sameString(val, "false")) ele = newJsonBoolean(FALSE); else errAbort("Invalid boolean value '%s'; pos: %d", val, *posPtr); *posPtr += i; +freez(&val); return (struct jsonElement *) ele; } static struct jsonElement *jsonParseNumber(char *str, int *posPtr) { int i; boolean integral = TRUE; +struct jsonElement *retVal = NULL; + for(i = 0;; i++) { char c = str[*posPtr + i]; if(c == 'e' || c == 'E' || c == '.') integral = FALSE; else if(!c || (!isdigit(c) && c != '-')) break; } char *val = cloneStringZ(str + *posPtr, i); *posPtr += i; if(integral) - return (struct jsonElement *) newJsonNumber(sqlLongLong(val)); + retVal = (struct jsonElement *) newJsonNumber(sqlLongLong(val)); else { double d; if(sscanf(val, "%lf", &d)) - return (struct jsonElement *) newJsonDouble(d); + retVal = (struct jsonElement *) newJsonDouble(d); else - errAbort("Invalid JSON Double: %s; pos: %d", val, *posPtr + i); + errAbort("Invalid JSON Double: %s", val); } -return NULL; +freez(&val); +return retVal; } static struct jsonElement *jsonParseExpression(char *str, int *posPtr) { skipLeadingSpacesWithPos(str, posPtr); char c = str[*posPtr]; if(c == '{') return jsonParseObject(str, posPtr); else if (c == '[') return jsonParseList(str, posPtr); else if (c == '"') return jsonParseString(str, posPtr); else if (isdigit(c) || c == '-') return jsonParseNumber(str, posPtr); else return jsonParseBoolean(str, posPtr); // XXXX support null? } struct jsonElement *jsonParse(char *str) { // parse string into an in-memory json representation int pos = 0; struct jsonElement *ele = jsonParseExpression(str, &pos); skipLeadingSpacesWithPos(str, &pos); if(str[pos]) errAbort("Invalid JSON: unprocessed trailing string at position: %d: %s", pos, str + pos); return ele; }