380 likes | 448 Views
Introduction to Dashboard Widgets. Michelle Hedstrom May 17, 2005. Agenda. Introduction to Dashboard/Widgets Building your first widget Adding on - web searching! Scrollbars and saved searches What’s Next Debugging. What is Dashboard?. Part of Mac OS 10.4 (Tiger).
E N D
Introduction to Dashboard Widgets Michelle Hedstrom May 17, 2005
Agenda • Introduction to Dashboard/Widgets • Building your first widget • Adding on - web searching! • Scrollbars and saved searches • What’s Next • Debugging
What is Dashboard? • Part of Mac OS 10.4 (Tiger). • Lets You Run Mini-Applications (Widgets). • Pretty wrapper around Apple’s WebKit. • Virtual Layer - only comes when called.
What is a Widget? • Mini application - can run in Safari. • Stored as a bundle. • HTML, JavaScript, & CSS. • Information, application, or accessory. • New widget object
Components of a Widget • HTML Files, Info.plist, Default.png, Icon.png • Optional: JavaScript & CSS Files Picture from http://developer.apple.com/macosx/dashboard.html
Making a Widget • Put all required files in a folder. • Add on to folder name .wdgt extension - turns into bundle. • Double-click .wdgt file to launch in Dashboard. • Optionally, move to ~/Library/Widgets.
We’re Not Talking About… • Error checking • Preferences • Most design conventions Aqua bad Custom good
Tools Needed • A Mac with Tiger (OS 10.4) installed • Text editor • Graphics editor (optional) • Xcode Tools (optional)
Build Stage 1 • Create the widget itself. • Put in the background graphic. • Add in the search form field with results area.
Stage 1 HTML <html> <head> <link rel="stylesheet" href="YahooClass.css" type="text/css" /> <title>Yahoo Class Widget</title> </head> <body> <div class="front"> <input type="search" id="searchYahoo" size="30" /> <div id="resultsArea"> </div> </body> </html> front searchYahoo resultsArea
Stage 1 CSS .front { width: 290px; height: 314px; background-image: url(Default.png); } .searchYahoo { position: absolute; top: 60px; left: 40px; text-align:left; } #resultsArea { background-color: white; position: absolute; font-size: small; top: 90px; left: 30px; width: 240px; height: 190px; }
Stage 1 Info.plist CFBundleDisplayName - actual widget name displayed in Finder and widget bar. CFBundleIdentifier - Uniquely identifying string in reverse domain format. CFBundleName - Name of widget, must match name of bundle on disk minus the .wdgt extension. CFBundleVersion - version number. MainHTML - name of the main HTML file implementing widget.
Build Stage 2 • Run search when user types in search terms. • Display results in results box. • Clicking on URL opens in widget window.
Stage 2 Info.plist AllowNetworkAccess - if widget requires any network resources.
Stage 2 HTML <html> <head> <link rel="stylesheet" href="YahooClass.css" type="text/css" /> <script language="JavaScript" src="Main.js"></script> <title>Yahoo Class Widget</title> </head> <body> <div class="front"> <input type="search" id=“searchYahoo” size="30" onSearch="doSearch(this.value)" /> <div id="resultsArea"> </div> </div> </body> </html>
Stage 2 JS - Vars var baseSearchURL = "http://api.search.yahoo.com/WebSearchService/V1/"; var searchMethod = "webSearch"; var appID = "DashboardSearch"; var numResultsReturned = "5"; var req; • Requirements of Yahoo Search API. • Num results returned optional - defaults to 10 if not provided.
Stage 2 JS - doSearch() function doSearch(searchValue) { var searchURL= baseSearchURL+searchMethod+"?"+"appid="+appID+"&query="+searchValue+"&results="+numResultsReturned; req = new XMLHttpRequest(); req.open("GET",searchURL,false); req.send(null); document.getElementById('resultsArea').innerHTML = (parseXML(req.responseXML)); } • Building the search URL: • http://api.search.yahoo.com/WebSearchService/V1/webSearch?appid=DashboardSearch&query=Michelle&results=5
…Stage 2 JS - doSearch()… function doSearch(searchValue) { var searchURL= baseSearchURL+searchMethod+"?"+"appid="+appID+"&query="+searchValue+"&results="+numResultsReturned; req = new XMLHttpRequest(); req.open("GET",searchURL,false); req.send(null); document.getElementById('resultsArea').innerHTML = (parseXML(req.responseXML)); } • HTTP request that returns XML DOM object. • req.open with false boolean - synchronous mode just for demo. Asynchronous better - check for onreadystatechange event. • req.send arg. used to transmit content for a POST request, null otherwise.
…Stage 2 JS - doSearch()… function doSearch(searchValue) { var searchURL= baseSearchURL+searchMethod+"?"+"appid="+appID+"&query="+searchValue+"&results="+numResultsReturned; req = new XMLHttpRequest(); req.open("GET",searchURL,false); req.send(null); document.getElementById('resultsArea').innerHTML= (parseXML(req.responseXML)); } • Looks for a div called resultsArea. • Gets the area where the content is stored for that div.
…Stage 2 JS - doSearch() function doSearch(searchValue) { var searchURL= baseSearchURL+searchMethod+"?"+"appid="+appID+"&query="+searchValue+"&results="+numResultsReturned; req = new XMLHttpRequest(); req.open("GET",searchURL,false); req.send(null); document.getElementById('resultsArea').innerHTML = (parseXML(req.responseXML)); } • Gets results back of search as a document object. • Passes off to parseXML function.
Stage 2 JS - parseXML() function parseXML(xmlResults) { var resultsStrings = ""; var indResponse = xmlResults.getElementsByTagName("Result"); for (i=0; i<indResponse.length; i++) { var title = indResponse[i].getElementsByTagName("Title")[0].firstChild.nodeValue; var URL = indResponse[i].getElementsByTagName("ClickUrl")[0].firstChild.nodeValue; var summary = indResponse[i].getElementsByTagName("Summary")[0].firstChild.nodeValue; resultsStrings += "<A HREF=\""+URL+"\">"+title+"</a><br>"+summary+"<p>"; } return resultsStrings; } • Find all nodes named Result. • Each individual search result is one such XML node.
…Stage 2 - parseXML() function parseXML(xmlResults) { var resultsStrings = ""; var indResponse = xmlResults.getElementsByTagName("Result"); for (i=0; i<indResponse.length; i++) { var title = indResponse[i].getElementsByTagName("Title")[0].firstChild.nodeValue; var URL = indResponse[i].getElementsByTagName("ClickUrl")[0].firstChild.nodeValue; var summary = indResponse[i].getElementsByTagName("Summary")[0].firstChild.nodeValue; resultsStrings += "<A HREF=\""+URL+"\">"+title+"</a><br>"+summary+"<p>"; } return resultsStrings; } • Iterate through all results, from high level object to value of each individual field we want. • Format all results in way we want with clickable title.
Stage 2 - CSS #resultsArea { background-color: white; position: absolute; font-size: small; top: 90px; left: 30px; width: 240px; height: 190px; overflow: hidden; } Without overflow:hidden With overflow:hidden
Build Stage 3 • Scroll bars for results. • Clicking on URL opens in default browser. • Saved keyword search.
Scrollbar • Custom built scrollbar - Apple code • “Define a parent DIV with overflow CSS property set to hidden” front #resultsArea { background-color: white; background-image: none; position: absolute; font-size: small; top: 90px; left: 30px; width: 240px; height: 190px; overflow:hidden; } searchYahoo resultsArea
Scrollbar Step 2 • “Place all scrollable content in a div within the parent div: this ‘child’ div should have an undefined overflow, or a value of visible.” Changed resultsArea to mainContent in Main.js <div id=“resultsArea”> <div id=“mainContent”> </div </div>
Scrollbar Step 3 • “Declare the Scroller divs and place them inside the parent div.” <div id="resultsArea"> <div id="mainContent"> </div> <div id='myScrollBar'> <div id='myScrollTrack' onmousedown='mouseDownTrack(event);' onmouseup='mouseUpTrack(event);'> <div class='scrollTrackTop' id='myScrollTrackTop'></div> <div class='scrollTrackMid' id='myScrollTrackMid'></div> <div class='scrollTrackBot' id='myScrollTrackBot'></div> </div> <div id='myScrollThumb' onmousedown='mouseDownScrollThumb(event);'> <div class='scrollThumbTop' id='myScrollThumbTop'></div> <div class='scrollThumbMid' id='myScrollThumbMid'></div> <div class='scrollThumbBot' id='myScrollThumbBot'></div> </div> </div> </div>
Scrollbar Step 4 • “It is important that the child div start with a CSS top property set to 0;” #mainContent { position:absolute; left:0; top: 0; right:35px; }
Scrollbar Step 5 • Scrollbar CSS from Apple #myScrollBar { /* border-style:solid; border-color:yellow; */ position:absolute; top:6px; bottom:14px; right:0px; width:19px; display:block; -apple-dashboard-region:dashboard-region(control rectangle); } /* Scroller track */ .scrollTrackTop { /* border-style:solid; border-color:red; */ position:absolute; top:0px; width:19px; height:18px; background:url(Images/top_scroll_track.png) no-repeat top left; } …
apple-dashboard-region #myScrollBar { /* border-style:solid; border-color:yellow; */ position:absolute; top:6px; bottom:14px; right:0px; width:19px; display:block; -apple-dashboard-region:dashboard-region(control rectangle); } Region used for specific purpose. 1st param - type of region defined. 2nd param - shape of region
Stage 3 HTML … <head> <link rel="stylesheet" href="YahooClass.css" type="text/css" /> <script language="JavaScript" src="Main.js"></script> <script language="JavaScript" src="Scroller.js"></script> <title>Yahoo Class Widget</title> </head> <body onload='setup();'> … Use the JavaScript file provided by Apple. Call a setup function after load.
Stage 3 JS - setup() function setup() { scrollerInit(document.getElementById("myScrollBar"), document.getElementById("myScrollTrack"), document.getElementById("myScrollThumb")); document.getElementById('mainContent').innerHTML = ""; calculateAndShowThumb(document.getElementById('mainContent')); } Call Apple defined init function. Set our content area to empty. Figure out the height of the view, and make the thumb proportional (Apple defined func.).
Stage 3 JS - doSearch() function doSearch(searchValue) { var searchURL= baseSearchURL+searchMethod+"?"+"appid="+appID+” &query="+searchValue+"&results="+numResultsReturned; req = new XMLHttpRequest(); req.open("GET",searchURL,false); req.send(null); document.getElementById('mainContent').innerHTML = (parseXML(req.responseXML)); calculateAndShowThumb(document.getElementById('mainContent')); } Resize the scrollbar for the current amount of text in the area.
Saved Keyword Search Change: <input type="search" id="searchYahoo” size="30" onSearch="doSearch(this.value)" /> To: <input type="search" id="searchYahoo" size="30" results="5" onSearch="doSearch(this.value)" /> Results is number of past searches saved.
Open URL in Default Browser function parseXML(xmlResults) { var resultsStrings = ""; indResponse = xmlResults.getElementsByTagName("Result"); for (var i=0; i<numResultsReturned; i++) { var title = indResponse[i].getElementsByTagName("Title")[0].firstChild.nodeValue; var URL = indResponse[i].getElementsByTagName("ClickUrl")[0].firstChild.nodeValue; var summary = indResponse[i].getElementsByTagName("Summary")[0].firstChild.nodeValue; if (window.widget) { resultsStrings += "<A HREF=\"javascript:widget.openURL('"+URL+"')\">"; } else { resultsStrings += "<A HREF=\""+URL+"\">"; } resultsStrings += title+"</a><br>"+summary+"<p>"; } return resultsStrings; }
What’s Next? • Truncate summary or build summary box at bottom of screen. • Preferences to support language, type of search, num results returned.
Debugging • Safari JavaScript Console • Open Terminal window, type: • defaults write com.apple.Safari IncludeDebugMenu 1 • Relaunch Safari, check “Log JavaScript Exceptions” in Debug Menu • Choose “Show JavaScript Console” in Debug menu. • Write directly to JS Console • window.console.log(“Problem if you get here") • Shows up in dark green • Create Debug div • Good ‘ol alerts
Resources • Developing Dashboard Widgets -http://developer.apple.com/macosx/dashboard.html • Apple Dashboard Documentation -http://developer.apple.com/documentation/AppleApplications/Dashboard-date.html • Yahoo Search API - http://developer.yahoo.net/ • /Library/WidgetResources for premade graphics/buttons • /Developer/Examples/Dashboard • /Developer/Applications/Utilities/Property List Editor • Me! - mhedstrom@ogre.com