Skip to content

Commit

Permalink
Drag and drop with offset works
Browse files Browse the repository at this point in the history
  • Loading branch information
marcuswestin committed Dec 28, 2011
1 parent 44e8216 commit 2fcc7fd
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 124 deletions.
13 changes: 2 additions & 11 deletions examples/drag.fun
@@ -1,12 +1,3 @@
import Mouse

<div style={ width:100, height:100, background:'steelblue', position:'absolute', top:Mouse.y, left:Mouse.x }/>

<pre>
{ Mouse:{ x:Mouse.x, y:Mouse.y, isDown:Mouse.isDown } }
</pre>

<pre>
"Mouse="Mouse
</pre>

<div id="output1" style={ position:"absolute", top:Mouse.y, left:Mouse.x }/>
<div id="output2" style={ position:"absolute", top:Mouse.y + 50, left:Mouse.x + 50 }/>
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -18,7 +18,7 @@
"require": "0.4.x"
},
"devDependencies": {
"nodeunit": "~0.5.1",
"nodeunit": "~0.6.4",
"zombie": "~0.12.9"
},
"engines": {
Expand Down
10 changes: 5 additions & 5 deletions src/Modules/Mouse.fun
Expand Up @@ -8,16 +8,16 @@ var Mouse = {
/* Mouse.x and Mouse.y
*********************/
fun.on(document, 'mousemove', function(e) {
fun.set('Mouse.x', { type:'VALUE_LITERAL', value:e.clientX })
fun.set('Mouse.y', { type:'VALUE_LITERAL', value:e.clientY })
_variableName_Mouse.set(['x'], fun.expressions.number(e.clientX))
_variableName_Mouse.set(['y'], fun.expressions.number(e.clientY))
})

/* Mouse.isDown
**************/
fun.on(document, 'mousedown', function() {
fun.set('Mouse.isDown', { type:'VALUE_LITERAL', value:true })
_variableName_Mouse.set(['isDown'], fun.expressions.logic(true))
})
fun.on(document, 'mouseup', function() {
fun.set('Mouse.isDown', { type:'VALUE_LITERAL', value:false })
_variableName_Mouse.set(['isDown'], fun.expressions.logic(false))
})
</script>
</script>
33 changes: 12 additions & 21 deletions src/compiler.js
Expand Up @@ -67,7 +67,7 @@ exports.compile = function(resolvedAST) {
modules: map(resolvedAST.imports, function(module, name) {
return boxComment('Module: ' + name) + '\n' + exports.compileRaw(module)
}).join('\n\n\n')
}) + '})();'
}) + '\n})();'
}

exports.compileRaw = function(ast, rootHook) {
Expand Down Expand Up @@ -184,7 +184,7 @@ var _handleXMLAttribute = function(nodeHookName, ast, staticAttrs, dynamicCode,
// do nothing
} else if (name == 'style') {
assert(ast, value.type == 'OBJECT_LITERAL' || value.type == 'REFERENCE', 'The style attribute should be an object, e.g. style={ color:"red" }')
dynamicCode.push(_compileStyleAttribute(nodeHookName, ast, value))
dynamicCode.push('fun.reflectStyles('+nodeHookName+', '+runtimeValue(value, true)+')')
} else if (name == 'data') {
// TODO Individual tags should define how to handle the data attribute
_handleDataAttribute(nodeHookName, ast, dynamicCode, value, attribute.dataType)
Expand All @@ -198,26 +198,13 @@ var _handleXMLAttribute = function(nodeHookName, ast, staticAttrs, dynamicCode,
}
}

// modifies dynamicCode
var _compileStyleAttribute = function(hookName, ast, value) {
switch(value.type) {
case 'OBJECT_LITERAL':
return 'fun.reflectStyles('+hookName+', '+q(value.content)+')'
case 'REFERENCE':
halt(ast, 'TODO implement style attribute by reference')
default:
halt(ast, 'Unknown style attribute type ' + value.type)
}
}

// modifies dynamicCode
var _handleDataAttribute = function(nodeHookName, ast, dynamicCode, value, dataType) {
dynamicCode.push(code(
'fun.reflectInput({{ hookName }}, {{ value }})',
{
hookName: nodeHookName,
value: runtimeValue(value),
type: q(dataType || 'string')
value: runtimeValue(value)
}))
}

Expand Down Expand Up @@ -353,7 +340,7 @@ var compileDeclaration = function(declaration) {
return code('var {{ name }} = fun.expressions.variable({{ initialContent }})', {
name:variableName(declaration.name),
type:getType(declaration.initialValue),
initialContent:runtimeValue(declaration.initialValue)
initialContent:runtimeValue(declaration.initialValue, true)
})
case 'TEMPLATE': return compileTemplateDeclaration(declaration)
case 'HANDLER': return compileHandlerDeclaration(declaration)
Expand Down Expand Up @@ -504,19 +491,22 @@ var indent = function(fn /*, arg1, ... argN */) {
return result
}

var runtimeValue = function(ast) {
var runtimeValue = function(ast, isVariable) {
switch(ast.type) {
case 'VALUE_LITERAL': return code('fun.expressions.{{ type }}({{ value }})', { type:getType(ast), value:q(ast.value) })
case 'VALUE_LITERAL':
return isVariable
? inlineCode('fun.expressions.variable({{ content }})', { content:runtimeValue(ast, false) })
: inlineCode('fun.expressions.{{ type }}({{ value }})', { type:getType(ast), value:q(ast.value) })
case 'REFERENCE': return code('fun.expressions.reference({{ name }}, {{ chain }})', { name:variableName(ast.name), chain:q(ast.chain) })
case 'ITERATOR': return ast.runtimeName
case 'ARGUMENT': return ast.runtimeName
case 'LIST_LITERAL': return q(ast.content)
case 'OBJECT_LITERAL':
return code('fun.expressions.dictionary({ {{ content }} })', {
content:map(ast.content, function(value, name) { return name+':'+runtimeValue(value) }).join(', ')
content:map(ast.content, function(value, name) { return name+':'+runtimeValue(value, isVariable) }).join(', ')
})
case 'COMPOSITE':
return code('fun.expressions.composite({{ left }}, {{ operator }}, {{ right }})', {
return code('fun.expressions.composite({{ left }}, "{{ operator }}", {{ right }})', {
left:runtimeValue(ast.left),
operator:ast.operator,
right:runtimeValue(ast.right)
Expand All @@ -533,6 +523,7 @@ var getType = function(ast) {
case 'VALUE_LITERAL':
assert(ast, !!_types[typeof ast.value], 'Unknown value literal type')
return _types[typeof ast.value]
case 'OBJECT_LITERAL': return 'dictionary'
default:
halt(ast, 'Unknown getType type')
}
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/expressions.js
Expand Up @@ -108,7 +108,7 @@ var variable = module.exports.variable = proto(function(content) { if (!content.
asString:function() { return this.content.asString() },
equals:function(that) { return this.content.equals(that) },
observe:function(chain, callback) {
var namespace = chain.join('.')
var namespace = chain ? chain.join('.') : ''
if (!this.observers[namespace]) { this.observers[namespace] = {} }
var uniqueID = 'u'+_unique++
this.observers[namespace][uniqueID] = callback
Expand Down
46 changes: 17 additions & 29 deletions src/runtime/library.js
@@ -1,4 +1,5 @@
var expressions = require('./expressions')
var expressions = require('./expressions'),
each = require('std/each')


;(function() {
Expand Down Expand Up @@ -119,39 +120,26 @@ var expressions = require('./expressions')
*****/
fun.attr = function(name, key, value) {
var match
if (match = key.match(/^style\.(\w+)$/)) {
fun.style(name, match[1], value)
} else {
_hooks[name].setAttribute(key, value)
}
if (match = key.match(/^style\.(\w+)$/)) { fun.setStyle(name, match[1], value) }
else { _hooks[name].setAttribute(key, value) }
}

// fun.style(hook, 'color', '#fff') or fun.style(hook, { color:'#fff', width:100 })
fun.style = function(name, key, value) {
if (typeof key == 'object') {
for (var styleKey in key) { fun.style(name, styleKey, key[styleKey]) }
} else {
if (typeof value == 'number') { value = value + 'px' }
if (key == 'float') { key = 'cssFloat' }
_hooks[name].style[key] = value
}
// fun.style(hook, 'color', '#fff')
fun.setStyle = function(hookName, key, value) {
var rawValue = value.evaluate().asString()
if (value.type == 'number' || rawValue.match(/\d+/)) { rawValue = rawValue + 'px' }
if (key == 'float') { key = 'cssFloat' }
_hooks[hookName].style[key] = rawValue
}

fun.reflectStyles = function(hookName, values) {
for (var key in values) {
var value = values[key]
switch(value.type) {
case 'VALUE_LITERAL':
fun.style(hookName, key, value.value)
break
case 'REFERENCE':
(function(value, key) {
fun.observe(namespace(value), function() {
fun.style(hookName, key, fun.get(namespace(value)).value)
})
})(value, key)
}
}
if (values.type != 'reference')
// TODO detect when the dictionary mutates (values added and removed)
each(values.content, function(val, key) {
val.observe(null, function() {
fun.setStyle(hookName, key, val)
})
})
}

fun.on = function(element, eventName, handler) {
Expand Down
130 changes: 74 additions & 56 deletions test/tests/test-4-compiler.js
Expand Up @@ -8,15 +8,16 @@ var std = require('std'),
zombie = require('zombie'),
compilerServerPort = 9797,
slice = require('std/slice'),
bind = require('std/bind')
bind = require('std/bind'),
each = require('std/each')

var currentTestCode, compilerServer

startCompilerServer()

test('a number and a string').code(
'<div>"Hello " 1</div>')
.htmlIs('div', '<div>Hello 1</div>')
'<div id="output">"Hello " 1</div>')
.textIs('#output', 'Hello 1')

test('clicking a button updates the UI').code(
'var foo = "bar"',
Expand All @@ -28,6 +29,38 @@ test('clicking a button updates the UI').code(
.click('#button')
.textIs('#output', 'cat')

test('object literals').code(
'var foo = { nested: { bar:1 } }',
'<div id="output">foo foo.nested foo.nested.bar</div>'
)
.textIs('#output', '{ nested:{ bar:1 } }{ bar:1 }1')

test('divs follow mouse').code(
'import Mouse',
'<div id="output1" style={ position:"absolute", top:Mouse.y, left:Mouse.x }/>',
'<div id="output2" style={ position:"absolute", top:Mouse.y + 50, left:Mouse.x + 50 }/>'
)
.moveMouse(100, 100)
.positionIs('#output1', 100, 100)
.positionIs('#output2', 150, 150)

// test('changing object literals').code(
// 'var foo = { a:1 }',
// '<div id="output">',
// ' { foo: { a:foo.a } }',
// ' { a:foo.a }',
// ' foo',
// '</div onclick=handler(){ foo.a.set(2) }>'
// )
// .textIs('#output', '{ foo:{ a:1 } }{ a:1 }{ a:1 }')
// .click('#output')
// .textIs('#output', '{ foo:{ a:2 } }{ a:2 }{ a:2 }')






// test('value changes type')
// .compile(
// 'let foo = "bar"',
Expand All @@ -41,34 +74,6 @@ test('clicking a button updates the UI').code(
// .textIs('#value', 1)
// .textIs('#Type', 'Number')

test('object literals').code(
'var foo = { nested: { bar:1 } }',
'<div id="output">foo foo.nested foo.nested.bar</div>'
)
.textIs('#output', '{ nested:{ bar:1 } }{ bar:1 }1')

test('drag square with mouse').code(
'import Mouse',
'<div style={ width:100, height:100, background:"red", position:"absolute", top:Mouse.y, left:Mouse.x }/>'
)

test('changing object literals').code(
'var foo = { a:1 }',
'<div id="output">',
' { foo: { a:foo.a } }',
' { a:foo.a }',
' foo',
'</div onclick=handler(){ foo.a.set(2) }>'
)
.textIs('#output', '{ foo:{ a:1 } }{ a:1 }{ a:1 }')
.click('#output')
.textIs('#output', '{ foo:{ a:2 } }{ a:2 }{ a:2 }')

// test('drag square with mouse with composite offset').code(
// 'import Mouse',
// '<div style={ width:100, height:100, background:"red", position:"absolute", top:Mouse.y + 50, left:Mouse.x + 50 }/>'
// )

// test('handler with logic')
// .code(
// 'let cat = "hi1"',
Expand All @@ -90,7 +95,8 @@ test('changing object literals').code(
******/
var isFirstTest = true
function test(name) {
return {
var actionHandlers = createActionHandlers()
var testObj = {
code: function() {
var code = std.slice(arguments).join('\n'),
actions = this._actions = []
Expand Down Expand Up @@ -122,39 +128,51 @@ function test(name) {
try {
actionHandlers[action.name].apply(this, [assert, browser, nextAction].concat(action.args))
} catch(e) {
console.log('compiler threw:', e.stack, '\nThe code that caused the throw:\n', currentTestCode)
console.log('compiler threw -', e.stack, '\n\nThe test code that caused the error:\n', currentTestCode)
}
}
nextAction()
})
}
return this
},
htmlIs: chainableAction('htmlIs'),
textIs: chainableAction('textIs'),
click: chainableAction('click')
}
}

function chainableAction(name) {
return function() {
this._actions.push({ name:name, args:slice(arguments) })
return this
}
}

each(actionHandlers, function(handler, name) {
testObj[name] = function chainableAction() {
this._actions.push({ name:name, args:slice(arguments) })
return this
}
})

return testObj
}

var actionHandlers = {
click: function(assert, browser, next, selector) {
var target = browser.querySelector(selector)
browser.fire('click', target, next)
},
htmlIs: function(assert, browser, next, selector, expectedHTML) {
assert.deepEqual(expectedHTML, browser.html(selector))
next()
},
textIs: function(assert, browser, next, selector, expectedHTML) {
assert.deepEqual(expectedHTML, browser.text(selector))
next()
function createActionHandlers() {
return {
// Events
click: function(assert, browser, next, selector) {
var target = browser.querySelector(selector)
browser.fire('click', target, next)
},
moveMouse: function(assert, browser, next, clientX, clientY) {
browser.fire('mousemove', browser.document, { attributes: {clientX:clientX, clientY:clientY} }, next)
},
// Asserts
htmlIs: function(assert, browser, next, selector, expectedHTML) {
assert.deepEqual(expectedHTML, browser.html(selector))
next()
},
textIs: function(assert, browser, next, selector, expectedHTML) {
assert.deepEqual(expectedHTML, browser.text(selector))
next()
},
positionIs: function(assert, browser, next, selector, x, y) {
var style = browser.querySelector(selector).style
assert.deepEqual(style.left, x+'px')
assert.deepEqual(style.top, y+'px')
next()
}
}
}

Expand Down

0 comments on commit 2fcc7fd

Please sign in to comment.