Created
February 25, 2012 17:57
-
-
Save Johnicholas/1909783 to your computer and use it in GitHub Desktop.
Idea regarding styling architecture like CSS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This is an example of a possible large-scale design idiom. | |
// (Basically the idea is to imitate cascading stylesheets). | |
// | |
// It's hard to give a small, comprehensible example of an | |
// idea intended to be useful at (medium to) large scale. | |
// No matter what you do, it's definitely overengineered. | |
// Sigh. | |
// | |
// Often, at the top of an app, or a big component, | |
// there is a wiring-things-together entity; it might be | |
// an xml document in some heavyweight dependency injection | |
// architecture, or it might be just a class whose job is | |
// to own several subcomponents and introduce them to | |
// one another. | |
// | |
// If you have some configuration of these subcomponents | |
// to do, then you might do it with mutators in this top | |
// dependency-injection object. However, configuration | |
// isn't really the same thing as architecture. | |
// | |
// So instead, why not provide a way to configure your | |
// architecture with a separate stylesheet object? | |
// | |
// We use the Visitor design pattern to implement the styling. | |
// First, we have some javascript language magic, | |
// which is how we're going to do inheritance. | |
// Based on yui2 yahoo Lang.js | |
Object.extend = function (subclass, superclass) { | |
if (!superclass || !subclass) { | |
throw new Error("extend failed, please check that all dependencies are included."); | |
} | |
var F = function () {} | |
var i | |
F.prototype = superclass.prototype | |
subclass.prototype = new F() | |
subclass.prototype.constructor = subclass; | |
subclass.superclass = superclass.prototype | |
if (superclass.prototype.constructor == Object.prototype.constructor) { | |
superclass.prototype.constructor = superclass; | |
} | |
} | |
// An element is an interface - it has four methods: | |
// 1. it needs to 'accept' an element visitor | |
// 2. it needs to offer a 'get' accessor that takes and returns a string | |
// 3. it needs to offer a 'getProperties' accessor that returns a | |
// string->string map | |
// 4. it needs to offer a 'set' mutator that takes a pair of strings | |
// An element visitor has one method, visitElement | |
// which takes an element and an array of elements | |
// A selector is an interface that has a 'matches' | |
// method that takes an element and returns a | |
// truthy or falsey value | |
// Domain is an interface | |
// Domain is a Component | |
// TODO details | |
// Persistence is an interface. | |
// Persistence is a Component. | |
// TODO: details | |
// Utilities is an interface | |
// Utilities is a Component. | |
// TODO: details | |
// A component is a kind of element, | |
// that implements set, get, and getProperties in the straightforward way, | |
// delegating to an internal associative array. | |
// It doesn't implement accept. | |
function Component() { | |
this.properties = {} | |
} | |
Component.prototype.set = function (property, value) { | |
this.properties[property] = value | |
} | |
Component.prototype.get = function (property) { | |
return this.properties[property] | |
} | |
Component.prototype.getProperties = function () { | |
return this.properties | |
} | |
// setId is a helper method to abbreviate some things | |
Component.prototype.setId = function (id) { | |
this.set('id', id) | |
return this // for method chaining | |
} | |
// setClass is a helper method to abbreviate some things | |
Component.prototype.setClass = function (classname) { | |
this.set('class', classname) | |
return this // for method chaining | |
} | |
// Rules is a class representing a stylesheet, a list of rules. | |
function Rules(representation) { | |
if (!representation) { | |
this.representation = [] | |
} else { | |
this.representation = representation | |
} | |
} | |
// Add is a mutator. | |
Rules.prototype.add = function (rule_to_add) { | |
this.representation.push(rule_to_add) | |
} | |
// Rules meets the element visitor interface. | |
Rules.prototype.visitElement = function (to_visit, dependencies) { | |
for (var i in this.representation) { | |
this.representation[i].run(to_visit); | |
} | |
for (var i in dependencies) { | |
dependencies[i].accept(this); | |
} | |
} | |
// ByClass is a selector that is constructed with a string | |
// It's like '.h1' in CSS. | |
function ByClass(class_name) { | |
this.class_name = class_name | |
} | |
ByClass.prototype.matches = function (to_test) { | |
return to_test.get("class") === this.class_name; | |
} | |
// ById is a selector, that is constructed with a string | |
// It's like '#body' in CSS. | |
function ById(id_name) { | |
this.id_name = id_name | |
} | |
ById.prototype.matches = function (to_test) { | |
return to_test.get("id") === this.id_name | |
} | |
// Rule is a class, constructed with a selector. | |
// It's something like '.h1 { color:red; }' in CSS. | |
function Rule(selector) { | |
this.selector = selector | |
this.properties = {} | |
} | |
Rule.prototype.set = function (property, value) { | |
this.properties[property] = value | |
return this // for method chaining | |
} | |
Rule.prototype.run = function (element) { | |
if (this.selector.matches(element)) { | |
for (i in this.properties) { | |
element.set(i, this.properties[i]) | |
} | |
} | |
} | |
// Renderer is an element visitor that prints out all the properties, | |
// thereby demonstrating that the stylesheet really did its job. | |
// | |
// Note that it keeps track of what elements its seen, | |
// to prevent showing the same element more than once. | |
function Renderer() { | |
this.seen = [] | |
} | |
Renderer.prototype.renderElement = function (to_render) { | |
print("Component:") | |
var properties = to_render.getProperties() | |
for (i in properties) { | |
print(" " + i + ": " + properties[i]) | |
} | |
} | |
Renderer.prototype.visitElement = function (to_render, dependencies) { | |
this.seen.push(to_render) | |
this.renderElement(to_render) | |
for (i in dependencies) { | |
if (this.seen.indexOf(dependencies[i]) === -1) { | |
dependencies[i].accept(this) | |
} | |
} | |
} | |
function Utilities() { | |
Utilities.superclass.constructor.call(this) | |
} | |
Object.extend(Utilities, Component) | |
Utilities.prototype.accept = function (visitor) { | |
visitor.visitElement(this, []) | |
} | |
// ...TODO... this would have more methods if it were not an example | |
function Database(utilities) { | |
Database.superclass.constructor.call(this) | |
this.utilities = utilities | |
} | |
Object.extend(Database, Component) | |
Database.prototype.accept = function (visitor) { | |
visitor.visitElement(this, [this.utilities]) | |
} | |
// ...TODO... this would have more methods if it were not an example | |
function Business(persistence, utilities) { | |
Business.superclass.constructor.call(this) | |
this.persistence = persistence | |
this.utilities = utilities | |
} | |
Object.extend(Business, Component) | |
Business.prototype.accept = function (visitor) { | |
visitor.visitElement(this, [this.persistence, this.utilities]) | |
} | |
// ...TODO... this would have more methods if it were not an example | |
function UI(domain, utilities) { | |
UI.superclass.constructor.call(this) | |
this.domain = domain | |
this.utilities = utilities | |
} | |
Object.extend(UI, Component) | |
UI.prototype.accept = function (visitor) { | |
visitor.visitElement(this, [this.domain, this.utilities]) | |
} | |
// ... TODO... this would have more methods if it were not an example | |
// stylesheet is a factory function with no arguments that returns an ElementVisitor. | |
// | |
// The idea is to create an entity which is something like this (utterly fake) CSS: | |
// #infrastructure { | |
// use-iso-dates:true; | |
// use-auth:false; | |
// } | |
// .stable { | |
// use-safety-interlocks:true; | |
// } | |
// .unstable { | |
// generate-admin-interface:true; | |
// } | |
function stylesheet() { | |
return new Rules([ | |
new Rule(new ById('infrastructure')) | |
.set('use-iso-dates', 'true') | |
.set('use-auth', 'false'), | |
new Rule(new ByClass('stable')) | |
.set('use-safety-interlocks', 'true'), | |
new Rule(new ByClass('unstable')) | |
.set('generate-admin-interface', 'true') | |
]) | |
} | |
// architecture is a factory function that returns an element | |
// | |
// The idea is that we have some sort of big layercake architecture, | |
// with several components depending on one another in a standard way. | |
// "configuring" the layercake, rewiring the dependency structure, | |
// is done by different people for different reasons than the | |
// "configuring" the app, styling it by flipping switches and | |
// setting parameters that control its behavior. | |
// architecture is a factory function with no arguments | |
// that returns something that can accept an element visitor | |
// and it may be able to run. | |
// | |
// Utilities depends on nothing but everything else depends on Utilities | |
// UI depends on Domain (business) | |
// Business depends on Persistence (database) | |
function architecture() { | |
var utilities = new Utilities().setId('infrastructure').setClass('stable') | |
var database = new Database(utilities).setId('data').setClass('stable') | |
var business = new Business(database, utilities).setId('business').setClass('unstable') | |
return new UI(business, utilities).setId('ui').setClass('unstable') | |
} | |
// app is a factory function that combines the architecture with the stylesheet | |
function app() { | |
var a = architecture() | |
var s = stylesheet() | |
a.accept(s) | |
return a | |
} | |
// This renders the app, demonstrating that the styling really does work. | |
function render() { | |
var a = app() | |
var r = new Renderer() | |
a.accept(r) | |
} | |
// This is more how the app would actually be run, | |
// but it doesn't actually do anything - this is just an example. | |
function run() { | |
var a = app() | |
a.run() | |
} | |
// For the example, we just print out the styled app. | |
render() |
Well, ids are normally unique, so aspect-weaving a mutator onto a component
using a visitor is just moving code - except the infrastructure to do it is
pretty verbose. However, the classes could in principle introduce
duplication and so save some code.
But you're right, the major point is to separate concerns - the
architecture and the style or configuration.
Johnicholas
…On Sun, Feb 26, 2012 at 10:46 PM, Darius Bacon < ***@***.*** > wrote:
This looks like it could have two kinds of value: to save code or to move
code around to match some division of responsibilities. To actually save
code would need fancier conditions than the ById and ByClass ones -- such
as compound conditions -- right?
---
Reply to this email directly or view it on GitHub:
https://gist.github.com/1909783
I was thinking for classes you might write
function setUpAFoo(aFoo) { ... }
and call that on Foo instances. So the configurator would own a bunch of definitions like that and the architect has to import them. But for something like a compound condition in CSS you can't avoid this infrastructure anymore.
(Interesting idea, BTW.)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This looks like it could have two kinds of value: to save code or to move code around to match some division of responsibilities. To actually save code would need fancier conditions than the ById and ByClass ones -- such as compound conditions -- right?