Introduction
Part 2 describes how to use Sajax, PHP, and JavaScript to develop basic albums. In the process of building a history stack for an application, we will rely on the Client technology and directly combine it with Part 1 of the Code. This article assumes that the reader understands JavaScript and browser cookies.
Save the status in the browser
When surfing the Internet, it is always from one page to another, from one site to another. In this process, the Web browser faithfully records the historical records where you have been, and creates a breadcrumbs digital track, along this track, we can return to the starting point step by step. The back button allows you to return to the position before the previous action. In this sense, it is the Undo button on the Web.
Web is a page-based media. The back and forward buttons in the browser toolbar direct the browser from one page to another. When Macromedia Flash went viral, developers and users found that Rich Internet applications (RIA) broke this pattern. Users can browse on several sites, and then log on to a Flash-based website to spend several minutes on it. When you click the back button, the game is over. The user did not return to the previous Flash site and did not know where it was.
This is also the case for an Ajax-based website-RIA. Websites that allow users to interact with a page multiple times are vulnerable to backend buttons or any history buttons (in this case ). The forward and reload buttons are the same as the Back buttons. The built-in internal history record mechanism of Web browsers is an unavoidable problem. For security reasons, developers cannot tamper with browser history or any related buttons. There are also availability issues. Imagine how confused the user would be if a mysterious warning prompt pops up or the user is sent to a new website.
Build a history stack
Although browser history cannot be changed, you can build a historical record used in RIA by yourself. Obviously, it should be separated from the standard browser navigation tool to some extent, but as mentioned above, rich applications deviate from the standard mode of Web pages to some extent.
We will create a stack to manage the historical event records of the application, that is, store a list and add elements at the end of the table. The stack is used to store data in the order of the last-in-first-out (LIFO. Although the data at the top of the stack is not deleted during rollback, this model is very similar to our needs. In JavaScript, stacks can be managed using arrays.
There is also a pointer with the stack to indicate our current position in the stack. When we click in the application, the new event is pushed to the top of the stack and the Pointer Points to the last added element. When you click the back and forward buttons of the application, no new events are added to the stack. Instead, the stack pointer is moved. Think about what will happen in the history stack when you use the back button: the browser returns to the last view page, and the previously unavailable forward button suddenly becomes available. When you browse a new page, the forward button turns gray again. The elements saved late in the browser history will pop up the stack, and new events will be pushed to the top of the stack. We will reproduce this behavior in our own history stack.
Our goal is to create a set of available history buttons: Back, forward, and refresh, as shown in 1.
Figure 1. The back, forward, and refresh history buttons are displayed on the left, and the unavailable status is displayed on the right. |
Reusable Design
JavaScript uses a very loose method to create objects and classes, but still creates reusable code. First, list the functions required by the history stack, and then use JavaScript to create a stack model. Before integrating the history stack into the album application, you must first create a simple page to test its functions. This has two advantages: the test page helps to focus on the core features of the development and test classes, and the establishment of a separate test page can avoid obfuscation of the history stack and album functions, this ensures reusability.
Buffer with cookies
We need the application history to exist throughout the browser session. As long as the user is still viewing the album page, the history stack object will always exist. This class copies the entire history record to the browser cookie whenever a change occurs. If the user leaves the page in the same browser session and returns again, the user will return the same location when he leaves the application.
Writing Class
Let's take a look at the data or attributes that need to be stored in the history stack. The stack (array) and pointer have been discussed earlier. The stack_limit attribute prevents cookie overflow caused by excessive data (see Listing 1 ). In practice, we hope to store 40-50 events before deleting the oldest record. For testing purpose, we set this value to 15.
Listing 1. history stack construction, including class attributes
Function HistoryStack () { This. stack = new Array (); This. current =-1; This. stack_limit = 15; } |
In addition to these three attributes, this class also requires some methods to add elements, retrieve stack data, and save stack data to browser cookies. First, take a look at the addResource () method, which is used to push records to the top of the stack of the history stack (see Listing 2 ). Note: If the stack length exceeds stack_limit, the oldest record will be removed from the stack.
List 2. addResource () method to add records to the top of the history stack
HistoryStack. prototype. addResource = function (resource) { If (this. stack. length> 0 ){ This. stack = this. stack. slice (0, this. current + 1 ); } This. stack. push (resource ); While (this. stack. length> this. stack_limit ){ This. stack. shift (); } This. current = this. stack. length-1; This. save (); }; |
The following three methods are added to the history stack to obtain information from this class (see listing 3 ). GetCurrent () returns the current record pointed to by the stack pointer, which is useful for navigation in the stack. The hasPrev () and hasNext () Methods return Boolean values, indicating whether there are records before or after the current record, or that we have reached the top of the stack or the end of the stack. These methods are simple, but they are useful when determining the status of the backward and forward buttons.
Listing 3. Methods for defining history stacks
HistoryStack. prototype. addResource = function (resource) HistoryStack. prototype. getCurrent = function () { Return this. stack [this. current]; };HistoryStack. prototype. hasPrev = function () { Return (this. current> 0 ); }; HistoryStack. prototype. hasNext = function () { Return (this. current <this. stack. length-1 & this. current>-1 ); }; |
Now you can add records to the history stack and locate them. However, you still cannot navigate in the stack. The go () method defined in Listing 4 allows us to move back and forth in the stack. You can move forward or backward in the stack by passing positive or negative increments. This is similar to the location. go () method built in JavaScript. Since the built-in functions are imitated, why not create models based on these existing methods?
In addition, we can use this method to implement the refresh function. You can navigate the stack by passing positive or negative parameters. If the value is zero, the current page is refreshed.
Listing 4. go () method of history stack
HistoryStack. prototype. go = function (increment) { // Go back... If (increment <0 ){ This. current = Math. max (0, this. current + increment );// Go forward... } Else if (increment> 0 ){ This. current = Math. min (this. stack. length-1, this. current + increment ); // Reload... } Else { Location. reload (); } This. save (); }; |
So far, as long as the HistoryStack object exists in the current document, the newly created class can work normally. We have discussed the problem that refreshing the page may cause data loss. Let's solve it now. In listing 5, you have added a method to set and access data in the browser cookie. All you need to do is set the name-value pairs for each cookie. Because you only need to save the cookie in the browser session, and do not need to set the validity period. To simplify the example, we do not consider other parameters, such as secure, domain, and path.
Note: If this type requires complex processing of cookies, it is wise to use a completely independent cookie management class. Creating and reading cookies is a little different from the history stack. If JavaScript allows you to specify the scope of method and attribute access, you can also set these methods to private.
Listing 5. How to create and access a browser cookie
HistoryStack. prototype. setCookie = function (name, value) { Var cookie_str = name + "=" + escape (value ); Document. cookie = cookie_str; };HistoryStack. prototype. getCookie = function (name) { If (! Name) return ''; Var raw_cookies, tmp, I; Var cookies = new Array (); Raw_cookies = document. cookie. split (';'); For (I = 0; I <raw_cookies.length; I ++ ){ Tmp = raw_cookies [I]. split ('= '); Cookies [tmp [0] = unescape (tmp [1]); } If (cookies [name]! = Null ){ Return cookies [name]; } Else { Return ''; } }; |
After defining a method to manage any cookie, you can write two other classes that specifically process the history stack. The save () method converts the stack into a string and saves it to the cookie. load () parses the string into an array used to manage the history stack (see Listing 6 ).
Listing 6. save () and load () Methods
HistoryStack. prototype. save = function () { This. setCookie ('chstack', this. stack. toString ()); This. setCookie ('chcurrent', this. current ); };HistoryStack. prototype. load = function () { Var tmp_stack = this. getCookie ('chstack '); If (tmp_stack! = ''){ This. stack = tmp_stack.split (','); } Var tmp_current = parseInt (this. getCookie ('chcurrent ')); If (tmp_current> =-1 ){ This. current = tmp_current; } }; |
Test class
You can use simple HTML pages and some JavaScript to test the completed classes. The test page displays the history button above. Only the activity button is highlighted and can be clicked. We have not created a complex test application. This page generates a random number every time you click a link. These numbers are events recorded in the history stack. The stack is also displayed on the page, and the current records marked by the pointer are displayed in bold.
Listing 7. simple HTML page for testing the history stack
<Html> <Head> <Title> </title> </Head><Body> <Div id = "historybuttons"> </div> <Div> <A href = "#" onclick = "do_add (); return false;"> Add Random Resource </a> </Div> <Div id = "output" style = "margin-top: 40px;"> </div> </Body> </Html> |
Add the JavaScript code shown in listing 8 to the header of the HTML page. This code first instantiate a new history stack object and load all the data that may have been stored in the browser cookie.
We have defined four do _ * () functions. These event handlers will be added to the link of the back, forward, and refresh buttons, and the Add Random Resource link, as shown in listing 7.
The display () function checks the current status of the History Object and generates HTML for the history button. It also generates a list of projects stored in the history.
Listing 8. Integrate the history record class and the JavaScript code on the test page
<Script type = "text/javascript" src = "history. js"> </script> <Script type = "text/javascript">Var myHistory = new HistoryStack (); MyHistory. load (); Function do_add () { Var num = Math. round (Math. random () * 1000 ); MyHistory. addResource (num ); Display (); Return false; } Function do_back () { MyHistory. go (-1 ); Display (); } Function do_forward () { MyHistory. go (1 ); Display (); } Function do_reload () { MyHistory. go (0 ); } Function display () { // Display history buttons Var str = ''; If (myHistory. hasPrev ()){ Str + = '<a href = "#" onclick = "do_back (); return false;">' + '/> </A> '; } Else { Str + = ' '; } If (myHistory. hasNext ()){ Str + = '<a href = "#" onclick = "do_forward (); return false;">' + '' + '</A> '; } Else { Str + = ' '; } Str + = '<a href = "#" onclick = "do_reload (); return false;">' + '/> </A> '; Document. getElementById ("historybuttons"). innerHTML = str; // Display the current history stack, highlighting the current // Position. Var str = '<div> History: </div> '; For (I = 0; I <myHistory. stack. length; I ++ ){ If (I = myHistory. current ){ Str + = '<div> <B>' + myHistory. stack [I] + '</B> </div> '; } Else { Str + = '<div>' + myHistory. stack [I] + '</div> '; } } Document. getElementById ("output"). innerHTML = str; } Window. onload = function (){ Display (); }; </Script> |
On the run test page, you can see that the history button reflects the status of the history stack (see figure 2 ). For example, the buttons are gray when the page is loaded for the first time. After adding some records to the stack, the back button becomes active. If you roll back in the stack, the forward button is highlighted. Note that if you click back several times and then click Add, a part of the stack will be truncated and new events will be pushed to the top of the shortened stack.
Figure 2. Test page of history stack |
After testing this class, you can enter the most exciting stage.
Integrated history object and album
We will call the history stack directly from the album page from the issue left in Part 1. You do not need to modify any PHP files.
First, you need to add a div tag to store the history button. As shown in listing 7.
<Div id = "historybuttons"> </div> |
The history stack code is saved to A. js file, which is linked to the album page.
<Script type = "text/javascript" src = "history. js"> </script> |
History stack objects need to be instantiated and loaded from the buffer. These operations can be added to the front of an existing script on the album page.
Var myHistory = new HistoryStack (); MyHistory. load (); |
In a test application targeting the history stack, only random numbers are stored as events. We can store any required information in the history, but remember that when you click the back button of the application, you also need to determine what content in the history stack is. The application only has two actions related to the x_get_table () and x_get_image () functions. Therefore, for each table link, you can store the table name and the start and step values as event identifiers, such as table-10-5. Similarly, you can store the name image and the index of the image to be viewed, such as image-20.
In section 1st, each link in the album is generated by the get_table_link () and get_image_link () functions. By editing these functions, you can have the function call the history stack before calling the Sajax function. Listing 9 shows these changes in bold.
Listing 9. Updated versions of the get_table_link () and get_image_link () Functions
Function get_table_link ($ title, $ start, $ step ){ $ Link = "myHistory. addResource ('table-$ start-$ step ');" . "X_get_table ($ start, $ step, to_window );" . "Return false ;"; Return '<a href = "#" onclick = "'. $ link. '">'. $ title. '</a> '; }Function get_image_link ($ title, $ index ){ $ Link = "myHistory. addResource ('image-$ Index ');" . "X_get_image ($ index, to_window );" . "Return false ;"; Return '<a href = "#" onclick = "'. $ link. '">'. $ title. '</a> '; } |
When the application calls Sajax, to_window () serves as the callback function to regenerate HTML on the page. In the test application, we used the function display () (listing 8) to complete two tasks: update the status of the page output and update history buttons. Now the following function calls will be added to the existing to_window () function body:
Display_history_buttons (); |
The function is defined in listing 10.
Listing 10. display_history_buttons () function
Function display_history_buttons () { Var str = ''; If (myHistory. hasPrev ()){ Str + = '<a href = "#" onclick = "do_back (); return false;"> </a> '; } Else { Str + = ' '; } If (myHistory. hasNext ()){ Str + = '<a href = "#" onclick = "do_forward (); return false;"> </a> '; } Else { Str + = ' '; } Str + = '<a href = "#" onclick = "do_reload (); return false;"> </a> '; Document. getElementById ("historybuttons"). innerHTML = str; } |
Before tracking the history of the album application, you only need to call the x_get_table () function during page loading. In this way, you can call the initial table displayed through Sajax.
Now we have a history stack, but we do not want to start from scratch every time we open this application. Instead, we want to start from where we left. Therefore, you need to add the load_current () function to expand the application. This function is called when the page is loaded. When you add a back and forward button handler, you can call this function to update the Page Based on the event ID saved to the history stack.
Figure 2. Test page of history stack |
After testing this class, you can enter the most exciting stage.
Integrated history object and album
We will call the history stack directly from the album page from the issue left in Part 1. You do not need to modify any PHP files.
First, you need to add a div tag to store the history button. As shown in listing 7.
<Div id = "historybuttons"> </div> |
The history stack code is saved to A. js file, which is linked to the album page.
<Script type = "text/javascript" src = "history. js"> </script> |
History stack objects need to be instantiated and loaded from the buffer. These operations can be added to the front of an existing script on the album page.
Var myHistory = new HistoryStack (); MyHistory. load (); |
In a test application targeting the history stack, only random numbers are stored as events. We can store any required information in the history, but remember that when you click the back button of the application, you also need to determine what content in the history stack is. The application only has two actions related to the x_get_table () and x_get_image () functions. Therefore, for each table link, you can store the table name and the start and step values as event identifiers, such as table-10-5. Similarly, you can store the name image and the index of the image to be viewed, such as image-20.
In section 1st, each link in the album is generated by the get_table_link () and get_image_link () functions. By editing these functions, you can have the function call the history stack before calling the Sajax function. Listing 9 shows these changes in bold.
Listing 9. Updated versions of the get_table_link () and get_image_link () Functions
Function get_table_link ($ title, $ start, $ step ){ $ Link = "myHistory. addResource ('table-$ start-$ step ');" . "X_get_table ($ start, $ step, to_window );" . "Return false ;"; Return '<a href = "#" onclick = "'. $ link. '">'. $ title. '</a> '; }Function get_image_link ($ title, $ index ){ $ Link = "myHistory. addResource ('image-$ Index ');" . "X_get_image ($ index, to_window );" . "Return false ;"; Return '<a href = "#" onclick = "'. $ link. '">'. $ title. '</a> '; } |
When the application calls Sajax, to_window () serves as the callback function to regenerate HTML on the page. In the test application, we used the function display () (listing 8) to complete two tasks: update the status of the page output and update history buttons. Now the following function calls will be added to the existing to_window () function body:
Display_history_buttons (); |
The function is defined in listing 10.
Listing 10. display_history_buttons () function
Function display_history_buttons () { Var str = ''; If (myHistory. hasPrev ()){ Str + = '<a href = "#" onclick = "do_back (); return false;"> </a> '; } Else { Str + = ' '; } If (myHistory. hasNext ()){ Str + = '<a href = "#" onclick = "do_forward (); return false;"> </a> '; } Else { Str + = ' '; } Str + = '<a href = "#" onclick = "do_reload (); return false;"> </a> '; Document. getElementById ("historybuttons"). innerHTML = str; } |
Before tracking the history of the album application, you only need to call the x_get_table () function during page loading. In this way, you can call the initial table displayed through Sajax.
Now we have a history stack, but we do not want to start from scratch every time we open this application. Instead, we want to start from where we left. Therefore, you need to add the load_current () function to expand the application. This function is called when the page is loaded. When you add a back and forward button handler, you can call this function to update the Page Based on the event ID saved to the history stack.
Listing 11. load_current () function
Function load_current () { // No existing history. If (myHistory. stack. length = 0 ){ X_get_table (to_window ); MyHistory. addResource ('table-0-5 '); // Load from history. } Else { Var current = myHistory. getCurrent (); Var params = current. split ('-'); If (params [0] = 'table '){ X_get_table (params [1], params [2], to_window ); } Else if (params [0] = 'image '){ X_get_image (params [1], to_window ); } } } |
The onload handler needs to be modified accordingly:
Window. onload = function (){ Load_current (); }; |
Finally, add the history button processing routine in listing 12. Note the similarity between the processing program and the test application.
List 12. History button event handler
Function do_back () { MyHistory. go (-1 ); Load_current (); }Function do_forward () { MyHistory. go (1 ); Load_current (); } Function do_reload () { MyHistory. go (0 ); } |
This completes the integration of the history stack to the album application. Product 3 after completion is shown in.
Figure 3. History button combined with the album Application |
Open the application and click the link to view the history stack and pointer stored in the browser cookie.
CHCurrent = 4 CHStack = table-0-5% 2Cimage-1% 2Cimage-2% 2Cimage-3% 2Ctable-3-5 |
If you are running Mozilla Firefox and have downloaded the Web Developer Toolbar extension, these operations are easy to implement.
Conclusion
We introduced how to create a custom history stack to track events in Ajax applications. You can add common back, forward, and refresh buttons on the Web browser to the application to navigate to the custom history stack.
To solve this problem, we have identified the problem and created a reusable solution that can be applied to other applications. Instead of creating a history stack directly in the album application, we use a simple page to test this class. This helps establish a solution that is not strictly bound to an application. This solution can be used by other Ajax applications to solve the same problem.