Part 5: File access and pickers (HTML)
[ This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation ]
In Part 2 of this tutorial series, Manage app lifecycle and state, you learned about app data and session data, and how to save this data. Your app can access certain file-system locations by default, like app data locations, the app install directory, and items it creates in the Downloads folder.
In contrast, user data, such as pictures, videos, and document files, is independent of your app and is typically stored in other locations in the file system, such as the user’s library folders. To access these locations, your app needs to declare capabilities to access the data programmatically, or use a file picker to let the user open the file manually. Here, you use a file picker to access the user’s Pictures, so you don't need to declare any app capabilities. (For more info about capabilities, see App capability declarations.)
In this tutorial, you add functionality to the photo app you created in Part 4: Layout and views. First, you handle the "Get photo" button-click event to open a file picker and let the user select an image from Pictures. Then you bind UI controls to file properties to show the picture info. Finally, we revisit what you learned in Part 2 about how to save app state. Here, you use a MostRecentlyUsedList to keep access to the image that the user selected.
In this tutorial, you learn how to:
- Use a FileOpenPicker to let a user get a file.
- Bind UI controls to data.
- Use a MostRecentlyUsedList to access a previously opened file.
.
Tip
If you want to skip the tutorial and go straight to the code, see Getting started with JavaScript: Complete code for the tutorial series.
Before you start...
- This is the fifth tutorial in a series. Before you start, read Part 1: Create a "Hello, world!" app, Part 2: Manage app lifecycle and state, Part 3: PageControl objects and navigation, and Part 4: Layout and views.
- You can see the complete code for this tutorial in Part 5 complete code.
Step 1: Use a file picker to get an image file
By using the file picker, your app can access files and folders all over the user's system. When you call the file picker, the user can browse the system and select files (or folders) to access and save. After the user picks files or folders, your app receives those picks as StorageFile and StorageFolder objects. Your app can then operate on picked files and folders by using those objects.
The first thing you need to do is handle the GetPhotoButton_Click
event, to get a picture to display.
We start here with the code from Part 4: Layout and views.
To add a file picker
Launch Microsoft Visual Studio and open your PhotoAppSample project.
In the Solution Explorer, double-click home.js to open it.
Just before the call to WinJS.UI.Pages.define, create an object named "photoObject". Give it fields for storing the image information we're interested in.
(function () { "use strict"; // Use this object to store info about the loaded image. var photoObject = { src: null, displayName: null, name: null, path: null, dateCreated: null }; WinJS.UI.Pages.define("/pages/home/home.html", { // This function is called whenever a user navigates to this page. It // populates the page elements with the app's data. ready: function (element, options) { // TODO: Initialize the page here. } }); })();
Create a variable named "homePage" and set it to the value returned by WinJS.UI.Pages.define. This will make it easier to reference the home page's members in later steps.
(function () { "use strict"; // Use this object to store info about the loaded image. var photoObject = { src: null, displayName: null, name: null, path: null, dateCreated: null }; var homePage = WinJS.UI.Pages.define("/pages/home/home.html", { // This function is called whenever a user navigates to this page. It // populates the page elements with the app's data. ready: function (element, options) { // TODO: Initialize the page here. } }); })();
Define three additional members for the page:
getPhotoButtonClickHandler
,loadImage
, anddisplayError
. You fill in the code for these members in the next steps.var homePage = WinJS.UI.Pages.define("/pages/home/home.html", { // This function is called whenever a user navigates to this page. It // populates the page elements with the app's data. ready: function (element, options) { // TODO: Initialize the page here. }, getPhotoButtonClickHandler: function (eventInfo) { }, loadImage: function (file) { }, displayError: function (error) { } });
Let's define our
getPhotoButtonClickHandler
method.Create a FileOpenPicker object. Set the properties on the file picker object that are relevant to your users and your app. (For guidelines to help you decide how to customize the file picker, see Guidelines for file pickers.)
Because the user is picking a picture, you set the SuggestedStartLocation to Pictures and the ViewMode to thumbnail. You also add file type filters so the picker shows only the file types you specify for image files.
getPhotoButtonClickHandler: function (eventInfo) { var openPicker = new Windows.Storage.Pickers.FileOpenPicker(); openPicker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.picturesLibrary; openPicker.viewMode = Windows.Storage.Pickers.PickerViewMode.thumbnail; openPicker.fileTypeFilter.clear(); openPicker.fileTypeFilter.append(".bmp"); openPicker.fileTypeFilter.append(".png"); openPicker.fileTypeFilter.append(".jpeg"); openPicker.fileTypeFilter.append(".jpg"); },
To display the file picker, call the pickSingleFileAsync method.
Note To let a user pick multiple files, call pickMultipleFilesAsync.
This method is asynchronous, so it returns a Promise for the StorageFile the user selected rather than returning the file directly. As described in Part 1: Create a "Hello, world!" app, to obtain a value from a Promise, you pass an event handler to its then or done method.
The most frequently used method on a Promise object is then, which takes up to three parameters: a function to call when the Promise finishes successfully, a function to call when the Promise finishes with an error, and a function to provide progress information. You can also use the done method, which takes the same parameters. The difference is that if there is an error in processing, the then function returns a Promise in the error state but does not throw an exception, while the done method throws an exception if an error function is not provided.
Let's use the done method and pass it a function for when the promise finishes successfully (
loadImage
) and a function for when there was an error (displayError
). Add this line of code to your event handler.openPicker.pickSingleFileAsync().done( homePage.prototype.loadImage, homePage.prototype.displayError);
Here's the complete event handler.
getPhotoButtonClickHandler: function (eventInfo) { var openPicker = new Windows.Storage.Pickers.FileOpenPicker(); openPicker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.picturesLibrary; openPicker.viewMode = Windows.Storage.Pickers.PickerViewMode.thumbnail; openPicker.fileTypeFilter.clear(); openPicker.fileTypeFilter.append(".bmp"); openPicker.fileTypeFilter.append(".png"); openPicker.fileTypeFilter.append(".jpeg"); openPicker.fileTypeFilter.append(".jpg"); openPicker.pickSingleFileAsync().done( homePage.prototype.loadImage, homePage.prototype.displayError); },
In your ready function, register the
getPhotoButtonClickHandler
with thegetPhotoButton
.ready: function (element, options) { // TODO: Initialize the page here. document.getElementById("getPhotoButton") .addEventListener("click", this.getPhotoButtonClickHandler, false); },
Our
loadImage
method is called when the Promise returned by pickSingleFileAsync finishes successfully. It takes a single parameter: a StorageFile object named file.If the user clicks the Cancel button in the open file picker, the picker still returns a Promise that finishes successfully, but it passes a null value for the file parameter. Add an if statement to check whether a file was actually picked.
loadImage: function (file) { if (file) { } },
The file parameter is a StorageFile object. This object has properties for retrieving the displayName, name, path, and dateCreated info of the file it represents. Store this info in the
photoObject
variable that we created earlier.loadImage: function (file) { if (file) { photoObject.displayName = file.displayName; photoObject.name = file.name; photoObject.path = file.path; photoObject.dateCreated = file.dateCreated; } },
To retrieve the image itself, use the URL.createObjectURL method to create a Blob object. To prevent memory leaks, pass the URL.createObjectURL method an object that contains a property named
oneTimeOnly
that is set to true. Use this Blob to set thesrc
property of ourphotoObject
.loadImage: function (file) { if (file) { photoObject.displayName = file.displayName; photoObject.name = file.name; photoObject.path = file.path; photoObject.dateCreated = file.dateCreated; var imageBlob = URL.createObjectURL(file, { oneTimeOnly: true }); photoObject.src = imageBlob; } },
Now define the
displayError
method. This method is called when the Promise returned by pickSingleFileAsync does not finish successfully.displayError: function (error) { document.getElementById("imageName").innerHTML = "Unable to load image."; }
Step 2: Display the image
At this point, clicking the "Get photo" button retrieves an image but doesn't show it. We could retrieve elements for displaying the image and its info and manually set them, but let's use the Windows Library for JavaScript data-binding feature to set these values instead.
Data binding connects an object, the source, to a UI object, the target.
In the previous steps, we created an object for storing photo info. This is our source object.
// Use this object to store info about the loaded image.
var photoObject =
{
src: null,
displayName: null,
name: null,
path: null,
dateCreated: null
};
Our target UI objects are the elements in our home.html file that we want to use to display image information.
<section aria-label="Main content" role="main">
<div id="contentGrid">
<button id="getPhotoButton">Get photo</button>
<div id="imageName" class="pageSubheader">Image name</div>
<div id="imageGrid">
<img id="displayImage" src="#" />
<div id="imageInfoContainer">
<label for="fileName">File name:</label>
<div id="fileName">File name</div>
<label for="path">Path:</label>
<div id="path">Path</div>
<label for="dateCreated">Date created:</label>
<div id="dateCreated">Date created</div>
</div>
</div>
</div>
</section>
To create a data binding, we add the data-win-bind attribute to the target HTML element. This attribute takes as its value the name of the target property to set and the name of the source property that provides the data:
targetPropertyName: sourcePropertyName
You can data bind multiple properties at once. Just list out each target/source pair separated by semicolons, like this:
targetPropertyName1: sourcePropertyName1; targetPropertyName2: sourcePropertyName2
This example binds the innerHTML property of a div element to the displayName
property of the source object.
<div id="imageName" class="pageSubheader"
data-win-bind="innerHTML: displayName">Image name</div>
To initiate the data binding, you call the WinJS.Binding.processAll and pass it either the target element or a parent of the target element, and the source object. This example calls WinJS.Binding.processAll on the contentGrid
element that contains all of the elements that we want to data-bind.
var contentGrid = document.getElementById("contentGrid");
WinJS.Binding.processAll(contentGrid, photoObject);
When you perform declarative binding (like we did in the previous example), you should always set the WinJS.Binding.optimizeBindingReferences property to true in your app's code. (If you don't, the bindings in your app may leak memory.)
WinJS.Binding.optimizeBindingReferences = true;
Let's use data binding to display our image and its accompanying info.
To data-bind the image
In the Solution Explorer, double-click home.html to open it.
Data-bind the elements that display the image and its info.
Bind the innerHTML property of the
imageName
element to thedisplayName
property of the data source.<div id="imageName" class="pageSubheader" data-win-bind="innerHTML: displayName">Image name</div>
Bind the src property of the
displayImage
element to thesrc
property of the data source.<img id="displayImage" src="#" data-win-bind="src: src" />
Bind the innerHTML property of the
fileName
element to thename
property of the data source.<div id="fileName" data-win-bind="innerHTML: name">File name</div>
Bind the innerHTML property of the
path
element to thepath
property of the data source.<div id="path" data-win-bind="innerHTML: path">Path</div>
Bind the innerHTML property of the
dateCreated
element to thedateCreated
property of the data source.<div id="dateCreated" data-win-bind="innerHTML: dateCreated">Date created</div>
Here's the updated HTML for the main section of our home.html file:
<section aria-label="Main content" role="main"> <div id="contentGrid"> <button id="getPhotoButton">Get photo</button> <div id="imageName" class="pageSubheader" data-win-bind="innerHTML: displayName">Image name</div> <div id="imageGrid"> <img id="displayImage" src="#" data-win-bind="src: src" /> <div id="imageInfoContainer"> <label for="fileName">File name:</label> <div id="fileName" data-win-bind="innerHTML: name">File name</div> <label for="path">Path:</label> <div id="path" data-win-bind="innerHTML: path">Path</div> <label for="dateCreated">Date created:</label> <div id="dateCreated" data-win-bind="innerHTML: dateCreated">Date created</div> </div> </div> </div> </section>
In the Solution Explorer, double-click home.js to open it.
In the
loadImage
method, after your existing code, retrieve thecontentGrid
element. Call the WinJS.Binding.processAll method to the end ofloadImage
and pass itcontentGrid
andphotoObject
.loadImage: function (file) { if (file) { photoObject.displayName = file.displayName; photoObject.name = file.name; photoObject.path = file.path; photoObject.dateCreated = file.dateCreated; var imageBlob = URL.createObjectURL(file, { oneTimeOnly: true }); photoObject.src = imageBlob; var contentGrid = document.getElementById("contentGrid"); WinJS.Binding.processAll(contentGrid, photoObject); } },
Run the app. Click the "Get photo" button and select an image.
Here's what the app looks like with a picture selected and the text blocks bound to data.
Step 3: Save and load state
In Part 2 of this tutorial series, Manage app lifecycle and state, you learned how to save and restore the app state. You need to save the state in our new photo app, too. You only need to save and restore the currently displayed image file.
But you can’t just save the path to the file and then reopen it using that path. When a user picks a file with a FileOpenPicker, they implicitly give your app permission to that file. If you try to retrieve the file later using just the path, permission is denied.
Instead, to preserve access to the file for later use, the StorageApplicationPermissions class provides two lists where you can store the file and the permissions to it that were granted when the user opened it with the file picker:
- MostRecentlyUsedList—for storing the last 25 files accessed.
- FutureAccessList—for general storage of up to 1000 files for future access.
You need access only to the last file the user picked so you can use it to restore the page state. For this, the MostRecentlyUsedList fits your needs.
When a user picks a file, you add it to the MostRecentlyUsedList. When you add a file to this list, the MostRecentlyUsedList returns a token that you use to retrieve the file later. You save this token and use it to retrieve the current image file when you restore the page state.
To save state
In the Solution Explorer, double-click home.js to open it.
In the
loadImage
method, add code to the end of the method to add the selected file to the mostRecentlyUsedList. Store the token it returns in the sessionState object.// Add picked file to MostRecentlyUsedList. WinJS.Application.sessionState.mruToken = Windows.Storage.AccessCache.StorageApplicationPermissions .mostRecentlyUsedList.add(file);
Here's the complete
loadImage
method:loadImage: function (file) { if (file) { photoObject.displayName = file.displayName; photoObject.name = file.name; photoObject.path = file.path; photoObject.dateCreated = file.dateCreated; var imageBlob = URL.createObjectURL(file, { oneTimeOnly: true }); photoObject.src = imageBlob; var contentGrid = document.getElementById("contentGrid"); WinJS.Binding.processAll(contentGrid, photoObject); // Add picked file to MostRecentlyUsedList. WinJS.Application.sessionState.mruToken = Windows.Storage.AccessCache.StorageApplicationPermissions .mostRecentlyUsedList.add(file); } },
To load state
In the Solution Explorer, double-click default.js to open it.
In the activated event handler, add code to store the previous execution state in our WinJS.Application.sessionState object so that our home page can access it. Add this code just inside the first if statement.
// Save the previous execution state. WinJS.Application.sessionState.previousExecutionState = args.detail.previousExecutionState;
Here's the complete activated event handler:
app.addEventListener("activated", function (args) { if (args.detail.kind === activation.ActivationKind.launch) { // Save the previous execution state. WinJS.Application.sessionState.previousExecutionState = args.detail.previousExecutionState; if (args.detail.previousExecutionState !== activation.ApplicationExecutionState.terminated) { // TODO: This application has been newly launched. Initialize // your application here. } else { // TODO: This application has been reactivated from suspension. // Restore application state here. } if (app.sessionState.history) { nav.history = app.sessionState.history; } args.setPromise(WinJS.UI.processAll().then(function () { if (nav.location) { nav.history.current.initialPlaceholder = true; return nav.navigate(nav.location, nav.state); } else { return nav.navigate(Application.navigator.home); } })); } });
In the Solution Explorer, double-click home.js to open it.
In your ready function, add code to test whether the app was previously terminated. If it was, use the mostRecentlyUsedList.getFileAsync method to retrieve the file. This method returns a Promise for the file. Pass the
loadImage
anddisplayError
methods to the Promise object's done method.if ( WinJS.Application.sessionState.previousExecutionState === Windows.ApplicationModel.Activation.ApplicationExecutionState.terminated) { var mruToken = WinJS.Application.sessionState.mruToken; if (mruToken) { Windows.Storage.AccessCache.StorageApplicationPermissions. mostRecentlyUsedList.getFileAsync(mruToken) .done(this.loadImage, this.displayError); } }
Here's the updated ready method.
ready: function (element, options) { // TODO: Initialize the page here. document.getElementById("getPhotoButton") .addEventListener("click", this.getPhotoButtonClickHandler, false); if ( WinJS.Application.sessionState.previousExecutionState === Windows.ApplicationModel.Activation.ApplicationExecutionState.terminated) { var mruToken = WinJS.Application.sessionState.mruToken; if (mruToken) { Windows.Storage.AccessCache.StorageApplicationPermissions. mostRecentlyUsedList.getFileAsync(mruToken) .done(this.loadImage, this.displayError); } } },
Press F5 to build and run the app. Click the "Get photo" button to launch the FileOpenPicker and pick a file. Now, when the app is suspended, terminated, and restored, the image is reloaded.
Note Review Part 2: Manage app lifecycle and state for instructions on how to suspend, terminate, and restore an app.
Summary
Congratulations, you're done with the fifth tutorial! You learned how to use file pickers and data binding in a Windows Store app.
Download the sample
Did you get stuck, or do you want to check your work? If so, download the Getting started with JavaScript sample.
Related topics
Getting started with JavaScript: Complete code for the tutorial series