/** * TrimPath Junction. Release 1.0.19. * Copyright (C) 2004, 2005 Metaha. * * TrimPath Junction is licensed under the GNU General Public License * and the Apache License, Version 2.0, as follows: * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed WITHOUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var TrimPath; (function(safeEval) { if (TrimPath == null) TrimPath = new Object(); var MANY_ZEROS = "000000000000000000"; var junctionUtil = TrimPath.junctionUtil = { upperFirst : function(str) { return str.charAt(0).toUpperCase() + str.substring(1); }, lowerFirst : function(str) { return str.charAt(0).toLowerCase() + str.substring(1); }, leftZeroPad : function(val, minLength) { if (typeof(val) != "string") val = String(val); return (MANY_ZEROS.substring(0, minLength - val.length)) + val; }, toSQLDateString : function(date) { if (date == null) date = new Date(); return junctionUtil.leftZeroPad(date.getUTCFullYear(), 4) + '/' + junctionUtil.leftZeroPad(date.getUTCMonth() + 1, 2) + '/' + junctionUtil.leftZeroPad(date.getUTCDate(), 2) + ' ' + junctionUtil.leftZeroPad(date.getUTCHours(), 2) + ':' + junctionUtil.leftZeroPad(date.getUTCMinutes(), 2) + ':' + junctionUtil.leftZeroPad(date.getUTCSeconds(), 2) + ' UTC'; }, prepSQLParams : function(sqlParams) { if (sqlParams != null) { for (var i = 0; i < sqlParams.length; i++) { if (sqlParams[i] instanceof Date) sqlParams[i] = junctionUtil.toSQLDateString(sqlParams[i]); } } return sqlParams; }, setMapTreeValue : function(mapTree, path, value) { // Example path is 'order[customer][name]'. var keys = path.replace(/\]/g, '').split('['); for (var k = 0; k < keys.length; k++) { var key = keys[k]; if (k < keys.length - 1) { if (mapTree[key] == null) mapTree[key] = {}; mapTree = mapTree[key]; } else { mapTree[key] = value; } } }, copyProps : function(src, dst) { for (var k in src) { if (typeof(src[k]) != 'function') { dst[k] = src[k]; // By convention, fields with "Id" suffix are integers. if (k.slice(-2) == "Id") dst[k] = junctionUtil.nanToNull(parseInt(dst[k])); } } return dst; }, nanToNull : function(val) { return isNaN(val) ? null : val; }, findRecordIndex : function(records, id) { // Returns the index of the record with the given id. if (records != null) { for (var i = 0; i < records.length; i++) { if (records[i].id == id) return i; } } return -1; }, beforeFilter : function(funcFilter, scopeObj) { // The remaining varargs are action function names in the scope object. var installBeforeFilter = function(actionName) { var funcOrig = scopeObj[actionName]; scopeObj[actionName] = function(req, res) { if (funcFilter(req, res) == false) return; return funcOrig(req, res); } } for (var i = 2; i < arguments.length; i++) installBeforeFilter(arguments[i]); }, createMemoryDataProvider : function(data) { var recordChanged = function(records, id, op) { if (records.changes == null) records.changes = {}; if (records.changes[id] == null) records.changes[id] = []; records.changes[id].push(op); } var memoryDataProvider = { query : function(stmt) { return stmt.filter(data); // See TrimQuery API. }, save : function(tableName, obj) { var records = data[tableName]; if (records == null) records = data[tableName] = []; var index = junctionUtil.findRecordIndex(records, obj.id); if (index >= 0) { junctionUtil.copyProps(obj, records[index]); recordChanged(records, obj.id, "update"); return true; } if (obj.id == null || obj.id == 0) // We use a negative id value to denote client-only existence. obj.id = memoryDataProvider.memoryLastId = memoryDataProvider.memoryLastId - 1; records.push(junctionUtil.copyProps(obj, {})); recordChanged(records, obj.id, "insert"); return true; }, destroy : function(tableName, id) { var records = data[tableName]; var index = junctionUtil.findRecordIndex(records, id); if (index >= 0) { records.splice(index, 1); recordChanged(records, id, "delete"); return true; } return false; }, // Attributes that part of memory data provider only... memoryLastId : 0, // Shared by all tables and grows negative. memoryData : data // Keyed by table name. }; return memoryDataProvider; }, toArray : function(obj, length) { length = length || obj.length; var result = []; for (var i = 0; i < length; i++) result.push(obj[i]); return result; }, // The following util functions only work in a DOM environment. toggleDisplay : function(id, doc) { var doc = doc || document; var el = doc.getElementById(id); if (el != null) el.style.display = (el.style.display != "block" ? "block" : "none"); return el; }, viewSource : function(viewSourceId, doc) { var doc = doc || document; var viewSourceId = viewSourceId || "junctionViewSource"; var viewSourceEl = junctionUtil.toggleDisplay(viewSourceId, doc); if (viewSourceEl != null) { if (viewSourceEl.style.display == "block") { var srcs, result = ""; viewSourceEl.innerHTML = result; } return false; } return true; } }; TrimPath.junctionCreate = function(env) { var modelInfos = {}; // Keyed by model name, like 'InvoiceLine'. var modelQueryLang = null; var lastFormArgs = null; // For startFormTag() and the other form tag helpers. var getFunc_cache = {}; var getFunc = function(funcName) { if (getFunc_cache[funcName] == null) getFunc_cache[funcName] = safeEval(funcName); return getFunc_cache[funcName]; } var junction = { env : env, queryLang : function() { if (modelQueryLang == null) modelQueryLang = env.makeQueryLang(env.dataSchemaProvider.getSchema()); return modelQueryLang; }, modelFor : function(modelName, func) { // The modelName is like 'InvoiceLine'. var modelInfo = modelInfos[modelName]; if (modelInfo == null) { modelInfo = modelInfos[modelName] = { name : modelName, funcName : modelName, func : func || getFunc(modelName), tableName : modelName, pluralName : modelName + 's', hasOne : {}, hasMany : {}, belongsTo : {}, validatesPresenceOf : [] } modelInfo.func.findAll = function(conditions, optParams) { var sql = "SELECT " + modelInfo.tableName + ".* FROM " + modelInfo.tableName; if (conditions != null) sql += " WHERE " + conditions; return modelInfo.func.findBySql(sql, optParams); } modelInfo.func.find = function(id) { return modelInfo.func.findFirst(modelInfo.tableName + ".id = " + parseInt(id)); // TODO: SQL injection problem here. } modelInfo.func.findFirst = function(conditions, optParams) { var sql = "SELECT " + modelInfo.tableName + ".* FROM " + modelInfo.tableName + " WHERE " + conditions; var record = env.dataProvider.query(junction.queryLang().parseSQL(sql, junctionUtil.prepSQLParams(optParams)))[0]; if (record != null) return modelInfo.func.newInstance(record); return null; } modelInfo.func.findBySql = function(sql, optParams) { var records = env.dataProvider.query(junction.queryLang().parseSQL(sql, junctionUtil.prepSQLParams(optParams))); var result = []; for (var i = 0; i < records.length; i++) result.push(modelInfo.func.newInstance(records[i])); return result; } modelInfo.func.newInstance = function(attrs) { var newObj = junctionUtil.copyProps(attrs, new (modelInfo.func)()); if (newObj != null) newObj.setConventionalAttributes(true); return newObj; } modelInfo.func.prototype.destroy = function() { return env.dataProvider.destroy(modelInfo.tableName, this.id); } modelInfo.func.prototype.save = function() { this.setConventionalAttributes(); return env.dataProvider.save(modelInfo.tableName, this); } modelInfo.func.prototype.updateAttributes = function(attrs) { var id = this.id; junctionUtil.copyProps(attrs, this); this.id = id; return this.save(); } modelInfo.func.prototype.setConventionalAttributes = function(skipLockVersion) { var tableSchema = env.dataSchemaProvider.getSchema()[modelInfo.tableName]; if (tableSchema != null) { if (tableSchema['lockVersion']) { if (this.lockVersion == null) this.lockVersion = 0; if (skipLockVersion != true) // Treats null as false. this.lockVersion += 1; } var now; if (tableSchema['createdAt'] && this.createdAt == null) this.createdAt = now = (now || junctionUtil.toSQLDateString(new Date())); if (tableSchema['createdOn'] && this.createdOn == null) this.createdOn = now = (now || junctionUtil.toSQLDateString(new Date())); if (tableSchema['updatedAt']) this.updatedAt = now = (now || junctionUtil.toSQLDateString(new Date())); if (tableSchema['updatedOn']) this.updatedOn = now = (now || junctionUtil.toSQLDateString(new Date())); } } modelInfo.func.prototype.validate = function() { // TODO: Placeholder. return true; } modelInfo.metaAspects = { tableName : function(tableNameVal) { modelInfo.tableName = tableNameVal; }, pluralName : function(pluralNameVal) { modelInfo.pluralName = pluralNameVal; }, hasMany : function(childPluralName, info) { // The childPluralName is like 'TodoItems'. info = info || {}; info.modelName = info.modelName || childPluralName.slice(0, -1); info.foreignKey = info.foreignKey || junctionUtil.lowerFirst(modelName) + "Id"; modelInfo.hasMany[childPluralName] = info; modelInfo.func.prototype['get' + childPluralName] = function(forceReload) { if (forceReload == true || this['_cached_' + childPluralName] == null) { var childModelInfo = modelInfos[info.modelName]; var conditions = childModelInfo.tableName + "." + info.foreignKey + " = " + this.id; if (info.conditions) conditions += " AND " + info.conditions; this['_cached_' + childPluralName] = childModelInfo.func.findAll(conditions); } return this['_cached_' + childPluralName]; } }, belongsTo : function(parentName, info) { // The parentName is like 'TodoList'. info = info || {}; info.modelName = info.modelName || parentName; info.foreignKey = info.foreignKey || junctionUtil.lowerFirst(parentName) + "Id"; modelInfo.belongsTo[parentName] = info; modelInfo.func.prototype['get' + parentName] = function(forceReload) { if (forceReload == true || this['_cached_' + parentName] == null) { var parentModelInfo = modelInfos[info.modelName]; var conditions = parentModelInfo.tableName + ".id = " + this[info.foreignKey]; this['_cached_' + parentName] = parentModelInfo.func.findFirst(conditions); } return this['_cached_' + parentName]; } }, validatesPresenceOf : function() { for (var k in arguments) modelInfo.validatesPresenceOf.push(arguments[k]); }, toString : function() { var result = '{'; for (var k in modelInfo) result = result + k + ': ' + modelInfo(k) + ', '; return result + '}'; } } } return modelInfo.metaAspects; }, scaffold : function(controllerFunc, modelName) { // The modelName is like 'InvoiceLine'. controllerFunc.prototype.index = function(req, res) { var modelInfo = modelInfos[modelName]; res[junctionUtil.lowerFirst(modelInfo.pluralName)] = modelInfo.func.findAll(); } controllerFunc.prototype.show = controllerFunc.prototype.edit = function(req, res) { var modelInfo = modelInfos[modelName]; res[junctionUtil.lowerFirst(modelInfo.name)] = modelInfo.func.find(req['objId']); } controllerFunc.prototype.update = function(req, res) { var modelInfo = modelInfos[modelName]; var key = junctionUtil.lowerFirst(modelInfo.name); res[key] = modelInfo.func.find(req['objId']); if (res[key].updateAttributes(req[key])) { res.flash['notice'] = 'The ' + modelName + ' is updated.'; res.redirectToAction('show', res[key].id); } else res.renderAction('edit'); } controllerFunc.prototype.newInstance = function(req, res) { var modelInfo = modelInfos[modelName]; res[junctionUtil.lowerFirst(modelInfo.name)] = modelInfo.func.newInstance(); } controllerFunc.prototype.create = function(req, res) { var modelInfo = modelInfos[modelName]; var key = junctionUtil.lowerFirst(modelInfo.name); res[key] = modelInfo.func.newInstance(req[key]); if (res[key].save()) { res.flash['notice'] = 'The ' + modelName + ' is created.'; res.redirectToAction('show', res[key].id); } else res.renderAction('newInstance'); } controllerFunc.prototype.destroy = function(req, res) { var modelInfo = modelInfos[modelName]; var obj = modelInfo.func.find(req['objId']); if (obj != null) { obj.destroy(); res.flash['notice'] = 'The ' + modelName + ' is deleted.'; } else res.flash['notice'] = 'We could not delete an unknown ' + modelName + '.'; res.redirectToAction('index'); } }, /////////////////////////////////////////////// invokeController : function(controllerName, actionName, objId, req) { var controllerFuncName = junctionUtil.upperFirst(controllerName) + "Controller"; var controllerFunc = getFunc(controllerFuncName); if (controllerFunc == null || typeof(controllerFunc) != 'function') throw ('Could not find function: ' + controllerFuncName); if (req == null) req = {}; req.controllerName = controllerName; req.actionName = actionName; req.objId = objId; req.session = env.getSession(); var urlForArgsCheck = function(controllerNameVal, actionNameVal, objIdVal) { return { controllerNameVal : (controllerNameVal || controllerName), actionNameVal : (actionNameVal || actionName), objIdVal : (objIdVal) }; } var resRendered = null; var resRedirect = null; var res = { req : req, session : req.session, flash : req.session.flash || {}, layoutName : controllerFunc.layoutName || 'default', renderAction : function(actionName) { res.render(controllerName + '/' + actionName); }, render : function(templateName) { if (templateName == null) templateName = controllerName + '/' + actionName; res.renderTemplate("/app/views/" + templateName + ".jst"); }, renderTemplate : function(templatePath) { res.renderText(env.templateRenderer(templatePath, res)); }, renderText : function(text) { resRendered = text; }, redirectTo : function(controllerNameVal, actionNameVal, objIdVal) { resRedirect = urlForArgsCheck(controllerNameVal, actionNameVal, objIdVal); }, redirectToAction : function(actionNameVal, objIdVal) { res.redirectTo(controllerName, actionNameVal, objIdVal); }, urlForArgs : function(args) { var result = [ '?controllerName=', args.controllerNameVal, '&actionName=', args.actionNameVal ]; if (args.objIdVal) result.push('&objId=' + args.objIdVal); return result.join(''); }, urlFor : function(controllerNameVal, actionNameVal, objIdVal) { return res.urlForArgs(urlForArgsCheck(controllerNameVal, actionNameVal, objIdVal)); }, linkToArgs : function(linkText, args, htmlOptions) { var result = [ ''); result.push(linkText); result.push(''); return result.join(''); }, linkTo : function(linkText, controllerNameVal, actionNameVal, objIdVal, htmlOptions) { return res.linkToArgs(linkText, urlForArgsCheck(controllerNameVal, actionNameVal, objIdVal), htmlOptions); }, linkToLocal : function(linkText, controllerNameVal, actionNameVal, objIdVal, htmlOptions) { var args = urlForArgsCheck(controllerNameVal, actionNameVal, objIdVal); htmlOptions = htmlOptions || {}; htmlOptions.onclick = "TrimPath.junction.invokeController('" + args.controllerNameVal + "', '" + args.actionNameVal + "'"; if (args.objIdVal) htmlOptions.onclick += ", '" + args.objIdVal + "'"; htmlOptions.onclick += "); return false"; return res.linkToArgs(linkText, args, htmlOptions); }, defaultFormId : function(args) { return res.urlForArgs(args).replace(/&/g, '|'); }, startFormTag : function(controllerNameVal, actionNameVal, objIdVal, htmlOptions) { lastFormArgs = urlForArgsCheck(controllerNameVal, actionNameVal, objIdVal); lastFormArgs.htmlOptions = htmlOptions || {}; lastFormArgs.htmlOptions.id = lastFormArgs.htmlOptions.id || res.defaultFormId(lastFormArgs); lastFormArgs.htmlOptions.method = lastFormArgs.htmlOptions.method || 'post'; var result = [ '
'); return result.join(''); }, startFormTagLocal : function(controllerNameVal, actionNameVal, objIdVal, htmlOptions) { args = urlForArgsCheck(controllerNameVal, actionNameVal, objIdVal); htmlOptions = htmlOptions || {}; htmlOptions.id = htmlOptions.id || res.defaultFormId(args); htmlOptions.onsubmit = [ "TrimPath.junction.env.invokeForm('", htmlOptions.id, "', '", args.controllerNameVal, "', '", args.actionNameVal, "'" ]; if (args.objIdVal) htmlOptions.onsubmit.push(", '" + args.objIdVal + "'"); htmlOptions.onsubmit.push('); return false'); htmlOptions.onsubmit = htmlOptions.onsubmit.join(''); return res.startFormTag(args.controllerNameVal, args.actionNameVal, args.objIdVal, htmlOptions); }, endFormTag : function() { lastFormArgs = null; return "
"; }, submitButton : function(name, value, htmlOptions) { htmlOptions = htmlOptions || {}; var result = [ ''); return result.join(''); }, submitButtonLocal : function(name, value, htmlOptions) { htmlOptions = htmlOptions || {}; htmlOptions.onclick = [ "TrimPath.junction.env.invokeForm('", lastFormArgs.htmlOptions.id, "', '", lastFormArgs.controllerNameVal, "', '", lastFormArgs.actionNameVal, "'" ]; if (lastFormArgs.objIdVal) htmlOptions.onclick.push(", '" + lastFormArgs.objIdVal + "'"); htmlOptions.onclick.push('); return false'); htmlOptions.onclick = htmlOptions.onclick.join(''); return res.submitButton(name, value, htmlOptions) }, inputField : function(objName, attrName, inputType) { var value = null; if (res[objName] != null) value = res[objName][attrName]; if (value == null) value = ""; return ''; }, textField : function(objName, attrName) { return res.inputField(objName, attrName, 'text'); }, hiddenField : function(objName, attrName) { return res.inputField(objName, attrName, 'hidden'); }, passwordField : function(objName, attrName) { return res.inputField(objName, attrName, 'password'); }, select : function(objName, attrName, choices) { var value = null; if (res[objName] != null) value = res[objName][attrName]; var result = ''; return result; }, optionsForSelect : function(choices, selected) { var result = []; for (var i = 0; i < choices.length; i++) { var name, value; var choice = choices[i]; if (choice != null && choice instanceof Array) { name = choice[0]; value = choice[1]; } else name = value = choice; if (value == null) value = ''; result.push(''); } return result.join(''); } } try { var controller = new (controllerFunc)(); if (controller && controller[actionName]) controller[actionName](req, res); if (resRedirect != null) { req.session.flash = res.flash; env.syncToServer(); return env.redirect(resRedirect.controllerNameVal, resRedirect.actionNameVal, resRedirect.objIdVal); } if (resRendered == null) res.renderAction(actionName); } catch (e) { resRendered = "[ERROR: controller processing: " + controllerName + ", " + actionName + ", " + objId + ": " + e.toString() + "]"; } if (res.layoutName != null) env.layoutRenderer(res.layoutName, resRendered, req, res); req.session.flash = null; env.syncToServer(); return resRendered; } } return junction; } // A web browser based environment for the default junction is defined here. // Other environments, like for Rhino, would override this. TrimPath.junction = TrimPath.junctionCreate({ appName : null, makeQueryLang : TrimPath.makeQueryLang, getSession : function() { if (TrimPath.junctionSession == null) TrimPath.junctionSession = {}; return TrimPath.junctionSession; }, templateCache : {}, templateRenderer : function(templatePath, context) { try { var template = TrimPath.junction.env.templateCache[templatePath]; if (template == null) { var templateEl = document.getElementById(templatePath); if (templateEl == null) return "[ERROR: could not find template: " + templatePath + "]"; // Jigger with newlines since textarea content sometimes isn't stable // through File-Save-As, which might introduce spurious newlines. var text = templateEl.value.replace(/\n/g, ' ').replace(/</g, "<").replace(/>/g, ">"); template = TrimPath.junction.env.templateCache[templatePath] = TrimPath.parseTemplate(text); } return template.process(context); } catch (e) { return "[ERROR: template parsing: " + templatePath + ": " + e.toString() + "]"; } }, layoutRenderer : function(layoutName, content, req, res, junction) { var targetElement = document.getElementById(layoutName); if (targetElement != null) targetElement.innerHTML = content; // Also render and update any subsidiary layout areas. var subsidiaryPrefix = layoutName + '.'; for (var i = 0, divs = document.getElementsByTagName("DIV"); i < divs.length; i++) { var div = divs[i]; if (div.id != null && div.id.substring(0, subsidiaryPrefix.length) == subsidiaryPrefix) { var subsidiaryPath = "/app/views/layouts/" + div.id.substring(subsidiaryPrefix.length) + ".jst"; div.innerHTML = TrimPath.junction.env.templateRenderer(subsidiaryPath, res); } } }, redirect : function(controllerName, actionName, objId) { return TrimPath.junction.invokeController(controllerName, actionName, objId); }, invokeForm : function(formId, controllerName, actionName, objId) { var formEl = document.getElementById(formId); if (formEl != null) { var req = {}; for (var i = 0; i < formEl.elements.length; i++) { var element = formEl.elements[i]; junctionUtil.setMapTreeValue(req, element.name, element.value); } return TrimPath.junction.invokeController(controllerName, actionName, objId, req); } }, syncToServer : function() { if (String(window.location).substring(0, 5) != "file:" && TrimPath.junction.env.appName != null) { var url = "/app/sync/" + TrimPath.junction.env.appName; var data = TrimPath.junction.env.dataProvider.memoryData; if (data != null) { var dirty = false; var delta = {}; for (var tableName in data) { var records = data[tableName]; if (records.changes != null) { dirty = true; delta[tableName] = {}; for (var id in records.changes) { var ops = records.changes[id]; var op = ops[ops.length - 1]; if (op == "delete") { delta[tableName][id] = [op]; } else { var index = junctionUtil.findRecordIndex(records, id); if (index >= 0) delta[tableName][id] = [op, records[index]]; } } records.changes = null; } } if (dirty) { dojo.io.bind({ // Relies on dojo toolkit. url : url, method : "post", content : { delta : junctionUtil.toJsonString(delta) }, load : function(type, data) { TrimPath.junction.env.dataProvider = safeEval('TrimPath.junctionUtil.createMemoryDataProvider(' + data + ')'); }, changeURL : true, mimetype : "text/plain" }); } } } } }); }) (function(evalExpr) { return eval(evalExpr); }); // The safeEval works in global scope. var modelFor = TrimPath.junction.modelFor; var scaffold = TrimPath.junction.scaffold; var queryLang = TrimPath.junction.queryLang; var beforeFilter = TrimPath.junctionUtil.beforeFilter; var toSQLDateString = TrimPath.junctionUtil.toSQLDateString; /* Copyright (c) 2002 JSON.org Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. The Software shall be used for Good, not Evil. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ TrimPath.junctionUtil.toJsonString = function(arg) { // Put into TrimPath namespace to avoid version conflicts. var i, o, u, v; switch (typeof arg) { case 'object': if (arg) { if (arg.constructor == Array) { o = ''; for (i = 0; i < arg.length; ++i) { v = TrimPath.junctionUtil.toJsonString(arg[i]); if (o) { o += ','; } if (v !== u) { o += v; } else { o += 'null,'; } } return '[' + o + ']'; } else if (typeof arg.toString != 'undefined') { o = ''; for (i in arg) { v = TrimPath.junctionUtil.toJsonString(arg[i]); if (v !== u) { if (o) { o += ','; } o += TrimPath.junctionUtil.toJsonString(i) + ':' + v; } } return '{' + o + '}'; } else { return; } } return 'null'; case 'unknown': case 'undefined': case 'function': return u; case 'string': return '"' + arg.replace(/(["\\])/g, '\\$1') + '"'; default: return String(arg); } }