How to insert an image into Word from a URL - javascript

I am currently working on an Office.js add in for Word and I am trying to insert an image from a given Url. I was reviewing the Office.js documentation which is located at :
InlinePicture class (JavaScript API for Word)
I see that they may have a built in functionality of getting the base64 representation from a img url by "getBase64ImageSrc()". The documentation on the dev office website is either misleading or incorrect.
Looking to see if anyone has built a word-addin that inserts an image from a url using "getBase64ImageSrc()"? Or am I looking in the wrong direction.

Need to elaborate more on Mike's answer, to avoid confusion.
Staffer901: you are talking about 2 different subjects on this post.
Inserting Images to the document. which i think is your bottom line question: how to insert an image with an image URL. The options that Michael mentioned, which are basically to insert classic HTML for an image, will work but i would NOT recommend you to use any of them. The reason why is because really what you are doing is storing a reference to the image that has a connection to the internet dependency, which means any user consuming that document must be connected to see the image.
What i DO recommend you to do for image insertion (permanent insertion :) ) is to use the range.insertInlinePictureFromBase64 method. You need to have an additional step to encode the image in the URL to a base64 string, which is what the methods accepts as input parameter and here is a good discussion on how to achieve this.. Check out a sample below showing inserting an InlinePicture on the first paragraph of the document, assumes you have the base64. Note that you can get the current insertion point and insert the pic there if needed as well. insertInlinePictureFromBase64 is a method of any objects that inherits from range, like body, paragraph, content control etc.
here is a sample:
// Run a batch operation against the Word object model.
Word.run(function (context) {
// Create a proxy object for the paragraphs collection.
var paragraphs = context.document.body.paragraphs;
// Queue a commmand to load the style property for all of the paragraphs.
context.load(paragraphs);
// Synchronize the document state by executing the queued commands,
// and return a promise to indicate task completion.
return context.sync().then(function () {
// Queue a command to get the first paragraph.
var paragraph = paragraphs.items[0];
var b64encodedImg = "iVBORw0KGgoAAAANSUhEUgAAAB4AAAANCAIAAAAxEEnAAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACFSURBVDhPtY1BEoQwDMP6/0+XgIMTBAeYoTqso9Rkx1zG+tNj1H94jgGzeNSjteO5vtQQuG2seO0av8LzGbe3anzRoJ4ybm/VeKEerAEbAUpW4aWQCmrGFWykRzGBCnYy2ha3oAIq2MloW9yCCqhgJ6NtcQsqoIKdjLbFLaiACnYyf2fODbrjZcXfr2F4AAAAAElFTkSuQmCC";
// Queue a command to insert a base64 encoded image at the beginning of the first paragraph.
paragraph.insertInlinePictureFromBase64(b64encodedImg, Word.InsertLocation.start);
// Synchronize the document state by executing the queued commands,
// and return a promise to indicate task completion.
return context.sync().then(function () {
console.log('Added an image to the first paragraph.');
});
});
})
.catch(function (error) {
console.log('Error: ' + JSON.stringify(error));
if (error instanceof OfficeExtension.Error) {
console.log('Debug info: ' + JSON.stringify(error.debugInfo));
}
});
Finally note that the setSelectedDataAsync method that Michaels mentioned, was recently updated to support image insertion, you also need to supply the base64 of the image but the benefit is that you get backwards compatibility (it will work with 2013 clients as well) here is a code sample of this:
// assumes a valid base64 is provided as the first parameter.
Office.context.document.setSelectedDataAsync(mybase64, { coercionType: 'image' }, function (result) {
if (result.status == 'succeeded')
app.showNotification("Image inserted");
else
app.showNotification("Error:" + result.error.message + " : " + error.name)
})
Consuming images from the document. This is about getting the base64 from existing images in the document. We have a body. inlinePictures collection you can use to get all the images in the document, and you use the getBase64 method to get the base64 encoded representation of the binary. I would like to know why this is confusing in the documentation, can you please elaborate on that?
I hope this is useful.
thanks and happy coding!
-Juan.

To insert an image from URL in Word, use either the Range.insertHtml method or the Document.setSelectedDataAsync method, depending on your specific scenario and goals.
It looks like there's an error in the documentation for the other method you linked to - I'll make sure that gets corrected, but I don't believe it's the API you're looking for.

Related

Send message from WeChat mini-program to web-view

I'm building WeChat Mini-Program that on one of it's pages has web-view control. For example:
page.wxml
<web-view src="https://..." bindmessage="onWebViewMessage"></web-view>
page.js
const app = getApp();
Page({
onWebViewMessage:function(e) {
console.log(e);
},
onLoad:function() {
}
});
In web-view an HTML page is loaded (index.html), that includes jweixin-1.3.2.js lib from WeChat, for connecting with WeChat API as well as connect to parent Mini-program. Page is empty, no DOM elements, just javascript that will execute when document is loaded.
It has it's javascript something like this:
index.js
document.addEventListener('DOMContentLoaded',function(){
wx.miniProgram.postMessage({data:'test'});
});
I am able to post messages from this document to mini-program without issues. Also can send some mini-program navigation commands such as wx.miniProgram.navigateTo({url:'path/to/page'}); so all seems fine. I can also get callback in Mini-program when web-view has completed loading.
Question:
How can I post message from Mini-program to web-view? For example, to pass a string or an Object to the web-view.
I have been googling for hours and can't seem to find anyone doing it, but I can't believe it's just one-way communication possible.
Any help or idea is appreciated!
I have found an effective way to pass data from mini-program to web-view content, and it seems at this moment in time, this is the only possible way to do it.
Mini-program
1. Base64 module
You will need to be able to convert normal String into Base64 string. Mini-program API has a method for converting byte array into base64 string, but that won't be usable for this purpose. So, create your own module that does that:
File: lib/b64.js
var string2base64 = function(str) {
.... here put your js code for making b64 string ....
return result;
};
module.exports = {
string2base64
};
2. Page with Web-View
In the page that has web-view control, prepare DOM element in wxml file like this:
File: pages/xxx/index.wxml
<web-view src="{{webURL}}" bindload="onWebLoad" binderror="onWebError"></web-view>
Notice that src parameter is now bound to page's webURL property. Whenever page sets value to this property, will automatically be applied to the DOM elemenet.
In file pages/xxx/index.js you will need to add base64 module:
const b64 = require('../../lib/b64.js')
note that require path may vary depending how you have setup your project
and in page's data object, add webURL and webBaseURL properties, like this:
Page({
data: {
webURL:'',
webBaseURL:'https://your/web/app/url',
messageQueue:[],
messageQueueSize:0,
.... other page properties go here ....
},
..... rest of your page code goes here .....
})
Notice that webURL is set to be empty. This means that when page loads, an empty string will be set to DOM object by default.
webBaseURL will explain just in a bit.
messageQueue is an Array that will store pending messages to be sent to web-view.
messageQueueSize is just Array length. Used for better performance, to avoid reading Array.length.
3. Start Message Queue
In onShow callback of the page, set webURL and start interval that will read messageQueue Array every 250ms. You can change the way this is done if you dislike using intervals, this was just simplest way to do theory test.
onShow: function(){
// This will start loading of the content in web-view
this.setData({webURL: this.data.webBaseURL } );
// Sends message from message queue to web-view
let _this = this;
setInterval(function(e) {
if( _this.data.messageQueueSize < 1 ) return;
_this.data.messageQueueSize --;
let msg = _this.data.messageQueue.splice(0,1);
_this.setData({webURL: _this.data.webBaseURL+"#"+msg});
},250);
}
You can see that message is appended to web-view source (url) as a hash.
webBaseURL is used to generate final URL with hash, that is then send to web-view.
4. Add a Message to the Queue
To create a message in message queue, just define following method in your page:
addMessageToQueue: function(obj) {
obj.unique = Math.round(Math.random()*100000);
let msg = b64.string2base64(JSON.stringify(obj));
this.data.messageQueue.push(msg);
this.data.messageQueueSize++;
}
Whenever you call this method, just pass an Object with whatever properties you need it to have, and it will be converted into JSON string, then to base64 string, and finally appended to the message queue.
unique property is added to make generated base64 result always different even if the rest of object properties are the same - I just needed this for the purpose of my project. You can ignore it / remove it if you do not need it.
Since there's interval running and checking on the message queue, all messages added like this will be sent to web-view in the same order they were added to the queue.
Now there's only one thing left - to add hash change listening in the HTML page we have loaded into the web-view:
HTML Web-app
1. Listen to hash change
window.addEventListener("hashchange",function(e){
let messageBase64 = window.location.hash.substr(1);
let json = window.atob( messageBase64 );
let data = JSON.parse(json);
console.log("Received data from mini-program:",data);
});
Tested on Xiaomi Mi8 Pro. I am yet to test on other devices sold in China.
Cheers!

Mirth channelMap in source JavaScript

In my source connector, I'm using javascript for my database work due to my requirements and parameters.
The end result is storing the data.
ifxResults = ifxConn.executeCachedQuery(ifxQuery); //var is declared
I need to use these results in the destination transformer.
I have tried channelMap.put("results", ifxResults);.
I get the following error ReferenceError: "channelMap" is not defined.
I have also tried to use return ifxResults but I'm not sure how to access this in the destination transformer.
Do you want to send each row as a separate message through your channel? If so, sounds like you want to use the Database Reader in JavaScript mode. Just return that ResultSet (it's really a CachedRowSet if you use executeCachedQuery like that) and the channel will handle the rest, dispatching an XML representation of each row as discrete messages.
If you want to send all rows in the result set aggregated into a single message, that will be possible with the Database Reader very soon: MIRTH-2337
Mirth Connect 3.5 will be released next week so you can take advantage of it then. But if you can't wait or don't want to upgrade then you can still do this with a JavaScript Reader:
var processor = new org.apache.commons.dbutils.BasicRowProcessor();
var results = new com.mirth.connect.donkey.util.DonkeyElement('<results/>');
while (ifxResults.next()) {
var result = results.addChildElement('result');
for (var entries = processor.toMap(ifxResults).entrySet().iterator(); entries.hasNext();) {
var entry = entries.next();
result.addChildElement(entry.getKey(), java.lang.String.valueOf(entry.getValue()));
}
}
return results.toXml();
I know this question is kind of old, but here's an answer just for the record.
For this answer, I'm assuming that you are using a Source connector type of JavaScript Reader, and that you're trying to use channelMap in the JavaScript Reader Settings editing pane.
The problem is that the channelMap variable isn't available in this part of the channel. It's only available in filters and transformers.
It's possible that what you want can be accomplished by using the globalChannelMap variable, e.g.
globalChannelMap.put("results", ifxResults);
I usually need to do this when I'm processing one record at a time and need to pass some setting to the destination channel. If you do it like I've done in the past, then you would first create a globalChannelMap key/value in the source channel's transformer:
globalchannelMap.put("ProcID","TestValue");
Then go to the Destinations tab and select your destination channel to make sure you're sending it to the destination (I've never tried this for channels with multiple destinations, so I'm not sure if anything different needs to be done).
Destination tab of source channel
Notice that ProcID is now listed in the Destination Mappings box. Click the New button next to the Map Variable box and you'll see Variable 1 appear. Double click on that and put in your mapping key, which in this case is ProcID.
Now go to your destination channel's source transformer. There you would enter the following code:
var SentValue = sourceMap.get("ProcID");
Now SentValue in your destination transformer has whatever was in ProcID when your source channel relinquished control.

How to display new image in place of old after user uploads it?

There are two conditionals involved here:
1) When the user first enters the page, an image may not already be uploaded and nothing is shown.
2) An image may already be uploaded, in which case it should show and be overridden in real time when a new image is uploaded to replace it.
The whole uploading thing and displaying it right away is taken care of, it's just updating the image in case 2 that I'm struggling with.
When I upload the image, two things happen as can be seen here:
uploadImage('myDirective', d_canvas, 'image/png', function (error, downloadUrl) {
if (! error) {
Meteor.call('updateDatabase', versionId, downloadUrl)
Session.set('imageAvailableAt', downloadUrl)
}
})
First it calls a method to update the database to add the new url. Second it sets the same url in a Session variable. Note that no matter if an image exists or not, the url will be the same and it will overridden automatically if it does.
The image review looks like this in the template:
{{#with uploadedImage}}
<div class="preview-of-uploaded-image">
<img src="{{this}}">
</div>
{{/with}}
And the helper:
var image = Session.get('imageAvailableAt')
return image
All right, this takes care of case 1, and it will display the image very neatly when it is uploaded.
But clearly nothing will show when an image is already uploaded here, and I have played with setting the same Session variable on onRendered, but then it won't update when I upload something new. I've tried different Session variables and conditionals but no luck. I've also tried Tracker.autorun but that doesn't work at all as described in the docs so I gave that up pretty quickly as well.
What should I do?
You are facing a caching issue, the image URL is being cached by the browser and it will continue showing the old version for some time although the actual resource pointed at has been updated.
What you need to do to solve your problem is append a timestamp to the URL when modifying the collection server-side.
In your Meteor method, when updating the collection, be sure to decorate the URL with the current timestamp using something like :
Meteor.methods({
updateDatabase: function(versionId, downloadUrl){
check(versionId, String);
check(downloadUrl, String);
[...]
Collection.update({
downloadUrl: downloadUrl + "?timestamp=" + Date.now()
});
}
});

Parsing a large JSON array in Javascript

I'm supposed to parse a very large JSON array in Javascipt. It looks like:
mydata = [
{'a':5, 'b':7, ... },
{'a':2, 'b':3, ... },
.
.
.
]
Now the thing is, if I pass this entire object to my parsing function parseJSON(), then of course it works, but it blocks the tab's process for 30-40 seconds (in case of an array with 160000 objects).
During this entire process of requesting this JSON from a server and parsing it, I'm displaying a 'loading' gif to the user. Of course, after I call the parse function, the gif freezes too, leading to bad user experience. I guess there's no way to get around this time, is there a way to somehow (at least) keep the loading gif from freezing?
Something like calling parseJSON() on chunks of my JSON every few milliseconds? I'm unable to implement that though being a noob in javascript.
Thanks a lot, I'd really appreciate if you could help me out here.
You might want to check this link. It's about multithreading.
Basically :
var url = 'http://bigcontentprovider.com/hugejsonfile';
var f = '(function() {
send = function(e) {
postMessage(e);
self.close();
};
importScripts("' + url + '?format=json&callback=send");
})();';
var _blob = new Blob([f], { type: 'text/javascript' });
_worker = new Worker(window.URL.createObjectURL(_blob));
_worker.onmessage = function(e) {
//Do what you want with your JSON
}
_worker.postMessage();
Haven't tried it myself to be honest...
EDIT about portability: Sebastien D. posted a comment with a link to mdn. I just added a ref to the compatibility section id.
I have never encountered a complete page lock down of 30-40 seconds, I'm almost impressed! Restructuring your data to be much smaller or splitting it into many files on the server side is the real answer. Do you actually need every little byte of the data?
Alternatively if you can't change the file #Cyrill_DD's answer of a worker thread will be able to able parse data for you and send it to your primary JS. This is not a perfect fix as you would guess though. Passing data between the 2 threads requires the information to be serialised and reinterpreted, so you could find a significant slow down when the data is passed between the threads and be back to square one again if you try to pass all the data across at once. Building a query system into your worker thread for requesting chunks of the data when you need them and using the message callback will prevent slow down from parsing on the main thread and allow you complete access to the data without loading it all into your main context.
I should add that worker threads are relatively new, main browser support is good but mobile is terrible... just a heads up!

JavaScript arrays are not same

Here I attach the screenshots of my console log results. Both of them are Objects. But they are not the same. What could be the problem? One is showing
(5) [{..},{..},{..},{..},{..}]
Another one just shows [].
let tmp_array = [];
this.database.ref('/users/').once('value', (snapshot) => {
snapshot.forEach( (childSnapshot) => {
var key = (childSnapshot.val() && childSnapshot.key) || 'Anonymous';
var name = (childSnapshot.val() && childSnapshot.val().name) || 'Anonymous';
var email = (childSnapshot.val() && childSnapshot.val().email) || 'Anonymous';
tmp_array.push({ key: key, email: email, name: name });
});
this.setState({ data: tmp_array });
this.getImageList(tmp_array);
console.log(tmp_array);
});
let tmp_array2 = [];
lodash.forEach(tmp_array, (value, key) => {
this.storage.ref().child(value.key + '.jpg').getDownloadURL().then(function (url) {
tmp_array2.push({ id: value.key, image: url });
});
});
this.setState({ image: tmp_array2 });
console.log(tmp_array2);
Welcome to programming with web APIs. You have a typical async problem here. I'll explain what's happening in your code, then give you first steps to a solution, point out some further considerations, and finally provide links with more information for you to read up on this incredibly common (but initially equally confusing) topic.
The problem: the download URLs are loaded asynchronously
Your code calls getDownloadURL, which happens asynchronously. This means that your other code continues while the URL is being loaded. Then when the URL is loaded, your callback is called with the URL. This means that any code that needs the URL, must be inside the callback.
This is easiest to see if you simply the code and add some log statements:
console.log("Before starting getDownloadURL");
this.storage.ref().child(value.key + '.jpg').getDownloadURL().then(function (url) {
console.log("Got download URL");
});
console.log("After starting getDownloadURL");
When you run this code it prints:
Before starting getDownloadURL
After starting getDownloadURL
Got download URL
That is probably not the order you expected, but it completely explains why the array is empty when you print it: the URLs haven't been loaded yet, so they haven't been added to the array yet.
The solution: put all code that needs the download URL inside the callback
To solve this problem all code that needs the download URL from the database must be inside the callback (or be called from there). So a first step would be:
let tmp_array2 = [];
lodash.forEach(tmp_array, (value, key) => {
this.storage.ref().child(value.key + '.jpg').getDownloadURL().then(function (url) {
tmp_array2.push({ id: value.key, image: url });
this.setState({ image: tmp_array2 });
console.log(tmp_array2);
});
});
When you run this, you will see that it logs the array as often as there are child nodes/images. Each time it gets a download URL (remember: this happens asynchronously) it adds it to the array, and then tells React that the state has changed (to it can update the UI).
Some additional considerations and problems
There are more pitfalls in my last code snippet still:
Calling setState() repeatedly may result in a UI that is only partially updated, or in flicker. In that case, consider checking if you've gotten all download URLs before calling setState with something like: if (tmp_array2.length === tmp_array.length) this.setState({ image: tmp_array2 });.
The download URL calls happen asynchronously, and may (I think, it depends on the backend implementation of that API) complete in a different order than in which you call them. In other words: the download URL for the second image may be returned before the download URL for the first image. If this is a concern for your application, be sure to either replicate the other information for the image in tmp_array2 (I think you already do this), or consider storing the download URLs in a way that allows you to associate them back with the items in tmp_array.
More info
As I said at the start: this is a very common problem for developers who are new to dealing with asynchronous APIs to run into. So there are probably hundreds more relevant questions/links if you search for them. Here are some of my favorites:
Doug's post on Firebase's asynchronous APIs
Handling Asynchronous Calls (Firebase) in functions
Best way to retrieve Firebase data and return it, or an alternative way
Why Does Firebase Lose Reference outside the once() Function?
JavaScript - Firebase value to global variable
Firebase use query result outside function
A [] is an empty array. The other [{..},{..},{..},{..},{..}] is an array containing 5 objects. Since you didn't specify which temp array is representing which logged array, I cannot help you determine why one array is empty.

Categories

Resources