Skip to content
Toggle navigation
Toggle navigation
This project
Loading...
Sign in
brainfood
/
rivets
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Graphs
Network
Create a new issue
Commits
Issue Boards
Files
Commits
Network
Compare
Branches
Tags
d0b185b9
authored
2012-10-23 18:20:16 -0700
by
Michael Richards
Browse Files
Options
Browse Files
Tag
Download
Plain Diff
Merge pull request #80 from mikeric/advanced-custom-binders-api
Advanced custom binders API
2 parents
4093da62
9b103172
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
136 additions
and
154 deletions
spec/rivets/binding.js
spec/rivets/routines.js
src/rivets.coffee
spec/rivets/binding.js
View file @
d0b185b
...
...
@@ -18,8 +18,8 @@ describe('Rivets.Binding', function() {
model
=
binding
.
model
;
});
it
(
'gets assigned the
routine function
matching the identifier'
,
function
()
{
expect
(
binding
.
routine
).
toBe
(
rivets
.
routine
s
.
text
);
it
(
'gets assigned the
proper binder routine
matching the identifier'
,
function
()
{
expect
(
binding
.
binder
.
routine
).
toBe
(
rivets
.
binder
s
.
text
);
});
describe
(
'bind()'
,
function
()
{
...
...
@@ -72,50 +72,24 @@ describe('Rivets.Binding', function() {
describe
(
'set()'
,
function
()
{
it
(
'performs the binding routine with the supplied value'
,
function
()
{
spyOn
(
binding
,
'routine'
);
spyOn
(
binding
.
binder
,
'routine'
);
binding
.
set
(
'sweater'
);
expect
(
binding
.
routine
).
toHaveBeenCalledWith
(
el
,
'sweater'
);
expect
(
binding
.
binder
.
routine
).
toHaveBeenCalledWith
(
el
,
'sweater'
);
});
it
(
'applies any formatters to the value before performing the routine'
,
function
()
{
rivets
.
formatters
.
awesome
=
function
(
value
)
{
return
'awesome '
+
value
};
binding
.
formatters
.
push
(
'awesome'
);
spyOn
(
binding
,
'routine'
);
spyOn
(
binding
.
binder
,
'routine'
);
binding
.
set
(
'sweater'
);
expect
(
binding
.
routine
).
toHaveBeenCalledWith
(
el
,
'awesome sweater'
);
expect
(
binding
.
binder
.
routine
).
toHaveBeenCalledWith
(
el
,
'awesome sweater'
);
});
it
(
'calls methods with the object as context'
,
function
()
{
binding
.
model
=
{
foo
:
'bar'
};
spyOn
(
binding
,
'routine'
);
spyOn
(
binding
.
binder
,
'routine'
);
binding
.
set
(
function
()
{
return
this
.
foo
;
});
expect
(
binding
.
routine
).
toHaveBeenCalledWith
(
el
,
binding
.
model
.
foo
);
});
describe
(
'on an event binding'
,
function
()
{
beforeEach
(
function
()
{
binding
.
options
.
special
=
'event'
;
});
it
(
'performs the binding routine with the supplied function and current listener'
,
function
()
{
spyOn
(
binding
,
'routine'
);
func
=
function
()
{
return
1
+
2
;
}
binding
.
set
(
func
);
expect
(
binding
.
routine
).
toHaveBeenCalledWith
(
el
,
binding
.
model
,
func
,
undefined
);
});
});
describe
(
'on an iteration binding'
,
function
(){
beforeEach
(
function
(){
binding
.
options
.
special
=
'iteration'
;
});
it
(
'performs the binding routine with the supplied collection and binding'
,
function
()
{
spyOn
(
binding
,
'routine'
);
array
=
[{
name
:
'a'
},
{
name
:
'b'
}];
binding
.
set
(
array
);
expect
(
binding
.
routine
).
toHaveBeenCalledWith
(
el
,
array
,
binding
);
});
expect
(
binding
.
binder
.
routine
).
toHaveBeenCalledWith
(
el
,
binding
.
model
.
foo
);
});
});
...
...
spec/rivets/routines.js
View file @
d0b185b
...
...
@@ -18,13 +18,13 @@ describe('Routines', function() {
describe
(
'text'
,
function
()
{
it
(
"sets the element's text content"
,
function
()
{
rivets
.
routine
s
.
text
(
el
,
'<em>gluten-free</em>'
);
rivets
.
binder
s
.
text
(
el
,
'<em>gluten-free</em>'
);
expect
(
el
.
textContent
||
el
.
innerText
).
toBe
(
'<em>gluten-free</em>'
);
expect
(
el
.
innerHTML
).
toBe
(
'<em>gluten-free</em>'
);
});
it
(
"sets the element's text content to zero when a numeric zero is passed"
,
function
()
{
rivets
.
routine
s
.
text
(
el
,
0
);
rivets
.
binder
s
.
text
(
el
,
0
);
expect
(
el
.
textContent
||
el
.
innerText
).
toBe
(
'0'
);
expect
(
el
.
innerHTML
).
toBe
(
'0'
);
});
...
...
@@ -32,13 +32,13 @@ describe('Routines', function() {
describe
(
'html'
,
function
()
{
it
(
"sets the element's HTML content"
,
function
()
{
rivets
.
routine
s
.
html
(
el
,
'<strong>fixie</strong>'
);
rivets
.
binder
s
.
html
(
el
,
'<strong>fixie</strong>'
);
expect
(
el
.
textContent
||
el
.
innerText
).
toBe
(
'fixie'
);
expect
(
el
.
innerHTML
).
toBe
(
'<strong>fixie</strong>'
);
});
it
(
"sets the element's HTML content to zero when a zero value is passed"
,
function
()
{
rivets
.
routine
s
.
html
(
el
,
0
);
rivets
.
binder
s
.
html
(
el
,
0
);
expect
(
el
.
textContent
||
el
.
innerText
).
toBe
(
'0'
);
expect
(
el
.
innerHTML
).
toBe
(
'0'
);
});
...
...
@@ -46,17 +46,17 @@ describe('Routines', function() {
describe
(
'value'
,
function
()
{
it
(
"sets the element's value"
,
function
()
{
rivets
.
routines
.
valu
e
(
input
,
'pitchfork'
);
rivets
.
binders
.
value
.
routin
e
(
input
,
'pitchfork'
);
expect
(
input
.
value
).
toBe
(
'pitchfork'
);
});
it
(
"applies a default value to the element when the model doesn't contain it"
,
function
()
{
rivets
.
routines
.
valu
e
(
input
,
undefined
);
rivets
.
binders
.
value
.
routin
e
(
input
,
undefined
);
expect
(
input
.
value
).
toBe
(
''
);
});
it
(
"sets the element's value to zero when a zero value is passed"
,
function
()
{
rivets
.
routines
.
valu
e
(
input
,
0
);
rivets
.
binders
.
value
.
routin
e
(
input
,
0
);
expect
(
input
.
value
).
toBe
(
'0'
);
});
});
...
...
@@ -64,14 +64,14 @@ describe('Routines', function() {
describe
(
'show'
,
function
()
{
describe
(
'with a truthy value'
,
function
()
{
it
(
'shows the element'
,
function
()
{
rivets
.
routine
s
.
show
(
el
,
true
);
rivets
.
binder
s
.
show
(
el
,
true
);
expect
(
el
.
style
.
display
).
toBe
(
''
);
});
});
describe
(
'with a falsey value'
,
function
()
{
it
(
'hides the element'
,
function
()
{
rivets
.
routine
s
.
show
(
el
,
false
);
rivets
.
binder
s
.
show
(
el
,
false
);
expect
(
el
.
style
.
display
).
toBe
(
'none'
);
});
});
...
...
@@ -80,14 +80,14 @@ describe('Routines', function() {
describe
(
'hide'
,
function
()
{
describe
(
'with a truthy value'
,
function
()
{
it
(
'hides the element'
,
function
()
{
rivets
.
routine
s
.
hide
(
el
,
true
);
rivets
.
binder
s
.
hide
(
el
,
true
);
expect
(
el
.
style
.
display
).
toBe
(
'none'
);
});
});
describe
(
'with a falsey value'
,
function
()
{
it
(
'shows the element'
,
function
()
{
rivets
.
routine
s
.
hide
(
el
,
false
);
rivets
.
binder
s
.
hide
(
el
,
false
);
expect
(
el
.
style
.
display
).
toBe
(
''
);
});
});
...
...
@@ -96,14 +96,14 @@ describe('Routines', function() {
describe
(
'enabled'
,
function
()
{
describe
(
'with a truthy value'
,
function
()
{
it
(
'enables the element'
,
function
()
{
rivets
.
routine
s
.
enabled
(
el
,
true
);
rivets
.
binder
s
.
enabled
(
el
,
true
);
expect
(
el
.
disabled
).
toBe
(
false
);
});
});
describe
(
'with a falsey value'
,
function
()
{
it
(
'disables the element'
,
function
()
{
rivets
.
routine
s
.
enabled
(
el
,
false
);
rivets
.
binder
s
.
enabled
(
el
,
false
);
expect
(
el
.
disabled
).
toBe
(
true
);
});
});
...
...
@@ -112,14 +112,14 @@ describe('Routines', function() {
describe
(
'disabled'
,
function
()
{
describe
(
'with a truthy value'
,
function
()
{
it
(
'disables the element'
,
function
()
{
rivets
.
routine
s
.
disabled
(
el
,
true
);
rivets
.
binder
s
.
disabled
(
el
,
true
);
expect
(
el
.
disabled
).
toBe
(
true
);
});
});
describe
(
'with a falsey value'
,
function
()
{
it
(
'enables the element'
,
function
()
{
rivets
.
routine
s
.
disabled
(
el
,
false
);
rivets
.
binder
s
.
disabled
(
el
,
false
);
expect
(
el
.
disabled
).
toBe
(
false
);
});
});
...
...
@@ -128,14 +128,14 @@ describe('Routines', function() {
describe
(
'checked'
,
function
()
{
describe
(
'with a truthy value'
,
function
()
{
it
(
'checks the element'
,
function
()
{
rivets
.
routines
.
checked
(
el
,
true
);
rivets
.
binders
.
checked
.
routine
(
el
,
true
);
expect
(
el
.
checked
).
toBe
(
true
);
});
});
describe
(
'with a falsey value'
,
function
()
{
it
(
'unchecks the element'
,
function
()
{
rivets
.
routines
.
checked
(
el
,
false
);
rivets
.
binders
.
checked
.
routine
(
el
,
false
);
expect
(
el
.
checked
).
toBe
(
false
);
});
});
...
...
@@ -144,14 +144,14 @@ describe('Routines', function() {
describe
(
'unchecked'
,
function
()
{
describe
(
'with a truthy value'
,
function
()
{
it
(
'unchecks the element'
,
function
()
{
rivets
.
routines
.
unchecked
(
el
,
true
);
rivets
.
binders
.
unchecked
.
routine
(
el
,
true
);
expect
(
el
.
checked
).
toBe
(
false
);
});
});
describe
(
'with a falsey value'
,
function
()
{
it
(
'checks the element'
,
function
()
{
rivets
.
routines
.
unchecked
(
el
,
false
);
rivets
.
binders
.
unchecked
.
routine
(
el
,
false
);
expect
(
el
.
checked
).
toBe
(
true
);
});
});
...
...
src/rivets.coffee
View file @
d0b185b
...
...
@@ -15,19 +15,21 @@ class Rivets.Binding
# element, the type of binding, the model object and the keypath at which
# to listen for changes.
constructor
:
(
@
el
,
@
type
,
@
model
,
@
keypath
,
@
options
=
{})
->
@
routine
=
switch
@
options
.
special
when
'event'
then
eventBinding
@
type
when
'class'
then
classBinding
@
type
when
'iteration'
then
iterationBinding
@
type
else
Rivets
.
routines
[
@
type
]
||
attributeBinding
@
type
unless
@
binder
=
Rivets
.
binders
[
type
]
for
identifier
,
value
of
Rivets
.
binders
if
identifier
isnt
'*'
and
identifier
.
indexOf
(
'*'
)
isnt
-
1
regexp
=
new
RegExp
"^
#{
identifier
.
replace
(
'*'
,
'.+'
)
}
$"
if
regexp
.
test
type
@
binder
=
value
@
args
=
new
RegExp
(
"^
#{
identifier
.
replace
(
'*'
,
'(.+)'
)
}
$"
).
exec
type
@
args
.
shift
()
@
formatters
=
@
options
.
formatters
||
[]
@
binder
or=
Rivets
.
binders
[
'*'
]
if
@
binder
instanceof
Function
@
binder
=
{
routine
:
@
binder
}
# Returns true|false depending on whether or not the binding should also
# observe the DOM element for changes in order to propagate those changes
# back to the model object.
isBidirectional
:
=>
@
type
in
[
'value'
,
'checked'
,
'unchecked'
]
@
formatters
=
@
options
.
formatters
||
[]
# Applies all the current formatters to the supplied value and returns the
# formatted value.
...
...
@@ -45,17 +47,12 @@ class Rivets.Binding
# Sets the value for the binding. This Basically just runs the binding routine
# with the suplied value formatted.
set
:
(
value
)
=>
value
=
if
value
instanceof
Function
and
@
options
.
special
isnt
'event'
value
=
if
value
instanceof
Function
and
!
@
binder
.
function
@
formattedValue
value
.
call
@
model
else
@
formattedValue
value
if
@
options
.
special
is
'event'
@
currentListener
=
@
routine
@
el
,
@
model
,
value
,
@
currentListener
else
if
@
options
.
special
is
'iteration'
@
routine
@
el
,
value
,
@
else
@
routine
@
el
,
value
@
binder
.
routine
?
.
call
@
,
@
el
,
value
# Syncs up the view binding with the model.
sync
:
=>
...
...
@@ -75,12 +72,10 @@ class Rivets.Binding
if
@
options
.
bypass
@
sync
()
else
@
binder
.
bind
?
.
call
@
,
@
el
Rivets
.
config
.
adapter
.
subscribe
@
model
,
@
keypath
,
@
sync
@
sync
()
if
Rivets
.
config
.
preloadData
if
@
isBidirectional
()
bindEvent
@
el
,
'change'
,
@
publish
if
@
options
.
dependencies
?
.
length
for
dependency
in
@
options
.
dependencies
if
/^\./
.
test
dependency
...
...
@@ -93,15 +88,12 @@ class Rivets.Binding
Rivets
.
config
.
adapter
.
subscribe
model
,
keypath
,
@
sync
# Unsubscribes from the model and the element.
unbind
:
=>
unless
@
options
.
bypass
@
binder
.
unbind
?
.
call
@
,
@
el
Rivets
.
config
.
adapter
.
unsubscribe
@
model
,
@
keypath
,
@
sync
if
@
isBidirectional
()
unbindEvent
@
el
,
'change'
,
@
publish
if
@
options
.
dependencies
?
.
length
for
keypath
in
@
options
.
dependencies
Rivets
.
config
.
adapter
.
unsubscribe
@
model
,
keypath
,
@
sync
...
...
@@ -119,28 +111,30 @@ class Rivets.View
prefix
=
Rivets
.
config
.
prefix
if
prefix
then
new
RegExp
(
"^data-
#{
prefix
}
-"
)
else
/^data-/
# Builds the Rivets.Binding instances for the view.
build
:
=>
@
bindings
=
[]
skipNodes
=
[]
iterator
=
null
bindingRegExp
=
@
bindingRegExp
()
eventRegExp
=
/^on-/
classRegExp
=
/^class-/
iterationRegExp
=
/^each-/
parseNode
=
(
node
)
=>
unless
node
in
skipNodes
for
attribute
in
node
.
attributes
if
bindingRegExp
.
test
attribute
.
name
type
=
attribute
.
name
.
replace
bindingRegExp
,
''
unless
binder
=
Rivets
.
binders
[
type
]
for
identifier
,
value
of
Rivets
.
binders
if
identifier
isnt
'*'
and
identifier
.
indexOf
(
'*'
)
isnt
-
1
regexp
=
new
RegExp
"^
#{
identifier
.
replace
(
'*'
,
'.+'
)
}
$"
if
regexp
.
test
type
binder
=
value
if
iterationRegExp
.
test
type
unless
@
models
[
type
.
replace
iterationRegExp
,
''
]
binder
or=
Rivets
.
binders
[
'*'
]
if
binder
.
block
skipNodes
.
push
n
for
n
in
node
.
getElementsByTagName
'*'
iterator
=
[
attribute
]
attributes
=
[
attribute
]
for
attribute
in
iterator
or
node
.
attributes
for
attribute
in
attributes
or
node
.
attributes
if
bindingRegExp
.
test
attribute
.
name
options
=
{}
...
...
@@ -162,31 +156,19 @@ class Rivets.View
if
dependencies
=
context
.
shift
()
options
.
dependencies
=
dependencies
.
split
/\s+/
if
eventRegExp
.
test
type
type
=
type
.
replace
eventRegExp
,
''
options
.
special
=
'event'
if
classRegExp
.
test
type
type
=
type
.
replace
classRegExp
,
''
options
.
special
=
'class'
if
iterationRegExp
.
test
type
type
=
type
.
replace
iterationRegExp
,
''
options
.
special
=
'iteration'
binding
=
new
Rivets
.
Binding
node
,
type
,
model
,
keypath
,
options
binding
.
view
=
@
@
bindings
.
push
binding
if
iterator
node
.
removeAttribute
(
a
.
name
)
for
a
in
iterator
iterator
=
null
attributes
=
null
if
attributes
return
for
el
in
@
els
parseNode
el
parseNode
node
for
node
in
el
.
getElementsByTagName
'*'
return
# Returns an array of bindings where the supplied function evaluates to true.
...
...
@@ -207,7 +189,7 @@ class Rivets.View
# Publishes the input values from the view back to the model (reverse sync).
publish
:
=>
binding
.
publish
()
for
binding
in
@
select
(
b
)
->
b
.
isBidirectional
()
binding
.
publish
()
for
binding
in
@
select
(
b
)
->
b
.
binder
.
publishes
# Cross-browser event binding.
bindEvent
=
(
el
,
event
,
handler
,
context
)
->
...
...
@@ -248,85 +230,111 @@ getInputValue = (el) ->
when
'select-multiple'
then
o
.
value
for
o
in
el
when
o
.
selected
else
el
.
value
# Returns an event binding routine for the specified event.
eventBinding
=
(
event
)
->
(
el
,
context
,
bind
,
unbind
)
->
unbindEvent
el
,
event
,
unbind
if
unbind
bindEvent
el
,
event
,
bind
,
context
# Returns a class binding routine for the specified class name.
classBinding
=
(
name
)
->
(
el
,
value
)
->
elClass
=
"
#{
el
.
className
}
"
hasClass
=
elClass
.
indexOf
(
"
#{
name
}
"
)
!=
-
1
if
!
value
is
hasClass
el
.
className
=
if
value
"
#{
el
.
className
}
#{
name
}
"
else
elClass
.
replace
(
"
#{
name
}
"
,
' '
).
trim
()
# Returns an iteration binding routine for the specified collection.
iterationBinding
=
(
name
)
->
(
el
,
collection
,
binding
)
->
if
binding
.
iterated
?
for
iteration
in
binding
.
iterated
iteration
.
view
.
unbind
()
iteration
.
el
.
parentNode
.
removeChild
iteration
.
el
else
binding
.
marker
=
document
.
createComment
" rivets: each-
#{
name
}
"
el
.
parentNode
.
insertBefore
binding
.
marker
,
el
el
.
parentNode
.
removeChild
el
binding
.
iterated
=
[]
for
item
in
collection
data
=
{}
data
[
n
]
=
m
for
n
,
m
of
binding
.
view
.
models
data
[
name
]
=
item
itemEl
=
el
.
cloneNode
true
previous
=
binding
.
iterated
[
binding
.
iterated
.
length
-
1
]
or
binding
.
marker
binding
.
marker
.
parentNode
.
insertBefore
itemEl
,
previous
.
nextSibling
?
null
binding
.
iterated
.
push
el
:
itemEl
view
:
rivets
.
bind
itemEl
,
data
# Returns an attribute binding routine for the specified attribute. This is what
# is used when there are no matching routines for an identifier.
attributeBinding
=
(
attr
)
->
(
el
,
value
)
->
if
value
then
el
.
setAttribute
attr
,
value
else
el
.
removeAttribute
attr
# Core binding routines.
Rivets
.
routine
s
=
Rivets
.
binder
s
=
enabled
:
(
el
,
value
)
->
el
.
disabled
=
!
value
disabled
:
(
el
,
value
)
->
el
.
disabled
=
!!
value
checked
:
(
el
,
value
)
->
checked
:
publishes
:
true
bind
:
(
el
)
->
bindEvent
el
,
'change'
,
@
publish
unbind
:
(
el
)
->
unbindEvent
el
,
'change'
,
@
publish
routine
:
(
el
,
value
)
->
if
el
.
type
is
'radio'
el
.
checked
=
el
.
value
is
value
else
el
.
checked
=
!!
value
unchecked
:
(
el
,
value
)
->
unchecked
:
publishes
:
true
bind
:
(
el
)
->
bindEvent
el
,
'change'
,
@
publish
unbind
:
(
el
)
->
unbindEvent
el
,
'change'
,
@
publish
routine
:
(
el
,
value
)
->
if
el
.
type
is
'radio'
el
.
checked
=
el
.
value
isnt
value
else
el
.
checked
=
!
value
show
:
(
el
,
value
)
->
el
.
style
.
display
=
if
value
then
''
else
'none'
hide
:
(
el
,
value
)
->
el
.
style
.
display
=
if
value
then
'none'
else
''
html
:
(
el
,
value
)
->
el
.
innerHTML
=
if
value
?
then
value
else
''
value
:
(
el
,
value
)
->
value
:
publishes
:
true
bind
:
(
el
)
->
bindEvent
el
,
'change'
,
@
publish
unbind
:
(
el
)
->
unbindEvent
el
,
'change'
,
@
publish
routine
:
(
el
,
value
)
->
if
el
.
type
is
'select-multiple'
o
.
selected
=
o
.
value
in
value
for
o
in
el
if
value
?
else
el
.
value
=
if
value
?
then
value
else
''
text
:
(
el
,
value
)
->
if
el
.
innerText
?
el
.
innerText
=
if
value
?
then
value
else
''
else
el
.
textContent
=
if
value
?
then
value
else
''
"on-*"
:
function
:
true
routine
:
(
el
,
value
)
->
unbindEvent
el
,
@
args
[
0
],
@
currentListener
if
@
currentListener
@
currentListener
=
bindEvent
el
,
@
args
[
0
],
value
,
@
model
"each-*"
:
block
:
true
bind
:
(
el
,
collection
)
->
el
.
removeAttribute
[
'data'
,
rivets
.
config
.
prefix
,
@
type
].
join
(
'-'
).
replace
'--'
,
'-'
routine
:
(
el
,
collection
)
->
if
@
iterated
?
for
view
in
@
iterated
view
.
unbind
()
e
.
parentNode
.
removeChild
e
for
e
in
view
.
els
else
@
marker
=
document
.
createComment
" rivets:
#{
@
type
}
"
el
.
parentNode
.
insertBefore
@
marker
,
el
el
.
parentNode
.
removeChild
el
@
iterated
=
[]
for
item
in
collection
data
=
{}
data
[
n
]
=
m
for
n
,
m
of
@
view
.
models
data
[
@
args
[
0
]]
=
item
itemEl
=
el
.
cloneNode
true
previous
=
@
iterated
[
@
iterated
.
length
-
1
]
or
@
marker
@
marker
.
parentNode
.
insertBefore
itemEl
,
previous
.
nextSibling
?
null
@
iterated
.
push
rivets
.
bind
itemEl
,
data
"class-*"
:
(
el
,
value
)
->
elClass
=
"
#{
el
.
className
}
"
if
!
value
is
(
elClass
.
indexOf
(
"
#{
@
args
[
0
]
}
"
)
isnt
-
1
)
el
.
className
=
if
value
"
#{
el
.
className
}
#{
@
args
[
0
]
}
"
else
elClass
.
replace
(
"
#{
@
args
[
0
]
}
"
,
' '
).
trim
()
"*"
:
(
el
,
value
)
->
if
value
el
.
setAttribute
@
type
,
value
else
el
.
removeAttribute
@
type
# Default configuration.
Rivets
.
config
=
preloadData
:
true
...
...
@@ -337,7 +345,7 @@ Rivets.formatters = {}
# The rivets module. This is the public interface that gets exported.
rivets
=
# Exposes the core binding routines that can be extended or stripped down.
routines
:
Rivets
.
routine
s
binders
:
Rivets
.
binder
s
# Exposes the formatters object to be extended.
formatters
:
Rivets
.
formatters
...
...
Please
register
or
sign in
to post a comment