Skip to content

Instantly share code, notes, and snippets.

@samselikoff
Created March 25, 2016 19:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save samselikoff/53ca7b5eda29487615b1 to your computer and use it in GitHub Desktop.
Save samselikoff/53ca7b5eda29487615b1 to your computer and use it in GitHub Desktop.
{{did-insert-element (action 'findAllExperiments')}}
<div class="row">
<div class="col-sm-8">
<h1>Overview</h1>
</div>
<div class="col-sm-4">
<div class="text-right">
{{link-to 'New Experiment' 'experiments.new'
class="btn btn-sm btn-default"}}
</div>
</div>
</div>
<hr>
{{#liquid-if hasFetchedAllExperiments use='crossFade' enableGrowth=false}}
<div class="Experiments u-relative u-padding-right-sm u-padding-bottom-sm">
<div class="row">
<div class="col-sm-3">
<h3 class='u-no-margin-top u-thin'>Group by</h3>
<ul class="nav nav-pills">
<li role="presentation" class="{{if (eq groupBy 'status') 'active'}}">
{{link-to 'Status' 'experiments.index' (query-params groupBy='status') class='link-plain'}}
</li>
<li role="presentation" class="{{if (eq groupBy 'hypothesis') 'active'}}">
{{link-to 'Hypothesis' 'experiments.index' (query-params groupBy='hypothesis') class='link-plain'}}
</li>
</ul>
<h3 class='u-thin u-margin-top-lg'>Filters</h3>
<p class=''><strong>Goal</strong></p>
<div class="list-group">
{{link-to 'All' 'experiments'
(query-params goalId='')
class='list-group-item'}}
{{#each goals as |goal|}}
{{#link-to 'experiments' (query-params goalId=goal.id) class='list-group-item'}}
<span class="badge">
{{length (filter-by 'goal.id' goal.id experiments)}}
</span>
{{goal.name}}
{{/link-to}}
{{/each}}
</div>
</div>
<div class="col-sm-9">
<div class="u-margin-left u-margin-bottom-lg">
{{#unless (length (filter-by 'goal.id' goalId experiments))}}
<p class="u-notice">
No experiments match these filters.
</p>
{{/unless}}
{{#if (eq groupBy 'status')}}
{{#with (filter-by 'isInProgress' true filteredExperiments) as |experiments|}}
{{#if experiments.length}}
<p class='text-muted'><em>In progress</em></p>
{{#each experiments as |experiment|}}
{{experiments/index/experiment-item experiment=experiment}}
{{/each}}
{{/if}}
{{/with}}
<div class="u-margin-bottom-lg">
{{#with (filter-by 'isUpcoming' true filteredExperiments) as |experiments|}}
<p class='text-muted'><em>Upcoming</em></p>
{{#each experiments as |experiment|}}
{{experiments/index/experiment-item experiment=experiment}}
{{/each}}
{{/with}}
</div>
<div class="u-margin-bottom-lg">
{{#with (filter-by 'isComplete' true filteredExperiments) as |experiments|}}
<p class='text-muted'><em>Completed</em></p>
{{#each experiments as |experiment|}}
{{experiments/index/experiment-item experiment=experiment}}
{{/each}}
{{/with}}
</div>
{{/if}}
{{#if (eq groupBy 'hypothesis')}}
<div class="u-margin-top">
{{#each (filter-by 'goal.id' goalId hypotheses) as |hypothesis|}}
<ul class="Experiments__hypothesis-label list-unstyled">
<li>
<em class='text-muted'>Hypothesis: </em>
<span>{{hypothesis.text}}</span>
</li>
</ul>
<div class="Experiments__hypothesis-list">
{{#each hypothesis.experiments as |experiment|}}
{{experiments/index/experiment-item experiment=experiment}}
{{/each}}
</div>
{{/each}}
</div>
{{/if}}
</div>
</div>
</div>
</div>
{{#if isCreatingExperiment}}
{{experiments/experiment-form
did-save=(action (toggle "isCreatingExperiment" this))
cancel=(action (toggle "isCreatingExperiment" this))}}
{{/if}}
{{else}}
<div class="u-centered-message">
<div>
<div class="loading-spinner"></div>
</div>
<p class='lead text-muted u-margin-top'>Loading experiments</p>
</div>
{{/liquid-if}}
@ef4
Copy link

ef4 commented Mar 25, 2016

This feels slightly WAT when you first see it

{{did-insert-element (action 'findAllExperiments')}}

But I think it's actually fine -- it's completely local and unambiguous what it does. And I like to do similar things with components that are pure behavior like, {{scroll-page-to position=0}} and {{#auto-focus }}<input>{{/auto-focus}}.

Overall I think this whole template reads great, though it's getting to the size where I start itching to break it into smaller pieces.

@samselikoff
Copy link
Author

@ef4 Haha, yep - that was an experiment. It's because this is a controller's template, and I was experimenting with non-blocking rendering. Since we have no routeable component I don't get a didInsertElement hook for this route's template, which is really what I wanted: initially render the template in a 0-data state, immediately fetch data & show loading spinner, then (automatically) rerender when the data comes back.

I was also experimenting, bc I'm starting to feel like data fetching should be done closer to components that need it. With such a great DI system I'm wondering if we even need routes? Non-blocking rendering & components that are smart enough to know how to render in various states (no data, loading, etc) makes for a better UX anyways. Thoughts?

@samselikoff
Copy link
Author

And I agree, I think this template is nearly at the point where I'd start breaking it up. But, it's so fun how much less code I had to write using these composable helpers. Also how easy it is to change.

@ef4
Copy link

ef4 commented Mar 27, 2016

There's definitely nothing that precludes doing component-centric data loading, so if that turns out to be the best approach I think it will just win out in the community through usage. But I think we still have a ways to go before asynchrony within components is as nice as what you get in Routes.

I also think a lot of aspects of routing should move to be less stateful. Today Routes are instances, but they really don't need to be and a lot of things get cleaner if they're not. Moving all data loading into components would potentially be a step backward along that dimension, unless we create a convention for routable components that lets them declare their asynchronous data requirements up front in static methods. Redirection, aborting, retrying, and error recovery mean you may need to bounce through multiple states before settling on one that you actually want to render. I don't want any of those intermediate steps to instantiate component instances.

We have clear separation today between the routing layer and the rendering layer that lets something like this work correctly:

{{#if something}}
 {{outlet}}
{{/if}}

In older Embers, if you toggled something off and on again, you would lose the state in the outlet, because the state was living in the view hierarchy. Now it lives (conceptually) in a separate service, and it all works nicely. If we load data in components, we would need to preserve this capability by not storing the routing state on the component instances and stashing it off on a router service instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment