Forum Moderators: open

Message Too Old, No Replies

creating an object property (and assigning it a value)

         

Stampede

5:11 am on Apr 11, 2005 (gmt 0)

10+ Year Member



The last line of this function isn't working. I'm at a complete loss as to why.

function parseXML(oLoadedXML, sKey) {
var i, j, oRawData, oRecord;
aTableData = new Array( );
oRawData = oLoadedXML.getElementsByTagName(sKey);
for (i=0; i<oRawData.length; i++) {
oRecord = aTableData[aTableData.length] = new Object( );
for (j=0; j<oRawData[i].childNodes.length; j++) {
if (oRawData[i].childNodes[j].nodeType!=1) continue;
oRecord['oRawData[i].childNodes[j].nodeName'] = oRawData[i].childNodes[j].firstChild.nodeValue;
}
}
}

If I place an alert directly after the last line:
alert(oRawData[i].childNodes[j].nodeName + ' ' + oRawData[i].childNodes[j].firstChild.nodeValue);
The alert correctly shows me all the headers and their assigned values.

But if I move the alert one brace down and replace it with:
alert(oRecord.Author);
I get a value of undefined for each record.

This also shows as undefined:
alert(oRecord.length);

The reason I think this should work is that I created a test using a similar technique which works fine:

var myCar = new Object;
myCar.make = 'Ford';
myCar['model'] = 'Aspire';
document.write(myCar.make + ' ' + myCar.model);

Bernard Marx

8:02 am on Apr 11, 2005 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



oRecord['oRawData[i].childNodes[j].nodeName'] = oRawData[i].childNodes[j].firstChild.nodeValue;

Remove the quotes around the expression:

oRawData[i].childNodes[j].nodeName
, otherwise it will be used as a literal string for the memberName, instead of its actual value.

Stampede

8:45 am on Apr 11, 2005 (gmt 0)

10+ Year Member



OMG that worked! I have to say you're incredibly knowledgable. I was expecting at most a couple tips that could point me in the right direction, but you knew precisely where the problem lay.

Although I'd tried that once, I must have broken something else and I probably wouldn't have even thought to go back and try that again. I have about 100 versions of my script in my directory after trying various ways of fixing it. I was getting pretty desperate because I couldn't find any working examples of this technique on the net. People don't seem to load values from DOM objects into associated arrays I guess. Personally, I think this is a perfect way to perform operations (sorting, filtering, formatting, etc) on tabular data without ever having the headache of referring back to the loaded DOM object.

Thank you very much for your help.

Bernard Marx

10:41 am on Apr 11, 2005 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



function parseXML(oLoadedXML, sKey) 
{
var i, j, oRawData, oRecord;
var aTableData = new Array( );
var dataElms = oLoadedXML.getElementsByTagName(sKey);
var dataElm, dataChildren, dataChild, oRecord, i=0, j;
while(dataElm=dataElms[i++]) {
oRecord = aTableData[aTableData.length] = new Object( );
dataChildren = dataElm.childNodes;
j=0;
while(dataChild=dataChildren[j++]){
if (dataChild.nodeType!=1) continue;
oRecord[dataChild.nodeName] = dataChild.firstChild.nodeValue;
}
}
return aTableData;
}

Just for kicks, I have tweaked the function a little (hopefully I haven't slipped up somewhere).

I made all the variables local, and made the function return the required array, rather than it being assigned to a global var inside the function (always feels suspect to me).

I also used my preferred form of iteration for element collections (or any form of collection that is guaranteed not to have members that have false boolean evaluations).
The only real point was cutting down on repeated use of the reference chain:

oRawData[i].childNodes[j].

It's all just trimming really.

I am worried about one thing though. If any set of 'dataChildren' has more than one element with the same nodeName, then some information will be lost, since they are being attached to the object via that name.

I'm not quite sure why you don't find many examples of this. It can certainly make things a little easier from a Javascript point of view. Perhaps it's because of the resulting duplication of information. It may be better to create a 'wrapper' for the DOM object. That way, all the info stays in one place, but you can access it however you like.

Stampede

9:09 pm on Apr 11, 2005 (gmt 0)

10+ Year Member



I don't have a lot of time to look through your example right now, but I believe I'll have some time to try it out tonight. At a brief glance though, I notice a couple issues.

1) The first line of the function is redundant. Pretty easy for me to take care of a minor typo like that.

2) Returning aTableData instead of using it as a global variable might be problematic because I actually need to return 2 things to the calling function and i'm not sure how to do that. To explain, I actually stripped a couple lines of code from my example to make it more readable for people to figure out the problem I was having. My function not only needed to collect the table's field data, but I also needed to collect an array of the field names to use with my <TH> elements when drawing my table.

One method that would work using your code is to write a function to collect the property names from one of the objects stored in aTableData that I can call when I go to draw the table.
Another method is if it were possible to return more than one thing. I haven't tried it, but I'm thinking it might go something like return x, y;.

As far as the wrapper concept, it certainly seems like it could potentially be the best solution with an object-oriented language like this. However I'm nowhere near figuring out how to do that yet. I just started programming this as a hobby project a little over a week ago, and this is the first program I've written in 15 years (when I'd taken a couple college classes with programming in Ada).

A couple things I noticed about JavaScript that I really would like to see is the ability to declare constants (that looks like it's an upcoming feature in JavaScript 1.5c) and the ability to control the scope of a variable a little better. For example, I'd often like to make a variable available to a function (and have it's value propigated to all it's sibling functions) without having to make it a global variable or having to deal with combersome code to work around this limitation.

BTW, here's what my code looked like with the header grab in place:

function parseXML(oLoadedXML, sKey) {
var i, j, k, oRawData, oRecord;
aTableData = new Array( );
aTableHeaders = new Array( );
oRawData = oLoadedXML.getElementsByTagName(sKey);
// Start grabbing header list
k=0;
for (i=0; i<oRawData[0].childNodes.length; i++) {
if (oRawData[0].childNodes[i].nodeType!=1) continue;
aTableHeaders[k++] = oRawData[0].childNodes[i].nodeName;
}
// End grab
for (i=0; i<oRawData.length; i++) {
oRecord = aTableData[aTableData.length] = new Object( );
for (j=0; j<oRawData[i].childNodes.length; j++) {
if (oRawData[i].childNodes[j].nodeType!=1) continue;
oRecord[oRawData[i].childNodes[j].nodeName] = oRawData[i].childNodes[j].firstChild.nodeValue;
}
}
}

Stampede

3:07 am on Apr 12, 2005 (gmt 0)

10+ Year Member



I gave your function a go. It didn't generate any JavaScript errors which is good. However it's not executing all the way through. For example, I added alert(dataChild.firstChild.nodeValue) just after the last line in the last while loop, however it never gets executed. Something must be faulty in the conditions for the while loops.

Both our functions were actually designed to accomplish the same number of iterations and the only tangible difference is the use of a for loop vs a while loop. Cleaning up the code for appearance sake is good for maintainability though so you have a valid point. I think simply assigning oRawData[i].childNodes[j] from my original function into a variable named dataChild (as from your function) could go a long way in cleaning up the appearance. I was going for performance over maintainability earlier, by not having to create another variable and then assign it new values each time through the inner loop. However, this isn't a speed critical place as I only load the XML document once. After that everything is done from my array. So I can easily afford the negligible performance hit here to ensure better readability as you suggest.

BTW, I thought about grabbing the header information (from the illustration in my last post) to the innermost loop because it would take less code and look a bit nicer. After consideration I found this to be a bad idea considering the amount of overhead an additional condition placed at that location would generate when processing excessivly large data files. My current implementation simply grabs all the headers it needs off the first data record and never has to process after that.

Bernard Marx

10:18 pm on Apr 12, 2005 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



I actually need to return 2 things to the calling function and i'm not sure how to do that.

More than one thing can't be returned from a function, of course, because it is a function. The only reason not to assign global variable values inside a function is that it reduces flexibility, and probably goes against some programming principle or other. But, for ease of use, and given that you know what you're doing, there's no reason not to do things however you please. In the situation I think you are in, I would probably, on second thoughts, do the same as you are doing.

Just for interest, a solution to the 'multiple return' problem might go like this:

an array

function halfAndDouble(n)
{
return [n/2, n*2];
}

var numbers = halfAndDouble(4)
// values held in numbers[0], numbers[1]

an object, for ease of development.

function halfAndDouble()
{
return {half: n/2, double: n*2 }
}

var numbers = halfAndDouble(4)
// values held in numbers.half, numbers.double

the only tangible difference is the use of a for loop vs a while loop.

Indeed. I shouldn't have introduced the while loop, as it is (for the moment) obscuring the main point...

Cleaning up the code for appearance sake is good for maintainability though so you have a valid point. I think simply assigning oRawData[x].childNodes[j] from my original function into a variable named dataChild (as from your function) could go a long way in cleaning up the appearance. I was going for performance over maintainability

Actually the point of using intermediary variables is for both readability andefficiency. I think the general rule is that time is saved if you are dereferencing a collection member more than once in a loop. Certainly, if there is a nested loop, it is worth putting the result of the outer loop into a variable for the inner loop to work on.

Javascript arrays aren't like those in Java, C etc. They don't imply any actual organization in lower-level storage, being rather just hashes that have numerical keys. So it's good to keep hold of a member once it has been referenced. This appears to be even 'more true' when it comes to DOM collections.

Here's a part of the latest function posted, with intermediary variables worked in. I'm hoping that it's both (a little) more legible, and (a little) more efficient.

for (i=0; i<oRawData.length; i++) { 
oRecord = aTableData[aTableData.length] = new Object( );
dataChildren = oRawData[i].childNodes;
for (j=0; j<dataChildren.length; j++) {
dataChild = dataChildren[j]
if (dataChild.nodeType!=1) continue;
oRecord[dataChild.nodeName] = dataChild.firstChild.nodeValue;
}
}

As far as the wrapper concept, it certainly seems like it could potentially be the best solution with an object-oriented language like this.

I'm going to contradict myself. It may not be worth it here, considering the simplicity of the task, and it's very specific nature. A way of interfacing with the object could built using functions instead (as now). This way each function doesn't need to constantly refer back to the wrapper's 'subject'. Creating a wrapper could be just a sldgehammer that only cracks one nut.

A couple things I noticed about JavaScript that I really would like to see is the ability to declare constants (that looks like it's an upcoming feature in JavaScript 1.5c)

Javascript a la Mozilla already supports these (

constant
operator). Not JScript though. I don't know what the situation is there. Seeing as MS are already doing JS v2 with JScript.NET, I don't know whether 'classic' JScript is going to be developed further.

For example, I'd often like to make a variable available to a function (and have it's value propigated to all it's sibling functions) without having to make it a global variable or having to deal with combersome code to work around this limitation.

Interesting. The standard approach is to go further into OO. All functions in the same 'family' are either methods of an object type that needs instantion; or in a simpler case, one basic Object object could be used:

var family =
{
a: 1,
b: 2,

func1: function(n){
return this.b = 2*n + this.a;
},

func2: function(){
return this.a = this.a+this.b;
}
}

alert(family.func1(2));
alert(family.func2( ));
alert(family.func1(2));

However, there is another approach that I've been working on. The only real benefit is that the code in the functions doesn't need 'this' liberally splashed around, and that the functions can be called directly.. but it's fun.

(function(){

var a = 1;
var b = 2;

func1 = function(n){
return b = 2*n + a;
}

func2 = function(){
return a = a+b;
}

})()

alert(func1(2));
alert(func2());
alert(func1(2));

Stampede

5:13 am on Apr 16, 2005 (gmt 0)

10+ Year Member



Thank you again. I found your replies to be insightful and informative. This is stuff I'll be able to use to make better-designed projects.

For example, I had no idea referencing a specific location in an array induced a behind-the-scenes hash lookup like that.

In the past couple days I've really made some new discoveries on my own too which have helped tremendously too. So much so that I've rewritten about 70% of the code in my project and have gotten it down to just over 200 lines now. Not only that, but I've made the code more generic. For example, portions of my program were hard-coded to handle creating a specific number of columns for a table. Now it can handle variable columns and is more flexible on the type of data coming in. For example, when reading in a record from an XML file I would generate an array that stored one object for each table row. The properties of that object stored the data for the td's before the next tr. Well now I additionally create an array of objects for each table column. Each object has default properties for things like sort type, css class, formatting functions for specific types of data such as numbers, etc. That way if I want to customize a specific column it's as easy as modifying properties on the object that represents that column.

As for your last example, I wouldn't have thought that would work. If a function is embedded within another function I didn't know if you could call it without calling the parent function first. Perhaps the idea that you could pass parameters to a function to distribute them to child functions and pass back the result is what you're trying to achieve. That way it acts as an intermediary to being able to call it twice with sending them a variable and then collect 2 different return values. It sure adds complexity though. I haven't tried other programming languages other than JavaScript or Ada, but I know in Ada you could have a proceedure have arguments which were in, in/out, or out. Thus the proceedure had the ability to pass multiple returns back to the calling function. I always thought that method worked out pretty nice.

Bernard Marx

2:33 pm on Apr 16, 2005 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member



Well now I additionally create an array of objects for each table column. Each object has default properties for things like sort type, css class,...

It would be interesting to have a peek at the code you're using for that (did you catch my sticky, BTW?).

Are you using 'custom types' for this?

As for your last example, I wouldn't have thought that would work. If a function is embedded within another function I didn't know if you could call it without calling the parent function first.

It's in the pudding, sir. Taste it yourself, while it's hot.

The anonymous parent function is executed from the getgo. The function definition is wrapped in, and followed by, parentheses.

(..function defn...)()

You could say it's 'self-executing' (if it wasn't misleading).

Because the, also anonymous, inner functions are assigned to global variables, a closure is formed, preserving the parent function's call object, and allowing it's variables to be accessed and changed later - but only by the two inner functions, which themselves can now be accessed from anywhere in the script.

In terms of code simplicity, I think it's the best solution for what I interpreted from your wish for a set of "sibling functions" that share common variables that aren't part of the global namespace.

If the parent function was defined, and called like this..

function parent()
{
..child fns..
}
parent()

..then it could be executed more than once, but that's another story.