Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations gkittelson on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Access JavaSript object properties with invalid names from VFP 1

Status
Not open for further replies.

Olaf Doschke

Programmer
Oct 13, 2004
14,847
DE
I am currently working on an API involving the necessity to go through JS functions I load into a Webbrowser control, as the endpoint API is using things like postMessage JS, which need a JS eventlistener.

I can get back a JS parameter "event" and Intellisnse shows me properties and subobjects and their properties, but I get error "Unknown Name" when I access properties with uppercase letters and properties of second or deeper levels no matter how the names are composed.

I found one solution in using a JS function "flattenobject" from and modified this to use '_' instead of '.' and create lower case names only. That still risks one problematic case where the JS object has a property oobject.test.property and object.test_property causing a conflict when test.property is added as the already existing test_property.

Besides solving that with a more complex replacement I remember there should be an easy solution, as JS objects also can be accessed with bracket notation in JS: object.['test.property'], but doing so with the object arriving in Foxpro isn't working anymore. I've seen a case where this was about native names invalid for VFP naming conventions, but I don't find that, it would be easier than fiddling with any reworking of JS objects before returning them to VFP.

EDIT: To illustrate the problem some reprocode:

Code:
Local loJSForm, lcScript, loDocument, loScript, loText, lcJSON

loJSForm = CreateObject('jsform')

Text To lcScript NoShow
   function json2vfp(json, vfpreceiver) {
      vfpreceiver.oInbox = JSON.parse(json)
   }
EndText

loDocument = loJSForm.oWeb.Document
loScript = loDocument.createElement('script')
loScript.setAttribute('type', 'text/javascript')
loText = loDocument.createTextNode(lcScript)
loScript.appendChild(loText)
loDocument.documentElement.appendChild(loScript)

Text To lcJSON NoShow
{ "workingproperty": true,
  "nonWorkingProperty": false,
  "obj": { "notatallWorkingProperty" : false,
           "sosoworkingproperty": true
         }
}
EndText

Clear 
On Error ?? Line(), Message()
loDocument.Script.json2vfp(lcJSON, loJSForm)

Define Class jsform As Form
   Add Object oWeb As cWeb

   oInbox = .Null.

   Procedure oInbox_assign(vNewVal)
      This.oInbox = vNewVal
           
      ? 'workingproperty', vNewVal.workingproperty
      ? 'nonWorkingProperty', vNewVal.nonWorkingProperty
      ? 'obj.notatallWorkingProperty', vNewVal.obj.notatallWorkingProperty
      ? 'obj.sosoworkingproperty', vNewVal.obj.sosoworkingproperty
   Endproc
Enddefine

Define Class cWeb As OleControl
   OleClass="Shell.Explorer.2"

   Procedure Init()
      This.navigate2("about:blank")
      Do While This.oWeb.readyState<>4
         DoEvents Force
      Enddo
   Endproc

   Procedure Refresh()
      Nodefault
   Endproc
Enddefine

To clarify: In this case, I could prevent the errors by simply defining all property names in lower case, but in the real world scenario the names of the JS properties are not in my control, they are partially native JS properties and partially added from the Restful API I use.

Also note: The 'sosoworkingproperty' can be read and is printed without error, but if you set a breakpoint in the oInbox_assign method and use intellisense to address the properties, you get obj listed when you type [tt][highlight #FFFFFF]vNewVal.[/highlight][/tt], but then [tt][highlight #FFFFFF]vNewVal.obj.[/highlight][/tt] is not telling you, that the sosoworkingproperty exists.

Bye, Olaf.

Olaf Doschke Software Engineering
 
Hi Olaf, I came across the same issue about three years ago, and this StackOverflow thread helped me to solve partially the problem with this function:

JavaScript:
function namespace(object, path) {
   var result = path.split('.').reduce(function (value, index) {
        return value[index]
    }, object)
   return result;
}

I needed array support, so I improved it and posted a working sample on that same thread:

JavaScript:
  getProperty(obj, path) {
    return path.split(/(\[|\]|\.)/).reduce(function (x, y) {
        return ('[].'.indexOf(y) > -1) ? x : (x === Object(x) && y in x) ? x[y] : undefined;
    }, obj)
}


this is your test using getProperty() function :

Code:
clear

Local loJSForm, lcScript, loDocument, loScript, loText, lcJSON

loJSForm = CreateObject('jsform')

Text To lcScript NoShow
   function json2vfp(json, vfpreceiver) {
      vfpreceiver.oInbox = JSON.parse(json)
   }

	function getProperty(obj, path) {
    return path.split(/(\[|\]|\.)/).reduce(function (x, y) {
        return ('[].'.indexOf(y) > -1) ? x : (x === Object(x) && y in x) ? x[y] : undefined;
    }, obj)
}

EndText

loDocument = loJSForm.oWeb.Document
loScript = loDocument.createElement('script')
loScript.setAttribute('type', 'text/javascript')
loText = loDocument.createTextNode(lcScript)
loScript.appendChild(loText)
loDocument.documentElement.appendChild(loScript)

Text To lcJSON NoShow
{ "workingproperty": "works",
  "nonWorkingProperty": "works too",
  "obj": { "notatallWorkingProperty" : "and this",
           "sosoworkingproperty": "and this one too"
         }
}
EndText

Clear 
On Error ?? Line(), Message()
loDocument.Script.json2vfp(lcJSON, loJSForm)

Define Class jsform As Form

   Add Object oWeb As cWeb

   oInbox = .Null.

   Procedure oInbox_assign(vNewVal)
      This.oInbox = vNewVal
           
      ? 'workingproperty', vNewVal.workingproperty
      ? 'nonWorkingProperty', vNewVal.nonWorkingProperty
      ? 'obj.notatallWorkingProperty', vNewVal.obj.notatallWorkingProperty
      ? 'obj.sosoworkingproperty', vNewVal.obj.sosoworkingproperty

? '---------------------------------------'
	? 'Using getproperty( obj, cPath ):'
? '---------------------------------------'
		js = this.oweb.document.script

      ? 'workingproperty', js.getProperty(vNewVal,"workingproperty")
      ? 'nonWorkingProperty', js.getProperty(vNewVal,"nonWorkingProperty")
      ? 'obj.notatallWorkingProperty', js.getProperty(vNewVal,"obj.notatallWorkingProperty")
      ? 'obj.sosoworkingproperty', js.getProperty(vNewVal,"obj.sosoworkingproperty")

   Endproc

Enddefine

Define Class cWeb As OleControl
   OleClass="Shell.Explorer.2"

   Procedure Init()
      This.navigate2("about:blank")
      Do While This.readyState<>4
         DoEvents Force
      Enddo
   Endproc

   Procedure Refresh()
      Nodefault
   Endproc
Enddefine

Hope this will solve your problem too.


 
Thanks, Marco!

I remember something else, but this works, too.
I added the function to the object, though, so it becomes an inbuilt function I can call in VFP.

Code:
Local loJSForm, lcScript, loDocument, loScript, loText, lcJSON

loJSForm = Createobject('jsform')

Text To lcScript NoShow
   function json2vfp(json, vfpreceiver) {
      var jsobj = JSON.parse(json);

      jsobj.getproperty =
         function (path) {
            return path.split(/(\[|\]|\.)/).reduce(
               function (obj, key) {
                   return (obj === Object(obj) && key in obj) ? obj[key] : ('[].'.indexOf(key) > -1) ? obj : undefined;
               }, this)
         };
      jsobj.json = JSON.stringify(jsobj , undefined, 3);
      vfpreceiver.oInbox = jsobj;
   }
EndText

loDocument = loJSForm.oWeb.Document
loScript = loDocument.createElement('script')
loScript.setAttribute('type', 'text/javascript')
loText = loDocument.createTextNode(lcScript)
loScript.appendChild(loText)
loDocument.documentElement.appendChild(loScript)

Text  To lcJSON NoShow
{ "workingproperty": true,
  "nonWorkingProperty": true,
  "obj": { "notatallWorkingProperty" : true,
           "sosoworkingproperty": true,
           "arr": [1,2,3]
         }
}
EndText

Clear 
On Error ?? Line(), Message()
loDocument.Script.json2vfp(lcJSON, loJSForm)

Define Class jsform As Form
   Add Object oWeb As cWeb

   oInbox = .Null.

   Procedure oInbox_assign(vNewVal)
      This.oInbox = vNewVal
      ? vNewVal.json
      
      ? 'workingproperty', vNewVal.workingproperty
      ? 'nonWorkingProperty', vNewVal.nonWorkingProperty
      ? 'obj.notatallWorkingProperty', vNewVal.obj.notatallWorkingProperty
      ? 'obj.sosoworkingproperty', vNewVal.obj.sosoworkingproperty
      ? '----------------------fixed with added function-------------------'
      ? 'workingproperty', vNewVal.getproperty('workingproperty')
      ? 'nonWorkingProperty', vNewVal.getproperty('nonWorkingProperty')
      ? 'obj.notatallWorkingProperty', vNewVal.getproperty('obj.notatallWorkingProperty')
      ? 'obj.sosoworkingproperty', vNewVal.getproperty('obj.sosoworkingproperty')
      ? 'obj.arr[0]', vNewVal.getproperty('obj.arr[0]')

   Endproc
Enddefine

Define Class cWeb As OleControl
   OleClass="Shell.Explorer.2"

   Procedure Init()
      This.navigate2("about:blank")
      Do While This.readyState<>4
         DoEvents Force
      Enddo
   Endproc

   Procedure Refresh()
      Nodefault
   Endproc
Enddefine

Bye, Olaf.

Olaf Doschke Software Engineering
 
I just thought, why not use eval()? Yes, it's evil, but it works:

Code:
...
  jsobj.getproperty =
     function (expr) { 
        return eval('this.'+expr);
     };

Bye, Olaf.

Olaf Doschke Software Engineering
 
Eval is like you say 'evil' if you use it to run Js not totally created by you.

If you are in total control of the source I see no reason not to use it.






Marco Plaza
@nfoxProject
 
It's all under my control, I indeed have no worries: The whole construct is... let's say interesting. The code you have to use is doing a popup that calls an API endpoint returning a html form, which in turn returns the final result after user interaction via a window.opener.postMessage(), that comes in as event.data in an event listener from the opener window you have to program from template code only halfways documented. WEll, at least they also provide an API sandbox that you can also look into, but as it is with such APIs, the template code is too basic and the sandbox is too complex, but at least I managed to extend the template code with the sandbox to something working now.

Your nfJson library would be another choice when I'd turn event.data into JSON via JSON.stringify() and then use nfJson to turn that into a real VFP object. But this mechanism couldn't pass anything that provides methods and not just some data properties.

Bye, Olaf.



Olaf Doschke Software Engineering
 
There's just one minor issue with the getproperty function, no matter if it's the eval or your function and I guess also in general, when you suspend the receiving code and do intellisense on the function, i.e. go into the command window and type [tt][highlight #FFFFFF]vNewVal.getproperty([/highlight] [/tt] VFP intellisense kicks in as it always does triggered by the opening bracket trying to get the method signature. It fails with a fatal error c0000029.

I leave this here as a warning because this could mean lost work. It's reproducable, yet the calls with expressions work even when you want to know something like the array length you can do vNewVal.getproperty('obj.arr.length'), that's working with eval.

So instead of discovering object properties with intelllisense, you better make use of the json property I added to get an overview of the object schema and available property names.

I wonder if this has to do with the anonymous nature of the function and if the getproperty function can be added to the jsobj so VFPs intellisense does not error. I guess it fails as you can't define parameter types. But it's only an intellisense problem, as said. The getproperty function doesn't even error on undefined properties, it only returns null, in your code you've forseen this with 'undefined', but this also comes back when you use eval('this.'+expr), so overall this is stable at runtime.

Bye, Olaf.

Olaf Doschke Software Engineering
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top