Search code examples
data-bindingpolymerweb-component

How to call a function in data-binding when lots of parameters are involved


At the moment I have this in my template:

  • WAY 1

          <template is="dom-if" if="{{_showCancel(userData.generic.id,info.userId,info._children.gigId.userId,info.accepted,info.cancelled,info.paymentTerms)}}">
    

And then this in my element:

  _showCancel: function(viewerUserId,offerUserId,gigUserId, accepted, cancel, paymentTerms) {

    // Offer needs to be accepted and NOT cancelled
    if (!info.accepted || info.cancelled) return false;

    // Gig owner can ALWAYS cancel an offer
    if (viewerUserId == gigUserId ) return true;

    // Offer maker can only cancel if the gig involved pre-payment
    if (viewerUserId == offerUserId && paymentTerms != 'on-day') return true;

    return false;
  },

Do I have just too many parameters to this function?

  • WAY 2

Should I just have something like this instead:

<template is="dom-if" if="{{_showCancel(userData, info)}}">
  • WAY 3

Although I would want to check if their sub-properties change too... so I would need:

<template is="dom-if" if="{{_showCancel(userData, info, userData.*, info.*)}}">
  • WAY 4

But then again I probably should just look for the properties and use the value property like so:

<template is="dom-if" if="{{_showCancel(userData.*, info.*)}}">

And then the function would be:

_showCancel: function(userDataObs, infoObs) {
  var userData = userDataObs.value;
  var info = infoObs.value;

  if( !userData || !info) return;

  ...

Questions:

  • Do you see any fundamental mistakes with ways 1 to 4?
  • Is WAY 1 really the best way to go about it? (it feels like it right now)
  • Is WAY 3 an acceptable pattern?

ADDENDUM

What would I do in cases where I have:

<paper-button 
  raised 
  hidden$="{{!_showCancel(item.id,userData.config.usersCategories,userData.config.userCategories,userData.config.usersCategories.*)}}"
>
Cancel
</paper-button>

(NOTE: usersCategories is an Array) And _showCancel being:

  _showCancel: function(categoryId, userCategories) {
    for (var k in usersCategories) {
      var $ucat = usersCategories[k];

      if ($ucat.categoryId == categoryId) {
        if ($ucat.status == 'applied' || $ucat.status == 'granted') return true;
      }
    }

    return false;
  },

The point being: I want both easy access to usersCategories, but don't want to get the value out of the "strange" array modifiers etc. So, is this a good pattern?


Solution

  • The "Way 1" is the right one. But, you should only reference the variables that you need, and you should always use them as they are defined in your function header.

    For example, you use:

    {{_showCancel(userData.generic.id,info.userId,info._children.gigId.userId,info.accepted,info.cancelled,info.paymentTerms)}}

    with the following function header:

    _showCancel: function(viewerUserId,offerUserId,gigUserId, accepted, cancel, paymentTerms).

    But then inside the function, you reference info.accepted and info.cancelled, whereas you should used accepted and cancelled.

    This is because inside the function, the referenced values will always be up-to-date, whereas referencing variables via this.<variable-name> can sometimes contain older values.

    In order for my answer to be complete, I will also explain certain "problems" with other ways.

    Way 2: Here, you only reference the Object as a whole. This won't trigger the call via subproperty change, so it won't work as desired.

    Way 3 and Way 4 are similar, and both are overkills. With the object.* notation, you listen to all subproperty changes, which you most likely don't want.

    tl;dr

    Go with "Way 1" and make things simpler by using computed properties.

    To do this, change:

    <template is="dom-if" if="{{_showCancel(userData.generic.id,info.userId,info._children.gigId.userId,info.accepted,info.cancelled,info.paymentTerms)}}">
    

    To:

    <template is="dom-if" if="{{isCanceled}}">
    

    And add the following computed property to the Polymer element:

    isCanceled: {
      type: Boolean,
      computed: '_showCancel(userData.generic.id,info.userId,info._children.gigId.userId,info.accepted,info.cancelled,info.paymentTerms)'
    }
    

    You already have _showCancel defined, so this is actually it. The code should work the same as your "Way 1" example, only the dom-if is cleaner. This is especially useful if you re-use the condition on multiple occurences.

    If you have any questions, don't hesitate do add a comment about it.