Community Tip - Visit the PTCooler (the community lounge) to get to know your fellow community members and check out some of Dale's Friday Humor posts! X
Hello everyone,
I am developing an 2D Experience where a list of files of a "fileRepositoryThing" is displayed. When clicking on one of the items the file should be downloaded.
For that I use the GetFileListingWithLinks of the Repository Thing. When clicking on a item the link is displayed in the "file-Widget" (Remember: 2D widget) and by clicking on the link the file should be downloaded.
This works for all file-types for the Experience Preview in the Browser. But when using this experience on my phone, pictures can not be correctly downloaded. When trying to, I get the following window:
PDF, Excel and so on works on the phone.
A simple scenario looks something like this:
Any Ideas what could be the reason for that? I also tested that on a colleagues phone and it didn't work as well.
For your information, the structure of the link is something like this:
https://mythingworxServer/Thingworx/FileRepositories/myFileRepository/myFirstFolder/foto.jpg
I tried a view workarounds like opening the link in javascript via window.open(link) also leading to the same result.
Any help is appreciated very much.
Best Regards,
Dominik
Solved! Go to Solution.
for the question how to download a file? -I think from one side there are security restictions : ( stackoverflow.com : "JavaScript Automatically Save Image File to Folder")
"There is no way for javascript to access the filesystem, this is due to security concerns. You can store the image data as a string in a lot of places though, such as browser storage. Luckily for you it looks as though you're trying to store it in a web server's folder!"
============
But there are other post's which sound more optimistic like : ( stackoverflow.com : How to save a base64 image to user's disk using JavaScript? ) Actually I tested this approach and was able to download the files but unfortunately they are corrupt - are not completely downloaded so only part of the original file content was saved – but content of this part was ok!)
Therefore I tried to follow another approach using a file widget and trying to simulate a click event:
The File Widget - File URL points to the repository location. Then calling the folliwng script:
//called by buttion click
$scope.testButton= function() {
console.log("test button click");
//this works only if one file Widget exist
let e1=document.querySelector('a.twx-file.twxHyperlink')
fireClick(e1)
}
///==== definition of firClick function:
function fireClick(node){
if (document.createEvent) {
var evt = document.createEvent('MouseEvents');
evt.initEvent('click', true, false);
node.dispatchEvent(evt);
console.log("MoseEvents")
} else if (document.createEventObject) {
console.log("onclick fireEvent")
node.fireEvent('onclick') ;
} else if (typeof node.onclick == 'function') {
console.log("onclick function")
node.onclick();
}
}
//----------------------------------------------------
this will start a downlaod of the specified File URL
I tested it in Preview and on IOS i-Pad 6 and this worked ok in current version. But on IOS it asks for credential
Hi @drieder ,
what is here the final goal. Do you want to display these files/pictures in Studio /vuforia view or these pictures should be only downloaded to the mobile device and should be used later with another applicaiton?
Hi @RolandRaytchev,
I guess both. My best case scenario would be to download it and automatically open in the default application for the file
I used the described technique for direct display in Vuforia Studio/view. So the data was received in memory in a session variable
Important are also 2 things:
- to add the method LoadBinary (respectively LoadImage) for the particular repository thing in your external data
- to set the permission (service execute) for the service (e.g. LoadBinary) for specific user or respectively for the es-public-access.
Why do you to download this file? Do you want to open (let say a particular picture ) file in an external application outside Vuforia view?
I am not quite certain about what will be done with the files in real scenarios.
I just want to have the user to have the option to distribute the files through other channels if he wants to (and for that he would need it saved locally).
On a pc the thingwidget is no problem, you can still right-click on it and select "save picture as". I dont know how it is on mobile
Do you have an idea why pictures can not be downloaded directly? I was just wondering why it does not work the "easy" way eventhough it works for every other file format (even excel)
for the question how to download a file? -I think from one side there are security restictions : ( stackoverflow.com : "JavaScript Automatically Save Image File to Folder")
"There is no way for javascript to access the filesystem, this is due to security concerns. You can store the image data as a string in a lot of places though, such as browser storage. Luckily for you it looks as though you're trying to store it in a web server's folder!"
============
But there are other post's which sound more optimistic like : ( stackoverflow.com : How to save a base64 image to user's disk using JavaScript? ) Actually I tested this approach and was able to download the files but unfortunately they are corrupt - are not completely downloaded so only part of the original file content was saved – but content of this part was ok!)
Therefore I tried to follow another approach using a file widget and trying to simulate a click event:
The File Widget - File URL points to the repository location. Then calling the folliwng script:
//called by buttion click
$scope.testButton= function() {
console.log("test button click");
//this works only if one file Widget exist
let e1=document.querySelector('a.twx-file.twxHyperlink')
fireClick(e1)
}
///==== definition of firClick function:
function fireClick(node){
if (document.createEvent) {
var evt = document.createEvent('MouseEvents');
evt.initEvent('click', true, false);
node.dispatchEvent(evt);
console.log("MoseEvents")
} else if (document.createEventObject) {
console.log("onclick fireEvent")
node.fireEvent('onclick') ;
} else if (typeof node.onclick == 'function') {
console.log("onclick function")
node.onclick();
}
}
//----------------------------------------------------
this will start a downlaod of the specified File URL
I tested it in Preview and on IOS i-Pad 6 and this worked ok in current version. But on IOS it asks for credential
For example I have here a simple project with 2d canvas with 2 widgets:
1.)2D video Widget : "video-1"
2.) 2d Image Widget: "image-1"
The used the following javaScript definition for functions:
////////////////////
$scope.CallRequest=1
$scope.type="gif"
$scope.play= false
//=============
// 1.) call video MP4
// 2.) call picture gif
// 3.) call call other
////////////////////read video from repository
// if play =true will play the video
$scope.GetVideoFromTwxRepository = function(path, play) {
$scope.CallRequest=1
$scope.play =play
$scope.$applyAsync(function() {
$rootScope.$broadcast('app.mdl.CAD-Files-Repository.svc.LoadBinary',
{"path":path}
);} ,500 );
console.log("after calling GetVideoFromTwx");
};
////////////////////////////////////////////////////////////////
//////////////////// read Image from repository
//type could be gif , bmp, jpg etc.
$scope.GetImageFromTwxRepository = function(path, type) {
$scope.CallRequest=2
$scope.type=type
$scope.$applyAsync(function() {
$rootScope.$broadcast('app.mdl.CAD-Files-Repository.svc.LoadBinary',
{"path":path}
);} ,500 );
console.log("after calling GetImageFromTwx");
};
//------------------------------------------
$scope.$root.$on('LoadBinary-complete', function(event, args) {
console.log("LoadImage-complete event");
console.log("name="+event.name)
console.warn(args.data);
console.warn(args.data[0]);
if ( $scope.CallRequest==1) {
console.log("$scope.CallRequest==1")
var ret_str='data:video/mp4;base64,'+args.data[0].Content;
$scope.view.wdg['video-1']['videosrc']=ret_str;
console.warn($scope.view.wdg['video-1']['videosrc'])
if($scope.play==true)
$timeout(function () { $scope.$root.$broadcast('app.view["Home"].wdg["video-1"].svc.play'); }, 500);
}
else if($scope.CallRequest==2) {
console.log("$scope.CallRequest==2")
var ret_str='data:image/'+$scope.type+';base64,'+args.data[0].Content;
$scope.view.wdg['image-1']['imgsrc']=ret_str;
}
else if ($scope.CallRequest==3) {
console.log("call other option")
}
});
//------------------------------------------
Here I used the TWX service 'app.mdl.CAD-Files-Repository.svc.LoadBinary' to load both image, or binary - this is the more generic service (for picture we have also LoadImage with also the corresponding -complete event)
In the service call in the function s. GetImageFromTwxRepository or GetVideoFromTwxRepository the code will call the service LoadBinary and will pass a path argument.
When the service is completed Vuforia Studio/View will call the 'LoadBinary-Complete" event. In this event the received data will be set to image or video widgets. I used variable to make difference between the current call. But this could be sometime disadvantageous - because the call is asynchronous and there could be a problem when we call the functions for different types successive without delay (e.g. video und image) . There is definitely a better solution where we can synchronize - but here it should display only the general principle (e.g. one possible solution is to use the LoadImage service and the corresponding event LoadImage-complete)
The following call:
//$rootScope.$on('modelLoaded', function() {
$scope.$on('$ionicView.afterEnter', function() {
$timeout( function () {$scope.GetImageFromTwxRepository('Image/n22-05-19-01.jpg','jpg');},500);
$timeout( function () {$scope.GetImageFromTwxRepository('Image/muuu.jpg','jpg');},2000);
$timeout( function () {$scope.GetVideoFromTwxRepository('/web/c-belt-converyor.mp4',true);},10000);
});
The code above will be called when the 2D canvas is loaded and it will do:
- read a jpg file form repository and set it to the image-1 / delay 500ms 0.5sec
-read a second jpg file form repository and set it to the image-1 / delay 2000ms -2sec
-read a video file from repository and set it to video-1 2d widget and play it / delay 10000ms - 10sec
Hello @RolandRaytchev,
thank you for your detailed explanation. In this case I will add a query to check whether the file is an image and handle it in the way explained by you.
I hope it will be still possible to download the image as well.
Best Regards,
Dominik
So I tried this approach in a bit simplyfied way as seen bellow:
$scope.GetImageFromRepo = function(){
$scope.$applyAsync(function(){
$rootScope.$broadcast('app.mdl.deviceFileRepo.svc.LoadBinary',
{"path": '/device/foto.jpg'}
);} ,500 );
};
$scope.$root.$on('LoadBinary-complete', function(event, args){
console.log("LoadImage-complete event");
console.log("name="+event.name)
console.warn(args.data);
console.warn(args.data[0]);
//console.warn(args.data[0].Content);
$scope.type = 'jpg';
var ret_str='data:image/'+$scope.type+';base64,'+args.data[0].Content;
$scope.view.wdg['imageDisplay']['imgsrc'] = ret_str;
But unfortunately the image is not showing. This is the result of the console:
})
I see that the data in your case is comming in the debugging console and seem to be ok. the problem is the setting of the property , I thing.
Here is my code , which I tested to load jpg and works fine:
$scope.$root.$on('LoadBinary-complete', function(event, args) {
console.log("LoadImage-complete event");
var ret_str='data:image/'+$scope.type+';base64,'+args.data[0].Content;
$scope.view.wdg['image-1']['imgsrc']=ret_str;
});
...
//where the call of the fuction is
$timeout( function () {$scope.GetImageFromTwxRepository('Image/n22-05-19-01.jpg','jpg');},500);
$timeout( function () {$scope.GetImageFromTwxRepository('Image/muuu.jpg','jpg');},2000);
....
// and the definiton of $scope.GetImageFromTwxRepository
$scope.GetImageFromTwxRepository = function(path, type) {
$scope.type=type
$scope.$applyAsync(function() {
$rootScope.$broadcast('app.mdl.CAD-Files-Repository.svc.LoadBinary',
{"path":path}
);} ,500 );
};
So from your description I could not see if this 2d or 3d image because there is difference
My example is for 2d image. For 3d your need to use the 'src' property
Here an another example vor svg display to explain the principle of usage:
$scope.svg_ret = function(txt,imgWidget) {
var txtL=""
txtL=txt
var xmlsrc='<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'
xmlsrc=xmlsrc+'<svg xmlns="http://www.w3.org/2000/svg" height="1000" width="600">\n'
xmlsrc=xmlsrc+'<rect x="50" y="300" width="200" height="100" style="fill:red"/>\n'
xmlsrc=xmlsrc+'<circle cx="350" cy="300" r="40" stroke="black" stroke-width="3" fill="red" margin-top="300px" />\n'
xmlsrc=xmlsrc+'<text x="10" y="20" style="fill:red;">Several lines:\n'
xmlsrc=xmlsrc+'<tspan x="10" y="45">First line.</tspan>\n'
xmlsrc=xmlsrc+'<tspan x="10" y="70">Second line.</tspan>\n'
xmlsrc=xmlsrc+'</text>\n'
xmlsrc=xmlsrc+'</svg>'
console.log('here $scope.view.wdg['+imgWidget+'].src=')
console.warn('data:image/svg+xml;base64,'+xmlsrc)
$scope.view.wdg[imgWidget].src='data:image/svg+xml;base64,'+btoa(xmlsrc);
}
We can see there 2 important points.
1.) we generate a some data which is not base64 coded.
So to use the data into the src property we need to code it to base 64 by the btoa() javascript function
2.) you need to add to the based64 a prefex
...['src']="data:"+TYPE + ";base64," +dataInBase64
where the Type could be ( image/jpg or image/gif or video/mp4 etc...)
The data what we received with the LoadBinary service is already as base64 and the btoa() is not required