Section 14.3. Base Functions


14.3. Base Functions

The MochiKit.Base module provides a collection of functions to make basic programming tasks in JavaScript a bit more pleasant. Base helps out with comparison and representation of JavaScript objects, conversion to JavaScript Object Notation (JSON) format, array operations, and other useful tasks. If you use any other part of MochiKit, odds are that you'll need to use Base, too, because MochiKit relies heavily on the functions in MochiKit.Base.

MochiKit's interactive interpreter is really handy for trying out basic JavaScript language features. You'll probably want to fire it up for this section.

14.3.1. The Comparison Problem

JavaScript provides a typical set of comparison operators: ==, !=, <, >, <=, =>. Starting by looking at just equality, JavaScript has two different ways to test equality: the standard == and the less-standard ===. == will try to change the type of the objects being compared in an effort to see whether they match. For example, "1" == 1 is true in JavaScript. The === is stricter: It doesn't do any kind of type conversion. So, "1" === 1 is false.

JavaScript will also do type conversions for expressions such as "1" < 2. You might be thinking that JavaScript's comparison operators seem straightforward to use. And you'd be correct if you're only comparing some combination of numbers or strings. As soon as you get beyond that, JavaScript has no built-in facility to help you.

For example, the following expression looks true:

  ["burger", "fries"] == ["burger", "fries"]


But JavaScript will tell you that's false. MochiKit's compare function knows the truth, however:

  compare(["burger", "fries"], ["burger", "fries"])


returns 0, which means that they're equal. compare gives you a negative number if the object on the left is less than the object on the right, and a positive number if the left is the greater one.

You can use compare for all of your comparisons:

  compare("1", 1)


returns true, just as "1" == 1 does.

14.3.2. More-Complicated Comparisons

The preceding examples show how JavaScript's operators handle comparisons of simple values and MochiKit's compare function handles simple values and arrays. What if you have something more complicated? Suppose, for example, that you have an object that represents a food order. Our Order objects will have a hash of items with the quantity for each item:

  Order = function(items) {       this.items = items;   }


If you're entering this in MochiKit's interactive interpreter, be sure to end each line with // so that it knows that you've got more to type, or use the "Multiline input" box that is available in the "mochiexamples" version of the interpreter.

Ideally, you could do this

  order1 = new Order(["burger", "fries"]);   order2 = new Order(["burger", "fries"]);


and be able to get true for order1 == order2. Sadly, it doesn't work that way. JavaScript sees that you've got two objects and just says, "Nope, not the same object," and returns false. Does MochiKit's magic compare function take care of it for us?

  compare(order1, order2);


Nope. You get an exception stating that the objects cannot be compared. That's not the whole story, however. MochiKit can't compare them now, because we haven't told MochiKit how to compare them. We can tell MochiKit how to compare two orders by using the registerComparator function. As you saw in the preceding section, the compare function can handle two arrays just fine. A function that can compare two Orders to see whether they're the same is simple:

  compareOrders = function(a, b) {       return compare(a.items, b.items);   }


You can test this by running compareOrders(order1, order2) and see that it returns 0. Just having a function that can compare your objects is not quite enough for register-Comparator, however. registerComparator needs a way to know that compareOrders is the function is has to call to compare the objects that it has been handed. Let's make a function that will answer that question:

  isOrder = function(obj) {       if (obj.items) {           return true;       }       return false;   }


With that function in hand, we can register a comparator:

  registerComparator("Order", isOrder, compareOrders);


The first parameter is a name that we're giving to this comparator. After registering this comparator, our earlier desire to run

  compare(order1, order2);


now works just as we'd expect and returns 0.

Other comparison functions available in MochiKit.Base are as follows:

  • objEqual(a, b) Returns true if compare(a, b) == 0.

  • objMax(obj[,...]) Use compare to find the maximum out of the list of arguments.

  • objMin(obj[,...]) Use compare to find the minimum out of the list of arguments.

14.3.3. A Word about Objects in JavaScript

You might have noticed that our isOrder function just checks that the object has items. What if there is some other object that has "items" on it? For the purposes of this script (and many JavaScripts on web pages), this is not a big concern. You don't have so many different objects floating around that you're likely to accidentally compare objects other than Orders that happen to have items. However, if you do want to play it safer, it's important to know a bit about objects in JavaScript.

Python, like many object-oriented languages, has the notion of a class. A class represents a particular kind of object, and an instance is one example of that type of object. When you've got an instance in Python, you know what kind of thing it is. You just ask for its __class__, and Python will tell you what it is. You can also call isinstance to do a check to see whether a particular object is an instance of a given class. Unlike checking __class__, isinstance looks up the inheritance hierarchy to see whether the class you're looking for is anywhere up there.

In the example in the previous section, the Order object is declared as a function. No class is involved because JavaScript doesn't have classes. What it does have is a concept called a "prototype," which serves a similar purpose and can even be used to implement something more like the classes you're used to from Python.

Fundamentally, all objects in JavaScript "look the same" and are basically just hash tables where a string name points to a value that is an object or one of the basic types. Every object has a special "prototype" value set on it that you can access like any other attribute by looking at object.prototype. When you attempt to look up an attribute on an object, the object is checked first. If that name isn't found, the prototype is checked. And then the prototype's prototype is checked, and so on.

None of those objects have the special role of being a "class." They're all just objects. Any object you can get ahold of can be used as a prototype for inheritance purposes.

It turns out that even though there are no classes in JavaScript, you can still do the equivalent of an isinstance check. JavaScript's instanceof operator enables you to check whether two objects have the same prototype. When you use the code order instanceof Order, the JavaScript interpreter will compare the prototypes of your order instance against the Order function and see that they're the same and return a true value. instanceof will also search prototypes of prototypes, in the same way isinstance does in Python.

In Python, it's generally considered bad style to overuse isinstance. The preference goes to "duck typing": If it walks like a duck and quacks like a duck, it must be a duck. In other words, if the methods or attributes you're looking for are on the object, just go ahead and use them. The same is true of JavaScript. Type checks are more consistent with the thinking of statically typed languages such as Java. If you're working in Python or JavaScript, duck typing is a better way to go.

Note

Douglas Crockford has written some excellent articles about the virtues of JavaScript's object model that are worth a read if you're interested in the topic: www.crockford.com/javascript/.


14.3.4. JavaScript and Python: Not So Different After All

There are some syntax differences and some object model differences, but the truth is that JavaScript and Python are more similar than they are different. These similarities are just increasing, and it's not a coincidence.

Brendan Eich, the creator of JavaScript, has written [1] about how JavaScript needs to solve some of the same problems Python does. Instead of treading over the same ground, he has chosen to take his inspiration directly from Python.

[1] "Python and JavaScript" from Brendan Eich's Roadmap Updates: http://weblogs.mozillazine.org/roadmap/archives/2006/02/js_and_python_news.html

JavaScript 1.7 is evidence of this.[2] New features include generators, iterators, and array comprehensions that look exactly like their Python counterparts.

[2] New in JavaScript 1.7, from the Firefox 2 documentation: http://developer.mozilla.org/en/docs/New_in_JavaScript_1.7

MochiKit makes it easier for Python programmers to cross the bridge into JavaScript programming, and JavaScript itself is evolving to make the transition even smoother!

14.3.5. Object Representation

Many object-oriented programming languages provide a facility to create a string representation of an object. In Java and JavaScript, for example, you can define a toString method to generate the string representation of an object. In Python, there are two different string methods: __zstr__ and __repr__. __str__ provides a string representation of the object that is suitable for users. _repr_ provides a representation for the programmers. The string returned by __repr__ ideally can be cut and pasted directly into the Python interpreter.

repr can prove very useful, especially when debugging. For this reason, MochiKit brings the repr function to JavaScript. Here's an example:

  >>> animals = ["dog", "cat", "chinchilla"];   ["dog", "cat", "chinchilla"]   >>> animals.toString()   "dog,cat,chinchilla"   >>> repr(animals)   "[\"dog\", \"cat\", \"chinchilla\"]"


The basic JavaScript toString for an array does a reasonable job in presenting the list of items. The repr for the array is useful, however, because you can paste it directly into a JavaScript program.

Previously, we created new Order objects to represent a food order. You may have noticed when creating an Order, the representation displayed was not amazingly useful:

  >>> order1 = new Order(["Burger", "Fries"]);   [object Object]


That's because MochiKit doesn't know how to represent our custom objects. Using the registerRepr function, which works much like the registerComparator function, we can create a more useful representation:

  orderRepr = function(order) {       return "new Order(" + repr(order.items) + ")";   }   registerRepr("Order", isOrder, orderRepr);


As in registerComparator, we need to tell registerRepr how to identify that it has an Order object. Lucky for us, we can use the exact same isOrder function that we had defined previously. Now, if you type order1 in the interactive shell, you'll see this:

  >>> order1   new Order(["Burger", "Fries"])


And that's a lot easier to look at than [object Object].

Creating a decent repr for your objects is a common enough task that MochiKit also provides a nicer way to do it. registerRepr is great when you're working with an object that is outside of your control. For your own objects, however, it is a lot nicer to just add a method to the object itself. MochiKit lets you do just that. If you define a repr or __repr__ method on your object, that method will be called for the representation.

Let's try it out that way. The first thing we need to do is remove the repr implementation that we just added:

  >>> reprRegistry.unregister("Order");   true   >>> order1   [object Object]


Now, create a prototype that uses the repr function we created previously:

  Order.prototype.repr = function() {       return "new Order(" + repr(this.items) + ")";   }


Let's see if that works:

  >>> order1   new Order(["burger", "fries"])


Indeed it does!

14.3.6. JSON Serialization

The JavaScript Object Notation (JSON) format was discussed earlier in Chapter 9, "Ajax and WhatWhat Status Projects." TurboGears includes simplejson in Python to do conversions to and from JSON on the server. MochiKit enables you to do similar conversions on the browser side.

Converting from JSON to JavaScript is easy because JSON is, by definition, valid JavaScript. MochiKit includes an evalJSON function that simply wraps the JSON in parenthesis and runs eval on it.

MochiKit also includes a companion serializeJSON function to take JavaScript values and turn them into valid JSON. Many values work just as you'd expect:

  >>> serializeJSON(1)   "1"   >>> serializeJSON("Hello")   "\"Hello\""   >>> serializeJSON([1,2,3])   "[1, 2, 3]"


How about objects, such as our Order example?

  >>> serializeJSON(order2);   "{\"items\":[\"Cheesy Beefwich\", \"Fizzy Beverage\"]}"


For primitive types (undefined, Boolean, string, number, null), serializeJSON is quite straightforward. For anything else, it gets trickier. But, when you think about it, JSON is just like repr but with a strict definition of what the representation needs to look like. serializeJSON, therefore, works a lot like repr. Here are the exact rules it follows:

  1. Primitives are converted directly into their JSON representation.

  2. If the object has a __json__ or json method, that is called to get the JSON representation. The result of the call to your json method is run through the JSON processing again. So, you can return an array of strings and be assured that the proper JSON will come out at the end.

  3. Anything with a length property that is a number is assumed to be an array, and that is how it will be serialized.

  4. You can register a JSON "simplifier" with the registerJSON function, which works just like the registration functions from the previous sections.

  5. Failing all of that, MochiKit will take the object's properties and serialize them (using these same rules) as name:value pairs wherever it can. Some things, such as methods, won't be serialized because they can't be handled by any of these rules.

When we called serializeJSON on our order2 object, the object failed all of the tests until it got to the fifth rule. So, we ended up with a name:value pair. This serialization is not bad, but maybe what we really want is for the order to just be serialized as a list of items. Based on the preceding rules, we can get that easily:

  >>> Order.prototype.json = function() { return this.items; }   function()  { return this.items; }   >>> serializeJSON(order2)   "[\"Cheesy Beefwich\", \"Fizzy Beverage\"]"


We can also try it by registering a simplifier:

  >>> delete Order.prototype.json   true   >>> orderItems = function(order) { return order.items; }   function (order) { return order.items; }   >>> registerJSON("Order", isOrder, orderItems);   >>> serializeJSON(order2)   "[\"Cheesy Beefwich\", \"Fizzy Beverage\"]"


MochiKit's JSON support makes it quite easy to ensure that your objects look the way they need to look for transporting to other systems. After the object has been converted to JSON, you can easily convert it back to a JavaScript object using evalJSON().

14.3.7. Working with Arrays

Python has a number of handy functions for working with lists that JavaScript doesn't. MochiKit helps out with functions that work on "array-like" objects. The isArrayLike function returns true if the object has a typeof "object" and the object has a length property.

In Python, the map and filter functions have largely been replaced by list comprehensions, and there is talk of deprecating those functions at some point years in the future. JavaScript doesn't offer list comprehensions yet, however. The map and filter functions can be amazingly useful and provide concise ways to do common operations with arrays. Here's an example of these in use:

  >>> mylist = [1,2,3,4,5,6,7,8,9]   [1, 2, 3, 4, 5, 6, 7, 8, 9]   >>> filter(function(a) { return a % 2 == 0 }, mylist)   [2, 4, 6, 8]   >>> map(function(a) { return a*2 }, mylist)   [2, 4, 6, 8, 10, 12, 14, 16, 18]


map and filter both take a function as the first argument and an array as the second argument. map creates a new list by running the function on each element of the array in turn. filter creates a new list containing only the elements for which the function returns true.

Base also includes xmap and xfilter functions that use the extra arguments as the "array" instead of passing in an array explicitly. Here's the same odd/even example from above rewritten with xfilter:

  >>> xfilter(function(a) { return a % 2 == 0 }, 1, 2, 3, 4, 5, 6, 7, 8, 9)   [2, 4, 6, 8]


Using these functions that take a function as the first argument, it is sometimes handy to be able to use JavaScript operators as if they were functions. Base includes a table of JavaScript's operators called operator. The functions contained in operator are found in Table 14.1:

Table 14.1. JavaScript Operators

Unary Logic Operators

Function Name

Implementation

Description

truth(a)

!!a

Logical truth

lognot(a)

!a

Logical not

identity(a)

a

Logical identity

Unary Math Operators

not(a)

~a

Bitwise not

neg(a)

-a

Negation

Binary Operators

add(a, b)

a + b

Addition

sub(a, b)

a - b

Subtraction

div(a, b)

a / b

Division

mod(a, b)

a % b

Modulus

mul(a, b)

a * b

Multiplication

and(a, b)

a & b

Bitwise and

or(a, b)

a | b

Bitwise or

xor(a, b)

a ^ b

Bitwise exclusive or

lshift(a, b)

a << b

Bitwise left shift

rshift(a, b)

a >> b

Bitwise signed right shift

zrshfit(a, b)

a >>> b

Bitwise unsigned right shift

Built-In Comparators

eq(a, b)

a == b

Equals

ne(a, b)

a != b

Not equal

gt(a, b)

a > b

Greater than

ge(a, b)

a >= b

Greater than or equal to

lt(a, b)

a < b

Less than

le(a, b)

a <= b

Less than or equal to

Extended Comparators (Uses compare)

ceq(a, b)

compare(a, b) == 0

Equals

cne(a, b)

compare(a, b) != 0

Not equal

cgt(a, b)

compare(a, b) == 1

Greater than

cge(a, b)

compare(a, b) != -1

Greater than or equal to

clt(a, b)

compare(a, b) == -1

Less than

cle(a, b)

compare(a, b) != 1

Less than or equal to

Binary Logical Operators

logand(a, b)

a && b

Logical and

logor(a, b)

a || b

Logical or

contains(a, b)

b in a

Has property (note order)


The following functions are also included:

  • arrayEqual(a1, a2) Compares two arrays to see whether they're equal

  • concat(a1, a2, ...) Concatenates all of the array-like objects into a new array

  • extend(self, obj, skip=0) Adds elements from obj to self

  • findValue(lst, value, start=0, end=lst.length) Returns the index for the value in the list, using compare()

  • findIdentical(lst, value, start=0, end=lst.length) Returns the index for the value in the list, using ===

  • flattenArguments(a1, a2, …) Extends array-like arguments in place and returns a single, flat array

  • flattenArray(lst) Returns a new single-level array with all the elements of the arrays contained within lst

  • listMax(lst) Returns the largest element of the list

  • listMin(lst) Returns the smallest element of the list

  • listMinMax(which, lst) which==1 is equivalent to listMax, which==-1 is equivalent to listMin

14.3.8. Pythonic Version of this

In both Python and JavaScript, it's easy to pass around functions for use in other contexts. In Python, it's also easy to pass around a method that is bound to a specific object. Consider this example:

  >>> class Pizza(object):   ...    def _init_(self, toppings):   ...        self.toppings = toppings   ...   ...    def eat(self):   ...        print "You just ate a pizza with %s" % self.toppings   ...   >>> def get_pepperoni_eater():   ...    pizza = Pizza("pepperoni")   ...    return pizza.eat   ...   >>> func = get_pepperoni_eater()   >>> func()   You just ate a pizza with pepperoni


The get_pepperoni_eater function returns the eat method bound to the pizza object that was created.

We can do something similar in JavaScript, too, but you might notice that JavaScript is not quite like Python when it comes to methods. For example, try this in MochiKit's interactive interpreter:

  Pizza = function (toppings) { this.toppings = toppings; }   Pizza.prototype = { 'eat' : function() {                   writeln("You just ate a pizza with " + this.toppings);               } }   pizza = new Pizza("pepperoni");   pizza.eat();   [ You just ate a pizza with pepperoni ]   pizza2 = new Pizza("sausage");   pizza2.eat();   [ You just ate a pizza with sausage ]   func = pizza.eat;   func();   [ You just ate a pizza with undefined ]   func = pizza2.eat;   func();   [ You just ate a pizza with undefined ]


That last line should have said, "You just ate a pizza with sausage." That is, it should have said that if we were using Python. This is JavaScript, however. MochiKit provides functions to make this behave more like we're used to self behaving in Python:

  func = bind(pizza2.eat, pizza2);   func()   [ You just ate a pizza with sausage ]


The bind function takes a function and an object and returns a new function that ensures that this is referring to that object. In the preceding example, we're certain that this will point to pizza2.

Of course, calling bind on every method would be a pain, so MochiKit includes a bindMethods function. Let's make a one-line change to the preceding example to see that this is bound properly:

  Pizza = function (toppings) {       this.toppings = toppings;       bindMethods(this);   }   Pizza.prototype = { 'eat' : function() {                   writeln("You just ate a pizza with " + this.toppings);               } }   pizza = new Pizza("pepperoni");   pizza.eat();   [ You just ate a pizza with pepperoni ]   pizza2 = new Pizza("sausage");   pizza2.eat();   [ You just ate a pizza with sausage ]   func = pizza.eat;   func();   [ You just ate a pizza with pepperoni ]   func = pizza2.eat;   func();   [ You just ate a pizza with sausage ]


Base also includes a function called method that is equivalent to bind, but the arguments are reversed.

14.3.9. Help with Calling Functions

Base also includes functions to help out with calling other functions a certain way when the time comes. For example, it's not uncommon for a function that you call to need a callback. Sometimes, those callbacks are required to take a certain number of arguments (often zero). You can use partial to fill in any arguments at the time the function is called:

  partialmap = partial(xmap, function(a) { return a* 2 }, 0, 1, 2, 3);   partialmap();   [ [0, 2, 4, 6] ]   partialmap(4);   [ [0, 2, 4, 6, 8] ]


partial creates a new function that "bakes in" some arguments and then passes in any additional arguments that are passed in to the partial when it is called.

  • compose(f1, f2, …) Creates a new function that is equivalent to f1(f2(…))

  • forwardCall(name) Returns a function that makes a method call to this.name(…)

  • methodCaller(name) Returns a function that calls the method name on its argument

14.3.10. Dictionary-Like Objects

In JavaScript, all objects act like dictionary or mapping objects. This example highlights this:

  >>> foo = new Object();   [object Object]   >>> foo.bar = "Hi";   "Hi"   >>> foo["bar"]   "Hi"


There is an exception to this dictionary-like behavior. Python dictionaries can have any object as keys. JavaScript objects coerce the keys to strings, which would almost certainly lead to behavior other than what you're looking for.

The ability to treat any object like a dictionary is handy, but JavaScript objects don't have some of the methods we have on Python dictionaries. MochiKit.Base provides items(obj) and keys(obj) to fill in some of the gaps.

Other functions for providing dictionary-like behavior to JavaScript objects include:

  • itemgetter(name) Returns a function(obj) that returns obj[name]

  • merge(obj[,...]) Creates a new object with every property from the given objects

  • setdefault(self, obj) Mutates self (and returns it), setting all properties from obj that are not already set on self

  • update(self, obj) Updates all the properties on self to match those in obj

  • updatetree(self, obj) Like update but will also recursively update where there is an object value in both self and obj

14.3.11. Working with Query Strings

Something that comes up fairly often in working on web pages is creating query strings for URLs or parsing a query string that you have in hand. MochiKit.Base provides a pair of functions that help out with this.

parseQueryString(str, useArrays=false) takes an encoded string and returns an object with either the values as strings or the values as lists. A couple of examples will make this clear:

  >>> isp_info = parseQueryString("name=Hosty%20Most&city=South%20Barton")   [object Object]   >>> isp_info.name   "Hosty Most"   >>> isp_info.city   "South Barton"


In this example, you can see a URL encoded query string get converted into a convenient object. It's possible to also have multiple values for each parameter that is coming in, in which case you'll need arrays rather than individual values. Here's an example of this usage:

  >>> isps = parseQueryString("name=Hosty%20Most&name=SBISP&name=BartonOnline", true);   [object Object]   >>> isps.name   ["Hosty Most", "SBISP", "BartonOnline"]


The flip side of parsing an existing query string is generating one. You can use the queryString function to generate query strings in a few different ways. Here is the inverse of the previous example:

  >>> queryString(["name", "name", "name"], ["Hosty Most", "SBISP", "BartonOnline"]);   "name=Hosty%20Most&name=SBISP&name=BartonOnline"


In this form, queryString takes a list of parameter names and a matching list of parameter values. Note that queryString handles URL encoding for you.

If your page has a form on it, you can easily turn the form's values into a query string. You just pass the ID of the form DOM node as the only parameter to queryString.

Finally, you can pass an object (that is not a string or a DOM node) in to queryString:

  >>> queryString({"name" : "Hosty Most", "city": "South Barton"})   "name=Hosty%20Most&city=South%20Barton"


One more function to help you with your URLs is urlEncode(un-encoded), which converts the unencoded string to a properly URL-encoded string.

14.3.12. Functions in MochiKit.Base

MochiKit.Baseis also the home to other utility functions that don't more properly belong in a different module.

  • camelize(str) Converts a hyphenated string to camelCase

  • clone(obj) Returns a shallow clone of an object

  • counter(n=1) Returns a number that is one greater than the previous value, starting at n

  • isDateLike(obj[,...]) Returns true if all of the objects passed in have a .getTime method

  • isEmpty(obj[,...]) Returns true if obj.length==0 for all objects passed in

  • isNotEmpty(obj[,...]) Returns true if obj.length > 0 for all objects passed in

  • isNull(obj[,...]) Returns true if all arguments are null

  • isUndefinedOrNull(obj[,...]) Returns true if all arguments are undefined or null

  • keyComparator(key Returns a function that compares a[key] with b[key], which is useful for sorting (see also reverseKeyComparator)

  • nameFunctions(namespace) Adds a NAME property to all the methods of namespace, based on the NAME property in namespace itself

  • noop() Equivalent to function(){}, which is sometimes used to avoid memory leaks in IE

  • nodeWalk(node, visitor) Nonrecursive breadth-first node walking function (the visitor returns a list of nodes to walk next)

  • reverseKeyComparator(key) Returns a function that compares b[key] with a[key] (the reverse order of keyComparator)

  • typeMatcher(typ[,...]) Returns a function(obj[,...]) that returns true if each object matches one of the types (listed as strings) passed in to typeMatcher




Rapid Web Applications with TurboGears(c) Using Python to Create Ajax-Powered Sites
Rapid Web Applications with TurboGears: Using Python to Create Ajax-Powered Sites
ISBN: 0132433885
EAN: 2147483647
Year: 2006
Pages: 202

Similar book on Amazon

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net