Skip to content

Commit 13095af

Browse files
committed
add pagination too list collaborations
fixes PLAT-1547 PLAT-1594 Test Plan: Lots of things to test so here it goes - Check when you have less than 10 requests a load more link at the bottom does not show up - When you add more than 10 collaborations notice that there is a load more link at the bottom that you can click to get more collaborations. - configure one lticollaboration notice that the button doesn't have a drop down. - configure two lti collaborations notice there is a drop down listing said lticollaborations. - notice there is no way to get to past collaborations now thats it let me know if there is any problems Change-Id: Ieb7d4fca79d5ccc085a981f3a41e9bef0a0273c3 Reviewed-on: https://gerrit.instructure.com/82283 Reviewed-by: Matthew Sessions <msessions@instructure.com> Product-Review: Matthew Sessions <msessions@instructure.com> Tested-by: Jenkins QA-Review: Heath Hales <hhales@instructure.com>
1 parent 910cef8 commit 13095af

15 files changed

Lines changed: 234 additions & 50 deletions

app/jsx/collaborations/Collaboration.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,12 @@ define([
4040
let [context, contextId] = splitAssetString(ENV.context_asset_string);
4141

4242
return (
43-
<div className='Collaboration'>
43+
<div ref="wrapper" className='Collaboration'>
4444
<div className='Collaboration-body'>
4545
<a
4646
className='Collaboration-title'
4747
href={`/${context}/${contextId}/collaborations/${collaboration.id}`}
48+
target="_blank"
4849
>
4950
{collaboration.title}
5051
</a>

app/jsx/collaborations/CollaborationsApp.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ define([
4646
onItemClicked={this.openModal} />
4747

4848
{list.length
49-
? <CollaborationsList collaborations={list} deleteCollaboration={this.props.actions.deleteCollaboration} />
49+
? <CollaborationsList collaborationsState={this.props.applicationState.listCollaborations} getCollaborations={this.props.actions.getCollaborations} deleteCollaboration={this.props.actions.deleteCollaboration} />
5050
: <GettingStartedCollaborations ltiCollaborators={this.props.applicationState.ltiCollaborators}/>
5151
}
5252
<Modal
Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,42 @@
11
define([
22
'react',
3-
'./Collaboration'
4-
], (React, Collaboration) => {
3+
'./Collaboration',
4+
'../shared/load-more',
5+
'./store/store'
6+
], (React, Collaboration, LoadMore, {dispatch}) => {
57
class CollaborationsList extends React.Component {
8+
9+
constructor (props) {
10+
super(props);
11+
this.loadMoreCollaborations = this.loadMoreCollaborations.bind(this);
12+
}
13+
14+
loadMoreCollaborations () {
15+
React.findDOMNode(this.refs[`collaboration-${this.props.collaborationsState.list.length - 1}`]).focus();
16+
dispatch(this.props.getCollaborations(this.props.collaborationsState.nextPage));
17+
}
18+
619
render () {
720
return (
821
<div className='CollaborationsList'>
9-
{this.props.collaborations.map(c => (
10-
<Collaboration key={c.id} collaboration={c} deleteCollaboration={this.props.deleteCollaboration} />
11-
))}
22+
<LoadMore
23+
isLoading={this.props.collaborationsState.listCollaborationsPending}
24+
hasMore={!!this.props.collaborationsState.nextPage}
25+
loadMore={this.loadMoreCollaborations} >
26+
{this.props.collaborationsState.list.map((c, index) => (
27+
<Collaboration ref={`collaboration-${index}`} key={c.id} collaboration={c} deleteCollaboration={this.props.deleteCollaboration} />
28+
))}
29+
</LoadMore>
1230
</div>
1331
)
1432
}
1533
};
1634

1735
CollaborationsList.propTypes = {
18-
collaborations: React.PropTypes.array,
19-
deleteCollaboration: React.PropTypes.func
36+
collaborationsState: React.PropTypes.object.isRequired,
37+
deleteCollaboration: React.PropTypes.func.isRequired,
38+
getCollaborations: React.PropTypes.func.isRequired
2039
};
2140

22-
return CollaborationsList
41+
return CollaborationsList;
2342
})

app/jsx/collaborations/CollaborationsNavigation.jsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ define([
2020
return (
2121
<div className="ic-Action-header">
2222
<div className="ic-Action-header__Secondary">
23-
<a href={url} rel="external" className="Button">{I18n.t('Past Collaborations')}</a>
2423
{this.renderNewCollaborationsDropDown()}
2524
</div>
2625
</div>

app/jsx/collaborations/NewCollaborationsDropDown.jsx

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,42 @@ define([
1616

1717
render () {
1818
let [context, contextId] = splitAssetString(ENV.context_asset_string)
19+
const hasOne = this.props.ltiCollaborators.length === 1
1920
return (
2021
<div className="al-dropdown__container create-collaborations-dropdown">
21-
<button className="al-trigger Button Button--primary" aria-label={I18n.t('Add Collaboration')} role="button" href="#">{I18n.t('+ Collaboration')}</button>
22-
<ul className="al-options" role="menu" tabIndex="0" aria-hidden="true" aria-expanded="false" aria-activedescendant="new-collaborations-dropdown">
23-
{
24-
this.props.ltiCollaborators.map(ltiCollaborator => {
25-
let itemUrl = `/${context}/${contextId}/external_tools/${ltiCollaborator.id}?launch_type=collaboration`
26-
return(
27-
<li key={ltiCollaborator.id}>
28-
<a
29-
href={itemUrl}
30-
rel="external"
31-
role="menuitem"
32-
onClick={(e) => this.openModal(e, `${itemUrl}&display=borderless`)}
33-
>
34-
{ltiCollaborator.name}
35-
</a>
36-
</li>
37-
)
38-
})
39-
}
40-
</ul>
22+
{hasOne
23+
?
24+
<button
25+
className="Button Button--primary"
26+
aria-label={I18n.t('Add Collaboration')}
27+
onClick={(e) => this.openModal(e, `/${context}/${contextId}/external_tools/${this.props.ltiCollaborators[0].id}?launch_type=collaboration&display=borderless`)}
28+
>
29+
{I18n.t('+ Collaboration')}
30+
</button>
31+
:
32+
<div>
33+
<button className="al-trigger Button Button--primary" aria-label={I18n.t('Add Collaboration')} role="button" href="#">{I18n.t('+ Collaboration')}</button>
34+
<ul className="al-options" role="menu" tabIndex="0" aria-hidden="true" aria-expanded="false" aria-activedescendant="new-collaborations-dropdown">
35+
{
36+
this.props.ltiCollaborators.map(ltiCollaborator => {
37+
let itemUrl = `/${context}/${contextId}/external_tools/${ltiCollaborator.id}?launch_type=collaboration`
38+
return(
39+
<li key={ltiCollaborator.id}>
40+
<a
41+
href={itemUrl}
42+
rel="external"
43+
role="menuitem"
44+
onClick={(e) => this.openModal(e, `${itemUrl}&display=borderless`)}
45+
>
46+
{ltiCollaborator.name}
47+
</a>
48+
</li>
49+
)
50+
})
51+
}
52+
</ul>
53+
</div>
54+
}
4155
</div>
4256
)
4357
}

app/jsx/collaborations/actions/collaborationsActions.jsx

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
define([
2+
'jquery',
3+
'i18n!course_wizard',
24
'axios',
3-
'compiled/str/splitAssetString'
4-
], (axios, splitAssetString) => {
5+
'compiled/str/splitAssetString',
6+
'jsx/shared/parseLinkHeader'
7+
], ($, I18n, axios, splitAssetString, parseLinkHeader) => {
58
const actions = {}
69

710
actions.LIST_COLLABORATIONS_START = 'LIST_COLLABORATIONS_START';
811
actions.listCollaborationsStart = () => ({ type: actions.LIST_COLLABORATIONS_START });
912

1013
actions.LIST_COLLABORATIONS_SUCCESSFUL = 'LIST_COLLABORATIONS_SUCCESSFUL';
11-
actions.listCollaborationsSuccessful = (collaborations) => ({ type: actions.LIST_COLLABORATIONS_SUCCESSFUL, payload: collaborations });
14+
actions.listCollaborationsSuccessful = (payload) => ({ type: actions.LIST_COLLABORATIONS_SUCCESSFUL, payload});
1215

1316
actions.LIST_COLLABORATIONS_FAILED = 'LIST_COLLABORATIONS_FAILED';
1417
actions.listCollaborationsFailed = (error) => ({ type: actions.LIST_COLLABORATIONS_FAILED, error: true, payload: error });
@@ -40,14 +43,20 @@ define([
4043
actions.CREATE_COLLABORATION_FAILED = 'CREATE_COLLABORATION_FAILED'
4144
actions.createCollaborationFailed = (error) => ({ type: actions.CREATE_COLLABORATION_FAILED, payload: error, error: true })
4245

43-
actions.getCollaborations = (context, contextId) => {
44-
return (dispatch) => {
46+
actions.getCollaborations = (url) => {
47+
return (dispatch, getState) => {
4548
dispatch(actions.listCollaborationsStart());
4649

47-
let url = `/api/v1/${context}/${contextId}/collaborations`;
48-
$.getJSON(url)
49-
.success((collaborations) => dispatch(actions.listCollaborationsSuccessful(collaborations)))
50-
.fail((err) => dispatch(actions.listCollaborationsFailed(err)));
50+
axios.get(url)
51+
.then((response) => {
52+
let {next} = parseLinkHeader(response.headers.link)
53+
let payload = {next, collaborations: response.data}
54+
if (getState().list.length != 0) {
55+
$.screenReaderFlashMessageExclusive(I18n.t("Loaded More Collaborations."));
56+
}
57+
dispatch(actions.listCollaborationsSuccessful(payload))
58+
})
59+
.catch((err) => dispatch(actions.listCollaborationsFailed(err)));
5160
}
5261
};
5362

@@ -72,7 +81,7 @@ define([
7281
axios.delete(url)
7382
.then((response) => {
7483
dispatch(actions.deleteCollaborationSuccessful(collaborationId))
75-
dispatch(actions.getCollaborations(context, contextId))
84+
dispatch(actions.getCollaborations(`/api/v1/${context}/${contextId}/collaborations`))
7685
})
7786
.catch((err) => dispatch(actions.deleteCollaborationFailed(err)));
7887
}
@@ -87,7 +96,7 @@ define([
8796
}})
8897
.then(({ data }) => {
8998
dispatch(actions.createCollaborationSuccessful(data))
90-
dispatch(actions.getCollaborations(context, contextId))
99+
dispatch(actions.getCollaborations(`/api/v1/${context}/${contextId}/collaborations`))
91100
})
92101
.catch(error => {
93102
dispatch(actions.createCollaborationFailed(error))

app/jsx/collaborations/reducers/listCollaborationsReducer.jsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@ define([
1919
};
2020
},
2121
[ACTION_NAMES.LIST_COLLABORATIONS_SUCCESSFUL]: (state, action) => {
22+
let list = state.list.slice()
23+
list.push(...action.payload.collaborations)
2224
return {
2325
...state,
2426
listCollaborationsPending: false,
2527
listCollaborationsSuccessful: true,
26-
list: action.payload
28+
list,
29+
nextPage: action.payload.next
2730
}
2831
},
2932
[ACTION_NAMES.LIST_COLLABORATIONS_FAILED]: (state, action) => {
@@ -32,6 +35,20 @@ define([
3235
listCollaborationsPending: false,
3336
listCollaborationsError: action.payload
3437
}
38+
},
39+
[ACTION_NAMES.CREATE_COLLABORATION_SUCCESSFUL]: (state, action) => {
40+
return {
41+
...state,
42+
list: [],
43+
nextPage: null
44+
}
45+
},
46+
[ACTION_NAMES.DELETE_COLLABORATION_SUCCESSFUL]: (state, action) => {
47+
return {
48+
...state,
49+
list: [],
50+
nextPage: null
51+
}
3552
}
3653
};
3754

app/jsx/collaborations/router.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ define([
1313
*/
1414
function renderShowCollaborations (ctx) {
1515
store.dispatch(actions.getLTICollaborators(ctx.params.context, ctx.params.contextId));
16-
store.dispatch(actions.getCollaborations(ctx.params.context, ctx.params.contextId));
16+
store.dispatch(actions.getCollaborations(`/api/v1/${ctx.params.context}/${ctx.params.contextId}/collaborations`));
1717

1818
let view = () => {
1919
let state = store.getState();

app/jsx/shared/load-more.jsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
define([
2+
'react',
3+
'i18n!react_collaborations'
4+
], (React, i18n) => {
5+
class LoadMore extends React.Component {
6+
7+
static propTypes: {
8+
hasMore: React.PropTypes.bool.isRequired,
9+
loadMore: React.PropTypes.func.isRequired,
10+
isLoading: React.PropTypes.bool,
11+
children: React.PropTypes.any
12+
}
13+
14+
componentDidUpdate (oldProps) {
15+
let oldCount = React.Children.count(oldProps.children)
16+
let newCount = React.Children.count(this.props.children)
17+
// not first results and not on delete
18+
if (oldCount > 0 && newCount > oldCount) {
19+
let element = this.refs.parent.querySelector(`*:nth-child(${oldCount + 1}) .lor-result a`)
20+
if (element) {
21+
element.focus()
22+
}
23+
}
24+
}
25+
26+
render () {
27+
const hasChildren = React.Children.count(this.props.children) > 0
28+
const opacity = this.props.isLoading ? 1 : 0
29+
30+
return (
31+
<div className='LoadMore' ref='parent'>
32+
{this.props.children}
33+
34+
{this.props.hasMore && !this.props.isLoading &&
35+
<div className='LoadMore-button'>
36+
<button className='Button--link' onClick={this.props.loadMore}>
37+
{i18n.t('Load more results')}
38+
</button>
39+
</div>
40+
}
41+
42+
{hasChildren && this.props.hasMore &&
43+
<div
44+
aria-hidden={!this.props.isLoading}
45+
className='LoadMore-loader'>
46+
</div>
47+
}
48+
</div>
49+
)
50+
}
51+
};
52+
53+
LoadMore.propTypes = {
54+
hasMore: React.PropTypes.bool.isRequired,
55+
loadMore: React.PropTypes.func.isRequired,
56+
isLoading: React.PropTypes.bool,
57+
children: React.PropTypes.any
58+
};
59+
60+
return LoadMore;
61+
});

app/jsx/shared/parseLinkHeader.jsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
define([
2+
], () => {
3+
return function (linkHeader) {
4+
if (!linkHeader) {
5+
return []
6+
}
7+
var retVal = {}
8+
linkHeader.split(',').map((partOfHeader) => partOfHeader.split('; '))
9+
.forEach(function (link) {
10+
var myUrl = link[0].substring(1, link[0].length - 1)
11+
var urlRel = link[1].split('=')
12+
urlRel = urlRel[1]
13+
urlRel = urlRel.substring(1, urlRel.length - 1)
14+
15+
retVal[urlRel] = myUrl
16+
})
17+
return retVal
18+
}
19+
});

0 commit comments

Comments
 (0)