PDA

View Full Version : [CLOSED] [MVC] namespace issue I think



pkellner
Sep 20, 2008, 10:25 PM
Using 0.5.4, I'm trying to define a listener on a panel that calls back into the Ext namespace. I'm trying to follow the pattern in the 0.6 svn Grid3Example. I'm getting Ext.centerPanelResize is not defined. I think it has to do with Ext.onReady called by Coolite no seeing my namespace but not sure.

Here is the javascript, and the relevant part of my page







/// <reference path="../../../Content/ExtJS/adapter/ext/ext-base.js" />


/// <reference path="../../../Content/ExtJS/ext-all-debug.js />


/// <reference path="../../../VEJS/VeJavaScriptIntellisenseHelper.js" />


var map;


if (typeof (DESIGN_TIME) == 'undefined') {


map = null;


} else {


map = new VEMap();


}


Ext.threepe = function() {


return {


centerPanelResize : function(el, adjWidth, adjHeight, rawHeight, rawWidth) {


if (map != undefined) {


map.Resize(adjWidth, adjHeight);


}


},


init: function() {


var s = new ZipCodeList();


s.getZipCodes('IL', 50, DataRetrieved);





}


};


} ();


Ext.onReady(Ext.threepe.init, Ext.threepe);


----------------------------------------------------------------------------------



<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">


<ext:ScriptManager ID="ScriptManager2" runat="server" CleanResourceUrl="false" ScriptMode="Debug"


/>


truck/available


<script type="text/javascript" src='http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.1'>


</script>


<%-- Nice URL trick: http://lancefisher.net/blog/archive/2008/03/16/referencing-javascript-in-asp.net-mvc-master-pages.aspx--%>


<script type="text/javascript" src='<%= Url.Content("~/DataHandlers/ZipCodeList.ashx") + "?proxy" %>'>


</script>


<script type="text/javascript" src='<%= Url.Content("~/Content/json.js") + "?proxy" %>'>


</script>


<script src="../../Content/Views/Truck/Available.aspx.js" type="text/javascript">


</script>


<script type="text/javascript">


// function centerPanelResize(el, adjWidth, adjHeight, rawHeight, rawWidth) {


// if (map != undefined) {


// map.Resize(adjWidth, adjHeight);


// }


// }


</script>


<div id="content">


<ext:Panel ID="Panel2" runat="server" Height="600" Title="Title">


<Content>


<ext:BorderLayout ID="BorderLayout2" runat="server">


<Center Collapsible="true">


<ext:Panel ID="Panel4" runat="server" Title="Virtual Earth Map of Locations (use splitter)">


<Content>


<div id='myMap'>






<div id='IDwaiting'>


Map Loading...






</Content>


<Listeners>


<Resize Delay="500" Handler="Ext.centerPanelResize(el, adjWidth, adjHeight, rawHeight, rawWidth);" />


</Listeners>


</ext:Panel>


</Center>

geoffrey.mcgill
Sep 22, 2008, 5:00 AM
Hi Peter,

Can you confirm that the toolkit related scripts (ext-all.js, etc..) are getting added to the Page before your <script> block and/or includes?


You might need to do one of the following:


1. Add the runat="server" attribute to the <head> tag of your Page/MasterPage. Or, 
2. Add an <ext:ScriptContainer> into the <head> of your MasterPage. If adding a ScriptContainer I don't think you need to set the runat=server" attribute on the <head> tag, although it's been a while since I tested to confirm. 


Hope this helps.

pkellner
Sep 22, 2008, 11:34 AM
My confusion is that I have two Ext.OnReady()'s going on. That is, you create one with coolite, and I have one in my own included javascript. Somehow, both are working but it makes me nervous. Seems like my stuff should be called from the Coolite OnReady init. (and yes, I confirmed the toolkit stuff is added first. my stuff is included much later).

geoffrey.mcgill
Sep 22, 2008, 11:45 AM
I think the issue is just one of scope. 

It would appear your "Panel4" is calling Ext.centerPanelResize when it resizes, although I suspect the Ext.centerPanelResize function is within a closure (Ext.onReady) and not available. Basically Ext.centerPanelResize is acting as a private function and only visible within the Ext.onReady block (or closure) where it's defined.


If you try calling Ext.centerPanelResize from even just a regular <script> block, I suspect you'll get the same error. 


You need to scope the "centerPanelResize" function to the window by defining it as "window.centerPanelResize" or "this.centerPanelResize".


Having multiple Ext.onReady functions on one Page is not a problem. 


Hope this helps.

pkellner
Sep 22, 2008, 1:51 PM
Still confused. I don't quite grock the scoping in JS very well yet.

In the aspx file, do I define the handler as below. Also, what does ScriptContainer do? Can it somehow combine js files into one download? the view/source of the page looked like the result was no different than if I just listed the includes.






<Listeners>


<Resize Delay="500" Handler="window.centerPanelResize(el, adjWidth, adjHeight, rawHeight, rawWidth);" />


</Listeners>




Or/and do I define the function in my .js file as






// need to get these out of public space


var map;


var layer;


if (typeof (DESIGN_TIME) == 'undefined') {


map = null;


layer = null;


} else {


map = new VEMap();


}


function window.centerPanelResize(el, adjWidth, adjHeight, rawHeight, rawWidth) {


if (map != undefined) {


map.Resize(adjWidth, adjHeight);


}


}

geoffrey.mcgill
Sep 23, 2008, 11:54 AM
Hi Peter,

The following post should help explain the purpose of the <ext:ScriptContainer>, see http://forums.ext.net/showthread.php?threadid=1209-16-1.aspx

I'll try to come up with a .aspx sample to demonstrate what might be going wrong with your code and how to correct.

pkellner
Sep 24, 2008, 9:36 PM
Hi Geoffrey,

Just in case you did the little example showing how to reference code from an aspx page event to a included javascript file and forgot to post it, here's a little reminder :).

Thanks

geoffrey.mcgill
Sep 25, 2008, 1:43 PM
Hi Peter,

Here's a sample demonstrating several techniques. I added a few comments to try and explain things.

This is a monster long post, but just copy/paste into a new .aspx Page and it should run.

As well, I found the followin blog posts which does a good job of explaining JavaScript scope, although not ExtJS specific, see Part1 (http://technomedia.co.uk/blog/2008/09/javascript-best-practice-part1-avoiding-scope-pollution/) and Part2 (http://technomedia.co.uk/blog/2008/09/javascript-best-practice-part-2-using-scope-effectively/).

Hope this helps.

Example


<%@ Page Language="C#" %>
<%@ Register Assembly="Coolite.Ext.Web" Namespace="Coolite.Ext.Web" TagPrefix="ext" %>


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">


<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
// Add a "show" function to the configuration <script> block
// rendered into the Page <head> element.
string fn = @"var show = function() { Window1.show(); updateLabel('Window1_Show'); }";
this.ScriptManager1.RegisterClientScriptBlock("show", fn);
}
</script>


<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
<title>Coolite Toolkit Example - Demonstrate various Component Listeners</title>

<ext:ScriptContainer ID="ScriptContainer1" runat="server" />

<style type="text/css">
/* Set some styles for the page elements */
body {
font-family : Tahoma;
}

#myDiv {
width: 1px;
height: 1px;
margin: 9px 0 0 9px;
background-color: #000;
color: #fff;
padding-top: 4px;
text-align: center;
}

#Label1 {
display:block;
padding: 8px;
font-weight: bold;
text-align:center;
border-bottom: 1px solid brown;
margin-bottom: 8px;
height: 25px;
}
</style>

<script type="text/javascript">
// Define our Namespace to attach functions and add other properties
// using Ext.namespace() so we avoid defining global objects.
Ext.namespace('CompanyX');


// Define a clearLabel function. Just resets the text of the Label.
CompanyX.clearLabel = function() {
Label1.setText('');
};


// Define a resize function to be called <Center> Panel.
CompanyX.resize = function (el) {
var div = Ext.get("myDiv"),
width = el.getInnerWidth() - 20,
height = el.getInnerHeight() - 20;


div.setOpacity(width / 800);
div.setSize(width, height, true);
updateLabel(el.id + "_Resize");
};


// Defining a 'collapse' function inside the Ext.onReady. The function
// is attached to the CompanyX object which is alread scoped at the
// window/document level and outside of the onReady function/closure.
Ext.onReady(function() {
CompanyX.collapse = function(el) {
hidden(el);
}


// Define a 'hidden' function inside the onReady closure.
// The hidden() function can not be called from outside onReady, but
// is available to functions defined within the onReady closure.
// Think of this 'hidden' function like a C# private or internal method.
var hidden = function(el) {
updateLabel(el.id + '_Collapse');
}
});


// Define simple global 'expand' function. This can be called from anywhere.
var expand = function (el) {
updateLabel(el.id + "_Expand");
}


// Define our 'updateLabel' function. Again, just a simple global function.
// The function updates the text of the Label, then highlights for two seconds,
// then fires a callback function after highlighting is done to clear the Label text.
var updateLabel = function(msg) {
Label1.setText(msg, false).getEl().highlight("ffff9c", { duration: 2, callback: CompanyX.clearLabel } );
};


</script>
</head>
<body>
<form id="form1" runat="server">
<ext:ScriptManager ID="ScriptManager1" runat="server">
<Listeners>
<%-- Define a hide function to add to the main config <script> block. --%>
<DocumentReady Fn="var hide = function(el){ updateLabel(el.id + '_Hide'); }" />
</Listeners>
</ext:ScriptManager>

<%-- Here's our main Label where we write messages to and highlight --%>
<ext:Label ID="Label1" runat="server" />


<%-- Button with client 'click' event to show the Window if hidden --%>
<ext:Button
ID="Button1"
runat="server"
Text="Show Window"
Icon="Application"
OnClientClick="show();">
<Listeners>
<%-- Add a simple <MouseOver> Listener --%>
<MouseOver Handler="updateLabel('Button1_Hover');" />
</Listeners>
</ext:Button>

<ext:Window
ID="Window1"
runat="server"
Collapsible="true"
Icon="Application"
Title="Component with Listeners"
Height="400"
Width="650">
<Content>
<ext:BorderLayout ID="BorderLayout1" runat="server">
<West
Collapsible="true"
Split="true"
MarginsSummary="5 0 5 5"
CMarginsSummary="5 5 5 5">
<ext:Panel
ID="Panel1"
runat="server"
Title="West"
Width="150"
Html="West"
BodyStyle="padding:5px;"
/>
</West>
<Center MarginsSummary="5 5 5 0">
<ext:Panel
ID="Panel2"
runat="server"
Title="Center"
Html="<div id='myDiv'>Your Map Here
">
<%-- Add a simple container to the Panel using
the Panels .Html property above. --%>
<Listeners>
<%-- Add <Resize> Listener and fire CompanyX.resize function --%>
<Resize Fn="CompanyX.resize" />
</Listeners>
</ext:Panel>
</Center>
</ext:BorderLayout>
</Content>
<Listeners>
<%-- Simple <Collapse> Listener which calls the collapse function, but
remember that the .collapse function also calls the private 'hidden'
function. You can test that .hidde() is not available by uncommentting
the following <Collapse> Listener.

<Collapse Handler="hidden(el);" /> --%>
<Collapse Handler="CompanyX.collapse(el);" />

<%-- Call the globally scoped 'expand' function. --%>
<Expand Handler="expand(el);" />

<%-- Call the 'hide' function we defined using the <ScriptManager> <DocumentReady>
configuration. The .hide function is added to the main config <script> block.
The .hide() function is available only to objects define into the same
onReady block. --%>
<Hide Handler="hide(el);" />

<%-- Simple <Move> Listener and call updateLabel passing in a new message. --%>
<Move Handler="updateLabel(el.id + '_Move');" />
</Listeners>
</ext:Window>
</form>
</body>
</html>

geoffrey.mcgill
Sep 26, 2008, 8:19 AM
Did you get your js functions firing properly?

One other thing I think I forgot to mention is that depending on the Control you're using the <Resize> Listener might fire during the initial rendering process if the control needs to resize itself. 


If you've attached a <Resize> Listener, and that Listener calls some external custom function, then that function must be defined before the Control renders. Adding your custom function inside an Ext.onReady block might prevent the function from being created in time for the calling control.


Hope this helps. 

pkellner
Sep 30, 2008, 1:53 AM
Well, sorry for being dense here. I've read the 2 documents and your example shows lots of cool stuff, but I just can't seem to get what I'm wanting. Basically, what I'm wanting to do for now is create the datastore with the aspx page and javascript code embedded in the page, then use that datastore in a .js file. That is, have the ExtJS stuff directly without actually using the ext:grid control.

I bundled up a simple project (see Default.aspx and Default.aspx.js) which obviously doesn't work, but if you could show me the magic lines of code to make it work, I'd be very appreciative. I'm slowly bending my mind around JavaScript but it's not coming easy yet.

Thanks.

geoffrey.mcgill
Sep 30, 2008, 6:40 AM
Hi Peter,

Thanks for the code samples demonstrating what you are attempting to do. They helped explain the problem.

In order to get the sample running I had to do a little clean-up and move things around a bit. I think the only real gotcha is that you need to ensure the <script> files are being loaded in the correct order. Your "Default.aspx.js" would need to be included after the required Ext and Coolite <script> files load.

The easiest way to ensure your <script>'s are loaded after the Toolkit <script>'s is to add a <ext:ScriptContainer> into the <head> of your Page. The <ext:ScriptManager> will then insert all the required scripts at the location of the <ext:ScriptContainer>.

An important point to remember with JavaScript is that "A" must be loaded/created before "B", if "B" needs "A".

Anyways, I'm including your revised "Default.aspx" and "Default.aspx.js" below. Compare the before and after, and let me know if you have any questions.

Hope this helps.

Default.aspx


<%@ Page Language="C#" %>


<%@ Register Assembly="Coolite.Ext.Web" Namespace="Coolite.Ext.Web" TagPrefix="ext" %>


<script runat="server">
protected void Store1_RefreshData(object sender, StoreRefreshDataEventArgs e)
{
ObjectDataSource1.SelectParameters["start"].DefaultValue = e.Start.ToString();
ObjectDataSource1.SelectParameters["limit"].DefaultValue = e.Limit.ToString();
ObjectDataSource1.SelectParameters["sort"].DefaultValue = e.Sort;
ObjectDataSource1.SelectParameters["dir"].DefaultValue = e.Dir.ToString();

Store1.DataBind();
}


protected void ObjectDataSource1_Selected(object sender, ObjectDataSourceStatusEventArgs e)
{
(this.Store1.Proxy[0] as DataSourceProxy).TotalCount = (int)e.OutputParameters["count"];
}


protected void GridPanel1_RowSelect(object sender, AjaxEventArgs e)
{
string record = e.UserParams["Record"];
}
</script>


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">


<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Custom GridPanel Script</title>
<ext:ScriptContainer ID="ScriptContainer1" runat="server" />
<script src="Default.aspx.js" type="text/javascript"></script>
</head>
<body>
<form id="form1" runat="server">
<ext:ScriptManager ID="ScriptManager1" runat="server" />

<asp:ObjectDataSource
ID="ObjectDataSource1"
runat="server"
OnSelected="ObjectDataSource1_Selected"
SelectMethod="GetMembersFilter"
TypeName="BO.BusinessObject" >
<SelectParameters>
<asp:Parameter Name="start" DefaultValue="0" Type="Int32" />
<asp:Parameter Name="limit" DefaultValue="3" Type="Int32" />
<asp:Parameter Name="sort" DefaultValue="" />
<asp:Parameter Name="dir" DefaultValue="" />
<asp:Parameter Name="count" DefaultValue="" Direction="Output" Type="Int32" />
</SelectParameters>
</asp:ObjectDataSource>

<ext:Store
ID="Store1"
runat="server"
DataSourceID="ObjectDataSource1"
AutoLoad="true"
RemoteSort="true"
OnRefreshData="Store1_RefreshData">
<AutoLoadParams>
<ext:Parameter Name="start" Value="={0}" />
<ext:Parameter Name="limit" Value="={3}" />
</AutoLoadParams>
<Proxy>
<ext:DataSourceProxy />
</Proxy>
<Reader>
<ext:JsonReader>
<Fields>
<ext:RecordField Name="Name" />
<ext:RecordField Name="Email" />
<ext:RecordField Name="LastName" />
<ext:RecordField Name="CreateDate" Type="Date" />
</Fields>
</ext:JsonReader>
</Reader>
<Listeners>
<LoadException Handler="var e = e || {message: response.responseText}; alert('Load failed: ' + e.message);" />
</Listeners>
</ext:Store>

<!-- Manually add an empty to hold our custom GridPanel -->
<div id="myCustomGrid">


</form>
</body>
</html>

Default.aspx.js


Ext.namespace('PeterKellnerSpace');


Ext.onReady(function() {
this.GridPanel1 = new Coolite.Ext.GridPanel({
id: "GridPanel1",
renderTo: "myCustomGrid",
width: 600,
height: 350,
title: "Employees",
frame: true,
bbar: new Ext.PagingToolbar({
id: "PagingToolBar1",
xtype: "paging",
store: Store1,
pageSize: 3,
displayInfo: true
}),
loadMask: {
showMask: true
},
store: Store1,
cm: new Ext.grid.ColumnModel({
proxyId: "ColumnModel1",
columns: [{
id: "Name",
width: 150,
sortable: true,
header: "Name",
dataIndex: "Name"
},
{
width: 150,
sortable: true,
header: "Email",
dataIndex: "Email"
},
{
width: 110,
sortable: true,
renderer: Ext.util.Format.dateRenderer('Y-m-d'),
header: "Create Date",
dataIndex: "CreateDate"
}]
})
});
});

pkellner
Sep 30, 2008, 12:07 PM
Thanks again Geoffrey,

You example worked great. I'm still somewhat confused though. I moved the javascript around (see attached) so that it is loaded after the core libraries (or so my view/source tells me) and it still works.

I'm also thinking a key assumption I had made was wrong, and that was that if you have two separate "Ext.onReady(function(){" declarations that they would not share scope with each other. That seems obviously wrong because this example now works.

Again, your solution is much appreciated.

pkellner
Sep 30, 2008, 12:12 PM
correction, I just moved my Default.aspx.js back to the head as you suggest and the order is what you suggested. It's just the scoping I don't get.

Thanks,

geoffrey.mcgill
Sep 30, 2008, 12:39 PM
Thanks for the update. I'm marking this thread as [CLOSED].

The "Store" object is scoped to the document level when created because we use the "this." specifier infront of the object name. The "this" is the "docement".


All the controls in the Toolkit are instantiated and added to the "document", which is basically the "Page". Once scoped to the document level, they become available to everything lower down. The following diagram may help with visualizing things. 


Diagram


             document
                    |
                    |
       -----------------------
       |                           |
onReady1            onReady2


If an object is scoped to the "document" level, then it's available to all levels below, but it does not work in reverse. If an object is scoped to the "onReady2" level, then it's not available higher up the tree, or across. Googling around for information about "javascript closure" might help bring up some good articles. 


Hope this helps.

pkellner
Sep 30, 2008, 12:47 PM
That's a big help. The part 1 of the JS article you forwarded now makes more sense. Clearly, I need to become one with JavaScript scoping. You post above helps with that.

Definitely, I concur with marking this thread solved (and, with a 10 out of 10! but I don't see a place to put that).