Skip to content
Browse files

added sub-views and fixed some bugs

  • Loading branch information...
1 parent a626c43 commit c23f0295194544430b84fa011acce4cfab78e9c4 @collin committed
View
2 lib/alpha_simprini/client.coffee
@@ -10,7 +10,7 @@ Client.require """
binding/model binding/field binding/input binding/select
binding/check_box binding/edit_line binding/one binding/many
- views/panel views/region
+ views/panel views/region views/dialog
models/targets
View
24 lib/alpha_simprini/client/application.coffee
@@ -3,29 +3,43 @@ Taxi = require("taxi")
{each} = require("underscore")
jwerty = require("jwerty").jwerty
domready = $ = require("jquery")
+_ = require "underscore"
AS.Application = AS.Object.extend ({def, include}) ->
include Taxi.Mixin
- def initialize: () ->
+ def initialize: (config={}) ->
+ _.extend(this, config)
@params = AS.params
+ @el ?= $("body")
@god_given_key_handlers()
domready =>
@boot()
- @el ?= $("body")
def boot: ->
def god_given_key_handlers: ->
handlers =
- '': 'esc'
+ '': 'escape'
'⌘+↩': 'accept'
'': 'delete'
+ #TODO: add to test suite
+ "": "open"
+ "up": "up"
+ "down": "down"
+ "home": "first"
+ "end": "last"
+ "left": "left"
+ "right": "right"
+ "tab": "indent"
+ "shift+tab": "dedent"
+ "[a-z]/[0-9]/shift+[a-z]": "alphanum"
+
+
each handlers, (trigger, key) =>
- jwerty.key key, (event) =>
- @trigger(trigger, event)
+ jwerty.key key, ( (event) => @trigger(trigger, event) ), @el
def view: (constructor, options={}) ->
options.application = this
View
18 lib/alpha_simprini/client/binding/field.coffee
@@ -1,5 +1,6 @@
AS = require("alpha_simprini")
_ = require "underscore"
+jQuery = require "jquery"
AS.Binding.Field = AS.Binding.extend ({def}) ->
@@ -14,16 +15,29 @@ AS.Binding.Field = AS.Binding.extend ({def}) ->
def setContent: ->
if @fn
+ @content.remove()
@container.empty()
+
+ fieldValue = @fieldValue()
+ # FIXME: this turned into a string :(
+ return if fieldValue is null
+ return if fieldValue is undefined
+ return if fieldValue is "null"
+ return if fieldValue is "undefined"
@bindingGroup.unbind()
@context.withinBindingGroup @bindingGroup, =>
@context.withinNode @container, =>
- @fn.call(@context)
+ made = if _.include(fieldValue.model?.constructor.ancestors, AS.Model)
+ value = AS.ViewModel.build(@context, fieldValue.model)
+ made = @fn.call(@context, value, AS.Binding.Model.new(@context, value, @container))
+ else
+ @fn.call(@context)
+ @content.push if made instanceof jQuery then made[0] else made
else
@content.text @fieldValue()
def makeContent: ->
- if @fn
+ @content = if @fn
@context.$ []
else
@context.$ @context.span()
View
2 lib/alpha_simprini/client/binding/many.coffee
@@ -95,7 +95,7 @@ AS.Binding.Many = AS.Binding.extend ({def}) ->
content.push made
# FIXME: paint!
- # binding.paint()
+ binding.paint()
@contents[item.cid] = content
return content
View
1 lib/alpha_simprini/client/binding/model.coffee
@@ -1,5 +1,6 @@
AS = require("alpha_simprini")
_ = require "underscore"
+$ = require "jquery"
AS.Binding.Model = AS.Object.extend ({def}) ->
def initialize: (@context, @model, @content=$([])) ->
View
3 lib/alpha_simprini/client/binding_group.coffee
@@ -25,8 +25,7 @@ AS.BindingGroup = AS.Object.extend ({def}) ->
handler: handler
context: context
- def addChild: ->
- child = AS.BindingGroup.new()
+ def addChild: (child=AS.BindingGroup.new())->
@children.push child
return child
View
43 lib/alpha_simprini/client/view.coffee
@@ -6,21 +6,28 @@ fleck = require("fleck")
AS.View = AS.DOM.extend ({delegate, include, def, defs}) ->
include Taxi.Mixin
+ delegate 'addClass', 'removeClass', 'show', 'hide', 'html', to: "el"
+
def tagName: "div"
def _ensureElement: -> @el ?= @$(@buildElement())
def initialize: (config={}) ->
+ config.el = @$(config.el) if config.el and !(config.el.jquery)
+
@cid = _.uniqueId("c")
for key, value of config
- if value instanceof AS.Model
- @[key] = AS.ViewModel.build(this, value)
+ if value?.model instanceof AS.Model
+ @[key] = AS.ViewModel.build(this, value.model)
else
@[key] = value
@bindingGroup = AS.BindingGroup.new()
@_ensureElement()
+
+ @currentNode = @el[0]
+ @childViews = []
@delegateEvents()
def append: (view) -> @el.append view.el
@@ -55,7 +62,6 @@ AS.View = AS.DOM.extend ({delegate, include, def, defs}) ->
classes.join(" ")
-
def baseAttributes: ->
attrs =
class: @klassString()
@@ -63,6 +69,37 @@ AS.View = AS.DOM.extend ({delegate, include, def, defs}) ->
def buildElement: ->
@currentNode = @[@tagName](@baseAttributes())
+ def view: (constructor, options={}) ->
+ options.application = @application
+ options.parentView = this
+ view = constructor.new(options)
+ @childViews.push(view)
+ @bindingGroup.addChild(view)
+ view.el[0]
+
+ def descendantViews: (views=[], constructor) ->
+ for view in @childViews
+ if constructor
+ views.push view if view instanceof constructor
+ else
+ views.push view
+ view.descendantViews(views, constructor)
+
+ views
+
+ def removeChild: (child) ->
+ @childViews = _.without(@childViews, child)
+
+ def unbind: ->
+ @parentView.removeChild(this)
+ @el.remove()
+
+ def binding: (bindable, fnOrElement) ->
+ if bindable instanceof AS.Collection
+ AS.Binding.Many.new(this, bindable, bindable, fnOrElement)
+ else if bindable instanceof AS.Model
+ AS.Binding.Model.new(this, bindable, fnOrElement)
+
def delegateEvents: () ->
if @events
@standardEvents = AS.ViewEvents.new(this, @events)
View
5 lib/alpha_simprini/client/view_model.coffee
@@ -2,6 +2,7 @@ AS = require "alpha_simprini"
_ = require "underscore"
jQuery = require "jQuery"
Pathology = require "pathology"
+Taxi = require("taxi")
AS.ViewModel = AS.Object.extend ({def, defs}) ->
@@ -21,14 +22,12 @@ AS.ViewModel = AS.Object.extend ({def, defs}) ->
for name, property of model.properties
klass.bindables[name] = switch property.constructor
- when AS.Model.Field
+ when AS.Model.Field, Taxi.Property, AS.Model.VirtualProperty
AS.Binding.Field
when AS.Model.BelongsTo, AS.Model.EmbedsOne, AS.Model.HasOne
AS.Binding.One
when AS.Model.HasMany, AS.Model.EmbedsMany
AS.Binding.Many
- # when AS.Model.HasOne
- # AS.Binding.HasOne
for method in AS.instanceMethods(model)
continue if _.include _.keys(Pathology.Object::), method
View
12 lib/alpha_simprini/core/model.coffee
@@ -9,6 +9,11 @@ AS.Model = AS.Object.extend ({def, include}) ->
def initialize: (attributes={}) ->
@model = this
+ if id = attributes.id
+ delete attributes.id
+ @setId(id)
+ else if !@id
+ @setId(AS.uniq())
@set(attributes)
def set: (attributes) ->
@@ -19,14 +24,17 @@ AS.Model = AS.Object.extend ({def, include}) ->
# assert @[key].set
@[key].set(value)
- def setId: (id=AS.uniq()) ->
+ def setId: (id) ->
if @id
delete AS.All.byId[@id]
delete AS.All.byIdRef["#{@id}-#{@constructor.path()}"]
@id = id
@idRef = "#{@id}-#{@constructor.path()}"
- @cid = @idRef or uniqueId("c")
+
+ # NEVER CHANGE THE CID
+ @cid ?= @idRef or uniqueId("c")
+
AS.All.byCid[@cid] = AS.All.byId[@id] = AS.All.byIdRef[@idRef] = this
# Don't trigger 'change', this must be specifically listened for.
View
1 lib/alpha_simprini/core/model/rest.coffee
@@ -79,6 +79,7 @@ AS.Model.REST = AS.Module.extend ({delegate, include, def, defs}) ->
@constructor.mappings()[key].sideloadData(embed, references) for embed in embeds
references.each (model, ids) -> model.resolveReferences(ids)
+ @trigger("ready")
return this
defs sideloadData: (modelData, references) ->
View
2 lib/alpha_simprini/core/models/radio_selection_model.coffee
@@ -1,6 +1,6 @@
AS = require "alpha_simprini"
AS.Models.RadioSelectionModel = AS.Model.extend ({def}) ->
- @hasOne 'selected'
+ @property 'selected'
def initialize: ->
@_super()
View
9 lib/alpha_simprini/core/properties/has_one.coffee
@@ -28,7 +28,16 @@ AS.Model.HasOne.Instance = AS.Model.Field.Instance.extend ({def}) ->
value = @options.model().new(value)
@value?.unbind(@namespace)
+
+ # TODO: test inverse
+ if @value and @options.inverse and @object[@options.inverse]
+ @value[@options.inverse].remove(@object)
+
@value = value
+
+ if @value and @options.inverse and @object[@options.inverse]
+ @value[@options.inverse].add(@object)
+
@value?.bind "all#{@namespace}", _.bind(@trigger, this)
@object.trigger("change")
@object.trigger("change:#{@options.name}")
View
14 lib/alpha_simprini/core/properties/virtual_property.coffee
@@ -16,11 +16,15 @@ AS.Model.VirtualProperty.Instance = AS.Property.Instance.extend ({def}) ->
for dependency in @options.dependencies
@object.bind "change:#{dependency}", _.bind @triggerFor, this, dependency
- def set: () -> throw "Can't set a VirtualProperty name: #{@options.name}, dependencies: #{@options.dependencies.join(',')}"
+ def set: (value) ->
+ if set = @options.getSet.set
+ set.call(@object, value)
+ else
+ throw "Can't set a VirtualProperty name: #{@options.name}, dependencies: #{@options.dependencies.join(',')}"
def get: -> @cached = @compute()
- def compute: (args) -> @options.fn.call(@object)
+ def compute: (args) -> @options.getSet.get.call(@object)
def triggerFor: (dependency) ->
computed = @compute()
@@ -36,4 +40,8 @@ AS.Model.VirtualProperty.Instance = AS.Property.Instance.extend ({def}) ->
AS.Model.defs virtualProperties: (dependencies..., properties) ->
for name, fn of properties
- AS.Model.VirtualProperty.new(name, this, dependencies: dependencies, fn: fn)
+ if _.isFunction(fn)
+ getSet = {get: fn}
+ else
+ getSet = fn
+ AS.Model.VirtualProperty.new(name, this, dependencies: dependencies, getSet: getSet)
View
30 test/client/application.coffee
@@ -1,17 +1,31 @@
{AS, $, _, sinon, jwerty} = require require("path").resolve("./test/client_helper")
exports.Application =
setUp: (callback) ->
- @app = AS.Application.new()
+ @app = AS.Application.new(el: @el = $("<div>"))
callback()
"attaches global key handlers w/jwerty": (test) ->
- test.expect 3
- @app.bind "esc", (event) -> test.ok event
- @app.bind "accept", (event) -> test.ok event
- @app.bind "delete", (event) -> test.ok event
- jwerty.fire "esc"
- jwerty.fire "cmd+enter"
- jwerty.fire "backspace"
+ events = [
+ "open", "up", "down", "first", "last", "left",
+ "right", "indent", "dedent", "alphanum"
+ "escape", "accept", "delete"
+ ]
+
+ for event in events
+ do (event) =>
+ @app.bind event, (_event) -> test.ok(_event)
+
+ triggers = [
+ "esc", "cmd+enter", "backspace", "enter", "up", "down",
+ "home", "end", "left", "right", "tab", "shift+tab"
+ "a", "b", "C", "D", "1", "2"
+ ]
+ test.expect triggers.length
+
+
+ for trigger in triggers
+ jwerty.fire trigger, @el
+
test.done()
"initializes views into the application context": (test) ->
View
18 test/client/binding/field.coffee
@@ -9,6 +9,24 @@ exports.Binding =
test.equal binding.container.find("span").text(), "value"
test.done()
+ "clears content when value undefined": (test) ->
+ [mocks, binding] = mock_binding AS.Binding.Field,
+ fn: ->
+ @h1 -> @span "fn value"
+
+ binding.model.field.set(undefined)
+ test.equal "", binding.container.html()
+ test.done()
+
+ "clears content when value null": (test) ->
+ [mocks, binding] = mock_binding AS.Binding.Field,
+ fn: ->
+ @h1 -> @span "fn value"
+
+ binding.model.field.set(null)
+ test.equal "", binding.container.html()
+ test.done()
+
"updates content when model changes": (test) ->
[mocks, binding] = mock_binding(AS.Binding.Field)
binding.model.field.set("new value")
View
20 test/client/binding/many.coffee
@@ -12,7 +12,7 @@ exports.Binding =
items.add()
- content_fn = (thing) -> @div id: thing.cid
+ content_fn = (thing) -> @div id: thing.cid.replace(".", "-")
[mocks, binding] = mock_binding(AS.Binding.Many, field: model.items, model: model, fn: content_fn)
@@ -23,14 +23,14 @@ exports.Binding =
"creates initial collection dom": (test) ->
test.expect 3
@items.each (item) =>
- test.ok @binding.container.find("##{item.cid}").is("div")
+ test.ok @binding.container.find("##{item.cid.replace(".", "-")}").is("div")
test.done()
"adds additional dom elements when items added to collection": (test) ->
test.expect 1
item = @items.add()
- test.ok @binding.container.find("##{item.cid}").is("div")
+ test.ok @binding.container.find("##{item.cid.replace(".", "-")}").is("div")
test.done()
@@ -38,14 +38,14 @@ exports.Binding =
test.expect 1
item = @items.add({}, at: 0)
- test.ok @binding.container.children(":first").is("##{item.cid}")
+ test.ok @binding.container.children(":first").is("##{item.cid.replace(".", "-")}")
test.done()
"removes dom elements when item removed from collection": (test) ->
item = @items.at(0)
@items.remove item
- test.ok @binding.container.find("##{item.cid}")[0] is undefined
+ test.ok @binding.container.find("##{item.cid.replace(".", "-")}")[0] is undefined
test.done()
HasManyWithFilter:
@@ -53,7 +53,7 @@ exports.Binding =
model = BoundModel.new()
items = model.items
- content_fn = (thing) -> @div id: thing.cid
+ content_fn = (thing) -> @div id: thing.cid.replace(".", "-")
[mocks, binding] = mock_binding(AS.Binding.Many,
field: items,
@@ -71,9 +71,9 @@ exports.Binding =
two = @items.add field: "false"
three = @items.add field: "43"
- test.equal @binding.container.find("##{one.cid}")[0].id, one.cid
- test.equal @binding.container.find("##{two.cid}")[0], undefined
- test.equal @binding.container.find("##{three.cid}")[0].id, three.cid
+ test.equal @binding.container.find("##{one.cid.replace(".", "-")}")[0].id, one.cid.replace(".", "-")
+ test.equal @binding.container.find("##{two.cid.replace(".", "-")}")[0], undefined
+ test.equal @binding.container.find("##{three.cid.replace(".", "-")}")[0].id, three.cid.replace(".", "-")
test.done()
"moves items into place in the collection when their values change": (test) ->
@@ -83,7 +83,7 @@ exports.Binding =
two.field.set("43")
- test.equal @binding.container.children()[1].id, two.cid
+ test.equal @binding.container.children()[1].id, two.cid.replace(".", "-")
test.done()
View
4 test/client/binding/one.coffee
@@ -7,7 +7,7 @@ exports.Binding =
setUp: (callback) ->
@model = BoundModel.new( owner: BoundModel.new() )
- content_fn = (thing) -> @div id: thing.cid
+ content_fn = (thing) -> @div id: thing.cid.replace('.', '-')
[mocks, binding] = mock_binding(AS.Binding.One, field: @model.owner, model: @model, fn: content_fn)
@@ -15,7 +15,7 @@ exports.Binding =
callback()
"creates initial dom": (test) ->
- test.ok @binding.container.find("##{@model.owner.get().cid}").is("div")
+ test.ok @binding.container.find("##{@model.owner.get().cid.replace('.', '-')}").is("div")
test.done()
"clears dom when field is null": (test) ->
View
81 test/client/view.coffee
@@ -21,8 +21,10 @@ exports.View =
test.equal AS.View.new(this: "that").this, "that"
test.done()
- "turns Model options into ASViewModels": (test) ->
- test.ok AS.View.new(it: new AS.Model).it instanceof AS.ViewModel
+ "turns Model options into AS.ViewModels": (test) ->
+ it = AS.Model.new()
+ view = AS.View.new(it:it)
+ test.ok view.it instanceof AS.ViewModel
test.done()
"has a root binding group": (test) ->
@@ -37,3 +39,78 @@ exports.View =
test.equal view.pluralize("duck", -1), "duck"
test.equal view.pluralize("cat", -4), "cats"
test.done()
+
+ "delegates view methods to @el": (test) ->
+ methods = ['addClass', 'removeClass', 'show', 'hide', 'html']
+ test.expect methods.length
+ view = AS.View.new()
+ view.el = el = {}
+
+ for method in methods
+ el[method] = -> test.ok(true)
+ view[method]()
+
+ test.done()
+
+ "allows view element to be set as an option in the constructor": (test) ->
+ el = $("<div>")
+ view = AS.View.new(el:el)
+ test.equal el[0], view.currentNode
+ test.done()
+
+ "stashes childViews": (test) ->
+ test.deepEqual [], AS.View.new().childViews
+ test.done()
+
+ "views have a 'view' method to create child views": (test) ->
+ view = AS.View.new()
+ returned = view.view AS.View, el: subEl = view.div()
+ test.equal subEl, returned
+ test.equal subEl, view.childViews[0].el[0]
+
+ # child view is added to the bindingGroup's children so 'unbind'
+ # will be called on the view object.
+ test.equal view.childViews[0], view.bindingGroup.children.reverse()[0]
+
+ test.done()
+
+ "when view is unbound it is removed from it's parents childViews and from the DOM": (test) ->
+ view = AS.View.new()
+ view.view(AS.View, el: view.div(-> @h1()))
+ view.bindingGroup.unbind()
+ test.deepEqual [], view.childViews
+ test.equal "", view.html()
+ test.done()
+
+ "binding()":
+ "creates a binding for a collection": (test) ->
+ view = AS.View.new()
+ collection = AS.Collection.new()
+ test.ok view.binding(collection, ->).constructor is AS.Binding.Many
+ test.done()
+
+ "creates a binding for a model": (test) ->
+ view = AS.View.new()
+ model = AS.Model.new()
+ test.ok view.binding(model).constructor is AS.Binding.Model
+ test.done()
+
+
+ "descendantViews()":
+ "returns all descendantViews": (test) ->
+ view = AS.View.new()
+ view.view AS.View
+ view.childViews[0].view AS.View
+
+ test.equal 2, view.descendantViews().length
+ test.done()
+
+ "filters descendantViews by constructor": (test) ->
+ NS.SubView = AS.View.extend()
+ view = AS.View.new()
+ view.view NS.SubView
+ view.childViews[0].view AS.View
+
+ test.equal 1, view.descendantViews(null, NS.SubView).length
+ test.done()
+
View
1 test/core/model/rest.coffee
@@ -2,7 +2,6 @@
exports.setUp = coreSetUp
AS.part("Core").require("model/rest")
-console.log AS.Model.REST.toString()
Rested = NS.Rested = AS.Model.extend ({delegate, include, def, defs}) ->
include AS.Model.REST
View
9 test/core/properties/virtual_property.coffee
@@ -9,6 +9,9 @@ NS.Virtualized = AS.Model.extend ->
@virtualProperties "name",
virtualA: -> Math.random()
virtualB: -> "steady as she goes"
+ virtualC:
+ get: -> @name.get()
+ set: (value) -> @name.set(value)
exports.VirtualProperty =
"is a virtual": (test) ->
@@ -33,6 +36,12 @@ exports.VirtualProperty =
o.name.set "Second Name"
test.done()
+ "with a setter": (test) ->
+ o = NS.Virtualized.new()
+ o.virtualC.set("vname")
+ test.equal "vname", o.name.get()
+ test.done()
+
"bindPath":
"may be used in path bindings": (test) ->
o = NS.Virtualized.new()
View
1 test/helper.coffee
@@ -5,6 +5,7 @@ _ = exports._ = require "underscore"
sinon = exports.sinon = require "sinon"
$ = exports.$ = require "jquery"
exports.jwerty = require("jwerty").jwerty
+AS.part("Core").require("model/share")
# AS.suppress_logging()
require("nodeunit").assert.AssertionError.prototype.toString = ->

0 comments on commit c23f029

Please sign in to comment.
Something went wrong with that request. Please try again.