2017-06-14 67 views
2

我對Aurelia仍然很陌生,並發現它具有約束力,我現在正遇到一個有點奇怪的問題。Aurelia repeat.for爲分頁綁定問題

主要前提是我正在研究從API獲取數據的更新提要Web應用程序。數據被分成可以查看的塊(卡)。還有搜索/排序組件供用戶過濾卡片。這是我遇到問題的地方...

當用戶進行搜索或排序數據時,卡片會被過濾並正確顯示。但是,我試圖添加一個分頁組件,出於某種原因,模型數據綁定在這個組件中沒有完全更新。

下面的代碼(包括只是在情況下,它需要的一切):

更新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(); 
     }); 

    } 

} 

search-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)); 
    } 

    } 

} 

主要問題COMPONENT 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仍然可以在此導航中訪問。另外,即使它真的需要能夠在視圖模型中處理所有這些,因爲在顯示視圖之前仍然需要更多的處理。

回答

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"是更好的方法。因此,只有在必要時才使用計算屬性。

希望這會有所幫助!

0

從我所瞭解的一覽中,您是否嘗試更新反映this.feed.cardList中元素數量的分頁列表?

如果是這樣,你可以爲分頁您card-commit元素做一樣的,你在pager.html沒有以上,並使用與repeat.for$index陣列(feedData)像如下設置頁碼編號:

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

這應該陣列feedData其中page是元素(有益的,如果你需要設置你的清單中,或與陣列數據鏈路元素)和$index是內feedDatapage元素的索引創建的每一個元素列表元素。上面的代碼應該反映對feedData所做的任何更改,例如添加或刪除元素。

我覺得更像是$index$first$last,也許$odd$even,等等。另外,你可以做repeat.for="page of feedData.slice(0, 3)"或類似的東西,如果你想限制列表中元素的個數。我只是比較新的Aurelia(以及網絡開發),所以我不是專家。從我所知道的情況來看,沒有一些額外的工作,某些事情是不可觀察的。我注意到了這一點 - 更改對象數組中的對象鍵值並不反映在repeat.for中。在這裏可能是一個類似的問題。