I am trying to use Zapier Javascript code step to invoke a HTML query to a remote server. The server returns an XML response body that needs to be parsed further into a JSON object.
Firstly, I had some trouble finding a quick way to do this conversion. There are modules/packages that are available using JQuery and other frameworks, which are unsupported by Zapier (Zapier engine supports NodeJS 8.10.x without many additional modules/packages). The standard documentation seems to be created for a case returning JSON object already.
I have the following code, and I am experiencing error relative to that as well:
EDIT (06/05):
Based on the comment, I am posting the calling code to the method xml2JSON.
let url = '<https://remote-sever-base-url?method, params etc.>'
let response = await fetch(url);
if (response.ok) { // if HTTP-status is 200-299
// Response body is in XML format. No JSON available.
var xmlDoc = await response.text();
var jsondata = xmlToJson(xmlDoc);
console.log(jsonData);
} else {
alert("HTTP-Error: " + response.status);
}
// Changes XML to JSON
function xmlToJson(xml) {
// Create the return object
var obj = {};
if (xml.nodeType == 1) { // element
// do attributes
if (xml.attributes.length > 0) {
obj["#attributes"] = {};
for (var j = 0; j < xml.attributes.length; j++) {
var attribute = xml.attributes.item(j);
obj["#attributes"][attribute.nodeName] = attribute.nodeValue;
}
}
} else if (xml.nodeType == 3) { // text
obj = xml.nodeValue;
}
// do children
if (xml.hasChildNodes()) {
for(var i = 0; i < xml.childNodes.length; i++) {
var item = xml.childNodes.item(i);
var nodeName = item.nodeName;
if (typeof(obj[nodeName]) == "undefined") {
obj[nodeName] = xmlToJson(item);
} else {
if (typeof(obj[nodeName].push) == "undefined") {
var old = obj[nodeName];
obj[nodeName] = [];
obj[nodeName].push(old);
}
obj[nodeName].push(xmlToJson(item));
}
}
}
return obj;
};
The error:
The run javascript could not be sent to Code by Zapier.
TypeError: xml.hasChildNodes is not a function
FYI - the incoming XML is pretty flat and predictable in its structure.
<result>
<record>
<field1>
<field 2>
....fields continue
</record>
...... list continues
</result>
Therefore the DOM does have child nodes, though not very deep tree.
Also, since these are records out of a relational DB, all record structures within a result set are similar, though between different POSTs they will differ. Hope this clarifies.
When strung together, you're basically calling (await response.text()).hasChildNodes(), which is a function that doesn't exist (hence your error). response.text() resolves to a regular string, not any sort of XML object.
Instead of parsing it yourself, I'd recommend using the "Webhooks by Zapier" app to make your request - it'll do the XML parsing for you and return a json-like object you can use downstream.
So I have this JS indexedDB library:
window.db = {
name:"indexedDBname",
varsion:0.7,
_settings:"indexedDBobject",
h:null, // Handler to the db
open:function(callback,callback1) {
var r = indexedDB.open( db.name );
r.onupgradeneeded = function(e){ console.log(".onupgradeneeded is not yet supported by webkit"); };
r.onsuccess = function(e){
db.h = e.target.result;
if( db.version !== db.h.version ) {
var v = db.h.setVersion( db.version );
v.onsuccess = function(e) {
if(db.h.objectStoreNames.contains( db._settings )) db.h.deleteObjectStore( db._settings );
db.h.createObjectStore(db._settings, { keyPath:"name" });
};
v.onfailure = db.onerror;
v.onerror = db.onerror;
v.onblocked = db.onerror;
}
// CALLBACKS
if(typeof callback=="function" && typeof callback1=="function") callback.call(window,callback1);
else if(typeof callback=="function" && typeof callback1!="function") callback.call(window);
};
r.onfailure = db.onerror;
},
getSettings:function(callback){ // retrieve user custom settings
var t = db.h.transaction( [db._settings], IDBTransaction.READ_ONLY ),
s = t.objectStore(db._settings),
keyRange = IDBKeyRange.lowerBound(0),
cursorRequest = s.openCursor(keyRange),
tmp = {};
cursorRequest.onsuccess = function(e) {
var result = e.target.result;
if(!!result==false) {
// CALLBACKS
if(typeof callback=="function") callback.call(window);
return;
}
tmp[result.value.name] = result.value.value;
result.continue();
}
cursorRequest.onerror = db.onerror;
},
onerror:function(e){ console.log("Handle and print error here:"+e); }
};
// actual run
db.open( db.getSettings, user.applySettings);
Which I tend to use pretty often, but as you can see, those callback doesn't look too well... And when I want to do series of tasks or even call any of those functions with set of their own parameters, my code starts to look really choppy, ex.
db.open('forWhichUser',newSettingsToApplyObject, callback1, argumentForCallback1, secondOptionalArgument, callback2, etc);
So in the old days I'll just do:
db.open('userName', settingsMap);
var opts = db.getSettings();
user.downloadInfoBasedOn( opts );
user.renderInfoTo('userDataHolderId');
but now, since everything can start/finish in unpredictable moments (depending on computer performance, db size, etc, etc...) how do I handle all of that asynchronicity keeping code graceful and readable?
You can use the JavaScript promises/deferreds pattern:
http://wiki.commonjs.org/wiki/Promises/A
http://blogs.msdn.com/b/ie/archive/2011/09/11/asynchronous-programming-in-javascript-with-promises.aspx
Promises/deferreds can help you create a much easier and readable async code. You can use jQuery deffered object to achieve that (http://api.jquery.com/category/deferred-object/)
Another option is to use story.js which wrap the IndexedDB API and exposes it in a much simpler way (http://blogs.microsoft.co.il/blogs/gilf/archive/2012/04/21/the-story-begins.aspx)
I hope you will find this answer helpful.
Gil
I created a function to do this.
var text="adsf [name]Victor[/name] dummytext [name]Elliot[/name] asdf [name]Jake[/name] asdf [foo]bar[/foo]";
alert( readTags(text,'name') ); //Victor,Elliot,Jake
alert( readTags(text,'foo') ); //bar
but now I like to implement a function that receive a string like this
[person]
[name]jake[/name]
[age]12[/age]
[/person]
and return a object like this
var object={};
object['person']={};
object['name']='jake';
object['age']='12';
return(object);
but I don't know how to loop through the text. How to deal with starting and ending tags?
like
[tag] [tag]value[/tag] [/tag]
I thought to find starting tag from left and ending tag from the right using indexOf('[tag]') and lastindexOf('[/tag]')
but doesn't work in this situation
[tag]value[/tag] [tag]value[/tag]
this is the previous function
function readTags(str,property){
var beginTag='['+property+']';
var endTag='[/'+property+']';
var values=new Array(0);
while(str.indexOf(beginTag)!=-1){
values[values.length]=strBetween(str,beginTag,endTag);
str=str.substring(str.indexOf(endTag)+endTag.length);
}
return(values);
}
function strBetween(string,strBegin,strEnd){ //StrBetween("abcdef","b","e") //return "cd"
var posBegin, posEnd;
posBegin=string.indexOf(strBegin);
string=string.substring(posBegin + strBegin.length);
posEnd=string.indexOf(strEnd);
string=string.substring(0,posEnd);
if ((posBegin==-1)||(posEnd==-1)){
return(null);
}else{
return(string);
}
}
Unless you have a good reason not to use JSON, don't do this. JSON handles all of those problems very well and can be floated around from server to client and vice versa quite easily.
But since this seems fun, I'll try and see if I can whip up an answer.
Since your structure resembles XML, just replace the brackets with < and > and parse it like XML:
text = text.replace('[', '<').replace(']', '>');
if (typeof DOMParser != "undefined") {
var parser = new DOMParser();
var xml = parser.parseFromString(text, 'text/xml');
} else {
var xml = new ActiveXObject('Microsoft.XMLDOM');
xml.async = 'false';
xml.loadXML(text);
}
Now xml holds a DOMDocument that you can parse:
xml.getElementsByTagName('person').childnodes;
Try this possibly-working code (didn't test):
function createObject(element) {
var object = {};
if (element.childNodes.length > 0) {
for (child in element.childnodes) {
object[element.tagName] = createObject(child);
}
return object;
} else {
return element.nodeValue;
}
}
I thought this would be interesting to do without a third-party parser, so I built me a simple one:
function parse(code)
{
var obj = {},
cur = obj,
stack = [];
code.replace(/\[([^\]]+)\]|([^\[]*)/g, function (match, tagName, text) {
if (tagName)
{
if (tagName.charAt(0) == "/")
{
/* end tag */
cur = stack.pop();
}
else
{
/* start tag */
stack.push(cur);
cur = cur[tagName] = {};
}
}
else
{
cur["#text"] = text;
}
});
return obj;
}
var obj = parse(text);
JSON <=> XML http://code.google.com/p/x2js/
Just wondering if there is anything built-in to Javascript that can take a Form and return the query parameters, eg: "var1=value&var2=value2&arr[]=foo&arr[]=bar..."
I've been wondering this for years.
The URLSearchParams API is available in all modern browsers. For example:
const params = new URLSearchParams({
var1: "value",
var2: "value2",
arr: "foo",
});
console.log(params.toString());
//Prints "var1=value&var2=value2&arr=foo"
2k20 update: use Josh's solution with URLSearchParams.toString().
Old answer:
Without jQuery
var params = {
parameter1: 'value_1',
parameter2: 'value 2',
parameter3: 'value&3'
};
var esc = encodeURIComponent;
var query = Object.keys(params)
.map(k => esc(k) + '=' + esc(params[k]))
.join('&');
For browsers that don't support arrow function syntax which requires ES5, change the .map... line to
.map(function(k) {return esc(k) + '=' + esc(params[k]);})
If you're using jQuery you might want to check out jQuery.param() http://api.jquery.com/jQuery.param/
Example:
var params = {
parameter1: 'value1',
parameter2: 'value2',
parameter3: 'value3'
};
var query = $.param(params);
console.log(query);
This will print out:
parameter1=value1¶meter2=value2¶meter3=value3
This doesn't directly answer your question, but here's a generic function which will create a URL that contains query string parameters. The parameters (names and values) are safely escaped for inclusion in a URL.
function buildUrl(url, parameters){
var qs = "";
for(var key in parameters) {
var value = parameters[key];
qs += encodeURIComponent(key) + "=" + encodeURIComponent(value) + "&";
}
if (qs.length > 0){
qs = qs.substring(0, qs.length-1); //chop off last "&"
url = url + "?" + qs;
}
return url;
}
// example:
var url = "http://example.com/";
var parameters = {
name: "George Washington",
dob: "17320222"
};
console.log(buildUrl(url, parameters));
// => http://www.example.com/?name=George%20Washington&dob=17320222
Create an URL object and append the values to seachParameters
let stringUrl = "http://www.google.com/search";
let url = new URL(stringUrl);
let params = url.searchParams;
params.append("q", "This is seach query");
console.log(url.toString());
The output will be
http://www.google.com/search?q=This+is+seach+query
With jQuery you can do this by $.param
$.param({ action: 'ship', order_id: 123, fees: ['f1', 'f2'], 'label': 'a demo' })
// -> "action=ship&order_id=123&fees%5B%5D=f1&fees%5B%5D=f2&label=a+demo"
ES2017 (ES8)
Making use of Object.entries(), which returns an array of object's [key, value] pairs. For example, for {a: 1, b: 2} it would return [['a', 1], ['b', 2]]. It is not supported (and won't be) only by IE.
Code:
const buildURLQuery = obj =>
Object.entries(obj)
.map(pair => pair.map(encodeURIComponent).join('='))
.join('&');
Example:
buildURLQuery({name: 'John', gender: 'male'});
Result:
"name=John&gender=male"
querystring can help.
So, you can
const querystring = require('querystring')
url += '?' + querystring.stringify(parameters)
No, I don't think standard JavaScript has that built in, but Prototype JS has that function (surely most other JS frameworks have too, but I don't know them), they call it serialize.
I can reccomend Prototype JS, it works quite okay. The only drawback I've really noticed it it's size (a few hundred kb) and scope (lots of code for ajax, dom, etc.). Thus if you only want a form serializer it's overkill, and strictly speaking if you only want it's Ajax functionality (wich is mainly what I used it for) it's overkill. Unless you're careful you may find that it does a little too much "magic" (like extending every dom element it touches with Prototype JS functions just to find elements) making it slow on extreme cases.
If you don't want to use a library, this should cover most/all of the same form element types.
function serialize(form) {
if (!form || !form.elements) return;
var serial = [], i, j, first;
var add = function (name, value) {
serial.push(encodeURIComponent(name) + '=' + encodeURIComponent(value));
}
var elems = form.elements;
for (i = 0; i < elems.length; i += 1, first = false) {
if (elems[i].name.length > 0) { /* don't include unnamed elements */
switch (elems[i].type) {
case 'select-one': first = true;
case 'select-multiple':
for (j = 0; j < elems[i].options.length; j += 1)
if (elems[i].options[j].selected) {
add(elems[i].name, elems[i].options[j].value);
if (first) break; /* stop searching for select-one */
}
break;
case 'checkbox':
case 'radio': if (!elems[i].checked) break; /* else continue */
default: add(elems[i].name, elems[i].value); break;
}
}
}
return serial.join('&');
}
You can do that nowadays with FormData and URLSearchParams without the need to loop over anything.
const formData = new FormData(form);
const searchParams = new URLSearchParams(formData);
const queryString = searchParams.toString();
Older browsers will need a polyfill, though.
Might be a bit redundant but the cleanest way i found which builds on some of the answers here:
const params: {
key1: 'value1',
key2: 'value2',
key3: 'value3',
}
const esc = encodeURIComponent;
const query = Object.keys(params)
.map(k => esc(k) + '=' + esc(params[k]))
.join('&');
return fetch('my-url', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: query,
})
Source
I'm not entirely certain myself, I recall seeing jQuery did it to an extent, but it doesn't handle hierarchical records at all, let alone in a php friendly way.
One thing I do know for certain, is when building URLs and sticking the product into the dom, don't just use string-glue to do it, or you'll be opening yourself to a handy page breaker.
For instance, certain advertising software in-lines the version string from whatever runs your flash. This is fine when its adobes generic simple string, but however, that's very naive, and blows up in an embarrasing mess for people whom have installed Gnash, as gnash'es version string happens to contain a full blown GPL copyright licences, complete with URLs and <a href> tags. Using this in your string-glue advertiser generator, results in the page blowing open and having imbalanced HTML turning up in the dom.
The moral of the story:
var foo = document.createElement("elementnamehere");
foo.attribute = allUserSpecifiedDataConsideredDangerousHere;
somenode.appendChild(foo);
Not:
document.write("<elementnamehere attribute=\""
+ ilovebrokenwebsites
+ "\">"
+ stringdata
+ "</elementnamehere>");
Google need to learn this trick. I tried to report the problem, they appear not to care.
You don't actually need a form to do this with Prototype. Just use Object.toQueryString function:
Object.toQueryString({ action: 'ship', order_id: 123, fees: ['f1', 'f2'], 'label': 'a demo' })
// -> 'action=ship&order_id=123&fees=f1&fees=f2&label=a%20demo'
I know this is very late answer but works very well...
var obj = {
a:"a",
b:"b"
}
Object.entries(obj).map(([key, val])=>`${key}=${val}`).join("&");
note: object.entries will return key,values pairs
output from above line will be a=a&b=b
Hope its helps someone.
Happy Coding...
The UrlSearchParams API is a great suggestion, but I can't believe nobody mentioned the incredibly useful .get and .set methods. They can be used to manipulate the query string and not only they're very easy to use, they also solve a number of issues you might encounter. For example, in my case I wanted to build a query string without duplicate keys. .set solves this problem for you. Quoting from the MDN docs:
URLSearchParams.set()
Sets the value associated with a given search parameter to the given value. If there are several values, the others are deleted.
Example (from MDN):
let url = new URL('https://example.com?foo=1&bar=2');
let params = new URLSearchParams(url.search);
// Add a third parameter
params.set('baz', 3);
params.toString(); // "foo=1&bar=2&baz=3"
Alternative, shorter syntax:
let url = new URL('https://example.com?foo=1&bar=2');
// Add a third parameter
url.searchParams.set('baz', 3);
url.searchParams.toString(); // "foo=1&bar=2&baz=3"
As Stein says, you can use the prototype javascript library from http://www.prototypejs.org.
Include the JS and it is very simple then, $('formName').serialize() will return what you want!
For those of us who prefer jQuery, you would use the form plugin: http://plugins.jquery.com/project/form, which contains a formSerialize method.
Is is probably too late to answer your question.
I had the same question and I didn't like to keep appending strings to create a URL. So, I started using $.param as techhouse explained.
I also found a URI.js library that creates the URLs easily for you. There are several examples that will help you: URI.js Documentation.
Here is one of them:
var uri = new URI("?hello=world");
uri.setSearch("hello", "mars"); // returns the URI instance for chaining
// uri == "?hello=mars"
uri.setSearch({ foo: "bar", goodbye : ["world", "mars"] });
// uri == "?hello=mars&foo=bar&goodbye=world&goodbye=mars"
uri.setSearch("goodbye", "sun");
// uri == "?hello=mars&foo=bar&goodbye=sun"
// CAUTION: beware of arrays, the following are not quite the same
// If you're dealing with PHP, you probably want the latter…
uri.setSearch("foo", ["bar", "baz"]);
uri.setSearch("foo[]", ["bar", "baz"]);`
These answers are very helpful, but i want to add another answer, that may help you build full URL.
This can help you concat base url, path, hash and parameters.
var url = buildUrl('http://mywebsite.com', {
path: 'about',
hash: 'contact',
queryParams: {
'var1': 'value',
'var2': 'value2',
'arr[]' : 'foo'
}
});
console.log(url);
You can download via npm https://www.npmjs.com/package/build-url
Demo:
;(function () {
'use strict';
var root = this;
var previousBuildUrl = root.buildUrl;
var buildUrl = function (url, options) {
var queryString = [];
var key;
var builtUrl;
var caseChange;
// 'lowerCase' parameter default = false,
if (options && options.lowerCase) {
caseChange = !!options.lowerCase;
} else {
caseChange = false;
}
if (url === null) {
builtUrl = '';
} else if (typeof(url) === 'object') {
builtUrl = '';
options = url;
} else {
builtUrl = url;
}
if(builtUrl && builtUrl[builtUrl.length - 1] === '/') {
builtUrl = builtUrl.slice(0, -1);
}
if (options) {
if (options.path) {
var localVar = String(options.path).trim();
if (caseChange) {
localVar = localVar.toLowerCase();
}
if (localVar.indexOf('/') === 0) {
builtUrl += localVar;
} else {
builtUrl += '/' + localVar;
}
}
if (options.queryParams) {
for (key in options.queryParams) {
if (options.queryParams.hasOwnProperty(key) && options.queryParams[key] !== void 0) {
var encodedParam;
if (options.disableCSV && Array.isArray(options.queryParams[key]) && options.queryParams[key].length) {
for(var i = 0; i < options.queryParams[key].length; i++) {
encodedParam = encodeURIComponent(String(options.queryParams[key][i]).trim());
queryString.push(key + '=' + encodedParam);
}
} else {
if (caseChange) {
encodedParam = encodeURIComponent(String(options.queryParams[key]).trim().toLowerCase());
}
else {
encodedParam = encodeURIComponent(String(options.queryParams[key]).trim());
}
queryString.push(key + '=' + encodedParam);
}
}
}
builtUrl += '?' + queryString.join('&');
}
if (options.hash) {
if(caseChange)
builtUrl += '#' + String(options.hash).trim().toLowerCase();
else
builtUrl += '#' + String(options.hash).trim();
}
}
return builtUrl;
};
buildUrl.noConflict = function () {
root.buildUrl = previousBuildUrl;
return buildUrl;
};
if (typeof(exports) !== 'undefined') {
if (typeof(module) !== 'undefined' && module.exports) {
exports = module.exports = buildUrl;
}
exports.buildUrl = buildUrl;
} else {
root.buildUrl = buildUrl;
}
}).call(this);
var url = buildUrl('http://mywebsite.com', {
path: 'about',
hash: 'contact',
queryParams: {
'var1': 'value',
'var2': 'value2',
'arr[]' : 'foo'
}
});
console.log(url);
var params = { width:1680, height:1050 };
var str = jQuery.param( params );
console.log(str)
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Remove undefined params 💪😃
urlParams = obj =>{
const removeUndefined = JSON.parse(JSON.stringify(obj))
const result = new URLSearchParams(removeUndefined).toString();
return result ? `?${result}`: '';
}
console.log(urlParams({qwe: undefined, txt: 'asd'})) // '?txt=asd'
console.log(urlParams({qwe: undefined})) // ''