PDA

View Full Version : BorderLayout scope issues depending on Javascript OO Syntax



brett
2 Feb 2007, 4:56 PM
We're using lots of YUI and yui-ext at work, and we're finally implementing BorderLayout for some projects. But, I've run into a scope issue that only surfaces with a prototype-based approach to OO-Javascript.

Firebug has been a big help, but even this seems to be beyond the reach of a good code-stepping.

I've put together a few test files to illustrate the issue. First is the HTML. Then, there are two flavors of Javascript I use for the custom piece. The first is a prototype approach, and the second uses the approach from the yui-ext examples, but slightly modified to be a bit more OO-friendly.

I've tested this by first using the yui-ext example flavor, and found that BorderLayout works as expected. Then, I swtiched to the prototype flavor, and Firebug kindly tells me that "this.el has no properties."

I suspect that in the prototype flavor, the document object is out of scope because document.body is null. Working it over with Firebug hasn't given me any clues otherwise.

Any help, insight or feedback is greatly appreciated.

index.html


<html>
<head>
<title>yui-test</title>
<link rel="stylesheet" type="text/css"
href="yui-ext/resources/css/yui-ext.css" />
<script type="text/javascript" src="yui/build/yahoo/yahoo-min.js"></script>
<script type="text/javascript" src="yui/build/event/event-min.js"></script>
<script type="text/javascript" src="yui/build/dom/dom-min.js"></script>
<script type="text/javascript" src="yui/build/dragdrop/dragdrop-min.js"></script>
<script type="text/javascript" src="yui-ext/yui-ext.js"></script>
<script type="text/javascript" src="layout.js"></script>
</head>

<body>
<div id="north">North</div>
<div id="west">West</div>
<div id="center">Center</div>
<div id="east">East</div>
<div id="south">South</div>
</body>
</html>


layout1.js - prototype flavor


function DefaultLayout() {
this.init();
}

DefaultLayout.prototype = {

_layout: null,

init: function() {
if (this._layout) { return; }

this._layout = new YAHOO.ext.BorderLayout(document.body, {
north: {},

east: {
split: true,
initialSize: 200,
collapsible: true,
minSize: 100,
maxSize: 400
},

south: {},

west: {
split: true,
initialSize: 200,
collapsible: true,
minSize: 100,
maxSize: 400
},

center: {
autoScroll: true
}
});
},

initLayout: function() {
this._layout.beginUpdate();
this._layout.add('north', new YAHOO.ext.ContentPanel('north'));
this._layout.add('east', new YAHOO.ext.ContentPanel('east'));
this._layout.add('south', new YAHOO.ext.ContentPanel('south'));
this._layout.add('west', new YAHOO.ext.ContentPanel('west'));
this._layout.add('center', new YAHOO.ext.ContentPanel('center', { fitToFrame: true }));
this._layout.endUpdate();
}
};

var layout = new DefaultLayout();

YAHOO.ext.EventManager.onDocumentReady(layout.initLayout,
layout, true);


layout2.js - yui-ext flavor


DefaultLayout = function() {
return {
_layout: null,
init: function() {
this._layout = new YAHOO.ext.BorderLayout(document.body, {
north: {},

east: {
split: true,
initialSize: 200,
collapsible: true,
minSize: 100,
maxSize: 400
},

south: {},

west: {
split: true,
initialSize: 200,
collapsible: true,
minSize: 100,
maxSize: 400
},

center: {
autoScroll: true
}
});

},

initLayout: function() {
if(!this._layout) {
this.init();
}

this._layout.beginUpdate();
this._layout.add('north', new YAHOO.ext.ContentPanel('north'));
this._layout.add('east', new YAHOO.ext.ContentPanel('east'));
this._layout.add('south', new YAHOO.ext.ContentPanel('south'));
this._layout.add('west', new YAHOO.ext.ContentPanel('west'));
this._layout.add('center', new YAHOO.ext.ContentPanel('center', { fitToFrame: true }));
this._layout.endUpdate();
}
}
}();

var layout = DefaultLayout;

YAHOO.ext.EventManager.onDocumentReady(layout.initLayout,
layout, true);

TommyMaintz
2 Feb 2007, 5:05 PM
...
DefaultLayout.prototype = {

_layout: null,

init: function(document) {
if (this._layout) { return; }

this._layout = new YAHOO.ext.BorderLayout(document.body, {
north: {},
...


Maybe try to delete the argument document in the init function. this overrides the default document object within the scope of the init function.

brett
2 Feb 2007, 5:18 PM
Tommy, thanks. My mistake. That was a remnant.

After removing the document argument on the init method, a new error occurs. Now, document.body is null.

TommyMaintz
2 Feb 2007, 5:28 PM
I think the problem is that you try to use document.body as the element to use for your borderlayout.
Try wrapping an element around your regions like:



<div id="container">
<div id="north">North</div>
<div id="west">West</div>
<div id="center">Center</div>
<div id="east">East</div>
<div id="south">South</div>
</div>


and use
this._layout = new YAHOO.ext.BorderLayout('container',...

That will fix the problem probably.

brett
2 Feb 2007, 5:39 PM
One would think that wrapping the elements and replacing document.body with the container id would work.

The sad truth is that it doesn't. Firebug reports the same error as with using document.body, only this time, it's not as obvious going into the BorderLayout contructor that something is wrong.

Here's the Firebug console output:



this.el has no properties
LayoutManager("container")yui-ext.js (line 669)
BorderLayout("container", Object north=Object east=Object south=Object west=Object)yui-ext.js (line 718)
init()layout.js (line 34)
DefaultLayout()layout.js (line 2)
[Break on this error] YAHOO.ext.LayoutManager=function(container){YAHOO.ext.LayoutManager.superclass.c...

TommyMaintz
2 Feb 2007, 5:54 PM
Could you try to get the document element and console log it, like this?

[code[
init: function() {
if (this._layout) { return; }

var tmp = Ext.get(document.body);
console.log(tmp)
this._layout = new YAHOO.ext.BorderLayout(tmp, {
[/code]

btw, you can remove the container if that wasnt the problem :)

brett
2 Feb 2007, 6:03 PM
Firefox console output using the temp var:



null
... original error as above ...

TommyMaintz
2 Feb 2007, 6:08 PM
Oh brett, sorry i havent thought of this before.

You cant do it the prototyping way.
This is because at the time that the prototype gets created, those elements are not in the document yet so you cant access them. This is why both document.body and the container element we tried are null. At the time the the code for the prototype gets parsed, the document is not initialized.

All functions in a prototype are created only once during page initialization. This is good because that way every time you create a new object for this class, they will all refer to the same method. And that is good because javascript has to place that method in the memory only once.

However, you will only create one object of the class DefaultLayout so using a prototype has no advantage.
The "yui-ext way" as you describe it is the best (and only) way to go in your site.

brett
2 Feb 2007, 6:14 PM
Of course!

It looks like we'll have to get used to using a new OO syntax -- the yui-ext way -- in some cases.

Thanks for all the help, Tommy.

jack.slocum
3 Feb 2007, 8:12 AM
You could do it, but you would need to remove the call to "init" in the constructor. As Tommy said, it is trying to access the body before it actually exists.

So if you called your init method onDocumentReady instead of from the constuctor (and had it in turn call initLayout), your prototyped object should work.

brett
3 Feb 2007, 8:34 AM
Thanks for the info, Jack. I suppose at this point, I should go deeper into what we're trying to do.

The problem we're facing is we'd like to implement BorderLayout in a more dynamic way. We don't know up front what will go into the center panel. But the most common case will be a nested BorderLayout.

So, we have a DefaultLayout and an InnerLayout object. Our DefaultLayout has an addCenterPanel() method that we pass our InnerLayout object to.

Of course, all of this sounds fine in terms of procedural code, but an event driven OO approach blows it all up.

By calling the init methods onDocumentReady, there's no guarantee which init will fire first - the true crux of the problem. We'd like both layouts to be initialized (not necessarily with panels yet) prior to initLayout (the adding of panels) firing.

brett
3 Feb 2007, 10:13 AM
Jack, it finally smacked me in the face. After a bit more trial and error, your solution does work. Thank you.

I've now got a parent BorderLayout with a dynamic center panel, and I'm able to nest a BorderLayout into the center panel.

For those interested, here is all the code from my working dynamically nested BorderLayout example:

index.html


<html>
<head>
<title>Dynamically Nested BorderLayout Example</title>
<link rel="stylesheet" type="text/css"
href="yui-ext/resources/css/yui-ext.css" />
<script type="text/javascript" src="yui/build/yahoo/yahoo-min.js"></script>
<script type="text/javascript" src="yui/build/event/event-min.js"></script>
<script type="text/javascript" src="yui/build/dom/dom-min.js"></script>
<script type="text/javascript" src="yui/build/dragdrop/dragdrop-min.js"></script>
<script type="text/javascript" src="yui-ext/yui-ext.js"></script>
<script type="text/javascript" src="defaultLayout.js"></script>
<script type="text/javascript" src="innerLayout.js"></script>
</head>

<body>
<div id="container">
<div id="north">North</div>
<div id="west">West</div>
<div id="center">
<div id="innerNorth">InnerNorth</div>
<div id="innerWest">InnerWest</div>
<div id="innerCenter">InnerCenter</div>
<div id="innerEast">InnerEast</div>
<div id="innerSouth">InnerSouth</div>
</div>
<div id="east">East</div>
<div id="south">South</div>
</div>
</body>
</html>


defaultLayout.js - The parent layout


function DefaultLayout() {
}

DefaultLayout.prototype = {

_layout: null,

init: function() {
if (this._layout) { return; }

this._layout = new YAHOO.ext.BorderLayout(document.body, {
north: {},

east: {
split: true,
initialSize: 200,
collapsible: true,
minSize: 100,
maxSize: 400
},

south: {},

west: {
split: true,
initialSize: 200,
collapsible: true,
minSize: 100,
maxSize: 400
},

center: {
autoScroll: true
}
});

this.initLayout();
},

addCenterPanel: function(panel) {
if (!this._layout) {
this.init();
}

this._layout.beginUpdate();
this._layout.add('center', panel);
this._layout.endUpdate();
},

initLayout: function() {
if (!this._layout) {
this.init();
}

this._layout.beginUpdate();
this._layout.add('north', new YAHOO.ext.ContentPanel('north'));
this._layout.add('east', new YAHOO.ext.ContentPanel('east'));
this._layout.add('south', new YAHOO.ext.ContentPanel('south'));
this._layout.add('west', new YAHOO.ext.ContentPanel('west'));
this._layout.endUpdate();
}
};

var defaultLayout = new DefaultLayout();

YAHOO.ext.EventManager.onDocumentReady(defaultLayout.init,
defaultLayout, true);


innerLayout.js - The nested layout


function InnerLayout(parent) {
this._parent = parent;
}

InnerLayout.prototype = {

_layout: null,
_parent: null,

init: function() {
if (this._layout) { return; }

this._layout = new YAHOO.ext.BorderLayout('center', {
north: {},

east: {
split: true,
initialSize: 200,
collapsible: true,
minSize: 100,
maxSize: 400
},

south: {},

west: {
split: true,
initialSize: 200,
collapsible: true,
minSize: 100,
maxSize: 400
},

center: {
autoScroll: true
}
});

this.initLayout();
},

initLayout: function() {
this._layout.beginUpdate();
this._layout.add('north', new YAHOO.ext.ContentPanel('innerNorth'));
this._layout.add('east', new YAHOO.ext.ContentPanel('innerEast'));
this._layout.add('south', new YAHOO.ext.ContentPanel('innerSouth'));
this._layout.add('west', new YAHOO.ext.ContentPanel('innerWest'));
this._layout.add('center', new YAHOO.ext.ContentPanel('innerCenter',
{ fitToFrame: true }));
this._layout.endUpdate();

if (this._parent) {
this._parent.addCenterPanel(new YAHOO.ext.NestedLayoutPanel(this._layout));
}
}
};

var innerLayout = new InnerLayout(defaultLayout);

YAHOO.ext.EventManager.onDocumentReady(innerLayout.init,
innerLayout, true);