diff --git a/android/src/main/java/com/pspdfkit/react/ReactPdfViewManager.java b/android/src/main/java/com/pspdfkit/react/ReactPdfViewManager.java index 8e6e60fa..b0b43683 100644 --- a/android/src/main/java/com/pspdfkit/react/ReactPdfViewManager.java +++ b/android/src/main/java/com/pspdfkit/react/ReactPdfViewManager.java @@ -46,6 +46,8 @@ public class ReactPdfViewManager extends ViewGroupManager { public static final int COMMAND_ADD_ANNOTATION = 5; public static final int COMMAND_GET_ALL_UNSAVED_ANNOTATIONS = 6; public static final int COMMAND_ADD_ANNOTATIONS = 7; + public static final int COMMAND_GET_FORM_FIELD_VALUE = 8; + public static final int COMMAND_SET_FORM_FIELD_VALUE = 9; private CompositeDisposable annotationDisposables = new CompositeDisposable(); @@ -77,7 +79,7 @@ public void onDropViewInstance(PdfView view) { @Nullable @Override public Map getCommandsMap() { - return MapBuilder.of( + Map commandMap = MapBuilder.of( "enterAnnotationCreationMode", COMMAND_ENTER_ANNOTATION_CREATION_MODE, "exitCurrentlyActiveMode", @@ -92,6 +94,9 @@ public Map getCommandsMap() { COMMAND_GET_ALL_UNSAVED_ANNOTATIONS, "addAnnotations", COMMAND_ADD_ANNOTATIONS); + commandMap.put("getFormFieldValue", COMMAND_GET_FORM_FIELD_VALUE); + commandMap.put("setFormFieldValue", COMMAND_SET_FORM_FIELD_VALUE); + return commandMap; } @ReactProp(name = "fragmentTag") @@ -189,10 +194,21 @@ public void accept(JSONObject jsonObject) { } break; case COMMAND_ADD_ANNOTATIONS: - if (args != null) { + if (args != null && args.size() == 1) { root.addAnnotations(args.getMap(0)); } break; + case COMMAND_GET_FORM_FIELD_VALUE: + if (args != null && args.size() == 2) { + final int requestId = args.getInt(0); + annotationDisposables.add(root.getFormFieldValue(requestId, args.getString(1))); + } + break; + case COMMAND_SET_FORM_FIELD_VALUE: + if (args != null && args.size() == 2) { + annotationDisposables.add(root.setFormFieldValue(args.getString(0), args.getString(1))); + } + break; } } diff --git a/android/src/main/java/com/pspdfkit/react/events/PdfViewDataReturnedEvent.java b/android/src/main/java/com/pspdfkit/react/events/PdfViewDataReturnedEvent.java index 571f48fe..54a3eaf0 100644 --- a/android/src/main/java/com/pspdfkit/react/events/PdfViewDataReturnedEvent.java +++ b/android/src/main/java/com/pspdfkit/react/events/PdfViewDataReturnedEvent.java @@ -1,6 +1,7 @@ package com.pspdfkit.react.events; import android.support.annotation.IdRes; +import android.support.annotation.NonNull; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.WritableMap; @@ -23,7 +24,7 @@ public class PdfViewDataReturnedEvent extends Event { private final WritableMap payload; - public PdfViewDataReturnedEvent(@IdRes int viewId, int requestId, List annotations) { + public PdfViewDataReturnedEvent(@IdRes int viewId, int requestId, @NonNull List annotations) { super(viewId); Map map = new HashMap<>(); map.put("requestId", requestId); @@ -41,7 +42,7 @@ public PdfViewDataReturnedEvent(@IdRes int viewId, int requestId, List map = new HashMap<>(); map.put("requestId", requestId); @@ -55,6 +56,14 @@ public PdfViewDataReturnedEvent(@IdRes int viewId, int requestId, JSONObject jso payload = Arguments.makeNativeMap(map); } + public PdfViewDataReturnedEvent(@IdRes int viewId, int requestId, @NonNull Throwable throwable) { + super(viewId); + + payload = Arguments.createMap(); + payload.putInt("requestId", requestId); + payload.putString("error", throwable.getMessage()); + } + @Override public String getEventName() { return EVENT_NAME; diff --git a/android/src/main/java/com/pspdfkit/react/helper/JsonUtilities.java b/android/src/main/java/com/pspdfkit/react/helper/JsonUtilities.java index eb483b8b..7d238b01 100644 --- a/android/src/main/java/com/pspdfkit/react/helper/JsonUtilities.java +++ b/android/src/main/java/com/pspdfkit/react/helper/JsonUtilities.java @@ -27,6 +27,8 @@ public static Map jsonObjectToMap(JSONObject object) throws JSON value = toList((JSONArray) value); } else if (value instanceof JSONObject) { value = jsonObjectToMap((JSONObject) value); + } else if ( value == JSONObject.NULL) { + value = null; } map.put(key, value); } @@ -41,6 +43,8 @@ private static List toList(JSONArray array) throws JSONException { value = toList((JSONArray) value); } else if (value instanceof JSONObject) { value = jsonObjectToMap((JSONObject) value); + } else if ( value == JSONObject.NULL) { + value = null; } list.add(value); } diff --git a/android/src/main/java/com/pspdfkit/views/PdfView.java b/android/src/main/java/com/pspdfkit/views/PdfView.java index ec7dec96..ce94b4cc 100644 --- a/android/src/main/java/com/pspdfkit/views/PdfView.java +++ b/android/src/main/java/com/pspdfkit/views/PdfView.java @@ -20,8 +20,14 @@ import com.pspdfkit.document.PdfDocument; import com.pspdfkit.document.formatters.DocumentJsonFormatter; import com.pspdfkit.document.providers.DataProvider; +import com.pspdfkit.forms.ChoiceFormElement; +import com.pspdfkit.forms.ComboBoxFormElement; +import com.pspdfkit.forms.EditableButtonFormElement; +import com.pspdfkit.forms.FormElement; +import com.pspdfkit.forms.TextFormElement; import com.pspdfkit.listeners.SimpleDocumentListener; import com.pspdfkit.react.R; +import com.pspdfkit.react.events.PdfViewDataReturnedEvent; import com.pspdfkit.react.events.PdfViewDocumentSaveFailedEvent; import com.pspdfkit.react.events.PdfViewDocumentSavedEvent; import com.pspdfkit.react.events.PdfViewStateChangedEvent; @@ -33,10 +39,12 @@ import com.pspdfkit.ui.thumbnail.PdfThumbnailBarController; import com.pspdfkit.ui.toolbar.ToolbarCoordinatorLayout; +import org.json.JSONArray; +import org.json.JSONException; import org.json.JSONObject; import java.io.ByteArrayOutputStream; -import java.io.IOException; +import java.util.ArrayList; import java.util.EnumSet; import java.util.List; import java.util.concurrent.Callable; @@ -344,8 +352,8 @@ public void saveCurrentDocument() { eventDispatcher.dispatchEvent(new PdfViewDocumentSaveFailedEvent(getId(), e.getMessage())); } } - } - + } + public Single> getAnnotations(int pageIndex, @Nullable String type) { return fragment.getDocument().getAnnotationProvider().getAllAnnotationsOfType(getTypeFromString(type), pageIndex, 1) .toList(); @@ -420,6 +428,113 @@ public void addAnnotations(ReadableMap annotation) { JSONObject json = new JSONObject(annotation.toHashMap()); final DataProvider dataProvider = new DocumentJsonDataProvider(json); DocumentJsonFormatter.importDocumentJson(fragment.getDocument(), dataProvider); + } + public Disposable getFormFieldValue(final int requestId, @NonNull String formElementName) { + return document.getFormProvider().getFormElementWithNameAsync(formElementName) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Consumer() { + @Override + public void accept(FormElement formElement) throws Exception { + JSONObject result = new JSONObject(); + if (formElement instanceof TextFormElement) { + TextFormElement textFormElement = (TextFormElement) formElement; + String text = textFormElement.getText(); + if (text == null || text.isEmpty()) { + result.put("value", JSONObject.NULL); + } else { + result.put("value", text); + } + } else if (formElement instanceof EditableButtonFormElement) { + EditableButtonFormElement editableButtonFormElement = (EditableButtonFormElement) formElement; + if (editableButtonFormElement.isSelected()) { + result.put("value", "selected"); + } else { + result.put("value", "deselected"); + } + } else if (formElement instanceof ComboBoxFormElement) { + ComboBoxFormElement comboBoxFormElement = (ComboBoxFormElement) formElement; + if (comboBoxFormElement.isCustomTextSet()) { + result.put("value", comboBoxFormElement.getCustomText()); + } else { + result.put("value", comboBoxFormElement.getSelectedIndexes()); + } + } else if (formElement instanceof ChoiceFormElement) { + result.put("value", ((ChoiceFormElement) formElement).getSelectedIndexes()); + } + + if (result.length() == 0) { + // No type was applicable. + result.put("error", "Unsupported form field encountered"); + eventDispatcher.dispatchEvent(new PdfViewDataReturnedEvent(getId(), requestId, result)); + } else { + eventDispatcher.dispatchEvent(new PdfViewDataReturnedEvent(getId(), requestId, result)); + } + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) { + eventDispatcher.dispatchEvent(new PdfViewDataReturnedEvent(getId(), requestId, throwable)); + } + }, new Action() { + @Override + public void run() { + try { + JSONObject result = new JSONObject(); + result.put("error", "Failed to get the form field value."); + eventDispatcher.dispatchEvent(new PdfViewDataReturnedEvent(getId(), requestId, result)); + } catch (Exception e) { + eventDispatcher.dispatchEvent(new PdfViewDataReturnedEvent(getId(), requestId, e)); + } + } + }); + + } + + public Disposable setFormFieldValue(@NonNull String formElementName, @NonNull final String value) { + return document.getFormProvider().getFormElementWithNameAsync(formElementName) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Consumer() { + + @Override + public void accept(FormElement formElement) { + if (formElement instanceof TextFormElement) { + TextFormElement textFormElement = (TextFormElement) formElement; + textFormElement.setText(value); + } else if (formElement instanceof EditableButtonFormElement) { + EditableButtonFormElement editableButtonFormElement = (EditableButtonFormElement) formElement; + if (value.equalsIgnoreCase("selected")) { + editableButtonFormElement.select(); + } else if (value.equalsIgnoreCase("deselected")) { + editableButtonFormElement.deselect(); + } + } else if (formElement instanceof ChoiceFormElement) { + ChoiceFormElement choiceFormElement = (ChoiceFormElement) formElement; + try { + int selectedIndex = Integer.parseInt(value); + List selectedIndices = new ArrayList<>(); + selectedIndices.add(selectedIndex); + choiceFormElement.setSelectedIndexes(selectedIndices); + } catch (NumberFormatException e) { + try { + // Maybe it's multiple indices. + JSONArray indices = new JSONArray(value); + List selectedIndices = new ArrayList<>(); + for (int i = 0; i < indices.length(); i++) { + selectedIndices.add(indices.getInt(i)); + } + choiceFormElement.setSelectedIndexes(selectedIndices); + }catch (JSONException ex) { + // This isn't an index maybe we can set a custom value on a combobox. + if (formElement instanceof ComboBoxFormElement) { + ((ComboBoxFormElement) formElement).setCustomText(value); + } + } + } + } + } + }); } } diff --git a/index.js b/index.js index 595d34bd..ad2f95de 100644 --- a/index.js +++ b/index.js @@ -127,11 +127,11 @@ class PSPDFKitView extends React.Component { UIManager.RCTPSPDFKitView.Commands.saveCurrentDocument, [] ) - } else if (Platform.OS === "ios"){ + } else if (Platform.OS === "ios") { NativeModules.PSPDFKitViewManager.saveCurrentDocument(findNodeHandle(this.refs.pdfView)); } } - + /** * Gets all annotations of the given type from the page. * @@ -158,7 +158,7 @@ class PSPDFKitView extends React.Component { ); return promise - } else if (Platform.OS === "ios"){ + } else if (Platform.OS === "ios") { return NativeModules.PSPDFKitViewManager.getAnnotations(pageIndex, type, findNodeHandle(this.refs.pdfView)); } } @@ -175,8 +175,8 @@ class PSPDFKitView extends React.Component { UIManager.RCTPSPDFKitView.Commands.addAnnotation, [annotation] ); - } else if (Platform.OS === "ios"){ - NativeModules.PSPDFKitViewManager.addAnnotation(annotation, findNodeHandle(this.refs.pdfView)); + } else if (Platform.OS === "ios") { + NativeModules.PSPDFKitViewManager.addAnnotation(annotation, findNodeHandle(this.refs.pdfView)); } } @@ -202,7 +202,7 @@ class PSPDFKitView extends React.Component { ); return promise - } else if (Platform.OS === "ios"){ + } else if (Platform.OS === "ios") { return NativeModules.PSPDFKitViewManager.getAllUnsavedAnnotations(findNodeHandle(this.refs.pdfView)); } } @@ -219,36 +219,58 @@ class PSPDFKitView extends React.Component { UIManager.RCTPSPDFKitView.Commands.addAnnotations, [annotations] ); - } else if (Platform.OS === "ios"){ - NativeModules.PSPDFKitViewManager.addAnnotations(annotations, findNodeHandle(this.refs.pdfView)); + } else if (Platform.OS === "ios") { + NativeModules.PSPDFKitViewManager.addAnnotations(annotations, findNodeHandle(this.refs.pdfView)); } } - + /** * Gets the value of the form element of the fully qualified name. * * @param fullyQualifiedName The fully qualified name of the form element. * * Returns a promise resolving a dictionary with the following structure: - * {'value' : value} or {'error' : 'Failed to get the form field value.'} - * - * @platform ios + * {'formElement' : value} or {'error' : 'Failed to get the form field value.'} */ getFormFieldValue = function (fullyQualifiedName) { - return NativeModules.PSPDFKitViewManager.getFormFieldValue(fullyQualifiedName, findNodeHandle(this.refs.pdfView)); + if (Platform.OS === "android") { + let requestId = this._nextRequestId++ + let requestMap = this._requestMap; + + // We create a promise here that will be resolved once onDataReturned is called. + let promise = new Promise(function (resolve, reject) { + requestMap[requestId] = { 'resolve': resolve, 'reject': reject } + }); + + UIManager.dispatchViewManagerCommand( + findNodeHandle(this.refs.pdfView), + UIManager.RCTPSPDFKitView.Commands.getFormFieldValue, + [requestId, fullyQualifiedName] + ); + + return promise; + } else if (Platform.OS === "ios") { + return NativeModules.PSPDFKitViewManager.getFormFieldValue(fullyQualifiedName, findNodeHandle(this.refs.pdfView)); + } } - + /** * Set the value of the form element of the fully qualified name. * - * @param value The string value form element. For button form elements pass 'selected' or 'deselected'. For choice form elements, pass the index of the choice to select, for example '1'. * @param fullyQualifiedName The fully qualified name of the form element. - * - * @platform ios + * @param value The string value form element. For button form elements pass 'selected' or 'deselected'. For choice form elements, pass the index of the choice to select, for example '1'. */ - setFormFieldValue = function (value, fullyQualifiedName) { - NativeModules.PSPDFKitViewManager.setFormFieldValue(value, fullyQualifiedName, findNodeHandle(this.refs.pdfView)); - } + setFormFieldValue = function (fullyQualifiedName, value) { + if (Platform.OS === "android") { + UIManager.dispatchViewManagerCommand( + findNodeHandle(this.refs.pdfView), + UIManager.RCTPSPDFKitView.Commands.setFormFieldValue, + [fullyQualifiedName, value] + ); + } else if (Platform.OS === "ios") { + NativeModules.PSPDFKitViewManager.setFormFieldValue(value, fullyQualifiedName, findNodeHandle(this.refs.pdfView)); + } + } } PSPDFKitView.propTypes = { diff --git a/samples/Catalog/Catalog.android.js b/samples/Catalog/Catalog.android.js index ab2e9df0..19eb1e4f 100644 --- a/samples/Catalog/Catalog.android.js +++ b/samples/Catalog/Catalog.android.js @@ -119,6 +119,14 @@ var examples = [ component.props.navigation.navigate("PdfViewInstantJsonScreen"); } }, + { + name: "Programmatic Form Filling", + description: + "Shows how to programatically read and write PDF form values.", + action: component => { + component.props.navigation.navigate("PdfViewFormFillingScreen"); + } + }, { name: "Split PDF", description: "Show two PDFs side by side by using PSPDFKitView components.", @@ -599,6 +607,57 @@ class PdfViewInstantJsonScreen extends Component<{}> { } } +class PdfViewFormFillingScreen extends Component<{}> { + static navigationOptions = ({ navigation }) => { + const params = navigation.state.params || {}; + + return { + title: "Programmatic Form Filling" + }; + }; + + render() { + + return ( + + + + +