Community:StrandMaps/Training/Tutorial Advanced
From NSDLWiki
Strand Maps Service Advanced Tutorial
This tutorial shows how to use the Strand Map Service and NSDL Search Service to build a custom map browser to display specific maps and display relevant NSDL resources in the Information Bubble for the benchmarks. The tutorial builds off of knowledge from the Basic Tutorial and covers more advanced topics such as creating a custom strand selector to provide access to a subset of maps, adding custom tabs to the information bubble, and describes a method of providing resources within the information bubble via a search API. Since the advanced tutorial will involve modifying the user interface created for the Basic Tutorial. You can download the files for the basic tutorial and start from there.
This tutorial steps through a PHP implementation, and a downloadable PHP code bundle is included in the advanced tutorial files to reference as you follow along. A alternate JSP example is also included in the downloadable files.
Scenario/Goal: Create an advanced Strand Maps user interface with a custom strand selector that shows only high school level astronomy resources from the NASA Earth Science Reviewed Collection. View a Demo
Requirements
To proceed with this tutorial, you will need:
- Familiarity with the NSDL Science Literacy Maps user interface
- Familiarity with the SMS JavaScript API documentation
- Familiarity with the topics discussed in the Basic Tutorial
- Programming skills in JavaScript, HTML, CSS, JSON, XML and a language such as PHP or JSP
- Web space to host your new user interface.
Although not required, it is useful to have the following:
- Familiarity with the NSDL Collection System
- Familiarity with the NSDL Search Service
- A set of resources to be aligned to the AAAS benchmarks
- A collection in NSDL
Step 1.a: Determine which maps to provide access to
To create a custom strand selector we need to know the Strandmap Service IDs, which are referred to in this tutorial as SMS IDs, for the maps or part of maps that we intend to display. To acquire a map ID, navigate to the specific map in the NSDL Science Literacy Maps and copying the ID from the browser's address bar. For example, the Stars map, located at http://strandmaps.nsdl.org/?id=SMS-MAP-1292 would have the ID SMS-MAP-1292.
By using the Science Literacy maps user interface and navigating the astronomy related maps, we know that the map IDs that we want are
- SMS-MAP-1292 for Stars
- SMS-MAP-1282 for Solar System
- SMS-MAP-1300 for Galaxies and the Universe
The Strandmap Service API provides the ability to limit the maps displayed to any level within the maps. It is possible to only show specific strands or specific grade bands of maps. To determine these sub IDs, you'll need to use the JSON explorer and traverse the JSON returned.
Step 1.b: Determining map IDs
Since we only want to provide access to the high school level (grades 9-12) of the astronomy maps, we'll need to use the JSON explorer to determine which IDs correspond to these subsections of the astronomy maps. The strand maps data model is based on a hierarchy that shows the relationships between benchmarks, the relationship between a benchmark to a grade band and strand, the relationship between a strand or grade level to a map, and so on. Therefore, to determine which ID for a grade band to use, we'll need to start with a benchmark that is within that grade band for a particular map.
For example, to determine the ID of the grades 9-12 grade band in the Stars map:
- Navigate to the Stars map
- Click on a benchmark from the 9-12 grade band to open the information bubble, such as Eventually, some stars exploded, producing clouds containing heavy elements...
- Copy the SMS ID for that benchmark, which is displayed in the benchmark full text. The ID of the example benchmark is SMS-BMK-1774.
- Navigate to the JSON explorer
- Paste the SMS ID in the ObjectID field and click the View JSON button.
- The JSON data is returned with the service response.
To make traversal easier, the strand and grade levels are listed above the gray JSON code area. Grade levels IDs are expressed by SMS-GRD and strand IDs are expressed by SMS-STD. For our example, only one grade level is listed so we can use the ID returned in our strand selector (SMS-GRD-1299). Some benchmarks may belong to multiple strands or grades, especially if the benchmark is found in multiple maps. In this case, you should click the explore link next to the possible IDs to verify that the grade band ID belongs to the appropriate map.
Repeat this process to get the 9-12 grade band ID for the Galaxies and the Universe map (SMS-GRD-1307) and the Solar System Map (SMS-GRD-1291).
Step 2: Create your custom strand selector
Starting with the files for the basic tutorial we will create a custom strand selector.
The <strandSelector> div tag is used to automatically populate the user interface with the default strand selector. Since we are replacing the default strand selector with a custom selector, remove that div tag entirely and replace with the following selector code.
<div id="smsNavigation">
<form action="" method="get" id="smsBrowse" class="smsForm">
<select name="id" id="id">
<option value="">-- Select a Topic --</option>
<option value="SMS-GRD-1307">Galaxies and the Universe</option>
<option value="SMS-GRD-1291">Solar System</option>
<option value="SMS-GRD-1299">Stars</option>
</select>
<input type="submit" value="Go" id="submitBrowse"/>
</form>
</div>
This snippet generates a new selector widget that sets the ID parameter on the page when submitted. The ID parameter is used by the Strandmap Service to pull up a particular map.
Step 3: Create a tab for Educational Resources
To display resources, we need to create a tab for them in the InfoBubble. The InfoBubble is a singleton object that controls the content, appearance, features and behaviors associated with the information bubble widget that appears in the maps. It allows you to add or remove tabs, register actions that are performed when the bubble is opened and closed, and access data associated with the bubble. The methods available for the infoBubble object are explained in the API documentation.
Within the setUpStrandMap function, which was described in Step 4.a. of the basic tutorial, add the following code snippet.
resourcesTab = new InfoBubbleTab('Related Resources');
infoBubble.addTab(resourcesTab);
SMSEvent.addListener(StrandMap,"onbenchmarkselect",resourcesTabCallback);
The tabs are displayed in the order they are added, so add this snippet before adding the NSES Standards tab if you want the resources to appear in the first tab. As a reminder, the setUpStrandMap function is a user-defined callback function that is used to provide additional instructions to your interface.
The callback function for the new onbenchmarkselect event listener has been defined as resourcesTabCallback so create a new function and set the initial content with the following snippet:
function resourcesTabCallback() {
resourcesTab.setContent('Loading...');
}
The setContent() function call is provided by the Strand Maps API and is used to set or update the content in the specified tab. You should initialize the content of the tab with a message such as 'Loading...' to indicate to the user that content is loading while they are waiting.
Step 4: Search for Resources to Display in the Information Bubble
In this step you will use the NSDL Search REST API to display relevant NSDL resources in the Information Bubble. There are many possible ways to determine relevant resources to display in the Information Bubble and some options include: a keyword search over a repository of resources, directly aligning resources with a cataloging system to a benchmark's SMS ID, or even directly aligning resources using a MySQL database. For more information on how to align resources to the NSDL Science Literacy Maps using the benchmarks' SMS IDs, see the How to Align Resources to Benchmarks wiki page.
To start, use the SMS API JSON explorer to see what data is associated with the benchmark SMS-BMK-1774, Eventually, some stars exploded from the Stars map. Using the API, we'll grab the keywords and use the Prototype Javascript library (bundled with the SMS API) to send the keywords to another page that will perform a keyword search with the NSDL Search REST API.
Step 4a: AJAX call
Add a function call to sendResRequest() in the resourcesTabCallback() function. Create the sendResRequest() function as follows.
function sendResRequest(offset){
if(!offset){
offset = 0;
}
var json = StrandMap.getSelectedBenchmarkRecordJson();
var id = StrandMap.getSelectedBenchmarkId();
var kwArray = json.itemRecord.Data.Keywords.keyword;
for(var i=0; i< kwArray.length; i++){
if(i == 0){
var keywords = kwArray[i];
} else if(i == kwArray.length-1){
keywords += '|' + kwArray[i];
} else {
keywords += '|' + kwArray[i];
}
}
var reqUrl = "resources.php?keywords="+ keywords+ "&offset="+offset;
new Ajax.Request(
reqUrl, {
method: 'post',
onSuccess: function (response) {
var c;
if(response.responseText){
resourcesTab.setContent(response.responseText);
}
}
});
}
This function retrieves the JSON formatted data from the Strand Map Service to obtain the keywords. The keywords are separated by a pipe "|" character and then sent as a string to another page, resources.php, via a Prototype AJAX call.
The offset argument is an optional value for pagination of our search results.
Step 4b: Create the search results page
Search results can be parsed and displayed with any programming language. In this example, we'll create a PHP file to parse the results, but feel free to use JSP or any language that you feel comfortable with. NOTE: A JSP example is included with the downloadable files.
Create a new file called resources.php and store it in the same location as your index.html file.
Within your new PHP file, store the offset and keywords in local variables and create a new variable toDisplay that will contain the value for the maximum number of results to display per page.
$keywords = $_REQUEST['keywords']; $offset = $_REQUEST['offset']; $toDisplay = 10;
Step 4c: Form the Search Query
We want to send a query to the NSDL Search REST API to receive a resources result set in XML. To do this, we will create a fielded search against the compoundDescription, compoundTitle and compoundAudience fields. These fields combine all of the dc:description, dc:title or dct:educationLevel content into aggregate, searchable fields. View a list of searchable fields from the NSDL Search Service.
Separate the keywords by OR for the search query.
$a_kw = explode('|',$keywords);
foreach($a_kw as $key=>$val){
if(!$kw){
$kw = '"'.$val.'"';
} else {
$kw = $kw .' OR "'.$val.'"';
}
}
Check the metadata description and titles only for these keywords
$kw = 'compoundDescription:('.$kw.') AND compoundTitle:('.$kw.')';
Limit the result set to only high school level resources.
$grades = 'compoundAudience:("high school")';
For this example, we want to search over only one collection: The NASA Earth Science Reviewed Collection. To search this collection, we need to get the collection ID and search for it within the inCollection searchable field. The easiest way to find a collection ID is to go to the NSDL Collection Browse look for the collection you want in the list and then click on the collection title. On the search results page, look in the browser URL and grab the value of the include_collection[] parameter, in this case: oai:nsdl.org:crs:280276. Be certain to escape the colon ':' characters in the ID, ie:
$collections = 'inCollection:oai\:nsdl.org\:crs\:2802786';
You now have all the elements to construct your query which will search for any of the keywords associated with the current benchmark that are high school level resources and in the NASA Earth Science Reviewed Collection.
$query = $kw .' AND '.$grades.' AND '.$collections;
Send this query, along with the offset and number of resources to display per page to the NSDL Search Service at http://ndrsearch.nsdl.org/search
$url = 'http://ndrsearch.nsdl.org/search?s='.$offset.'&n='.$toDisplay.'&q='.rawurlencode($query);
Step 4d. Parse the results
Create a parsing function that will get the contents of the search query URL and return the XML results.
function parseResults($url){
if(!$curld = curl_init($url)){
$o_XML->error = 'Could not initialize session';
} else {
$options = array(CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_CONNECTTIMEOUT => 30,
CURLOPT_TIMEOUT => 30);
curl_setopt_array($curld,$options);
$s_xml = curl_exec($curld);
try{
$o_XML = new SimpleXMLElement($s_xml);
} catch(Exception $e){
$o_XML->error = 'Could not parse XML';
}
}
if(!$o_XML->error){
return $o_XML;
}
}
Step 4e. Display the results
Create a display function that will take the XML from the parsing function and display the search results. One example of display code is provided below and includes simple pagination code for next and prev links to cycle through results. The pagination link references javascript:sendResRequest(), which is our JavaScript AJAX call function. The next and prev values sent to this function corresponds to the optional offset value used in the sendResRequest() JavaScript function.
You can modify this code to display different information, choose a specific title or description based on your own criteria and have more complex pagination.
function displayResults($o_XML,$offset,$toDisplay){
if(($total = $o_XML->SearchResults->resultsInfo->totalNumResults) > 0){
// pagination code
echo 'Displaying: '.($offset+1);
if($offset + $toDisplay > $total) {
echo ' - '.$total;
} else if($offset + $toDisplay <= $total) {
echo ' - '.($offset + $toDisplay);
}
echo ' out of '.$total;
if($total > 1) {
echo ' results';
} else {
echo ' result';
}
// previous and next links
$prev = $offset - $toDisplay;
$next = $offset + $toDisplay;
if($prev >= 0){
echo ' <a href="javascript:sendResRequest('.$prev.')">Prev</a>';
}
if($prev >= 0 && $next < $total){
echo ' | ';
}
if($next < $total){
echo ' <a href="javascript:sendResRequest('.$next.')">Next</a>';
}
// display the records
$records = $o_XML->SearchResults->results;
foreach($records->document as $index=>$item){
// pick a title - we'll use the first title for this example
$a_titles = explode(' ~^ ',$item->fields->compoundTitle);
$a_descriptions = explode(' ~^ ',$item->fields->compoundDescription);
$s_link = $item->header->resourceIdentifier;
echo '<p><a href="'.$s_link.'">'.$a_titles[0].'</a><br/>'.$a_descriptions[0].'</p>';
}
} else {
echo '<p>There are no related resources for this benchmark.</p>';
}
}
At the bottom of your resources.php file, make a call to the parsing and display functions.
$xml = parseResults($url); displayResults($xml,$offset,$toDisplay);
If you view your user interface in a browser, you should now be able to navigate to the three sub-maps, open the information bubble for the benchmarks and view search results.
Step 5. Options: Indicate which map is being viewed
In the basic tutorial, a breadcrumb bar is provided with the <StrandSelector> tag that indicates what map is being viewed. Since we created a custom StrandSelector for this tutorial, that information isn't immediately available. One option is to traverse the Strand Maps JSON hierarchy to get the parent map name and display that in our own breadcrumb bar.
In the setUpStrandMap() function, make a call to createBreadcrumb(). Create the createBreadcrumb() function, which takes the current map ID (in this case the ID of the grade band for the parent map) and inserts a script tag into the user interface's HEAD element to get the data associated with the grade band from the SMS API call.
function createBreadcrumb(){
var gradeId = StrandMap.getMapId();
var myUrl = 'http://strandmaps.nsdl.org/cms1-2/jsapi/json?callBack=getParentMap&Format=SMS-JSON&ObjectID='+gradeId;
var mapScriptReq = document.createElement( 'script' );
mapScriptReq.src = myUrl;
document.getElementsByTagName('head')[0].appendChild( mapScriptReq );
}
Create the callback function getParentMap that was defined in your API call that inspects the JSON response for relations that have the relationType of is part of and has a catalogID containing MAP. Note that grades bands and strands will only have one relationship - to their parent map. For benchmarks, there will be multiple relationships.
function getParentMap(jsonResponse){
var relations = jsonResponse['SMS-CSIP'].QueryResponse.SMS.Record.itemRecord.Data.InternalRelationship.CatalogID;
if(relations.RelationType == 'is part of' && relations.CatalogNumber.search('MAP') != -1 ){
var parentMapID = relations.CatalogNumber;
}
}
We need to traverse up the hierarchy one more time to get the name of the parent map. To do this, add a similar script tag to the user interface's HEAD element from within the getParentMap() function to get the data associated with the parent map from the SMS API call
var myUrl = 'http://strandmaps.nsdl.org/cms1-2/jsapi/json?callBack=getParentMapName&Format=SMS-JSON&ObjectID='+parentMapID;
var mapScriptReq = document.createElement( 'script' );
mapScriptReq.src = myUrl;
// Insert the script in the document head, which executes the callback getParentMapName()
document.getElementsByTagName('head')[0].appendChild( mapScriptReq );
Create the callback function getParentMapName that was defined in the API call that inspects the JSON response for the Name of the map.
function getParentMapName(jsonResponse){
var name = jsonResponse['SMS-CSIP'].QueryResponse.SMS.Record.itemRecord.Data.Name;
}
In your index.html file, create an empty <div> tag with ID smsMapName inside of the smsNavigation <div> tag. Add the following to the getParentMapName() function to populate the new <div> tag.
var title = name + ' Map > Grades 9-12';
$('smsMapName').update(title);
You'll notice that the browser title is defaulting to the title provided by the service. To change that, add the following to the getParentMapName function:
document.title = title;
This process can be followed to access any data from the SMS API.

