diff options
author | mkearney@google.com <mkearney@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-11-06 18:17:42 +0000 |
---|---|---|
committer | mkearney@google.com <mkearney@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-11-06 18:17:42 +0000 |
commit | 7f03b20e1745bce856b7cd11783760cb5836637b (patch) | |
tree | 8e658e768b4b3fe160b3de7410a694e301f22e3f /chrome/common | |
parent | 946abd609ecbb4e021f8d355a230089fb805a9fd (diff) | |
download | chromium_src-7f03b20e1745bce856b7cd11783760cb5836637b.zip chromium_src-7f03b20e1745bce856b7cd11783760cb5836637b.tar.gz chromium_src-7f03b20e1745bce856b7cd11783760cb5836637b.tar.bz2 |
New angular 'getting started' tutorial.
This was written by Eric Bidelman in a doc;
I've put it into html.
I've expanded the MVC chapter so the tutorials are exposed;
I've also included links in the MVC architecture doc to the tutorials.
Review URL: https://codereview.chromium.org/11193011
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@166229 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/common')
12 files changed, 781 insertions, 8 deletions
diff --git a/chrome/common/extensions/docs/static/images/csperrors.png b/chrome/common/extensions/docs/static/images/csperrors.png Binary files differnew file mode 100644 index 0000000..9644703 --- /dev/null +++ b/chrome/common/extensions/docs/static/images/csperrors.png diff --git a/chrome/common/extensions/docs/static/images/customframe.png b/chrome/common/extensions/docs/static/images/customframe.png Binary files differnew file mode 100644 index 0000000..e5ebaca --- /dev/null +++ b/chrome/common/extensions/docs/static/images/customframe.png diff --git a/chrome/common/extensions/docs/static/images/fetchedicon.png b/chrome/common/extensions/docs/static/images/fetchedicon.png Binary files differnew file mode 100644 index 0000000..11d5607 --- /dev/null +++ b/chrome/common/extensions/docs/static/images/fetchedicon.png diff --git a/chrome/common/extensions/docs/static/images/fileicons.png b/chrome/common/extensions/docs/static/images/fileicons.png Binary files differnew file mode 100644 index 0000000..92a364a --- /dev/null +++ b/chrome/common/extensions/docs/static/images/fileicons.png diff --git a/chrome/common/extensions/docs/static/images/listoffiles.png b/chrome/common/extensions/docs/static/images/listoffiles.png Binary files differnew file mode 100644 index 0000000..153cd46 --- /dev/null +++ b/chrome/common/extensions/docs/static/images/listoffiles.png diff --git a/chrome/common/extensions/docs/static/images/noframe.png b/chrome/common/extensions/docs/static/images/noframe.png Binary files differnew file mode 100644 index 0000000..1b422a7 --- /dev/null +++ b/chrome/common/extensions/docs/static/images/noframe.png diff --git a/chrome/common/extensions/docs/static/images/uploader.png b/chrome/common/extensions/docs/static/images/uploader.png Binary files differnew file mode 100644 index 0000000..92a364a --- /dev/null +++ b/chrome/common/extensions/docs/static/images/uploader.png diff --git a/chrome/common/extensions/docs/static/images/writecompleted.png b/chrome/common/extensions/docs/static/images/writecompleted.png Binary files differnew file mode 100644 index 0000000..0615b8d --- /dev/null +++ b/chrome/common/extensions/docs/static/images/writecompleted.png diff --git a/chrome/common/extensions/docs/templates/articles/angular_framework.html b/chrome/common/extensions/docs/templates/articles/angular_framework.html new file mode 100644 index 0000000..46495f7 --- /dev/null +++ b/chrome/common/extensions/docs/templates/articles/angular_framework.html @@ -0,0 +1,771 @@ +<meta name="doc-family" content="apps"> +<h1>Build Apps with AngularJS</h1> +<!--Article written by Eric Bidelman--> +<p> +This guide gets you started building packaged apps +with the <a href="http://angularjs.org/">AngularJS</a> MVC framework. +To illustrate Angular in action, +we'll be referencing an actual app built using the framework, +the Google Drive Uploader. +The <a href="https://github.com/GoogleChrome/chrome-app-samples/tree/master/gdocs">source code</a> +is available on GitHub. +</p> + +<h2 id="first">About the app</h2> + +<img src="{{static}}/images/uploader.png" + width="296" + height="347" + style="float: right; padding-left: 5px" + alt="Google Drive Uploader"> + +<p> +The Google Drive Uploader allows users to quickly view and interact +with files stored in their Google Drive account +as well as upload new files using the +<a href="http://www.html5rocks.com/en/tutorials/dnd/basics/">HTML Drag and Drop APIs</a>. +It's a great example of building an app which talks +to one of <a href="https://developers.google.com/apis-explorer/#p/">Google's APIs</a>; +in this case, the Google Drive API. +</p> + +<p class="note"> +<strong>Note: </strong> +You can also build apps which talk to 3rd party APIs/services +that are OAuth2-enabled. +See <a href="http://developer.chrome.com/trunk/apps/app_identity.html#non">non-Google Account authentication</a>. +</p> + +<p> +The Uploader uses OAuth2 to access the user's data. The +<a href="http://developer.chrome.com/trunk/apps/experimental.identity.html">chrome.experimental.identity API</a> +handles fetching an OAuth token for the logged-in user, +so the hard work is done for us! +Once we have a long-lived access token, +the apps uses the +<a href="https://developers.google.com/drive/get-started">Google Drive API</a> +to access the user's data. +</p> + +<p> +Key features this app uses: +</p> + +<ul> + <li>Angular JS's autodetection for + <a href="http://developer.chrome.com/trunk/apps/app_csp.html">CSP</a></li> + <li>Render a list of files fetched from the + <a href="https://developers.google.com/drive/get-started">Google Drive API</a></li> + <li><a href="http://www.html5rocks.com/en/tutorials/file/filesystem/">HTML5 Filesystem API</a> + to store file icons offline</li> + <li><a href="http://www.html5rocks.com/en/tutorials/dnd/basics/">HTML5 Drag and Drop</a> + for importing/uploading new files from the desktop</li> + <li>XHR2 to load images, cross-domain</li> + <li><a href="http://developer.chrome.com/trunk/apps/app_identity.html">chrome.experimental.identity API</a> + for OAuth authorization</li> + <li>Chromeless frames to define the app's own navbar look and feel</li> +</ul> + +<h2 id="second">Creating the manifest</h2> + +<p> +All packaged apps require a <code>manifest.json</code> file +which contains the information Chrome needs to launch the app. +The manifest contains relevant metadata and +lists any special permissions the app needs to run. +</p> + +<p> +A stripped down version of the Uploader's manifest looks like this: +</p> + +<pre> +{ + "name": "Google Drive Uploader", + "version": "0.0.1", + "manifest_version": 2, + "oauth2": { + "client_id": "665859454684.apps.googleusercontent.com", + "scopes": [ + "https://docs.google.com/feeds/", + "https://docs.googleusercontent.com/", + "https://spreadsheets.google.com/feeds/", + "https://www.googleapis.com/auth/drive" + ] + }, + ... + "permissions": [ + "experimental", + "https://docs.google.com/feeds/", + "https://docs.googleusercontent.com/", + "https://spreadsheets.google.com/feeds/", + "https://ssl.gstatic.com/", + "https://www.googleapis.com/" + ] +} +</pre> + +<p> +The most important parts of this manifest are the "oauth2" and "permissions" sections. +</p> + +<p> +The "oauth2" section defines the required parameters by OAuth2 to do its magic. +To create a "client_id", follow the instructions in +<a href="http://developer.chrome.com/apps/app_identity.html#client_id">Get your client id</a>. +The "scopes" list the authorization scopes +that the OAuth token will be valid for (for example, the APIs the app wants to access). +</p> + +<p class="note"> +<strong>Note: </strong> +The Uploader actually uses the +<a href="https://developers.google.com/google-apps/documents-list/">Documents List API v3</a> +to access Google Drive content, +hence needing the first several scopes to access all the different types of files. +However, when using the Google Drive API, you only need +<a href="https://www.googleapis.com/auth/drive">"https://www.googleapis.com/auth/drive"</a>. +</p> + +<p> +The "permissions" section includes the "experimental" bit +(necessary because the current chrome.identity API is still experimental) +and additional URLs that the app will access via XHR2. +The URL prefixes are required in order for Chrome +to know which cross-domain requests to allow. +</p> + +<h2 id="three">Creating the event page</h2> + +<p> +All packaged apps require a background script/page +to launch the app and respond to system events. +</p> + +<p> +In its +<a href="https://github.com/GoogleChrome/chrome-app-samples/blob/master/gdocs/js/background.js">background.js</a> +script, +Drive Uploader opens a 500x600px window to the main page. +It also specifies a minimum height and width for the window +so the content doesn't become too crunched: +</p> + +<pre> +chrome.app.runtime.onLaunched.addListener(function(launchData) { + chrome.app.window.create('../main.html', { + width: 500, + height: 600, + minWidth: 500, + minHeight: 600, + frame: 'none' + }); +}); +</pre> + +<p> +The window is created as a chromeless window (frame: 'none'). +By default, windows render with the OS's default close/expand/minimize bar: +</p> + +<img src="{{static}}/images/noframe.png" + width="508" + height="75" + alt="Google Drive Uploader with no frame"> + +<p> +The Uploader uses <code>frame: 'none'</code> to render the window as a "blank slate" +and creates a custom close button in <code>main.html</code>: +</p> + +<img src="{{static}}/images/customframe.png" + width="504" + height="50" + alt="Google Drive Uploader with custom frame"> + +<p> +The entire navigational area is wrapped in a <nav> (see next section). +To declutter the app a bit, +the custom close button is hidden until the user interacts with this the area: +</p> + +<pre> +<style> +nav:hover #close-button { + opacity: 1; +} + +#close-button { + float: right; + padding: 0 5px 2px 5px; + font-weight: bold; + opacity: 0; + -webkit-transition: all 0.3s ease-in-out; +} +</style> + +<button class="btn" id="close-button" title="Close">x</button> +</pre> + +<p> +In +<a href="https://github.com/GoogleChrome/chrome-app-samples/blob/master/gdocs/js/app.js">app.js</a>, +this button is hooked up to <code>window.close()</code>. +</p> + +<h2 id="four">Designing the app the Angular way</h2> + +<p> +Angular is an MVC framework, so we need to define the app in such a way that a +model, view, and controller logically fall out of it. Luckily, this is trivial when using Angular. +</p> + +<p> +The View is the easiest, so let's start there. +</p> + +<h3 id="view">Creating the view</h3> + +<p> +<a href="https://github.com/GoogleChrome/chrome-app-samples/blob/master/gdocs/main.html">main.html</a> +is the "V" in MVC; where we define HTML templates to render data into. +In Angular, templates are simple blocks of HTML with some special sauce. +</p> + +<p> +Ultimately we want to display the user's list of files. +For that, a simple <ul> list makes sense. +The Angular bits are highlighted in bold: +</p> + +<pre> +<ul> + <li <strong>data-ng-repeat="doc in docs"</strong>> + <img data-ng-src=<strong>"{{doc.icon}}"</strong>> <a href=<strong>"{{doc.alternateLink}}"</strong>><strong>{{doc.title}}</strong></a> +<strong>{{doc.size}}</strong> + <span class="date"><strong>{{doc.updatedDate}}</strong></span> + </li> +</ul> +</pre> + +<p> +This reads exactly as it looks: +stamp out an <li> for every doc in our data model "docs". +Each item contains a file icon, link to open the file on the web, +and last updatedDate. +</p> + +<p class="note"> +<strong>Note: </strong> +To make the template valid HTML, +we're using <code>data-*</code> attributes for Angular's +<a href="http://docs.angularjs.org/api/ng.directive:ngRepeat">nRepeat</a> iterator, +but you don't have to. +You could easily write the repeater as <code><li ng-repeat="doc in docs"></code>. +</p> + +<p> +Next, we need to tell Angular which controller will oversee this template's rendering. +For that, we use the +<a href="http://docs.angularjs.org/api/ng.directive:ngController">ngController</a> +directive to tell the <code>DocsController</code> to have reign over the template <body>: +</p> + +<pre> +<body <strong>data-ng-controller="DocsController"</strong>> +<section id="main"> + <ul> + <li data-ng-repeat="doc in docs"> + <img data-ng-src="{{doc.icon}}"> <a href="{{doc.alternateLink}}">{{doc.title}}</a> {{doc.size}} + <span class="date">{{doc.updatedDate}}</span> + </li> + </ul> +</section> +</body> +</pre> + +<p> +Keep in mind, +what you don't see here is us hooking up event listeners or properties for data binding. +Angular is doing that heavy lifting for us! +</p> + +<p> +The last step is to make Angular light up our templates. +The typical way to do that is include the +<a href="http://docs.angularjs.org/api/ng.directive:ngApp">ngApp</a> +directive all the way up on <html>: +</p> + +<pre> +<html <strong>data-ng-app="gDriveApp"</strong>> +</pre> + +<p> +You could also scope the app down +to a smaller portion of the page if you wanted to. +We only have one controller in this app, +but if we were to add more later, +putting <a href="http://docs.angularjs.org/api/ng.directive:ngApp">ngApp</a> +on the topmost element makes the entire page Angular-ready. +</p> + +<p> +The final product for <code>main.html</code> looks something like this: +</p> + +<pre> +<html <strong>data-ng-app="gDriveApp"</strong>> +<head> + … + <!-- crbug.com/120693: so we don't need target="_blank" on every anchor. --> + <base target="_blank"> +</head> +<body <strong>data-ng-controller="DocsController"</strong>> +<section id="main"> + <nav> + <h2>Google Drive Uploader</h2> + <button class="btn" <strong>data-ng-click="fetchDocs()"</strong>>Refresh</button> + <button class="btn" id="close-button" title="Close"></button> + </nav> + <ul> + <li <strong>data-ng-repeat="doc in docs"</strong>> + <img data-ng-src=<strong>"{{doc.icon}}"</strong>> <a href=<strong>"{{doc.alternateLink}}"</strong>><strong>{{doc.title}}</strong></a> <strong>{{doc.size}}</strong> + <span class="date"><strong>{{doc.updatedDate}}</strong></span> + </li> + </ul> +</section> +</pre> + +<h3 id="csp">A word on Content Security Policy</h3> + +<p> +Unlike many other JS MVC frameworks, +Angular v1.1.0+ requires no tweaks to work within a strict +<a href="http://developer.chrome.com/trunk/apps/app_csp.html">CSP</a>. +It just works, out of the box! +</p> + +<p> +However, if you're using an older version +of Angular between v1.0.1 and v1.1.0, +you'll need tell Angular to run in a "content security mode". +This is done by including the +<a href="http://docs.angularjs.org/api/ng.directive:ngCsp">ngCsp</a> +directive alongside <a href="http://docs.angularjs.org/api/ng.directive:ngApp">ngApp</a>: +</p> + +<pre> +<html data-ng-app data-ng-csp> +</pre> + +<h3 id="authorization">Handling authorization</h3> + +<p> +The data model isn't generated by the app itself. +Instead, it's populated from an external API (the Google Drive API). +Thus, there's a bit of work necessary in order to populate the app's data. +</p> + +<p> +Before we can make an API request, +we need to fetch an OAuth token for the user's Google Account. +For that, we've created a method to wrap the call +to <code>chrome.experimental.identity.getAuthToken()</code> and +store the <code>accessToken</code>, +which we can reuse for future calls to the Drive API. +</p> + +<pre> +GDocs.prototype.auth = function(opt_callback) { + try { + <strong>chrome.experimental.identity.getAuthToken({interactive: false}, function(token) {</strong> + if (token) { + this.accessToken = token; + opt_callback && opt_callback(); + } + }.bind(this)); + } catch(e) { + console.log(e); + } +}; +</pre> + +<p class="note"> +<strong>Note: </strong> +Passing the optional callback gives us the flexibility +of knowing when the OAuth token is ready. +</p> + +<p class="note"> +<strong>Note: </strong> +To simplify things a bit, +we've created a library, +<a href="https://github.com/GoogleChrome/chrome-app-samples/blob/master/gdocs/js/gdocs.js">gdocs.js</a> +to handle API tasks. +</p> + +<p> +Once we have the token, +it's time to make requests against the Drive API and populate the model. +</p> + +<h3 id="skeleton">Skeleton controller</h3> + +<p> +The "model" for the Uploader is a simple array (called docs) +of objects that will get rendered as those <li>s in the template: +</p> + +<pre> +var gDriveApp = angular.module('gDriveApp', []); + +gDriveApp.factory('gdocs', function() { + var gdocs = new GDocs(); + return gdocs; +}); + +function DocsController($scope, $http, gdocs) { + $scope.docs = []; + + $scope.fetchDocs = function() { + ... + }; + + // Invoke on ctor call. Fetch docs after we have the oauth token. + gdocs.auth(function() { + $scope.fetchDocs(); + }); + +} +</pre> + +<p> +Notice that <code>gdocs.auth()</code> is called +as part of the DocsController constructor. +When Angular's internals create the controller, +we're insured to have a fresh OAuth token waiting for the user. +</p> + +<h2 id="five">Fetching data</h2> + +<p> +Template laid out. +Controller scaffolded. +OAuth token in hand. +Now what? +</p> + +<p> +It's time to define the main controller method, +<code>fetchDocs()</code>. +It's the workhorse of the controller, +responsible for requesting the user's files and +filing the docs array with data from API responses. +</p> + +<pre> +$scope.fetchDocs = function() { + $scope.docs = []; // First, clear out any old results + + // Response handler that doesn't cache file icons. + var successCallback = function(resp, status, headers, config) { + var docs = []; + var totalEntries = resp.feed.entry.length; + + resp.feed.entry.forEach(function(entry, i) { + var doc = { + title: entry.title.$t, + updatedDate: Util.formatDate(entry.updated.$t), + updatedDateFull: entry.updated.$t, + icon: gdocs.getLink(entry.link, + 'http://schemas.google.com/docs/2007#icon').href, + alternateLink: gdocs.getLink(entry.link, 'alternate').href, + size: entry.docs$size ? '( ' + entry.docs$size.$t + ' bytes)' : null + }; + + $scope.docs.push(doc); + + // Only sort when last entry is seen. + if (totalEntries - 1 == i) { + $scope.docs.sort(Util.sortByDate); + } + }); + }; + + var config = { + params: {'alt': 'json'}, + headers: { + 'Authorization': 'Bearer ' + gdocs.accessToken, + 'GData-Version': '3.0' + } + }; + + $http.get(gdocs.DOCLIST_FEED, config).success(successCallback); +}; +</pre> + +<p> +<code>fetchDocs()</code> uses Angular's <code>$http</code> service +to retrieve the main feed over XHR. +The oauth access token is included +in the <code>Authorization</code> header +along with other custom headers and parameters. +</p> + +<p> +The <code>successCallback</code> processes the API response and +creates a new doc object for each entry in the feed. +</p> + +<p> +If you run <code>fetchDocs()</code> right now, +everything works and the list of files shows up: +</p> + +<img src="{{static}}/images/listoffiles.png" + width="580" + height="680" + alt="Fetched list of files in Google Drive Uploader"> + +<p> +Woot! +</p> + +<p> +Wait,...we're missing those neat file icons. +What gives? +A quick check of the console shows a bunch of CSP-related errors: +</p> + +<img src="{{static}}/images/csperrors.png" + width="947" + height="84" + alt="CSP errors in developer console"> + +<p> +The reason is that we're trying +to set the icons <code>img.src</code> to external URLs. +This violates CSP. +For example: +<code>https://ssl.gstatic.com/docs/doclist/images/icon_10_document_list.png</code>. +To fix this, +we need to pull in these remote assets locally to the app. +</p> + +<h3 id="import">Importing remote image assets</h3> + +<p> +For CSP to stop yelling at us, +we use XHR2 to "import" the file icons as Blobs, +then set the <code>img.src</code> +to a <code>blob: URL</code> created by the app. +</p> + +<p> +Here's the updated <code>successCallback</code> +with the added XHR code: +</p> + +<pre> +var successCallback = function(resp, status, headers, config) { + var docs = []; + var totalEntries = resp.feed.entry.length; + + resp.feed.entry.forEach(function(entry, i) { + var doc = { + ... + }; + + <strong>$http.get(doc.icon, {responseType: 'blob'}).success(function(blob) { + console.log('Fetched icon via XHR'); + + blob.name = doc.iconFilename; // Add icon filename to blob. + + writeFile(blob); // Write is async, but that's ok. + + doc.icon = window.URL.createObjectURL(blob); + + $scope.docs.push(doc); + + // Only sort when last entry is seen. + if (totalEntries - 1 == i) { + $scope.docs.sort(Util.sortByDate); + } + });</strong> + }); +}; +</pre> + +<p> +Now that CSP is happy with us again, +we get nice file icons: +</p> + +<img src="{{static}}/images/fileicons.png" + width="580" + height="680" + alt="Google Drive Uploader with file icons"> + +<h2 id="six">Going offline: caching external resources</h2> + +<p> +The obvious optimization that needs to be made: +not make 100s of XHR requests for each file icon +on every call to <code>fetchDocs()</code>. +Verify this in the Developer Tools console +by pressing the "Refresh" button several times. +Every time, n images are fetched: +</p> + +<img src="{{static}}/images/fetchedicon.png" + width="118" + height="19" + alt="Console log 65: Fetched icon via XHR"> + +<p> +Let's modify <code>successCallback</code> +to add a caching layer. +The additions are highlighted in bold: +</p> + +<pre> +$scope.fetchDocs = function() { + ... + + // Response handler that caches file icons in the filesystem API. + var successCallbackWithFsCaching = function(resp, status, headers, config) { + var docs = []; + var totalEntries = resp.feed.entry.length; + + resp.feed.entry.forEach(function(entry, i) { + var doc = { + ... + }; + + <strong>// 'https://ssl.gstatic.com/doc_icon_128.png' -> 'doc_icon_128.png' + doc.iconFilename = doc.icon.substring(doc.icon.lastIndexOf('/') + 1);</strong> + + // If file exists, it we'll get back a FileEntry for the filesystem URL. + // Otherwise, the error callback will fire and we need to XHR it in and + // write it to the FS. + <strong>var fsURL = fs.root.toURL() + FOLDERNAME + '/' + doc.iconFilename; + window.webkitResolveLocalFileSystemURL(fsURL, function(entry) { + doc.icon = entry.toURL(); // should be === to fsURL, but whatevs.</strong> + + $scope.docs.push(doc); // add doc to model. + + // Only want to sort and call $apply() when we have all entries. + if (totalEntries - 1 == i) { + $scope.docs.sort(Util.sortByDate); + $scope.$apply(function($scope) {}); // Inform angular that we made changes. + } + + <strong>}, function(e) { + // Error: file doesn't exist yet. XHR it in and write it to the FS. + + $http.get(doc.icon, {responseType: 'blob'}).success(function(blob) { + console.log('Fetched icon via XHR'); + + blob.name = doc.iconFilename; // Add icon filename to blob. + + writeFile(blob); // Write is async, but that's ok. + + doc.icon = window.URL.createObjectURL(blob); + + $scope.docs.push(doc); + + // Only sort when last entry is seen. + if (totalEntries - 1 == i) { + $scope.docs.sort(Util.sortByDate); + } + }); + + });</strong> + }); + }; + + var config = { + ... + }; + + $http.get(gdocs.DOCLIST_FEED, config).success(successCallbackWithFsCaching); +}; +</pre> + +<p> +Notice that in the <code>webkitResolveLocalFileSystemURL()</code> callback +we're calling <code>$scope.$apply()</code> +when the last entry is seen. +Normally calling <code>$apply()</code> isn't necessary. +Angular detects changes to data models automagically. +However in our case, +we have an addition layer of asynchronous callback +that Angular isn't aware of. +We must explicitly tell Angular when our model has been updated. +</p> + +<p> +On first run, +the icons won't be in the HTML5 Filesystem and the calls to +<code>window.webkitResolveLocalFileSystemURL()</code> will result +in its error callback being invoked. +For that case, +we can reuse the technique from before and fetch the images. +The only difference this time is that +each blob is written to the filesystem (see +<a href="https://github.com/GoogleChrome/chrome-app-samples/blob/master/gdocs/js/app.js#L25">writeBlob()</a>). +The console verifies this behavior: +</p> + +<img src="{{static}}/images/writecompleted.png" + width="804" + height="42" + alt="Console log 100: Write completed"> + +<p> +Upon next run (or press of the "Refresh" button), +the URL passed to <code>webkitResolveLocalFileSystemURL()</code> exists +because the file has been previously cached. +The app sets the <code>doc.icon</code> +to the file's <code>filesystem: URL</code> and +avoids making the costly XHR for the icon. +</p> + +<h2 id="seven">Drag and drop uploading</h2> + +<p> +An uploader app is false advertising +if it can't upload files! +</p> + +<p> +<a href="https://github.com/GoogleChrome/chrome-app-samples/blob/master/gdocs/js/app.js#L177">app.js</a> +handles this feature by implementing a small library +around HTML5 Drag and Drop called <code>DnDFileController</code>. +It gives the ability to drag in files from the desktop +and have them uploaded to Google Drive. +</p> + +<p> +Simply adding this to the gdocs service does the job: +</p> + +<pre> +gDriveApp.factory('gdocs', function() { + var gdocs = new GDocs(); + + var dnd = new DnDFileController('body', function(files) { + var $scope = angular.element(this).scope(); + Util.toArray(files).forEach(function(file, i) { + gdocs.upload(file, function() { + $scope.fetchDocs(); + }); + }); + }); + + return gdocs; +}); +</pre> + +<p class="backtotop"><a href="#top">Back to top</a></p>
\ No newline at end of file diff --git a/chrome/common/extensions/docs/templates/articles/app_frameworks.html b/chrome/common/extensions/docs/templates/articles/app_frameworks.html index 29e8b606..42765be 100644 --- a/chrome/common/extensions/docs/templates/articles/app_frameworks.html +++ b/chrome/common/extensions/docs/templates/articles/app_frameworks.html @@ -36,7 +36,7 @@ into a series of independent components following the MVC pattern. <p> In the last few years, a series of JavaScript MVC frameworks have been developed, -such as <a href="http://backbonejs.org/">backbone.js</a>, <a href="http://emberjs.com/">ember.js</a>, <a href="http://angularjs.org/">AngularJS</a>, <a href="http://sencha.com/">Sencha</a>, <a href="http://kendo.com/">Kendo UI</a>, and more. +such as <a href="http://backbonejs.org/">backbone.js</a>, <a href="http://emberjs.com/">ember.js</a>, <a href="http://angularjs.org/">AngularJS</a>, <a href="http://sencha.com/">Sencha</a>, <a href="http://www.kendoui.com/">Kendo UI</a>, and more. While they all have their unique advantages, each one of them follows some form of MVC pattern with the goal of encouraging developers to write more structured JavaScript code. </p> @@ -296,12 +296,12 @@ for writing secure and scalable Chrome packaged apps: </p> <ul> - <li><a href="http://angularjs.org/">AngularJS</a> - (<a href="https://github.com/GoogleChrome/textdrive-app">Text Drive Reference App</a>)</li> - <li><a href="http://kendo.com/">Kendo UI</a> - (<a href="https://github.com/GoogleChrome/kendo-photo-booth-app">Photo Booth Reference App</a>)</li> - <li><a href="http://www.sencha.com/">Sencha</a> - (<a href="https://github.com/GoogleChrome/sencha-video-player-app">Video Player Reference App</a>)</li> + <li><a href="http://angularjs.org/">AngularJS</a> + (<a href="https://github.com/GoogleChrome/textdrive-app">Text Drive Reference App</a> and <a href="angular_framework.html">Build Apps with AngularJS tutorial</a>)</li> + <li><a href="http://www.kendoui.com/">Kendo UI</a> + (<a href="https://github.com/GoogleChrome/kendo-photo-booth-app">Photo Booth Reference App</a>)</li> + <li><a href="http://www.sencha.com/">Sencha</a> + (<a href="https://github.com/GoogleChrome/sencha-video-player-app">Video Player Reference App</a> and <a href="sencha_framework.html">Build Apps with Sencha Ext JS tutorial</a>)</li> </ul> <h2 id="resources">Useful resources</h2> diff --git a/chrome/common/extensions/docs/templates/private/apps_sidenav.html b/chrome/common/extensions/docs/templates/private/apps_sidenav.html index 038ac2a..0f68663 100644 --- a/chrome/common/extensions/docs/templates/private/apps_sidenav.html +++ b/chrome/common/extensions/docs/templates/private/apps_sidenav.html @@ -32,7 +32,8 @@ </ul> </li> <li><span><a href="app_frameworks.html">MVC Architecture</a></span> - <ul toggleable> + <ul> + <li><a href="angular_framework.html">Build Apps with AngularJS</a></li> <li><a href="sencha_framework.html">Build Apps with Sencha Ext JS</a></li> </ul> </li> diff --git a/chrome/common/extensions/docs/templates/public/apps/angular_framework.html b/chrome/common/extensions/docs/templates/public/apps/angular_framework.html new file mode 100644 index 0000000..292c852 --- /dev/null +++ b/chrome/common/extensions/docs/templates/public/apps/angular_framework.html @@ -0,0 +1 @@ +{{+partials.standard_apps_article article:intros.angular_framework}} |