PhoneGap on iOS with absolute path URLs for assets? - ios

Porting a web app to phoneGap on iOS, we have asset (and ajax) URLs that are absolute paths, e.g.:
<img src="/img/logo.png">
We have the img directory packaged under PhoneGap's www directory. The image will not load with the absolute path, but only with a relative path, e.g.:
<img src="img/logo.png">
Could someone please explain how the URL is being prefixed or translated in this context? I.e.:
<img src="/www/img/logo.png">
does not work either. So what is the URL base used by PhoneGap on iOS?
We've also tried, e.g.:
<img src="file://img/logo.png">
<img src="file:///img/logo.png">
but no go.
We would like to avoid changing the URLs to relative for the port, as absolute path URLs are used throughout the CSS, Ajax code, are set by Sprockets with the Rails backend, etc. How can we just get PhoneGap/UIWebView on iOS to load the assets using the absolute path URLs as written?
I see this question is asked a lot in various forms here on StackOverflow, but I've yet to see a correct answer.

One can get the path to the application in JavaScript by:
cordova.file.applicationDirectory
Since I'm on Android, it says: "file:///android_asset/" ...for example:
var img_path = cordova.file.applicationDirectory + 'www/img/logo.png';
Like this all resources would be found when cross-building for various platforms.

Did some testing and maybe a bit of JavaScript hackery can make it a bit more manageable. This will change all <a> and <img> tags with URL starting with / to be relative to the current file.
Put this into a file and include it with a <script> tag or inject it with stringByEvaluatingJavaScriptFromString:
document.addEventListener("DOMContentLoaded", function() {
var dummy = document.createElement("a");
dummy.setAttribute("href", ".");
var baseURL = dummy.href;
var absRE = /^\/(.*)$/;
var images = document.getElementsByTagName("img");
for (var i = 0; i < images.length; i++) {
var img = images[i];
var groups = absRE.exec(img.getAttribute("src"));
if (groups == null) {
continue;
}
img.src = baseURL + groups[1];
}
var links = document.getElementsByTagName("a");
for (var i = 0; i < links.length; i++) {
var link = links[i];
var groups = absRE.exec(link.getAttribute("href"));
if (groups == null) {
continue;
}
link.href = baseURL + groups[1];
}
});

When checking the absolute path through your iPhone/iPad you would see something like this:
<img src="file:///var/mobile/Applications/7D6D107B-D9DC-479B-9E22-4847F0CA0C40/YourApplication.app/www/logo.png" />
And it will be different on Android or Windows devices so I don't think it's actually a good idea to reference assets using absolute paths in this case.
As an alternative you could consider using the base64 strings in your CSS files:
div#overview
{
background-image: url('data:image/jpeg;base64, <IMAGE_DATA>');
background-repeat: no-repeat;
}

In ionic 5 / angular 12 I have
this.file.applicationDirectory+'/www/assets/pdf/my.pdf'
and it works for my app with
https://ionicframework.com/docs/native/file
https://ionicframework.com/docs/native/document-viewer.

Related

pdf.js rendering as PDF with base64

I am stuck at last point of my application, i am supposed to display user form in PDF which works fine on desktop browsers as they has pdf viewer built in, but for Android / iOS its not working as pdf viewer is missing.
So i was trying to use PDF.js to display it, (to be honest, this is very widely used but documentation is lacking), only catch is i am getting data in base64 format. PDF.js has example on site which shows how to render the base64 data but its not PDF, for that displaying PDF as "PDF" i need to user their "viewer.html" but that does not take base64 data?
closest i have come to Pdf.js: rendering a pdf file using base64... on stack overflow, but i dont know how to use it after PDFJS.getDocument(pdfAsArray)?.
Other link that came across was other link
I dont want to rely on Google / Third party PDF viewer as i dont know how long they will support this.
There are no end-to-end answers on this topic in community so here is my attempt to put something here. (maybe it will help others)
Okay, PDF.js is one way of showing PDF in browser, specially when you don't want to rely on PDF plugin to be installed. In my case, my application generates report in PDF and that can be viewed before downloading but on handheld devices it was not working because of missing PDF viewer plugin.
In my case PDF was sent to browse in base64 string, that I can use to display PDF with <object src="base64-data"...></object>. This works like charm on Chrome / FF but switch to mobile view and it stops working.
<object type="application/pdf" id="pdfbin" width="100%" height="100%" title="Report.pdf">
<p class="text-center">Looks like there is no PDF viewer plugin installed, try one of the below approach...</p>
</object>
In above code it will try to show the PDF or fall back to <p> and show error message. And I Was planning to add the PDF viewer at this point, PDF.js was the choice but was not able to display it. One example on PDF.js with Base64 data shows how to do this but that renders it as an Image not PDF, and I was not able to find solution for that and hence the question, here is what I did,
First add the JavaScript code to convert base64 to array
convert to blob and use viewer.html file packaged with PDF.js to display it as PDF
In case if you are wondering why base64 data, then answer is simple I can create the PDF, read it, send the data to client and delete the file, I don't have to run any cleaner service/cron job to delete generated PDF files
Few Things To Note
Below code is using Flask + Jinja2, change the way base64 is read in html if you are using something else
viewer.html needs to be changed to have required js & css files in proper location (by default their location is relative; you need them to be referred from static folder)
viewer.js looks for pdf.worker.js in predefined location, change that in case its throwing error as above file not found.
viewer.js might throw file origin does not match viewer error in that case as a quick fix comment the code which throws this error and see if that solves the issue (look for that error in viewer.js)
I am not the author of below code, I have just put it together from different places.
Now to the code (so PDF will be displayed when user clicks on button with id="open_id")
Jquery
var pdfDataX = '{{ base64Pdf }}';
var BASE64_MARKER = ';base64,';
PDFJS.workerSrc = "{{ url_for('static', filename='js/pdf.worker.js') }}";
$('#open_id').click(function() {
PDFJS.disableWorker = true;
var pdfAsDataUri = "data:application/pdf;base64," + pdfDataX ;
PDFJS.workerSrc = "{{ url_for('static', filename='js/pdf.worker.js') }}";
// Try to show in the viewer.html
var blob = base64toBlob(pdfDataX, 'application/pdf');
var url = URL.createObjectURL(blob);
var viewerUrl = "{{ url_for('static', filename='viewer.html') }}" + '?file=' + encodeURIComponent(url);
$('#pdfViewer').attr('src', viewerUrl);
// Finish
var mdObj = $('#pdfbin');
mdObj.hide();
mdObj.attr('data', pdfAsDataUri);
mdObj.show();
$('#myModal').modal();
});
var base64toBlob = function(b64Data, contentType, sliceSize) {
contentType = contentType || '';
sliceSize = sliceSize || 512;
var byteCharacters = atob(b64Data);
var byteArrays = [];
for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
var slice = byteCharacters.slice(offset, offset + sliceSize);
var byteNumbers = new Array(slice.length);
for (var i=0; i<slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
var blob = new Blob(byteArrays, {type: contentType});
return blob;
}
$('.save').click(function(e) {
e.preventDefault();
var blob = base64toBlob(pdfDataX, 'application/pdf');
saveAs(blob, 'abcd.pdf'); // requires https://github.com/eligrey/FileSaver.js/
return false;
});
HTML
<object type="application/pdf" id="pdfbin" width="100%" height="100%" title="Resume.pdf">
<p class="text-center">Looks like there is no PDF viewer plugin installed, try one of the below approach...</p>
<iframe id="pdfViewer" style="width: 100%; height: 100%;" allowfullscreen="" webkitallowfullscreen=""></iframe>
</object>
Hope this will be useful for others in future.

Bundling, but per user

We have javascript files which get bundled and compressed using the normal asp.net mvc mechanism.
We also have some javascript files which get transformed via httphandlers to deal with phrases, colour schemes, etc. At present these are simply linked in, could these be compressed and bundled but at the user level?
Unfortunately we can't group these easily, but even if we could we couldn't do it within a global.ascx file without a lot of rejigging. I mention this as it's not simply a case of having bundle1 = french, bundle2=german, etc
Compression I'm assuming could be done via IIS and static compression, but bundling?
thanks
There is no easy way to do this.
The easiest I can see is to skip the whole Bundling and Minification that is shipped with MVC 5.
Handle it yourself. Generate CSS for your user and have it go through this piece of code:
public static string RemoveWhiteSpaceFromStylesheets(string body)
{
body = Regex.Replace(body, #"[a-zA-Z]+#", "#");
body = Regex.Replace(body, #"[\n\r]+\s*", string.Empty);
body = Regex.Replace(body, #"\s+", " ");
body = Regex.Replace(body, #"\s?([:,;{}])\s?", "$1");
body = body.Replace(";}", "}");
body = Regex.Replace(body, #"([\s:]0)(px|pt|%|em)", "$1");
// Remove comments from CSS
body = Regex.Replace(body, #"/\*[\d\D]*?\*/", string.Empty);
return body;
}
Or any CSS minifier for that matter. Just make sure to include proper caching tag for your user and you won't even have to regenerate it too much.
Code taken from Mads Kristensen

Rails Image assets in AngularJS Directive and template

I have Rails 4 Application with AngularJS using these gems:
gem 'angularjs-rails'
gem 'angular-rails-templates'
gem 'asset_sync'
It works great with a template like this:
<img ng-controller='LikePostController'
ng-dblclick='like(post);'
ng-src='{{post.photo.standard}}'
class='lazy post_photo pt_animate_heart'
id='post_{{post.id}}_image'
/>
The Image render correctly. However in my other js
petto.directive('ptAnimateHeart', ['Helper', function(Helper){
linkFunc = function(scope, element, attributes) {
$heartIcon = $("#heart_icon");
if($heartIcon.length == 0) {
$heartIcon = $("<img id='heart_icon' src='/assets/feed.icon.heart.png' alt='Like' /> ");
$(document.body).append($heartIcon);
}
element.on('dblclick', function(event){
$animateObj = $(this);
Helper.animateHeart($animateObj);
});
}
return {
restrict: 'C',
link: linkFunc
}
}])
I got 'assets/feed.icon.heart.png' was not found error from the browser console. I have feed.icon.heart.png located under app/assets/feed.icon.heart.png.
ps: Forget to mention I use assets sync gem to host assets in amazon s3. the image worked well in development but not in production.
Hardcoded asset links only work in development because in production the assets get precompiled. Which means, amongst other things, the filename changes from:
my_image.png
into something like this (it adds and unique md5-hash):
"my_image-231a680f23887d9dd70710ea5efd3c62.png"
Try this:
Change the javascript file extension to: yourjsfile.js.erb
And the link to:
$heartIcon = $("<img id='heart_icon' src='<%= image-url("feed.icon.heart.png") %>' alt='Like' /> ");
For better understanding The Asset Pipeline — Ruby on Rails Guides
You can define the following method somewhere in your helpers, e.g. in app/helpers/application_helper.rb:
def list_image_assets(dir_name)
path = File.expand_path("../../../app/assets/images/#{dir_name}", __FILE__)
full_paths = Dir.glob "#{path}/**.*"
assets_map = {}
full_paths.each do |p|
original_name = File.basename p
asset_path = asset_path p[p.index("#{dir_name}")..-1]
assets_map[original_name] = asset_path
end
assets_map.to_json
end
One can modify the method to work with any assets you wish, not just the ones located in subdirs of app/assets/images as in this example. The method will return a map with all the original asset names as keys and their 'compiled' names as values.
The map returned can be passed to any angular controller via ng-init (not generally recommended, but appropriate in this case):
<div ng-controller="NoController" ng-init="assets='<%=list_image_assets "images_dir_name"%>'"></div>
To make the assets really usable in angular, define a new $scope valiable in the controller:
$scope.$watch('assets', function(value) {
if (value) {
$scope.assets = JSON.parse(value);
}
});
Having this in the $scope, it's possible to use assets names as usual, in e.g. ng-src directives, and this won't brake after the precompile process.
<img ng-src={{::assets['my_image.png']}}/>
Just do the following:
app.run(function($rootScope,$location){
$rootScope.auth_url = "http://localhost:3000"
$rootScope.image_url = $rootScope.auth_url + "/uploads/user/image/"
});
In controller inject dependency for $rootScope
and in views
<img ng-src="{{user.image.url}}" width="100px" height="100px">
Note: It's working great in Rails API and it assumes that you've user object available so that it could specify the correct image in the /uploads/image/ directory

How to make Firefox treat forward and back slashes the same in url or file-paths?

Is there a way to make Firefox treat / like \ and vice versa in the url or local file path (rewrite it)? Through a tweak, add-on, or anything?
this is related to some local pages and links in some files.
P.S. That behavior is already in IE and Chrome.
Actually you should NEVER user backslashes in a URL. Backslahes are not url safe - and although most browsers are somewhat relaxed with them, it can cause problems at many points of the url interpretation.
To ensure you don't have to do that, Windows browsers should be able to understand forward slashes in this context.
Like:
file:///C|/W95/Calc.exe
I heard there is an Addon calles Slashy that may do what you want. I have not tried it, just threw it into Google.
here's something that might help you:
<html>
<head>
<script language="javascript">
function onload() {
var list=document.getElementsByTagName("A");
for(i = 0; i < list.length; i++)
{
if (list[i].href != null && list[i].href.length > 2 && list[i].href.substring(2,1) == ":") {
list[i].href = "file:///"+list[i].href.replace(/\\/g, '\/');
}
}
}
</script>
</head>
<body onload="onload();">
hey you
</body>
</html>
Tested on IE, Chrome, Safari and FF

IIS7.5 not displaying Flash object

I'm writing a MVC web app in ASP.NET MVC, which is supposed to be serving up a Flash object written by one of my colleagues. I don't know any Flash; he doesn't know any C#/ASP.NET; hence the question goes to SO!
The code on my web page looks like this:
<head>
(blah blah blah...)
<script type="text/javascript" src="/FlashStuff/js/swfobject.js"></script>
<script type="text/javascript">
var GP_MLM_flashvars = {};
GP_MLM_flashvars.remote = 'true';
GP_MLM_flashvars.streamprovider = 'localweb';
GP_MLM_flashvars.referer = '';
GP_MLM_flashvars.bgcolor = '#000033';
var GP_MLM_params = {};
GP_MLM_params.menu = 'false';
GP_MLM_params.allowFullScreen = 'true';
GP_MLM_params.salign = 'tl';
GP_MLM_params.scale = 'noscale';
GP_MLM_params.wmode = 'opaque';
GP_MLM_params.bgcolor = '#000033';
var GP_MLM_attributes = {};
GP_MLM_attributes.id = 'GP_MLM';
GP_MLM_attributes.name = 'GP_MLM';
swfobject.embedSWF('/FlashStuff/swf/GP_MLM.swf', 'GP_MLM', '100%', '100%', '9', '/FlashStuff/expressInstall.swf', GP_MLM_flashvars, GP_MLM_params, GP_MLM_attributes);
</script>
</head>
(etc.)
When I debug this page using the VS Development Server, it all appears very happily and works fine. But if I try to debug using my local IIS (7.5) server, the Flash object doesn't get loaded.
I'm guessing I need to do something on IIS to enable using the Flash object - but what?
EDIT: Problem partially solved; the clue came from the "404" error (thanks #Beliskner).
It appears that when you're running under the VS Development Server, your root folder is the project folder, and in my case "/FlashStuff" comes directly off my project folder, so that worked fine.
But when you run off the IIS server, the root folder is the Default Web Site (or whatever site you're using). Now, with a project URL set to "http://localhost/MyTestApp", I have to prefix all my paths with "/MyTestApp", e.g.:
<script type="text/javascript" src="/MyTestApp/FlashStuff/js/swfobject.js"></script>
Changed all the paths; works fine now.
This is a pretty ugly situation now, though - because I am now hard coding deployment-specific information into my app! So if I decide to deploy my app onto an IIS server in a folder called "MyLiveApp", I have to go around changing the file references everywhere! And if I want to debug it - then what? Go changing all the references back to "MyTestApp"?
Obviously I'm not the first developer to come up against this situation, and it is unthinkable that you have to do what I'm saying above. So what is the trick for dealing with this situation?
Have you setup the IIS MIME types? Have you used firefox firebug to check the request isn't 404ing?
Mime types
http://technet.microsoft.com/en-us/library/cc725608(WS.10).aspx - I suggest using the GUI
Extension: ".swf"
Type is: "application/x-shockwave-flash"
Firebug
Firebug network monitor: http://getfirebug.com/network
Edit
Use this to solve your problem: http://www.dailycoding.com/Posts/the_script_tag_runatserver_problem_solution_using_resolveurl.aspx
Try embedding the Flash object in the body of your html page
<head> (blah blah blah...)
<script type="text/javascript" src="/FlashStuff/js/swfobject.js"></script>
</head>
<body>
<script type="text/javascript">
var GP_MLM_flashvars = {};
GP_MLM_flashvars.remote = 'true';
GP_MLM_flashvars.streamprovider = 'localweb';
GP_MLM_flashvars.referer = '';
GP_MLM_flashvars.bgcolor = '#000033';
var GP_MLM_params = {};
GP_MLM_params.menu = 'false';
GP_MLM_params.allowFullScreen = 'true';
GP_MLM_params.salign = 'tl';
GP_MLM_params.scale = 'noscale';
GP_MLM_params.wmode = 'opaque';
GP_MLM_params.bgcolor = '#000033';
var GP_MLM_attributes = {};
GP_MLM_attributes.id = 'GP_MLM';
GP_MLM_attributes.name = 'GP_MLM';
swfobject.embedSWF('/FlashStuff/swf/GP_MLM.swf', 'GP_MLM', '100%', '100%', '9', '/FlashStuff/expressInstall.swf', GP_MLM_flashvars, GP_MLM_params, GP_MLM_attributes);
</script>
(etc.)
</body>
I guess embedSWF is a javascript function to write out the object tag
Yes, you need to add swf as a IIS 7 mime type per site.
I had this same issue with .mp4 files

Resources