Archive for February, 2009

Facebook Camp

Saturday, February 28th, 2009

We were invited to Facebook Camp give a 5 minute presentation on our Facebook Connect implementation.

The event was organized by the folks at Refresh Partners, Roy Pereira and Colin Smillie. They put together a great event, I was impressed by how well it was put together (hey, I still remember how much work went into organizing my wedding :) ).

We’ve met a lot of fun folks as well, like Loren Norona from AppsSavvy, Faramarz Hashemi from Lookality, Natasha D’Souza from VirtualEyeSee, Majid Hashemi, Sina Amiri, and of course last but not least the organizers Roy and Colin.

The presentation on Facebook Connect was quite informative (Matt Wyndowe did a great job!), and it helped us see a lot more potential integration between Facebook and Zipalong. So expect a lot of cool stuff in this area in the next little while.

Fun stuff,
Dimi

Release 1.7.20

Sunday, February 15th, 2009

Here’s what’s new:

  • Shares tracking and control. If you are an editor you can see who sent the shares, when, and even disable them if you need to. Non-editors can track only their own shares.
  • Nicer, cleaner emails. We like our emails to be small, clean, and easy to read. We’ve put a lot of work into them, we hope you’ll like them too.
  • Improvements in the registration process, not something that you care about once you signup on Zipalong :)
  • Many small improvements and fixes all over the place

How do you get to track your shares?

Zipalong’s Privacy settings

Friday, February 13th, 2009

Zipalong has a comprehensive and flexible privacy system that is simple and intuitive to use.

You can customize the privacy settings of your trips and pictures as much as you like. For example you can set your trips and pictures to be viewable only by your friends with one simple step.

What if you have a trip that you’re really proud of, and you want everybody to see it? Nothing simpler: just make that one trip public. You have some pictures in that trip that you want to keep private? Again, just set the picture privacy level of each picture to a level you feel comfortable with.

Good, but what if you want to show a trip to someone who is not your friend? Just send him or her a trip share. Can they request access to my private trip? Of course they can!

The nitty-gritty

So if you’re interested in the technical details of how all this works read on.

To understand privacy we need to explain a bit the hierarchy of the various entities used on Zipalong. The top most entity would be the user profile. The user profile contains one or more trips which in turn contain an itinerary, journals entries, and pictures. You can control the privacy setting at each of the three levels.

The profile privacy is accessible through the settings link on your home page. It has three levels:

  • friends only - your profile can be accessed only by your friends
  • members only - your profile can be accessed only by Zipalong members
  • public - your profile is public, indexable by Google

The trip privacy is accessible on the trip cover tab, under the settings link. It has five levels:

  • default - meaning it inherits your profile privacy setting
  • participants - the trip can be viewed only by trip participants
  • friends - the trip can be viewed only by your friends
  • members - the trip can be viewed by all Zipalong members
  • public - trip is public, indexable by Google

The picture privacy is available on the picture details bubble. It has six levels:

  • default - meaning it inherits your trip level setting
  • private - nobody but you can access this picture
  • participants - the picture can be viewed only by trip participants
  • friends - the picture can be viewed only by your friends
  • members - the picture can be viewed by all Zipalong members
  • public - picture is public, indexable by Google

It is key that the same hierarchy that we identified for the entities above applies for the privacy levels. Therefore, the profile privacy level will be inherited by the trip privacy level, and the trip privacy level will be inherited by the picture privacy level. So if you set the profile privacy level at friends only, automatically all your trips and pictures will only be available to your friends.

Release 1.7.18

Monday, February 9th, 2009

One more release aimed at stabilization and addressing user feedback.

Here’s what’s new:

  • The system will now keep you updated if any of your friends join Zipalong. You’ll be notified via email.
  • You can easily suggest friends to new users to help them get started (assuming you have common friends, of course…)
  • Opera is fully supported browser now. We recommend version 9.6 or later. Please send feedback if you encounter any problem.
  • We’ve added a slideshow link right in your newsfeed so you can easily get there when someone uploads pictures into a trip.
  • The picture details dialog defaults now to the larger version of the picture. We’d like to hear from you if you think it’s too big for your screen.
  • Some important speed optimizations in our relentless drive to an ever faster experience.
  • By popular demand we’ve added a “Download All” link in the slideshow so you can download all pictures in one go. Look for it in the left-bottom corner of the screen, under Picture Details.
  • Lots of other fixes and feedbacks made it in

The big happy news of course is that the team is one (super cute) baby stronger! Needless to say, we are all very excited. Maybe we’ll find out more from the happy parents…

Release 1.7.16

Monday, February 2nd, 2009

For the last few days we decided to go back and address many feedbacks that were waiting in the wings. And we did such a good job, that we couldn’t wait to bring them online :)

Here’s what’s new:

  • Nicer waypoints: when building land-route itineraries, we now handle waypoints a lot better than in the past. In particular, long drives are handled much soother. It took us a while to get it right, and we hope did. Let us know if you still discover problems with it.
  • Improved newsfeed: there were several enhancements here, like displaying the pictures on which tags you suggested were declined, better handling of deleted or private pictures, showing the picture caption on hover, and so on.
  • Speed improvements: the Share/Invite dialog has seen some dramatic improvements when you have to deal with large numbers of friends.
  • Feedbacks: many were addresses, too many to list here.

We are already planning a new release in a few days to bring you another batch of improvements across the site, so stay tuned.

jQuery: x800 faster .clean() function

Sunday, February 1st, 2009

As I mentioned in my previous post, we’ve noticed severe performance problems when loading large chunks (660KB) of HTML via an .ajax() call.

Short story short, .load calls .clean() which uses .trim() which sucks. In our case it was taking around 2.7s!

So how can we get rid of .trim()? Here’s the first attempt:

 	// Trim whitespace, otherwise indexOf won't work as expected
-	var tags = jQuery.trim( elem ).toLowerCase();
+	var tags = elem.replace(/^\s+/, '').toLowerCase();

This is great as it takes the code from 2700ms to about 440ms.

But we can do a lot better! Why are we doing a .toLowerCase() on a 660KB string just to test the first 4-5 chars?! Also, we use indexOf() that will have to scan the entire 660KB string just to see if the string begins with a given string — and we do that a 6-7 times!

Let’s get rid of all that unnecessary work:

                                // Trim whitespace, otherwise indexOf won't work as expected
-                               var tags = jQuery.trim( elem ).toLowerCase();
+                               var tags = elem.replace(/^\s+/, '').substring(0, 10).toLowerCase();

                                var wrap =
                                        // option or optgroup
@@ -906,11 +906,12 @@
                                if ( !jQuery.support.tbody ) {

                                        // String was a <table>, *may* have spurious <tbody>
-                                       var tbody = !tags.indexOf("<table") && tags.indexOf("<tbody") < 0 ?
+                                       var hasBody = /<tbody/i.test(elem);
+                                       var tbody = !tags.indexOf("<table") && !hasBody ?
                                                div.firstChild && div.firstChild.childNodes :

                                                // String was a bare <thead> or <tfoot>
-                                               wrap[1] == "<table>" && tags.indexOf("<tbody") < 0 ?
+                                               wrap[1] == "<table>" && !hasBody ?
                                                        div.childNodes :
                                                        [];

That brings it down to about 4ms! And I’ll bet you it makes the browser “feel” lighter too as it uses a lot less memory.

Size impact is tiny as well. The simpler first try adds just 6 bytes to the minimized version, or only 4 bytes to the gziped version. The second try adds 17 bytes on top of the first one (in the minimized version), or only 12 bytes to the gziped version.

You can track this patchset in ticket 4037.

jQuery: x1000 faster test for blank strings (on large strings)

Sunday, February 1st, 2009

We are making heavy use of jQuery, and we were glad to learn version 1.3 is so much faster than 1.2.

However, we discovered that loading a particularly large popup takes a long time, somewhere in the neighborhood of 6s! After profiling it a bit, we discovered that jQuery takes close to 3s just to process the returned HTML.

First thing that popped up on the radar was .trim(). There’s an independent effort to try to make it faster, but that can be improved only so much. It would be best if we could avoid it altogether, at least in core.

In fact, one large use case for .trim() is to test for blank strings, that is empty strings or ones made up only of whitespaces. Of course, that’s a much easier problem to solve, so it would seem best to expose a different function that does exactly that. I called that function .blank()

Here’s my first try at that function:

      blank: function( text ) {
              return !text ||  /^\s*$/.test(text);
      },

When used on a large string (660KB of HTML), this executes in about 10ms vs. .trim() at 2000ms!

At this point I sent in a patch, just to discover in the morning (it was 3:30AM when I did this) that we can do a lot better :)

Here is my second version of .blank():

      blank: function( text ) {
              return !text || !/\S/.test(text);
      },

This one executes in less than 0.5ms on the exact same monster string. Now that’s pretty sweet.

Given how much .trim() is used to test for a blank string, and how much lighter it is to implement, I think it should be exposed as part of the core.

As for the price we pay (in terms of size), it is absolutely tiny: 45 additional bytes for the minified version, or only 17 more bytes when it is gziped. Not bad of >1000x speed improvement.

This takes care of one use of .trim() in jQuery’s core. Stay tuned for the next one.

You can follow the faith of this patch in ticket 4036.

Here is the complete patch:

--- jquery-1.3.1-orig.js        2009-02-01 10:53:19.000000000 -0500
+++ jquery-1.3.1-d.js   2009-02-01 10:53:44.000000000 -0500
@@ -636,9 +636,7 @@

        // Evalulates a script in a global context
        globalEval: function( data ) {
-               data = jQuery.trim( data );
-
-               if ( data ) {
+               if ( !jQuery.blank( data ) ) {
                        // Inspired by code by Andrea Giammarchi
                        // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
                        var head = document.getElementsByTagName("head")[0] || document.documentElement,
@@ -1050,6 +1048,10 @@
                return elem[ name ];
        },

+       blank: function( text ) {
+               return !text ||  !/\S/.test(text);
+       },
+
        trim: function( text ) {
                return (text || "").replace( /^\s+|\s+$/g, "" );
        },
Until next time,
Dimi