Aurelia repeat.for绑定分页问题

时间:2017-06-14 12:41:30

标签: typescript pagination aurelia aurelia-binding

我还是Aurelia的新手并且弄清楚它的约束力,我现在遇到了一个有点好奇的问题。

主要前提是我正在开发一个从API获取数据的更新Feed网络应用。数据被分成可供查看的块(卡)。还有搜索/排序组件供用户过滤卡片。这是我遇到问题的地方......

当用户对数据进行搜索或排序时,过滤并正确显示卡片。但是,我正在尝试添加分页组件,并且由于某种原因,模型数据绑定在此组件中没有完全更新。

以下是代码(包括所有需要的内容):

更新feed.html

<!--Main view component for the update feed. Will house all cards generated from update-feed.ts-->
<template>

  <!--Custom loader-->
  <load-screen if.bind="router.isNavigating || loading"></load-screen>

  <!--Search component-->
  <search-feed></search-feed>

  <!--Loader while fetching results-->
  <h1 if.bind="!results" class="default-msg" textContent.bind="defaultMessage"></h1>

  <!--If tasks found bind the pager-->
  <div if.bind="!loading && results" id="feed-main" role="main" class="container-fluid clear-top">

    <!--Pass data to pager to handle pagination-->
    <pager>
      <!--<card-commit></card-commit> //pager.html-->
    </pager>

  </div>

</template>

更新feed.ts

import BuildDash from '../../../../services/build-dash';
import {FeedData} from '../../../../services/feed-data';
import {bindable, inject} from 'aurelia-framework';

@inject(BuildDash, FeedData)
export class UpdateFeed {

  /**
   * Class will handle retrieving and displaying all card data
   */
  public buildDash;
  public loading: boolean;
  public results: boolean;
  public defaultMessage: string;
  public feedData: FeedData;

  // Prop for unit-test overriding of cardData with mockData
  public taskList: Array<any>;

  constructor(BuildDash, FeedData) {
    this.buildDash = BuildDash;
    this.feedData = FeedData;

    // Start loader
    this.loading = true;
    this.results = false;
    this.getDefaultMsg();
  }

  public activate() {

    /**
     * Using a setTimeout until a way to tap into the activate() method is solved.
     * This throws an error in unit tests 'Unhandled rejection Error: Not Found'
     * This error can be ignored since it calls a stubbed version of this in testing for the actual unit tests.
     */
    return setTimeout(() => this.getData(), 0);
  }

  public getDefaultMsg() {
    // Default message if there are no cards to display
    this.defaultMessage = this.loading ? 'Searching for tasks...' : 'You currently have no projects needing updated';
  }

  public getData() {

    // TODO: buildDash.build(portfolioId), portfolioId needs to be grabbed from current embedded portfolio page
    return this.buildDash.build(portfolioId).then(res => {

      // Primary data assignment
      return this.feedData.load(res.data);
    })
      .then(() => {
        console.log('Fetch complete');

        // Stop loader
        this.loading = false;

        // Cast FeedData to bool for results
        this.results = !!this.feedData.cardList;

        return this.getDefaultMsg();
      });

  }

}

搜索-feed.html

<template>

  <div class="container-fluid">
    <div class="search-bar">
      <form class="form-inline projector-forgive">

        <div class="refresh-btn">
          <button role="button" class="btn btn-primary form-inline projector-forgive" click.delegate="refreshFeed()">
            <span class="glyphicon glyphicon-refresh" aria-hidden="true"></span>
            Refresh Feed
          </button>
        </div>

        <div class="search-input">
          <select class="form-control" value.bind="selectedSort" change.delegate="sortDropDown()">
            <option repeat.for="option of sortOptions" model.bind="option.val">${option.text}</option>
          </select>

          <input class="form-control" type="search" placeholder="Search" value.bind="searchQuery" input.delegate="searchInput()"/>
        </div>

      </form>
    </div>
  </div>

</template>

搜索-feed.ts

import {bindable, inject} from 'aurelia-framework';
import {DashBoard} from '../../pages/pmdash/pmdash';
import {FeedData} from '../../services/feed-data';

@inject(DashBoard, FeedData)
export class SearchFeed {

  @bindable public searchQuery: string;
  @bindable public selectedSort: string;
  @bindable public sortOptions: Array<object>;
  public data: FeedData;
  public router: any;

  constructor(DashBoard, FeedData) {
    this.router = DashBoard.router;
    this.data = FeedData;

    this.sortOptions = [
      {'val': 'commLate',  'text': 'Commit Date Late'},
      {'val': 'commEarly', 'text': 'Commit Date Early'},
      {'val': 'taskAsc',   'text': 'Task Name (Ascending)'},
      {'val': 'taskDesc',  'text': 'Task Name (Descending)'},
      {'val': 'projAsc',   'text': 'Project Name (Ascending)'},
      {'val': 'projDesc',  'text': 'Project Name (Descending)'}
    ];

    this.searchQuery = sessionStorage.getItem('Query') ? sessionStorage.getItem('Query') : '';

    if (sessionStorage.getItem('Dropdown')) {
      this.selectedSort = sessionStorage.getItem('Dropdown');
    }

  }

  public refreshFeed() {
    // Full refresh of feed components
    this.router.navigateToRoute(
      this.router.currentInstruction.config.name,
      this.router.currentInstruction.params,
      { replace: true }
    );
  }

  public sortDropDown() {
    sessionStorage.setItem('Dropdown', this.selectedSort);
    return this.searchInput();
  }

  public searchInput() {
    // console.log(this.searchQuery);
    return this.data.getFeedData(this.searchQuery);
  }
}

馈data.ts

import {observable, noView} from 'aurelia-framework';

@noView()
export class FeedData {

  /**
   * Class to represent all card data in the update-feed.
   * Used to filter and render search results without modifying original API data
   */
  public cardListMaster: Array<any>;
  public cardList: Array<any>;
  public loadFlag: boolean;
  @observable public temp: Array<any>;

  constructor () {
    console.log('FeedData constructor');
    this.cardList = [];
    this.loadFlag = true;
  }

  public tempChanged() {
    // console.log('tempChanged: length', this.temp);
  }

  public load(data) {
    /**
     * Method used to prepare data for views during API calls
     */
    this.cardListMaster = data;
    this.temp = this.cardListMaster.slice();
    let query = sessionStorage.getItem('Query');
    return this.getFeedData(query);
  }

  public getFeedData(query) {

    let sort = sessionStorage.getItem('Dropdown');

    switch (sort) {
      case 'commLate':
        return this.sortCommitLate(query);
      case 'commEarly':
        return this.sortCommitEarly(query);
      case 'taskAsc':
        return this.sortTaskAsc(query);
      case 'taskDesc':
        return this.sortTaskDesc(query);
      case 'projAsc':
        return this.sortProjAsc(query);
      case 'projDesc':
        return this.sortProjDesc(query);
      default:
        return this.sortCommitLate(query);
    }
  }

  public sortCommitLate(query) {
    return this.searchInput(query).sort((a, b) => b['DE:pln_to_commit_delta'] - a['DE:pln_to_commit_delta']);
  }

  public sortCommitEarly(query) {
    return this.searchInput(query).sort((a, b) => a['DE:pln_to_commit_delta'] - b['DE:pln_to_commit_delta']);
  }

  public sortTaskAsc(query) {
    return this.searchInput(query).sort((a, b) => {

      const taskNameA = a.name.toLowerCase();
      const taskNameB = b.name.toLowerCase();

      if (taskNameA < taskNameB) {
        return -1;
      }
      if (taskNameA > taskNameB) {
        return 1;
      }
      return 0;
    });
  }

  public sortTaskDesc(query) {
    return this.searchInput(query).sort((a, b) => {

      const taskNameA = a.name.toLowerCase();
      const taskNameB = b.name.toLowerCase();

      if (taskNameA < taskNameB) {
        return 1;
      }
      if (taskNameA > taskNameB) {
        return -1;
      }
      return 0;
    });
  }

  public sortProjAsc(query) {
    return this.searchInput(query).sort((a, b) => {

      const projNameA = a.project.name.toLowerCase();
      const projNameB = b.project.name.toLowerCase();

      if (projNameA < projNameB) {
        return -1;
      }
      if (projNameA > projNameB) {
        return 1;
      }
      return 0;
    });
  }

  public sortProjDesc(query) {
    return this.searchInput(query).sort((a, b) => {

      const projNameA = a.project.name.toLowerCase();
      const projNameB = b.project.name.toLowerCase();

      if (projNameA < projNameB) {
        return 1;
      }
      if (projNameA > projNameB) {
        return -1;
      }
      return 0;
    });
  }

  public searchInput(query) {
    query = !query ? '' : query.toLowerCase();

    this.temp = this.cardListMaster.slice();
    let masterCopy = this.cardListMaster.slice();

    if (sessionStorage.getItem('Query') === query && !this.loadFlag) {

      return this.cardList;

    } else {

      sessionStorage.setItem('Query', query);

      let filteredList = masterCopy.filter(card => {
        for (const key in card) {
          if (String(card[key]).toLowerCase().includes(query)) {
            // console.log(card);
            return card;
          }
        }
      });

      this.loadFlag = false;
      Array.prototype.splice.apply(this.temp, [0, this.temp.length].concat(filteredList));
      return Array.prototype.splice.apply(this.cardList, [0, this.cardList.length].concat(this.temp));
    }

  }

}

主要问题组成部分 的 pager.ts

import {inject, observable} from 'aurelia-framework';
import {FeedData} from '../../services/feed-data';

@inject(FeedData)
export class Pager {

  public feed: FeedData;
  public feedData: Array<any>;
  public feedLength: number;
  public pageList: number;
  public cardsPerPage;
  public currentPage;
  public load: boolean;

  constructor(FeedData) {
    this.feed = FeedData;
    // this.loadPager();

    console.log('loadPager called');

    this.feedData = this.feed.cardList;
    this.feedLength = FeedData.cardList.length;

    this.cardsPerPage = 20;
    this.currentPage = 1;
    this.pageList = Math.ceil(this.feedLength / this.cardsPerPage);
    // FIXME: pageList not updating!
    // I've tried referencing this property just about every way I know how to, but no matter what it does not update like 'feedData' does automatically in the view

    console.log(this.pageList);

  }
}

pager.html

<template>

  <!--THIS WORKS-->
  <!-- feedData updates automatically with the model-->
  <template repeat.for="task of feedData">

    <!--Bind each tasks data to a card as we loop-->
    <card-commit

      task-id.bind="task.ID"
      task-name.bind="task.name"
      project-name.bind="task.project.name"
      assigned-to.bind="task.assignedTo.name"
      successors.bind="task.successors"
      commit-delta.bind="task['DE:pln_to_commit_delta']"
      commit-status.bind="task.commitStatus"
      planned-start-date.bind="task.plannedStartDate"
      planned-comp-date.bind="task.plannedCompletionDate"
      duration.bind="task.duration"
      actual-start.bind="task.actualStart"
      commit-date.bind="task.commitDate"
      condition.bind="task.condition"
      note.bind="task.lastNote"
      note-text.bind="task.lastNote.noteText"
      note-entry-date.bind="task.lastNote.entryDate"
      note-avatar-download-url.bind="task.lastNote.owner.avatarDownloadURL"
      note-owner-name.bind="task.lastNote.owner.name"

    ></card-commit>

  </template>

  <!--Pager Nav-->
  <nav aria-label="Page navigation">
    <ul class="pagination">

      <!--Previous Link-->
      <li class="page-item">
        <a class="page-link" href="#" aria-label="Previous">
          <span aria-hidden="true">&laquo;</span>
          <span class="sr-only">Previous</span>
        </a>
      </li>


      <!-- THIS DOES NOT WORK-->
      <!-- I have to manually refresh the page to get 'feedLength' to update-->
      <!--Pages-->
      <li repeat.for="page of feedLength" class="page-item">
        <a class="page-link" href="#">${page + 1}</a>
      </li>

      <!--Next Link-->
      <li class="page-item">
        <a class="page-link" href="#" aria-label="Next">
          <span aria-hidden="true">&raquo;</span>
          <span class="sr-only">Next</span>
        </a>
      </li>
    </ul>
  </nav>

</template>

同样,主要问题在于此寻呼机组件。我不明白为什么feedData会自动更新,而没有其他属性引用它。我该如何解决这个问题?

有点有趣的是,我可以在寻呼机导航中执行类似的操作,它会自动更新:

  <!--Pages-->
  <li repeat.for="page of feedData.length" class="page-item">
    <a class="page-link" href="#">${page + 1}</a>
  </li>

但是这当然并没有真正给我我所需要的东西,它所做的就是表明在这个导航中仍然可以访问feedData。此外,即使它确实如此,我真的需要能够在视图模型中处理所有这些,因为在视图中显示它之前仍然需要更多的处理。

2 个答案:

答案 0 :(得分:1)

这是预期的结果。当你这样做时:

this.feedLength = this.feed.cardList.length;

您要将原始值分配给feedLength,而不是可观察对象。

要解决您的问题,您必须使用计算属性:

@computedFrom('feed.cardList') //imported from aurelia-framework
get feedLength() {
  return this.feed.cardList.length
}

然后,您可以在视图中将其用作普通属性:

<li repeat.for="page of feedLength" class="page-item">
  <a class="page-link" href="#">${page + 1}</a>
</li>

无论如何,repeat.for="page of feedData.length"是一种更好的方法。因此,仅在必要时使用计算属性。

希望这有帮助!

答案 1 :(得分:0)

根据我的理解一目了然,您是否尝试更新反映this.feed.cardList中元素数量的分页列表?

如果是这样,您可以为pager.html card-commit元素中的feedData元素执行相同的分页操作,并对repeat.for使用数组($index)和<li repeat.for="page of feedData" class="page-item"> <a class="page-link" href="#">${$index + 1}</a> </li> 设置分页编号如下:

feedData

这应该为数组page中的每个元素创建一个list元素,其中$index是元素(如果需要使用数组中的数据设置列表或链接元素,则会很有用)和{{ 1}}是pagefeedData元素的索引。上述代码应反映对feedData所做的任何更改,例如添加或删除元素。

我认为还有更像“$index”,例如$first$last,可能还有$odd$even等。另外,您可以{ {1}}或类似内容,如果您想限制列表元素的数量。

仅供参考,我对Aurelia也是相对较新的(和网络开发),所以我不是专家。据我所知,某些事情在没有额外工作的情况下是不可观察的。我注意到了这一点 - 更改对象数组中的对象键值并不反映在repeat.for="page of feedData.slice(0, 3)"中。这可能是一个类似的问题。