baconjs / bacon.js

Functional reactive programming library for TypeScript and JavaScript

Home Page:https://baconjs.github.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Optional fields in baconjs form

lukasz-madon opened this issue · comments

I'm building customisable form in bacon.js. I have a problem with optional fields that can be empty e.g. phone is sometimes required or sometimes optional.

field_events = _.merge(
  _.mapValues(@form, @_build_field_streams), dynamic_field_events
)
valid_streams = _.pluck(_.values(field_events), 0)
all_invalid = Bacon.all(valid_streams).not().toProperty()

data_prop = Bacon.combineTemplate(
  _.mapValues(field_events, ([is_valid, value_stream]) -> value_stream)
)

submit_stream = @$el
  .asEventStream('click', @submit_selector)
  .doAction(".preventDefault")
  .alwaysSkipWhile(all_invalid)

@actions = Bacon.when(
  [data_prop, submit_stream],
  (data) -> {action: 'submit', param: data}
)

data_prop is evaluated lazily, so if phone stream doesn't have any value, we won't submit the form. Is there a way to give stream a default value or filter empty streams from combineTemplate ?

Rest of the code:

  _build_field_streams: ({selector, validator}, field) =>
    value_stream = Bacon.mergeAll(
      @_get_field_change_streams(selector)
    ).map((e) ->
      $(e.currentTarget).val()
    )

    if _.isString validator
      validator = exports.validators[validator]()

    # optional field are valid by default and for empty values
    is_optional_field = field not in @mandatory_field
    curr_validator = validator || @noop_validator
    validator_fun = (x) -> if is_optional_field and not x
      return true
    else
      return curr_validator(x)
    validator_stream = value_stream.map(validator_fun)

    is_valid = validator_stream.map(_.isEmpty).toProperty(is_optional_field).log('field')

    [ok_events, bad_events] = validator_stream.split(_.isEmpty)
    bad_events = bad_events.debounce(@invalid_delay)

    defocus = @$el.asEventStream('blur', selector)

    # TODO: Might be good to instantly go bad _if_ it was already valid.
    Bacon.when(
      [ok_events],            true
      [is_valid, bad_events], ((valid) -> valid),
      [is_valid, defocus],    ((valid) -> valid)
    ).onValue( (valid) =>
      if valid
        @render_field_valid(selector)
      else
        @render_field_invalid(selector)
    )
    return [is_valid, value_stream]

  _get_field_change_streams: (selector) ->
    # Hopefully these 2 cover most things.  Can always add more if we need.
    return _.map ['change', 'keyup'], (handler) =>
      @$el.asEventStream(handler, selector)

Stack overflow might be better suited for these kinda questions. Any case, properties should be initialized so that they have a starting value (can be undefined/null if you like to represent empties like this), supposing your problem is that you want it to have a value also when a field is missing a value. Posting a jsfiddle or similar with clear problem description will also help you!

-juha-

On 25.1.2016, at 19.53, lukas notifications@github.com wrote:

I'm building customisable form in bacon.js. I have a problem with optional fields that can be empty e.g. phone is sometimes required or sometimes optional.

field_events = _.merge(
_.mapValues(@Form, @_build_field_streams), dynamic_field_events
)
valid_streams = .pluck(.values(field_events), 0)
all_invalid = Bacon.all(valid_streams).not().toProperty()

data_prop = Bacon.combineTemplate(
_.mapValues(field_events, ([is_valid, value_stream]) -> value_stream)
)

submit_stream = @$el
.asEventStream('click', @submit_selector)
.doAction(".preventDefault")
.alwaysSkipWhile(all_invalid)

@actions = Bacon.when(
[data_prop, submit_stream],
(data) -> {action: 'submit', param: data}
)
data_prop is evaluated lazily, so if phone stream doesn't have any value, we won't submit the form. Is there a way to give stream a default value or filter empty streams from combineTemplate ?

Rest of the code:

_build_field_streams: ({selector, validator}, field) =>
value_stream = Bacon.mergeAll(
@_get_field_change_streams(selector)
).map((e) ->
$(e.currentTarget).val()
, )

if _.isString validator
  validator = exports.validators[validator]()

# optional field are valid by default and for empty values
is_optional_field = field not in @mandatory_field
curr_validator = validator || @noop_validator
validator_fun = (x) -> if is_optional_field and not x
  return true
else
  return curr_validator(x)
validator_stream = value_stream.map(validator_fun)

is_valid = validator_stream.map(_.isEmpty).toProperty(is_optional_field).log('field')

[ok_events, bad_events] = validator_stream.split(_.isEmpty)
bad_events = bad_events.debounce(@invalid_delay)

defocus = @$el.asEventStream('blur', selector)

# TODO: Might be good to instantly go bad _if_ it was already valid.
Bacon.when(
  [ok_events],            true
  [is_valid, bad_events], ((valid) -> valid),
  [is_valid, defocus],    ((valid) -> valid)
).onValue( (valid) =>
  if valid
    @render_field_valid(selector)
  else
    @render_field_invalid(selector)
)
return [is_valid, value_stream]

_get_field_change_streams: (selector) ->
# Hopefully these 2 cover most things. Can always add more if we need.
return _.map ['change', 'keyup'], (handler) =>
@$el.asEventStream(handler, selector)

Reply to this email directly or view it on GitHub.

Thanks for the hint! I converted stream to prop with a default value

    data_prop = Bacon.combineTemplate(
      _.mapValues(field_events, ([is_valid, value_stream]) -> value_stream.toProperty(''))
    )