Skip to content

Instantly share code, notes, and snippets.

@werelax
Created October 19, 2011 23:52
Show Gist options
  • Save werelax/1300027 to your computer and use it in GitHub Desktop.
Save werelax/1300027 to your computer and use it in GitHub Desktop.
LousyCells + Pseudo-Declarative behaviour proof of concept
// Some helpers
function unique (array) {
var a = [];
var l = array.length;
for(var i=0; i<l; i++) {
for(var j=i+1; j<l; j++) {
// If array[i] is found later in the array
if (array[i] === array[j])
j = ++i;
}
a.push(array[i]);
}
return a;
}
var debounce = (function() {
var limit = function(func, wait, debounce) {
var timeout;
return function() {
var context = this, args = arguments;
var throttler = function() {
timeout = null;
func.apply(context, args);
};
if (debounce) clearTimeout(timeout);
if (debounce || !timeout) timeout = setTimeout(throttler, wait);
};
};
return function(func, wait) { return limit(func, wait, true); };
})();
// LousyCell implementation
var Cell = (function () {
var current_id = 0;
function Cell(value) {
this.id = current_id++;
this.value = value;
this.observers = [];
};
Cell.prototype = {
get: function() { return this.value; },
set: function(value) {
this.value = value;
this.notify_change(this.id, value);
return this.value;
},
observe: function(callback) {
this.observers.push(callback);
this.ovservers = unique(this.observers);
},
notify_change: function() {
for (var i=0, _len=this.observers.length; i < _len; i++) {
var observer = this.observers[i];
if (observer) try {
this.observers[i].call(this.value);
} catch (e) { }
}
}
};
return Cell;
})();
var $C = function(arg1, arg2) {
arg1 || (arg1 = '');
var is_array = function(a) { return a.constructor == Array.prototype.constructor; }
var attach_observer = function (cell_decorator, fn) {
var callback = function() { fn(cell_decorator()); };
var deps = cell_decorator._root_cells;
for (var i=0, _len = deps.length; i < _len; i++) {
var dep_cell = deps[i];
dep_cell.observe(callback);
}
};
var bind_cell = function (input, fn) {
is_array(input) || (input = [input]);
var decorator = function() {
var values = input.map(function(c) { return c(); });
return fn ? fn.apply({}, values) : values[0];
}
decorator._is_cell = true;
decorator._root_cells = input.reduce(function (r, c) { return r.concat(c._root_cells); }, []);
decorator._root_cells = unique(decorator._root_cells);
decorator._dependencies = input;
decorator.listen = function (fn) { return attach_observer(decorator, fn); }
return decorator;
};
var create_input_cell = function (value, getter_filter) {
var cell = new Cell(value);
var decorator = function (new_value) {
if (new_value !== undefined) { return cell.set(new_value);}
else { return getter_filter ? getter_filter(cell.get()) : cell.get(); }
}
decorator._is_cell = true;
decorator._root_cells = [cell];
decorator.connect = function (selector, options) { return connect_to_selector(decorator, selector, options); };
return decorator;
};
if (arg1._is_cell || (is_array(arg1) && typeof(arg2) == 'function')) {
return bind_cell(arg1, arg2);
} else {
return create_input_cell(arg1, arg2);
};
};
/** Example:
var input_cell = $C('Hello');
var cell1 = $C(input_cell);
console.log('cell1: ' + cell1());
// "cell1: Hello"
var cell2 = $C(input_cell, function(cell) { return cell + ", I said."});
console.log('cell2: ' + cell2());
// "cell2: Hello, I said."
var cell3 = $C([cell1, cell2], function(c1, c2) { return c1 + ". " + c2; });
console.log('cell3: ' + cell3());
// "cell3: Hello. Hello, I said."
$C(cell3).listen(function(value) {
console.log('cell3 changed to: ' + value);
});
input_cell('Goodbye');
// "cell3 changed to: Goodbye. Goodbye, I said."
/** Another example with multiple input cells
var ic1 = $C(10);
var ic2 = $C(5);
var ic3 = $C(200);
var result = $C([ic1, ic2, ic3], function(v1, v2, v3) { return v1 + v2 + v3});
$C(result).listen(function(result) {
console.log("Result changed to: " + result);
});
console.log("At the start, result is: " + result());
// "At the start, result is: 215"
ic1(0);
// Result changed to: 205
ic2(1000);
// Result changed to: 1200
ic3(-1123);
// Result changed to: -123
**/
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Simple_declarative_view_example</title>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.6.4.js"></script>
<script type="text/javascript" src="lousy_cells.js" charset="utf-8"></script>
</head>
<body>
<script type="text/template" id="test-template">
<h1>{{name}}</h1>
<hr/>
<label> Type something or put the mouse over:
<input id="input1" type="text"/>
</label>
<div id="result1"></div>
<div id="msg1" style="display:none;color:green">
Your mouse is over the input box
</div>
</script>
<div id="container"></div>
<script type="text/javascript" charset="utf-8">
// Some shitty templates / view system just for make it work.
// Dont look at this code, this is only auxiliar bullshit just for
// avoid adding too mucho complexity to the example with external libs
function LousyTemplate(id) {
var text = $('#'+id).text();
return function() {
return LousyTemplate.renderer.call(this, text);
};
}
LousyTemplate.renderer = function (text) {
var data = this;
var result = text;
var placeholders = text.match(/{{.*?}}/g);
var already_replaced = {};
for (i=0,_len=placeholders.length; i<_len; i++) {
var placeholder = placeholders[i];
if (placeholder in already_replaced) continue;
var prop_name = placeholder.match(/[^{|}]+/);
var replacement = data[prop_name];
if (typeof(replacement) == 'function') replacement = replacement.call(this);
var rep_exp = new RegExp(placeholder, 'g');
result = result.replace(rep_exp, replacement);
already_replaced[placeholder] = true;
}
return result;
};
var T = {test: LousyTemplate('test-template') };
// The code of the declarative behaviour view must be something in this line:
function SimpleView(params) {
params || (params = {});
this.el = params.el;
this.template = params.template;
this.behaviour = params.behaviour;
}
SimpleView.prototype = {
render: function() {
$(this.el).html(this.template());
this.bind_behaviour();
},
bind_behaviour: function() {
for(var i=0, _len1=this.behaviour.length; i<_len1; i++) {
var pattern = this.behaviour[i];
var selector = pattern[0];
var event = pattern[1];
var binding = pattern[2];
var cell_or_func = pattern[3];
if (binding == 'value') binding = function() { return $(this).val(); };
if (event == 'observe') {
this.connect_output(selector, binding, cell_or_func);
} else {
this.connect_input(selector, event, binding, cell_or_func);
}
}
},
connect_input: function(selector, event, binding, cell_or_func) {
var self = this;
$(this.el).find(selector)[event](function() {
var value = binding.call(this);
cell_or_func.call(self, value);
});
},
connect_output: function(selector, binding, cell) {
var element = $(this.el).find(selector);
$C(cell).listen(function(value) {
if (typeof(binding) == 'function') {
binding.call(element, value);
} else {
element[binding](value);
}
});
}
};
// Once the library is ready, the code for using it should be something like this:
var SomeView = function(params) {
params.template = T['test'];
this.data1 = $C();
this.data2 = $C();
this.name = "SIMPLE PSEUDO-DECLARATIVE EXAMPLE";
this.mix = $C([this.data1, this.data2], function (v1, v2) {
return "You typed: '" + v1 + (v2?"' and your mouse is over the input":"'");
});
params.behaviour = [
['#input1', 'keyup', 'value', this.data1],
['#input1', 'mouseover', function(){return true;}, this.data2],
['#input1', 'mouseout', function(){return false;}, this.data2],
['#result1', 'observe', function(v){this.html(v);}, this.mix],
['#msg1', 'observe', function(v){this.css('display', v?'block':'none')}, this.data2]
];
SimpleView.call(this, params);
};
SomeView.prototype = new SimpleView();
var v1 = new SomeView({el: $('#container').get(0)}).render();
</script>
</body>
</html>
@werelax
Copy link
Author

werelax commented Oct 20, 2011

The live example (as ugly as it is) in: http://jsfiddle.net/werelax/76vvr/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment