Query Params

More powerful than you'd ever imagined

@jeffreybiles

www.emberscreencasts.com

Part 1: Basic

@jeffreybiles

www.emberscreencasts.com

In which the audience acquires a basic understanding of query-params

through boring examples

Sorting

Before

export default Ember.Controller.extend(
  Ember.SortableMixin, {
    queryParams: ['sortProperties', 
                'sortAscending'],
    sortProperties: ['createdAt'],
    sortAscending: true
  }
);

Sorting Basics

{{#each post in arrangedContent}}
  <tr>
    <td>{{post.title}}</td>
    //repeated for each column
  </tr>
{{/each}}

content -> arrangedContent

<thead>
  <tr>
    // was simply `<td>title</td>` before
    {{sortable-header 
        title='Title' 
        sortProperty='title' 
        selectedSorts=sortProperties 
        isAscending=sortAscending}}
    //repeated for each column
  </tr>
</thead>
//sortable-header.js

tagName: 'th',
downArrow: '▼',
upArrow: '▲',

isSelectedSort: Ember.computed('selectedSorts', 'sortProperty', function(){
  return this.get('selectedSorts')[0] === this.get('sortProperty');
}),
upArrowHighlighted: Ember.computed('isSelectedSort', 'isAscending', function(){
  return this.get("isSelectedSort") && this.get('isAscending');
}),
downArrowHighlighted: Ember.computed('isSelectedSort', 'isAscending', function(){
  return this.get("isSelectedSort") && !this.get('isAscending');
}),
sortProperties: Ember.computed('sortProperty', function(){
  return [this.get('sortProperty')]
})
//sortable-header.hbs

{{title}}
{{#link-to 'posts' (query-params sortProperties=sortProperties sortAscending=true) tagName="button"}}
  <div class="{{if upArrowHighlighted 'gold'}}">
    {{{upArrow}}}
  </div>
{{/link-to}}
{{#link-to 'posts' (query-params sortProperties=sortProperties sortAscending=false) tagName="button"}}
  <div class="{{if downArrowHighlighted 'gold'}}">
    {{{downArrow}}}
  </div>
{{/link-to}}

{{#link-to 'posts' 
    (query-params 
        sortProperties=sortProperties 
        sortAscending=true) 
    tagName="button"}}

  <div class="{{if upArrowHighlighted 'gold'}}">
    {{{upArrow}}}
  </div>

{{/link-to}}

{{#link-to 'posts' 
    (query-params 
        sortProperties=sortProperties 
        sortAscending=true) 
    tagName="button"}}

Link-to changes the url

Query Params are part of the url

Therefore, link-to can change query-params


{{#link-to 'posts' 
    (query-params 
        sortProperties=sortProperties 
        sortAscending=true) 
    tagName="button"}}

Sorting

More sorting resources

Syntax overview

in controller

in handlebars

queryParams: ['param1', 'param2', ...]
{{#link-to 'routeName'
  (query-params param1=foo param2=bar)
  }}

Pagination

//controllers/posts.js

queryParams: ['sortProperties',
              'sortAscending',
              'pageNumber',
              'pageSize']
pageNumber: 0,
pageSize: 10,
possiblePageSizes: [10, 25, 50, 100],
//controllers/posts.js


pages: Ember.computed('arrangedContent', 'pageSize', 'sortAscending', function(){
  var pages = [];
  var arrangedContent = this.get('arrangedContent').copy();
  while (arrangedContent.length > 0) {
    pages.push(arrangedContent.splice(0, this.get('pageSize')));
  }
  return pages;
}),
paginatedContent: Ember.computed('pages', 'pageNumber', function(){
  return this.get('pages')[this.get('pageNumber')]
}),
//posts.hbs

{{#each post in paginatedContent}}
  <tr>
    <td>{{post.title}}</td>
    //repeated for each column
  </tr>
{{/each}}
//posts.hbs

{{#each pages as |page index|}}
  <span class="{{if (is-equal index pageNumber) 'bold big-text'}}">
    {{#link-to 'posts' (query-params pageNumber=index) tagName="button"}}
      {{add-one index}}
    {{/link-to}}
  </span>
{{/each}}
//posts.hbs

<button {{action 'previousPage'}}>Previous Page</button>

{{#each pages as |page index|}}
  <span class="{{if (is-equal index pageNumber) 'bold big-text'}}">
    {{#link-to 'posts' (query-params pageNumber=index) tagName="button"}}
      {{add-one index}}
    {{/link-to}}
  </span>
{{/each}}

<button {{action 'nextPage'}}>Next Page</button>
//controllers/posts.js

actions: {
  previousPage: function(){
    if(this.get('pageNumber') > 0){
     this.set('pageNumber', this.get('pageNumber') - 1);
    }
  },
  //nextPage is similar
}
//controllers/posts.hbs

<span class="option-name">Page size</span>
{{#each possiblePageSizes as |newSize|}}
  <button {{action 'changePageSize' newSize}} 
        class="{{if (is-equal newSize pageSize) 'bold big-text'}}">
    {{newSize}}
  </button>
{{/each}}
//controllers/posts.js

actions: {
  //other actions
  changePageSize: function(newPageSize){
    var currentOffset = this.get('pageSize') * this.get('pageNumber');
    var newPageNumber = Math.floor(currentOffset / newPageSize);
    this.set('pageNumber', newPageNumber);
    this.set('pageSize', newPageSize)
  },
}

Pagination

More Pagination Resources

  • EmberScreencasts.com
    • 24: Handlebars Subexpressions (coming soon)
    • 25: Client-side Pagination (coming soon)

Part 2: Awesome

@jeffreybiles

www.emberscreencasts.com

In which the audience acquires more understanding of query-params

through awesome examples

Dynamic Columns

First, abstract "columns"

//controllers/posts.js
availableColumns: [
  {'title': 'Title', 'property': 'title', 'display': 'plain'},
  {'title': 'Author', 'property': 'author', 'display': 'plain'},
  {'title': 'Updated', 'property': 'updatedAt', 'display': 'date'},
  {'title': 'Created', 'property': 'createdAt', 'display': 'date'}
],
columns: Ember.computed.alias('availableColumns') //changing soon

//posts.hbs
{{#each column in columns}}
  {{sortable-header 
    title=column.title 
    sortProperty=column.property 
    selectedSorts=sortProperties
    isAscending=sortAscending}}
{{/each}}

...

{{#each column in columns}}
  <td>
    {{table-block-output column=column item=post}}
  </td>
{{/each}}
//table-block-output.js

export default Ember.Component.extend({
  isPlain: Ember.computed.equal('column.display', 'plain'),
  isDate: Ember.computed.equal('column.display', 'date'),

  itemProperty: Ember.computed('column.property', 'item', function(){
    return this.get('item').get(this.get('column.property'))
  })
});

//table-block-output.hbs

{{#if isPlain}}
  {{truncate-text itemProperty 50}}
{{else if isDate}}
  {{format-date itemProperty}}
{{/if}}

Filter based on columnsUsed

availableColumns

columns

columnsUsed

(query-parameter)

//controllers/posts.js

queryParams: ['sortProperties', 'sortAscending', 
            'pageNumber', 'pageSize', 'columnsUsed'],
availableColumns: [{property: 'author', ...}, ...], //same as before
columnsUsed: [
  'title',
  'author',
  'updatedAt'
],
columns: Ember.computed('availableColumns', 'columnsUsed.@each', function(){
  var controller = this;
  return this.get('columnsUsed').map(function(columnProperty){
    return controller.get('availableColumns').filter(function(column){
      return columnProperty == column.property
    })[0]
  })
}),

Change columnsUsed

//posts.hbs

{{#each availableColumns as |column|}}
  {{list-item-toggle displayedList=columnsUsed displayableItem=column}}
{{/each}}


//list-item-toggle.hbs

<button {{action 'toggle'}} class={{if isDisplayed 'bold big-text' 'text-muted'}}>
  {{displayableItem.title}}
</button>


//list-item-toggle.js

tagName: 'span',
isDisplayed: Ember.computed('displayableItem.property', 'displayedList.@each', function(){
  var displayedList = this.get('displayedList')
  return displayedList.contains(this.get('displayableItem.property'))
}),
actions: {
  toggle: function(){
    if(this.get('isDisplayed')){
      this.get('displayedList').removeObject(this.get('displayableItem.property'))
    } else {
      this.get('displayedList').addObject(this.get('displayableItem.property'))
    }
  }
}

Dynamic Columns

Rearrange Columns

//controllers/posts.js

actions: {
  ...
  moveLeft: function(property){
    var columns = this.get('columnsUsed')
    var index = columns.indexOf(property)
    columns.removeObject(property)
    if(index == 0){
      columns.insertAt(index, property)
    } else {
      columns.insertAt(index - 1, property)
    }
  },
  //moveRight is similar
}

//sortable-header.hbs

<button {{action 'moveLeft'}} class="pull-left">{{show-brackets '<'}}</button>
...
<button {{action 'moveRight'}} class="pull-right">{{show-brackets '>'}}</button>


//action passed up through component
//in posts.hbs and sortable-header.js
//but it's boring code

Rearrange Columns

Demo

Brainstorming

Part 3: Fun

@jeffreybiles

www.emberscreencasts.com

In which the audience's mind is blown

Level Design

Stages of Level Design

What about url size?

2048kb/68kb = 30.1

Part 4: Dangerous

@jeffreybiles

www.emberscreencasts.com

In which the audience's mind is blown

and they contemplate changing the world

again

Charts

Charts help us understand data

Charts "help" us "understand" data

Charts taken from http://blogging.alastair.is/charts-can-say-anything-you-want-them-to/

Charts twist data to fit the prejudices of the presenter

Charts taken from http://blogging.alastair.is/charts-can-say-anything-you-want-them-to/

Current Method of Investigating a Chart

  1. Locate and retrieve data source
  2. Make the data source fit into your favorite data-visualization software.
    • You have a favorite, right?
  3. Recreate the data segmentation and data elimination (outliers, etc.) and adjustments for confounding factors that the original chart used.
    • Information about this is usually hidden in a research paper somewhere.  Hope you have access to a university library.

(theoretical)

Current Method of Investigating a Chart

  1. Locate and retrieve data source
  2. Make the data source fit into your favorite data-visualization software.
    • You have a favorite, right?
  3. Recreate the data segmentation and data elimination (outliers, etc.) and adjustments for confounding factors that the original chart used.
    • Information about this is usually hidden in a research paper somewhere.  Hope you have access to a university library.
  4. Recreate the graph type and settings of the original visualization.
    • Bonus points: coloring and font
  5. Investigate
  6. Export into a sharable format
  7. Share
    • Preferably detailing how your graph is different and why it's better
    • But you're probably too tired by now

(theoretical)

Current Method of Investigating a Chart

if (chart.conclusion == user.preconceptions){

   return "So insightful!"

} else {

  return "Such bullshit"

}

(actual)

Proposed Method of Investigating a Chart

  1. Click on the associated link
  2. Investigate
  3. Share your results in a url

Rational Discussion

Make it easier to have a

Talk Across Tribes

Make it easier to

Data Guided Discussions

Don't trust a chart you can't verify

Brainstorming

End

@jeffreybiles

www.emberscreencasts.com

Query Params

By Jeffrey Biles

Query Params

Query Params are a ridiculously powerful tool for enhancing your apps. Dream big.

  • 1,626