Over the last 4 years, JavaScript has been critical to the majority of the web applications that I have worked on; dealing with the logical organization of said files has always been a less-than-satisfactory experience until about 2 years ago when I began using the following techniques for a easier design-time experience.
Page-Specific JavaScript Files
Let's say I have a page named foo.aspx and I have JavaScript specific to it contained in a file - I name the .js file foo.aspx.js. Then, in a base class for the pages of the application, I have the following code:
1: protected override void OnLoad(EventArgs e)
2: {
3: base.OnLoad(e);
4: AttachPageSpecificJavaScriptFile();
5: }
6:
7: private void AttachPageSpecificJavaScriptFile()
8: {
9: var appRelativeVirtualPath = string.Format("{0}.js", TemplateControl.AppRelativeVirtualPath);
10: RegisterJavaScriptFile(appRelativeVirtualPath);
11: }
12:
13: protected void RegisterJavaScriptFile(string appRelativeVirtualPath)
14: {
15: if (JavaScriptFileExists(appRelativeVirtualPath))
16: {
17: string url = ResolveClientUrl(appRelativeVirtualPath);
18: ClientScript.RegisterClientScriptInclude(url, url);
19: }
20: }
Essentially, for each page in my application, the app will look for a *.aspx.js file that matches the name of the page (in our example, foo.aspx.js) and place, within the rendered page, a script tag referencing it.
Since every request to a given page would need to check to see whether or not an associated .js file existed, and considering the fact that checking with the file system each time would be somewhat costly, I have the following code take care of checking and then caching the results at the application level:
1: private Dictionary<string, bool> JavaScriptFileRegistry
2: {
3: get
4: {
5: var javaScriptFileRegistry = Application["JavaScriptFileRegistry"] as Dictionary<string, bool>;
6: if (javaScriptFileRegistry == null)
7: {
8: javaScriptFileRegistry = new Dictionary<string, bool>();
9: Application["JavaScriptFileRegistry"] = javaScriptFileRegistry;
10: }
11: return javaScriptFileRegistry;
12: }
13: }
14:
15: protected bool JavaScriptFileExists(string appRelativeVirtualPath)
16: {
17: var registry = JavaScriptFileRegistry;
18: if (registry.ContainsKey(appRelativeVirtualPath) == false)
19: {
20: registry.Add(appRelativeVirtualPath, File.Exists(Server.MapPath(appRelativeVirtualPath)));
21: }
22: return registry[appRelativeVirtualPath];
23: }
To complete this, I have a registry hack that will cause any *.aspx.js files to collapse underneath the *.aspx page in the solution explorer of Visual Studio (i.e. it will hide underneath the page, just like the *.aspx.cs file does). Depending on the version of Visual Studio you are using, the registry hack is different. Here are a couple that I use with Windows XP (I don't know if they differ for Vista because I don't use Vista) - copy each one into a text file and rename it with a .reg extension, then execute the file.
Visual Studio 2005
1: Windows Registry Editor Version 5.00
2: [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\8.0\Projects\{E24C65DC-7377-472b-9ABA-BC803B73C61A}\RelatedFiles\.aspx\.js]
3: @=""
Visual Studio 2008
1: Windows Registry Editor Version 5.00
2: [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\Projects\{E24C65DC-7377-472b-9ABA-BC803B73C61A}\RelatedFiles\.aspx\.js]
3: @=""
You will probably need to reboot your machine before these take effect. Also, the nesting will only take place for newly-added .js files, any that you have which are already named *.aspx.js can be nested by either re-adding them to the project or manually modifying the .csproj file's XML.
Anyway, that is how I do things and it really helps to keep things organized. I also do all of this for user controls (ascx files) - with the above, it should be simple enough to figure out how to do this for the user controls and ascx file extension.
Hope this helps someone!