Split html string into multiple pages - android-webview

I am trying to develop eBook reader kind of android application. I need to split long html string(sent by server at run-time) into pages on fly based on the screen space available. Html content is an article which could contain text, images, videos etc.I am using WebView to display the html.
Can anyone give me direction towards how this can be achieved.
Thanks in advance.

Doing any sort of HTML/DOM parsing is going to drive you up the wall, I think, and means that you're effectively starting to develop your own HTML layout engine.
It's a better idea to use the CSS3 column functions. Basically, get your content to render within a fixed width-and-height column. This becomes your page. Then shift your content position left to move between pages, and wrap it in a container that will hide overflow elements.
Our HTML will basically be:
<body>
<div id="container">
<div id="content">
CONTENT GOES HERE
<span id="endMarker"></span>
</div>
</div>
<div>
<button id="previous">Previous</button>
<span id="page">Page N of M</span>
<button id="next">Next</button>
</div>
</body>
Our basic CSS is:
#container {
width: 240px;
overflow: hidden;
background-color: yellow;
}
#content {
position: relative;
height: 30em;
-moz-column-width: 240px;
-webkit-column-width: 240px;
column-width: 240px;
-moz-column-gap: 10px;
-webkit-column-gap: 10px;
column-gap: 10px;
}
Now we will set the left CSS property for the #content to switch between pages, in multiples of -250px.
We've only got to work out how many columns our content takes, and we've got paging. How to get css3 multi-column count in Javascript gives a hint: we're going to find it from the left position of #endMarker.
Here's a working example http://lateral.co.za/pages.html, with the first chapter of Moby Dick. It works in Chrome and on Android, but not in Firefox - I think because of differences in the CSS columns implementations. Since we're interested in Android here, the code is good.
The important parts are:
The CSS settings as above.
The addition of a <span id="endMarker"></span> after the content (but within the #content div)
The addition of a #previous and #next button, and a #page span, all outside the #container.
This javascript after jQuery loads:
var _column = 0;
var _columnCount = 0;
var _columnWidth = 240;
var _columnGap = 10;
$(function() {
_columnCount = Math.floor($('#endMarker').position().left/(_columnWidth + _columnGap));
setColumn = function(i) {
_column = i;
document.getElementById('content').style.left = -1 * _column * (_columnWidth + _columnGap);
$('#page').html('Page ' + (_column+1) + ' of ' + (_columnCount+1));
}
$('#next').click(function() {
if (_column==_columnCount) return;
setColumn(_column+1);
});
$('#previous').click(function() {
if (0==_column) return;
setColumn(_column-1);
});
setColumn(0);
});
That's it.
There's room for work. One might want to think about that number of columns calculation, I sort of sucked it from the referenced SO post, but haven't actually thought it through... The width of the container seems to affect the column width of the content, which doesn't entirely make sense to me.
But at least everything seems to be working without having to do any HTML parsing and own layout, at least in Chrome and on Android.

You have to parse the HTML, best would be to use some library where you can access the DOM like in JavaScript. Then you can create a custom layout for the parsed content. Does WebView supports JavaScript? That would be a good start to try with.
Obviously you can not split at arbitrary locations of the HTML file, your have to consider the HTML-Tags. After modifying the DOM or splitting the html you can provide an custom CSS file for the content to display it in a way you like and add some pagination using your library or JavaScript.
Using <span style="display:none"></span> can help you to hide content in a website. Then you don't have to split is physically (it is in memory after loading the page anyway).
Hope that helped a bit. You will not get a full solution here, but maybe you got some ideas. If you find further more specific problems it will be easier to ask more specific questions.

Related

Vue Leaflet incompatible with Vuetify's VDialog [duplicate]

I am using tabs to display clear content, but one of them stopped downloading well since it is in the data-toggle tab. It is a Leaflet map.
Here is the code :
Navbar code :
<ul class="nav nav-tabs">
<li class="active"><a data-toggle="tab" href="#home">Données principales</a></li>
<li><a data-toggle="tab" href="#carte">Carte</a></li>
</ul>
<div class="tab-content">
<div id="home" class="tab-pane fade in active">Lorem ipsum</div>
<div id="carte" class="tab-pane fade"> **//see script below\\** </div>
</div>
Script :
<div id="carteBenef"></div>
<script type="text/javascript">
$(document).ready(function () {
var map = new L.Map('carteBenef');
var cloudmadeUrl = 'http://{s}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.png',
subDomains = ['otile1', 'otile2', 'otile3', 'otile4'],
cloudmadeAttrib = 'Data, imagery and map information provided by MapQuest, OpenStreetMap and contributors, CC-BY-SA';
var cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18, attribution: cloudmadeAttrib, subdomains: subDomains});
var iades = new L.LatLng(<?php echo $beneficiaire->latitude . ', ' . $beneficiaire->longitude; ?>)
map.addLayer(cloudmade).setView(iades, 15);
var benefLocation = new L.LatLng(<?php echo $beneficiaire->latitude . ', ' . $beneficiaire->longitude; ?>);
var benef = new L.Marker(benefLocation);
map.addLayer(benef);
benef.bindPopup("<?php echo htmlspecialchars($beneficiaire->nom) . ' ' . htmlspecialchars($beneficiaire->prenom); ?>").openPopup();
});
</script>
The map was appearing well before I put it in the tab, does someone have an idea why it does not work now? Thank you =)
Welcome to SO!
If your Leaflet map suddenly works correctly after you resize your browser window, then you experience the classic "map container size not valid at map initialization": in order to work correctly, Leaflet reads the map container size when you initialize the map (L.map("mapContainerId")).
If your application hides that container (typically through CSS display: none;, or some framework tab / modal / whatever…) or later changes its dimensions, Leaflet will not be aware of the new size. Hence it will not render correctly. Typically, it downloads tiles only for the fraction of the container it thinks is shown. This can be a single tile in the top left corner in the case of a container that was entirely hidden at map initialization time.
This mistake often arises when embedding the map container in a "tab" or "modal" panel, possibly using popular frameworks (Bootstrap, Angular, Ionic, etc.).
Leaflet listens to browser window resize event, and reads again the container size when it happens. This explains why the map suddenly works on window resizing.
You can also manually trigger this update by calling map.invalidateSize() when the tab panel is displayed (e.g. add a listener on the tab button click), at least the first time the container is rendered with its correct dimensions.
As for implementing the tab button click listener, perform a new search on SO on that topic: you should have plenty resources available for that matter, for most of the popular frameworks.
First, thank you #ghybs for your good explanation on why the Leaflet maps are not shown properly in these cases.
For those who tried unsuccessfully the #ghybs's answer, you should try to resize your browser instead of calling a method of the map object :
window.dispatchEvent(new Event('resize'));
As #MarsAndBack said, this fix may cause issues on pan/animation/etc features that Leaflet's invalidateSize provides.
I have this problem because i used modal bootstarp. and it not solved anyway. i tried map.invalidateSize() and window.dispatchEvent(new Event('resize')); but not fixed.
finaly it fixed by this:
$('#map-modal').on('shown.bs.modal', function(event) {});
'shown.bs.modal' event means when modal is completely load and not any confuse on size, now inside that write your codes.
For those like me weren't able to solve the challenge with the precious #ghybs explanation (map.invalidateSize()) and were able to do it with #gangai-johann one, there's still a chance you may do it the right way. Put your invalidate instruction inside a setTimeout, as it may be too early to dispatch that instruction.
setTimeout(() => {
this.$refs.mymap.mapObject.invalidateSize()
}, 100)
Refer to https://stackoverflow.com/a/56364130/4537054 answer for detailed instructions.

How to programmatically collapse space in empty div when google ad does not show

Is there any way to programmatically collapse the empty space that results when a google ad does not show? If so, I would love to see an illustrative example of the same.
Searching around has led me to this official Google resource for accomplishing exactly what I've asked. However, that pertains to Doubleclick for Publishers, which is unfortunately a separate product. I'm pining to know how to handle this for AdSense - some of my users are staring at empty spaces at the moment.
In case it matters, here's an example ad snippet provided by Google AdSense (which I've center-aligned):
<div style="text-align:center">
<ins class="adsbygoogle"
style="display:block"
data-ad-client="ca-pub-0000000000000000"
data-ad-slot="0044031319"
data-ad-format="auto"></ins>
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script>
</div>
I know this is old, but since I've been dealing with this now. A simple enough way to do this in jQuery is to check for all elements with the class adsbygoogle that have no child inside.
This selects all the elements with that class and hides them, effectively collapsing them.
$(".adsbygoogle:empty").hide();
You can do a lot of other stuff with it too, like if it's in a div and you need to hide that too, use the $(".adsbygoogle:empty").parent().hide() to collapse it further.
I'm sure this can be done with vanilla javascript as easily. I suggest to run this line of code after the DOM has loaded and even wait like 10 seconds just to see if google populates the ads.
But now it is very simple, just insert this CSS;
<style>
ins[data-ad-status=unfilled] {display:none!important}
</style>
I noticed that the AdSense code broadcasts a MessageEvent, so when I get a resize-me type event, with 'r_nh': 0 key/value pair, I hide the AdSense container (CSS class adsense-ad) manually.
If you have multiple AdSense containers on the same page, you could try to also parse the qid key from the same message.
window.addEventListener("message", (event)=>{
try {
let message = JSON.parse(event.data);
if (message.msg_type === 'resize-me') {
let shouldCollapseAd = false;
for (let index in message.key_value) {
let key = message.key_value[index].key;
let value = message.key_value[index].value;
if (key === 'r_nh' && value === '0') {
shouldCollapseAd = true;
}
}
if (shouldCollapseAd) {
$('.adsense-ad').hide();
}
}
} catch (e) {
}
});
The link provided which refers to DFP Premium at this point redirects to documentation for Google Ad Manager, so it's possible this feature is available without DFP Premium at this point.
Aside from that...
Usually the existence of an iframe element where you expect it is enough to know whether an ad was put where you were expecting one to be put, or not, in my experience.
setTimeout(function () {
if (!document.querySelector('#adcontainer').querySelectorAll('iframe').length > 0) {
document.querySelector('#adcontainer').remove();
}
},1000*2);
As to whether something useful was loaded into that iframe—that isn't something Google is concerned with, so good luck, you'll need it.
I tried to solve it with CSS as Adsense injects various iframe,ins, and divs with various properties.
This code will collapse whitespace but when you ad is in text, it will overflow some of the text, so inline this needs modification:
<style>
iframe { height: auto !important}
ins { height: auto !important}
#google_ads_frame1 { height: auto !important}
.adsbygoogle, #aswift_0_expand, #aswift_0_anchor { height: auto!important} /* there might be more of those */
</style>

How to create custom UI for a MATLAB application?

MATLAB possesses all features I need in my application, except for one - a 21st century look to the applications it builds. The old UI controls with the skin from the 90s just don't cut it anymore. Are there ways to customize the GUI of the compiled window itself? I know of App Designer for MATLAB (which is an alternative for GUIDE as I understood) but that's not close enough. For example, I want a complete flat design look with sprites/buttons/etc drawn in Photoshop for my built application.
Would an external C library/framework be able to build a custom GUI when compiling an application?
I've tried to google this question and it seems like nobody cares about design in the scientific community. I'm not quite sure how to phrase my question, apologies. I hope some of you have a bit of experience with this.
P.S.: Below is the look I'd like to achieve:
As described in my previous comment, and starting from R2019b, you can use uihtml components in new web-based uifigure (introduced in R2016a) to customize style of elements appearing on screen via css + javascript
Here below is a very simple example for a css-styled button:
First the matlab code (TestUiHtml.m):
function [] = TestUiHtml()
%[
% Create the figure and layout
fig = uifigure('Name', 'Test uihtml', 'Color', [1 1 1]);
grid = uigridlayout('Parent', fig, 'ColumnWidth', { '1x', '1x' }, 'RowHeight', {200, '1x' }, 'BackgroundColor', fig.Color);
% Add a uihtml element (that will act like a button using html+css+javascript)
button = uihtml('Parent', grid, 'HTMLSource', 'myway.html', 'DataChangedFcn', #(s,e)onBtnClicked(fig));
% Button layout
button.Layout.Row = 1; button.Layout.Column = [1 2];
%]
end
function [] = onBtnClicked(fig)
%[
uialert(fig, 'World!', 'Clicked', 'Icon', 'info');
%]
end
NB: Above code simply adds in the figure a uihtml component whose property HTMLSource points to the html content you want to have.
Here is the html code to describe the custom uihtml component content (myway.html):
<!DOCTYPE html>
<head>
<link rel="stylesheet" type="text/css" href="myway.css">
<script type="text/javascript" src="myway.js"></script>
</head>
<html>
<body>
<div class="container">
<button class="skewBtn blue" id="btn">Hello!</button>
</div>
</body>
</html>
NB: It is not mandatory to have a fully well-formed html code (i.e. just a <button style="...">Hello</button> would do).
The linked css code to style the html content (myway.css):
body {
background-color: transparent;
}
.container {
display: flex;
justify-content: space-evenly;
align-items: center;
flex-wrap: wrap;
height: 97vh;
}
.container .skewBtn {
width: 180px;
height: 80px;
outline: none;
cursor: pointer;
background: none;
transition: 0.5s;
font-size: 24px;
border-radius: 5px;
font-family: "Lato", sans-serif;
}
.container .skewBtn:hover {
border: none;
color: white;
font-size: 28px;
transform: scale(1.1);
}
.blue {
border: 2px solid #1976D2;
color: #1976D2;
}
.blue:hover {
background-color: #1976D2;
transition: all 0.3s ease-in-out;
}
And finally the javascript code for event/data marshalling between matlab and the html-based component ... or for more advanced programming logic, if any, for the html-content (myway.js):
function setup(htmlComponent)
{
document.getElementById("btn").addEventListener("click", function(event)
{
htmlComponent.Data = 1234; // dummy value to fire event on matlab side
});
}
Marshalling is ensure via the data property of a htmlComponent object which both sides (i.e. matlab/javascript) are listening to for changes. Matlab automatically uses jsonencode/jsondecode in the background so you can pass back and forth almost any structure/values you need directly.
And voilà ... a nice-and-responsive css-styled button in matlab's figure:
It would be of course better to refactor above code as a uibuttonex class modeled on existing uibutton property name/value pairs.
Sarting from R2021a it is eventually possible to add it to app designer library of components as described here (I personally don't use the designer and prefer to code interfaces programmatically).
Some remarks
It is not possible to directly style exiting uibutton, uipanel, uitab, etc... elements => Mathwork does not allow to access uifigure's html nor to inject css/javascript in it easily (see following undocumented-matlab thread and mlapptools project to try to cheat things). For instance a uibutton does not translate to directly <button> markup in uifigure's source but a lot of imbricated <div>, so it is very difficult to style (and will probably change from one release to another).
I would not recommend to create a single uihtml element in the figure and code everything in html+css+javascript, but I would rather recommend to create small and simple custom uibuttonex, etc elements ... else you'll have to deal with a single data blob to connect you interface with the matlab code which can be tricky and difficult to reuse.
Unfortunately it is yet not possible to create custom html-containers in which you can add other child ui-elements (custom or non-custom) ... say bye-bye to create containers similar to uix.GridFlex, uix.CardPanel from the wonderful GUI Layout toolbox or to creating nicely styled uitabex, etc... This is really a missing part !
Looking at source code of uifigure pages, custom ui-elements are also embedded in <iframe></iframe> markups, so it is not possible to inject some global css/javascript code (i.e. you'll have to repeat this for each uihtml element you create). It is not possible (or difficult) then to have multiple uihtml elements to interact with each-other (unless coding everything in a single page or passing through matlab code/events only).
Addendum : Here is a solution to get css-style from parent, unfortunately this does not solve one need to inject this css in the main uifigure source (well this can be done in javascript ... something like if !exist(parent.elements where id="mycss") => parent.document.inject(lalala))
Mathworks is going the good direction with uifigure and html in the background, but it's still a bit too locked for now and missing important containers from the GUI Layout toolbox.
NB: I tried the GUI Layout toolbox elements in uifigure ... some are working (with lot of resizing bugs), other are not working at all (GridFlex). Maybe a new version of the toolbox will come.
Charting
For modern charting you can use javascript libraries like D3js, SciChart, LightningChart, or others!!.
There is some example using D3 on fileexchange

TinyMCE - preserve style and function of selected element

I have decided to 'enhance' a textarea in a form with TinyMCE... however, doing so has interrupted the styling and jQuery functionality of the original element, as TinyMCE wraps that element in an iframe and a few divs. What I'd love to be able to do is to get the TinyMCE functionality (preserving text formatting, etc.) but not lose the styling and functions that I had associated with the original textarea. I looked through the TinyMCE documentation, but couldn't seem to find anything about this. Does anyone have any thoughts on how to accomplish this?
My form features the textarea like so:
<head>
<script>tinymce.init( { selector: 'textarea' } );</script>
</head>
<div class="form-element">
<div class="label-container">
<label for="body">Post</label><span class="warning">Please fill out this field</span>
</div>
<textarea id="body" class="input-field" name="body"></textarea>
</div>
but adding TinyMCE breaks the label/textarea relationship.
Also, jQuery functionality is broken, such as this validation script:
$("form").submit(function(e){
tinyMCE.triggerSave();
var inputFields = $(".input-field");
var proceed = true;
for(var i = 0; i < inputFields.length; i++){
if($(inputFields[i]).val() == ""){
$(inputFields[i]).css("border", "solid 3px #E86F3A");
$(inputFields[i]).prev().find(".warning").show();
var proceed = false;
e.preventDefault();
}
else{
$(inputFields[i]).css("border", "none");
$(inputFields[i]).prev().find(".warning").hide();
};
};
//OTHER STUFF...
});
since the textarea.input-field is no longer picked up in the inputFields variable.
So, in a nutshell, I'm looking for the TinyMCE wrapper to 'inherit' the styling and functionality of the element that it is attached to. Possible?
As you have correctly surmised when you invoke TinyMCE on a <textarea> the original <textarea> is no longer visible on the page and its replaced by an <iframe> and series of <div> elements.
If you want to keep the underlying <textarea> in sync you can use the tinymce.triggerSave() method. This will update all of the underlying <textarea> elements with the current value of the appropriate instance of TinyMCE.
You can do this when someone tries to save/submit the content or you can try to perform this when certain editor events happen (https://www.tinymce.com/docs/advanced/events/#editorevents). Unless you need real time accuracy of the contents of the <textarea> its far easier to call the triggerSave() method right before you perform you jQuery validation.
Note: Putting a border on the <textarea> won't have any impact on TinyMCE as you no longer see the underlying <textarea>. You can certainly try to add CSS to the editor's HTML in real time. The outer border of TinyMCE 4.4 has these classes attached:
class="mce-tinymce mce-container mce-panel"
...but do note that these classes could change over time so if you upgrade TinyMCE check to make sure your CSS still works before upgrading.

CSS multi-column layout of forms in Firefox

It looks like FireFox (my version is 19.0.2 - OSX) doesn't render forms properly using multicolumn layout. It renders normal paragraph text etc properly, but not forms.
The CSS:
fieldset {
-moz-column-count: 2;
column-count: 2;
-webkit-column-count: 2;
}
This is what it looks like in FireFox:
This is what it looks like in WebKit browsers:
Please view this sample code / live demo in FireFox to see that it doesn't render the multi-column layout for form elements.
Is this a bug, or is there something I can do to get it working in FireFox too?
Maybe the multi column attribute can't apply for the moment on this specific markup element that you use (namely fieldset).
Fieldset is dedicated to group some form fields, but I'm not sure it is already implemented to render in multi column...
So I should think that it is a bug that will be corrected in the future...
You can counter this by creating a simple div with flot left for the first form elements... Of course, with this option, you have to manually split the form elements that will be drawned on the left and on the right, you don't have the magic of equals length mutli-colmun anymore...
div#column1{
width : 400px;
float : left;
}
div#column2 {
float : left;
}
<div id="#column1">Your first fields</div>
<div id="#column2">Your second fields</div>