Skip to content

Instantly share code, notes, and snippets.

@rheaton
Last active August 29, 2015 14:22
Show Gist options
  • Save rheaton/b57c224922d11a9241e1 to your computer and use it in GitHub Desktop.
Save rheaton/b57c224922d11a9241e1 to your computer and use it in GitHub Desktop.
Emerson: Problem Solving

Browser history is hard. Even github can't get it right (I found a bug writing this: Click "Raw" and then go Back in Chrome).

##Scenario

AB test single-page-checkout vs. 3 step checkout with minimal code duplication for fewer bugs and chances to pollute the test. 3-step checkout is already in place and refactored using a combination of emerson views and traits.

<div data-view='checkout' data-checkout-path='checkout/customer'>
...
</div>
<div data-view='checkout' data-checkout-path='checkout/shipping'>
  <form data-remote='true'>
  ...
    <div data-traits='address-autocompleter'>
      <input data-outlet='zipCode'/>
      <input data-outlet='city'/>
      <input data-outlet='state'/>
      <input data-outlet='country'/>
    </div>
    ...
  </form>
...
</div>
<div data-view='checkout' data-checkout-path='checkout/billing'>
  <form data-traits='stripe-form' data-remote='true'>
    <input data-outlet='cardNumber'/>
    <input data-outlet='cardCvv'/>
    ...
    <button type='submit'>Apply</button>
  </form>
...
</div>
<div data-view='checkout' data-checkout-path='checkout/confirm'>
  <div data-view='coupon'>
    <div data-sink='coupon-error'></div>
    <form data-remote='true' ...>
      <input type='text' data-outlet='couponName'/>
      <button type='submit'>Apply</button>
    </form>
    <form data-remote='true' ...>
      <button type='submit'>Complete Order</button>
    </form>
  </div>
  ...
</div>
var view = define('checkout', {
  initialize : function() {
    this.pushState();
  },
  subscribe : function() {
  
  }
});
view.extend({
  pushState: function() {
      if (history && history.pushState) {
        if (!history.state) {
          history.replaceState({checkout: true}, "", this.data('checkout-path'));
        } else {
          history.pushState({checkout: true}, "", this.data('checkout-path'));
        }
      }
    }
});

It also doesn't take into account navigating directly to the URL (yet) as it should behave as expected in the single-page version.

This doesn't even work in all scenarios, take a peak at the For the Makers store.js and the store in action with filtering, pagination, and AFAIK working back button and copy/paste of URL.

This example illustrates a couple issues to work around:

  1. sink:after is triggered on the document so listen for all events and check for the target having the trait/view.
  2. initialize() is triggered before the html is replaced/appended/etc. so listen for sink:after and check for matching [data-traits]
  var trait = define('scrollToSection', {
    initialize : function initialize() {
      this.scrollToSelected();
    },

    subscribe : {
      document : {
        'sink:after': function(ev) {
          // this could break if we add additional traits
          if($(ev.target).data('traits') == 'panelFocus') {
            this.scrollToSelected();
          }
        }
      }
    }
  });
  
  trait.extend({
    selected: function() {
      return this.outlet('selectable').filter('[data-selected]');
    },
    scrollToSelected: function() {
      if (this.selected().offset().top > 0) {
        //...
      }
    }
  });

Ideas

  • accessor function for "name" of trait/view
  • sink:after is triggered on initialized view or some other event is triggered on view, for example render:after or onpage:after
  • Popular library foundation.js doesn't re-do it's thing when something is added to the page. It "sees" it, but you have to trigger a reflow or some other foolish thing, so the above would be useful for integrating with libraries like this. For example, if something with a [data-interchange] image is rendered, $(document).foundation('interchange', 'reflow'); must be called in order for the image to show up and/or be the correct size/resolution.

Related

I haven't taken the time to figure out the order things when a view is sinked from a response JSON, but I think this is correct:

  1. form event: ajaxSend
  2. form event: ajaxSuccess/ajaxError
  3. form event: ajaxComplete
  4. view.initialize()
  5. view replaced/appended/etc.
  6. document event: sink:after
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment