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="+search Value+"&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=DashboardSea rch&query=Michelle&results=5 …Stage 2 JS - doSearch()… function doSearch(searchValue) { var searchURL= baseSearchURL+searchMethod+"?"+"appid="+appID+"&query="+search Value+"&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="+search Value+"&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="+search Value+"&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” #resultsArea { background-color: white; background-image: none; position: absolute; font-size: small; top: 90px; left: 30px; width: 240px; height: 190px; overflow:hidden; } front 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.” <div id=“resultsArea”> <div id=“mainContent”> </div </div> •Changed resultsArea to mainContent in Main.js 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/Dashboarddate.html • • • • • Yahoo Search API - http://developer.yahoo.net/ /Library/WidgetResources for premade graphics/buttons /Developer/Examples/Dashboard /Developer/Applications/Utilities/Property List Editor Me! - [email protected]
© Copyright 2024