Andrew Sellergren
#submit
.centered
form input[type=text]
id
of submit
. The second selector above will get us all HTML elements that have the class
attribute of "centered." The third selector above will get us the <input>
elements within a <form>
element which have type
of "text."Ajax stands for Asynchronous JavaScript and XML. It allows us to make HTTP requests programmatically within JavaScript code. We can apply this to CS50 Finance so that users won't have to click the Back button and resubmit a form every time they want to fetch a stock price. In quote1.js
, we have JavaScript code that will fetch the stock price for us asynchronously:
$(document).ready(function() {
// load data via ajax when form is submitted
$('#form-quote').on('submit', function() {
// determine symbol
var symbol = $('#form-quote input[name=symbol]').val();
// send request to quote.php
$.ajax({
url: 'quote.php',
type: 'POST',
data: {
symbol: symbol
},
success: function(response) {
$('#price').text(response);
}
});
// since we're overridding form submission, make sure it doesn't submit
return false;
});
});
ready
method of the jQuery-enhanced document
object. The ready
method takes as input a function that should be called when the webpage has finished loading. We pass to this method an anonymous function. This anonymous function attaches an event handler to the onsubmit
event of the form, which we retrieve from the DOM using its id
of form-quote
. When the form is submitted, our innermost anonymous function will be called. This function first gets the value of the <input>
element which has name
of "symbol." Then it passes this value, which should be the stock symbol, to the ajax
method of the jQuery object $
. To the ajax
method, we pass an object which describes the HTTP request we want to make. In this case, the HTTP request is to URL quote.php
using the POST method. In this HTTP request, we pass a single parameter named symbol
containing the stock symbol we want to look up. The last property (named success
) of the object we pass to the ajax
method is a function we want to be called when the Ajax request is successful. This function takes a single argument named response
containing the actual HTTP response from the server. We expect this HTTP response to be the price of the stock, so we're going to insert that into the HTML element with id
of price
.onsubmit
) from actually firing. Since we're sending the form data programmatically in JavaScript, we don't want the form to actually submit or else the browser will navigate to the URL specified in the action
attribute.This is still a little annoying though since we have to click the "Get Quote" button each time we want to look up a stock price. What if instead we automatically fetch stock prices as soon as the user begins typing, just like Google Instant does for searches? To do this, we need only make a few changes:
$(document).ready(function() {
// load data via ajax when form is submitted
$('#form-quote input[name=symbol]').on('keyup', function() {
// determine symbol
var symbol = $('#form-quote input[name=symbol]').val();
// send request to quote.php
$.ajax({
url: 'quote.php',
type: 'POST',
data: {
symbol: symbol
},
success: function(response) {
$('#price').text(response);
}
});
});
});
onkeyup
event in the text input box. The only other change we made was to get rid of the return false
line. This time, we do want the onkeyup
event to fire when we're done issuing the Ajax request simply so that the user's input actually shows up in the search box.We can take this UI design one step further by implementing autocomplete. With autocomplete, the user will get suggestions for stock symbols as he types. We can implement this using the typeahead feature of the Bootstrap JavaScript library:
$(document).ready(function() {
// create autocomplete from list of stock symbols
$('#form-quote input[name=symbol]').typeahead({
source: SYMBOLS
});
// load data via ajax when form is submitted
$('#form-quote').on('submit', function() {
// determine symbol
var symbol = $('#form-quote input[name=symbol]').val();
// send request to quote.php
$.ajax({
url: 'quote.php',
type: 'POST',
data: {
symbol: symbol
},
success: function(response) {
$('#price').text(response);
}
});
// since we're overridding form submission, make sure it doesn't submit
return false;
});
});
typeahead
method. We provide it with an array of stock symbols named SYMBOLS
from which it can choose.To add relevance to our suggestions, we're going to punt the hard work to Yahoo! Finance. We've written another file named suggest.php
which will issue a query to Yahoo! to get stock symbol suggestions. Now, in our JavaScript, we're going to make an Ajax request to this file:
$(document).ready(function() {
// create autocomplete
$('#form-quote input[name=symbol]').typeahead({
// load autocomplete data from suggest.php
source: function(query, callback) {
$.ajax({
url: 'suggest.php',
type: 'POST',
dataType: 'json',
data: {
symbol: query
},
success: function(response) {
callback(response.symbols);
}
});
}
});
// load data via ajax when form is submitted
$('#form-quote').on('submit', function() {
// determine symbol
var symbol = $('#form-quote input[name=symbol]').val();
// send request to quote.php
$.ajax({
url: 'quote.php',
type: 'POST',
data: {
symbol: symbol
},
success: function(response) {
$('#price').text(response);
}
});
// since we're overridding form submission, make sure it doesn't submit
return false;
});
});
Before, the value of the source
property of the object we passed to the typeahead
method was an array of strings. Now, the value of the source
property is a function which issues an Ajax request to get the stock symbol suggestions. The options we pass to the ajax
method include a dataType
property which is set to "json." JSON stands for JavaScript Object Notation, a way of specifying JavaScript objects in string format. Since we've specified JSON as the dataType
, jQuery will return the HTTP response to us as a JavaScript object that looks something like this:
{"symbols": ["AA", "AAPL", "AAP"]}
Let's try to implement the functionality of typeahead ourselves:
$(document).ready(function() {
// create autocomplete
$('#form-quote input[name=symbol]').on('keyup', function() {
// load autocomplete data from suggest.php
$.ajax({
url: 'suggest.php',
type: 'POST',
dataType: 'json',
data: {
symbol: $(this).val()
},
success: function(response) {
// build html string for a list of suggestions
var suggestions = '<ul>';
for (var i in response.symbols)
suggestions += '<li><a href="#" class="suggestion">' + response.symbols[i] + '</a></li>';
// display list of suggestions
suggestions += '</ul>';
$('#suggestions').html(suggestions);
}
});
});
// set value of symbol field when a suggestion is clicked
$('#suggestions').on('click', '.suggestion', function() {
$('#form-quote input[name=symbol]').val($(this).text());
});
// load data via ajax when form is submitted
$('#form-quote').on('submit', function() {
// determine symbol
var symbol = $('#form-quote input[name=symbol]').val();
// send request to quote.php
$.ajax({
url: 'quote.php',
type: 'POST',
data: {
symbol: symbol
},
success: function(response) {
$('#price').text(response);
}
});
// since we're overridding form submission, make sure it doesn't submit
return false;
});
});
typeahead
method, we need to build our own HTML to represent the list of suggested stock symbols. We do this by iterating over the symbols passed back by suggest.php
and constructing a string of HTML representing a <ul>
list. We then inject that HTML into the <div>
with id
of "suggestions."This injected HTML looks pretty ugly, but it makes room for one other improvement. Users aren't likely to recognize all the suggested stocks by their symbols, so we can do them a favor by displaying company names instead:
$(document).ready(function() {
// create autocomplete
$('#form-quote input[name=symbol]').on('keyup', function() {
// load autocomplete data from suggest.php
$.ajax({
url: 'suggest_more.php',
type: 'POST',
dataType: 'json',
data: {
symbol: $(this).val()
},
success: function(response) {
// build html string for a list of suggestions
var suggestions = '<ul>';
for (var i in response.symbols)
suggestions += '<li><a href="#" class="suggestion" data-symbol="' + response.symbols[i].symbol + '">' +
response.symbols[i].name + '</a></li>';
// display list of suggestions
suggestions += '</ul>';
$('#suggestions').html(suggestions);
}
});
});
// set value of symbol field when a suggestion is clicked
$('#suggestions').on('click', '.suggestion', function() {
$('#form-quote input[name=symbol]').val($(this).attr('data-symbol'));
});
// load data via ajax when form is submitted
$('#form-quote').on('submit', function() {
// determine symbol
var symbol = $('#form-quote input[name=symbol]').val();
// send request to quote.php
$.ajax({
url: 'quote.php',
type: 'POST',
data: {
symbol: symbol
},
success: function(response) {
$('#price').text(response);
}
});
// since we're overridding form submission, make sure it doesn't submit
return false;
});
});
In our injected HTML, we're now listing the company names rather than the stock symbols. When one of those company names is clicked, the search box will be populated with the corresponding stock symbol. This stock symbol we actually get from the HTML of the list item itself. When we created this HTML, we added an attribute named data-symbol
that we can access like any other HTML attribute. We do this in the following lines:
// set value of symbol field when a suggestion is clicked
$('#suggestions').on('click', '.suggestion', function() {
$('#form-quote input[name=symbol]').val($(this).attr('data-symbol'));
});
on
method, we're assigning an anonymous function as the event handler for any click that occurs inside an element that has a class
of suggestion
within the "suggestions" <div>
. This anonymous function assigns to the symbol search box the value of the data-symbol
attribute of $(this)
. $(this)
gives us the element that was clicked, which in this case would be the list item representing the company name.Let's add some functionality to our Portfolio page:
$(document).ready(function() {
// key pressed in search field, so filter table
$('#search').on('keyup', function() {
// determine symbol we're searching for
var query = $(this).val();
// iterate over each row in the table
$('#table-portfolio tbody tr').each(function(e) {
// check if the symbol cell contains the query
if (!query || $(this).children().first().text().toLowerCase().indexOf(query) > -1)
$(this).show();
// no match, so hide row
else
$(this).hide();
});
});
// load table into ticker
$('#table-portfolio tbody tr').each(function(e) {
// get the cells in the row
var children = $(this).children();
// add all rows to ticker except the last, which is cash
if (children.length > 2)
$('#ticker').append('<span>' + children.eq(0).text() + ' ' + children.eq(3).text() + '</span> ');
});
// animate the ticker
function animateTicker() {
// move ticker text all the way off to the right
$('#ticker').css({ marginLeft: '100%' });
// animate ticker text all the way off to the left, and restart animation when done
$('#ticker').animate({ marginLeft: '-100%' }, 10000, 'linear', animateTicker);
}
// start animation loop
animateTicker();
});
<tr>
elements within the element with id
of "table-porfolio." We iterate over these rows using the jQuery each
method, which takes as input a function that is to be called with a row as its only argument, in this case e
. To search each row for the user's query, we get all of the row's children using the children
method, we take the first child using the first
method, we convert it to lowercase text using the text
and toLowerCase
methods, and finally we look for the user's query as a substring using the indexOf
method, which returns a non-zero index corresponding to the substring it has found.