Our thoughts in real time
January 30, 2012
Fast and slow-motion video with ffmpeg
In my last installment we looked at all the reasons why editing video isn't as easy as it should be. Let's assume that we've cleared those hurdles, and now actually want to do some video editing from the command line. A not-uncommon video effect is fast- and slow-motion, sped-up or slowed-down video. Being common, you'd think it would be readily available in any video editing software, but you'd be wrong. Out of respect for all its other virtues, we'll be using ffmpeg today.
Searching for "slow motion ffmpeg" or "speed up ffmpeg" turns up links like this blog and this forum that offer a method for getting video at different speeds, but it's a terrible wway to do it. The method involves breaking out each individual frame and then putting them all back together, dropping or duplicating frames as necessary to get the new framerate. This takes forever if you have a video of any length (you know, the sort've thing you might want to speed up), and hurts the quality because you're retranscoding.
Another wrong, but popular and plausible, method is simply changing the framerate. A faster or slower framerate should equate to faster or slower video playback, right? Yes and no. In certain cases, this might have the desired effect, but there are severe limits. Some standards, like NTSC, have fixed framerates, so changes aren't going to work at all. Others, like H.264, have bit-rate limits so that the maximum framerate is dependent on the resolution of the video. Framerates outside of the common ranges are also handled differently by different video playback software, so the actual playback speed becomes dependent on that as well.
Two bad methods out of the way, the right way to change video speed using ffmpeg is by adjusting the "presentation time stamp" (PTS). This adjusts frames' metadata related to how long each is displayed -- exactly what you want. Adjusting it with ffmpeg is done via the setpts video filter like so:
ffmpeg -i input.mp4 -vf "setpts=(1/<speed>)*PTS" output.mp4
where <speed> is the multiplier you want (2x faster: speed=2, etc). Ffmpeg's video filters are a very powerful set of tools, but they're not as well documented as they could be. In this case, the filter sets up the PTS variable for you which has the current PTS. In that way playback speed is adjusted relative to current speed, instead of in absolute terms. Because this is just metadata, not serious frame manipulation, the process is usually limited by the time necessary to write the output video to disk--fast!
As an example of ffmpeg's sometimes lacking documentation, this information is right in the description of the setpts filter! However, if you didn't already know that you wanted the PTS, you'd be unlikely to look there. The documentation in total is around thirty-thousand words, about the same length as Steinbeck's Of Mice and Men, so unless you've got a keen interest and some time, you're unlikely to find it by reading everything. I hope this post can help push the relevant info higher up in the search results.
Here's the results:
Actual speed:
Sped up:
A slowed down clip of that trick shot:
Posted at 02:28 PM in Video | Permalink | Comments (0) | TrackBack (0)
January 24, 2012
Scrum is like 20th Century Politics (Part 3 of 3)
In Part 1 and Part 2, we saw the forces of extreme right and extreme left savage two organizations in the name of scrum. One company goose stepped about in Luftwaffe jumpsuits. Another built a gulag for stakeholders who would question its developers-only central planning meetings.
In both cases, malevolent propagandists diverted the team's purpose, and product progress all but ceased. The right was called wrong. The in progress was called done. The 1 was called 0. User stories were enigmatic and undecipherable.
...
In isolation, these two examples find one doubting the utility of agile as a strategy for product governance.
Is the answer devolution, a stepping back? Should process be foregone entirely? Shall we place our faith in the anarchic code of the 19th century cowboy coder, or in a justice league future of caped super geeks?
For a company to survive, products must be built. The company needs a social contract, one that helps developers and product managers to find common purpose.
In other words, a third way.
Scrum, when used properly, can provide balance between the extremes of oppression. Best of all, it promises super stable neat innovative stuff made real quick.
Effective scrum starts with a division of power, so that (for example) the legislators of user stories are not the same individuals as those responsible for commanding the code. While code commanders in chief and the user story legislators often resolve conflicts on their own, gridlock can arise. In these cases, a judicious VP of product or the like can be brought in to arbitrate.
Summary of Roles:
- Development Team (Executive) with Scrum master (president) facilitating
- Product Owner (Legislative) representing the stakeholders (citizenry)
- Senior Product Manager (Judicial) to arbitrate disputes
Members of the judiciary serve for life unless deposed by a natural disaster (board member or CEO). Product Owners can be replaced preriodically by popular demand of the stakeholders. The development team needs to deliver results at the end of every sprint cycle, or risks losing stakeholder support.
While extremists might threaten this balance from time to time, corrections are inevitable so long as everyone understands the process and keeps watch on the others. When working, the system results in frequent releases and giddy, smiling stakeholders.
Companies that follow this third way are more likely to emerge as winners in a market conflict. They are also more like to feel good about work, have more satisfactory love lives, and sleep more soundly.
Happy product building!
Posted at 06:41 PM in Agile, Current Affairs | Permalink | Comments (0) | TrackBack (0)
January 18, 2012
HTML 5: Now With More Method to the Madness
Up to now, many people have regarded HTML coding as simply a matter of making the content fit together and look pretty on your browser the way you want, via CSS formatting of various <div> regions. Not true anymore with HTML5, which not only introduces new content element tags but also a new algorithm that renders the contents of a web document in outline form.
The <div> tag does not have any semantic meaning and is simply a generic container to be styled and formatted as the coder wished, and that still remains the case. However, new HTML5 tags such as <header>, <footer>, <article>, <aside>, <nav> and <section> have specific meanings relating to their content.
At first glance, the <section> tag appears to be the most generic of all the new content elements introduced in HTML5. However, it is not simply a substitute for <div>, as many developers make the mistake of assuming. In fact, it is one of the most significant elements in determining the outline structure of your document.
Here's a basic example of how the <section> tag works in the context of outlining content:
<body>
<h1>This is the main body</h1>
<section>
<h1>This is top-level section #1</h1>
<section>
<h1>This is a second-level section</h1>
</section>
</section>
<section>
<h1>This is top-level section #2</h1>
</section>
</body>
This content will result in the following outline:
This is the main body
- This is top-level section #1
- This is a second-level section
- This is top-level section #2
Likewise, just as <section> is not a newfangled, hip, with-it HTML5 update of <div>, the <nav> tag is not just to be used for any region with lots of links. It is a navigation region, and it's to be used only for links that are relevant to moving around the site. One notable example of a group of links that need not go in a <nav> section is what you might find in the footer. In that case, the <footer> tag is more than enough.
So don't be tempted to use lots of new HTML5 tags just because you can. And when in doubt, you can still use <div>: it's not going away just yet.
Posted at 05:32 PM in Design, Web/Tech | Permalink | Comments (0) | TrackBack (0)
January 02, 2012
Typography: An Essential Element of Modern Website Design
Typography (from the Greek words τύπος(typos) = form and γραφή(graphy) = writing) is the art and technique of arranging type in order to make language visible.
wikipedia
Typography is an under-appreciated art form which is used most often and probably understood the least. Typography is of high importance for printing and screening, as well as for websites. Some of you are probably wondering just why I am making such a big deal about typography and why should folks in the web industry, like us, care about typography. Let me tell you this: "web design is 95% typography".
Some may disagree with this statement, like this post that comments: "Type is only 90%!!". So it is either 90 or 95 percent, I am not here to quibble. But I believe that you would agree with me that typography plays a major role in web design.
Good typography not only helps readers go through the site content with ease, but also provides visual elements to the design of the website. The following websites in my opinion make outstanding use of typography:
http://www.mcfarlanechangemanagement.com.au/home.php
Now that you have seen how beautiful typography can enhance a website, I bet you would like to try it on your sites, too. But before we go running off putting together a great new design, let's take a closer look at some of the techniques involved in arranging types.
Typeface Selection
Since we are talking about typography, it is only natural that we should include the selection of the font.
Each typeface conveys a different message and is suitable for different purpose. Some are better for hard-copy printing while others are better for online reading. It is important that you consider how you want to portray your website to the readers. Then, you should choose the typeface that enables you to do so.
Today, I am only touching on the surface of each aspect of typographic elements so I do not go on at length to explain the difference between serif and san serif. However, you may want to have a look at this article which gives further explanation on type selection: http://ilovetypography.com/2008/04/04/on-choosing-type/
Before CSS3 came into the picture, the choices of typeface available for the web were very limited. Now, we can use @font-face to implement a number of different typefaces on our website. Personally, I like to use the Google web font service; although the choice maybe limited, it is free of charge.
Before we go on to the next technique, here are some more tools that can be handy when choosing typefaces for your web:
Sizing
Next we have to think about the appropriate font size for our purpose. When choosing the font size, you need to keep in mind that the words must be legible. I would recommend keeping the size at the minimum of 10pt. Anything below that would be tough to read, especially, for older readers.
Apart from the legibility issue, we should also consider the overall hierarchy as well. For example, If your site has a main header, you would want to use a bigger size text for the main header ( header tag <h1>../<h1> ) in order to grab the user's attention, while your footer fonts should be smaller as the footnotes are less important. However, you may get creative and tweak some of these rules around if you are after a modern feel. This website Coudal Partners is a good example of this.
Leading
Leading is the space between lines of text. I am sure you have heard of "single-space" or "double-space" when working on MS word document report. It is pretty much the same thing. The word, leading, in this particular case has its origin in the old days when lead strips were used in arranging letter blocks on the printing press to add a vertical space between each line of text.
In CSS, we use the line-height property to control the leading of the text. Most browsers set a default value between 1.0-1.2 for line-height. This can be overridden to adjust the leading to suit your purpose.
Tracking
Tracking is oftentimes confused with kerning; I have to admit that I used to get these two words mixed up. Tracking refers to the spacing of a group of letters. In CSS, tracking is also known as letter-spacing. The amount of letter-spacing can affect the legibility and readability of the text. A tight letter-spacing can be very hard to read and make the reader feel tense.
Kerning
Unlike tracking, kerning refers to the process of adjusting the spacing between two individual characters. Most fonts have their default kerning value but, in some cases, there is still a need for adjustment.
Here is a little game that can help you appreciate the importance of kerning: http://type.method.ac/
Since kerning involves a particular pair of characters, there is no specific CSS rules that directly handle kerning. However, there is a kerning.js , that enables you to set your own CSS rules and makes kerning a little easier.
I have covered only a tiny area of typography. For further reading, there are many typography articles online. The following is a list of some of the best typography blogs.
- http://ilovetypography.com/
- http://www.alistapart.com/topics/topic/typography/
- http://nicewebtype.com/
- http://fontfeed.com/
Remember, "never stop learning", and "practice makes perfect".
Posted at 09:49 PM in Design | Permalink | Comments (1) | TrackBack (0)
December 21, 2011
Drawing On Maps -- Not Just For Surveyors and Destructive Children
There's really no excuse for not incorporating the Google Maps API into your applications these days. It's free, easy to use, and with version 3 you don't even need an api key anymore (although you can still use one). What's that I hear? You don't need interactive maps on your site? Not good enough.
We'll be using the Javascript V3 API.
The Map
Basic usage is so simple it's almost frightening. You include the api library, create a DOM container to house your map, and create a map object which gets inserted into your container.
<script type="text/javascript"
src="https://maps.googleapis.com/maps/api/js?sensor=false">
</script>
<div id="map-container" style="width:400px; height:400px;"></div>
<script type="text/javascript">
var latlng = new google.maps.LatLng(-34.397, 150.644);
var mapOptions = {
zoom: 8,
mapTypeId: google.maps.MapTypeId.ROADMAP,
center: latlng
};
var myMap = new google.maps.Map(document.getElementById("map-container"), mapOptions);
</script>
That's it. There's your basic map. The latitude and longitude settings have put you in Sydney, Australia. I can't find this confirmed anywhere, but it looks like the zoom value goes from 0 (viewing the whole world) to 22 (pressing your face against the ground)
A few points that I may have glossed over:
- The mapOptions object is required, as are the three elements it contains (zoom, center, and mapTypeId) here. There are a number of other optional values that you can add to mapOptions to configure your map, but these three are essential.
- You'll probably want to defer creation of the map object until the dom is loaded with your favorite
document.onReady()-type of construct, as well as checking to make sure the API is availableif(google).
Now we've got a map. Let's put it through its paces.
Geolocation
The easiest way to get the user's location is with a browser that enables HTML5 geolocation. The Maps Api documentation describes a few other possibilities, but since all major current browsers support HTML5 geolocation, we won't bother with anything else at the moment.
navigator.geolocation.getCurrentPosition(function(position) {
initialLocation = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
myMap.setCenter(initialLocation);
},
function() {
alert("couldn't get your location");
});
This should send your map to Moosejaw, Saskatchewan, or wherever you happen to be sitting at the moment. navigator.geolocation.getCurrentPosition is of course provided by the browser, not the maps API.
Overlays
You can add a wide variety of graphical elements to your map: markers (most often seen as a pin in the map, but they can be any graphic you choose), polylines, polygons, circles and "ground overlays" (we won't look at these last here, but here's an example).
Placing a marker on the map
var latLng = new google.maps.LatLng(-34.397, 150.644);
var marker = new google.maps.Marker({
icon: 'http://url.of.image',
position: latlng,
map: myMap,
title: "If you lived on this marker, you'd be home right now."
});
position and map are required if you want your marker to display. Other parameters are optional. You can set create the marker first and add position and map later however, with marker.setPosition() and marker.setMap().
Removing a marker (or other overlay) from the map
marker.setMap(null);
if you want to actually delete the marker, set the marker variable itself to null.
Circles and polygons are nearly as simple.
Drawing a circle
var latLng = new google.maps.LatLng(-34.397, 150.644);
var circle = new google.maps.Circle({
strokeColor: "#FF0000",
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: "#FF0000",
fillOpacity: 0.35,
map: myMap,
center: latLng,
radius: 200
});
One thing to note is that all measurements (in this case, radius) are in meters.
Drawing a polygon (this example from the documentation draws the Bermuda Triangle)
var pathsArray = [
new google.maps.LatLng(25.774252, -80.190262),
new google.maps.LatLng(18.466465, -66.118292),
new google.maps.LatLng(32.321384, -64.75737),
new google.maps.LatLng(25.774252, -80.190262)
];
var polygon = new google.maps.Polygon({
paths: pathsArray,
strokeColor: "#FF0000",
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: "#FF0000",
fillOpacity: 0.35,
map: myMap
});
Note that it's not necessary to close the polygon (the end point does not have to be the same as the initial point).
Pixels to Map Coordinates and Vice-Versa
One particular problem I encountered was converting between screen pixels and the latitude/longitude measurements that are the units the api generally works with. Either this is a rare example of the documentation being insufficient, or I was just obtuse, because it took me a while to figure out.
The application I was working on called for a user to be able to draw a polygon by clicking several times on the map. Finally I wanted to close the polygon when the user clicked on or near the original point. But what's "close?" If the map is zoomed way in, close on screen may be a distance of a few meters, but if the map window is viewing all of Asia, a difference of a few pixels may mean 100 miles. Here's what I did to see whether the user has clicked within 6 pixels of the initial polygon point.
In order to get this to work, I needed to get an OverlayView object from the map and from that get a Projection object to manipulate the Latitute/Longitude data. Incidentally, in order to use an OverlayView, you have to override its draw method.
function pointsAreClose(latLng1, latLng2, map) {
var pointThreshold = 6;
var overlay = new google.maps.OverlayView();
overlay.draw = function(){};
overlay.setMap(map);
var projection = overlay.getProjection();
var areClose = false;
if (projection != undefined) {
var coords1 = overlay.getProjection().fromLatLngToContainerPixel(latLng1);
var coords2 = overlay.getProjection().fromLatLngToContainerPixel(latLng2);
if ( Math.sqrt( Math.pow(coords1.x - coords2.x,2) + Math.pow(coords1.y - coords2.y,2)) < pointThreshold) {
areClose = true;
} else {
areClose = false;
}
}
overlay.setMap(null);
return areClose;
}
Loading Libraries
Currently there are five additional libraries, some of which are related to drawing. Google, being Google, can be expected to add more in short order:
- AdSense (I'm not really sure what this is. It requires a Google AdSense account)
- Drawing (provides tools for user-initiated drawing without having to create your own interface)
- Geometry (contains advanced methods for calculating spherical projections and the like)
- Panoramio (allows the adding of photos from Panoramio)
- Places (provides access to a database of specific places e.g. The Empire State Building, provides an autocomplete feature)
You include libraries by adding this query string parameter to your javascript src attribute: libraries=library1,library2,library3 e.g. https://maps.googleapis.com/maps/api/js?sensor=false&libraries=drawing,places
Wrap-Up
I've barely scratched the surface of the Maps API's drawing capabilites here, much less the power of the API overall. It's powerful, versatile, and I really enjoy using it. Check it out for yourself. This is one area at least where Google's all-seeing eye seems to be working in the service of the public.
Posted at 09:24 AM in Web/Tech | Permalink | Comments (0) | TrackBack (0)
December 04, 2011
Is Software a Science or an Art?
As we approach software today, often through web or mobile applications, people generally appreciate the elegance of the interaction or lack thereof. But as software engineers know, there is a lot going on behind the scenes. Of course, with user interfaces for the masses becoming a necessity for modern applications, designers and more artistic–oriented folks have been contributing to the practice of software development. That leads to the question: Is software more of a science or an art?
Of course, science is based on logic, with a hypothesis, experimentation, results and conclusions based on the outcome. To some degree software fits this role with predetermined algorithms for processing and analyzing information.
However, a strong argument can be made for the artistry in software. Take for instance the wide variety of expression allowed via websites such as Facebook, YouTube and Twitter. These services have empowered user creativity and fostered new forms of communication. If I told you someone was ‘tweeting’ five years ago you’d think they were doing their best bird imitation.
User interfaces in general have more recently adopted a more visceral approach to presentation. Websites are instruments of personal expression. Most popular applications today have a game-like interface or are in fact games themselves. This drives my bias (generally) towards the interpretation that software today is leaning closer to art than science. I wouldn’t have felt that way ten years ago.
What do you think? I’m interested in hearing and responding to your comments.
Posted at 08:34 PM in Current Affairs, Science, Web/Tech | Permalink | Comments (1) | TrackBack (0)
November 29, 2011
How to set up and exploit an Apache Solr environment on Amazon EC2
What’s Solr?
Solr is an open source enterprise search platform from the Apache Lucene project. Its major features include powerful full-text search, hit highlighting, faceted search, dynamic clustering, database integration, rich document (e.g., Word, PDF) handling, and geospatial search. Solr is highly scalable, providing distributed search and index replication, and it powers the search and navigation features of many of the world's largest internet sites.
What’s EC2?
Amazon Elastic Compute Cloud (Amazon EC2) is a web service that provides resizable compute capacity in the cloud. It is designed to make web-scale computing easier for developers. An Amazon Machine Image (AMI) is a special type of pre-configured operating system and virtual application software which is used to create a virtual machine within the Amazon Elastic Compute Cloud (EC2). It serves as the basic unit of deployment for services delivered using EC2.
Requirements
This tutorial uses:
- An AMI consisting of a 32bit base Fedora 8 install (most of the Linux based AMI will work fine)
- Java version: 1.6.0_29
- Solr version: 3.4.0
Let’s now look at the actual installation steps:
0) Open 8983 port
Before starting it’s necessary to open port 8983, which is the port that Solr listens on by default.
This can be done by adding a rule to the security group which the chosen AMI belongs to.
1) Install java
To check if java is already installed in the machine, enter the command:
java -version
if the response is:
-bash: java: command not found
java is not installed in the machine.
To download and install java, enter the following commands
wget http://download.oracle.com/otn-pub/java/jdk/6u29-b11/jdk-6u29-linux-i586-rpm.bin
chmod +x jdk-6u29-linux-i586-rpm.bin
2) Install Solr
To get Solr, enter:
wget http://mirror.nyi.net/apache//lucene/solr/3.4.0/apache-solr-3.4.0.tgz
tar xzf apache-solr-3.4.0.tgz
3) Start Solr
Solr comes with its own servlet container, Jetty, bundled withe package above.
To start Solr, go to the example directory:
cd apache-solr-3.4.0/example/
and enter:
java -jar start.jar
To verify that the server is running correctly, open the web browser and enter the following URL:
http://ec2-xxx-xxx-xxx-xxx.compute-1.amazonaws.com:8983/solr/admin/
(where xxx-xxx-xxx-xxx stands for the public DNS address)
4) Start Indexing
Now that the Solr server is running, it is possible to start building a simple index.
To create a new index go to the exampledocs folder and enter:
java -jar post.jar *.xml
This command will index all the .xml documents contained in the exampledocs folder.
Solr allows you to import data in many different ways and formats. It is possible to index CSV files, JSON documents, .pdf and .doc documents through Solr Cell (http://wiki.apache.org/solr/ExtractingRequestHandler) and it is possible to get records directly from the database through the Data Import Handler (http://wiki.apache.org/solr/DataImportHandler).
One way to search over the just indexed files is to go to the admin page (http://ec2-xxx-xxx-xxx-xxx.compute-1.amazonaws.com:8983/solr/admin/) and query the index through with the Solr query language (http://wiki.apache.org/solr/SolrQuerySyntax).
This language is an extension of the Lucene query language and allows you to exploit the powerful searching features of Solr by simply adding some parameters to the query.
A more interactive way to search over the created index is to open the browser and go to the following URL:
http://ec2-xxx-xxx-xxx-xxx.compute-1.amazonaws.com:8983/solr/browse
An example of search interface will let you search over the documents and show some of the most relevant features of Solr (autocomplete, faceting, highlighting…)
Several clients can be used to distribute Solr client software depending on the developer preferences. A list of some popular clients follows:
These simple steps are all you need to run a Solr instance on Amazon EC2.
Now that the project is set up you can take advantage of your Solr-AMI to perform all kinds of smart searches for your own projects and websites.
Don’t forget to visit and explore the Solr Wiki Page for more advanced uses and customizations of Solr.
Alberto Montagnese
Posted at 10:39 PM in Amazon EC2, Java, Web/Tech | Permalink | Comments (0) | TrackBack (0)
November 28, 2011
JQuery DataTable Plugin Pagination
One of the most common tasks when displaying data through an HTML table is to allow the user to manipulate data within the table. Displaying data on a grid can involve many different operations (retrieving, sorting, live editing, search). Using AJAX to dynamically fill the table requires even more work since the application (in this case, javascript code) has to convert JSON/XML/TEXT data to an HTML element. Each of these tasks can involve more or less time in order to be completed, and of course, tested.
Rather than starting the implementation from scratch I decided to look around for a jQuery plugin able to perform all the necessary operations on a grid (retrieving, sorting, searching, exporting).
After trying out different plugins I ended up choosing the DataTable plugin.
From the first look at the examples on the official website (http://datatables.net/) the plugin looked extremely easy to use and flexible to customize (data sources, data type detection). Some of the key features are listed below:
- Variable length pagination
- On-the-fly filtering
- Display data from almost any data source: DOM, Javascript array, Ajax file and server-side processing (PHP, C#, Perl, Ruby, AIR, Gears etc)
- Dynamic creation of tables
- Ajax auto loading of data
- Custom DOM positioning
- Single column filtering
- Sorting column(s) highlighting
- Extensive plug-in support
- Sorting, type detection, API functions, pagination and filtering.
Most of the features of the plugin are enabled by default so the developer can focus on the real problems (business logic). Here is a “zero configuration” example:
HTML Code:
(create your own table within your html page something like this):
<table cellpadding="0" cellspacing="0" border="0" class="display" id="example">
<thead>
<tr>
<th>Rendering engine</th>
<th>Browser</th>
<th>Platform(s)</th>
<th>Engine version</th>
<th>CSS grade</th>
</tr>
</thead>
<tbody>
<tr class="odd gradeX">
<td>Trident</td>
<td>Internet Explorer 4.0</td>
<td>Win 95+</td>
<td class="center"> 4</td>
<td class="center">X</td>
</tr>
<tr class="even gradeC">
<td>Trident</td>
<td>Internet Explorer 5.0</td>
<td>Win 95+</td>
<td class="center">5</td>
<td class="center">C</td>
</tr>
…
</tbody>
</table>
Remember to include the javascript plugin file:
<script type="text/javascript" language="javascript" src="js/jquery.dataTable.mins.js"></script>
and finally call the datatable function:
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
$('#example').dataTable();
});
</script>
You can download this example from github (git://github.com/GrioSF/datatablepaginationplugin.git).
Now that you have an idea (of course you can take a look at the documentation), we can now move on to the real topic of this post, implementing custom pagination controls for the DataTable plugin.
Of course, the plug-in lets the developer to build his/her own style and logic for pagination. Customizing the pagination was the hardest part among all the possible plug-in’s operations I had to perform.
I created the following pagination style (the code on github will provide css code to reproduce the structure but not the exact style):
The component contains 3 different parts: a previous button, an information field and a next button.
I decided to modify one of the examples provided on the website in order to create my own plug-in.
The code is divided into three parts:
- Pagination plug-in: html structure and behavior
- Page information plug-in: to obtain information about the current page index
- Code to initialize the pagination html components
-
Pagination plug-in: This is the core of the new pagination controls. This code will create HTML elements and logic to handle the previous and next buttons:
/**
* Datatable plugin pagination style
*/
$.fn.dataTableExt.oPagination.extStyleLF = {
/*
* Function: oPagination.extStyle.fnInit
* Purpose: Initalise dom elements required for pagination with a list of the pages
* Returns: -
* Inputs: object:oSettings - dataTables settings object
* node:nPaging - the DIV which contains this pagination control
* function:fnCallbackDraw - draw function which must be called on update
*/
"fnInit": function (oSettings, nPaging, fnCallbackDraw) {
/*
* code to create pagination buttons
*/
nPrevious = $('<span />', { 'class': 'paginate_button previous' });
nNext = $('<span />', { 'class': 'paginate_button next' });
/*
* code to create pagination information field
*/
var navigationLabel = 'Page <span class="pageIndex">#</span> of <span class="totalPages">TOTAL</span>';
navLabel = $('<div />', { html: navigationLabel, 'class': 'navigationLabel'});
$(nPaging)
.append(nPrevious)
.append(navLabel)
.append(nNext);
nPrevious.click(function () {
oSettings.oApi._fnPageChange(oSettings, "previous");
fnCallbackDraw(oSettings);
}).bind('selectstart', function () { return false; });
nNext.click(function () {
oSettings.oApi._fnPageChange(oSettings, "next");
fnCallbackDraw(oSettings);
}).bind('selectstart', function () { return false; });
},
/*
* Function: oPagination.extStyle.fnUpdate
* Purpose: Update the list of page buttons shows
* Returns: -
* Inputs: object:oSettings - dataTables settings object
* function:fnCallbackDraw - draw function which must be called on update
*/
"fnUpdate": function (oSettings, fnCallbackDraw) {
if (!oSettings.aanFeatures.p) {
return;
}
/* Loop over each instance of the pager */
var an = oSettings.aanFeatures.p;
for (var i = 0, iLen = an.length; i < iLen; i++) {
//var buttons = an[i].getElementsByTagName('span');
var buttons = $(an[i]).find('span.paginate_button');
if (oSettings._iDisplayStart === 0) {
buttons.eq(0).attr("class", "paginate_disabled_previous paginate_button");
}
else {
buttons.eq(0).attr("class", "paginate_enabled_previous paginate_button");
}
if (oSettings.fnDisplayEnd() == oSettings.fnRecordsDisplay()) {
buttons.eq(1).attr("class", "paginate_disabled_next paginate_button");
}
else {
buttons.eq(1).attr("class", "paginate_enabled_next paginate_button");
}
}
}
}; -
Page information plugin (provided by the official website): this code is an helper for the plugin so the application can easily retrieve information about pagination (current page, total number of pages):
/**
* Datatable plugin page information
*/
$.fn.dataTableExt.oApi.fnPagingInfo = function ( oSettings )
{
return {
"iStart": oSettings._iDisplayStart,
"iEnd": oSettings.fnDisplayEnd(),
"iLength": oSettings._iDisplayLength,
"iTotal": oSettings.fnRecordsTotal(),
"iFilteredTotal": oSettings.fnRecordsDisplay(),
"iPage": Math.ceil( (oSettings._iDisplayStart + oSettings._iDisplayLength) / oSettings._iDisplayLength ),
"iTotalPages": Math.ceil( oSettings.fnRecordsDisplay() / oSettings._iDisplayLength )
};
} -
Code to initialize html components:
$(document).ready(function() {
var oTable = $('#example').dataTable({
"sPaginationType": "extStyleLF",
"bAutoWidth": false,
"fnDrawCallback": function () {
var length = this.fnGetData().length;
if (length <= this.fnPagingInfo().iLength) {
$(this).parent().children(".dataTables_paginate").hide();
}
$(this).parent().find(".dataTables_paginate .navigationLabel .pageIndex")
.text(" " + this.fnPagingInfo().iPage + " ");
$(this).parent().find(".dataTables_paginate .navigationLabel .totalPages")
.text(this.fnPagingInfo().iTotalPages);
},
"sDom" : '<"clear">ilprtp'
});
});
As you can see, a lot of code is needed to customize the pagination controls (compared to other customizations) but the official website and the forum contains a lot of useful information. In addition, the documentation offers good examples to use as a starting point. I highly recommend the JQuery DataTable plug-in for your tabulation needs.
Giuseppe
Posted at 11:49 AM in Web/Tech | Permalink | Comments (0) | TrackBack (0)
November 19, 2011
Why Things Go Wrong: A Simple Programming Problem
Much of programming is writing code with an obvious solution, it's being a code monkey. You lay things out, you move them around, you wire it up, but you don't really need to stop and think. I'd estimate that everyone is about equally good at this. But some of the work is also spent solving problems you've never seen before. This is where bugs are introduced, bad decisions are made, schedules are thrown out, and things go wrong. These problems are the really defining part of being a great programmer.
I think understanding the hidden complexites of seemingly minor problems is important in understanding programmers. Studies conducted starting as far back as the 60's have shown that most all developers will complete the same problem in a similar amount of time. In fact the best developers are about 2 to 3 times faster than average and the worst are 2 to 3 times slower. But the best developers will have about 1/10 as many bugs in their code as their average counterparts. In fact, when a restraint was placed on the allowed number of bugs, the best developers were 20 times faster than the average developer.
But why is that? Well, it's hard to explain without giving an example, so I'd like to present a case study of one such problem. I picked a problem that was easy to understand and small in scope. It's a real problem I came across and I remember it well because I thought it'd make a great interview question, if only it was even smaller in scope.
(On a very interesting tangent, the same studies have shown that the best developers make 30 to 50 percent more money than the average. Keep that in mind when interviewing and hiring, if quality is a concern, you get 20 times the speed for 1.5 times the cost. But the business sense of your typical developer is a topic for another day.)
Trying to solve this problem for yourself is probably the best way to fully understand the thesis of this article. I encourage you to work on it yourself before reading my solution. If you're not a programmer, you can solve it with pen and paper, that's how I did it.
So without further ado, I give you my case study of great programming.
For full disclosure, this problem was solved by another programmer, let's call him Adam. He's one of those great programmer types I mentioned above. I actually solved it and then got a merge conflict because Adam had already done the work. I very much enjoyed reading his solution to the problem, as it was very different from mine yet equally good. So, sadly, the below code never made it to production, as often happens, but I'm glad it can find some use here.
The problem:
You have an unknown number of video streams (we'll use red boxes instead) that each have the same unknown width and height (aspect ratio). They're in a rectangular container that also has an unknown width and height. You want to scale the video streams to maximize the surface area they take up in the container.
For instance, here's a red box in a container:
Or if there were 5 of them, you'd want this:
The Solution:
Let's dive right in. We'll be using everyone's favorite programming language, Javascript.
First off, let's define the variables we're working with:
W, H: the width and height of the containerw, h: the starting width and height of a red box
n: the number of red boxes
m: the multiplier (the amount to scale the size of the red box by)
and, to keep the javascript police off my back, to transform my pseudocode into real code, let's do this:
var floor = Math.floor;
Ok, so our function will have known constants for W, H, w, h, and n. They're unknown right now, but they will be the input. What we really want to solve for is m, the amount to scale each stream by.
what does m equal?
Well, we know for a given m:
floor(H/(h*m)) * floor(W/(w*m)) is the maximum number of red boxes we can fit inside the container
So we want the largest m that satisfies:
floor(H/(h*m)) * floor(W/(w*m)) >= n
Now we're getting somewhere. But as you can see, there are an infinite number of m that will satisfy:
floor(H/(h*m)) * floor(W/(w*m)) = x, for any constant x.
For example, let's say H = W = h = w = 1. And x is 1. Then:
floor(1/m) * floor(1/m) = 1
Which means m has an infinite set of solutions, namely, (1/2, 1], that is, all real numbers from 1/2 to 1, including 1 but not including 1/2.
This seems like a troubling problem. An infinite set of solutions is not what a programmer yielding loops as a tool wants to hear. But if you consider it for a moment, you'll realize we don't care what m is exactly. In fact, we just want the value in the set with the largest m, since they produce the same x anyway. In fact we know that:
floor(H/(h*m)) is always an integer, and,
floor(W/(w*m)) is always an integer.
So what we really want to solve is all of the possible x values regardless of m, take the smallest x that's greater than or equal to n, and then take the largest m for the given range of m that yields the accepted solution.
I'm throwing a lot of math around, so let me break it down a bit. Think of w and h not as sides of a rectangle, but as a grid, or a spreadsheet, starting at the top left corner of the container. Each row is h units tall and each column is w units wide. So as you multiply w and h by m, or scale the size of the columns and rows proportionally, a different number of rectangles in your grid will fit inside the container. If you can fit 9 red boxes inside, then there are an infinite combination of different sizes of w and h that will also result in 9 red boxes. But we don't have to care about that, we only have to care about about the largest size that can fit 9 red boxes inside. And the largest size of w and h that accomplishes that will always occur when we stretch the grid until a certain number of columns fit perfectly in the container OR a certain number of rows fit perfectly.
So now we have a set of all possible solutions for x. It is the union of solving for when H/(h*m) equals an integer or when W/(w*m) equals an integer. Or, the following union:
k*floor(k*(W*h)/(H*w)) U k*floor(k*(H*w)/(W*h))
You might notice that the solutions on either side of the union are not mutually exclusive. We will need to find the value for each set that's the smallest value greater than or equal to n, and then we will need to take the one with the largest m.
That's it, that's the solution. We need to solve for both sets and then take the largest of the two m values. I can't see how to simplify it further. If you can, please write a comment below, and I'll update the post.
Ok, so let's code it up.
First, let's define the structure of a container and stream object that our function will take as input:
var container = {
width: 0,
height: 0
};
var stream = {
count: 1,
width: 0,
height: 0
};
And now let's solve for m.
var layoutStreams = function(container, stream){
var calculateMultiplier = function(__container, __stream){
// get best solution when grid extends to right side of container
var rowSolution;
var k = 1;
while (!rowSolution) {
var nBoxes = k * Math.floor(( k * __container.width * __stream.height )/( __container.height * __stream.width ));
if (__stream.count <= nBoxes) {
rowSolution = __container.height / (__stream.height * k);
}
++k;
}
return rowSolution;
};
var invertContainer = {
width: container.height,
height: container.width
};
var invertStream = {
count: stream.count,
width: stream.height,
height: stream.width
};
var rowSolution = calculateMultiplier(container, stream);
var colSolution = calculateMultiplier(invertContainer, invertStream);
var m = (colSolution > rowSolution) ? colSolution : rowSolution;
// ------------------------------------------------
// now use m to layout streams
// ------------------------------------------------
};
How will you layout your streams? Well, I'll leave that as an exercise for you, but I'll provide the most primitive of solutions, which is to start in the top left and go left to right, top to bottom.
..................
// ------------------------------------------------
// now use m to layout streams
// ------------------------------------------------
var streamDim = {
width: Math.floor(stream.width * m),
height: Math.floor(stream.height * m)
};
var grid = {
cols: Math.floor(container.width / streamDim.width),
rows: Math.floor(container.height / streamDim.height)
};
var eleContainer = <your container>
var streams = [];
for (var i=0; i<stream.count; ++i){
streams[i] = document.createElement("div");
eleContainer.appendChild(streams[i]);
streams[i].style.width = streamDim.width;
streams[i].style.height = streamDim.height;
streams[i].style.position = "absolute";
// primitive layout strategy
streams[i].style.top = (Math.floor(i / grid.cols)) * (streamDim.height) + "px";
streams[i].style.left = (i % grid.cols) * (streamDim.width) + "px";
}
...................................
And that's it. Here's a demo you can play with, and you can grab the source code right off of this page if you'd like. (if the iframe doesn't load, you can go to page directly). Or grab the demo code from github.
Please send hate mail to my secretary, ptubig@grio.com. Any other comments can be sent to me, griddle@grio.com.
Posted at 04:41 PM | Permalink | Comments (0) | TrackBack (0)
November 09, 2011
Twitter Stock Ticker
Last night I was rummaging through some of my past projects doing a little winter cleaning, and I stumbled upon this jQuery plugin that I wrote a while back. I ran the code to see if it still functions (it was using jQuery 1.3). And to my pleasant surprise, the code still runs like a charm. So, I just want to take the time to share it with you all.
This jQuery plugin is a Twitter stock ticker; the stock ticker one sees on Wall Street. And this is an example HTML page that uses the plugin:
<html>
<head>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.7.min.js"></script>
<script type="text/javascript" src="tweetstockticker.js"></script>
<script>
$(document).ready(function() {
$('#tweet-container').tweetstocktick('san francisco', 5, 15);
});
</script>
<style>
#tweet-container {
overflow: hidden;
position: relative;
width: 100%;
height: 100px;
}
#rotating-ticker-item {
font-size: 36px;
position: absolute;
text-transform: uppercase;
white-space:nowrap;
width: auto;
}
</style>
</head>
<body>
<div id="tweet-container"></div>
</body>
</html>
It’s a pretty simple implementation using the Twitter Search API
.tweetstocktick(q, rpp, speed)
q = search term (ie. “San Francisco”)
rpp = number of the most recent tweets
speed = used to calculate duration (speed * width).
Feel free to share any thoughts, and I hope it helps.
Posted at 10:06 AM in Web/Tech | Permalink | Comments (1) | TrackBack (0)
November 07, 2011
Android Pattern Lock on iPhone
I'm a loyal user of iPhone. I've been using it since its first version and now I'm on 4S. Though I'm staying in iOS camp and don't have any intention of moving, I occasionally get the urge to test out and see what's out there. That's the reason why I convinced myself to own and play with Dell Streak 5" phone and Google Nexus S. They are both smart smart-phones. But it's hard to pick their pros and cons by simply "playing" with it. You've got to use it. Daily. For everything including surfing, speed dialing, social-networking and games. Everything. And that's something that I didn't get a chance to do until last summer when I was traveling and in need of an unlocked phone. I could make my iPhone work (don't ask me how) but thought I'd give an Android phone a real kick.
So, I spent 3 weeks using my Dell Streak full-time. I'm not going to tell you about how bad or good Android phone is compared to its counterparts, or discuss my constant efforts to find for an outlet to charge the phone. But I do want to talk about one feature that I used a lot but can't find on iPhone: It's the well-known pattern lock feature. If you've never seen it, here is a screenshot.
The idea is simple. Rather than entering 4 digit PIN, you connect the 9-dots (3 by 3) presented on the screen to form a secret pattern. The number of dots connected to form a pattern can be as many as the available dots on screen. In the case of Android lock, the max is 9 dots and the min is 4 dots. In my 3 weeks of usage, I think I can unlock faster using pattern lock versus using PIN lock. And that's when I started to wonder why in all the apps that I've ever used on iPhone, none of them provides this type of lock. Mint is one of my most frequently used apps. I'd love to see it using pattern lock instead of pin lock. It will save me about 0.5 second every time I use the app. Yeah, that's a very valuable 0.5 second.
So, I set aside 2 hours of my time and created a component that provides the pattern lock feature. If you're an iOS developer, feel free to use it in your app. Mention Grio if you feel indebted to us. Lock your user's data more securely. If security is less of your concern, do it for the sake of giving users more options.
The code is pretty self-explanatory. The main class is DrawPatternLockViewController. You can set the matrix size by changing the following constant:
#define MATRIX_SIZE 3
In the sample app, there's a Lock button, which will bring up the pattern lock screen.
It's very easy to use the component. Create an instance, register callback and you're all set. Here's the sample code:
DrawPatternLockViewController *lockVC = [[DrawPatternLockViewController alloc] init];
[lockVC setTarget:self withAction:@selector(lockEntered:)];
[self presentModalViewController:lockVC animated:YES];
When the user is finished drawing, lockEntered will be called. You can take a look at that function in PatternLockAppViewController. It will compare what user entered with a hardcoded pattern. In this implementation, I store the pattern in an NSString. If you replace the dots with series of numbers (1, 2, 3, ...) from top left moving in the right direction downward, you'll see the dots to be similar to the keypad on a phone. And a pattern string stores those numbers by concatenating them in proper order. So, if you draw a pattern by connecting the dots along the edge clockwise starting from top left toward bottom right corner, you'd end up with an NSString with value @"0102030609". You can easily change the way you store and interpret a pattern by changing getKey function in DrawPatternLockViewController.
Note: The dot was designed by Grio's talented designer, #Yui. This project was written using iOS 5 with ARC on. You can download it here.
November 02, 2011
Dieter Rams’s Ten Commandments
You might not be aware of it yet, however SFMOMA (the San Francisco Museum Of Modern Art) is currently hosting Dieter Rams’s “Less but Better” product exhibit.
You might also not be aware of who Dieter Rams is, since he is not directly related to the software industry.
Indeed, for almost 40 years Dieter Rams was Chief of Design at Braun’s: he designed record players, razors, radio sets, etc.
He didn’t have much to do with computers.
So why talk about him?
Well, he is one of the most prominent design engineers of our era. His designs have affected many fields, as well as influenced and inspired many designers. This includes Apple’s Vice-President of Industrial Design: Briton Jonathan Ive.
A few seconds looking at the designs and the similitudes are striking:
|
iPhone Calculator App. |
ET 66 calculator, 1987, by Dietrich Lubs for Braun |
Dieter Rams work is remarkable in that he was not just an innovator; he was also an aggregator and a simplifier, putting words to ideas. He strived to do better with less.
“Surfaces that were without apology, bold, pure, perfectly proportioned, coherent and effortless” – Jonathan Ive.
Over the years, Rams was able to refine, and then define his design ideals. They are summed up in his Ten Principles for good design (also referred to as his Ten Commandments):
- Good design is Innovative.
- Good design makes a product useful.
- Good design is aesthetic.
- Good design makes a product understandable.
- Good design is unobtrusive.
- Good design is honest.
- Good design is long lasting.
- Good design is thorough, down to the last detail.
- Good design is environmental friendly.
- Good design is as little design as possible.
Ten principles that follow themselves.
I could dig in to each principle, however that is not the object of this blog article… Those principles are quite self-explanatory, since most of those ideas are the writ form of notions we all have. Some can also be correlated to familiar terminologies (ie. KISS).
In short, Dieter Rams started realizing in the 80’s that design engineering was a mess – “an impenetrable confusion of forms, colors and noises”. Being an important contributor to that mess, he asked himself a question we should ask ourselves daily when we create: “Is my design good design?”
This realization is common to many fields, including the software industry. The spreading of the Web changed our society. More companies, more engineers, more clients, more products, etc. Boom! Within a short decade we have a world of new industries.
To be able to keep as much of an understanding as possible, we devise paradigms, design patterns and frameworks, all so that we can find some common understanding; an understanding not just among programmers (see for example Brad’s article about naming conventions: http://blog.grio.com/2011/09/name-recognition.html) but also for the public. Users do not want to feel lost.
Website designs evolve just as much as product designs do, with a notable difference: Users zap through pages. They read this, tweet that. And thus the design has slowly evolved towards more simple layouts, where at a glance, they know what is where:
- Google: Text field with a “Search” button.
- Tweeter: 140 characters.
- Amazon: 1-Click.
One function, sharp edges, rounded corners.
Apple’s designs have been following those ideas for more than a decade now, however those principles were really only pushed to their first extreme with the iPod in 2001.
|
|
It’s simple, to the point, does it’s job, looks nice, and looks solid (even in pink).
Just as the users started realizing that they were looking for familiar landmarks on websites, Apple designs got accessible, keeping only those landmarks. And obviously it works.
So what am I getting at?
We are aware of these ideas: Functionality, simplicity and reliability. We have to contend with these issues daily. We are aware, but not all the time and not with such a precise understanding.
Dieter Rams’s Ten Principles (as mentioned earlier) are an aggregation of those ideas that he put into writing; ideas we can all understand.
So don’t hesitate to refer to that list once in a while, it might help you figure out a flaw, or some enhancement you could make to your websites, apps and/or architectures.
From a visual perspective, designs inspired by Braun or Apple products, products that are seen and understood by a majority of people, will only be made more understandable by their familiarity.
Don’t hesitate to take the time to check out the Dieter Rams expo. Observe his and his team’s work, with the ten principles in mind. Look at how the products are functional, aesthetic, detailed; how it is not fashionable and therefore never out of style.
Running until the 20th of February 2012.
Ten Principles for good design: http://www.vitsoe.com/en/gb/about/dieterrams/gooddesign
Posted at 05:19 PM in iPhone, Java, Web/Tech | Permalink | Comments (1) | TrackBack (0)
October 17, 2011
A quick introduction to not editing video
Pop quiz!
Given (A) foo.mov, (B) bux.avi, and (C) baz.m4v, which can your video software handle correctly?
- A
- A & B
- All of them
- All or some or none of them depending on their codecs, your software's codecs, and whether they're doing anything special with the container format.
And the answer is... 4!
A quick introduction to video codecs and why editing video is hard
Unlike most of the other files on your computer, a video file's extension only hints at what sort of content might be inside. An extension like .mov or .avi refers to the type of the container format, not the video itself. Think of the container format like a Powerpoint file with text and images and perhaps a few other multimedia bits embedded in it. As anyone who's had to do many presentations on a computer other than their own can attest, just because software can open the container, doesn't mean that it has the right fonts to avoid turning all your text into strange symbols.
There are only a handful of video container formats in popular use, so what really matters for handling video is the codec used. The codec is how the video inside the container was compressed. Video usually needs to be compressed because it's big. Just as you couldn't keep your entire music collection on your computer before the advent of the MP3 audio codec, you're not going to have much video if it's not compressed using a good video codec. Unlike audio unfortunately, there are lots of video codecs out in the world. FFMPEG, the go-to video-editing software on my computer, has 147 different video codecs that it understands to some degree or another. There are so many codecs there partially for the usual bad reasons: politics, patents, not-invented-here syndrome, etc; but also for some good reasons. Efficiently recording video is a very different problem than efficiently compressing it for playback, both are a different problem than streaming it, and, as mentioned, video is really big.
A brief aside on the size of video. Pixels on your computer screen are each generally represented by a single 32-bit number. We'll assume that video isn't transparent and call it 24 bits. With a small 1024 by 768 screen, that means that a raw fullscreen image is 2.25MB. Video needs to be about 30 frames per second to look smooth. If video just consisted of flashing images past you at 30fps, one hour of video would be over 200GB. Thank goodness for good codecs.
So dealing with video correctly depends on your software understanding whatever codec was used to encode the video you want to use as input, and then being able to write out video in a codec that the software you're using for playback will be able to understand. And the container format.
Remember a couple paragraphs ago when I told you that there were only a few popular container formats and that what really causes problems is the codec? That's true, except for when it isn't. I recently discovered the hard way that iPhones only record video in one orientation. As far as the phone is concerned, the top of the screen is opposite the volume buttons and holding your phone in any other orientation has the same result as if doing that with a video camera--sideways or upside-down video. Except that it doesn't. Video recorded on a phone plays back pleasantly upright on the phone and in Quicktime, regardless of the phone's orientation. This is because of the container format.
When you record a movie on your phone, the phone stores its orientation in the metadata in the container format. Software like Quicktime reads that information, and rotates the video appropriately before playing it back. Your video editing software probably doesn't. FFMPEG can do all sorts of trick rotating video this way and that, but it doesn't read the .mov headers that give the orientation information. When you convert the video to some other codec in some other container file, the header information is lost and the video is sideways.
On a final note, video encoding is generally a lossy process. That much compression depends on not keeping every last bit of data. The upshot for editing video is that you want to re-encode the video as few times as possible, preferably only once to best preserve the quality.
Given all this, I hope you'll see why editing video is a harder problem than you thought it was, and that you're probably better off avoiding it.
Posted at 11:38 PM | Permalink | Comments (0) | TrackBack (0)
Running A Good Scrum
Whether or not they follow other precepts of Agile development, many software companies have implemented some form of a 'scrum' - a short daily stand-up in which team members report on their current progress. A well run scrum can be an extremely valuable communication asset for team members. A poorly run scrum can be an annoying and time consuming hassle that turns teams off of scrum. And even though teams are supposed to be "self-organizing", in my experience a good scrummaster is always important for a good scrum outcome. Below I've outlined a few common problems for scrums, and things the scrummaster can do to keep things on track.
Here are some tips to make sure your scrum stays awesome.
Problem: Scrum updates turn into discussions between team members
Someone mentions a tricky problem they've been working on, someone else says "Have you tried...", another team member adds his or her opinion and suddenly the scrum has turned into a 15 minute discussion of one person's problem instead of a 15 minute round-up. Discussing problems and solutions is with your co-workers is extraordinarily helpful and valuable - but not during the scrum. A few of these interruptions can quickly add up to a lot of time wasted by team members waiting for their turn to speak. If discussions between team members are a problem, the scrummaster needs to know when to step in (hint: soon) to ask people to take things off line.
Maybe the scrum is the only time in the day when all your team members are together and some of these questions can be answered? Try asking those interested to stay after the scrum to sort it out, maybe start getting the team together for brown-bag lunches to discuss things, or see if any team members can be relocated so that the team is all physically located in the same area of the building. What you don't want is to have five people patiently (not so patiently) waiting while three people finish a discussion.
Problem: Team members are long-winded, confused, or go off on tangents
At the scrum, its important to stick to only the highlights of what you are working on and it is the scrummaster's responsibility to keep things moving if people start to delve into too much detail. If some team members try to dive deeply into problems or go off on tangents, it's the scrummaster's responsibility to bring them back to the point.
This can be especially tricky if you are the scrummaster and the deep diver happens to be your boss, your boss's boss, or another company higher-up who likes the sound of his or her own voice. For the good of the team, its important to not let fragile egos or company politics get in the way of a good scrum. Let everyone know that the scrum needs to move on - maybe by suggesting a separate meeting to talk about this topic.
Some team members may also have trouble organizing their thoughts. Sometimes people aren't used to much public speaking and can get flustered or off track. In these situations it can be helpful to fall back on the three scrum questions - What did you do yesterday? What are you going to do to day? What's blocking (preventing) you from finishing? Getting back to these three questions usually helps bring the focus back.
Problem: Too much chatter when the scrum is going on
Sometimes team members don't start a group discussion, but they do start talking or whispering amongst themselves about something. This not only draws attention away from the current speaker but it sends a message to the entire team that the scrum is not important or worth listening to. It's disrespectful and annoying and should be stopped ASAP. Practice your best librarian "SHH!" on people who talk during the scrum.
Posted at 04:22 PM | Permalink | Comments (0) | TrackBack (0)
September 26, 2011
Name Recognition
Dear reader:
After being heads down in the code for quite a while now, we decided to dedicate a little time to sharpening our writing skills. In the coming weeks you should see a number of contributions covering a wide variety of topics. We'll kick things off today with a seemingly pedestrian subject: the naming classes, files, variables, packages, etc.
Developers can be classified into two types.
Type 1: the naming stickler. This type considers naming to be a very important aspect of writing and maintaining code. These people will often take the time to rename variables and files that other developers created, because it bugs them to see imprecise or misleading names in the codebase. They often have strong views about how to name, and they view themselves as authors writing code for others to maintain in the future. In their view, poorly named code (even when it runs perfectly well) is poor code, because it is difficult to maintain.
Type 2: the naming sloth. Another camp is more relaxed about naming. They value getting the code working over names. In my experience, these people are often pretty good at reading complicated code. They also might not have strong feelings about what constitutes a "good name", so they settle for whatever comes to mind.
I'd like to argue that at as developers, we should all be in the first camp without exception, and for every project. There are a few reasons for this:
1) The code is a lasting impression of the work we have created. At developers this is our core product, along with customer service.*
2) Other developers who come after us will need to maintain our code. Easy to read code adds value for your customers because the code is more maintainable.
3) People will badmouth your abilities if you do not take the time to name things properly, even if you are a supernaturally talented engineer. You don't want to be known as the code slob.
4) Proper naming and organization will help you understand your own code in the future.
Naming is an essential part of software development.
For those of us who speak English as a second language, proper naming can pose added challenges. Nonetheless, it is still important and will require extra effort. If you are not a native speaker, it is a good idea to reach out to someone who is if you are uncertain. A brief 30 second conversation could greatly improve the usefulness of your code, and it will help you improve your English - the language of Isaac Newton and Mr T!
Note that convention trumps inventiveness when it comes to naming. Always.
This means that if plurals are used for table names in the database, you should stick with plurals. If someone before you has 20 classes in a package called "JumboServiceUser", "JumboServiceProduct", "JumboServiceAuth", etc, then name your service "JumboServiceXxx". This is important, even though "Jumbo" adds no value (I would go with "AuthService", personally). The other alternative would be to refactor everything, which is great if you have a few minutes to do so.**
Why? Because when the code is uniform, it is easier to read, and for the reasons outlined above we need to consider ourselves authors who are concerned about others reading our code.
Commenting: Probably another topic, but my take is that comments should be used when you are doing something strange, in-obvious, or something that deserves clarification. Comments for the sake of commenting clutter up the code and usually don't keep up with refactoring. They can rapidly become outdated and annoying.
That's all for now. Happy naming!
* if you are an "in house" developer, your customer is probably your product manager
** If the original author is available, it's a good idea to explain your plan to them before making changes. The human ego can be surprisingly sensitive about the way those bits are arranged!
Posted at 09:40 AM | Permalink | Comments (0) | TrackBack (0)
September 01, 2011
Solving the Firefox Ellipsis Problem
On a recent project for a client, I was asked to make the CSS style "text-overflow: ellipsis" work on FireFox browsers, which has never supported this style. This style basically truncates text and adds an ellipsis (...) when the length of the text overflows the container. Supposedly FireFox 7.0 will correctly this issue, but I'll believe it when I see it.
While there are several good blog posts out there to help out, none of them handle the resizing of the window/text container. That gets tricky because you need to persist the original text so you can make the text longer or shorter as needed during resizing.
Anyway, I thought I'd share my code here to help out any other poor souls stuck with this problem. Here it is (I am assuming you use jQuery; who doesn't? :-)):
var ellipsisPages = (function() {
return {
init: function() {
ellipsisPages.drawEllipses();
},
drawEllipses: function() {
/**
* Trigger FF ellipsis if text overflow isn't supported
and it's a Gecko based browser
*/
var textOverflowSupported =
typeof document.createElement("span").style.textOverflow === "string";
if (navigator.product === "Gecko" && !textOverflowSupported) {
if($('.ffEllipsis').length > 0){
$('.ffEllipsis').each(function(idx, item) {
if ($(item).attr('data') == undefined) {
$(item).attr('data', item.innerHTML)
}
// Determine the text width
function getTextWidth(text) {
var lDiv = document.createElement('lDiv');
document.body.appendChild(lDiv);
lDiv.style.fontSize = item.style.fontSize;
lDiv.style.fontFamily = item.style.fontFamily;
lDiv.style.position = "absolute";
lDiv.style.left = -1000;
lDiv.style.top = -1000;
lDiv.innerHTML = text;
var textWidth = lDiv.clientWidth;
document.body.removeChild(lDiv);
lDiv = null;
return textWidth;
}
var text = $(item).attr('data');
var origTextLength = text.length;
var tw = getTextWidth(text);
var parentWidth = item.clientWidth;
// TODO: perhaps recurse up to a width?
if (parentWidth == 0) {
parentWidth = item.parentNode.clientWidth;
}
// Shrink text to fit; TODO: optimize this algorithm
while (parentWidth > 20 && tw > parentWidth - 20) {
text = text.substring(0, text.length - 1);
tw = getTextWidth(text);
}
if (origTextLength > text.length) {
var lDiv = null;
// See if we already added our custom element
for (var node in item.childNodes) {
if (node.localName == 'ldiv') {
lDiv = node;
break;
}
}
// Create an inner element if we haven't already
if (lDiv == null) {
while(item.childNodes[0])
item.removeChild(item.childNodes[0]);
lDiv = document.createElement('ldiv');
lDiv.style.fontSize = item.style.fontSize;
lDiv.style.fontFamily = item.style.fontFamily;
item.appendChild(lDiv);
}
lDiv.innerHTML = text + "…";
}
});
}
}
}
}
})();
$(document).ready(function(){
// Call on a window resize
$(window).resize(ellipsisPages.drawEllipses());
});
Note the area you want 'ellipsified' needs to have the CSS class 'ffEllipsis' assigned (and the JavaScript included, of course) and you are off and running! One note: this can be a performance drag on the page if there are a lot of elements needing ellipses. Use with caution.
Enjoy!
Posted at 02:34 PM in Web/Tech | Permalink | Comments (1) | TrackBack (0)
June 20, 2011
Grio Helps Playnomics to Enhance its Software Platform
Playnomics has agreed to leverage Grio's talents to provide software development assistance and recommendations for architecture improvements and optimization.
Playnomics provides a platform that publishers of online games can use to understand who their most valuable players are and how to acquire players who will be the most dominant to their game. The platform collects in-game event data and uses proprietary algorithms in combination with an advanced recommendation engine to quantify player value.
In this collaboration, Grio will help build applications that interact with and enhance the Playnomics platform.
Posted at 02:16 AM in News | Permalink | Comments (0) | TrackBack (0)
March 09, 2011
Alltel Wireless Taps Grio for Website Development
Allied Wireless (Alltel) has selected Grio to create its web presence that includes the alltelwireless.com <http://www.alltelwireless.com> consumer website as well as Alltel’s corporate intranet website.
Grio is assisting Alltel in various technical areas ranging from architectural recommendations to service integrations with Alltel's internal systems. Grio is providing software development resources for the creation and maintenance of Alltel’s website properties and content management systems.
Allied Wireless Communications Corporation is a wireless telecommunications provider based in Little Rock, Arkansas. It currently serves approximately one million subscribers in six states - Georgia, North Carolina, South Carolina, Illinois, Ohio and Idaho.
Posted at 09:09 AM in News | Permalink | Comments (0) | TrackBack (0)
February 15, 2011
Game Developers Conference 2011 with Schedule Builder powered by Grio
This year's Game Developers Converence® (GDC) will take place from Feb 28 - Mar 4, 2011 in Moscone Center, San Francisco, CA. Grio developed and maintains Schedule Builder, Schedule Build Mobile (enhanced for Blackberry devices and iPhones) and Vault for GDC.
The GDC is the world’s largest professionals-only game industry event. Presented every spring in San Francisco, it is the essential forum for learning, inspiration, and networking for the creators of computer, console, handheld, mobile, and online games.
The GDC attracts over 17,000 attendees, and is the primary forum where programmers, artists, producers, game designers, audio professionals, business decision-makers and others involved in the development of interactive games gather to exchange ideas and shape the future of the industry. The GDC is produced by the Think Services Game Group, a division of United Business Media.
Posted at 10:21 AM in News | Permalink | Comments (1) | TrackBack (0)
January 27, 2011
vChatter Dials up Grio for Software Help
vChatter has selected Grio to help enhance its Internet audio-visual communication tools and platforms. These tools provide video-chat capabilities and other features to the public via Facebook and other social networks. vChatter is the largest video chat service on Facebook with over 2 Million users and over 25 Million calls established. It provides a fun, casual and comfortable environment to discover new people and develop meaningful relationships.
In this collaboration, Grio team will provide engineering expertise and support for custom software development, recommendations for software design and architecture improvement and optimization.
Posted at 05:22 PM in News | Permalink | Comments (0) | TrackBack (0)