2

I am trying to build a web app with aurelia and I couldn't find a way to add data via AJAX after everything on page has been rendered. The scenario (simplified):

There's a page, some part of which is dynamically composed from a component (say, data-table). data-table has a title, and table-rows to show the data. data-table should load it's data dynamically via an AJAX call. What I want is, loading the data after the page is rendered.

I tried using promises but it does the opposite, ie, aurelia waits until the promise is resolved before attaching the view (explained by Jeremy Danyow as well: "...In this example Aurelia will wait for the Promise returned by the activate method to resolve before binding the view to the viewmodel." (in his post titled "ES7 async/await with Aurelia") This causes the page remain stalled until all data is loaded.

A simple code example is provided below. Here, if you navigate to this page, you won't see anything (or the page wont get attached) until all data is loaded. What I want is, to load the page and show "Table for ..." title, and at the same time start loading data in the background and show the table itself when loading completes. The desired behavior is illustrated in the following "mock" screenshots.

before the ajax requrest is completed

after the ajax request is completed

Additionally, the tables may need to be updated based on user choices (additional data may be loaded and added to the tables) or additional tables may need to be added to the page.

I don't think the desired behavior matches any of the bind/attached/detached etc. life-cycle behaviors (but could be wrong). This could be implemented utilizing a variant of body.onload (or jquery etc.) but I wonder if this is possible to do using aurelia only (or mostly).

Maybe, being able to load data after everything is attached (eg. a "postattached" callback) could help. In that case, I would load all necessary components with their already loaded data (eg. their titles) and show them. Then, in the "postattached" section I will start loading data.

Sample code:

test.ts

export class testPage {
  ids: number[] = [1,2,3] // for example 1,2,3; will be dynamically loaded as well
}

test.html

<template>
  <h1>Test</h1>

  <div repeat.for="id of ids">
    <compose view-model="./components/table" model.bind="id"></compose>
  </div>
</template>

table.ts

import { Loader } from './loader';

export class table  {
  id: number
  tableData: number[][] = []

  activate(model)  {
    this.id = model
  }

  attached() {
    Loader.LoadData(this.id).then((res)=>{this.tableData = res})
  }
}

table.html

<template>
  <h2>Table for ${id}</h2>

  <div repeat.for="rows of tableData">${rows}</div>
</template>

loader.ts

export class Loader {
  static LoadData(tid): Promise<number[][]> { //simple stub to imitate real data loading
    let data: number[][] = []
    switch (tid) {
      case 1:
        data.push([11, 12, 13])
        data.push([14, 15, 16])
        break;
      case 2:
        data.push([21, 22, 23])
        data.push([24, 25, 26])
        break;
      case 3:
        data.push([31, 32, 33])
        data.push([34, 35, 36])
        break;
    }
    this.sleep()

    return new Promise((resolve, reject) => {
      this.sleep()
      resolve(data)
    })
  }

  protected static sleep(): boolean { // just to imitate loading time
    let miliseconds = Math.floor(Math.random() * (3 - 1 + 1) + 1);
    var currentTime = new Date().getTime();
    console.debug("Wait for a sec: " + miliseconds)
    while (currentTime + miliseconds * 1000 >= new Date().getTime()) {
    }
    return true
  }
}

edit: Corrected code which was miscarried to the example here

kyvanc
  • 23
  • 5
  • 1
    can't you use the `table` as a custom element instead of using `compose` ? - i.e. ` ... `; note you need to use other names other than `table` and `id` because these are html reserved – Ovidiu Dolha May 30 '17 at 12:38
  • Thanks for the quick response. Unfortunately, same thing happens (ie. all content is loaded, thus the page is stalled before all data loading is completed, before showing anything) with custom element as well. – kyvanc May 30 '17 at 14:31
  • hmm.. could you make a jsfiddle or something? i don't think that should be happening, so i wonder what's the issue. – Ovidiu Dolha May 30 '17 at 14:33
  • 1
    Firstly, you should try using `window.timeout` to emulate the loading time. Your sleep function will block all other execution. Also, `.then((res)=>{this.tableData = res})` replaces the reference to tableData which aurelia may not pick up. Try modifying the existing array by deleting existing items and adding the new ones in – dpix May 31 '17 at 02:06
  • thanks for all the responses. @dpix indeed the sleep was the culprit as it blocked the single threaded execution of js (my wrong assumption of promise behaving like a go routine!), so it was not the correct way to emulate loading. In the end wrote a simple http service to test and it worked. Just a note, aurelia was able to pickup the `this.tableData = res` as it updated the view when data was loaded. In the end the problem in the question was not about aurelia and ajax, so what should I do? Close it somehow? It seems I can't to accept your comment as an answer because it's a comment – kyvanc May 31 '17 at 10:06
  • You can answer your own question and accept that if you want to – Flores May 31 '17 at 14:20

1 Answers1

0

You should try using window.timeout to emulate the loading time. Because Javascript is single threaded, your sleep function will block all other execution on the thread.

This answer is possibly a bit over the top but explains in more detail how to write a sleep function in javascript: What is the JavaScript version of sleep()?

dpix
  • 1,936
  • 1
  • 12
  • 22