tag:blogger.com,1999:blog-26608918243596041952024-02-20T01:02:21.850-08:00Java, JavaScript, jQuery, & GWTSteve Claflinhttp://www.blogger.com/profile/03658610422946111549noreply@blogger.comBlogger3125tag:blogger.com,1999:blog-2660891824359604195.post-22202746530283270682013-02-01T06:25:00.001-08:002013-04-30T18:02:17.731-07:00CSS Resources: @Import and @ImportedWithPrefix<h1>
CSS Resources</h1>
<h1>
<code>@Import</code> and <code>@ImportedWithPrefix</code></h1>
A CSS Resource can import other CSS resources. GWT supplies two annotations
for this: <code>@Import</code> and <code>@ImportedWithPrefix</code>.<br />
Unfortunately, the online examples and tutorials that I have found for this
are woefully incomplete. They all suffer from what I have found to be an ever-increasing
approach to programming discussions and documentation: they have either
been written hypothetically (i.e., the code they list was never actually run),
or, if they did come from a working example, the small details crucial to understanding
and implementing the concept have been left out.<br />
Plus, to make things even more confusing, the <code>@Import</code> annotation
does not produce the same effect as using <code>@import</code> within a CSS
file. When used in a CSS file associated with a <code>CssResource</code>, <code>@import</code> doesn't
seem to do anything.<br />
So, below is my attempt at describing these two annotations and how they are
used. <br />
A <code>CssResource</code> sub-interface can have a prefix automatically prepended
to its style class names by annotating it with the <code>@ImportedWithPrefix(<span class="subst">"<span class="subst">prefix</span>"</span>)</code> annotation.
As a result, all the style class names
will have the prefix prepended, regardless of whether the CSS resource is imported
into another, of just used without importing it anywhere(a better name for the annotation might have been @PrefixedWith). In the associated
CSS file, however, you should write the class names without the prefix (which
would match the method names in the interface).<br />
Another CSS resource can import one or more additional <code>CssResource</code> classes
by creating a method that returns a <code>CssResource</code>, annotated with <code>@Import</code>.
The value supplied for the annotation can be a single class object for another <code>CssResource</code>,
or an array of those class objects. Given that the imported class names will
have the prefix regardless of whether they're imported anywhere, the real
benefit of this is that the associated stylesheet can modify the rules involving
the imported class name (but, they would now use the prefix).<br />
For any class names that the importing CSS does want to use, there must be
an associated method in the interface (but, without any prefix). It appears
that the GWT compiler, as of GWT 2.5, will allow the CSS file to have any prefixed
class names as long as the end part of the name is declared as a method in
the interface, even if the specific prefix applied doesn't match anything that
was imported.<br />
<pre>@ImportedWithPrefix("<span class="subst">prefix</span>")
public interface <span class="subst">CssResourceNameToBePrefixed</span> extends CssResource {
public String <span class="subst">accessorMethods</span>();
}</pre>
Within the associated CSS file, all the style class names used in the interface
must be defined, but <i>without</i> the prefix. When the resource is imported
into another, the resulting class definitions will have the prefix (of course,
you won't be able to tell, since they will be obfuscated).<br />
Note that by doing this, you are locking this resource into always using the
specified prefix. <br />
<pre>public interface BundleClass extends ClientBundle {
@Source(value="<span class="subst">CssResourceToBePrefixedCssFilePath</span>")
public <span class="Subst">CssResourceNameToBePrefixed</span> <span class="subst">accessorMethods</span>();
@Import(value=<span class="subst">Class<? extends CssResource></span>)
@Source(value="<span class="subst">ImportingCssResourceCssFilePath</span>")
public <span class="Subst">ImportingCssResourceClassName</span> <span class="subst">accessorMethods</span>();
}</pre>
The CSS file associated with the new CSS resource can also contain rules involving
the imported classes, but now those rules <i>must</i> include the prefix. <br />
But, to use the prefixed rules, <i>your client bundle must have an instance
of the CSS resource that originally defined them</i>, not just the one that
imported them. So, the client bundle containing the code snippet above, you
must also define a method to retrieve the original CSS resource (the one
with <code>@ImportedWithPrefix</code>). In fact, a CSS resource that uses the <code>@ImportedWithPrefix</code> annotation
doesn't even have to be imported into any other - you can just invoke the accessor
methods to retrieve the obfuscated name, which will be different from the same
name defined in a different CSS resource with a different prefix or no prefix.<br />
But, if this resource is imported into another, any rules added
by the importing resource's CSS file will be applied also, since that caused
the CSS to be generated and injected.<br />
<h2>
A Working Example</h2>
<a href="http://www.steveclaflin.com/blog-stuff/gwt/CssResourceImports.zip">The Eclipse Project</a> <br />
Note that in order to minimize the file size, the <code>gwt-servlet.jar</code> file is not included. After unzipping and importing into an Eclipse workspace,
you can usually reinstate it by going to the project's properties, under the
<i>Google</i> ... <i>Web Toolkit</i> sections, removing the check from <i>Use Google Web Toolkit</i>,
then OK. Then go back through the same process to put the check back and OK
again. <br />
You can try the code below to see all three CSS resources in action. There
are embedded notes regarding code to comment out to try it without any instance
of the
<code>Styles</code> resource - they must be done as a group.<br />
<pre>/* Styles.css */
@external .gwt-HTML;
.special { font-size: 14pt; }
/* Try commenting out the two x-special rules below */
.a-special { font-size: 18pt; }
.b-special { font-size: 22pt; }
/* Not related to the example, just to make things look nicer */
html .gwt-HTML {
margin-top: 12px;
border: 1px solid blue;
padding: 12px;
border-radius: 6px;
}</pre>
<pre>// Styles.java
package whatever.client;
import com.google.gwt.resources.client.CssResource;
public interface Styles extends CssResource {
public String special();
}</pre>
<pre>/* AStyles.css */
.special { background-color: #ffaaaa; }</pre>
<pre>// AStyles.java
package whatever.client;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.resources.client.CssResource.ImportedWithPrefix;
@ImportedWithPrefix("a")
public interface AStyles
//extends Styles // would also work with this instead of below
extends CssResource {
public String special();
}</pre>
<pre>/* BStyles.css */
.special { background-color: #aaaaff; }</pre>
<pre>// BStyles.java
package whatever.client;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.resources.client.CssResource.ImportedWithPrefix;
@ImportedWithPrefix("b")
public interface BStyles
//extends Styles // would also work with this instead of below
extends CssResource {
public String special();
}</pre>
<pre>// SomeResources.java
package whatever.client;
import com.google.gwt.core.client.GWT;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource.Import;
public interface SomeResources extends ClientBundle {
/* Try commenting out the three lines below, after
* commenting out the prefixed lines in the Styles.css file
*/
@Import({AStyles.class, BStyles.class})
@Source("Styles.css")
public Styles styles();
@Source("AStyles.css")
public AStyles aStyles();
@Source("BStyles.css")
public BStyles bStyles();
public static final SomeResources INSTANCE =
GWT.create(SomeResources.class);
}</pre>
In the code below, the lines marked with <code>/**/</code> are to be commented
out to try it without any instance of the <code>Styles</code> class.<br />
<pre>// SomeResources.java
package whatever.client;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.*;
public class CssResourceImports implements EntryPoint {
private static final SomeResources resources
= SomeResources.INSTANCE;
private static final AStyles aStyles = resources.aStyles();
private static final BStyles bStyles = resources.bStyles();
private static final Styles styles = resources.styles(); /**/
public void onModuleLoad() {
aStyles.ensureInjected();
bStyles.ensureInjected();
styles.ensureInjected(); /**/
FlowPanel panel = new FlowPanel();
HTML special = new HTML("Special"); /**/
special.addStyleName(styles.special()); /**/
panel.add( special ); /**/
HTML aSpecial = new HTML("A Special");
aSpecial.addStyleName(aStyles.special());
panel.add( aSpecial );
HTML bSpecial = new HTML("B Special");
bSpecial.addStyleName(bStyles.special());
panel.add( bSpecial );
RootPanel.get().add( panel );
}
}</pre>
Steve Claflinhttp://www.blogger.com/profile/03658610422946111549noreply@blogger.com0tag:blogger.com,1999:blog-2660891824359604195.post-53134132131639911112013-01-09T08:28:00.000-08:002013-02-01T06:17:40.280-08:00Scalable IFrame Video<h1>
Scalable Video </h1>
<p>It's easy to scale <code><img></code> and <code><video></code> elements with window width. The "max-height:
100% trick" makes this task straightforward:</p>
<pre>img.scalable, video.scalable {
max-width: 100%;
}</pre>
<div class="float-right-box" id="viddiv" style="width: 50%;">
<div style="max-width: 100%; position: relative; width: 420px;">
<div style="max-width: 100%; overflow: hidden; width: 420px;">
<img src="http://www.steveclaflin.com/blog-stuff/html/vid-mock-3x4.gif" style="max-width: 100%;" />
<img src="http://www.steveclaflin.com/blog-stuff/html/vid-controls.gif" style="height: 36px; width: 100%;" />
</div>
<iframe allowfullscreen="allowfullscreen" frameborder="0" height="315" src="http://www.youtube.com/embed/0WyhMV81dro" style="height: 100%; left: 0; position: absolute; top: 0; width: 100%;" width="420"></iframe>
</div>
<p>Width (%): <input id="widthpct" name="widthpct" size="6" style="text-align:right;" onchange="document.getElementById('viddiv').style.width=parseInt(this.value)+'%';" type="text" value="50" /></p>
</div>
<p>It's not really a trick - it uses features that have been around in HTML
more or less forever. If you give an image <code>height</code> and <code>width</code> attributes,
it will scale to that size, even if that distorts the aspect ratio. However,
if you give it only one of the two values, the other will scale to match,
keeping the original aspect ratio. This behavior carries over to measurements
provided via CSS rather than as tag attributes.</p>
<p>When you use <code>max-width: 100%</code>, and the container element becomes narrower
than the image's or video's natural size, it becomes a similar constraint
on the width, and the height scales to match. (One thing that this means
is that you're not necessarily limited to using 100%. You can set <code>max-width</code>
to whatever percentage you wish.)</p>
<p>But, when the video is embedded in an <code><iframe></code> element,
in particular an iframe that is from a different server, things become more
difficult. You can't get inside a foreign iframe to stylize it's elements,
and iframes don't have a concept of a natural aspect ratio.</p>
<p>Yet another issue is that the space needed by a video won't actually have
a constant aspect ratio if it has controls - the controls have a fixed
height regardless of the width. </p>
<p>I have seen solutions that rely on <code>padding-bottom</code> using a percentage,
taking advantage of the fact that vertical padding and margins expressed
in percentages are percentages of the element's <em>width</em>, not its height.
My experience has been that this usually requires a bit of tinkering to
get right, and adds some not-so-intuitive markup. I would like to propose
what I believe is a simpler and more robust approach. While it does add
some markup, I think that it's easier to understand what it does.</p>
<p>The video embedded here uses a that approach. Rather than mess around trying to break my template styles in order that the column be resizable, I added a text box that you can use to enter a width (in percent of the column width) for the video iframe. If you want to see a more dynamic, window-width-based version, see the <a href="http://www.steveclaflin.com/blog-stuff/html/scalable-video.html">Scalable Iframe Video</a> page at my site.</p>
<h3>Mocking the Video</h3>
<p>Not, I'm not going to make fun of it - I'm going to create a mockup using
transparent GIF images. The total space occupied by the video will usually
consist of two pieces: the video itself, which has a fixed aspect ratio,
and a controller, which will have a fixed height. The
combination of those elements will therefore not have a fixed aspect ratio.</p>
<p>But, we can build a div using two transparent images with the expected
sizes. In the model I tested, the video has a 4/3 aspect ratio, so I used
a 420 * 315 pixel image as a mock. I also used a 420 * 36 pixel image to
mock the controls. The key aspect of the associated CSS is that the mock
image for the video itself has <code>max-width: 100%</code> and no height
specification, but the controls image has the height fixed at 36px.</p>
<p>I put both of these in in a container div which in turn sits in a larger,
outer container div, along with the iframe for the video.</p>
<p>The combined height of the two mock images determines the height of their
immediate container. The width of that container, however, is determined
by its parent.</p>
<p>The parent that contains the assembled mock div and the iframe has
<code>position:relative;</code>, in order to serve as an offset parent to its children. The mock
div is unpositioned, so that it sits at 0,0 by default, and also serves
to determine the height of the container. The iframe, however is absolutely
positioned at 0,0, and has height and width of 100%, which is what makes
it assume the aspect ratio of the container. </p>
<pre style="float:right;width:50%;">
<div class="vidFrame">
<div class="vid-box">
<img src="vid-mock-3x4.gif" class="vid-mock">
<img src="vid-controls.gif" class="vid-controls">
</div>
<iframe width="420" height="315"
src="http://www.youtube.com/embed/0WyhMV81dro"
frameborder="0"
allowfullscreen></iframe>
</div>
</pre>
<div>
<pre>.vid-box {
width: 420px;
overflow: hidden;
max-width: 100%;
}
.vid-box img {
display: block;
width: 100%;
}
.vid-mock {
max-width: 100%;
}
.vid-controls {
height: 36px;
max-width: 100%;
}
.vidFrame {
position: relative;
width: 420px;
max-width: 100%;
}
.vidFrame iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
</pre>
</div>
Steve Claflinhttp://www.blogger.com/profile/03658610422946111549noreply@blogger.com0tag:blogger.com,1999:blog-2660891824359604195.post-61848338314374682512012-04-07T11:28:00.002-07:002012-04-07T11:45:32.508-07:00jQuery delegate and on subqueriesI was puzzled at first when I found out that <code>$.fn.delegate</code> and <code>$.fn.on</code> do not use the current collection as the context for the subquery. Instead, they run the query from the document root. In other words, with the following HTML structure:<br />
<pre><div id='blog'>
<ul>
<li>
<h3>I Like Ice Cream</h3>
<div>
<h3>My Favorite Flavors</h3>
<ul>etc.</ul>
</div>
</li>
<!-- more li's with same structure -->
</ul>
</div>
</pre>I wanted to set up click handling on the <code>h3</code> tags to replace the brief summary content in the content div's below with the full entries loaded via Ajax, but ensure that any <code>h3</code> elements in the content div's did not accidentally become clickable. And, since the div content is arbitrary, that would be a possibility.<br />
<br />
So, I tried:<br />
<pre>$('#blog>ul').delegate('click', '>li>h3', handler);
</pre> But, that fails. The <code>$.fn.find</code> method can use a selector that begins with <code>'>'</code> to indicate that the selector path must begin with the direct children of the starting element. Like,<br />
<pre>$('#blog>ul').find('>li>h3').text();
</pre>would return 'My First Entry'. That works because <code>find</code> uses the current collection as the <i>context</i> for the selector you pass to it.<br />
<br />
But, it turns out that the context for <code>delegate</code> is the document root; i.e., the same context that <code>$(<i>selector</i>)</code> uses.<br />
<br />
However, I can get the desired behavior by using:<br />
<pre>$('#blog>ul').delegate('click', '#blog>ul>li>h3', handler);
</pre>(And, yes, it would probably be better if I used a class instead of just <code>h3</code> to indicate that something is a clickable element, but that just fixes things for one specific case, and still leaves the general issue unresolved.)<br />
<br />
So, I asked myself, "why does the processing of the query start at the root context instead of with the collection?"<br />
<br />
I finally did come up with a possible good use - if you wanted to locate the elements to delegate to using a different path from the root.<br />
<br />
Imagine a page with multiple sections, only one of which is "active" at any time. I could set up the delegating click handling on the blog section, and similar handling on other sections, when the page loads. I could add a class like 'active' to the major section that I wanted to be currently active, and control which delegating handlers actually ran without having to constantly be delegating and undelegating.<br />
<br />
The code below demonstrates this (you can see it running at <a href="http://www.steveclaflin.com/blog-stuff/jquery/active-delegating.html" target="_blank">http://www.steveclaflin.com/blog-stuff/jquery/active-delegating.html</a>).<br />
<pre><body>
<div id="controller" class="active">
<h1>Master Controller</h1>
<ul class="liButtonBar">
<li class="liButton" data-section="#colors">Colors</li>
<li class="liButton" data-section="#blog">Blog</li>
</ul>
</div>
<div id="colors" class="section active">
<h2>Try Out Some Colors</h2>
<div id="colorSample" class="floatBox"></div>
<ul>
<li class="liButton">Red</li>
<li class="liButton">Green</li>
<li class="liButton">Blue</li>
</ul>
</div>
<div id='blog' class="section">
<ul>
<li>
<h3>I Like Ice Cream</h3>
<div>
<h3>My Favorite Flavors</h3>
<ul>
<li>Chocolate Almond</li>
<li>Turtle</li>
<li>Big Papa</li>
</ul>
</div>
</li>
<li>
<h3>Programming</h3>
<div>
<p>Programming is fun! And, sometimes, it isn't!</p>
</div>
</li>
</ul>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js">
</script>
<script type="text/javascript">
$(document).ready(function() {
// manage which section is "active"
$('#controller').on('click', '.liButton', function() {
$('.section.active').removeClass('active');
$($(this).data('section')).addClass('active');
});
// set up delegating click handling in which sub-selector
// looks for presence of .active on container
<b>$('#colors').on('click', '.active .liButton', colorHandler);
$('#blog>ul').on('click', '.active>ul>li>h3', blogHandler);</b>
function colorHandler() {
$('#colorSample')
.css('backgroundColor', $.trim(this.innerHTML).toLowerCase());
}
function blogHandler() {
alert(this.innerHTML);
}
});
</script>
</body>
</pre>Steve Claflinhttp://www.blogger.com/profile/03658610422946111549noreply@blogger.com0