Permalink
Browse files

Preliminary sketch of WebModules

  • Loading branch information...
netj committed Aug 6, 2011
0 parents commit 92fabee5bff780c7784e2de7dd4800dd260d16fe
@@ -0,0 +1,2 @@
*.sw[poq]
.DS_Store
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<title>POPONG</title>
<script src="webmodules/init.js"></script>
<link rel="webmodules-map" type="application/json" href="webmodules-map.json">
<style>
body {
width: 900px;
margin: 10px auto;
}
</style>
</head>
<body>
WebModules test
<a href="#!/intimacy/jaeho.shin">intimacy(jaeho.shin)</a>
<a href="#!/intimacy/jooddang">intimacy(jooddang)</a>
<a href="#!/">others</a>
<a href="#!/asdfqwer">garbage</a>
</body>
</html>
@@ -0,0 +1,45 @@
<style>
#frame {
position: absolute;
top: 50px;
}
#neighbors, #background {
display: block;
position: absolute;
padding: 0;
margin: 0;
width: 900px;
height: 600px;
border: 1px solid;
}
#neighbors > li, #center {
display: block;
position: absolute;
}
#background {
border: 2px dotted blue;
z-index: -1;
}
#center {
z-index: 1;
}
.object {
text-align: center;
border: 1px solid #39f;
padding: 10px;
background-color: #9cf;
border-radius: 10px;
box-shadow: 0px 3px 5px rgba(128,128,128,0.7);
cursor: move;
}
.object:hover, .object.dragging {
background-color: #ff9;
}
</style>
<div id="frame">
<canvas id="background"></canvas>
<div id="center"></div>
<ul id="neighbors">
<li>...</li>
</ul>
</div>
@@ -0,0 +1,135 @@
module.load = function(me) {
module.display("frame.html").then(function(){
// template with {number} for positional arguments
String.prototype.format = function() {
var formatted = this;
for (var i = 0; i < arguments.length; i++) {
var regexp = new RegExp('\\{'+i+'\\}', 'gi');
formatted = formatted.replace(regexp, arguments[i]);
}
return formatted;
};

var profileTemplate = '\
<a href="http://www.facebook.com/{0}"><img src="{1}" alt=""><br>{0}</a>\
';

if (!me)
me = 'jaeho.shin';
// TODO fetch neighbors
var neighbors =
[{weight:1.0, facebookId:'jooddang'}
,{weight:0.3, facebookId:'Osing604'}
,{weight:0.7, facebookId:'100000136930302'}
,{weight:0.9, facebookId:'dmlord'}
,{weight:0.8, facebookId:'kyujin.shim'}
,{weight:0.4, facebookId:'dgtgrade'}
,{weight:0.8, facebookId:'pcpenpal'}
,{weight:0.2, facebookId:'1526484720'}
,{weight:0.3, facebookId:'1034516089'}
,{weight:0.5, facebookId:'galadbran'}
,{weight:1.0, facebookId:'cornchz'}
];

function profileImage(facebookId) {
return 'http://graph.facebook.com/' + facebookId + '/picture';
}

$(document).ready(function() {
// fill page
$("#center")
.html(profileTemplate.format(me, profileImage(me)))
.addClass("object");
$("#neighbors").html("");
for (var i in neighbors) {
var neighbor = neighbors[i];
$("<li class='object'>")
.html(profileTemplate.format(neighbor.facebookId, profileImage(neighbor.facebookId)))
.each(function() { this.info = neighbor; })
.appendTo("#neighbors")
;
}

// circular layout
var frame = $("#neighbors")[0];
var center = $("#center")[0];
var width = frame.offsetWidth;
var height = frame.offsetHeight;
var rW = width/2 - center.offsetWidth /2;
var rH = height/2 - center.offsetHeight/2;
center.style.left = rW +'px';
center.style.top = rH +'px';
var objects = $("#neighbors > li");
var n = objects.length;
var i = 0;
$(objects).each(function(i) {
var t = i * 2*Math.PI / n - Math.PI/2;
var w = this.info.weight;
var x = parseInt(rW + rW * (1-0.4*w) * Math.cos(t));
var y = parseInt(rH + rH * (1-0.4*w) * Math.sin(t));
this.style.left = x +'px';
this.style.top = y +'px';
console.log(this.innerText, x, y);
});

// lines btwn objects
function lineColor(w) {
var lineColorMin = 64;
var lineColorMax = 255;
return "rgb({0},{0},{1})".format(parseInt((lineColorMax-lineColorMin)*(1-w)+lineColorMin), lineColorMax, lineColorMin);
}
var canvas = $("#background")[0];
canvas.width = width;
canvas.height = height;
if (canvas.getContext) {
var c = canvas.getContext("2d");
function updateLines() {
canvas.width = width; // reset canvas
c.lineCap = c.lineJoin = "round";
c.lineWidth = 3;
$(objects).each(function(i) {
c.beginPath();
c.moveTo(center.offsetLeft+center.offsetWidth/2, center.offsetTop+center.offsetHeight/2);
c.lineTo( this.offsetLeft+ this.offsetWidth/2, this.offsetTop+ this.offsetHeight/2);
c.closePath();
c.strokeStyle = lineColor(this.info.weight);
c.lineWidth = parseInt(1 + 5 * this.info.weight);
c.stroke();
});
}
updateLines();
}

// let user drag objects and customize layout
var dragInfo = null;
var dragOldE = null;
var dragUpdateDelay = 50; //ms
$("#frame")
.mousemove(function(e) {
if (dragInfo && e.timeStamp - dragInfo.e.timeStamp > dragUpdateDelay) {
dragInfo.e = e;
dragInfo.o.style.left = e.pageX - dragInfo.x +'px';
dragInfo.o.style.top = e.pageY - dragInfo.y +'px';
updateLines();
}
})
$(".object")
.mousedown(function(e) {
dragInfo = {
x: e.pageX - this.offsetLeft,
y: e.pageY - this.offsetTop,
e: e,
o: this
}
this.style.zIndex = 2;
$(this).toggleClass("dragging");
return false;
})
.mouseup(function() {
$(this).toggleClass("dragging");
this.style.zIndex = null;
dragInfo = null;
});
});
});
};
@@ -0,0 +1,3 @@
module.load = function(){
module.display("welcome.html");
}
@@ -0,0 +1 @@
<b>Hello</b>, <em>World</em>!
@@ -0,0 +1,8 @@
{
"intimacy": {
"urls": "/intimacy/(.*)"
},
"start": {
"urls": ".*"
}
}
No changes.
@@ -0,0 +1,15 @@
WM = parent.WM;
jQuery = parent.jQuery; $ = parent.$;

function WebModule(name) {
// prototype for WebModule
this.name = name;
this.base = WM.pathForModuleName(name);

var presentationFrame;
this.display = function(path) {
// FIXME check if url is relative or not?
return WM.webmodules.presentation.display(module.base +"/"+ path);
};
}
module = new WebModule(moduleName);
@@ -0,0 +1,141 @@
// WebModules -- a set of scripts and conventions to build a modular web site
// Author: Jaeho Shin <netj@sparcs.org>
// Created: 2011-08-01

function WebModules() {
var WM = this;
// TODO track basepath
var readyListBeforeInit = [];
this.ready = function(fn) { readyListBeforeInit.push(fn) };
this.init = function() {
$(window).load(function() {
var d = WM.loadModule("webmodules.urlrouter");
d = d.pipe(function(){ return WM.loadModule("webmodules.presentation"); });
// TODO dependency btwn modules?
for (var i in readyListBeforeInit)
d.done(readyListBeforeInit[i]);
});
};
this.debug = function() {
/* XXX remove me */
arguments[arguments.length++] = "(WebModules)";
console.log.apply(console, arguments);
};
this.addHTMLElement = function(name, params) {
var doc = params.document;
if (!doc)
doc = document;
var e = doc.createElement(name);
var properties = params.properties;
if (properties)
for (var propName in properties)
e[propName] = properties[propName];
var attributes = params.attributes;
if (attributes)
for (var attrName in attributes)
e.setAttribute(attrName, attributes[attrName]);
var content = params.content;
if (content)
e.innerHTML = content;
var target = params.target;
if (!target)
target = doc.body;
target.appendChild(e);
return e;
};
this.pathForModuleName = function(moduleName) {
return moduleName.replace(/\./, "/");
};
this._modules = [];
this.registerModule = function(moduleName, module) {
this.debug("loaded "+ moduleName);
this._modules[moduleName] = module;
var moduleNameSegments = moduleName.split(/\./);
var last = moduleNameSegments.pop();
var p = WM;
for (var i in moduleNameSegments) {
var s = moduleNameSegments[i];
if (p[s] == undefined)
p[s] = {};
if (typeof p[s] != "object")
throw Error("module name '" + moduleName + "' contains a name overwriting existing object: " + s);
p = p[s];
}
p[last] = module;
// TODO signal module observers
};
// module loader using iframe, avoid redundant loading
this.loadModule = function(moduleName) {
var defer = $.Deferred();
// TODO check _modules first
var module = this._modules[moduleName];
if (module) {
defer.resolve(module);
return defer;
}
var sandbox = this.addHTMLElement("iframe", {
attributes: {
name: moduleName + "-WebModulesLoaderSandbox",
style: "display: none;"
}
});
var w = sandbox.contentWindow;
w.moduleName = moduleName;
w.moduleBase = WM.pathForModuleName(moduleName);
// top-half
WM.addHTMLElement("script", {
properties: {
src: "webmodules/core.js",
onload: function(){
// module body
WM.addHTMLElement("script", {
properties: {
src: w.moduleBase + "/module.js",
onload: function() {
// bottom-half
var m = w.module;
WM.registerModule(moduleName, m);
if (m.onload)
setTimeout(m.onload, 1);
// XXX unviable: sandbox.parentNode.removeChild(sandbox);
w.document.clear();
defer.resolve(m);
}
},
document: w.document
});
}
},
document: w.document
});
return defer;
};
return this;
}
WM = new WebModules();

// load jQuery first if not available
if (window.$ && $.fn.jquery >= "1.6")
WM.init();
else {
WM.addHTMLElement("script", {
properties: {
src: "http://code.jquery.com/jquery-latest.min.js",
onload: WM.init
},
target: document.head
});
}

// url router
// TODO load the current url (from full path, and hash too?)
// TODO function to jump to a new URL and subsequently route to some module without reloading page w/ HTML5 history API


// presentations
// TODO function for adding+showing/hiding html fragment from URL (within a new iframe?)


// data sources
// TODO function for accessing (+ loading if needed) cached primitive data, e.g. HTML, JSON, XML, ... w/ XHR
// TODO support higher-level data source modules that provide complex operations on them? or should these be just ordinary modules without presentation stuff?
Oops, something went wrong.

0 comments on commit 92fabee

Please sign in to comment.