Interestings
MyRA won a design award!
MyRA, an iOS app for rheumatoid arthritis patients designed by Jessica Miller (former Pivot) and developed here in SF, won a design award. Awesome!
MyRA, an iOS app for rheumatoid arthritis patients designed by Jessica Miller (former Pivot) and developed here in SF, won a design award. Awesome!
A good style guide is similar to a recipe for baking a cake. The goal is to break down design attributes in a simple format that bridges the beginning stages of the design to development. Here at pivotal we don’t create fancy spec documents but rather a simple break-down of ui elements in one easy recipe:
Break down your type sizes in categories:
• H1 large 34 pt-size
• H2 medium 21 pt-size
• H3 small 13 pt-size
• A1 paragraph 10 pt-size)
Color Guide:
• Call-out the first and secondary color choices
• Show the Hex and RGB specifications next to each specific color swatch
Forms:
• Communicate the states and their attributes
If your worried about a “inner shadow,” show a screen shot of your photoshop inner shadow dialog window.
(this will give developers an ideas of what the pixel implementations are)
Buttons:
• Communicate the states and their attributes
• Call out any subtle gradients or strokes
Background:
• Noise or subtle textures
Stop and capture these attributes during early phases of the visual design process and stay consistent as you move throughout or else your cake will taste like eggs.
I had the pleasure of appearing on the Ruby Rogues podcast this week talking about how Pivotal Labs does Extreme Programing (XP). Accompanying me was ex-pivot Josh Susser, James Edward Gray, Avdi Grimm, and Charles Max Wood. I had more fun recording it than I would have imagined and the pivots here have enjoyed listening to it, I hope you will too!
Apple OS X’s PlistBuddy is a tool for modifying p-list (i.e. Property List, .plist) files; however, it has difficulty populating null keys (though little difficulty creating them). This blog post describes a method of using PlistBuddy to populate null keys by first creating & populating a placeholder key and then using the copy directive to populate the null key.
Creating the null key is easy; use a double-colon (“::”):
/usr/libexec/PlistBuddy -c "add :: dict" /tmp/junk.plist; cat /tmp/junk.plist
File Doesn't Exist, Will Create: /tmp/junk.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key></key>
<dict/>
</dict>
</plist>
Populate the null key by first creating a placeholder key, populating that, and then copying it to the null key:
rm /tmp/junk.plist
/usr/libexec/PlistBuddy -c "add :placeholder dict" /tmp/junk.plist
/usr/libexec/PlistBuddy -c "add :placeholder:nullKeySubKey string 'hello'" /tmp/junk.plist
/usr/libexec/PlistBuddy -c "copy :placeholder ::" /tmp/junk.plist
/usr/libexec/PlistBuddy -c "delete :placeholder" /tmp/junk.plist
cat /tmp/junk.plist
....
<dict>
<key></key>
<dict>
<key>nullKeySubKey</key>
<string>hello</string>
</dict>
</dict>
...In general, PlistBuddy will concatenate a double-colon (“::”) into one (“:”). So the key ::nullKeySubKey is treated as :nullKeySubKey, i.e.
/usr/libexec/PlistBuddy -c "add /usr/libexec/PlistBuddy -c "add ::nullKeySubKey string 'hello'" /tmp/junk.plist will not create a null key.
Attempts to trick PlistBuddy with an empty string (e.g. “”) won’t work, either. /usr/libexec/PlistBuddy -c "add :'':nullKeySubKey string 'hello'" /tmp/junk.plist will not create a null key.
The ~/Library/Preferences/com.apple.desktop.plist is a typical p-list file that contains a null key. First, we’ll convert the p-list from binary to XML using plutil, and then we’ll examine the XML to confirm that there is a null key:
plutil -convert xml1 ~/Library/Preferences/com.apple.desktop.plist
less ~/Library/Preferences/com.apple.desktop.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Background</key>
<dict>
<key>spaces</key>
<dict>
<key></key>
...
There was a question about uploading to S3 on an iOS project backed by non-rails ruby.
Best practice (Rails or not) seems to be using the AWS gem to generate URLS, send those to the phone, and do all the manipulation and uploading from the device.
Ping pong tournament with other startups @ Pivotal Labs. We have 2 teams made up of: Palermo and Phan, and Michael and Danny. Come cheer us on!
The Bulletproof font-face syntax effectively gives you broad browser support for your fancy (or austere) web fonts. However, using the Bulletproof font-face syntax with SASS carries a couple of gotchas. Reference this article when you are starting a new project and setting up the fonts.
//
// mixin for bullet proof font declaration syntax
//
@mixin declare-font-face($font-family, $font-filename, $font-weight : normal, $font-style :normal, $font-stretch : normal) {
@font-face {
font-family: '#{$font-family}';
src: url(font-path('#{$font-filename}.eot'));
src: url(font-path('#{$font-filename}.eot?#iefix')) format('embedded-opentype'),
url(font-path('#{$font-filename}.woff')) format('woff'),
url(font-path('#{$font-filename}.ttf')) format('truetype'),
url(font-path('#{$font-filename}.svg##{$font-family}')) format('svg');
font-weight: $font-weight;
font-style: $font-style;
font-stretch: $font-stretch;
}
}
@include declare-font-face('Gill Sans', 'Gill-Sans-MT-Pro-Light', 200);
@include declare-font-face('Gill Sans', 'Gill-Sans-MT-Pro-Italic', 400, italic);
It uses SCSS syntax, because SCSS can do multi-line and SASS can’t. It’s fine if you use SASS syntax, just name this file .scss and @import it just the same.
The mixin has defaults for last three parameters (denoted by the colons in the method signature). This way, you don’t have to go out of your way to specify normal each time.
It uses the font-path inside of the url(…). That is because I have seen lots of bugs with complex file names for the font files, and the quotes guarantee the file will be found. Either rename all your fonts with no dashes or spaces or other weird characters, or use this trick.
This isn’t really a trick, but it is more convienent to put all font variants inside the same font-family. It just makes it easier to have everything as “Gill Sans” and change the font-weight: italic, rather than having “Gill Sans” and “Gill Sans Italic” and however other many variants you have.
The original trick: Originally developed by Paul Irish (Bulletproof font-face syntax) and more recently by Fontspring (The New Bulletproof syntax), the syntax gives you broad browser support for your web fonts. IE8 loads the first .eot file and is done, but it “tricks” IE9 by relying on a bug in the parser. It dies on the question mark in the second line, and doesn’t continue on to the other file formats (which don’t work in IE9, and would cause it to not display). The other formats are handled by Safari, Chrome and Firefox, and carry no such trickery.
And be sure to follow me on twitter for new fancy posts!
When you get into the rhythm of pushing new features through the product development lifecycle, it can be addictive. Theoretically every new push brings more customer value, so why not keep going? Restated, when should you stop writing code and put your efforts elsewhere?
If you assume that you’ll never truly know (except in a vacuum), I think there are two main factors to consider: First, every feature you implement requires justification to build and effort to maintain. Second, if you are following lean principles it’s important to create valid experiments by measuring the viability of your current product, not one that is constantly shifting.
Let’s break that down.
Internalizing the lifetime cost of everything you build is critical to your [and your customers’] sanity. A friend of mine recently endeavored to simplify her life by trying the 100 thing challenge. Take a look at the three driving principles and you’ll see the parallels to product development:
Reduce by reducing the number of our possessions for an extended period of time, we prove to ourselves that consumerism does not define us
Refuse by refusing to go along with in the misleading lifestyle of consumerism, we form new priorities in line with personal virtue and what is best for the world around us
Rejigger by rejiggering our lives through simplicity, we nurture better relationships with family and community and nature
If you ignore the preachiness, this can serve as a template for your product. Don’t add new features just for the sake of it. The best apps do something specific, and do it very well. Refuse to add something that serves as a barrier between your customers and derived value just because it’s an emerging trend. Lastly, by focusing on the relationship your customer has with your product, you’ll shift focus from business-centric to customer-centric interaction goals.
The purpose of shipping early and often is to err on the side of learning too much. If you’re launching a new product, chances are you’re only going after early adopters. These people LOVE using unfinished products, since the chance of finding something that exactly addresses their wants/needs is high. These initial customers probably try 10 new services a week. Instead of chasing their wish list, you should figure out their common thread and target more of their peers. Reach out to them and figure out which part of your value proposition they care about — this messaging should be clear and consistent throughout your marketing and product (i.e., product/market fit). If you can tune this acquisition engine, you should see clear improvement in your cohorts without additional feature work.
The caveat emptor to the above advice is that you can never have too many tools at your disposal. While your product may be “good enough,” the instrumentation you use to measure your product are probably not. Devote some dev cycles to email tracking, behavioral metrics and personalized drip campaigns. Some might argue these are core product enhancements, even if they’re invisible (when done well). If they are helping you attract and engage new customers, I think it’s a moot point — just make sure that you’re not spending your time building the perfect product at the expense of one that’s good enough.
On the Tracker team, I love hearing great stories about our users and the things they are able to accomplish using agile methodologies and collaboration. I’m also a woman in tech and have a keen interest in supporting other women as well as other diverse groups who work persistently to reach their goals in tech. One story I’ve been following lately is the story of Tracker user Martha Chelimo Njeri Chumo (“Njeri”) and her teammates.
After raising funds, Njeri was set to attend this summer’s Hacker school in New York. Unfortunately she was denied a visa by the U.S. consulate in Kenya, curtailing her original plans.
It’s very easy when facing something beyond your own control to give up or to become discouraged. Njeri, however, was able to completely turn the tables on this setback by looking towards her own community and reaching for collaboration. Today, Njeri and her teammates are making plans to set up Nairobi’s own hacker school: Nairobi Dev Camp.
While I’ve given the Nairobi Dev School some support on my own, I decided to take a lesson from them and rope in my own team to support her cause.
Unsurprisingly, the rest of the Tracker team was also inspired by Njeri’s determination and wanted to pass her story along. While she still has a lot of work ahead of her to make this camp a reality, she also recognizes that she can’t do it alone, which is why she launched a campaign for Nairobi Dev School on Indiegogo.
We hope that you will be as moved as we are and help the Nairobi Dev School anyway you see fit. We wish Njeri and her teammates success.
Will Read & ex-pivot Josh Susser talk with folks like Avdi Grimm about XP and what it looks like at Pivotal Labs. ~1hr.
http://rubyrogues.com/109-rr-extreme-programming-with-will-read/
Building a bookmarklet provides an interesting challenge. It involves interaction a website your application does not control where that site could be anything with any number of dependencies on CSS or Javascript libraries. The first choice to make is trying to work with that website and probably setting an !important on every CSS selector used and hope that there’s no namespace or versioning clashes with any Javascript included; or use an iframe.
Iframes seem to have fallen out of favour in recent times but the sandboxed nature of the content inside an iframe mean the worries of CSS and Javascript clashes are gone. However this is replaced with a communications overhead of communicating between the host document and the iframe. I wanted to touch on some of the things our team did on a recent project to try and make this fairly seamless.
A bookmarklet is a small piece of Javascript that a user can drag onto the bookmark bar and upon pressing the link the Javascript will run. Because there is no guarentee what site will be loaded and if that will have any number of Javascript libraries included it’s best to use plain old Javascript to create an iframe element and append it to the body of the document. Our bookmarklet also needed to have some javascript if for nothing else but to be able to dismiss and remove the newly created elements. This can be done through the creation of a script element and appending to the body just like the iframe itself.
Once the iframe and script tags are appended they are treated the same as any other element. The content is loaded and the script is executed. The next step is getting the window to talk to the iframe. As a convienence the domain with protocol and port of the iframe is stored in a variable for later use.
element = document.createElement('iframe');
element.id = 'example_iframe';
element.src = 'example.com?referrer=' + window.location;
document.body.appendChild(element);
script = document.createElement('script');
script.src = 'example.com/bookmark.js';
document.body.appendChild(script);
The location is also sent to the remote server to load the iframe so that it can also store that location for passing messages to the host.
At this point this Javascript could also append a script tag to a version of libraries that may be required for it’s own application to run. It could also test for the existance of that library before hand so it doesn’t bring down an incompatible version. For example, bringing in jquery:
if ($ === undefined) {
var jq = document.createElement('script');
jq.src = "//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js";
document.body.appendChild(jq);
}
The postmessage method is available to communicate bewtween the window and the iframe. The host window with a reference to the iframe can call postmessage with a string as a message and as a security measure the target location. We had stored this during the loading of the elements as described above.
iframe.postmessage('hello', 'www.example.com');
That message won’t get anywhere unless the iframe is listening for the message event on the other end.
// native Javascript
window.addEventListener('message', function(event){ … });
// jQuery
$(window).on('message', function(event){ … });
I’ve used the native Javascript above but really, once in the iframe itself the application has full control and could use JQuery or any other library at this point. Our application needs to listen to messages on both sides though so we needed the above to run in the host anyway.
Sending a message is all well and good but any non-trival application is going to have more than one function to run. We took influence from Remote Procedure Calls (RPC) to call functions within the host and remote sites. The message sent was stringified JSON with a very light ‘schema’ of the function to run and parameters to send to the function.
{
f: 'theFunction',
params: {
...
}
}
The recipient could then parse the string it knew to be JSON, extract the function to call and call it with any optional parameters also sent. This does create a binding between the host and the iframe but as the appliction controlled both sides we deemed it an acceptable risk. The function can be run from the window like so:
window['theFunction']();
// or from the event listener
var fn = JSON.parse(event.data).['f'];
window[fn]();
It was mentioned earlier that one of the goals of using an iframe was not to clobber any Javascript namespaces but we did end up including Javascript in the host and to avoid this we used an application namespace. However calling that as a property on the window would no longer work.
// doesn't work
window['my.app.function']();
// works
window['my']['app']['function']();
We looked to ElementalJS as an example of dealing with namespaced functions to parse the function.
window.addEventListener('message', function(){
var fn = window;
var data = JSON.parse(event.data);
var namespaced = data['f'].split('.');
for (var i in namespaced) {
fn = fn[namespaced[i]];
}
fn(data.params);
});
This is natually fairly crude, some defensive code could be added but this demonstrates the intent of the processing. Defense like making sure that function existed, or that ‘f’ existed in the data for the window could receive a message from another iframe.
The bookmarket inserts two elements, an iframe and a script. The iframe has the source example.com?referer=bar.com. bar.com is inserted as a variable in the iframe Javascript code. The iframe inserted has an id of example_iframe
The host Javascript listens to the message event
window.addEventListener('message', function(event){
var fn = window;
var data = JSON.parse(event.data);
var namespaced = (data['f'] || "").split('.');
for (var i in namespaced) {
fn = fn[namespaced[i]];
}
if (typoe(fn) === 'function') {
fn(data.params);
}
});
The host also sets up a namespace and a function to be called.
window.example = window.example || {};
example.hello = function(){ … }
When the iframe has loaded, it sends a ready message to the host
$(document).ready(function(){
var data = JSON.strinify({f: 'example.hello'});
parent.postMessage(data, referer); // referer set by server from the request param for the iframe
});
The host from the script loaded in the bookmarket has the example.hello function and it’s run. This in turn replies to the iframe.
var example.hello = function(){
var iframe = document.getElementById('example_iframe');
var data = JSON.stringify({f: 'example.world'})
iframe.contentWindow.postMessage(data, 'example.com');
};
The iframe has an event listener which is the same code as the host, and runs the function example.world
var example.world = function(){
// hello, world
};
This has shown some of the techniques for a ‘hello, world’ bookmark with two way communication between host and iframe that uses Javascript namespaces. This was enough to get our application off the ground as the two way communication acted as a solid base to build upon.