Learning.md: Notes from Rock And Roll With Ember Octane

Ember.js

Some commands & principles I’ve learned from the Rock And Roll with Ember Octane that I might want to have in an overview!

Design principles

The rule of Least Power

For Ember, (I think) it boils down to this:

  • You should use the least powerful language to fix problems
  • No JavaScript in templating/handlebars means that future updates will have minimal breaking changes

Also see 11:30 in this Emberweekend episode and the W3C piece on it.

Handlebars’ fail-softness

undefined/null properties get ignored in Handlebars. This gives cleaner output and less conditional code, but is something to keep in mind when debugging.

Handlebars expressions

Expression …is a… Definition location
Block variable Nearest block
Property Template’s context
Argument Call site

(See pages 772 of Rock and Roll with Ember Octane)

Routing & outlet

  • Everything gets routed through the application.hbs, useful for:
    • Setting the language
    • Fetching data to render a common layout
    • Basically to define structure & common markup across your application
  • Subroutes render their content into the parent template , & the child routes also inherit their parents’ model()
  •   this.route('bands'); == this.route('bands', { path: 'bands' }, function () {});
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    - `bands.band.songs` = full name. Entities in:
    - `app/routes/bands/band/songs.js`
    - `app/templates/bands/band/songs.hbs`
    - The top-level route will be executed once, and by using `this.modelFor("modelname");` things can be passed down to the lower levels
    - This has some implications! If you load in things in bands.band, navigate to bands.band.details, and then to `<LinkTo @route="bands.band>`, it will actually link to bands.band.index. That does *not* require it to pass bands.band, and thus it won't, and the loading happening within bands.band won't execute
    - But don't change the LinkTo to bands.band.index, because then it will goof some CSS stuff; bands.band won't be the active route then, but bands.band.index. Bands.band.index is not a parent of bands.band.songs or bands.band.details, so those links won't be marked as active and that's just goofy and also this is super hard to explain so check page 166 of Rock & Roll with EmberJS in case of doubt.
    - Also, if you do `<LinkTo @model={{item.band}}>` instead of `<LinkTo @model={{item.band.id}}>` (thus the dynamic segment of :id and a context object (band object)) the model hook will only fire on a fresh reload, and not from pressing the link
    - You can use `modelFor` to get the parent routes' model.
    ```js
    export default class BandsBandSongsRoute extends Route {
    model() {
    let band = this.modelFor('bands.band');
    return band.songs;
    }
    }

Components

  • Mostly self contained so they can be moved across applications
  • Call using <CamelisedName> to distinguish from html
  • Previously used to only be called using {curly braces}

Controllers

  • The @action decorator (from '@ember/object') is only needed if the function gets called from the template
  • Controllers are singletons (single instances). If that gives any issues, use resetController(controller) { /* reset values here */ }
  • The on method creates listeners. On components, you have to use off to unregister, but controllers are only created once and remain until the app is closed, so off is not necessary.
  • Controllers seem to be in a weird limbo state in terms of how long they’ll remain useful or best practice, as often a component or route can better be utilised. The best rule of thumb I’ve found is from Rock & Roll with Ember: The best way to think about [controllers] is that they are top-level components for a specific route, with a few peculiarities...

Routes hook order

  1. beforeModel(transition){}
  2. model(params, transition){}
  3. afterModel(model, transition){}
  4. setupController(controller, model, transitions)

Sequence example: application(beforeModel -> model ->afterModel) –> route(beforeModel –> model –> afterModel)–> subroute(beforeModel –> model –> afterModel) –>application(setupController) –> route(setupController) –> subroute(setupController)

There’s also activate (fires when new route entered, but not when the model changes) and deactivate (fired when a route is completely exited, and not fired when only the model changes)

Loading/Error templates funky town

(All the following is written for loading, but it can normally just be interswitched with error for, errors)

  • Okay so putting a loading.hbs on any level in templates/ seems to automatically use it while any of its siblings in the route are loading and it just works I think help.
  • To override this behaviour, you can use
    1
    2
    3
    4
    5
    6
    7
    8
    export default class ExampleRoute extends Route {
    // ...
    @action
    loading() {
    // Do things here
    }
    // ...
    }
  • For a route specific one, you can make routename-loading.hbs
  • The lookup order works from most specific > least specific:
    • route.sub.place-loading
    • route.sub.loading / route.sub-loading
    • route.loading / route-loading
    • loading / application.loading

Data down actions up

The best explanation is this diagram created by Andy del Valle!
The actions-up-data-down diagram created by Andy del Valle

Testing

Okay this is a bit funky town.

  • As with testing any project, Ember test can be written before or after developing things.
  • You can seemingly create any folder structure you want, as long as your filename follows *-test.js.
  • The default testing framework is qunit but this can be replaced.
    • The Ember.js docs mention Mocha/Chai DOM.
    • I’ve seen a lot of people online use Sinonjs, especially due to the time manipulation capabilities.
  • You can use Ember(cli) Mirage to stub servers.

There are a few types of tests, with examples imported & simplified from Ember docs

  • Unit tests

    • Fast, tests specific functions & code.
    • Default for adapters, controllers, initializers, models, serializers, services & utilities according to the docs. But it seems that these are also the default for routes
    • Looks like the following:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      module('Unit | Controller | posts', function(hooks) {
      setupTest(hooks);

      test('should update A and B on setProps action', function(assert) {
      assert.expect(4);
      let controller = this.owner.lookup('controller:posts');
      controller.send('setProps', 'Testing Rocks!');
      assert.equal(controller.propB, 'Testing Rocks!', 'propB updated');
      });
      });
  • Rendering/integration tests

    • Okay speed, test individual rendering
    • Default for components & helpers
    • Rendering is the new name, but currently ember cli seems to still use integration.
    • Looks like the following:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      module('Integration | Helper | format currency', function(hooks) {
      setupRenderingTest(hooks);

      test('formats 199 with $ as currency sign', async function(assert) {
      this.set('value', 199);
      this.set('sign', '$');
      await render(hbs`{{format-currency value sign=sign}}`);
      assert.equal(this.element.textContent.trim(), '$1.99');
      });
      });
  • Application/acceptance tests

    • Slow, tests the total package
    • View how different things interact with eachother.
    • Application is the new name, but currently ember cli seems to still use acceptance.
    • Looks like the following:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      module('Acceptance | posts', function(hooks) {
      setupApplicationTest(hooks);

      test('should add new post', async function(assert) {
      await visit('/posts/new');
      await fillIn('[data-test-field="Title"]', 'My new post');
      await click('[data-test-button="Save"]');
      assert
      .dom('[data-test-post-title]')
      .hasText('My new post', 'The user sees the correct title.');
      });
      });

Testing route-specific rendering

Imagine this: you have a route that works independant of anything else. A good example would be a contact form!
Testing its internal stuff would be ideal for unit tests. But what if you wanna test the rendering of the route, independant of the rest of the application?

Unit tests are out of the question: they do not have any rendering capabilities. Unit route testing is for internal functions & actions, not the rendering.

So instead, with the information gained from the Ember docs/written above, my instinct would say “rendering test”. But I haven’t been able to see any way to render routes independently à la render(hbs`{{route}}`).

So acceptance testing is the only option left, but Ember doesn’t suggest nor facilitate route-specific acceptance testing by default. ember generate route-test creates a unit test. The auto-generated test is also unit. The visit helper (that seems to be the main method of selecting & rendering what to test in acceptance testing) does not take a route name. It only takes a path, and that would be an incredibly shakey way to test. I did find a workaround for this, so at the moment that seems like the ‘best’ option.


Commands

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# Install Ember-cli
npm install -g ember-cli

# New project with embroider enabled
ember new $projectname --no-welcome --embroider

# Run Ember project
ember server
ember s

# Common ember generators
ember generate {route,template,controller,service,helper} $routename
ember g {route,template,controller,service,helper} $routename

# Generate component...
ember g component $componentname # ...hbs
ember g component $componentname --with-component-class # ...hbs & .js
ember g component-class $componentclassname

# Generate dynamic route
ember g route bands/band --path=':id'

# Destroy route (removes corresponding routes/, templates/, tests/)
ember destroy route $routename

# Test
ember test
ember t
ember t --server # Open a browser & rerun tests with every change

ember g acceptance-test $testname

# Mirage (back-end mocking)
ember install ember-cli-mirage
ember g mirage-model $modelname

How-to

Tracked property

  • “If a property is used in the template, it should be marked as tracked”
  • Getters (in components at least) are auto-tracked
  • Make sure to track the deepest object:
    • this.storage.bands.push() –> this.storage doesn’t get changed, so tracking that won’t work; track this.storage.bands instead.
    • You might have to use this.storage.bands = tracked([]), which you can get after running and importing ember i tracked-built-ins

Also, in my personal experience, objects + tracked-built-ins works the best. But I might be goofing things.

1
2
3
4
5
import { tracked } from '@glimmer/tracking';

class Band {
@tracked name;
// ...

LinkTo dynamic path/model

1
2
3
4
5
6
7
8
9
{{!-- LinkTo using a single dynamic segment --}}
<LinkTo @route="bands.band.songs" @model={{band.id}}>
{{band.name}}
</LinkTo>

{{!-- LinkTo using multiple dynamic segments --}}
<LinkTo @route="bands.band.songs" @model={{array band1.id, band2.id}}>
{{band.name}}
</LinkTo>

Component with optional block

1
2
3
4
5
6
7
8
9
{{#if (has-block)}}
{{yield this.data}}
{{else}}
{{#each this.data as |data|}}
<input type="text"
placeholder={{data}}
/>
{{/each}}
{{/if}}

Install & use a CSS framework through NPM & PostCSS

1
2
npm install -D ember-cli-postcss  # Allows using postcss
npm install -D $npm-css-framework
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ember-cli-build.js
const EmberApp = require('ember-cli/lib/broccoli/ember-app');

module.exports = function (defaults) {
let app = new EmberApp(defaults, {
// ...
postcssOptions: {
compile: {
plugins: [{ module: require('tailwindcss') }],
},
},
// ...
});
// ...
}
// ...
1
2
3
4
/* app/styles/app.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

Router service

1
2
3
4
5
6
7
8
9
10
11
import { inject as service } from '@ember/service';

export default class NewController extends Controller {
@service router;

@action
save() {
// ...
this.router.transitionTo('models.model.property', model.id);
}
}

Query parameters

1
2
3
4
5
6
7
8
9
export default class BandsBandSongsRoute extends Route {
// ...
queryParams = {
sortBy: {
as: 's',
}
}
// ...
}

Change controller value from a component

Components are isolated, but you can still do this with a bit of a roundabout way:

1
2
3
4
5
6
7
8
9
10
<!-- Template with controller -->
<SortButton
@sortBy={{fn (set this 'sortBy' '-rating')}}
/>

<!-- Component -->
<button
type="button"
{{on "click" (fn @sortBy)}}
></button>

Testing

Use ember install ember-test-selectors! It puts data-test- automatically on components, but strips those in production.

For assert.dom() functions, check the qunit-dom api docs.

Ember Test Helper visit with route name

1
2
let router = this.owner.lookup('service:router');
await visit(router.urlFor('route.name'));

Custom helper function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
export function testSelector(name, psuedoClass = '') {
let selector = `[data-test-rr="${name}"]`;
if (psuedoClass != '') {
selector += `${psuedoClass}`;
}
return selector;
}

export async function dataTestSteps(...args) {
for (let i = 0; i < args.length; i += 2) {
let func = args[i];
let target = args[i + 1];
let selector;
if (target.startsWith('[')) {
selector = target;
} else {
selector = testSelector(target);
}

// If no additional parameter
if (typeof args[i + 2] === 'function') {
await func(selector);
}
// If additional parameter
else {
await func(selector, args[i + 2]);
i++; // Skip the additional parameter next step
}
}
}

/* This code allows the following syntax:
await dataTestSteps(
click,
testSelector('band-link', ':first-child'),
click,
'songs-nav-item'
);
*/

Using a helper in templates & js simulteanously

1
2
3
4
5
6
7
8
9
10
// app/helpers/example.js
// Original: export default helper(function example(positional) {
// Replace with:
export function example(positional) {
// ...
return "";
}

export default helper(example);

Source: Rarwe

Deploying on surge using ember-cli-surge

1
2
3
4
5
6
- name: Use ember-cli-surge to build & deploy
env:
SURGE_LOGIN: ${{ secrets.surgeLogin }}
SURGE_TOKEN: ${{ secrets.surgeToken }}
run: ./deploy.sh
shell: bash
1
2
3
4
#!/bin/bash
# This fixes ember surge cli not auto exiting, thus forever keeping the GitHub Action going
npx ember surge &
sleep 180

Learning.md: Notes from the SPARQL youtube series by Wouter Beek

SPARQL

Notes I made while watching Wouter Beek’s series on SparQL. It should be noted

Terms

Syntax:

  • ?variable
  • <absolute-uri>
  • end of a tripple pattern.
  • repeat the subject;
  • repeat the subject and predicate,

?s ?p ?o –> subject/predicate/object


Notation Example
Absolute IRI <http://www.w3.org/1999/02/22-rdf-syntax-ns#type>
Relative IRI <type> (requires base)
Prefixed IRI rdf:type (requires prefix)
Type IRI a (only in predicate position)

(Table imported from this video)


Symbol Notation type
. Simple triple
; Predicate list
, Object list

(Table imported from this video)


Example Datatype IRI
false xsd:boolean
11 xsd:integer
1.1 xsd:decimal
1.1e0 xsd:double
“abc” xsd:string

prefix xsd: <http://www.w3.org/2001/XMLSchema#>

(Table imported from here)


  • Okay so one thing you have to understand is that you can’t look at sparql like most code where it goes line-by-line. The entire thing gets parsed at once
  • Don’t interpet it as an SQL database. Just because a Record has a property called Track, doesn’t mean you can query for Tracks.
  • Also it’s case sensitive! mo:Record & mo:track work. mo:record & mo:Track breaks. (Usually, a capital means it’s a type, and a non-capital means it’s a predicate)
  • There are somewhat-standardised prefixes. See the Linked Open Vocabularies vocabs page (for some/all of them?)

Structure

Note: you can test out the triplydb/pokemon examples here

Okay, the value you provide and where you put ?variables changes things

1
2
3
WHERE {
?thing rdf:type ?type .
}

Queries everything with rdf:type for example

Basic spo select

1
2
3
4
5
6
# Prologue

SELECT ?s ?p ?o { # Projection: columns
?s ?p ?o. # Pattern: cells/content
}
limit x # Modifier: affects rows

Filter select on object value

  • ?p can be replaced with a predicate uri: ?p –> https://triplydb.com/academy/pokemon/vocab/colour
  • ?o can be replaced with a specific object: ?0 –> “yellow”
    1
    2
    3
    select ?pokemon {
    ?pokemon <https://triplydb.com/academy/pokemon/vocab/colour> "yellow"
    }

Bind (assigning variables)

1
2
3
4
5
select ?pokemon ?color ? greeting {
?pokemon <https://triplydb.com/academy/pokemon/vocab/colour> ?color.
bind("Hi!" as ?greeting)
}
limit 10

Literal string interpretation

1
2
3
4
select ?location {
# "literal"^^<how-to-interpet>
bind("Point(4.8649 52.33287)"^^<http://www.opengis.net/ont/geosparql#wktLiteral> as ?location)
}

Bind calculation

1
2
3
4
select ?pokemon ?weightKilograms ?weightPounds {
?pokemon <https://triplidb.com/academy/pokemon/vocab/weight> ?weightKilograms.
bind(?weightKilograms * "2.20462"^^<http://www.w3.org/2001/XMLSchema#float> as ?weightPounds)
}

Query against multiple of the same, only returning when there are multiple but allowing switching around/duplicate

1
2
3
4
5
6
7
select ?color ?cry ?name ?type1 ?type2 {
?pokemon <https://triplydb.com/academy/pokemon/vocab/colour> ?color.
?pokemon <https://triplydb.com/academy/pokemon/vocab/cry> ?cry.
?pokemon <http://www.w3.org/2000/01/rdf-schema#label> ?name.
?pokemon <https://triplydb.com/academy/pokemon/vocab/type> ?type1.
?pokemon <https://triplydb.com/academy/pokemon/vocab/type> ?type2.
}

Get an object from an object of an object

1
2
3
4
5
6
7
8
9
select ?color ?cry ?name ?type1Name ?type2Name {
?pokemon <https://triplydb.com/academy/pokemon/vocab/colour> ?color.
?pokemon <https://triplydb.com/academy/pokemon/vocab/cry> ?cry.
?pokemon <http://www.w3.org/2000/01/rdf-schema#label> ?name.
?pokemon <https://triplydb.com/academy/pokemon/vocab/type> ?type1.
?pokemon <https://triplydb.com/academy/pokemon/vocab/type> ?type2.
?type1 <http://www.w3.org/2000/01/rdf-schema#label> ?type1Name.
?type2 <http://www.w3.org/2000/01/rdf-schema#label> ?type2Name.
}

Abbrevations

Use prefixes to shorten the previous query

Most prefixes end with / or #

1
2
3
4
5
6
7
8
9
10
11
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix vocab: <https://triplydb.com/academy/pokemon/vocab/>
select ?color ?cry ?name ?type1Name ?type2Name {
?pokemon vocab:colour ?color.
?pokemon vocab:cry ?cry.
?pokemon rdfs:label ?name.
?pokemon vocab:type ?type1.
?pokemon vocab:type ?type2.
?type1 rdfs:label ?type1Name.
?type2 rdfs:label ?type2Name.
}

Use base IRI

1
2
3
4
5
6
7
8
9
10
11
base <https://triplydb.com/academy/pokemon/vocab/>
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
select ?color ?cry ?name ?type1Name ?type2Name {
?pokemon <colour> ?color.
?pokemon <cry> ?cry.
?pokemon rdfs:label ?name.
?pokemon <type> ?type1.
?pokemon <type> ?type2.
?type1 rdfs:label ?type1Name.
?type2 rdfs:label ?type2Name.
}

Empty alias

1
2
3
4
5
6
7
8
9
10
11
base <https://triplydb.com/academy/pokemon/vocab/>
prefix : <http://www.w3.org/2000/01/rdf-schema#>
select ?color ?cry ?name ?type1Name ?type2Name {
?pokemon <colour> ?color.
?pokemon <cry> ?cry.
?pokemon :label ?name.
?pokemon <type> ?type1.
?pokemon <type> ?type2.
?type1 :label ?type1Name.
?type2 :label ?type2Name.
}

Special IRI: rdf type

<http://www.3.org/1999/02/22-rdf-syntax-ns#type> -> a
(This can only be used in the predicate position, not object or subject)

1
2
3
4
5
6
7
8
9
10
11
12
base <https://triplydb.com/academy/pokemon/vocab/>
prefix : <http://www.w3.org/2000/01/rdf-schema#>
select ?color ?cry ?name ?type1Name ?type2Name {
?pokemon a ?class.
?pokemon <colour> ?color.
?pokemon <cry> ?cry.
?pokemon :label ?name.
?pokemon <type> ?type1.
?pokemon <type> ?type2.
?type1 :label ?type1Name.
?type2 :label ?type2Name.
}

In literals

1
2
3
4
prefix geo: <http://www.opengis.net/ont/geosparql#>
select ?location {
bind("Point(4.8649 52.33287)"^^geo:wktLiteral as ?location)
}

End-of-lines

1
2
3
4
5
6
7
8
9
10
11
12
base <https://triplydb.com/academy/pokemon/vocab/>
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>
select ?color ?cry ?name ?type1Name ?type2Name {
?pokemon a ?class;
<colour> ?color;
<cry> ?cry;
rdfs:label ?name;
<type> ?type1, # You could also type <type> ?type1, ?type2 on one line
?type2.

?type1 rdfs:label ?type1Name, ?type2Name.
}

Literal abbrevations (see table above)

1
2
3
4
5
6
prefix vocab: <https://triplydb.com/academy/pokemon/vocab/>
prefix xsd: <http://www.w3.org/2001/XMLSchema#>
select ?pokemon {
# ?pokemon vocab:weight "80"^^xsd:integer.
?pokemon vocab:weight 80.
}

Bind + functions + html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
prefix vocab: <https://triplydb.com/academy/pokemon/vocab/>
prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>

select ?color ?name ?text ?widget {
?pokemon
vocab:colour ?color;
vocab:cry ?cry;
rdfs:label ?name;
foaf:depiction ?image.
bind(concat("Hi!", " ", ?name) as ?text)

# bind(concat("202", "0")^^xsd:gYear as ?widget) # --> Error
# bind(strdt(concat("202", "0"), xsd:gYear) as ?widget) # --> No error

bind(strdt(concat("<h2 style='color:", ?color, "'>", ?name, "</h2><img src='", str(?image), "'><audio controls><source src='", str(?cry), "'></audio>"), rdf:HTML) as ?widget)
}
limit 10

Discover all available predicates

1
2
3
4
prefix mo: <http://purl.org/ontology/mo/>
SELECT DISTINCT ?p {
?record a mo:Record; ?p ?o.
}

(Thanks Felix!)

Also check out Sparklis & its examples! (Thanks Claire!)

Thanks to Wouter Beeks video series as well as my colleagues for not banning me from the rocket chat for my questions


Here’s some other queries I made that don’t fit in the above because they work but I barely understand them myself but they might contain wisdom somewhere

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
prefix mo: <http://purl.org/ontology/mo/>
prefix dc: <http://purl.org/dc/elements/1.1/>


SELECT ?title (SUM(?duration)/1000/60 AS ?total_duration_in_min) {
?record a mo:Record;
dc:title ?title;
mo:track ?track.
?track mo:duration ?duration.
}
GROUP BY ?title
HAVING((SUM(?duration)/(1000.0*60.0)) < 60)
ORDER BY DESC(?total_duration_in_min)
LIMIT 10

(Thanks Claire, Riad and Niels for dealing with my spam about this)