Skip to content

Commit ddd8a7d

Browse files
committed
💈 Paginated Highscores using TSTableView and infinite scroll
1 parent 06728bb commit ddd8a7d

File tree

3 files changed

+123
-116
lines changed

3 files changed

+123
-116
lines changed

Assets/AppServices/table/query/CustomQuery.cs

Lines changed: 59 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
using UnityEngine;
44

55
/// <summary>
6-
/// Query records operation https://msdn.microsoft.com/en-us/library/azure/jj677199.aspx
6+
/// Implemention of Query records operation https://msdn.microsoft.com/en-us/library/azure/jj677199.aspx
77
/// There is a maximum of 50 records returned in a query - use top and skip params to return additional pages of results.
8-
/// NB: `$inlinecount` (which returns count of all items without paging applied) is not set here as it changes the data model and the way the REST decode callback works makes it intangible to decode.
9-
/// Rather the '$inlinecount=allpages' param is automically set when using the table's Query method and wrapping your data model with the NestedResults object wrapper.
8+
/// NB: `$inlinecount` (which returns count of all items without paging applied) is not set here as it changes the data model shape and the way the REST decode callback works makes it non-trival to decode.
9+
/// Rather the '$inlinecount=allpages' param is automatically set when using the table's Query method and wrapping your data model with the NestedResults object wrapper.
1010
/// </summary>
1111
namespace Unity3dAzure.AppServices
1212
{
@@ -20,74 +20,75 @@ public enum MobileServiceSystemProperty
2020
deleted = 0x8
2121
}
2222

23-
[CLSCompliant(false)]
24-
public class CustomQuery
25-
{
23+
[CLSCompliant (false)]
24+
public class CustomQuery
25+
{
2626
// query option parameters defined by the Open Data Protocol (OData)
27-
private string _filter;
28-
private string _orderBy;
29-
private uint _top;
30-
private uint _skip;
31-
private string _select;
27+
public string Filter;
28+
public string OrderBy;
29+
public uint Top;
30+
public uint Skip;
31+
public string Select;
3232
// other params
33-
private MobileServiceSystemProperty _systemProperties;
34-
private bool _includeDeleted;
33+
public MobileServiceSystemProperty SystemProperties;
34+
public bool IncludeDeleted;
3535

36-
public CustomQuery(string filter, string orderBy=null, uint top=0, uint skip=0, string select=null, MobileServiceSystemProperty systemProperties=MobileServiceSystemProperty.nil, bool includeDeleted=false)
37-
{
38-
_filter = filter; // return only rows that satisfy the specified filter predicate
39-
_orderBy = orderBy; // sort column by one or more columns: order can be specified in 'desc' or 'asc' order ('asc' is default)
40-
_top = top; // return the top n entities for any query
41-
_skip = skip; // the n of records to skip (used for paging results)
42-
_select = select; // defines new projection of data by specifying the columns
43-
_systemProperties = systemProperties; // list of system properties to be included in the response
44-
_includeDeleted = includeDeleted; // if table has soft delete enabled then deleted records will be included in the results
45-
}
36+
public CustomQuery (string filter = "", string orderBy = null, uint top = 0, uint skip = 0, string select = null, MobileServiceSystemProperty systemProperties = MobileServiceSystemProperty.nil, bool includeDeleted = false)
37+
{
38+
this.Filter = filter; // return only rows that satisfy the specified filter predicate
39+
this.OrderBy = orderBy; // sort column by one or more columns: order can be specified in 'desc' or 'asc' order ('asc' is default)
40+
this.Top = top; // return the top n entities for any query
41+
this.Skip = skip; // the n of records to skip (used for paging results)
42+
this.Select = select; // defines new projection of data by specifying the columns
43+
this.SystemProperties = systemProperties; // list of system properties to be included in the response
44+
this.IncludeDeleted = includeDeleted; // if table has soft delete enabled then deleted records will be included in the results
45+
}
4646

47-
public static CustomQuery OrderBy(string orderBy) {
48-
return new CustomQuery("", orderBy);
47+
public static CustomQuery CreateWithOrderBy (string orderBy)
48+
{
49+
return new CustomQuery ("", orderBy);
4950
}
5051

51-
public override string ToString()
52-
{
53-
string queryString = "";
54-
string q = "?";
55-
if (!string.IsNullOrEmpty(_filter)) {
56-
queryString += string.Format("{0}$filter=({1})", q, _filter);
57-
q = "&";
58-
}
59-
if (!string.IsNullOrEmpty(_orderBy)) {
60-
queryString += string.Format("{0}$orderby={1}", q, _orderBy);
61-
q = "&";
62-
}
63-
if (_top > 0) {
64-
queryString += string.Format("{0}$top={1}", q, _top.ToString());
52+
public override string ToString ()
53+
{
54+
string queryString = "";
55+
string q = "?";
56+
if (!string.IsNullOrEmpty (this.Filter)) {
57+
queryString += string.Format ("{0}$filter=({1})", q, this.Filter);
58+
q = "&";
59+
}
60+
if (!string.IsNullOrEmpty (this.OrderBy)) {
61+
queryString += string.Format ("{0}$orderby={1}", q, this.OrderBy);
6562
q = "&";
6663
}
67-
if (_skip > 0) {
68-
queryString += string.Format("{0}$skip={1}", q, _skip.ToString());
64+
if (this.Top > 0) {
65+
queryString += string.Format ("{0}$top={1}", q, this.Top.ToString ());
6966
q = "&";
7067
}
71-
if (!string.IsNullOrEmpty(_select)) {
72-
queryString += string.Format("{0}$select={1}", q, _select);
68+
if (this.Skip > 0) {
69+
queryString += string.Format ("{0}$skip={1}", q, this.Skip.ToString ());
7370
q = "&";
7471
}
75-
if (_systemProperties!=MobileServiceSystemProperty.nil) {
72+
if (!string.IsNullOrEmpty (this.Select)) {
73+
queryString += string.Format ("{0}$select={1}", q, this.Select);
74+
q = "&";
75+
}
76+
if (this.SystemProperties != MobileServiceSystemProperty.nil) {
7677
// NB: setting __systemproperties param doesn't seem to do anything different as these properties are all included by default, but we can append values to the 'select' param.
77-
if (!string.IsNullOrEmpty (_select)) {
78-
queryString += string.Format (",{0}", SystemPropertiesValues (_systemProperties));
78+
if (!string.IsNullOrEmpty (this.Select)) {
79+
queryString += string.Format (",{0}", SystemPropertiesValues (this.SystemProperties));
7980
}
80-
queryString += string.Format("{0}__systemproperties={1}", q, SystemPropertiesValues(_systemProperties));
81+
queryString += string.Format ("{0}__systemproperties={1}", q, SystemPropertiesValues (this.SystemProperties));
8182
q = "&";
8283
}
83-
if (_includeDeleted) {
84-
queryString += string.Format("{0}__includeDeleted=true", q);
84+
if (this.IncludeDeleted) {
85+
queryString += string.Format ("{0}__includeDeleted=true", q);
8586
}
86-
return EscapeURL(queryString);
87-
}
87+
return EscapeURL (queryString);
88+
}
8889

89-
private string EscapeURL(string query)
90-
{
90+
private string EscapeURL (string query)
91+
{
9192
string q = WWW.EscapeURL (query);
9293
StringBuilder sb = new StringBuilder (q);
9394
sb.Replace ("+", "%20"); // NB: replace space with '%20' instead of '+'
@@ -96,15 +97,15 @@ private string EscapeURL(string query)
9697
sb.Replace ("%26", "&");
9798
sb.Replace ("%3d", "=");
9899
sb.Replace ("%24", "$");
99-
return sb.ToString();
100-
}
100+
return sb.ToString ();
101+
}
101102

102-
private string SystemPropertiesValues(MobileServiceSystemProperty systemProperties)
103+
private string SystemPropertiesValues (MobileServiceSystemProperty systemProperties)
103104
{
104105
if (systemProperties == MobileServiceSystemProperty.nil) {
105106
return "";
106107
}
107-
return systemProperties.ToString().Replace(" ",""); // remove spaces from string
108+
return systemProperties.ToString ().Replace (" ", ""); // remove spaces from string
108109
}
109-
}
110+
}
110111
}

Assets/Scripts/HighscoresDemo.cs

Lines changed: 59 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,12 @@ public class HighscoresDemo : MonoBehaviour, ITableViewDataSource
4747
bool HasNewData = false; // to reload table view when data has changed
4848

4949
// infinite scroll vars
50+
private bool _isPaginated = false; // only enable infinite scrolling for paginated results
5051
private const float _infiniteScrollSize = 0.2f;
5152
private bool _isLoadingNextPage = false; // load once when scroll buffer is hit
52-
private bool _isInfiniteScroll = true; // enable inifine scrolling
5353
private const uint _noPageResults = 50;
5454
private uint _skip = 0; // no of records to skip
55-
private uint _total = _noPageResults; // default is no more records than requested
55+
private uint _totalCount = 0; // count value should be > 0 to paginate
5656

5757
[Space(10)]
5858
[SerializeField]
@@ -106,56 +106,6 @@ void Update ()
106106
}
107107
}
108108

109-
#region Infinite Scroll private methods for Highscores
110-
111-
void OnEnable()
112-
{
113-
_tableView.GetComponent<ScrollRect>().onValueChanged.AddListener(OnScrollValueChanged);
114-
}
115-
116-
void OnDisable()
117-
{
118-
_tableView.GetComponent<ScrollRect>().onValueChanged.RemoveListener(OnScrollValueChanged);
119-
}
120-
121-
private void OnScrollValueChanged(Vector2 newScrollValue)
122-
{
123-
// skip if not infinite scroll or if still loading next page of results
124-
if (!_isInfiniteScroll || _isLoadingNextPage)
125-
{
126-
return;
127-
}
128-
//Debug.Log (string.Format("Scroll y:{0} table view scroll: {1}/{2}", newScrollValue.y, _tableView.scrollY, _tableView.scrollableHeight));
129-
float scrollY = _tableView.scrollableHeight - _tableView.scrollY;
130-
float scrollBuffer = _infiniteScrollSize * _tableView.scrollableHeight;
131-
// scrollY is still at 'top' and so no need to load anything at this point
132-
if (scrollY > scrollBuffer)
133-
{
134-
return;
135-
}
136-
// scrollY has reached 'bottom' minus buffer size
137-
// only trigger request if there are more records to load
138-
if (_skip < _total)
139-
{
140-
_isLoadingNextPage = true;
141-
_skip += _noPageResults;
142-
Debug.Log (string.Format("Load next page @{0} scroll: {1}<{2}", _skip, scrollY, scrollBuffer));
143-
GetPageHighscores ();
144-
}
145-
}
146-
147-
// Tip: When infinite scrolling and using TSTableView's ReloadData() method I prefer to wrap "disable and enable scrollbar" calls around it to help prevent jumpy behaviour when continously dragging the scrollbar thumb.
148-
private void SetInteractableScrollbars(bool isInteractable)
149-
{
150-
Scrollbar[] scrollbars = _tableView.GetComponentsInChildren<Scrollbar> ();
151-
foreach (Scrollbar scrollbar in scrollbars) {
152-
//Debug.Log (string.Format("Scrollbar {0} is {1}",scrollbar.name, isInteractable));
153-
scrollbar.interactable = isInteractable;
154-
}
155-
}
156-
157-
#endregion
158-
159109
public void Login()
160110
{
161111
_client.Login(MobileServiceAuthenticationProvider.Facebook, _facebookAccessToken, OnLoginCompleted);
@@ -253,6 +203,7 @@ private void OnReadCompleted(IRestResponse<List<Highscore>> response)
253203
Debug.Log("OnReadCompleted data: " + response.ResponseUri +" data: "+ response.Content);
254204
List<Highscore> items = response.Data;
255205
Debug.Log("Read items count: " + items.Count);
206+
_isPaginated = false; // default query has max. of 50 records and is not paginated so disable infinite scroll
256207
_scores = items;
257208
HasNewData = true;
258209
}
@@ -268,13 +219,13 @@ private void OnReadNestedResultsCompleted(IRestResponse<NestedResults<Highscore>
268219
{
269220
Debug.Log("OnReadNestedResultsCompleted: " + response.ResponseUri +" data: "+ response.Content);
270221
List<Highscore> items = response.Data.results;
271-
_total = response.Data.count;
222+
_totalCount = response.Data.count;
272223
Debug.Log("Read items count: " + items.Count + "/" + response.Data.count);
273-
// append results if paginated, otherwise set
224+
_isPaginated = true; // nested query will support pagination
274225
if (_skip != 0) {
275-
_scores.AddRange (items);
226+
_scores.AddRange (items); // append results for paginated results
276227
} else {
277-
_scores = items;
228+
_scores = items; // set for first page of results
278229
}
279230
HasNewData = true;
280231
}
@@ -300,7 +251,7 @@ private void GetPageHighscores()
300251

301252
public void GetTopHighscores()
302253
{
303-
DateTime today = DateTime.Today.AddDays(-1);
254+
DateTime today = DateTime.Today;
304255
string day = today.ToString("s");
305256
string filter = string.Format("createdAt gt '{0}Z'", day); //string.Format("score gt {0}", 999);
306257
Debug.Log ("filter:" + filter);
@@ -512,7 +463,7 @@ private void UpdateUI()
512463

513464
#endregion
514465

515-
#region ITableViewDataSource
466+
#region TSTableView ITableViewDataSource
516467

517468
public int GetNumberOfRowsForTableView(TableView tableView)
518469
{
@@ -554,6 +505,56 @@ public void OnSelectedRow(Button button) {
554505
_score = score; // update editor with selected item
555506
}
556507

508+
#region Infinite Scroll private methods for Highscores
509+
510+
void OnEnable()
511+
{
512+
_tableView.GetComponent<ScrollRect>().onValueChanged.AddListener(OnScrollValueChanged);
513+
}
514+
515+
void OnDisable()
516+
{
517+
_tableView.GetComponent<ScrollRect>().onValueChanged.RemoveListener(OnScrollValueChanged);
518+
}
519+
520+
private void OnScrollValueChanged(Vector2 newScrollValue)
521+
{
522+
// skip if not paginated results, or if no items, or if busy already loading next page of results
523+
if (!_isPaginated || _totalCount==0 || _isLoadingNextPage)
524+
{
525+
return;
526+
}
527+
//Debug.Log (string.Format("Scroll y:{0} table view scroll: {1}/{2}", newScrollValue.y, _tableView.scrollY, _tableView.scrollableHeight));
528+
float scrollY = _tableView.scrollableHeight - _tableView.scrollY;
529+
float scrollBuffer = _infiniteScrollSize * _tableView.scrollableHeight;
530+
// scrollY is still at 'top' and so no need to load anything at this point
531+
if (scrollY > scrollBuffer)
532+
{
533+
return;
534+
}
535+
// scrollY has reached 'bottom' minus buffer size
536+
// only trigger request if there are more records to load
537+
if (_skip < _totalCount)
538+
{
539+
_isLoadingNextPage = true;
540+
_skip += _noPageResults;
541+
//Debug.Log (string.Format("Load next page @{0} scroll: {1}<{2}", _skip, scrollY, scrollBuffer));
542+
GetPageHighscores ();
543+
}
544+
}
545+
546+
// Tip: When infinite scrolling and using TSTableView's ReloadData() method I prefer to wrap "disable and enable scrollbar" calls around it to help prevent jumpy behaviour when continously dragging the scrollbar thumb.
547+
private void SetInteractableScrollbars(bool isInteractable)
548+
{
549+
Scrollbar[] scrollbars = _tableView.GetComponentsInChildren<Scrollbar> ();
550+
foreach (Scrollbar scrollbar in scrollbars) {
551+
//Debug.Log (string.Format("Scrollbar {0} is {1}",scrollbar.name, isInteractable));
552+
scrollbar.interactable = isInteractable;
553+
}
554+
}
555+
556+
#endregion
557+
557558
/// <summary>
558559
/// Handler to go to next scene
559560
/// </summary>

Assets/TSTableView/TableView.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ public void ReloadData() {
7272
this.isEmpty = m_rowHeights.Length == 0;
7373
ClearAllRows();
7474
if (this.isEmpty) {
75+
// reset content size when empty
76+
var rect = m_scrollRect.content.GetComponent<RectTransform> ();
77+
rect.localPosition = Vector2.zero;
78+
rect.sizeDelta = Vector2.zero;
79+
m_requiresReload = false;
7580
return;
7681
}
7782
m_cumulativeRowHeights = new float[m_rowHeights.Length];

0 commit comments

Comments
 (0)