Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions VIRUS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Virus

## Concept
Viruses are a plug-in system that makes it able to include custom transformations to the cells, extending them as far as wanted.

To use it, you should "infect" a Gene with a Virus, which is a function that takes a Gene-like object and returns another Gene-like object. This returned object _could_ be a real Gene or an object to be transformed by another Virus (pretty much like a Pipeline).

## Examples
The following virus propagates the $update method down the inheritance tree when a variable is changed:

```
function update_propagating_virus(component){
let recursive_update = (node) => {
for(let n of node.children){
n.$update && n.$update()
recursive_update(n)
}
}

let old_update = component.$update

component.$update = function(){
old_update && old_update.call(this)
recursive_update(this)
}

return component
}

window.c = {
$cell: true,
$type: 'ul',
_name: '',
$virus: update_propagating_virus,
$components: [
{ $type: 'li',
$components: [
{ $type: 'p',
$text: '',
$update: function(){
this.$text = this._name;
}
},
{ $type: 'p', $text: 'other' }
]
}
]
}
```

As the virus can be any function that expects a component, you can also send other arguments to it.

The following example shows how to make a virus to call a method every X seconds:

```
var Tickable = function(timer, trigger){
return function(gene) {
gene.$init = function() {
var self = this;
setInterval(function() {
self[trigger]();
}, timer);
}
return gene;
}
}

...

{
$virus: [Tickable(500, '_mutate')]
}
```

Refer to the [tests](test/integration.js) for more examples and details.
25 changes: 20 additions & 5 deletions cell.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,20 @@
Genotype.set($node, key, gene[key]);
}
},
infect: function(gene) {
var virus = gene.$virus;
if (!virus) return gene;
var mutations = Array.isArray(virus) ? virus : [virus];
delete gene.$virus;
return mutations.reduce(function(g, mutate) {
var mutated = mutate(g);
if (mutated === null || typeof mutated !== 'object') {
throw new Error('$virus mutations must return an object');
}
mutated.$type = mutated.$type || 'div';
return mutated;
}, gene);
},
};
var Gene = {
/*
Expand Down Expand Up @@ -240,17 +254,17 @@
$type: function(model, namespace) {
var meta = {};
var $node;
if (model.$type === 'text') {
if (model.$text && typeof model.$text === 'function') model.$text = Phenotype.multiline(model.$text);
$node = $root.document.createTextNode(model.$text);
} else if (model.$type === 'svg') {
if (model.$type === 'svg') {
$node = $root.document.createElementNS('http://www.w3.org/2000/svg', model.$type);
meta.namespace = $node.namespaceURI;
} else if (namespace) {
$node = $root.document.createElementNS(namespace, model.$type);
meta.namespace = $node.namespaceURI;
} else if (model.$type === 'fragment') {
$node = $root.document.createDocumentFragment();
} else if (model.$type === 'text') {
if (model.$text && typeof model.$text === 'function') model.$text = Phenotype.multiline(model.$text);
$node = $root.document.createTextNode(model.$text);
} else {
$node = $root.document.createElement(model.$type || 'div');
}
Expand Down Expand Up @@ -522,7 +536,8 @@
// As a result, all HTML elements become autonomous.
if ($context === undefined) $context = $root;
else $root = $context;
$context.DocumentFragment.prototype.$build = $context.Element.prototype.$build = function(gene, inheritance, index, namespace, replace) {
$context.DocumentFragment.prototype.$build = $context.Element.prototype.$build = function(healthy_gene, inheritance, index, namespace, replace) {
var gene = Genotype.infect(healthy_gene);
var $node = Membrane.build(this, gene, index, namespace, replace);
Genotype.build($node, gene, inheritance || [], index);
Nucleus.build($node);
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"uglify": "uglifyjs cell.js --compress --mangle --output cell.min.js",
"test:lint": "eslint cell.js",
"test:mocha": "nyc --reporter=html --reporter=text _mocha --require jsdom-global/register",
"test": "npm run test:lint && npm run test:mocha && npm run site",
"test": "npm run test:lint && npm run test:mocha",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"site": "node ./node_modules/phd"
},
Expand Down
4 changes: 2 additions & 2 deletions phd.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module.exports = {
module.exports = [{
js: [
'https://www.celljs.org/cell.js',
'https://cdnjs.cloudflare.com/ajax/libs/timeago.js/3.0.1/timeago.min.js',
Expand Down Expand Up @@ -35,4 +35,4 @@ module.exports = {
ga('create', 'UA-54282166-10', 'auto');
ga('send', 'pageview');
}
}
}]
49 changes: 49 additions & 0 deletions test/Genotype.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,5 +137,54 @@ describe("Genotype", function() {
c: "C"
})
})
describe("$virus", function() {
let ul_mutating_virus = function(component){
component.id = "infected";
component.$components = [{$type: 'li'}];
return component;
}
it("Applies a single virus mutation", function() {
let component = { $type: 'ul', $virus: ul_mutating_virus };

compare(Genotype.infect(component), {
$type: 'ul',
$components: [{$type: 'li'}],
id: 'infected',
})

let $node = root.document.body.$build(component, []);
compare($node.outerHTML, '<ul id="infected"><li></li></ul>');
})
it("Applies multiple virus mutations sequentially", function() {
let id_mutating_virus = function(component){
component._previous_id = component.id;
component.id += "_again";
return component;
}

let component = {
$type: 'ul',
$virus: [ul_mutating_virus, id_mutating_virus]
};

compare(Genotype.infect(component), {
$type: 'ul',
$components: [{$type: 'li'}],
_previous_id: 'infected',
id: 'infected_again',
})

let $node = root.document.body.$build(component, []);
compare($node.outerHTML, '<ul id="infected_again"><li></li></ul>');
})
it("Errors when mutation does not comply with the API", function() {
let wrong_mutation = function(component){
let the_thing = "return nothing";
}

let component = { $type: 'ul', $virus: wrong_mutation };
assert.throws(() => Genotype.infect(component), /return an object/)
})
})
})
})
15 changes: 11 additions & 4 deletions test/Phenotype.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ describe("Phenotype", function() {
compare(node.namespaceURI, "dummy namespace")
compare(node.Meta, {namespace: "dummy namespace"})
})
it("text node as svg descendent", function() {
const node = Phenotype.$type({$type: "text"}, "http://www.w3.org/2000/svg")
// Should not be a text node. Should be a svg text node
compare(node.nodeType === Node.TEXT_NODE, false)
compare(node.namespaceURI, "http://www.w3.org/2000/svg");
compare(node.Meta, {namespace: "http://www.w3.org/2000/svg"})
})
})
it("fragment", function() {
const node = Phenotype.$type({$type: "fragment"})
Expand Down Expand Up @@ -413,13 +420,13 @@ describe("Phenotype", function() {

styleAttr = $node.getAttribute("style");
styleProp = $node.style;

compare(styleAttr, "background-color: red; font-family: Courier;")
compare(styleProp.backgroundColor, "red");
compare(styleProp.fontFamily, "Courier");

compare(Object.getPrototypeOf(styleProp).constructor.name, "CSSStyleDeclaration");

});
});
describe("string", function() {
Expand All @@ -441,13 +448,13 @@ describe("Phenotype", function() {

styleAttr = $node.getAttribute("style");
styleProp = $node.style;

compare(styleAttr, "background-color: red;")
// even if we initially set the style as string,
// we should be able to access it as an object property
compare(styleProp.backgroundColor, "red");
compare(Object.getPrototypeOf(styleProp).constructor.name, "CSSStyleDeclaration");

});
it("class", function() {
const $parent = document.createElement("div");
Expand Down
Loading