This content originally appeared on SitePoint and was authored by Beardscript
Whenever we find ourselves trying to add any complex functionality to an app, the question arises, “should I roll my own?” And unless your goal is to build that functionality, the answer is almost always a straight “no”.
What you need is something to help you get to an MVP as quickly as possible, and the best way to achieve that is to use a complete out-of-the-box solution that can help you save time, which, in turn, translates into saving on development costs.
I’ll assume that you’re still here because the above resonates with you. So, now that we’re in sync, what I want to show you in this article is how easy it is to integrate Onlyoffice in your web app.
What is Onlyoffice?
From their website:
ONLYOFFICE offers the most feature-rich office suite available, highly compatible with Microsoft Office and OpenDocument file formats. View, edit and collaboratively work with documents, spreadsheets and presentations directly from your web application.
In this case, we are going to be using the Developer Edition, since it’s the best for our purpose, but if you’re looking to integrate with other services like SharePoint then you should check out the Integration Edition.
Developer Edition
The Developer Edition not only gives you enough freedom to integrate the editors within your app, but it also comes with a “White Label” option which lets you fully customize the editors to use them under your own brand.
Document Server Integration
To integrate with your web app, you first need to download the Document Server installation and set it up on your local server.
Another option is to install it with Docker using the following command:
docker run -i -t -d -p 8080:80 onlyoffice/documentserver
After you’ve installed it you can start implementing the requests to handle documents on your server. Onlyoffice provides some very nice examples for .NET, Java, Node.js, PHP, Python and Ruby.
You can download the Document Server and your preferred example and try it straight away on your machine.
I’ll demonstrate how you can go about starting to integrate into your app. For this purpose, we’ll use a very simple example with Node.js and Express. I won’t go into much detail on the implementation, I’ll lay out the bare bone essentials and let you fill in the blanks to build a robust and scalable system.
I have an app with the following structure:
- node_modules
- public
- backups
- css
- main.css
- documents
- sample.docx
- javascript
- main.js
- samples
- new.docx
- new.xlsx
- new.pptx
- app.js
- index.html
- package.json
We’ll use the public/documents
folder to store the documents. The app.js
file is where our Express app code is, and index.html
is where we’ll show our documents. I’ve dropped a sample.docx
file in the documents folder for testing purposes.
The tree files inside public/samples/
are the blank files that we’ll copy when “creating” new files.
The backups
folder, as you’ll see later, will not only help us keep backups of previous versions but also assist us in generating the unique identifier for our documents after modifying them.
The public/css/main.css
and public/javascript/main.js
files will be used by the index.html
. We’ll look into that later.
Let’s take a look at the app.js
file:
const express = require('express');
const bodyParser = require("body-parser");
const path = require('path');
const fs = require('fs');
const syncRequest = require('sync-request');
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(express.static("public"));
app.get("/", (req, res) => {
res.sendFile(path.join(__dirname, "/index.html"));
});
const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`App listening on http://localhost:${port}`));
What we’re doing is serving the files as localhost:3000/documents/filename
.
I’ve also gotten ahead of myself and added syncRequest
, fs
, and bodyParser
. These are not relevant right now but we’ll use them later.
Fetch Documents
To show the available documents we’ll need to get a list of all the filenames and send them to the client. We’ll create the /documents
route for this:
app.get("/documents", (req, res) => {
const docsPath = path.join(__dirname, "public/documents");
const docsPaths = fs.readdirSync(docsPath);
const fileNames = [];
docsPaths.forEach(filePath => {
const fileName = path.basename(filePath);
fileNames.push(fileName);
});
res.send(fileNames);
});
Create Documents
At the beginning we’ll just have a sample document, but that’s no fun at all. Let’s add a /create
route to assist us with adding some files. We’ll simply take a fileName
and copy the corresponding template into the public/documents
folder with its new name:
app.post("/create", async (req, res) => {
const ext = path.extname(req.query.fileName);
const fileName = req.query.fileName;
const samplePath = path.join(__dirname, "public/samples", "new" + ext);
const newFilePath = path.join(__dirname, "public/documents", fileName);
// Copy the sample file to the documents folder with its new name.
try {
fs.copyFileSync(samplePath, newFilePath);
res.sendStatus(200);
} catch (e) {
res.sendStatus(400);
}
});
Delete Documents
We also need a way to delete documents. Let’s create a the /delete
route:
app.delete("/delete", (req, res) => {
const fileName = req.query.fileName;
const filePath = path.join(__dirname, "public/documents", fileName);
try {
fs.unlinkSync(filePath);
res.sendStatus(200);
} catch (e) {
res.sendStatus(400);
}
});
This one’s super simple. We’ll delete the file and send a 200
status code to let the user know it all went fine. Otherwise, they’ll get a 400
status code.
Save Documents
So far, we can open our documents for editing, but we have no way of saving our changes. Let’s do that now. We’ll add a /track
route to save our files:
app.post("/track", async (req, res) => {
const fileName = req.query.fileName;
const backupFile = filePath => {
const time = new Date().getTime();
const ext = path.extname(filePath);
const backupFolder = path.join(__dirname, "public/backups", fileName + "-history");
// Create the backups folder if it doesn't exist
!fs.existsSync(backupFolder) && fs.mkdirSync(backupFolder);
// Remove previous backup if any
const previousBackup = fs.readdirSync(backupFolder)[0];
previousBackup && fs.unlinkSync(path.join(backupFolder, previousBackup));
const backupPath = path.join(backupFolder, time + ext);
fs.copyFileSync(filePath, backupPath);
}
const updateFile = async (response, body, path) => {
if (body.status == 2) {
backupFile(path);
const file = syncRequest("GET", body.url);
fs.writeFileSync(path, file.getBody());
}
response.write("{\"error\":0}");
response.end();
}
const readbody = (request, response, path) => {
const content = "";
request.on("data", function (data) {
content += data;
});
request.on("end", function () {
const body = JSON.parse(content);
updateFile(response, body, path);
});
}
if (req.body.hasOwnProperty("status")) {
const filePath = path.join(__dirname, "public/documents", fileName);
updateFile(res, req.body, filePath);
} else {
readbody(req, res, filePath);
}
});
This is a tricky one, since it’s going to be used by the Document Server when the file is saved by the editor. As you can see, we’re returning "{\"error\":0}"
, which tells the server that it’s all good.
When the editor is closed, the current version of the file will be backed up in public/backups/fileName-history/
with the current time in milliseconds as the file’s name. We’ll use the file’s name later in the front end, as you’ll see.
In this example, we’re replacing the previous backup every time we save a new one. How would you go about keeping more backups?
Fetching backups
We’ll need a way to get the backups for a particular file, so we’re adding a /backups
route to handle this:
app.get("/backups", (req, res) => {
const fileName = req.query.fileName;
const backupsPath = path.join(__dirname, "public/backups", fileName + "-history");
if (!fs.existsSync(backupsPath)) {
return res.send([]);
}
const backupsPaths = fs.readdirSync(backupsPath);
const fileNames = [];
backupsPaths.forEach(filePath => {
const fileName = path.basename(filePath);
fileNames.push(fileName);
});
res.send(fileNames);
});
Here we’re making sure that the backup folder for that file exists, and returning an array of all the backup files in that folder. Yes, this will help you in your task of keeping more backups for a single file. I can’t keep doing all the work for you!
Continue reading Add Office Functionality to Your Web App with OnlyOffice on SitePoint.
This content originally appeared on SitePoint and was authored by Beardscript
Beardscript | Sciencx (2020-12-01T22:58:08+00:00) Add Office Functionality to Your Web App with OnlyOffice. Retrieved from https://www.scien.cx/2020/12/01/add-office-functionality-to-your-web-app-with-onlyoffice/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.