create blog

go home go home
  1. about
  2. code
  3. wiki
  4. blog

Archive for the ‘Uncategorized’ Category

SproutCore Animation: SC.Animatable

Monday, December 21st, 2009

In real life, things don’t just suddenly disappear from one place and appear in another. This isn’t Harry Potter1. We can’t apparate.

But chances are, you know that, and don’t need to be convinced of the wonder of animation. So, instead, I will tell you about the beginnings of SproutCore’s support for animation.

SproutCore’s animation support currently takes the form of a mixin for views: SC.Animatable. Here are some of the features:

  • Relatively fast. It can animate 1000 or so views with reasonable performance on a fast machine. Oddly, the biggest performance hurdle may now be the rendering engine (unless you are on IE—the slow script speed will kill you first).
  • Uses CSS transitions where possible. Oddly, CSS transitions do not appear to be significantly faster2 than JavaScript transitions (again, indicating the rendering engine could now be the bottleneck).
  • Support for Layout. This almost goes without saying, as what animation framework would be worth anything if it didn’t allow you to animate the position of views?
  • Support for Opacity. So you can fade things in and out.
  • Support for display:none. You can add transitions to the display property, and they will simply be ignored (display gets applied immediately) except in the special case of display:none, which will only apply after the specified duration. See display:none notes
  • Support for Timing Curves. You can specify curves to adjust the speed of the animation, and have them work in both JavaScript transitions and CSS transitions, or optionally, CSS transitions only.

Animating with SC.Animatable

It is quite simple. Here are the steps:

  • Add “sproutcore/animation” to the required frameworks array in your Buildfile (in quotes and all).
  • Mix in SC.Animatable to your view.
  • Add transitions.
  • Change layout, opacity, or display.

So, how about some code? First, let’s take a look at a Buildfile:

# The Contacts application Buildfile
config :contacts, :required => [:sproutcore, :"sproutcore/animation", :forms, :pomona]

Now, how about a view?

myView: SC.View.design(SC.Animatable, { // mix it in
  transitions: {
    // and add transitions
    left: .25,
    top: {duration: .25},
    width: { duration: .25, timing: SC.Animatable.TRANSITION_EASE_IN_OUT }, // with timing curve
    height: { duration: .5, timing: [0, 0, 0.58, 1.0] }, // with custom timing curve
    opacity: { duration: .5, timing: SC.Animatable.TRANSITION_CSS_EASE_IN_OUT }, // CSS-transition-only timing function (JavaScript gets linear)
    display: .75 // a bit longer than opacity 
  }
})

And finally, to animate!

myView.adjust("left", 250);
myView.set("layout", {left: 100, top: 300, width: 250, height: 340});
 
// style manipulation: a feature of SC.Animatable
myView.adjust("opacity", .5);
myView.set("style", {opacity: .7});
myView.adjust("display", "none"); // applies after .75 seconds
myView.adjust("display", "block"); // applies immediately
 
// disable animation temporarily to change something directly
myView.disableAnimation();
myView.adjust("opacity", 0).updateStyle(); // makes invisible and applies immediately.
myView.enableAnimation();
myView.adjust("opacity", 1); // fades in to visibility (applying lazily).

Pretty simple!

display:none notes

When animating the display: none property, you should add a bit of extra time to its transition.

If everything was animated using JavaScript, there would be no issue. However, because SC.Animatable takes advantage of CSS transitions where possible, and CSS transitions don’t necessarily begin immediately, if you don’t add extra time to display’s transition it could set display:none before the other transitions end (in effect, cutting them short). In the most usual case—an opacity transition—the view will start to fade out and then suddenly disappear. Again, the easiest way to fix this is by simply adding a bit of time to the transition for the display property.

The Problem with Callbacks

Currently, SC.Animatable does not have callbacks for animation completion. In fact, unless the view is being animated using JavaScript (non-WebKit browsers and Firefox earlier than 3.6, I believe), it is even impossible to tell if a view is animating.

For JavaScript-based transitions, this should not be a terribly difficult feature to add in general; there is a question of, since animations are started and stopped automatically instead of explicitly by the developer, how those callbacks would be set: should it be one per property? One per the whole view?

For CSS transitions, however, this is trickier. There is an event fired in WebKit’s implementation, but I am unable to find any documentation on Mozilla’s support for it (or, for that matter, lack thereof).

Even if support was present, it may or may not be tricky to reconcile that with the actual animation library: what if one transition ends, but a new one starts immediately after? Will the event fire after the new one begins, tricking others into thinking it has already fired? It may be more reliable to “guesstimate,” and just set a timer for a callback to go off somewhere shortly after the target time (much like the way the “display” transition is implemented).

Demo

It is not “officially” released by any means—it still needs fixes, including at least one animation-related one—but a fancy demo of the animation (used in FormView, another project I am working on) can be found here.

The code is all online too at GitHub. I am slowly making it into a full sample app for SproutCore that demonstrates animation, FormView, and Comet—and hopefully multiple back-ends (I’m working on a wacky Python one right now, and I’d like to make a node.js-based one eventually, too).


  1. I had to slip in some HP reference.3 

  2. It is tricky to tell, because I cannot measure the FPS of transitions objectively. Actually, the JavaScript transitions looked faster than the CSS transitions. 

  3. I love footnotes too much. In some papers, I have filled almost half the page with footnotes. The thing about footnotes is that they allow you to add extra information (sometimes a lot of extra information) while not badly interrupting the flow of the original piece. 

SproutCore, Todos, Django, and Comet in < 100 Lines of Code

Friday, December 11th, 2009

This is a continuation of the Django Todos Back-End Tutorial that adds Comet using Roots.

Warning: This is not tested by anyone other than me at the moment, and might prove somewhat bumpy. Feedback is very welcome!

Here is a high-level introduction of how Roots works is available here.

Very little needs to be done to add Comet. There are three main steps on the server side:

  1. Get Roots and Cornelius.
  2. Add views to allow clients to subscribe/connect to events.
  3. Trigger events.

On the client side, there are also three major steps:

  1. Get+set up Pomona.
  2. Subscribe to events.
  3. Receive events.

First Step

You will need at least three terminal windows—one for each server. You might as well open them first.

You will have three directories to work out of:

  • Roots (or Dobby)—not much to do here :)
  • SproutCore Project
  • Django Project

Tip: if you want to be able to open only one TextMate window (or whatever IDE you use), you can place the Django project (and, even, Roots) in a subdirectory of your SproutCore project.

Server-Side

Getting Roots and Cornelius

First, let’s get Roots. In the Roots terminal, navigate to the folder you’d like to keep it, and:

$ git clone git://github.com/ialexi/dobby.git

And now, you can start it (assuming you have the prerequisites):

$ cd dobby
$ python dobby.py
Instating Dolores High Inquisitor of Hogwarts

There may be some deprecation warnings due to issues with Twisted, but they don’t cause problems. (Roots should be ported to node.js soon, and node.js doesn’t have these issues).

Now, get Cornelius. In the Django terminal, navigate to your Django project directory. Then run:

$ git clone git://github.com/ialexi/cornelius.git

Next, you need to add Cornelius to your Django project as an application (making it really easy to import). To do this, edit settings.py and add “cornelius” in the INSTALLED_APPS setting:

	'django.contrib.auth',
	'django.contrib.contenttypes',
	'django.contrib.sessions',
	'django.contrib.sites',
	'todos',
	'cornelius' # <- SEE! :D

Making Views to Make Connections

We can’t trust the client. As such, we do not allow the client to connect itself to any path it wants. Instead, it has to go through our Django web app. Ironically, since we’re just doing a simple application, we are in fact, going to allow the client to connect to whatever it wants (I’ll show how to change this, though).

First, you need an updated urls.py:

from django.conf.urls.defaults import *
 
urlpatterns = patterns('',
	# You don't have to do it this way, but in this example, we match
	# optional ending slashes.
	(r'^tasks/?$', "todos.views.tasks"),
	(r'^task/(?P<taskid>[0-9]+)$', "todos.views.task"),
 
	# See here:
	(r'^tasks/connect/(?P<uid>[^\s]+)$', "todos.views.connect"),
	(r'^tasks/disconnect/(?P<uid>[^\s]+)$', "todos.views.disconnect")
)

This makes tasks/connect/(CLIENT_ID) call our “connect” view, and tasks/disconnect/(CLIENT_ID) call our “disconnect” view.

Now, for the views themselves:

# Import cornelius's Dudley
from cornelius import dudley
 
# Connect
def connect(request, uid):
	paths = json.loads(request.raw_post_data)  # Pomona sends the list of paths to connect to in a JSON array.
	# note: paths is just a plain list of strings; you can loop through them and filter for security.
	dudley.connect(uid, paths)  # Dudley takes a client id and a list of paths. That's it.
 
def disconnect(request, uid):
	paths = json.loads(request.raw_post_data)
	# note: ditto regarding paths being a list of strings.
	dudley.connect(uid, paths)

Triggering Events

Django has nice post-save and post-delete events we can use to trigger events (“updates” in Roots-speak).

Here’s how we use them:

# Comet alerters
from django.db.models.signals import post_save, post_delete
def task_saved(sender, **kwargs):
	try:
		instance = kwargs["instance"]
		dudley.update("tasks", json.dumps(task_to_raw(instance)))
	except:
		pass
 
def task_destroyed(sender, **kwargs):
	try:
		instance = kwargs["instance"]
		data = task_to_raw(instance)
		data["DELETE"] = True
		dudley.update("tasks", json.dumps(data))
	except:
		pass
post_save.connect(task_saved, sender=Task)
post_delete.connect(task_destroyed, sender=Task)

Note how we once again use task_to_raw.

And now, the server-side is done!

Of course, start it with:

$ python manage.py runserver

Client Side

Getting and Setting Up Pomona

In your SproutCore terminal, navigate to your SproutCore project. Then, get Pomona and put it in the “frameworks” folder:

$ git clone git://github.com/ialexi/pomona.git frameworks/pomona

Next, just like you add the project in Django, you need to add the framework to your SproutCore project. To accomplish this, you edit the “config” line of “Buildfile” to look like this:

config :all, :required => [:sproutcore, :pomona]

You changed it from the solitary :sproutcore, to the array [:sproutcore, :pomona].

Next, you need to proxy Roots’ long-polling server. Just add this line at the end of Buildfile after the other two proxy lines:

proxy "/comet/", :to => "localhost:8008", :url=>"/"

Subscribing to Events

There are two things that have to happen to subscribe to events: create and configure an instance of the long-poller, and call connect().

The easiest place to do this is in an init function in our data source. Just place this code in the DataSource definition (preferably at the top).

  init: function(){
    // DataSource has no init right now, but what if it adds one?
    // so, call super function first.
    sc_super(); 
 
    // needs proxy /comet/ to dobby
    this.firenze = Pomona.Firenze.create({    // Firenze is the name of the long-poll system.
      connectUrl: "/tasks/connect/%@",        // %@ is replaced with the client id
      disconnectUrl: "/tasks/disconnect/%@"
    });
 
    // tell it to call this.taskReceived when a message is received on path "tasks"
    this.firenze.connect("tasks", this, "taskReceived");
  },

The connectUrl/disconnectUrl parameters tell Pomona what URLs to send connect and disconnect requests to. All it does is send a JSON array of path (event) names in a JSON array). It will automatically re-send these messages if the connection with the long-polling server is interrupted and a new connection (with a new id) is created.

All that’s left now is to receive messages!

Receiving Messages

Receiving messages is simple: we just implement the “taskReceived” function we told it about earlier.

  taskReceived: function(path, message) {
    if (message.trim() === "") return;  // the first time, we receive "" to confirm connection
    var data = JSON.parse(message);   // we sent a JSON-encoded message from Python
 
    if (data.DELETE) {
      // We set DELETE if the record needed deleting
      Todos.store.pushDestroy(Todos.Task, data.guid);
    } else {
      // otherwise, we should just re-load that record.
      Todos.store.loadRecords(Todos.Task, [data]);
    } 
  },

That’s All!

Start sc-server, open two browsers, both pointing at http://localhost:4020/todos, and hopefully, you’ll have Comet.

There may be some bumps—if anyone is willing to test and offer feedback, please do so!

Good luck!

SproutCore Animation Mixin Timing Curves (and BUG FIXES!!!)

Thursday, December 10th, 2009

First, the comparatively boring but very good news: several bugs which made CSS transitions not always work too well have been fixed! There were a lot of little errors—including a really odd one in Safari that caused transitions to be ignored if they were initiated too soon after a style.display change—that were causing some rather annoying issues, and they have now been resolved.

Now, the comparatively exciting: The SproutCore Animation mixin now supports timing curves!

There are two ways to set the timing curve to use: globally and locally. The global timing curve (null by default) is used if there is no local timing curve—in essence, the global timing curve is the default timing curve (in fact, it is named defaultTimingFunction).

aView: SC.LabelView.design(Animate.Animatable, {
    transitions: {
        left: .25,
        top: {duration: .25},
        width: { duration: .25, timing: Animate.TIMING_EASE_IN_OUT }, // with timing curve
        height: { duration: .5, timing: [0, 0, 0.58, 1.0] } // with custom timing curve
    }
})

Timing curves are cubic beziér curves, as used in CSS Transitions. They can be used as CSS-only, or, if you are willing to take a bit of a JavaScript performance hit, in both CSS and JavaScript. In fact, the JavaScript variety’s code is based on WebKit’s actual code for handling cubic beziér curves.

The pre-defined timing curves are identical to the CSS timing functions:

// CSS-only
TRANSITION_NONE: "linear",
TRANSITION_CSS_EASE: "ease",
TRANSITION_CSS_EASE_IN: "ease-in",
TRANSITION_CSS_EASE_OUT: "ease-out",
TRANSITION_CSS_EASE_OUT: "ease-in-out",
 
// JavaScript-enabled
TRANSITION_EASE: [0.25, 0.1, 0.25, 1.0],
TRANSITION_LINEAR: [0.0, 0.0, 1.0, 1.0],
TRANSITION_EASE_IN: [0.42, 0.0, 1.0, 1.0],
TRANSITION_EASE_OUT: [0, 0, 0.58, 1.0],
TRANSITION_EASE_IN_OUT: [0.42, 0, 0.58, 1.0]

The implementation is somewhat simple; if the timing function is a string, it is set directly into the transition-timing-function CSS property—and JavaScript animations ignore it (and thus work linearly). Otherwise, a cubic-bezier() is inserted into the CSS, and the JavaScript implementation is set up.

The calculations for the cubic beziér curves involve loops, and must be calculated every frame—they have a significant potential to slow things down (it may be wise to have a pre-calculated option as well at some point to help alleviate this). As such, you may not want to use the JavaScript variety unless you are animating relatively few elements.

I don’t know if anyone is using the Animate mixin (aside from me) but if anyone is, I hope they enjoy this new feature!

Type Trees

Saturday, March 28th, 2009

The Script Engine in the Create Framework was built so that existing class hierarchies could be wrapped very easily. There is a somewhat deep class hierarchy in the framework, and as such, something that handled inheritance easily and transparently was required.

In many cases, there were arrays of base classes. In C++, handling these is relatively simple: you either treat them as base classes, or you dynamic_cast, etc. The same thing applied with functions accepting pointers to base classes as arguments: you have to either treat it as a base class, or test by converting. I considered using this approach; it would be quite easy to implement, and would be very high-performance. In fact, there are a couple of objects which are still wrapped for scripting using this approach, with isWhatever and whateverValue members.

I eventually decided against using this principle, however. The reason is simple: I am wrapping C++ objects for JavaScript. To C++, I want everything to look and act like C++. To JavaScript, however, I don’t want things to act like C++ — I want them to act like JavaScript.

(more…)

Welcome!

Wednesday, January 28th, 2009

It is, I believe, customary to have a welcome post. Please consider this humble little post to be your welcome. If you find it unsatisfactory, just pretend something satisfactory is here. Or look at the first real post.

Thank you.