How To Make Phonegap's (cordova) File Api Work Like File Api In Normal Browser
Is there a way to make Phonegap's File API work just like same API works in browser? I need same behavior as in browser: Tap on 'Open file' button -> 'Select File' dialog ->.
Solution 1:
Well the problem is most mobile browsers do not support the file dialog. IIRC iOS 6 is the first mobile browser to support this functionality. I did write some code in my Corinthian project, not under active development, that does monkey patch this functionality.
First you'd need to write an Android plugin to start an intent to provide the file dialog. I'm using OI File Manager.
package org.apache.cordova;
import org.apache.cordova.api.CallbackContext;
import org.apache.cordova.api.CordovaPlugin;
import org.apache.cordova.api.LOG;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
/**
* This class exposes methods in DroidGap that can be called from JavaScript.
*/publicclassAppextendsCordovaPlugin {
/**
* Executes the request and returns PluginResult.
*
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackContext The callback context from which we were invoked.
* @return A PluginResult object with a status and message.
*/publicbooleanexecute(String action, JSONArray args, CallbackContext callbackContext)throws JSONException {
PluginResult.Statusstatus= PluginResult.Status.OK;
Stringresult="";
try {
if (action.equals("clearCache")) {
this.clearCache();
}
elseif (action.equals("show")) {
// This gets called from JavaScript onCordovaReady to show the webview.// I recommend we change the name of the Message as spinner/stop is not// indicative of what this actually does (shows the webview).
cordova.getActivity().runOnUiThread(newRunnable() {
publicvoidrun() {
webView.postMessage("spinner", "stop");
}
});
}
elseif (action.equals("loadUrl")) {
this.loadUrl(args.getString(0), args.optJSONObject(1));
}
elseif (action.equals("cancelLoadUrl")) {
this.cancelLoadUrl();
}
elseif (action.equals("clearHistory")) {
this.clearHistory();
}
elseif (action.equals("backHistory")) {
this.backHistory();
}
elseif (action.equals("overrideButton")) {
this.overrideButton(args.getString(0), args.getBoolean(1));
}
elseif (action.equals("overrideBackbutton")) {
this.overrideBackbutton(args.getBoolean(0));
}
elseif (action.equals("exitApp")) {
this.exitApp();
}
callbackContext.sendPluginResult(newPluginResult(status, result));
returntrue;
} catch (JSONException e) {
callbackContext.sendPluginResult(newPluginResult(PluginResult.Status.JSON_EXCEPTION));
returnfalse;
}
}
//--------------------------------------------------------------------------// LOCAL METHODS//--------------------------------------------------------------------------/**
* Clear the resource cache.
*/publicvoidclearCache() {
this.webView.clearCache(true);
}
/**
* Load the url into the webview.
*
* @param url
* @param props Properties that can be passed in to the DroidGap activity (i.e. loadingDialog, wait, ...)
* @throws JSONException
*/publicvoidloadUrl(String url, JSONObject props)throws JSONException {
LOG.d("App", "App.loadUrl("+url+","+props+")");
intwait=0;
booleanopenExternal=false;
booleanclearHistory=false;
// If there are properties, then set them on the Activity
HashMap<String, Object> params = newHashMap<String, Object>();
if (props != null) {
JSONArraykeys= props.names();
for (inti=0; i < keys.length(); i++) {
Stringkey= keys.getString(i);
if (key.equals("wait")) {
wait = props.getInt(key);
}
elseif (key.equalsIgnoreCase("openexternal")) {
openExternal = props.getBoolean(key);
}
elseif (key.equalsIgnoreCase("clearhistory")) {
clearHistory = props.getBoolean(key);
}
else {
Objectvalue= props.get(key);
if (value == null) {
}
elseif (value.getClass().equals(String.class)) {
params.put(key, (String)value);
}
elseif (value.getClass().equals(Boolean.class)) {
params.put(key, (Boolean)value);
}
elseif (value.getClass().equals(Integer.class)) {
params.put(key, (Integer)value);
}
}
}
}
// If wait property, then delay loadingif (wait > 0) {
try {
synchronized(this) {
this.wait(wait);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.webView.showWebPage(url, openExternal, clearHistory, params);
}
/**
* Cancel loadUrl before it has been loaded (Only works on a CordovaInterface class)
*/@DeprecatedpublicvoidcancelLoadUrl() {
this.cordova.cancelLoadUrl();
}
/**
* Clear page history for the app.
*/publicvoidclearHistory() {
this.webView.clearHistory();
}
/**
* Go to previous page displayed.
* This is the same as pressing the backbutton on Android device.
*/publicvoidbackHistory() {
cordova.getActivity().runOnUiThread(newRunnable() {
publicvoidrun() {
webView.backHistory();
}
});
}
/**
* Override the default behavior of the Android back button.
* If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired.
*
* @param override T=override, F=cancel override
*/publicvoidoverrideBackbutton(boolean override) {
LOG.i("App", "WARNING: Back Button Default Behaviour will be overridden. The backbutton event will be fired!");
webView.bindButton(override);
}
/**
* Override the default behavior of the Android volume buttons.
* If overridden, when the volume button is pressed, the "volume[up|down]button" JavaScript event will be fired.
*
* @param button volumeup, volumedown
* @param override T=override, F=cancel override
*/publicvoidoverrideButton(String button, boolean override) {
LOG.i("DroidGap", "WARNING: Volume Button Default Behaviour will be overridden. The volume event will be fired!");
webView.bindButton(button, override);
}
/**
* Return whether the Android back button is overridden by the user.
*
* @return boolean
*/publicbooleanisBackbuttonOverridden() {
return webView.isBackButtonBound();
}
/**
* Exit the Android application.
*/publicvoidexitApp() {
this.webView.postMessage("exit", null);
}
}
package com.simonmacdonald.corinthian;
import org.apache.cordova.api.Plugin;
import org.apache.cordova.api.PluginResult;
import org.json.JSONArray;
import org.json.JSONObject;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.util.Log;
publicclassFileDialogextendsPlugin {
privatestaticfinalintPICK_FILE_RESULT_CODE=8974;
privatestaticfinalintPICK_DIRECTORY_RESULT_CODE=8975;
privatestaticfinalStringLOG_TAG="FileDialog";
public String callbackId;
/**
* Executes the request and returns PluginResult.
*
* @param action The action to execute.
* @param args JSONArry of arguments for the plugin.
* @param callbackId The callback id used when calling back into JavaScript.
* @return A PluginResult object with a status and message.
*/@Overridepublic PluginResult execute(String action, JSONArray args, String callbackId) {
this.callbackId = callbackId;
JSONObjectoptions= args.optJSONObject(0);
if (action.equals("pickFile")) {
showDialog(options, PICK_FILE_RESULT_CODE);
} elseif (action.equals("pickFolder")) {
showDialog(options, PICK_DIRECTORY_RESULT_CODE);
}
else {
returnnewPluginResult(PluginResult.Status.INVALID_ACTION);
}
PluginResultr=newPluginResult(PluginResult.Status.NO_RESULT);
r.setKeepCallback(true);
return r;
}
privatevoidshowDialog(JSONObject options, int type) {
Intent intent;
if (type == PICK_FILE_RESULT_CODE) {
intent = newIntent("org.openintents.action.PICK_FILE");
} else {
intent = newIntent("org.openintents.action.PICK_DIRECTORY");
}
if (options != null) {
Stringtitle= options.optString("title");
if (title != null) {
intent.putExtra("org.openintents.extra.TITLE", title);
}
Stringbutton= options.optString("button");
if (button != null) {
intent.putExtra("org.openintents.extra.BUTTON_TEXT", button);
}
}
//intent.setData(Uri.fromFile(new File("/")));try {
this.cordova.startActivityForResult((Plugin)this,intent,PICK_FILE_RESULT_CODE);
} catch (ActivityNotFoundException e) {
showDownloadDialog();
}
}
privatevoidshowDownloadDialog() {
finalContextcontext=this.cordova.getContext();
Runnablerunnable=newRunnable() {
publicvoidrun() {
AlertDialog.Builderdialog=newAlertDialog.Builder(context);
dialog.setTitle("Install File Manager?");
dialog.setMessage("This requires the free OI File Manager app. Would you like to install it now?");
dialog.setPositiveButton("Yes", newDialogInterface.OnClickListener() {
publicvoidonClick(DialogInterface dlg, int i) {
dlg.dismiss();
Intentintent=newIntent(Intent.ACTION_VIEW,
Uri.parse("market://search?q=pname:org.openintents.filemanager")
);
try {
context.startActivity(intent);
} catch (ActivityNotFoundException e) {
// We don't have the market app installed, so download it directly.Intentin=newIntent(Intent.ACTION_VIEW);
in.setData(Uri.parse("http://openintents.googlecode.com/files/FileManager-1.2.apk"));
context.startActivity(in);
}
}
});
dialog.setNegativeButton("No", newDialogInterface.OnClickListener() {
publicvoidonClick(DialogInterface dlg, int i) {
dlg.dismiss();
}
});
dialog.create();
dialog.show();
}
};
this.cordova.getActivity().runOnUiThread(runnable);
}
@OverridepublicvoidonActivityResult(int reqCode, int resultCode, Intent data) {
//super.onActivityResult(reqCode, resultCode, data);//Log.d(LOG_TAG, "Data is " + data.getData().toString());
Log.d(LOG_TAG, "we are in on activity result");
switch (reqCode) {
case PICK_FILE_RESULT_CODE:
case PICK_DIRECTORY_RESULT_CODE: {
if (resultCode==Activity.RESULT_OK && data!=null && data.getData()!=null) {
StringfilePath="file://" + data.getData().getPath();
Log.d(LOG_TAG, "The data is = " + filePath);
Log.d(LOG_TAG, "Calling succes with callback id = " + this.callbackId);
this.success(newPluginResult(PluginResult.Status.OK, filePath), this.callbackId);
}
break;
}
}
}
}
Then you'd need to write your JavaScript interface:
FileDialog: {
pickFile: function(successCallback, errorCallback, options) {
var win = typeof successCallback !== 'function' ? null : function(f) {
window.resolveLocalFileSystemURI(f, function(fileEntry) {
successCallback(fileEntry);
}, fail);
};
cordova.exec(win, errorCallback, "FileDialog", "pickFile", [options]);
},
pickFolder: function(successCallback, errorCallback, options) {
var win = typeof successCallback !== 'function' ? null : function(d) {
window.resolveLocalFileSystemURI(d, function(dirEntry) {
successCallback(dirEntry);
}, fail);
};
cordova.exec(win, errorCallback, "FileDialog", "pickFolder", [options]);
},
patch: function() {
var inputs = document.getElementsByTagName("input");
for (var i=0; i < inputs.length; i++) {
if (inputs[i].getAttribute('type') == 'file'){
var me = inputs[i];
inputs[i].addEventListener("click", function() {
corinthian.FileDialog.pickFile(function(fileEntry) {
me.value = fileEntry.fullPath;
});
});
}
}
}
}
and finally monkey patch by calling FileDialog.patch(); once you have received the deviceready event in PhoneGap.
Hope this helps...
Post a Comment for "How To Make Phonegap's (cordova) File Api Work Like File Api In Normal Browser"