Pastie TextMate integration at your finger tips
Grab the bundle and enjoy, Pastis is included so nothing else is needed.
- pastes selected text or entire document
- syntax automatically deduced
- opens instantly in your favorite browser
advised methods for Javascript with Prototype
an AOP implementation
Well, it's been a long time since including AOP is planned for Prototype (original ticket by Tobie Langel).
This is a working candidate with slight differences and improvements. Let's start with some code, as usual :)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
PeriodicalExecuter.addMethods(Advisable); PeriodicalExecuter.prototype.addAroundAdvice('execute', function(proceed) { // assuming the existence of some benchmark function var duration = benchmark(proceed); console.log('Periodical execution took '+duration+'s'); }); // For now every single instance of PeriodicalExecuter will log the execution duration. var greeter = Object.extend({ congrat: function() { return "Congratulations"; } }, Advisable); greeter.addAfterAdvice('congrat', function(returnValue) { return returnValue + " for Prototype 1.6, you guys made a great work." }); greeter.congrat(); // "Congratulations for Prototype 1.6, you guys made a great work." |
Differences and improvements
Among everything, Aspect is not a mixin anymore but a class that represents aspect of a given object and holds its originals methods. Advisable is the mixin containing AOP-methods that you can extend an instance with or mix into a class. What is the advantage ? well, your object won't be "polluted" with private attributes or methods anymore, but only with a single _aspect private attribute referring to the corresponding Aspect instance, created on demand.
Another feature that motived me in first place, is that you can now remove a given advice (well, only if you have a reference to it, like event handlers) and still have your method doing fine with other advices. The resulting method is computed only when advices are added or removed.
Plays nicely with prototypes and instances
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
var Example = Class.create(Advisable, { example: function() { console.log('in original method'); } }); var ex = new Example(); ex.addAroundAdvice('example', function(proceed) { console.log('around, before (instance advice)'); proceed(); console.log('around, after (instance advice)'); }); Example.prototype.addBeforeAdvice('example', function() { console.log('before (prototype advice)'); }); ex.example(); // Guess what's in console ? // around, before (instance advice) // before (prototype advice) // in original method // around, after (instance advice) |
Some syntaxic sugar
This prototype word is kind of annoying sometimes, and there are several cases when you just want to invoke a prototype method on directly on the class holding it.
A simple way to do it is to bind all method in Advisable to Example.prototype and then store it in our class Example.
1 2 3 4 5 6 7 8 9 |
Object.keys(Advisable).each(function(methodName) { Example[methodName] = Advisable[methodName].bind(Example.prototype); }); // now Example.addBeforeAdvice('example', advice); // is equivalent to Example.prototype.addBeforeAdvice('example', advice); // but maybe sexier :p |
This is a pattern we are using in Prototype UI in our class.js file containing extensions to Class.Methods
Download
You can check out from the svn repository to get source and unit tests.
svn co http://svn.gotfresh.info/projects/advisable/
un pastis oui, un pastiche non !
Pastis is a simple but complete Ruby interface to Pastie. It lets you create, search or list pastes. A standalone script helps integration with the shell.
gem install pastis
Use cases
From shell
- open a new file in $EDITOR and pastes it on exit
pastis -
edit something.js and paste it (ruby is the default language)
pastis -e something.js -l javascript - make a private paste with STDIN
cat something.rb | pastis -p
From Ruby
1 2 3 4 5 6 7 8 |
paste = Pastis.paste a_string, :language => :text, :private => true puts paste.url, paste.body results = Pastis.search('mixin') puts results.first.body puts results.next_page.first.preview unless results.last_page? |
From TextMate
Open the bundle editor and add this command in Source bundle (Input: Selected Text or Document, Output: discard). It will paste selected text or entire document with correct syntax, and open it in a browser:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#!/usr/bin/env ruby require 'rubygems' require 'pastis' input = ENV['TM_SELECTED_TEXT'] || File.read(ENV['TM_FILEPATH']) languages = { /source\.ruby/ => :ruby, /text\.html\.basic/ => :html, /text\.html\.ruby/ => :rhtml, /source\.js/ => :javascript, /source\.(c|c\+\+)/ => :c, /source\.sql/ => :sql, /source\.diff/ => :diff } language = languages[languages.keys.find { |pattern| ENV['TM_SCOPE'] =~ pattern }] || :plaintext paste = Pastis.paste(input, :language => language) `open #{paste.url}` |
For further information, read the documentation.
Have fun !
Prototype and Options
Tired of seeing Object.extend and this.options everywhere in your Prototype code ? Try this Options mixin !
example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
var Widget = Class.create({ defaultOptions: { className: 'widget', theme: 'BeOS' }, initialize: function(element, options) { this.setOptions(options); ... } }); // For 1.6 Class.mixin(Widget, Options); // For 1.5 Object.extend(Widget.prototype, Options); |
some fun with ruby lambdas
introducing SourceProc
In Ruby, some objects cannot be dumped with Marshal, including procedure or methods, bindings, continuations, IO instances or singleton objects.
I personnaly needed to serialize some procedures once, so I decided to create a class of procedures that know their source code and can be marshalled.
Here is SourceProc, read the documentation for more information.
YAGNI, just for fun ;)
a simple example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
add = slambda '|x, y| x + y' add.call(10, Math::PI) # => 13.1415926535898 add.source # => '|x, y| x + y' add.to_s # => 'slambda("|x, y| x + y")' Marshal.load(Marshal.dump(add)).call(2, 3) # => 5 |
inheritance and prototype.js
I'm reviewing the content of this article to reflect the new implementation I made (slight differences, more dynamic) for Prototype Window Class we are curently rewriting with Sébastien Gruhier. Stay tuned !
Good news !
It seems that inheritance finally went in Prototype trunk.As I said in my last article, I was myself working on some implementation of inheritance that could be integrated to Prototype so I was really happy to find a lot of people discussing about inheritance on the Prototype core mailing list.
I love the AspectOrientedProgramming-like way of calling the parent method that is, if a method want to call its parent one (the one defined in the superclass' prototype), it simply has to receive it as first argument called $super.
So I decided to mix all good ideas of each implementation. Here is the listing of some propositions and the source code (you can download a patch for the revision 7299). Let's give your opinion/critics !
Propositions :
Base class
There is one base class called Base, every class will be a subclass of Base.
It defines a method extend in its prototype.
This method simply extend the current object with a given object, allowing to define methods which can call their parent if they include $super in their signature.
Okay so basically, object.extend(properties) is the
Object.extend(object, properties) on steroïds.
Classes
Classes have a property constructor set to Class
Classes have methods coming from Class.prototype but are not dynamically linked to this prototype. These are just the usefull methods we want all classes to understand.
Classes methods are not shared with subclasses.
Class methods are :
Klass.extend(object): which extends the current class with the given object (likeObject.extend(Klass, object))Klass.include(object): which extends Klass prototype with the given object.Klass.classEval(declaration): see classEval
Class creation
Class.create or new Class can be called 3 different ways :
- with no argument to create a class like within Prototype.
- with one argument which can be either a class to inherit from (if it's a function) or a declaration body (if it's an object).
- with two arguments : superclass, declaration body
Base is taken.
The declaration body will be an argument to the classEval class method
classEval
Klass.classEval(declaration) : declaration is an hash with special properties :
selfis an hash containing class methods and attributesincludeis a module or an array of modules to include withKlass.include
classEval is like reopening the class, it could be named open.
Source code
The source code has been changed since, you can view the last version.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
var Class = Object.extend(function(superclass, body) { if (typeof superclass != 'function') body = superclass, superclass = Base; var cls = function() { if (arguments[0] !== Class._extending_ && this.initialize) this.initialize.apply(this, arguments); } // Getting properties from Class.prototype Object.extend(cls, this); Object.extend(cls, { superclass: superclass, prototype: new superclass(Class._extending_), subclasses: [] }); cls.constructor = Class; cls.prototype.constructor = cls; if (body) cls.classEval(body); superclass.subclasses.push(cls); if (superclass.inherited) superclass.inherited(cls); if (cls.afterCreation) cls.afterCreation(); return cls; }, { _extending_: new Object(), // backward compatibility create: function(superclass, body) { return new this(superclass, body); } }); Class.prototype = { extend: function(object) { Object.extend(this, object); }, include: function() { this.prototype.extend(mixin); if (Object.isFunction(mixin.included)) mixin.included(this); }, classEval: function(body) { this.extend(Object.erase(body, 'self') || {}); if (typeof body.include == 'object') [ Object.erase(body, 'include') ].flatten().each(this.include.bind(this)); this.prototype.extend(body); } }; var Base = Object.extend(function() {}, { subclasses: [], constructor: Class }); Base.prototype = { extend: function(object) { var ancestor = this.constructor.superclass.prototype; for (var property in object) { if (Object.isFunction(object[property]) && Object.isFunction(ancestor[property]) && object[property].toString().include('$super')) { var method = object[property]; this[property] = Object.extend(ancestor[property].wrap(method), { valueOf: function() { return method; }, toString: function() { return method.toString(); } }); } else this[property] = object[property]; } } } |
a simple URI class
what ?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
var uri = $U("ftp://billy:bobby@ftp.somewhere.com/path/to/something"); uri.scheme // => "ftp" uri.user // => "billy" uri.host // => "ftp.somewhere.com" uri.path // => "/path/to/something" uri == $U(uri) // => true uri.user = 'robert'; uri.path = '/other/place'; uri.scheme = 'http'; uri.toString(); // => "http://robert:bobby@ftp.somewhere.com/other/place" |
source
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
/* Copyright (c) 2007 Samuel Lebeau this piece of software is largely inspired by http://download.dojotoolkit.org/release-0.4.2/dojo-0.4.2-ajax/src/uri/Uri.js Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the Software), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ var URI = Class.create({ initialize: function(uri) { var r = uri.match(this.constructor.uriPattern); if (!r) throw this.constructor._invalidURIError(uri); this._uri = uri; this.scheme = r[2] || (r[1] ? '' : null); this.authority = r[4] || (r[3] ? '' : null); this.path = r[5]; this.query = r[7] || (r[6] ? '' : null); this.fragment = r[9] || (r[8] ? '' : null); if (this.authority) { r = this.authority.match(this.constructor.authorityPattern); this.user = r[3] || null; this.password = r[4] || null; this.host = r[5]; this.port = parseInt(r[7]) || null; } }, toString: function() { return (this.scheme ? this.scheme + '://' : '') + (this.user ? this.user + (this.password ? ':' + this.password : '') + '@' : '' ) + (this.host ? this.host + (this.port ? ':' + this.port : '') : '') + this.path + (this.query ? '?' + this.query : '') + (this.fragment ? '#' + this.fragment : ''); } }); Object.extend(URI, { parse: function(uri) { return new this(uri); }, from: function(object) { return (object.constructor == URI) ? object : URI.parse(object.toString()); }, uriPattern: new RegExp("^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$"), authorityPattern: new RegExp("^(((?:([^:]+):)?([^@]+))@)?([^:]*)(:([0-9]+))?$"), _invalidURIError: function(uri) { this.name = 'InvalidURIError'; this.message = 'Invalid URI : ' + uri + ''; } }); var $U = URI.from; |
yet another javascript inheritance implementation for prototype.js
introduction
As I was writing a complex web application for my school, I feel like I was terribly in need of a nice and useful inheritance mechanism for my javascript code.
I went on the Ruby On Rails Trac and started to look after some interesting patch.
What I found was the famous ticket 4060 submitted by Ben Newman and a link to the paper he wrote about this.
This was really interesting but unfortunately, the source code was too big to be integrated in Prototype. Then I found Base.js on Dean Edward's blog which I liked a lot, but wasn't really the Prototype way (especially the extend semantic).
Another interesting candidate was inheritance.js posted at twologic but I realized it wasn't using a real prototype inheritance (note the new version does, and has inspired Prototype 1.6) but modifying functions (if you decide to redefine a method at runtime, it won't be able to call the parent one).
So I decided to look around and do something by myself with the minimum code possible and looking like Ruby syntax.
usage
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
var Person = Class.create({ // class declaration self: { // class method find: function(name) { return this.population.find(function(p) { return p.name == name; }); }, // class attribute population: [] } // instance declaration // mixins include: [ Comparable, Loggable ], // imaginary mixins initialize: function(name) { this.name = name; // accessing class from instance this.constructor.population.push(this); }, introduce: function() { return My name is + this.name; } }); // We extend Person class var Employee = Class.create(Person, { initialize: function(name, salary) { // call the parent initialize method this.callSuper('initialize', name); this.salary = salary; }, introduce: function() { return this.callSuper('introduce') + and I earn + this.salary; } }); var jack = new Person('Jack'); var billy = new Employee('Billy', 2000); jack.introduce(); // -> My name is Jack billy.introduce(); // -> My name is Billy and I earn 2000 Person.find('Jack').name; // -> 'Jack' Person.find('Billy'); // -> null Employee.find('Billy').name; // -> 'Billy' |
important points
- Class.create is 100% compatible with Prototype and gives an empy initialize method in case no one is given
- Object.prototype isn't modified
- Class declaration goes in self
- initialize special class method is executed at class creation
- In a subclass, callSuper is available as a class method or an instance method, and calls respectively class or instance method with the given name and arguments in superclass
- When deriving a class, we copy it's method and attributes in the subclass, and we inherit it's prototype (modified class method won't be modified in subclass, except if it calls callSuper)
- Every class has 2 two class methods : include and extend, they simply act like their ruby equivalents (see further).
- A subclass has a superclass attribute
- Any initialize method can be omitted, default behavior is calling the parent one
let's go further
What about private methods and attributes ? Well, to be prototype compatible, it seems that we need a dirty hook in class constructor (which is not the initialize function but the one returned by Class.create) so maybe we should just use some convention, like this._one, and be conscious of what we're doing (If I'm calling a method which starts with an underscore from something that is not this, I'm certainly doing something wrong...).
How can we include a mixin after declaration ?The good old Prototype way still works :
Object.extend(MyClass.prototype, MyMixin)
The new funky way :
MyClass.include(MyMixin);
While
MyClass.extend(MyMixin)
litterally extends class with mixin, making MyMixin methods and attributes available in MyClass
Be careful, mixins here are not ruby modules ! They are not part of the inheritance hierarchy. If they redefine a method, the previous one won't be available by calling callSuper.
source
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
/* Copyright (c) 2007 Samuel Lebeau Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the Software), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ var Class = { create: function(superclass, body) { if (!body) { var body = superclass || {}; var superclass = null; } var klass = function() { this.constructor = klass; if (!Class._prototyping_) this.initialize.apply(this, arguments); } if (superclass) { Object.extend(klass, superclass); Object.extend(klass, { superclass: superclass, callSuper: Class._bindCallSuper(superclass), prototype: Class._inheritPrototype(superclass.prototype) }); klass.prototype.callSuper = Class._bindCallSuper(superclass.prototype) } klass.include = Class._include; klass.extend = Class._extend; if (body.self) { Object.extend(klass, body.self); delete body.self; } if (body.include) { [ body.include ].flatten().each(function(mixin) { klass.include(mixin); }); delete body.include; } Object.extend(klass.prototype, body); if (!klass.prototype.initialize) klass.prototype.initialize = Prototype.emptyFunction; if (klass.initialize) klass.initialize(); return klass; }, _inheritPrototype: function(proto) { var inheritance = function() {}; inheritance.prototype = proto; Class._prototyping_ = true; var prototype = new inheritance(); delete Class._prototyping_; return prototype; }, _include: function(mixin) { Object.extend(this.prototype, mixin); }, _extend: function(mixin) { Object.extend(this, mixin); }, _bindCallSuper: function(ancestor) { return function() { var args = $A(arguments), method = args.shift(), ret; this.callSuper = ancestor.callSuper; try { if (ancestor[method] && ancestor[method] != this[method]) ret = ancestor[method].apply(this, args); else if (!this.callSuper) throw new Class._noSuperMethodError(method); else ret = this.callSuper.apply(this, arguments); } finally { this.callSuper = arguments.callee } if (ret) return ret; } }, _noSuperMethodError: function(method) { this.name = 'NoSuperMethodError'; this.message = 'no super method ' + method + ''; } } |
download
- patch for prototype trunk including tests
- prepatched prototype.js (revision 6729)
installation from trunk
$ cd /path/to/prototype/trunk
$ wget http://svn.gotfresh.info/classjs/class-patch-trunk.diff
$ patch -p0 < class-patch-trunk.diff
$ rake dist
then check test/unit/class.html or 'rake test' !