![]() | You are viewing Log in Create a LiveJournal Account Learn more | Explore LJ: Life Entertainment Music Culture News & Politics Technology |
| Sleepless in the Saddle 20 most recent entries |
The one time I wanted anonymous function syntax, so I could do pattern matching against the enclosing function:
find_option(Option, Default, Options) ->
case lists:foldl(fun ({Option, Value}, Acc) -> [Value | Acc];
(_, Acc) -> Acc
end, [], Options) of
[] -> Default;
[Val] -> Val
end.
In other words, I want the Option in the lambda for lists:fold/3 to match the Option passed into find_option/3. Woulda been nice, huh? Unfortunately: ./mod_jnctn.erl:30: Warning: variable 'Option' is unused ./mod_jnctn.erl:30: Warning: variable 'Option' shadowed in 'fun'post a comment
Fuck. post a comment
A is for Atom
There are plenty of libraries out there for attaching scripted behavior to HTML elements. Unfortunately, we're using Prototype, which lacks support for this. While we could integrate one of these libraries, in theory, I believe it's not worth the side-effects: possible destabilization due to conflicts, learning a new library, and additional download time for our clients. Instead, I wrote one up myself. It doesn't have to do very much: scan the DOM when the page loads and monitor the DOM for dynamic updates. I've chosen to do strict class-based behavior. To enable behavior on an element, you must do three things:
Well, that's the theory anyway. On to the code. First, let's create a module to contain all this code, along with the public API methods:
var DOMWatcher = function () {
return {
EventHandlers: {},
scanDocument: function () {
attachBehavior();
},
addWatcher: function (klass, watcher) {
DOMWatcher.EventHandlers[klass] = watcher;
if (document.body) {
attachBehavior();
}
},
removeWatcher: function (klass, watcher) {
delete DOMWatcher.EventHandlers[klass];
}
};
}();
Simple enough. A method to scan the document and attach behavior, and a couple of accessors to add and remove behavior after the document has been loaded.
To define the element behaviors themselves, I've gone with a simple
map of CSS classes to behavior objects, which is stored
in Here's a simple example:
var AlertLink = {
setup: function () {
this._alert = 'Hello World!';
},
onclick: function (event) {
alert(this._alert);
}
};
DOMWatcher.EventHandlers.alert_link = AlertLink;
During execution of behavior methods, I want the
So now that we know what we want the code to look like, lets have a
go at the
var DOMWatcher = function () {
var ATTRIBUTE_BOUND = '_DOMWatcher_bound';
function attachBehavior(target) {
var elements, elt, klass, i, length, handler, method;
target = target || document.body;
elements = target.getElementsByTagName('*');
for (i = 0, length = elements.length; i < length; i++) {
elt = $(elements[i]);
for (klass in DOMWatcher.EventHandlers) {
if (DOMWatcher.EventHandlers.hasOwnProperty(klass) &&
!elt[ATTRIBUTE_BOUND] && elt.hasClassName(klass)) {
elt[ATTRIBUTE_BOUND] = true;
handler = DOMWatcher.EventHandlers[klass];
if (handler.setup) {
handler.setup.call(elt);
}
for (method in handler) {
if (method.substring(0, 2) == 'on') {
Event.observe(elt, method.substring(2, method.length),
handler[method].bindAsEventListener(elt));
}
}
}
}
}
}
...
}();
Let's break that down: first we grab the node from which to begin scanning, defaulting to document.body, if it wasn't passed in, and grab all its children:
target = target || document.body;
elements = target.getElementsByTagName('*');
Once we have all the child elements, we can iterate over them, looking for classes with behavior defined:
for (i = 0, length = elements.length; i < length; i++) {
elt = $(elements[i]);
for (klass in DOMWatcher.EventHandlers) {
if (DOMWatcher.EventHandlers.hasOwnProperty(klass) &&
!elt[ATTRIBUTE_BOUND] && elt.hasClassName(klass)) {
elt[ATTRIBUTE_BOUND] = true;
...
}
}
}
Note that we're using elt[ATTRIBUTE_BOUND] in order to store whether
or not we've already attached behavior to this element, as an
optimization to prevent reattaching behavior.
With the element stored in our temporary
...
handler = DOMWatcher.EventHandlers[klass];
if (handler.setup) {
handler.setup.call(elt);
}
for (method in handler) {
if (method.substring(0, 2) == 'on') {
Event.observe(elt, method.substring(2, method.length),
handler[method].bindAsEventListener(elt));
}
}
...
Now let's set up the scan to happen when the document is finished loading, so our behavior will get attached when the page is ready: Event.observe(window, 'load', DOMWatcher.scanDocument); And we're almost done. We also need to handle the case of Ajax updates to the DOM. Unfortunately, there's no good cross-browser way to do this, as only a few support DOMNodeInserted. Notably, IE does not, and has no equivalent that we could use in its stead. Also, Prototype has no facilities to support this, and in fact makes it quite painful to try and do it cleanly.
Luckily, JavaScript is incredibly dynamic, and has no real security
model, so what we can do instead of events is scan the document when
certain Prototype functions are called. Since we use Prototype
exclusively, this is only a matter of figuring out which calls
update the DOM, and wrapping them to add a call
to
(function () {
var oldReplace = Element.Methods.replace;
var oldUpdate = Element.Methods.update;
var oldInsertion = Abstract.Insertion.prototype.initialize;
Element.Methods.replace = function (element, html) {
oldReplace(element, html);
DOMWatcher.scanDocument();
};
Element.replace = Element.Methods.replace;
Element.Methods.update = function (element, html) {
oldUpdate(element, html);
DOMWatcher.scanDocument();
};
Element.update = Element.Methods.update;
Abstract.Insertion.prototype.initialize = function (element, content) {
oldInsertion.call(this, element, content);
DOMWatcher.scanDocument();
};
})();
It's worth noting that I used three temporary variables, because IE had issues when I tried to use a single variable inside an iterative function. Oh well. People would probably find this version easier to read anyway. And that's all there is to it. Really. With this foundation in place, we now have the ability to add behaviors to elements fairly cleanly, which encourages a nice separation of code from HTML and from other code by use of the module pattern. Coming up: using DOMWatcher to automatically enable and disable links and forms. post a comment
I thought I'd start trying to develop a decent MVC/GUI framework for JS, since none exist at the moment. I was originally thinking of porting Rails, but Rails itself is fatally flawed as a generic MVC framework. On the other hand, Cocoa is not.
<div class="jib" jib="/javascript/currency_converter.js">
<form>
<label for="nativeCurrency">Native</label>
<input id="nativeCurrency"/>
<br/>
<label for="exchangeRate">Exchange Rate</label>
<input id="exchangeRate"/>
<br/>
<label for="foreignCurrency">Foreign</label>
<span id="foreignCurrency" class="value"></span>
<br/>
<ul class="controls">
<li><a id="cancelButton">cancel</a></li>
<li><a id="convertButton">convert</a></li>
</ul>
</form>
</div>
Next, create the JIB file to load (from the jib attribute of the above div). The JIB file consists of the JIB itself, plus any implementations it might need. So here's the controller code:
var CurrencyController = function () {};
CurrencyController.prototype = Object.extend(new AppKit.Controller(), {
cancel: function (sender, event) {
alert('This is kind of meaningless.');
},
convert: function (sender, event) {
if (this.nativeCurrency.value && this.exchangeRate.value) {
var foreignAmt = this.nativeCurrency.value * this.exchangeRate.value;
this.foreignCurrency.innerHTML = foreignAmt.toFixed(2);
}
}
});
and here's the JIB definition itself. The idea is that this can be auto-generated from tools at some point in the future, but for now we can do it by hand fairly easily:
var currencyConverterJib = function () {
var controller = new CurrencyController();
controller.setBindings({
nativeCurrency: AppKit.Jib.Outlet('nativeCurrency'),
exchangeRate: AppKit.Jib.Outlet('exchangeRate'),
foreignCurrency: AppKit.Jib.Outlet('foreignCurrency'),
});
AppKit.Jib.Connection(AppKit.Jib.Outlet('nativeCurrency'), 'nextResponder',
AppKit.Jib.Outlet('exchangeRate'));
AppKit.Jib.Connection(AppKit.Jib.Outlet('nativeCurrency'), 'onblur',
AppKit.Jib.Action(controller, 'convert'));
AppKit.Jib.Connection(AppKit.Jib.Outlet('exchangeRate'), 'nextResponder',
AppKit.Jib.Outlet('nativeCurrency'));
AppKit.Jib.Connection(AppKit.Jib.Outlet('exchangeRate'), 'onblur',
AppKit.Jib.Action(controller, 'convert'));
AppKit.Jib.Connection(AppKit.Jib.Outlet('convertButton'), 'onclick',
AppKit.Jib.Action(controller, 'convert'));
AppKit.Jib.Connection(AppKit.Jib.Outlet('cancelButton'), 'onclick',
AppKit.Jib.Action(controller, 'cancel'));
};
The AppKit.Jib.{Outlet, Action, Connection} methods are fairly straightforward, and only really used in this style for easier automation. One of the things I added was a "nextResponder", like the one in Cocoa. This allows me to specify how the tab key moves you around forms. To make this work, I process every element w/in a JIB's HTML view, and extend it via some built-in methods. One of which hooks up to 'onblur' and looks at the 'nextResponder' for an element. There's more to come, but I wanted to post this up today, since I got the CurrencyConverter working, and maybe some people will be inspired to start taking JS to where it should be. post a comment
As previously reported, a statement from Al Sharpton’s National Action Network said the civil rights activist was “appalled” to see the cartoon, and cited the drawing as “further proof that the hip hop community and those who market them must be held accountable for the destruction they are causing in the black community.” BLACK JOURNALISTS REACT TO EDITORIAL CARTOON: Editor issues apology after picture causes uproar. I don't have words yet for this. I may not ever. post a comment
The state originally estimated that 64,000 people—mostly state employees—had personal data stored on the laptop. However, not long after the theft, the state bumped that number to around 229,000, then 500,000, and it's now close to 1 million. The expanded number of people whose personal data was lost included hundreds of thousands of state taxpayers who had not yet cashed tax refund checks, hundreds of Ohio lottery winners who had not yet cashed winnings checks, and thousands of others who either had not cashed various payments or whose direct deposits failed to go through. Ohio bumps data theft estimate up to 1 million (Ars Technica) Let me get this straight: 22-year-old female intern leaves laptop in car over weekend and loses data on at least one million people who haven't picked up their money from the government. 007'll be all over this case. Will it be Russians or the vaguely middle-eastern this time? Server: www.livejournal.com post a comment
Galaxy Zoo is the coolest thing to happen since the Fonz hooked up with Elvis in James Dean's Porche. post a comment
#rubyonrails has failed me. I can't say I'm surprised, since this is completely bizarre:
One of my jobs at DoubleClick was developing an application which calculated eCPM (effective Cost-Per-thousand-iMpressions) based on the CTR (click-through rate) of a CPC (Cost Per Click) ad. This isn't anywhere near as simple as it sounds, mainly because we were interested in the future eCPM rate, so we could bias ad delivery effectively.
With a slight re-wording, this would probably apply to most other fields, as well. I got this while listening to some professional musicians about what it means to be good. So without further ado, here are the three things you'll need to be a good programmer:
If you only have one, it's probably "Depth of Field." You've been using the same language and environment your entire life - you might not even know there's a difference between language and environment. The second one is more variable, but it'll probably be "Resource Efficiency". Most people tend to really over-do it at this stage, under the (wrong!) assumption that programs should be as efficient as possible. If you have any two of these three, you're an ok programmer. If you have all three, you're world-class. It takes a lot to have all three. Personally, I only have the first and the third. I switch languages and environments too much to have much depth of field in whatever I'm currently working on. Except for brief interludes with perl and c, anyway. On the other hand, it's hard to get elegance without breadth of field. Knowing lisp doesn't just make you a better lisp programmer - it makes you a better programmer. C people just don't recurse as often as they should, and their code is a mess as a result. Recursion is more elegant than iteration most of the time because it cleanly separates leaf behavior from tree behavior. As a result, it has a tendency to force you to simplify your code to those terms. So anyway, I'm looking forward to really learning ruby and rails, at least to the point where I can stop reading the damn docs every few minutes. post a comment
I haven't felt comfortable posting here at the times when I'm most likey to be able to. I also find the web interface to be kind of slow and kludgey.
(add-to-list 'load-path "~/src/elisp/ljupdate")
(add-to-list 'load-path "~/src/elisp/http-emacs")
(mapcar (lambda (fn) (autoload fn "ljupdate" "Livejornal updater." t))
'(lj-login lj-logout lj-compose))
(mapcar (lambda (fn) (autoload fn "http-post" "Load HTTP transfer." t))
'(http-post http-get))
Coming up: 37 signals of the end of time. post a comment
It's about the objects, stupid!
Well, not really. This is really just a problem with twitteriffic, but this is a problem so huge I need to blame a conspiracy for it.
Sucked. And I'm talking about the DS version. I'm sure the Wii version is even more abysmal.
I know it's only been a few years, but that's part of what makes the question so interesting. |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||