Someone asked me today how to implement a Javascript loading progress bar like the 163 mailbox.
I don't know, but it's not difficult to implement one, because <script /> has onload and onreadystatechange. Also, we have Atlas.
There is a class in Atlas: Sys.ScriptLoader, its function is to load multiple Script files in the page in sequence. Before implementing it, let’s analyze the code of this class.
1Sys.ScriptLoader = function() {
2
3 //Array of reference objects for all Scripts.
4 var _references;
5 //Callback function executed after all Scripts are loaded.
6 var _completionCallback;
7 // The context (parameters) provided when executing the callback function.
8 var _callbackContext;
9
10 // HTTP Element (<script />) of the currently loading Script.
11 var _currentLoadingReference;
12 // The callback function called after the current Script is loaded.
13 var _currentOnScriptLoad;
14
15 // The only method of ScriptLoader is to pass in three parameters. The meaning of the parameters will not be repeated.
16 this.load = function(references, completionCallback, callbackContext) {
17 _references = references;
18 _completionCallback = completionCallback;
19 _callbackContext = callbackContext;
20
21 loadReferences();
twenty two }
twenty three
24 // Start loading references.
25 function loadReferences() {
26 // If a Script is currently loading.
27 // This means that this method is not called for the first time, but is loaded in a Script
28 // Called after completion to load the next Script.
29 if (_currentLoadingReference) {
30 // Check the readyState of the current Script element, which is complete under IE.
31 // Other browsers such as FF are loaded (FF actually does not have this attribute.
32 // But the code below will set it to loaded).
33 // If loading fails, exit.
34 if ((_currentLoadingReference.readyState != 'loaded') &&
35 (_currentLoadingReference.readyState != 'complete')) {
36 return;
37 }
38 else {
39 // Entering this branch indicates successful loading.
40
41 // If the current Script defines the onLoad function.
42 if (_currentOnScriptLoad) {
43 // Called via eval (here is the trouble).
44 eval(_currentOnScriptLoad);
45 //Set to null to release resources.
46 _currentOnScriptLoad = null;
47 }
48
49 // Set related events to null to ensure resources are released.
50 if (Sys.Runtime.get_hostType() != Sys.HostType.InternetExplorer) {
51 // If the current browser is not IE, see the code below
52 // You will find that the onload event is defined for <script />.
53 _currentLoadingReference.onload = null;
54 }
55 else {
56 // If it is IE, see the code below and you will find that
57 // <script /> defines the onreadystatechange event.
58 _currentLoadingReference.onreadystatechange = null;
59 }
60
61 //Finally release the current <script /> reference.
62 _currentLoadingReference = null;
63}
64}
65
66 // If there are still unloaded Scripts.
67 if (_references.length) {
68 // Dequeue.
69 var reference = _references.dequeue();
70 // Create <script />
71 var scriptElement = document.createElement('script');
72 //Set the current <script /> and the current callback function for successful loading.
73 _currentLoadingReference = scriptElement;
74 _currentOnScriptLoad = reference.onscriptload;
75
76 if (Sys.Runtime.get_hostType() != Sys.HostType.InternetExplorer) {
77 // If it is not IE, then set the attribute readyState for <script />.
78 // And use onload event.
79 scriptElement.readyState = 'loaded';
80 scriptElement.onload = loadReferences;
81 }
82 else {
83 // If it is IE, use the onreadystatechange event.
84 scriptElement.onreadystatechange = loadReferences;
85}
86 scriptElement.type = 'text/javascript';
87 scriptElement.src = reference.url;
88
89 // Add <script /> to DOM
90 var headElement = document.getElementsByTagName('head')[0];
91 headElement.appendChild(scriptElement);
92
93 return;
94}
95
96 // If the execution reaches this point, it means that all scripts have been loaded.
97 // If the callback function that is executed after all Scripts are loaded is defined,
98 // Then execute and release resources.
99 if (_completionCallback) {
100 var completionCallback = _completionCallback;
101 var callbackContext = _callbackContext;
102
103 _completionCallback = null;
104 _callbackContext = null;
105
106 completionCallback(callbackContext);
107 }
108
109 _references = null;
110 }
111}
112Sys.ScriptLoader.registerClass('Sys.ScriptLoader');
It can be seen that the method for Sys.ScriptLoader to load script is to add <script /> elements to <header /> sequentially through code. In fact, it is used very rarely in Atlas.
In fact, the code of Sys.ScriptLoader is very simple, and the comments I added seem to be superfluous. It is worth noting that all resources are released as much as possible. Pay special attention to the code starting from line 99. The if body first uses temporary variables to retain two global variables, and then releases the global variables. Its purpose is to avoid memory leaks caused by exceptions thrown when completionCallback is executed, even if there is only a one in ten thousand possibility. The more Javascript there is, the easier it is to cause memory leaks. It is best to pay attention to this issue when writing JS code.
Next, explain the first parameter of the load method, references. I originally thought that this was an array of the Sys.Reference class, but I found that it was actually quite different. Anyway, take a look at the code of this class.
1Sys.Reference = function() {
2
3 var _component;
4 var _onload;
5
6 this.get_component = function() {
7 return _component;
8}
9 this.set_component = function(value) {
10 _component = value;
11 }
12
13 this.get_onscriptload = function() {
14 return _onload;
15}
16 this.set_onscriptload = function(value) {
17 _onload = value;
18}
19
20 this.dispose = function() {
21 _component = null;
twenty two }
twenty three
24 this.getDescriptor = function() {
25 var td = new Sys.TypeDescriptor();
26
27 td.addProperty('component', Object);
28 td.addProperty('onscriptload', String);
29 return td;
30}
31}
32Sys.Reference.registerSealedClass('Sys.Reference', null, Sys.ITypeDescriptorProvider, Sys.IDisposable);
33Sys.TypeDescriptor.addType('script', 'reference', Sys.Reference);
Paying attention to the code of the Sys.ScriptLoader class, we can see that each element of the reference array is actually just a simple "{ url : " http://www.sample.com/sample.js ", onscriptload : "alert(1)"}" form object. But this is fine, you can easily use JSON to construct such an array.
At this point, I think everyone should have thought of how to use Sys.ScriptLoader to easily create a JS loading progress bar. But now that I have written it here, I will continue to implement it in a simple way.
First is the aspx file.
1<%@ Page Language="C#" %>
2
3<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" " http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd ">
4
5<script runat="server">
6
7</script>
8
9<html xmlns=" http://www.w3.org/1999/xhtml " >
10<head runat="server">
11 <title>Load Scripts</title>
12 <script language="javascript">
13 function Load()
14 {
15 document.getElementById("bar").style.width = "0px";
16 var scripts = new Array();
17 for (var i = 0; i < 8; i++)
18 {
19 var s = new Object();
20 var sleep = Math.round((Math.random() * 400)) + 100;
21 s.url = "Script.ashx?sleep=" + sleep + "&t=" + Math.random();
22 s.cost = sleep;
23 scripts.push(s);
twenty four }
25
26 Jeffz.Sample.LoadScripts.load(scripts);
27}
28 </script>
29</head>
30<body style="font-family: Arial;">
31 <form id="form1" runat="server">
32 <div>
33 <atlas:ScriptManager ID="ScriptManager1" runat="server">
34 <Scripts>
35 <atlas:ScriptReference Path="js/LoadScripts.js" />
36 </Scripts>
37 </atlas:ScriptManager>
38
39 Progress Bar:
40 <div style="border: solid 1px black;">
41 <div id="bar" style="height: 20px; width:0%; background-color:Red;"></div>
42 </div>
43 <input type="button" onclick="Load()" value="Load" />
44 <div id="message"></div>
45 </div>
46 </form>
47</body>
48</html>
Very simple. The simplest progress bar is made using two DIVs. The Load() function is called when the button is clicked. This function randomly generates script links and generates an 8-element scripts array. The format of the scripts array is as follows:
1var scripts =
2[
3 { url : " http://www.sample.com/sample1.js ", cost : costOfLoading1 },
4 { url : " http://www.sample.com/sample2.js ", cost : costOfLoading2 },
5 { url : " http://www.sample.com/sample3.js ", cost : costOfLoading3 }
6];
Needless to say, the url attribute of each element, and the function of cost is to represent the value of the time spent loading the file. This value has no unit, only the proportion of this value in the total consumption is used. In addition, you can see that there is a Script.ashx, which is used to simulate a long script loading. It will sleep the thread for a period of time based on the value of sleep in the querystring (as for the following t, the purpose is just to avoid clicking the button by changing the querystring browser cache), this file has almost no code and its implementation can be seen in the sample download. Finally, it is loaded by calling the Jeffz.Sample.LoadScripts.load method, which involves the following code, LoadScripts.js:
1Type.registerNamespace('Jeffz.Sample');
2
3Jeffz.Sample.LoadScripts = new function()
4{
5 var totalCost = 0;
6 var scriptLoader = new Sys.ScriptLoader();
7
8 this.load = function(scripts)
9 {
10 if (Jeffz.Sample.__onScriptLoad != null)
11 {
12 throw new Error("In progress");
13}
14
15 totalCost = 0;
16 Jeffz.Sample.__onScriptLoad = onScriptLoad;
17 var references = new Array();
18
19 var loadedCost = 0;
20 for (var i = 0; i < scripts.length; i++)
twenty one {
22 totalCost += scripts[i].cost;
23 loadedCost += scripts[i].cost;
twenty four
25 var ref = createReference(scripts[i].url, loadedCost);
26
27 references.push(ref);
28 }
29
30 scriptLoader.load(references, onComplete);
31}
32
33 function createReference(url, loadedCost)
34 {
35 var ref = new Object();
36 ref.url = url;
37 ref.onscriptload = "Jeffz.Sample.__onScriptLoad('" + url + "', " + loadedCost + ")";
38 return ref;
39 }
40
41 function onComplete()
42 {
43 Jeffz.Sample.__onScriptLoad = null;
44}
45
46 function onScriptLoad(url, loadedCost)
47 {
48 var progress = 100.0 * loadedCost / totalCost;
49 document.getElementById("bar").style.width = progress + "%";
50 document.getElementById("message").innerHTML += ("<strong>" + url + "</strong>" + " loaded.<br />");
51 }
52}
Alas, it seems that there is no need to explain the code at all. So far, a simple Script loading progress bar has been completed, which is quite simple. The code can be downloaded by clicking here, or the effect can be viewed by clicking here.
But is that the end of the matter? In fact, I'm not very satisfied with this solution, although it should be enough for most situations. You can notice that I implemented Jeffz.Sample.LoadScripts as a Singleton, that is, there is no other instance like it. And at the beginning of the load method, it is judged whether it is loading. If so, an exception will be thrown. The direct reason for achieving such a "single-threaded" loading is that it is limited by the implementation of Sys.ScriptLoader.
Please look at line 44 of the Sys.ScriptLoader code. It uses eval to "evilly" call back when the script is loaded. This is actually a very uncomfortable implementation for developers, because of eval, it is impossible to pass a reference to a function as a callback function. The only thing that can be done is to pass the "root code" to Sys.ScriptLoader as a string. Although it is still possible to achieve "concurrent" Script loading through Sys.ScriptLoader (to put it bluntly, at most you can create a queue like Sys.ScriptLoader), the amount of code will naturally increase, and the complexity of development will also increase.
However, I think this "single-threaded" script loading is sufficient for most situations. And if there are really "special" requirements, wouldn't it be easy for the majority of developers to rewrite one by referring to such a clear example of Sys.ScriptLoader?
http://www.cnblogs.com/JeffreyZhao/archive/2006/09/13/502357.html