Home | | Internet & World Wide Web HOW TO PROGRAM | | Internet Programming | | Web Programming | Creating a Full-Scale Ajax-Enabled Application

Chapter: Internet & World Wide Web HOW TO PROGRAM - The Ajax Client - Ajax-Enabled Rich Internet Applications

Study Material, Lecturing Notes, Assignment, Reference, Wiki description explanation, brief detail

Creating a Full-Scale Ajax-Enabled Application

Our next example demonstrates additional Ajax capabilities. The web application interacts with a web service to obtain data and to modify data in a server-side database.

Creating a Full-Scale Ajax-Enabled Application

 

Our next example demonstrates additional Ajax capabilities. The web application interacts with a web service to obtain data and to modify data in a server-side database. The web application and server communicate with a data format called JSON (JavaScript Object Notation). In addition, the application demonstrates server-side validation that occurs in parallel with the user interacting with the web application. You can test the application  at test.deitel.com/examples/iw3htp4/ajax/fig15_09_10/AddressBook.html.

 

Using JSON

JSON (JavaScript Object Notation)—a simple way to represent JavaScript objects as strings—is an alternative way (to XML) for passing data between the client and the server. Each object in JSON is represented as a list of property names and values contained in curly braces, in the following format:

 

{ "propertyName1" : value1, "propertyName2'": value2 }

 

Arrays are represented in JSON with square brackets in the following format:

 

[ value1, value2, value3 ]

 

Each value can be a string, a number, a JSON representation of an object, true, false or null. You can convert JSON strings into JavaScript objects with JavaScript’s eval func tion. To evaluate a JSON string properly, a left parenthesis should be placed at the begin-ning of the string and a right parenthesis at the end of the string before the string is passed to the eval function.

 

The eval function creates a potential security risk—it executes any embedded Java-Script code in its string argument, possibly allowing a harmful script to be injected into JSON. A more secure way to process JSON is to use a JSON parser. In our examples, we use the open source parser from  www.json.org/js.html. When you download its Java-Script file, place it in the same folder as your application. Then, link the json.js file into your XHTML file with the following statement in the head section:

 

<script type = "text/javascript" src = "json.js">

 

You can now call function parseJSON on a JSON string to convert it to a JavaScript object. JSON strings are easier to create and parse than XML, and require fewer bytes. For these reasons, JSON is commonly used to communicate in client/server interaction. For more information on JSON, visit our JSON Resource Center at  www.deitel.com/json.

 

Rich Functionality

The previous examples in this chapter requested data from static files on the server. The example in Fig. 15.9 is an address-book application that communicates with a server-side application. The application uses server-side processing to give the page the functionality and usability of a desktop application. We use JSON to encode server-side responses and to create objects on the fly.

 

Initially the address book loads a list of entries, each containing a first and last name (Fig. 15.9(a)). Each time the user clicks a name, the address book uses Ajax functionality to load the person’s address from the server and expand the entry without reloading the page (Fig. 15.9(b))—and it does this in parallel with allowing the user to click other names. The application allows the user to search the address book by typing a last name. As the user enters each keystroke, the application asynchronously displays the list of names in which the last name starts with the characters the user has entered so far (Fig. 15.9(c), Fig. 15.9

(d) and Fig. 15.9(e))—a popular feature called type ahead.

 

1     <?xml version = "1.0" encoding = "utf-8"?>

 

2     <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"

 

3           "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

4              

5     <!-- Fig. 15.9 addressbook.html -->

 

6     <!-- Ajax enabled address book application. -->

 

7     <html xmlns = "http://www.w3.org/1999/xhtml">

 

8     <head>

 

9           <title>Address Book</title>

 

10          <link rel = "stylesheet" type = "text/css" href = "address.css" />

 

11          <script type = "text/javascript" src = "json.js"></script>

 

12          <script type = "text/javascript">

13                 <!--

 

14                 // URL of the web service

 

var webServiceUrl = '/AddressBookWebService/AddressService.asmx';

17                 var phoneValid = false; // indicates if the telephone is valid

 

18                 var zipValid = false; //indicates if the zip code is valid

19

20                 // get a list of names from the server and display them

 

21                 function showAddressBook()

22                 {

 

23                       // hide the "addEntry" form and show the address book

 

24                       document.getElementById( 'addEntry' ).style.display = 'none';

 

25                       document.getElementById( 'addressBook' ).style.display = 'block';

26           

27                       var params = "[]"; // create an empty object

 

28                       callWebService( 'getAllNames', params, parseData );

 

29                 } // end function showAddressBook

30

31                 // send the asynchronous request to the web service

 

32                 function callWebService( method, paramString, callBack )

33                 {

 

34                       // build request URL string

 

35                       var requestUrl = webServiceUrl + "/" + method;

 

36                       var params = paramString.parseJSON();

37                        

 

38                       // build the parameter string to add to the url

 

39                       for ( var i = 0; i < params.length; i++ )

{

            // checks        whether it is the first parameter and builds                    

41                                           

42        // the parameter string accordingly                       

43        if ( i ==            0 )                   

44        requestUrl = requestUrl + "?" + params[ i ].param +                  

45        "="       + params[ i ].value; // add first parameter to url              

46        else                            

47        requestUrl = requestUrl + "&" + params[ i ].param +                 

48        "="       + params[ i ].value; // add other parameters to url                      

49        } // end for                             

51                       // attempt to send the asynchronous request

52                       try

{

54        var asyncRequest = new XMLHttpRequest(); // create request

55                                           

56        // set up callback function and store it

57                    asyncRequest.onreadystatechange = function()         

58                    {                      

59                    callBack( asyncRequest );

60                    }; // end anonymous function       

61                                           

62        // send the asynchronous request

63        asyncRequest.open( 'GET', requestUrl, true );

64                    asyncRequest.setRequestHeader("Accept",     

65                    "application/json; charset=utf-8" );

66        asyncRequest.send(); // send request

66                       } // end try

 

68                       catch ( exception )

69                       {

70                              alert ( 'Request Failed' );

 

71                       } // end catch

 

72                 } // end function callWebService

73           

74                 // parse JSON data and display it on the page

 

75                 function parseData( asyncRequest )

76                 {

 

77                       // if request has completed successfully process the response

 

78                       if ( asyncRequest.readyState == 4 && asyncRequest.status == 200 )

{

// convert        the JSON string to an Object

var data =       asyncRequest.responseText.parseJSON();

82                              displayNames( data ); // display data on the page

 

83                       } // end if

 

84                 } // end function parseData

85

86                 // use the DOM to display the retrieved address book entries

 

87                 function displayNames( data )

88                 {

 

89                       // get the placeholder element from the page

 

90                       var listBox = document.getElementById( 'Names' );

 

91                       listBox.innerHTML = ''; // clear the names on the page

92

93                       // iterate over retrieved entries and display them on the page

 

94                       for ( var i = 0; i < data.length; i++ )

{

96        //          dynamically create a div element  for each entry

97        //          and a fieldset element to place it in

98        var       entry = document.createElement( 'div' );

99        var       field = document.createElement(  'fieldset' );

100     entry.onclick = handleOnClick; // set onclick event handler

100                           entry.id = i; // set the id

 

101                           entry.innerHTML = data[ i ].First + ' ' + data[ i ].Last;

 

102                           field.appendChild( entry ); // insert entry into the field

 

103                           listBox.appendChild( field ); // display the field

104                       } // end for

 

105               } // end function displayAll

107

108               // event handler for entry's onclick event

 

109               function handleOnClick()

110               {

 

111                     // call getAddress with the element's content as a parameter

 

112                     getAddress( eval( 'this' ), eval( 'this.innerHTML' ) );

 

113               } // end function handleOnClick

114

115               // search the address book for input

 

116               // and display the results on the page

 

117               function search( input )

118               {

 

119                     // get the placeholder element and delete its content

 

var listBox = document.getElementById( 'Names' );

121               listBox.innerHTML = ''; // clear the display box

 

122

123                     // if no search string is specified all the names are displayed

 

124                     if ( input == "" ) // if no search value specified

125                     {

 

126                           showAddressBook(); // Load the entire address book

127                     } // end if

128                     else

129                     {

 

130                           var params = '[{"param": "input", "value": "' + input + '"}]';

 

131                           callWebService( "search",  params , parseData );

132                     } // end else

 

133               } // end function search

134

135               // Get address data for a specific entry

 

136               function getAddress( entry, name )

137               {

 

138                     // find the address in the JSON data using the element's id

 

139                     // and display it on the page

 

140                     var firstLast = name.split(" "); // convert string to array

 

141                     var requestUrl = webServiceUrl + "/getAddress?first="

 

142                           + firstLast[ 0 ] + "&last=" + firstLast[ 1 ];

143

144                     // attempt to send an asynchronous request

145                     try

146                     {

 

147                           // create request object

 

148                           var asyncRequest = new XMLHttpRequest();

149        

150                           // create a callback function with 2 parameters

 

151                           asyncRequest.onreadystatechange = function()

{

153     displayAddress( entry, asyncRequest );

154     }; // end anonymous function

156                           asyncRequest.open( 'GET', requestUrl, true );

 

157                           asyncRequest.setRequestHeader("Accept",

 

158                           "application/json; charset=utf-8"); // set response datatype

 

159                           asyncRequest.send(); // send request

160                     } // end try

 

161                     catch ( exception )

162                     {

 

163                           alert ( 'Request Failed.' );

 

164                     } // end catch

 

165               } // end function getAddress

166

167               // clear the entry's data.

 

168               function displayAddress( entry, asyncRequest )

169               {

 

170                     // if request has completed successfully, process the response

 

171                     if ( asyncRequest.readyState == 4 && asyncRequest.status == 200 )

172                     {


 

173                           // convert the JSON string to an object

 

174                           var data = asyncRequest.responseText.parseJSON();

 

175                           var name = entry.innerHTML // save the name string

 

entry.innerHTML = name + '<br/>' + data.Street +

177     '<br/>' + data.City      + ', ' + data.State                                                      

178     + ', ' + data.Zip +       '<br/>' + data.Telephone;               

180                           // clicking on the entry removes the address

 

181                           entry.onclick = function()

182                           {

183     clearField( entry, name );

184     }; // end anonymous function

186                       185    } // end if

 

187               } // end function displayAddress

188        

189               // clear the entry's data

 

190               function clearField( entry, name )

191               {

 

192                     entry.innerHTML = name; // set the entry to display only the name

 

193                     entry.onclick = function() // set onclick event

194                     {

 

195                           getAddress( entry, name ); // retrieve address and display it

 

196                     }; // end function

 

197               } // end function clearField

198

199               // display the form that allows the user to enter more data

 

200               function addEntry()

201               {

 

202                     document.getElementById( 'addressBook' ).style.display = 'none';

 

203                     document.getElementById( 'addEntry' ).style.display = 'block';

 

204               } // end function addEntry

205

206               // send the zip code to be validated and to generate city and state

 

207               function validateZip( zip )

208               {

 

209                     // build parameter array

 

210                     var params = '[{"param": "zip", "value": "' + zip + '"}]';

 

211                     callWebService ( "validateZip", params, showCityState );

 

212               } // end function validateZip

213

214               // get city and state that were generated using the zip code

 

215               // and display them on the page

 

216               function showCityState( asyncRequest )

217               {

 

218                     // display message while request is being processed

 

219                     document.getElementById( 'validateZip' ).

 

220                           innerHTML = "Checking zip...";

221

222                     // if request has completed successfully, process the response

 

223                     if ( asyncRequest.readyState == 4 )

{

225                           if ( asyncRequest.status == 200 )

{

227     // convert the JSON string to an object

228                 var data = asyncRequest.responseText.parseJSON();

229                            

230     // update zip code validity tracker and show city and state

231     if ( data.Validity == 'Valid' )

232     {          

233                 zipValid = true; // update validity tracker

234                            

235                 // display city and state

236                 document.getElementById( 'validateZip' ).innerHTML = '';

237                 document.getElementById( 'city' ).innerHTML = data.City;

238                 document.getElementById( 'state' ).

239                 innerHTML = data.State;

240     } // end if

241     else

242     {          

243                 zipValid = false; // update validity tracker

244                 document.getElementById( 'validateZip' ).

245                 innerHTML = data.ErrorText; // display the error

246                            

247                 // clear city and state values if they exist

248                 document.getElementById( 'city' ).innerHTML = '';

249                 document.getElementById( 'state' ).innerHTML = '';

250     } // end else

251                           } // end if

 

252                           else if ( asyncRequest.status == 500 )

{

254 document.getElementById( 'validateZip'     ).                     

255     innerHTML = 'Zip validation service         not avaliable';          

256                           } // end else if

257                     } // end if

 

258               } // end function showCityState

259        

260               // send the telephone number to the server to validate format

 

261               function validatePhone( phone )

262               {

 

263                     var params = '[{ "param": "tel", "value": "' + phone + '"}]';

 

264                     callWebService( "validateTel", params, showPhoneError );

 

265               } // end function validatePhone

266

267               // show whether the telephone number has correct format

 

268               function showPhoneError( asyncRequest )

269               {

 

270                     // if request has completed successfully, process the response

 

271                     if ( asyncRequest.readyState == 4 && asyncRequest.status == 200 )

272                     {

 

273                           // convert the JSON string to an object

 

274                           var data = asyncRequest.responseText.parseJSON();

275

276                           if ( data.ErrorText != "Valid Telephone Format" )

{

278     phoneValid = false; // update validity tracker     

279     } // end if

280     else

281     {

 

                       

282     phoneValid = true; // update validity         tracker

283     } // end else  

284                

285     document.getElementById( 'validatePhone'       ).

286     innerHTML = data.ErrorText; // display the error

287                       } // end if

 

288               } // end function showPhoneError

289        

290               // enter the user's data into the database

 

291               function saveForm()

292               {

 

293                     // retrieve the data from the form

 

294                     var first = document.getElementById( 'first' ).value;

 

295                     var last = document.getElementById( 'last' ).value;

 

296                     var street = document.getElementById( 'street' ).value;

 

297                     var city = document.getElementById( 'city' ).innerHTML;

 

298                     var state = document.getElementById( 'state' ).innerHTML;

 

299                     var zip = document.getElementById( 'zip' ).value;

 

300                     var phone = document.getElementById( 'phone' ).value;

301

302                     // check if data is valid

 

303                     if ( !zipValid || !phoneValid  )

304                     {

 

305                           // display error message

 

306                           document.getElementById( 'success' ).innerHTML =

307                         'Invalid data entered. Check form for more information';

 

308                     } // end if

309                     else if ( ( first == "" ) || ( last == "" ) )

310                     {

 

311                           // display error message

 

312                           document.getElementById( 'success').innerHTML =

313                         'First Name and Last Name must have a value.';

 

314                     } // end if

315                     else

316                     {

 

317                           // hide the form and show the addressbook

 

document.getElementById( 'addEntry' )

319     .style.display = 'none';

320     document.getElementById( 'addressBook' ).

321     style.display = 'block';

322    

323     // build the parameter to include in the web service URL

 

324     params = '[{"param": "first", "value": "' + first +

 

'"}, { "param": "last", "value": "'        + last +                      

325                                        

326     '"}, { "param": "street", "value":       "'+ street +                 

327     '"}, { "param": "city", "value": "'        +  city +                      

328     '"}, { "param":  "state", "value:":       "' + state +                 

329     '"}, { "param": "zip", "value": "'         + zip +                       

330     '"}, { "param": "tel", "value": "'          + phone + '"}]';                      

331

 

332                           // call the web service to insert data into the database

 

333                           callWebService( "addEntry", params, parseData );

334                     } // end else

 

335              } // end function saveForm

336              //-->

 

337       </script>

 

338  </head>

 

339  <body onload = "showAddressBook()">

340       <div>

 

341              <input type = "button" value = "Address Book"

 

342                     onclick = "showAddressBook()"/>

 

343              <input type = "button" value = "Add an Entry"

 

344                     onclick = "addEntry()"/>

345       </div>

 

346       <div id = "addressBook" style = "display : block;">

 

347                 Search By Last Name:

 

348              <input onkeyup = "search( this.value )"/>

349              <br/>

 

350              <div id = "Names">

351              </div>

352       </div>

 

353       <div id = "addEntry" style = "display : none">

 

354              First Name: <input id = 'first'/>

355              <br/>

 

356              Last Name: <input id = 'last'/>

357              <br/>

 

358              <strong> Address: </strong>

359              <br/>

 

360              Street: <input id = 'street'/>

361              <br/>

 

362              City: <span id = "city" class = "validator"></span>

363              <br/>

 

364              State: <span id = "state" class = "validator"></span>

365              <br/>

 

366              Zip: <input id = 'zip' onblur = 'validateZip( this.value )'/>

 

367              <span id = "validateZip" class = "validator">

 

368              </span>

369              <br/>

 

370              Telephone:<input id = 'phone'

 

371                     onblur = 'validatePhone( this.value )'/>

 

372              <span id = "validatePhone" class = "validator">

 

373              </span>

374              <br/>

 

375              <input type = "button" value = "Submit"

 

376                     onclick = "saveForm()" />

377              <br/>

 

378              <div id = "success" class = "validator">

379              </div>

380       </div>

 

381  </body>

 

</html>






Fig. 15.9 | Ajax-enabled address-book application.

 

 

The application also enables the user to add another entry to the address book by clicking the addEntry button (Fig. 15.9(f)). The application displays a form that enables live field validation. As the user fills out the form, the zip-code value is validated and used to generate the city and state (Fig. 15.9(g), Fig. 15.9(h) and Fig. 15.9(i)). The telephone number is validated for correct format (Fig. 15.9(j)). When the Submit button is clicked, the application checks for invalid data and stores the values in a database on the server (Fig. 15.9(k) and Fig. 15.9(l)). You can test-drive this application at test.deitel.com/examples/iw3htp4/ajax/fig15_09_10/AddressBook.html.

 

Interacting with a Web Service on the Server

 

When the page loads, the onload event (line 339) calls the showAddressBook function to load the address book onto the page. Function showAddressBook (lines 21–29) shows the addressBook element and hides the addEntry element using the HTML DOM (lines 24– 25). Then it calls function callWebService to make an asynchronous request to the server (line 28). Function callWebService requires an array of parameter objects to be sent to the server. In this case, the function we are invoking on the server requires no arguments, so line 27 creates an empty array to be passed to callWebService. Our program uses an ASP.NET web service that we created for this example to do the server-side processing. The web service contains a collection of methods that can be called from a web application.

 

Function callWebService (lines 32–72) contains the code to call our web service, given a method name, an array of parameter bindings (i.e., the method’s parameter names and argument values) and the name of a callback function. The web-service application and the method that is being called are specified in the request URL (line 35). When sending the request using the GET method, the parameters are concatenated URL starting with a ? symbol and followed by a list of parameter=value bindings, each separated by an &. Lines 39–49 iterate over the array of parameter bindings that was passed as an argument, and add them to the request URL. In this first call, we do not pass any parameters because the web method that returns all the entries requires none. However, future web method calls will send multiple parameter bindings to the web service. Lines 52–71 prepare and send the request, using similar functionality to the previous two examples. There are many types of user interaction in this application, each requiring a separate asynchronous request. For this reason, we pass the appropriate asyncRequest object as an argument to the function specified by the callBack parameter. However, event handlers cannot receive arguments, so lines 57–60 assign an anonymous function to asyncRequest’s onready-statechange property. When this anonymous function gets called, it calls function call-Back and passes the asyncRequest object as an argument. Lines 64–65 set an Accept request header to receive JSON formatted data.

 

Parsing JSON Data

Each of our web service’s methods in this example returns a JSON representation of an object or array of objects. For example, when the web application requests the list of names in the address book, the list is returned as a JSON array, as shown in Fig. 15.10. Each object in Fig. 15.10 has the attributes first and last.

 

Line 11 links the json.js script to the XHTML file so we can parse JSON data. When the XMLHttpRequest object receives the response, it calls function parseData (lines 75–84). Line 81 calls the string’s parseJSON function, which converts the JSON string into a JavaScript object. Then line 82 calls function displayNames (lines 87–106), which

1    [ { "first": "Cheryl", "last": "Black" },

 

2        { "first": "James", "last": "Blue" },

 

3        { "first": "Mike", "last": "Brown" },

 

4        { "first": "Meg", "last": "Gold" } ]

 

Fig. 15.10 | Address-book data formatted in JSON.

 

displays the first and last name of each address-book entry passed to it. Lines 90–91 use the DOM to store the placeholder div element Names in the variable listbox, and clear its content. Once parsed, the JSON string of address-book entries becomes an array, which this function traverses (lines 94–105).

 

Creating XHTML Elements and Setting Event Handlers on the Fly

 

Line 99 uses an XHTML fieldset element to create a box in which the entry will be placed. Line 100 registers function handleOnClick as the onclick event handler for the div created in line 98. This enables the user to expand each address-book entry by clicking it. Function handleOnClick (lines 109–113) calls the getAddress function whenever the user clicks an entry. The parameters are generated dynamically and not evaluated until the getAddress function is called. This enables each function to receive arguments that are specific to the entry the user clicked. Line 102 displays the names on the page by accessing the first (first name) and last (last name) fields of each element of the data array.

 

Function getAddress (lines 136–166) is called when the user clicks an entry. This request must keep track of the entry where the address is to be displayed on the page. Lines 151–154 set the displayAddress function (lines 168–187) as the callback function, and pass it the entry element as a parameter. Once the request completes successfully, lines 174–178 parse the response and display the addresses. Lines 181–184 update the div’s onclick event handler to hide the address data when that div is clicked again by the user. When the user clicks an expanded entry, function clearField (lines 190–197) is called. Lines 192–196 reset the entry’s content and its onclick event handler to the values they had before the entry was expanded.

 

Implementing Type-Ahead

The input element declared in line 348 enables the user to search the address book by last name. As soon as the user starts typing in the input box, the onkeyup event handler calls the search function (lines 117–133), passing the input element’s value as an argument. The search function performs an asynchronous request to locate entries with last names that start with its argument value. When the response is received, the application displays the matching list of names. Each time the user changes the text in the input box, function search is called again to make another asynchronous request.

 

The search function (lines 117–133) first clears the address-book entries from the page (lines 120–121). If the input argument is the empty string, line 126 displays the entire address book by calling function showAddressBook. Otherwise lines 130–131 send a request to the server to search the data. Line 130 creates a JSON string to represent the parameter object to be sent as an argument to the callWebServices function. Line 131 converts the string to an object and calls the callWebServices function. When the server responds, callback function parseData is invoked, which calls function displayNames to display the results on the page.

Implementing a Form with Asynchronous Validation

 

When the Add an Entry button (lines 343–344) is clicked, the addEntry function (lines 200–204) is called, which hides the addressBook element and shows the addEntry ele-ment that allows the user to add a person to the address book. The addEntry element (lines 353–380) contains a set of entry fields, some of which have event handlers that enable val-idation that occurs asynchronously as the user continues to interact with the page. When a user enters a zip code, the validateZip function (lines 207–212) is called. This function calls an external web service to validate the zip code. If it is valid, that external web service returns the corresponding city and state. Line 210 builds a parameter object containing validateZip’s parameter name and argument value in JSON format. Line 211 calls the callWebService function with the appropriate method, the parameter object created in line 210 and showCityState (lines 216–258) as the callback function.

 

Zip-code validation can take a long time due to network delays. The showCityState function is called every time the request object’s readyState property changes. Until the request completes, lines 219–220 display "Checking zip code..." on the page. After the request completes, line 228 converts the JSON response text to an object. The response object has four properties—Validity, ErrorText, City and State. If the request is valid, line 233 updates the zipValid variable that keeps track of zip-code validity (declared at line 18), and lines 237–239 show the city and state that the server generated using the zip code. Otherwise lines 243–245 update the zipValid variable and show the error code. Lines 248–249 clear the city and state elements. If our web service fails to connect to the zip-code validator web service, lines 252–256 display an appropriate error message.

 

Similarly, when the user enters the telephone number, the function validatePhone (lines 261–265) sends the phone number to the server. Once the server responds, the showPhoneError function (lines 268–288) updates the validatePhone variable (declared at line 17) and shows the message that the web service returned.

When the Submit button is clicked, the saveForm function is called (lines 291–335). Lines 294–300 retrieve the data from the form. Lines 303–308 check if the zip code and telephone number are valid, and display the appropriate error message in the Success ele-ment on the bottom of the page. Before the data can be entered into a database on the server, both the first-name and last-name fields must have a value. Lines 309–314 check that these fields are not empty and, if they are empty, display the appropriate error mes-sage. Once all the data entered is valid, lines 318–321 hide the entry form and show the address book. Lines 324–333 build the parameter object using JSON and send the data to the server using the callWebService function. Once the server saves the data, it queries the database for an updated list of entries and returns them; then function parseData dis-plays the entries on the page.


Study Material, Lecturing Notes, Assignment, Reference, Wiki description explanation, brief detail


Copyright © 2018-2020 BrainKart.com; All Rights Reserved. Developed by Therithal info, Chennai.