How can I create a pdf for download in blazor wasm - pdf-generation

In a blazor wasm, I want to create a pdf and make it downloadable. I tested pdfflow, but was only able to make it run in a console application. There are several commercial solutions (devexpress, syncfusion), but they are really expensive.
Next, I stepped over the blog post Generate PDF files using an html template and Playwright, that seemed much promising to me. Unfortunately, it does not contain a complete example and I cannot make it run. Here is, what I tried:
I added template.html to wwwroot folder.
template.html
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="invoice.css">
</head>
<body style="padding: 3rem">
<h1>Invoice</h1>
Awesome company<br />
</body>
</html>
I added a button, that invokes CreatePdf(). I copied the code from the link above and modified it according to my needs (and because e. g. there is no method LoadOrder(orderId) provided).
Index.razor
#page "/"
#using Scriban
#using System.IO
#using Microsoft.Playwright
#inject HttpClient Http
<button class="btn btn-primary" #onclick="CreatePdf">pdf</button>
#code{
private async Task CreatePdf()
{
//var templateContent = File.ReadAllText("template.html");
var templateContent = await Http.GetStringAsync("template.html");
var template = Template.Parse(templateContent);
//var templateData = new { Invoice = LoadOrder(orderId) };
//var pageContent = template.Render(templateData);
var pageContent = "testString";
//var dataUrl = "data:text/html;base64," + Convert.ToBase64String(Encoding.UTF8.GetBytes(pageContent));
var dataUrl = "data:text/html;base64," + pageContent;
I added the following two lines, because "browser" was not declared (Source from playwright).
// Here, the app crashes.
using var playwright = await Playwright.CreateAsync();
var browser = await playwright.Webkit.LaunchAsync();
await using var context = await browser.NewContextAsync();
var page = await context.NewPageAsync();
await page.GotoAsync(dataUrl, new PageGotoOptions { WaitUntil = WaitUntilState.NetworkIdle });
var output = await page.PdfAsync(new PagePdfOptions
{
Format = "A4",
Landscape = false,
});
await File.WriteAllBytesAsync("output.pdf", output);
} }
How can i make above code run?

You cannot use Playwright from a browser (js / wasm). So, you have to use another solution. For instance, you can use jsPDF. This library is not perfect when converting html to pdf, but maybe it will be ok for your usage.
Add a few script references in index.html to use jsPDF (you can also install them using npm if you prefer)
<script src="_framework/blazor.webassembly.js"></script>
<!-- jsPDF references -->
<script src="https://unpkg.com/jspdf#latest/dist/jspdf.umd.min.js"></script>
<script src="https://html2canvas.hertzen.com/dist/html2canvas.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dompurify#2.3.3/dist/purify.min.js"></script>
Create a file wwwroot/HtmlToPdf.js with the following content:
export function generateAndDownloadPdf(htmlOrElement, filename) {
const doc = new jspdf.jsPDF({
orientation: 'p',
unit: 'pt',
format: 'a4'
});
return new Promise((resolve, reject) => {
doc.html(htmlOrElement, {
callback: doc => {
doc.save(filename);
resolve();
}
});
});
}
export function generatePdf(htmlOrElement) {
const doc = new jspdf.jsPDF();
return new Promise((resolve, reject) => {
doc.html(htmlOrElement, {
callback: doc => {
const output = doc.output("arraybuffer");
resolve(new Uint8Array(output));
}
});
});
}
Then, you can use the script from a Razor component:
#inject IJSRuntime JSRuntime
<button type="button" #onclick="DownloadPdf">Generate</button>
#code {
async Task DownloadPdf()
{
await using var module = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./HtmlToPdf.js");
// Generate and download the PDF
await module.InvokeVoidAsync("generateAndDownloadPdf", "<h1>sample</h1>", "sample.pdf");
// Generate the PDF and get its content as byte[] (need .NET 6 to support Uint8Array)
var bytes = await module.InvokeAsync<byte[]>("generatePdf", "<h1>sample</h1>");
}
}

Related

Event on node not calling the function when using the render function

I'm trying to render dynamic charts and therefore need to use the mermaid.render function. But using click does not work when the code isn't entered in a static way as you can see in the snippet bellow:
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
<script>
mermaid.initialize({
theme: "base",
startOnLoad: false,
securityLevel: 'loose'
});
</script>
<p onclick="CreateGraph()">CreateGraph</p>
<div class="mermaid"></div>
<script>
var CreateGraph = function() {
var gdata = `
flowchart
a-->b
click a callback
`;
var div = document.getElementsByClassName("mermaid")[0];
mermaid.render("graphDiv", gdata, (svgCode, bindFunctions) => {
div.innerHTML = svgCode;
});
};
var callback = function() {
alert('A callback was triggered');
};
</script>
But if you were to put the content of gdata and put directly inside the mermaid div (as well as setting startOnLoad to true) then everything would work as expected.
Does anyone have a clue on what's happening or maybe a fix?
Thank you!
Alternative. Use href to include the function call in the parameter.
var gdata = `
flowchart
a-->b
click a href "javascript:callback();"
`;
For the click events to work, after updating the innerHTML of the element you must also set the bindFunctions.
element = document.querySelector('#some_id');
const insertSvg = function (svgCode, bindFunctions) {
element.innerHTML = svgCode;
bindFunctions(element);
};
const graphDefinition = 'graph TB\na-->b';
// Render the graph
const graph = await mermaid.mermaidAPI.render('graphDiv', graphDefinition, insertSvg);

How to load Google API client library with SvelteKit

I'm new to SvelteKit and trying to find out how to load the Google client library for Javascript.
Google tells me to do it like this:
<head>
<script src="https://apis.google.com/js/api.js"></script>
<script>
function start() {
// Initializes the client with the API key and the Translate API.
gapi.client.init({
'apiKey': 'YOUR_API_KEY',
'discoveryDocs': ['https://www.googleapis.com/discovery/v1/apis/translate/v2/rest'],
}).then(function() {
// Executes an API request, and returns a Promise.
// The method name `language.translations.list` comes from the API discovery.
return gapi.client.language.translations.list({
q: 'hello world',
source: 'en',
target: 'de',
});
}).then(function(response) {
console.log(response.result.data.translations[0].translatedText);
}, function(reason) {
console.log('Error: ' + reason.result.error.message);
});
};
// Loads the JavaScript client library and invokes `start` afterwards.
gapi.load('client', start);
</script>
</head>
The problem is that SvelteKit doesn't allow 2 or more script tags on a page (I don't want it to be the layout page).
<script src="https://apis.google.com/js/api.js"></script>
<script>
import { onMount } from 'svelte';
gapi.client.init({...
</script>
This results in follwing error message:
A component can only have one instance-level <script> element
As my intention is to create a progressive web app (PWA) using Workbox I don't want to import the Google library as described here because the package containing this library would become too heavy.
Any ideas how to load the Google client library? Maybe there's a Workbox way to do it? Couldn't find a SvelteKit example on Google or YouTube.
Thanks in advance
The svelte:head tag allows you to add resources to the document head when a component is loaded. This example should work:
<script>
const start = async () => {
// Initializes the client with the API key and the Translate API.
// #ts-ignore
gapi.client.init({
'apiKey': 'YOUR_API_KEY',
'discoveryDocs': ['https://www.googleapis.com/discovery/v1/apis/translate/v2/rest'],
}).then(function() {
// Executes an API request, and returns a Promise.
// The method name `language.translations.list` comes from the API discovery.
return gapi.client.language.translations.list({
q: 'hello world',
source: 'en',
target: 'de',
});
}).then(function(response) {
console.log(response.result.data.translations[0].translatedText);
}, function(reason) {
console.log('Error: ' + reason.result.error.message);
});
};
const initializeGapi = async () => {
gapi.load('client', start);
}
</script>
<svelte:head>
<script src="https://apis.google.com/js/api.js" on:load={initializeGapi}></script>
</svelte:head>
I've made something like this.
Save it as GoogleMap.svelte to your lib folder. and use it like this;
<GoogleMap
{map}
globally
on:load={() => {
console.log('MAP SAYS IM LOADED');
}}
/>
Map is a reference object
globally defines it to window.map
<script>
import { onMount } from 'svelte';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
//import mapStyles from './map-styles'; // optional
export let globally = false;
export let map;
let src = '';
const key = '';
// #ts-ignore
let container;
let zoom = 8;
let center = { lat: 37.5742776, lng: 43.7260158 };
onMount(() => {
Object.assign(window, {
mapLoaded: () => {
// #ts-ignore
map = new google.maps.Map(container, {
zoom,
center
// styles: mapStyles
});
dispatch('load', true);
if (globally) {
Object.assign(window, { map });
}
}
});
//Assign
src = `https://maps.googleapis.com/maps/api/js?key=${key}&callback=mapLoaded`;
});
</script>
<!-- This is tailwind css class change with whatever fits to your case. -->
<div class="w-full h-full" bind:this={container} />
<svelte:head>
{#if src}
<script {src}></script>
{/if}
</svelte:head>

How to get a blob from selected files on GooglePicker

I am using GooglePicker with React, and the result I am getting is an array of objects...
[
{
"id": "1...m",
"serviceId": "docs",
"mimeType": "image/jpeg",
"name": "name.jpg",
"description": "",
"type": "photo",
"lastEditedUtc": 1575388407136,
"iconUrl": "https://drive-thirdparty.googleusercontent.com/16/type/image/jpeg",
"url": "https://drive.google.com/file/d/1...m/view?usp=drive_web",
"embedUrl": "https://drive.google.com/file/d/1...m/preview?usp=drive_web",
"sizeBytes": 111364,
"rotation": 0,
"rotationDegree": 0,
"parentId": "0...A"
}]
So I tried to access through https://www.googleapis.com/drive/v3/files and directly through file.url using
const fetchOptions = { headers: { Authorization: `Bearer ${accessToken}` } };
docs.forEach((file) => {
...
fetch(file.url, fetchOptions).then((res) => {
const blob = res.blob();
uploadFile(blob);
});
});
But I get 403 or CORS; I tried setting the relayUrl in the picker, but this broke the Picker.
Notes:
I have these 3 scopes in my auth2:
['https://www.googleapis.com/auth/drive.file',
'https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/drive.readonly']```
I have my computer's url with port and protocol set as Authorized JavaScript origins and Authorized redirect URIs
Any ideas?
Edit 1:
I also tried using Google API like this:
const FILE_URL = 'https://www.googleapis.com/drive/v3/files';
const url = isDoc
? `${FILE_URL}/${file.id}/export?mimeType=${mimeType}`
: `${FILE_URL}/${file.id}?alt=media`;
fetch(url, fetchOptions).then((res) => {
const blob = res.blob();
uploadFile(blob);
});
You'll need the Drive API
From your question it seems that you are trying to do everything with Google Picker. However, the picker will only get you limited metadata for the files, so you can open them with your account (i.e. see them in another window) or let you upload files. If you want to download the actual file, then you will need to use the Drive API.
Drive Quickstart for browser JavaScript
The flow might be:
let user pick file
get metadata object
extract file id from object
make a call to Drive API (get with alt='media')
If I have misunderstood and you are already using the Drive API, then it would be helpful to see the associated code with that.
Ref
Quickstart
get
export)
EDIT:
Here is an example of using the Picker API to feed into the Drive API with gapi using the same login client.
HTML
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>Google Picker Example</title>
</head>
<body>
<button id="authorize_button" style="display: none;">Authorize</button>
<button id="signout_button" style="display: none;">Sign Out</button>
<div id="result"></div>
<script type="text/javascript" src="script.js"></script>
<script async defer src="https://apis.google.com/js/api.js"
onload="this.onload=function(){};handleClientLoad()"
onreadystatechange="if (this.readyState === 'complete') this.onload()">
</script>
</body>
</html>
JS
const API_KEY = 'AI...';
const CLIENT_ID = '44...';
const appId = "44...";
const SCOPES = ["https://www.googleapis.com/auth/drive"];
const DISCOVERY_DOCS = [
"https://www.googleapis.com/discovery/v1/apis/drive/v3/rest",
];
const authorizeButton = document.getElementById("authorize_button");
const signoutButton = document.getElementById("signout_button");
// Use the Google API Loader script to load the google.picker script.
function handleClientLoad() {
gapi.load("client:auth2:picker", initClient);
}
function initClient() {
gapi.client.init({
apiKey: API_KEY,
clientId: CLIENT_ID,
discoveryDocs: DISCOVERY_DOCS,
scope: SCOPES[0]
})
.then(
function () {
// Listen for sign-in state changes.
gapi.auth2.getAuthInstance().isSignedIn.listen(handleSignIn);
// Handle the initial sign-in state.
handleSignIn(gapi.auth2.getAuthInstance().isSignedIn.get());
authorizeButton.onclick = handleAuthClick;
signoutButton.onclick = handleSignoutClick;
},
function (error) {
appendPre(JSON.stringify(error, null, 2));
}
);
}
function handleSignIn(isSignedIn) {
if (isSignedIn) {
authorizeButton.style.display = "none";
signoutButton.style.display = "block";
createPicker();
} else {
authorizeButton.style.display = "block";
signoutButton.style.display = "none";
}
}
function handleAuthClick(event) {
gapi.auth2.getAuthInstance().signIn();
}
function handleSignoutClick(event) {
gapi.auth2.getAuthInstance().signOut();
}
function createPicker() {
const token = gapi.client.getToken().access_token
if (token) {
let view = new google.picker.View(google.picker.ViewId.DOCS);
view.setMimeTypes("image/png,image/jpeg,image/jpg");
let picker = new google.picker.PickerBuilder()
.enableFeature(google.picker.Feature.NAV_HIDDEN)
.enableFeature(google.picker.Feature.MULTISELECT_ENABLED)
.setAppId(appId)
.setOAuthToken(token)
.addView(view)
.addView(new google.picker.DocsUploadView())
.setDeveloperKey(API_KEY)
.setCallback(getFile)
.build();
picker.setVisible(true);
}
}
function getFile(pickerResp) {
gapi.client.drive.files
.get({
fileId: pickerResp.docs[0].id,
alt: 'media'
})
.then(resp => {
console.log("fetch response", resp.status)
let binary = resp.body
// EDIT - addition from Gabrielle vvvv
let l = binary.length
let array = new Uint8Array(l);
for (var i = 0; i<l; i++){
array[i] = binary,charCodeAt(i);
}
let blob = new Blob([array], {type: 'application/octet-stream'});
// EDIT - addition from Gabrielle ^^^^
}
This code is adapted from the Drive Quickstart and the Picker Quickstart.
Note - this does give an error in the console, but it does seem to work all the same. This does seem to be a bug with the Picker - https://issuetracker.google.com/177046274
EDIT from Gabrielle
Note - using get with alt = media is for binary files. To get sheets/docs/slides etc, you need to use the export end point.

ReactJS + Flux: How to pass data attribute from HTML?

I'm using ReactJS(Flux) and Laravel framework. I need to pass a variable from blade template to React components. I'm trying to use data-x attribute.
My question are..
How can I get the data in React(or Flux architecture)?
Do you have the best practice to store the data in Flux. Is app.js the best place to do it?
Thanks,
index.blade.php
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ReactJS</title>
</head>
<body>
<section id="react" data-domain="{{env("DOMAIN")}}"></section>
<script src="{{ elixir('js/bundle.js') }}"></script>
</body>
</html>
/app.js
var React = require('react');
var SampleApp = require('./components/SampleApp.react');
React.render(
<SampleApp />,
document.getElementById('react')
);
/components/SampleApp.react
var React = require('react');
var SampleApp = React.createClass({
render: function() {
return (
<div>
Hello
</div>
);
}
});
module.exports = SampleApp;
===================================================
Edit 1
I could pass data by using {this.props.domain}. Next step is how to store the data as Flux way.
/app.js
var React = require('react');
var SampleApp = require('./components/SampleApp.react');
var react = document.getElementById('react');
React.render(
<SampleApp domain={react.dataset.domain}/>,
react
);
/components/SampleApp.react
var React = require('react');
var SampleApp = React.createClass({
render: function() {
return (
<div>
Hello {this.props.domain}
</div>
);
}
});
module.exports = SampleApp;
===================================================
Edit 2
Fixed
/index.blade.php
/app.js
/components/SampleApp.react.js
/actions/SampleActionCreators.js
/constans/SampleConstants.js
/dispatcher/SampleAppDispatcher.js
/stores/SampleStore.js
index.blade.php
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ReactJS</title>
</head>
<body>
<section id="react" data-domain="test.example.com"></section>
<script src="{{ elixir('js/bundle.js') }}"></script>
</body>
</html>
/app.js
var React = require('react');
var SampleApp = require('./components/SampleApp.react');
React.render(
<SampleApp />,
document.getElementById('react')
);
/components/SampleApp.react.js
var React = require('react');
var SampleActionCreators = require('../actions/SampleActionCreators');
var SampleStore = require('../stores/SampleStore');
function getSampleState() {
return {
domain: SampleStore.getDomain()
};
}
var SampleApp = React.createClass({
getInitialState: function() {
return getSampleState();
},
componentDidMount: function() {
SampleStore.addChangeListener(this._onChange);
var domain = document.querySelector('#react').dataset.domain;
SampleActionCreators.initData(domain);
},
componentWillUnmount: function() {
SampleStore.removeChangeListener(this._onChange);
},
render: function() {
return (
<div>
Hello {this.state.domain}
</div>
);
},
_onChange: function() {
this.setState(getSampleState());
}
});
module.exports = SampleApp;
/actions/SampleActionCreators.js
var SampleAppDispatcher = require('../dispatcher/SampleAppDispatcher');
var SampleConstants = require('../constants/SampleConstants');
var ActionTypes = SampleConstants.ActionTypes;
module.exports = {
initData: function(domain) {
SampleAppDispatcher.dispatch({
type: ActionTypes.SAMPLE_INIT_DATA,
domain: domain
});
},
};
/constans/SampleConstants.js
var keyMirror = require('keymirror');
module.exports = keyMirror({
ActionTypes: keyMirror({
SAMPLE_INIT_DATA: null
})
});
/dispatcher/SampleAppDispatcher.js
var Dispatcher = require('flux').Dispatcher;
module.exports = new Dispatcher();
/stores/SampleStore.js
var SampleAppDispatcher = require('../dispatcher/SampleAppDispatcher');
var SampleConstants = require('../constants/SampleConstants');
var EventEmitter = require('events').EventEmitter;
var assign = require('object-assign');
var CHANGE_EVENT = "change";
var _domain = "";
var SampleStore = assign({}, EventEmitter.prototype, {
getDomain: function() {
return _domain;
},
emitChange: function() {
this.emit(CHANGE_EVENT);
},
addChangeListener: function(callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener: function(callback) {
this.removeListener(CHANGE_EVENT, callback);
}
});
SampleAppDispatcher.register(function(action) {
switch (action.actionType) {
case SampleConstants.SAMPLE_INIT_DATA:
_domain = action.domain;
SampleStore.emitChange();
break;
default:
// no op
}
});
module.exports = SampleStore;
Your second edit is "correct" Flux way, same one I used on several large scale projects.
More simpler, alternative approach would be to get domain in app.js and pass it to your SampleApp as prop. This requires for DOM from your blade template to be loaded before JS executes. Since you are already putting your JS on bottom you should be good but I added window.onload event just to make it sure.
var React = require('react');
var SampleApp = require('./components/SampleApp.react');
function getDomain() {
return document.getElementById('react').dataset.domain;
}
function initReact() {
var params = {
domain: getDomain()
};
React.render(
<SampleApp {...params} />,
document.getElementById('react')
);
}
window.onload = function() {
initReact();
};
Now you can use {this.props.domain} to access your domain value in SampleApp.reatc.js

PhoneGap upload Image to server on form submit

I am facing problem here as in phonegap image is uploaded to the server once u select a picture.I don't want to upload image before submitting form. Image is uploaded automatically to server which is something i don't want.I want to upload image with the form, where form contains many more fields which is required to send along with image. What are the possible ways to submit with form?
<!DOCTYPE HTML >
<html>
<head>
<title>Registration Form</title>
<script type="text/javascript" charset="utf-8" src="phonegap-1.2.0.js"></script>
<script type="text/javascript" charset="utf-8">
// Wait for PhoneGap to load
document.addEventListener("deviceready", onDeviceReady, false);
// PhoneGap is ready
function onDeviceReady() {
// Do cool things here...
}
function getImage() {
// Retrieve image file location from specified source
navigator.camera.getPicture(uploadPhoto, function(message) {
alert('get picture failed');
},{
quality: 50,
destinationType: navigator.camera.DestinationType.FILE_URI,
sourceType: navigator.camera.PictureSourceType.PHOTOLIBRARY
});}
function uploadPhoto(imageURI) {
var options = new FileUploadOptions();
options.fileKey="file";
options.fileName=imageURI.substr(imageURI.lastIndexOf('/')+1);
options.mimeType="image/jpeg";
var params = new Object();
params.value1 = "test";
params.value2 = "param";
options.params = params;
options.chunkedMode = false;
var ft = new FileTransfer();
ft.upload(imageURI, "http://yourdomain.com/upload.php", win, fail, options);
}
function win(r) {
console.log("Code = " + r.responseCode);
console.log("Response = " + r.response);
console.log("Sent = " + r.bytesSent);
alert(r.response);
}
function fail(error) {
alert("An error has occurred: Code = " = error.code);
}
</script>
</head>
<body>
<form id="regform">
<button onclick="getImage();">select Avatar<button>
<input type="text" id="firstname" name="firstname" />
<input type="text" id="lastname" name="lastname" />
<input type="text" id="workPlace" name="workPlace" class="" />
<input type="submit" id="btnSubmit" value="Submit" />
</form>
</body>
</html>
Create two functions you can call separately. One function for just getting the image, and another function to upload the image.
You can do something like below.
<!DOCTYPE html>
<html>
<head>
<title>Submit form</title>
<script type="text/javascript" charset="utf-8" src="cordova.js"></script>
<script type="text/javascript" charset="utf-8">
var pictureSource; // picture source
var destinationType; // sets the format of returned value
// Wait for device API libraries to load
//
document.addEventListener("deviceready",onDeviceReady,false);
// device APIs are available
//
function onDeviceReady() {
pictureSource = navigator.camera.PictureSourceType;
destinationType = navigator.camera.DestinationType;
}
// Called when a photo is successfully retrieved
//
function onPhotoURISuccess(imageURI) {
// Show the selected image
var smallImage = document.getElementById('smallImage');
smallImage.style.display = 'block';
smallImage.src = imageURI;
}
// A button will call this function
//
function getPhoto(source) {
// Retrieve image file location from specified source
navigator.camera.getPicture(onPhotoURISuccess, onFail, { quality: 50,
destinationType: destinationType.FILE_URI,
sourceType: source });
}
function uploadPhoto() {
//selected photo URI is in the src attribute (we set this on getPhoto)
var imageURI = document.getElementById('smallImage').getAttribute("src");
if (!imageURI) {
alert('Please select an image first.');
return;
}
//set upload options
var options = new FileUploadOptions();
options.fileKey = "file";
options.fileName = imageURI.substr(imageURI.lastIndexOf('/')+1);
options.mimeType = "image/jpeg";
options.params = {
firstname: document.getElementById("firstname").value,
lastname: document.getElementById("lastname").value,
workplace: document.getElementById("workplace").value
}
var ft = new FileTransfer();
ft.upload(imageURI, encodeURI("http://some.server.com/upload.php"), win, fail, options);
}
// Called if something bad happens.
//
function onFail(message) {
console.log('Failed because: ' + message);
}
function win(r) {
console.log("Code = " + r.responseCode);
console.log("Response = " + r.response);
//alert("Response =" + r.response);
console.log("Sent = " + r.bytesSent);
}
function fail(error) {
alert("An error has occurred: Code = " + error.code);
console.log("upload error source " + error.source);
console.log("upload error target " + error.target);
}
</script>
</head>
<body>
<form id="regform">
<button onclick="getPhoto(pictureSource.PHOTOLIBRARY);">Select Photo:</button><br>
<img style="display:none;width:60px;height:60px;" id="smallImage" src="" />
First Name: <input type="text" id="firstname" name="firstname"><br>
Last Name: <input type="text" id="lastname" name="lastname"><br>
Work Place: <input type="text" id="workplace" name="workPlace"><br>
<input type="button" id="btnSubmit" value="Submit" onclick="uploadPhoto();">
</form>
</body>
</html>
You're already sending custom fields in your example.
var params = new Object();
params.value1 = "test";
params.value2 = "param";
options.params = params;
Just populate params with your form fields.
I also faced same problem, but I have done using two server side calls on one click. In this, in first call submit data and get its id in callback using JSON then upload image using this id. On server side updated data and image using this id.
$('#btn_Submit').on('click',function(event) {
event.preventDefault();
if(event.handled !== true)
{
var ajax_call = serviceURL;
var str = $('#frm_id').serialize();
$.ajax({
type: "POST",
url: ajax_call,
data: str,
dataType: "json",
success: function(response){
//console.log(JSON.stringify(response))
$.each(response, function(key, value) {
if(value.Id){
if($('#vImage').attr('src')){
var imagefile = imageURI;
$('#vImage').attr('src', imagefile);
/* Image Upload Start */
var ft = new FileTransfer();
var options = new FileUploadOptions();
options.fileKey="vImage";
options.fileName=imagefile.substr(imagefile.lastIndexOf('/')+1);
options.mimeType="image/jpeg";
var params = new Object();
params.value1 = "test";
params.value2 = "param";
options.params = params;
options.chunkedMode = false;
ft.upload(imagefile, your_service_url+'&Id='+Id+'&mode=upload', win, fail, options);
/* Image Upload End */
}
}
});
}
}).done(function() {
$.mobile.hidePageLoadingMsg();
})
event.handled = true;
}
return false;
});
On server side using PHP
if($_GET['type'] != "upload"){
// Add insert logic code
}else if($_GET['type'] == "upload"){
// Add logic for image
if(!empty($_FILES['vImage']) ){
// Copy image code and update data
}
}
I could not get these plugins to upload a file with the other answers.
The problem seemed to stem from the FileTransfer plugin, which states:
fileURL: Filesystem URL representing the file on the device or a data URI.
But that did not appear to work properly for me. Instead I needed to use the File plugin to create a temporary file using the data uri to get me a blob object: in their example, writeFile is a function which takes a fileEntry (returned by createFile) and dataObj (blob). Once the file is written, its path can be retrieved and passed to the FileTransfer instance. Seems like an awful lot of work, but at least it's now uploading.

Resources