As I’ve mentioned before, I advocate using a Model-View-Controller pattern for certain types of JavaScript-heavy web-app clients. In spite of recent licensing issues, I still think ExtJS is among the better libraries supporting MVC. For example, (if you don’t know what namespace/using are, please read this)
namespace('dk.okooko.model'); using(dk.okooko.model).run(function(m){ m.OrderItem = Ext.data.Record.create([ {"type": "int", "name": "id"}, {"type": "int", "name": "product_id"}, {"type": "int", "name": "quantity"}, {"type": "int", "name": "order_id"}, {"type": "date", "format": "d/m/Y-H:i", "name": "created_at"}, {"type": "date", "format": "d/m/Y-H:i", "name": "updated_at"} ]); });
The ‘Record.create’ function gives a way of succinctly creating constructor functions for model objects which are supported throughout the Ext functions; you can read more about it here.
If you are using Ruby on Rails at server-side (something which I have been doing quite a bit recently), then you realize that you are manually duplicating all the rails model classes in JavaScript. For example, here is the corresponding migration:
class CreateOrderItems < ActiveRecord::Migration def self.up create_table :order_items do |t| t.integer :product_id t.integer :quantity t.integer :order_id t.timestamps end end def self.down drop_table :order_items end end
I was annoyed by this lack of dryness: whenever I changed the migration, I’d have to manually change the corresponding JS model. So I slapped together a really simple code generator which can create the model files from the rails models. It adds a method acts_as_jsmodel which is intended to be called by a rails model class, e.g.,
class OrderItem < ActiveRecord::Base belongs_to :order belongs_to :product has_one :order_item_status acts_as_jsmodel end
So you can annotate model classes that you’d like to generate js models for. The acts_as_jsmodel causes a file to be written to public/javascripts/dk/okooko/model (generally, it depends on a constant APP_NAMESPACE).
Here is the code for the generator:
module Trifork module JSModel JS_MODEL_DIR = "public/javascripts/#{APP_NAMESPACE}/model".gsub!('.','/') MODEL_PACKAGE = APP_NAMESPACE + '.model' def acts_as_jsmodel export_record "#{JS_MODEL_DIR}/#{self}.js" rescue nil end def export_record(fn) File.open(fn,'w') do |f| s =<<END namespace('#{MODEL_PACKAGE}'); using(#{MODEL_PACKAGE}).run(function(m){ m.#{self} = Ext.data.Record.create([ #{self.to_record} ]); }); END f.write s end end def to_record(spec = {},datefm=nil) spec[:exclude] ||= [] keys = spec[:only] ? [*spec[:only]] : (columns.map &:name).reject {|a| spec[:exclude].include?a} props = columns_hash keys.map do |k| p = props[k.to_s] if p.nil? raise "Property #{p} is not a column of #{self}" end {:name => p.name}.merge!(Trifork::JSModel::to_ext_type(p.type,datefm)).to_json end.join(",\n ") end def self.to_ext_type(t,datefm) type = case t when :integer then 'int' when :string, :text then 'string' when :boolean then 'bool' when :datetime then 'date' else 'auto' end res = {:type => type} res.merge!(:format => (datefm || 'd/m/Y-H:i')) if t==:datetime res end end end
You can download the files from this entry here.
I’ve had a similar idea in the past but found that you can’t safely regenerate your JS models this way (or at least I can’t) because the JS models often contain additional model logic. I’ve been creating an MVC framework on top of ExtJS for a few months and a typical model looks like this:
AdFunded.models.AdvertisingCategory = new Ext.ux.MVC.model.Base(‘advertising_category’, {
fields: [
{ name: 'id', type: 'int'},
{ name: 'name', type: 'string'},
{ name: 'description', type: 'string'}
],
controller_name: ‘AdvertisingCategoriesController’,
human_plural_name: ‘Advertising Categories’,
url_name: ‘advertising_categories’
});
There’s a bit of magic going on under the covers here but essentially the model creates an Ext.data.Record with the fields you pass it in the background. Trouble is – to regenerate this would lose the additional config options I’ve given the model (and some models have much more inside them than this one).
To counter this I have been writing a Schema library for Ext MVC, which converts Rails migrations (or potentially any other kind) into a JS form and runs them in sequence at run time. That would mean an end to specifying the fields in the model, which makes migration easier, but it’s also nice to have them there in the model file (like with the rails annotated models plugin).
I’m still not sure what the best way to go on that one is – any ideas?
Hello Ed,
Ah, yes I see the problem. I’m not sure how your MVC.model.Base works; however, if you were just using regular models (i.e. models created by Ext.Record.create), then I think you could get around this using inheritance.
The strategy could be: code-generate the base model using the approach above (or your own adapted version), then create a model which extends the base. If you put all your extra logic in the extended class then you can safely regenerate the base class when your migration changes — the base would be overridden, but the extending model would remain the same.
Incidentally, you could use the code I posted on extending models created by Ext.Record.create in this blog posting.
Think that would work out for you?
/Karl
Hi Karl,
Thanks for that. MVC.model.Base is somewhat akin to ActiveRecord::Base – you just tell it what your model is called and what fields to include and from that it knows how to map each model to a URL resource, how to pluralize/inflect (in most instances anyway), as well as providing convenient Stores for both an individual model (e.g. /my_resource/1.json) or a collection (/my_resource.json).
Model was one of the first classes I wrote and is in need of some major refactoring – I’m just trying to figure out the best way at the moment. I like the approach you take in your post about extending Ext.data.Record – I think I’ll give it a go that way. I’m really trying to achieve a Rails-like model definition so I’ll see how far that goes…
The code is on Github at http://github.com/edspencer/ext-mvc/tree/master/model/base.js if you’re interested. Model.Base is not pretty (yet) but some of the other bits are!
One final point about style – from your examples you seem to usually downcase everything except the last part of the class name (e.g. com.trifork.tribook.model.Reservation). ExtJs seems to be quite random though – e.g. Ext.data.Record, Ext.form.Action.Load, Ext.ColorPalette all seem to follow a different convention. ‘Ext’ is always capitalized, the next segment is usually not (except for some classes like Ext.Ajax and Ext.ColorPalette), then the third segment is usually capitalized again. Do you know of a ‘universal’ convention for this? Why not just capitalize everything?
Thanks for maintaining a great blog!
Ed
Hi again, Ed.
I like that you are attempting to mimick ActiveRecord::Base in JavaScript, it sounds interesting and useful; I’ll have a look at your code at some point…
“One final point about style – from your examples you seem to usually downcase everything except the last part of the class name ”
My convention is certainly not universal. I try to follow these primary conventions for code that is written in what Douglas Crockford calls “Pseudo classical” style (i.e., how JavaScript is usually written):
I haven’t checked this, but I believe that Ext is actually consistent in its naming conventions: Ext is a singleton object (which lives in the global namespace) ✓ Ext.data plays the role of a package object so it is not capitalized ✓ Ext.data.Record is a constructor function ✓
I think the only other rule that governs this is: if the object/function consists of two words then each word is uppercase, e.g., ColorPalette.
“Thanks for maintaining a great blog!”
Thanks for that comment! Great to get some interaction, so keep commenting
/Karl
Pingback: Recent Links Tagged With "mvc" - JabberTags