Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
ff1a08cb
Commit
ff1a08cb
authored
Nov 03, 2014
by
E. Kolpakov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Paging for LibraryView added with JS tests.
parent
05817614
Hide whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
1964 additions
and
606 deletions
+1964
-606
cms/djangoapps/contentstore/views/item.py
+17
-1
cms/static/coffee/spec/main.coffee
+1
-0
cms/static/js/factories/container.js
+10
-12
cms/static/js/factories/library.js
+10
-12
cms/static/js/spec/views/library_container_spec.js
+489
-0
cms/static/js/spec/views/pages/container_spec.js
+458
-435
cms/static/js/views/container.js
+4
-0
cms/static/js/views/library_container.js
+164
-0
cms/static/js/views/pages/container.js
+38
-14
cms/static/js/views/paging_footer.js
+6
-0
cms/static/sass/elements/_pagination.scss
+120
-0
cms/static/sass/elements/_uploaded-assets.scss
+1
-114
cms/static/sass/elements/_xblocks.scss
+31
-0
cms/static/sass/style-app-extend1-rtl.scss
+1
-0
cms/static/sass/style-app-extend1.scss
+1
-0
cms/templates/container.html
+4
-1
cms/templates/js/mock/mock-container-paged-after-add-xblock.underscore
+283
-0
cms/templates/js/mock/mock-container-paged-xblock.underscore
+257
-0
cms/templates/library.html
+6
-2
common/lib/xmodule/xmodule/library_root_xblock.py
+40
-14
common/lib/xmodule/xmodule/video_module/video_handlers.py
+0
-1
lms/templates/studio_render_paged_children_view.html
+23
-0
No files found.
cms/djangoapps/contentstore/views/item.py
View file @
ff1a08cb
...
...
@@ -237,12 +237,28 @@ def xblock_view_handler(request, usage_key_string, view_name):
if
view_name
==
'reorderable_container_child_preview'
:
reorderable_items
.
add
(
xblock
.
location
)
paging
=
None
try
:
if
request
.
REQUEST
.
get
(
'enable_paging'
,
'false'
)
==
'true'
:
paging
=
{
'page_number'
:
int
(
request
.
REQUEST
.
get
(
'page_number'
,
0
)),
'page_size'
:
int
(
request
.
REQUEST
.
get
(
'page_size'
,
0
)),
}
except
ValueError
:
log
.
exception
(
"Couldn't parse paging parameters: enable_paging:
%
s, page_number:
%
s, page_size:
%
s"
,
request
.
REQUEST
.
get
(
'enable_paging'
,
'false'
),
request
.
REQUEST
.
get
(
'page_number'
,
0
),
request
.
REQUEST
.
get
(
'page_size'
,
0
)
)
# Set up the context to be passed to each XBlock's render method.
context
=
{
'is_pages_view'
:
is_pages_view
,
# This setting disables the recursive wrapping of xblocks
'is_unit_page'
:
is_unit
(
xblock
),
'root_xblock'
:
xblock
if
(
view_name
==
'container_preview'
)
else
None
,
'reorderable_items'
:
reorderable_items
'reorderable_items'
:
reorderable_items
,
'paging'
:
paging
}
fragment
=
get_preview_fragment
(
request
,
xblock
,
context
)
...
...
cms/static/coffee/spec/main.coffee
View file @
ff1a08cb
...
...
@@ -239,6 +239,7 @@ define([
"js/spec/views/assets_spec"
,
"js/spec/views/baseview_spec"
,
"js/spec/views/container_spec"
,
"js/spec/views/library_container_spec"
,
"js/spec/views/group_configuration_spec"
,
"js/spec/views/paging_spec"
,
"js/spec/views/unit_outline_spec"
,
...
...
cms/static/js/factories/container.js
View file @
ff1a08cb
define
([
'jquery'
,
'js/models/xblock_info'
,
'js/views/pages/container'
,
'jquery'
,
'
underscore'
,
'
js/models/xblock_info'
,
'js/views/pages/container'
,
'js/collections/component_template'
,
'xmodule'
,
'coffee/src/main'
,
'xblock/cms.runtime.v1'
],
function
(
$
,
XBlockInfo
,
ContainerPage
,
ComponentTemplates
,
xmoduleLoader
)
{
function
(
$
,
_
,
XBlockInfo
,
ContainerPage
,
ComponentTemplates
,
xmoduleLoader
)
{
'use strict'
;
return
function
(
componentTemplates
,
XBlockInfoJson
,
action
,
isUnitPage
)
{
var
templates
=
new
ComponentTemplates
(
componentTemplates
,
{
parse
:
true
}),
mainXBlockInfo
=
new
XBlockInfo
(
XBlockInfoJson
,
{
parse
:
true
});
xmoduleLoader
.
done
(
function
()
{
var
view
=
new
ContainerPage
({
return
function
(
componentTemplates
,
XBlockInfoJson
,
action
,
options
)
{
var
main_options
=
{
el
:
$
(
'#content'
),
model
:
mainXBlockInfo
,
model
:
new
XBlockInfo
(
XBlockInfoJson
,
{
parse
:
true
})
,
action
:
action
,
templates
:
templates
,
isUnitPage
:
isUnitPage
});
templates
:
new
ComponentTemplates
(
componentTemplates
,
{
parse
:
true
})
};
xmoduleLoader
.
done
(
function
()
{
var
view
=
new
ContainerPage
(
_
.
extend
(
main_options
,
options
));
view
.
render
();
});
};
...
...
cms/static/js/factories/library.js
View file @
ff1a08cb
define
([
'jquery'
,
'js/models/xblock_info'
,
'js/views/pages/container'
,
'jquery'
,
'
underscore'
,
'
js/models/xblock_info'
,
'js/views/pages/container'
,
'js/collections/component_template'
,
'xmodule'
,
'coffee/src/main'
,
'xblock/cms.runtime.v1'
],
function
(
$
,
XBlockInfo
,
ContainerPage
,
ComponentTemplates
,
xmoduleLoader
)
{
function
(
$
,
_
,
XBlockInfo
,
ContainerPage
,
ComponentTemplates
,
xmoduleLoader
)
{
'use strict'
;
return
function
(
componentTemplates
,
XBlockInfoJson
)
{
var
templates
=
new
ComponentTemplates
(
componentTemplates
,
{
parse
:
true
}),
mainXBlockInfo
=
new
XBlockInfo
(
XBlockInfoJson
,
{
parse
:
true
});
return
function
(
componentTemplates
,
XBlockInfoJson
,
options
)
{
var
main_options
=
{
el
:
$
(
'#content'
),
model
:
new
XBlockInfo
(
XBlockInfoJson
,
{
parse
:
true
}),
templates
:
new
ComponentTemplates
(
componentTemplates
,
{
parse
:
true
}),
action
:
'view'
};
xmoduleLoader
.
done
(
function
()
{
var
view
=
new
ContainerPage
({
el
:
$
(
'#content'
),
model
:
mainXBlockInfo
,
action
:
"view"
,
templates
:
templates
,
isUnitPage
:
false
});
var
view
=
new
ContainerPage
(
_
.
extend
(
main_options
,
options
));
view
.
render
();
});
};
...
...
cms/static/js/spec/views/library_container_spec.js
0 → 100644
View file @
ff1a08cb
define
([
"jquery"
,
"underscore"
,
"js/common_helpers/ajax_helpers"
,
"URI"
,
"js/models/xblock_info"
,
"js/views/library_container"
,
"js/views/paging_header"
,
"js/views/paging_footer"
],
function
(
$
,
_
,
AjaxHelpers
,
URI
,
XBlockInfo
,
PagedContainer
,
PagingContainer
,
PagingFooter
)
{
var
htmlResponseTpl
=
_
.
template
(
''
+
'<div class="xblock-container-paging-parameters" data-start="<%= start %>" data-displayed="<%= displayed %>" data-total="<%= total %>"/>'
);
function
getResponseHtml
(
options
){
return
'<div class="xblock" data-request-token="request_token">'
+
'<div class="container-paging-header"></div>'
+
htmlResponseTpl
(
options
)
+
'<div class="container-paging-footer"></div>'
+
'</div>'
}
var
PAGE_SIZE
=
3
;
var
mockFirstPage
=
{
resources
:
[],
html
:
getResponseHtml
({
start
:
0
,
displayed
:
PAGE_SIZE
,
total
:
PAGE_SIZE
+
1
})
};
var
mockSecondPage
=
{
resources
:
[],
html
:
getResponseHtml
({
start
:
PAGE_SIZE
,
displayed
:
1
,
total
:
PAGE_SIZE
+
1
})
};
var
mockEmptyPage
=
{
resources
:
[],
html
:
getResponseHtml
({
start
:
0
,
displayed
:
0
,
total
:
0
})
};
var
respondWithMockPage
=
function
(
requests
)
{
var
requestIndex
=
requests
.
length
-
1
;
var
request
=
requests
[
requestIndex
];
var
url
=
new
URI
(
request
.
url
);
var
queryParameters
=
url
.
query
(
true
);
// Returns an object with each query parameter stored as a value
var
page
=
queryParameters
.
page_number
;
var
response
=
page
===
"0"
?
mockFirstPage
:
mockSecondPage
;
AjaxHelpers
.
respondWithJson
(
requests
,
response
,
requestIndex
);
};
var
MockPagingView
=
PagedContainer
.
extend
({
view
:
'container_preview'
,
el
:
$
(
"<div><div class='xblock' data-request-token='test_request_token'/></div>"
),
model
:
new
XBlockInfo
({},
{
parse
:
true
})
});
describe
(
"Paging Container"
,
function
()
{
var
pagingContainer
;
beforeEach
(
function
()
{
var
feedbackTpl
=
readFixtures
(
'system-feedback.underscore'
);
setFixtures
(
$
(
"<script>"
,
{
id
:
"system-feedback-tpl"
,
type
:
"text/template"
}).
text
(
feedbackTpl
));
pagingContainer
=
new
MockPagingView
({
page_size
:
PAGE_SIZE
});
});
describe
(
"Container"
,
function
()
{
describe
(
"setPage"
,
function
()
{
it
(
'can set the current page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
respondWithMockPage
(
requests
);
expect
(
pagingContainer
.
collection
.
currentPage
).
toBe
(
0
);
pagingContainer
.
setPage
(
1
);
respondWithMockPage
(
requests
);
expect
(
pagingContainer
.
collection
.
currentPage
).
toBe
(
1
);
});
it
(
'should not change page after a server error'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
respondWithMockPage
(
requests
);
pagingContainer
.
setPage
(
1
);
requests
[
1
].
respond
(
500
);
expect
(
pagingContainer
.
collection
.
currentPage
).
toBe
(
0
);
});
});
describe
(
"nextPage"
,
function
()
{
it
(
'does not move forward after a server error'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
respondWithMockPage
(
requests
);
pagingContainer
.
nextPage
();
requests
[
1
].
respond
(
500
);
expect
(
pagingContainer
.
collection
.
currentPage
).
toBe
(
0
);
});
it
(
'can move to the next page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
respondWithMockPage
(
requests
);
pagingContainer
.
nextPage
();
respondWithMockPage
(
requests
);
expect
(
pagingContainer
.
collection
.
currentPage
).
toBe
(
1
);
});
it
(
'can not move forward from the final page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
1
);
respondWithMockPage
(
requests
);
pagingContainer
.
nextPage
();
expect
(
requests
.
length
).
toBe
(
1
);
});
});
describe
(
"previousPage"
,
function
()
{
it
(
'can move back a page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
1
);
respondWithMockPage
(
requests
);
pagingContainer
.
previousPage
();
respondWithMockPage
(
requests
);
expect
(
pagingContainer
.
collection
.
currentPage
).
toBe
(
0
);
});
it
(
'can not move back from the first page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
respondWithMockPage
(
requests
);
pagingContainer
.
previousPage
();
expect
(
requests
.
length
).
toBe
(
1
);
});
it
(
'does not move back after a server error'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
1
);
respondWithMockPage
(
requests
);
pagingContainer
.
previousPage
();
requests
[
1
].
respond
(
500
);
expect
(
pagingContainer
.
collection
.
currentPage
).
toBe
(
1
);
});
});
});
describe
(
"PagingHeader"
,
function
()
{
beforeEach
(
function
()
{
var
pagingFooterTpl
=
readFixtures
(
'paging-header.underscore'
);
appendSetFixtures
(
$
(
"<script>"
,
{
id
:
"paging-header-tpl"
,
type
:
"text/template"
}).
text
(
pagingFooterTpl
));
});
describe
(
"Next page button"
,
function
()
{
beforeEach
(
function
()
{
pagingContainer
.
render
();
});
it
(
'does not move forward if a server error occurs'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
respondWithMockPage
(
requests
);
pagingContainer
.
pagingHeader
.
$
(
'.next-page-link'
).
click
();
requests
[
1
].
respond
(
500
);
expect
(
pagingContainer
.
collection
.
currentPage
).
toBe
(
0
);
});
it
(
'can move to the next page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
respondWithMockPage
(
requests
);
pagingContainer
.
pagingHeader
.
$
(
'.next-page-link'
).
click
();
respondWithMockPage
(
requests
);
expect
(
pagingContainer
.
collection
.
currentPage
).
toBe
(
1
);
});
it
(
'should be enabled when there is at least one more page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
respondWithMockPage
(
requests
);
expect
(
pagingContainer
.
pagingHeader
.
$
(
'.next-page-link'
)).
not
.
toHaveClass
(
'is-disabled'
);
});
it
(
'should be disabled on the final page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
1
);
respondWithMockPage
(
requests
);
expect
(
pagingContainer
.
pagingHeader
.
$
(
'.next-page-link'
)).
toHaveClass
(
'is-disabled'
);
});
it
(
'should be disabled on an empty page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
AjaxHelpers
.
respondWithJson
(
requests
,
mockEmptyPage
);
expect
(
pagingContainer
.
pagingHeader
.
$
(
'.next-page-link'
)).
toHaveClass
(
'is-disabled'
);
});
});
describe
(
"Previous page button"
,
function
()
{
beforeEach
(
function
()
{
pagingContainer
.
render
();
});
it
(
'does not move back if a server error occurs'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
1
);
respondWithMockPage
(
requests
);
pagingContainer
.
pagingHeader
.
$
(
'.previous-page-link'
).
click
();
requests
[
1
].
respond
(
500
);
expect
(
pagingContainer
.
collection
.
currentPage
).
toBe
(
1
);
});
it
(
'can go back a page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
1
);
respondWithMockPage
(
requests
);
pagingContainer
.
pagingHeader
.
$
(
'.previous-page-link'
).
click
();
respondWithMockPage
(
requests
);
expect
(
pagingContainer
.
collection
.
currentPage
).
toBe
(
0
);
});
it
(
'should be disabled on the first page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
respondWithMockPage
(
requests
);
expect
(
pagingContainer
.
pagingHeader
.
$
(
'.previous-page-link'
)).
toHaveClass
(
'is-disabled'
);
});
it
(
'should be enabled on the second page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
1
);
respondWithMockPage
(
requests
);
expect
(
pagingContainer
.
pagingHeader
.
$
(
'.previous-page-link'
)).
not
.
toHaveClass
(
'is-disabled'
);
});
it
(
'should be disabled for an empty page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
AjaxHelpers
.
respondWithJson
(
requests
,
mockEmptyPage
);
expect
(
pagingContainer
.
pagingHeader
.
$
(
'.previous-page-link'
)).
toHaveClass
(
'is-disabled'
);
});
});
describe
(
"Page metadata section"
,
function
()
{
it
(
'shows the correct metadata for the current page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
),
message
;
pagingContainer
.
setPage
(
0
);
respondWithMockPage
(
requests
);
message
=
pagingContainer
.
pagingHeader
.
$
(
'.meta'
).
html
().
trim
();
expect
(
message
).
toBe
(
'<p>Showing <span class="count-current-shown">1-3</span>'
+
' out of <span class="count-total">4 total</span>, '
+
'sorted by <span class="sort-order">Date added</span> descending</p>'
);
});
});
describe
(
"Children count label"
,
function
()
{
it
(
'should show correct count on first page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
respondWithMockPage
(
requests
);
expect
(
pagingContainer
.
pagingHeader
.
$
(
'.count-current-shown'
)).
toHaveHtml
(
'1-3'
);
});
it
(
'should show correct count on second page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
1
);
respondWithMockPage
(
requests
);
expect
(
pagingContainer
.
pagingHeader
.
$
(
'.count-current-shown'
)).
toHaveHtml
(
'4-4'
);
});
it
(
'should show correct count for an empty collection'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
AjaxHelpers
.
respondWithJson
(
requests
,
mockEmptyPage
);
expect
(
pagingContainer
.
pagingHeader
.
$
(
'.count-current-shown'
)).
toHaveHtml
(
'0-0'
);
});
});
describe
(
"Children total label"
,
function
()
{
it
(
'should show correct total on the first page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
respondWithMockPage
(
requests
);
expect
(
pagingContainer
.
pagingHeader
.
$
(
'.count-total'
)).
toHaveText
(
'4 total'
);
});
it
(
'should show correct total on the second page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
1
);
respondWithMockPage
(
requests
);
expect
(
pagingContainer
.
pagingHeader
.
$
(
'.count-total'
)).
toHaveText
(
'4 total'
);
});
it
(
'should show zero total for an empty collection'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
AjaxHelpers
.
respondWithJson
(
requests
,
mockEmptyPage
);
expect
(
pagingContainer
.
pagingHeader
.
$
(
'.count-total'
)).
toHaveText
(
'0 total'
);
});
});
});
describe
(
"PagingFooter"
,
function
()
{
var
pagingFooter
;
beforeEach
(
function
()
{
var
pagingFooterTpl
=
readFixtures
(
'paging-footer.underscore'
);
appendSetFixtures
(
$
(
"<script>"
,
{
id
:
"paging-footer-tpl"
,
type
:
"text/template"
}).
text
(
pagingFooterTpl
));
});
describe
(
"Next page button"
,
function
()
{
beforeEach
(
function
()
{
// Render the page and header so that they can react to events
pagingContainer
.
render
();
});
it
(
'does not move forward if a server error occurs'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
respondWithMockPage
(
requests
);
pagingContainer
.
pagingFooter
.
$
(
'.next-page-link'
).
click
();
requests
[
1
].
respond
(
500
);
expect
(
pagingContainer
.
collection
.
currentPage
).
toBe
(
0
);
});
it
(
'can move to the next page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
respondWithMockPage
(
requests
);
pagingContainer
.
pagingFooter
.
$
(
'.next-page-link'
).
click
();
respondWithMockPage
(
requests
);
expect
(
pagingContainer
.
collection
.
currentPage
).
toBe
(
1
);
});
it
(
'should be enabled when there is at least one more page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
respondWithMockPage
(
requests
);
expect
(
pagingContainer
.
pagingFooter
.
$
(
'.next-page-link'
)).
not
.
toHaveClass
(
'is-disabled'
);
});
it
(
'should be disabled on the final page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
1
);
respondWithMockPage
(
requests
);
expect
(
pagingContainer
.
pagingFooter
.
$
(
'.next-page-link'
)).
toHaveClass
(
'is-disabled'
);
});
it
(
'should be disabled on an empty page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
AjaxHelpers
.
respondWithJson
(
requests
,
mockEmptyPage
);
expect
(
pagingContainer
.
pagingFooter
.
$
(
'.next-page-link'
)).
toHaveClass
(
'is-disabled'
);
});
});
describe
(
"Previous page button"
,
function
()
{
beforeEach
(
function
()
{
// Render the page and header so that they can react to events
pagingContainer
.
render
();
});
it
(
'does not move back if a server error occurs'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
1
);
respondWithMockPage
(
requests
);
pagingContainer
.
pagingFooter
.
$
(
'.previous-page-link'
).
click
();
requests
[
1
].
respond
(
500
);
expect
(
pagingContainer
.
collection
.
currentPage
).
toBe
(
1
);
});
it
(
'can go back a page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
1
);
respondWithMockPage
(
requests
);
pagingContainer
.
pagingFooter
.
$
(
'.previous-page-link'
).
click
();
respondWithMockPage
(
requests
);
expect
(
pagingContainer
.
collection
.
currentPage
).
toBe
(
0
);
});
it
(
'should be disabled on the first page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
respondWithMockPage
(
requests
);
expect
(
pagingContainer
.
pagingFooter
.
$
(
'.previous-page-link'
)).
toHaveClass
(
'is-disabled'
);
});
it
(
'should be enabled on the second page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
1
);
respondWithMockPage
(
requests
);
expect
(
pagingContainer
.
pagingFooter
.
$
(
'.previous-page-link'
)).
not
.
toHaveClass
(
'is-disabled'
);
});
it
(
'should be disabled for an empty page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
AjaxHelpers
.
respondWithJson
(
requests
,
mockEmptyPage
);
expect
(
pagingContainer
.
pagingFooter
.
$
(
'.previous-page-link'
)).
toHaveClass
(
'is-disabled'
);
});
});
describe
(
"Current page label"
,
function
()
{
it
(
'should show 1 on the first page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
respondWithMockPage
(
requests
);
expect
(
pagingContainer
.
pagingFooter
.
$
(
'.current-page'
)).
toHaveText
(
'1'
);
});
it
(
'should show 2 on the second page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
1
);
respondWithMockPage
(
requests
);
expect
(
pagingContainer
.
pagingFooter
.
$
(
'.current-page'
)).
toHaveText
(
'2'
);
});
it
(
'should show 1 for an empty collection'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
AjaxHelpers
.
respondWithJson
(
requests
,
mockEmptyPage
);
expect
(
pagingContainer
.
pagingFooter
.
$
(
'.current-page'
)).
toHaveText
(
'1'
);
});
});
describe
(
"Page total label"
,
function
()
{
it
(
'should show the correct value with more than one page'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
respondWithMockPage
(
requests
);
expect
(
pagingContainer
.
pagingFooter
.
$
(
'.total-pages'
)).
toHaveText
(
'2'
);
});
it
(
'should show page 1 when there are no assets'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
AjaxHelpers
.
respondWithJson
(
requests
,
mockEmptyPage
);
expect
(
pagingContainer
.
pagingFooter
.
$
(
'.total-pages'
)).
toHaveText
(
'1'
);
});
});
describe
(
"Page input field"
,
function
()
{
var
input
;
it
(
'should initially have a blank page input'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
respondWithMockPage
(
requests
);
expect
(
pagingContainer
.
pagingFooter
.
$
(
'.page-number-input'
)).
toHaveValue
(
''
);
});
it
(
'should handle invalid page requests'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
respondWithMockPage
(
requests
);
pagingContainer
.
pagingFooter
.
$
(
'.page-number-input'
).
val
(
'abc'
);
pagingContainer
.
pagingFooter
.
$
(
'.page-number-input'
).
trigger
(
'change'
);
expect
(
pagingContainer
.
collection
.
currentPage
).
toBe
(
0
);
expect
(
pagingContainer
.
pagingFooter
.
$
(
'.page-number-input'
)).
toHaveValue
(
''
);
});
it
(
'should switch pages via the input field'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
respondWithMockPage
(
requests
);
pagingContainer
.
pagingFooter
.
$
(
'.page-number-input'
).
val
(
'2'
);
pagingContainer
.
pagingFooter
.
$
(
'.page-number-input'
).
trigger
(
'change'
);
AjaxHelpers
.
respondWithJson
(
requests
,
mockSecondPage
);
expect
(
pagingContainer
.
collection
.
currentPage
).
toBe
(
1
);
expect
(
pagingContainer
.
pagingFooter
.
$
(
'.page-number-input'
)).
toHaveValue
(
''
);
});
it
(
'should handle AJAX failures when switching pages via the input field'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
pagingContainer
.
setPage
(
0
);
respondWithMockPage
(
requests
);
pagingContainer
.
pagingFooter
.
$
(
'.page-number-input'
).
val
(
'2'
);
pagingContainer
.
pagingFooter
.
$
(
'.page-number-input'
).
trigger
(
'change'
);
requests
[
1
].
respond
(
500
);
expect
(
pagingContainer
.
collection
.
currentPage
).
toBe
(
0
);
expect
(
pagingContainer
.
pagingFooter
.
$
(
'.page-number-input'
)).
toHaveValue
(
''
);
});
});
});
});
});
cms/static/js/spec/views/pages/container_spec.js
View file @
ff1a08cb
...
...
@@ -3,537 +3,560 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
"js/views/pages/container"
,
"js/models/xblock_info"
,
"jquery.simulate"
],
function
(
$
,
_
,
str
,
AjaxHelpers
,
TemplateHelpers
,
EditHelpers
,
ContainerPage
,
XBlockInfo
)
{
describe
(
"ContainerPage"
,
function
()
{
var
lastRequest
,
renderContainerPage
,
expectComponents
,
respondWithHtml
,
model
,
containerPage
,
requests
,
initialDisplayName
,
mockContainerPage
=
readFixtures
(
'mock/mock-container-page.underscore'
),
mockContainerXBlockHtml
=
readFixtures
(
'mock/mock-container-xblock.underscore'
),
mockBadContainerXBlockHtml
=
readFixtures
(
'mock/mock-bad-javascript-container-xblock.underscore'
),
mockBadXBlockContainerXBlockHtml
=
readFixtures
(
'mock/mock-bad-xblock-container-xblock.underscore'
),
mockUpdatedContainerXBlockHtml
=
readFixtures
(
'mock/mock-updated-container-xblock.underscore'
),
mockXBlockEditorHtml
=
readFixtures
(
'mock/mock-xblock-editor.underscore'
);
beforeEach
(
function
()
{
var
newDisplayName
=
'New Display Name'
;
EditHelpers
.
installEditTemplates
();
TemplateHelpers
.
installTemplate
(
'xblock-string-field-editor'
);
TemplateHelpers
.
installTemplate
(
'container-message'
);
appendSetFixtures
(
mockContainerPage
);
EditHelpers
.
installMockXBlock
({
data
:
"<p>Some HTML</p>"
,
metadata
:
{
display_name
:
newDisplayName
}
});
initialDisplayName
=
'Test Container'
;
model
=
new
XBlockInfo
({
id
:
'locator-container'
,
display_name
:
initialDisplayName
,
category
:
'vertical'
});
});
function
parameterized_suite
(
label
,
global_page_options
,
fixtures
)
{
describe
(
label
+
" ContainerPage"
,
function
()
{
var
lastRequest
,
getContainerPage
,
renderContainerPage
,
expectComponents
,
respondWithHtml
,
model
,
containerPage
,
requests
,
initialDisplayName
,
mockContainerPage
=
readFixtures
(
'mock/mock-container-page.underscore'
),
mockContainerXBlockHtml
=
readFixtures
(
fixtures
.
initial
),
mockXBlockHtml
=
readFixtures
(
fixtures
.
add_response
),
mockBadContainerXBlockHtml
=
readFixtures
(
'mock/mock-bad-javascript-container-xblock.underscore'
),
mockBadXBlockContainerXBlockHtml
=
readFixtures
(
'mock/mock-bad-xblock-container-xblock.underscore'
),
mockUpdatedContainerXBlockHtml
=
readFixtures
(
'mock/mock-updated-container-xblock.underscore'
),
mockXBlockEditorHtml
=
readFixtures
(
'mock/mock-xblock-editor.underscore'
);
afterEach
(
function
()
{
EditHelpers
.
uninstallMockXBlock
();
});
beforeEach
(
function
()
{
var
newDisplayName
=
'New Display Name'
;
lastRequest
=
function
()
{
return
requests
[
requests
.
length
-
1
];
};
respondWithHtml
=
function
(
html
)
{
var
requestIndex
=
requests
.
length
-
1
;
AjaxHelpers
.
respondWithJson
(
requests
,
{
html
:
html
,
"resources"
:
[]
},
requestIndex
);
};
renderContainerPage
=
function
(
test
,
html
,
options
)
{
requests
=
AjaxHelpers
.
requests
(
test
);
containerPage
=
new
ContainerPage
(
_
.
extend
(
options
||
{},
{
model
:
model
,
templates
:
EditHelpers
.
mockComponentTemplates
,
el
:
$
(
'#content'
)
}));
containerPage
.
render
();
respondWithHtml
(
html
);
};
expectComponents
=
function
(
container
,
locators
)
{
// verify expected components (in expected order) by their locators
var
components
=
$
(
container
).
find
(
'.studio-xblock-wrapper'
);
expect
(
components
.
length
).
toBe
(
locators
.
length
);
_
.
each
(
locators
,
function
(
locator
,
locator_index
)
{
expect
(
$
(
components
[
locator_index
]).
data
(
'locator'
)).
toBe
(
locator
);
});
};
EditHelpers
.
installEditTemplates
();
TemplateHelpers
.
installTemplate
(
'xblock-string-field-editor'
);
TemplateHelpers
.
installTemplate
(
'container-message'
);
appendSetFixtures
(
mockContainerPage
);
describe
(
"Initial display"
,
function
()
{
it
(
'can render itself'
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
expect
(
containerPage
.
$
(
'.xblock-header'
).
length
).
toBe
(
9
);
expect
(
containerPage
.
$
(
'.wrapper-xblock .level-nesting'
)).
not
.
toHaveClass
(
'is-hidden'
);
});
it
(
'shows a loading indicator'
,
function
()
{
requests
=
AjaxHelpers
.
requests
(
this
);
containerPage
.
render
();
expect
(
containerPage
.
$
(
'.ui-loading'
)).
not
.
toHaveClass
(
'is-hidden'
);
respondWithHtml
(
mockContainerXBlockHtml
);
expect
(
containerPage
.
$
(
'.ui-loading'
)).
toHaveClass
(
'is-hidden'
);
});
it
(
'can show an xblock with broken JavaScript'
,
function
()
{
renderContainerPage
(
this
,
mockBadContainerXBlockHtml
);
expect
(
containerPage
.
$
(
'.wrapper-xblock .level-nesting'
)).
not
.
toHaveClass
(
'is-hidden'
);
expect
(
containerPage
.
$
(
'.ui-loading'
)).
toHaveClass
(
'is-hidden'
);
});
EditHelpers
.
installMockXBlock
({
data
:
"<p>Some HTML</p>"
,
metadata
:
{
display_name
:
newDisplayName
}
});
it
(
'can show an xblock with an invalid XBlock'
,
function
()
{
renderContainerPage
(
this
,
mockBadXBlockContainerXBlockHtml
);
expect
(
containerPage
.
$
(
'.wrapper-xblock .level-nesting'
)).
not
.
toHaveClass
(
'is-hidden'
);
expect
(
containerPage
.
$
(
'.ui-loading'
)).
toHaveClass
(
'is-hidden'
);
});
initialDisplayName
=
'Test Container'
;
it
(
'inline edits the display name when performing a new action'
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
,
{
action
:
'new'
model
=
new
XBlockInfo
({
id
:
'locator-container'
,
display_name
:
initialDisplayName
,
category
:
'vertical'
});
expect
(
containerPage
.
$
(
'.xblock-header'
).
length
).
toBe
(
9
);
expect
(
containerPage
.
$
(
'.wrapper-xblock .level-nesting'
)).
not
.
toHaveClass
(
'is-hidden'
);
expect
(
containerPage
.
$
(
'.xblock-field-input'
)).
not
.
toHaveClass
(
'is-hidden'
);
});
});
describe
(
"Editing the container"
,
function
()
{
var
updatedDisplayName
=
'Updated Test Container'
,
getDisplayNameWrapper
;
afterEach
(
function
()
{
EditHelpers
.
cancelModalIfShowing
();
afterEach
(
function
()
{
EditHelpers
.
uninstallMockXBlock
();
});
getDisplayNameWrapper
=
function
()
{
return
containerPage
.
$
(
'.wrapper-xblock-field'
)
;
lastRequest
=
function
()
{
return
requests
[
requests
.
length
-
1
]
;
};
it
(
'can edit itself'
,
function
()
{
var
editButtons
,
displayNameElement
;
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
displayNameElement
=
containerPage
.
$
(
'.page-header-title'
);
// Click the root edit button
editButtons
=
containerPage
.
$
(
'.nav-actions .edit-button'
);
editButtons
.
first
().
click
();
// Expect a request to be made to show the studio view for the container
expect
(
str
.
startsWith
(
lastRequest
().
url
,
'/xblock/locator-container/studio_view'
)).
toBeTruthy
();
AjaxHelpers
.
respondWithJson
(
requests
,
{
html
:
mockContainerXBlockHtml
,
resources
:
[]
});
expect
(
EditHelpers
.
isShowingModal
()).
toBeTruthy
();
// Expect the correct title to be shown
expect
(
EditHelpers
.
getModalTitle
()).
toBe
(
'Editing: Test Container'
);
// Press the save button and respond with a success message to the save
EditHelpers
.
pressModalButton
(
'.action-save'
);
AjaxHelpers
.
respondWithJson
(
requests
,
{
});
expect
(
EditHelpers
.
isShowingModal
()).
toBeFalsy
();
// Expect the last request be to refresh the container page
expect
(
str
.
startsWith
(
lastRequest
().
url
,
'/xblock/locator-container/container_preview'
)).
toBeTruthy
();
AjaxHelpers
.
respondWithJson
(
requests
,
{
html
:
mockUpdatedContainerXBlockHtml
,
resources
:
[]
});
// Respond to the subsequent xblock info fetch request.
AjaxHelpers
.
respondWithJson
(
requests
,
{
"display_name"
:
updatedDisplayName
});
// Expect the title to have been updated
expect
(
displayNameElement
.
text
().
trim
()).
toBe
(
updatedDisplayName
);
});
respondWithHtml
=
function
(
html
)
{
var
requestIndex
=
requests
.
length
-
1
;
AjaxHelpers
.
respondWithJson
(
requests
,
{
html
:
html
,
"resources"
:
[]
},
requestIndex
);
};
it
(
'can inline edit the display name'
,
function
()
{
var
displayNameInput
,
displayNameWrapper
;
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
displayNameWrapper
=
getDisplayNameWrapper
();
displayNameInput
=
EditHelpers
.
inlineEdit
(
displayNameWrapper
,
updatedDisplayName
);
displayNameInput
.
change
();
// This is the response for the change operation.
AjaxHelpers
.
respondWithJson
(
requests
,
{
});
// This is the response for the subsequent fetch operation.
AjaxHelpers
.
respondWithJson
(
requests
,
{
"display_name"
:
updatedDisplayName
});
EditHelpers
.
verifyInlineEditChange
(
displayNameWrapper
,
updatedDisplayName
);
expect
(
containerPage
.
model
.
get
(
'display_name'
)).
toBe
(
updatedDisplayName
);
});
});
getContainerPage
=
function
(
options
)
{
var
default_options
=
{
model
:
model
,
templates
:
EditHelpers
.
mockComponentTemplates
,
el
:
$
(
'#content'
)
};
return
new
ContainerPage
(
_
.
extend
(
options
||
{},
global_page_options
,
default_options
));
};
describe
(
"Editing an xblock"
,
function
()
{
afterEach
(
function
()
{
EditHelpers
.
cancelModalIfShowing
();
});
renderContainerPage
=
function
(
test
,
html
,
options
)
{
requests
=
AjaxHelpers
.
requests
(
test
);
containerPage
=
getContainerPage
(
options
);
containerPage
.
render
();
respondWithHtml
(
html
);
};
it
(
'can show an edit modal for a child xblock'
,
function
()
{
var
editButtons
;
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
editButtons
=
containerPage
.
$
(
'.wrapper-xblock .edit-button'
);
// The container should have rendered six mock xblocks
expect
(
editButtons
.
length
).
toBe
(
6
);
editButtons
[
0
].
click
();
// Make sure that the correct xblock is requested to be edited
expect
(
str
.
startsWith
(
lastRequest
().
url
,
'/xblock/locator-component-A1/studio_view'
)).
toBeTruthy
();
AjaxHelpers
.
respondWithJson
(
requests
,
{
html
:
mockXBlockEditorHtml
,
resources
:
[]
expectComponents
=
function
(
container
,
locators
)
{
// verify expected components (in expected order) by their locators
var
components
=
$
(
container
).
find
(
'.studio-xblock-wrapper'
);
expect
(
components
.
length
).
toBe
(
locators
.
length
);
_
.
each
(
locators
,
function
(
locator
,
locator_index
)
{
expect
(
$
(
components
[
locator_index
]).
data
(
'locator'
)).
toBe
(
locator
);
});
expect
(
EditHelpers
.
isShowingModal
()).
toBeTruthy
();
});
};
it
(
'can show an edit modal for a child xblock with broken JavaScript'
,
function
()
{
var
editButtons
;
renderContainerPage
(
this
,
mockBadContainerXBlockHtml
);
editButtons
=
containerPage
.
$
(
'.wrapper-xblock .edit-button'
);
editButtons
[
0
].
click
();
AjaxHelpers
.
respondWithJson
(
requests
,
{
html
:
mockXBlockEditorHtml
,
resources
:
[]
describe
(
"Initial display"
,
function
()
{
it
(
'can render itself'
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
expect
(
containerPage
.
$
(
'.xblock-header'
).
length
).
toBe
(
9
);
expect
(
containerPage
.
$
(
'.wrapper-xblock .level-nesting'
)).
not
.
toHaveClass
(
'is-hidden'
);
});
expect
(
EditHelpers
.
isShowingModal
()).
toBeTruthy
();
});
});
describe
(
"Editing an xmodule"
,
function
()
{
var
mockXModuleEditor
=
readFixtures
(
'mock/mock-xmodule-editor.underscore'
),
newDisplayName
=
'New Display Name'
;
beforeEach
(
function
()
{
EditHelpers
.
installMockXModule
({
data
:
"<p>Some HTML</p>"
,
metadata
:
{
display_name
:
newDisplayName
}
it
(
'shows a loading indicator'
,
function
()
{
requests
=
AjaxHelpers
.
requests
(
this
);
containerPage
=
getContainerPage
();
containerPage
.
render
();
expect
(
containerPage
.
$
(
'.ui-loading'
)).
not
.
toHaveClass
(
'is-hidden'
);
respondWithHtml
(
mockContainerXBlockHtml
);
expect
(
containerPage
.
$
(
'.ui-loading'
)).
toHaveClass
(
'is-hidden'
);
});
});
afterEach
(
function
()
{
EditHelpers
.
uninstallMockXModule
();
EditHelpers
.
cancelModalIfShowing
();
});
it
(
'can save changes to settings'
,
function
()
{
var
editButtons
,
modal
,
mockUpdatedXBlockHtml
;
mockUpdatedXBlockHtml
=
readFixtures
(
'mock/mock-updated-xblock.underscore'
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
editButtons
=
containerPage
.
$
(
'.wrapper-xblock .edit-button'
);
// The container should have rendered six mock xblocks
expect
(
editButtons
.
length
).
toBe
(
6
);
editButtons
[
0
].
click
();
AjaxHelpers
.
respondWithJson
(
requests
,
{
html
:
mockXModuleEditor
,
resources
:
[]
it
(
'can show an xblock with broken JavaScript'
,
function
()
{
renderContainerPage
(
this
,
mockBadContainerXBlockHtml
);
expect
(
containerPage
.
$
(
'.wrapper-xblock .level-nesting'
)).
not
.
toHaveClass
(
'is-hidden'
);
expect
(
containerPage
.
$
(
'.ui-loading'
)).
toHaveClass
(
'is-hidden'
);
});
modal
=
$
(
'.edit-xblock-modal'
);
expect
(
modal
.
length
).
toBe
(
1
);
// Click on the settings tab
modal
.
find
(
'.settings-button'
).
click
();
// Change the display name's text
modal
.
find
(
'.setting-input'
).
text
(
"Mock Update"
);
// Press the save button
modal
.
find
(
'.action-save'
).
click
();
// Respond to the save
AjaxHelpers
.
respondWithJson
(
requests
,
{
id
:
model
.
id
it
(
'can show an xblock with an invalid XBlock'
,
function
()
{
renderContainerPage
(
this
,
mockBadXBlockContainerXBlockHtml
);
expect
(
containerPage
.
$
(
'.wrapper-xblock .level-nesting'
)).
not
.
toHaveClass
(
'is-hidden'
);
expect
(
containerPage
.
$
(
'.ui-loading'
)).
toHaveClass
(
'is-hidden'
);
});
// Respond to the request to refresh
respondWithHtml
(
mockUpdatedXBlockHtml
);
// Verify that the xblock was updated
expect
(
containerPage
.
$
(
'.mock-updated-content'
).
text
()).
toBe
(
'Mock Update'
);
it
(
'inline edits the display name when performing a new action'
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
,
{
action
:
'new'
});
expect
(
containerPage
.
$
(
'.xblock-header'
).
length
).
toBe
(
9
);
expect
(
containerPage
.
$
(
'.wrapper-xblock .level-nesting'
)).
not
.
toHaveClass
(
'is-hidden'
);
expect
(
containerPage
.
$
(
'.xblock-field-input'
)).
not
.
toHaveClass
(
'is-hidden'
);
});
});
});
describe
(
"xblock operations"
,
function
()
{
var
getGroupElement
,
NUM_COMPONENTS_PER_GROUP
=
3
,
GROUP_TO_TEST
=
"A"
,
allComponentsInGroup
=
_
.
map
(
_
.
range
(
NUM_COMPONENTS_PER_GROUP
),
function
(
index
)
{
return
'locator-component-'
+
GROUP_TO_TEST
+
(
index
+
1
);
}
);
getGroupElement
=
function
()
{
return
containerPage
.
$
(
"[data-locator='locator-group-"
+
GROUP_TO_TEST
+
"']"
);
};
describe
(
"
Deleting an xblock"
,
function
()
{
var
clickDelete
,
deleteComponent
,
deleteComponentWithSuccess
,
promptSpy
;
describe
(
"
Editing the container"
,
function
()
{
var
updatedDisplayName
=
'Updated Test Container'
,
getDisplayNameWrapper
;
beforeEach
(
function
()
{
promptSpy
=
EditHelpers
.
createPromptSpy
();
afterEach
(
function
()
{
EditHelpers
.
cancelModalIfShowing
();
});
clickDelete
=
function
(
componentIndex
,
clickNo
)
{
getDisplayNameWrapper
=
function
()
{
return
containerPage
.
$
(
'.wrapper-xblock-field'
);
};
// find all delete buttons for the given group
var
deleteButtons
=
getGroupElement
().
find
(
".delete-button"
);
expect
(
deleteButtons
.
length
).
toBe
(
NUM_COMPONENTS_PER_GROUP
);
it
(
'can edit itself'
,
function
()
{
var
editButtons
,
displayNameElement
;
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
displayNameElement
=
containerPage
.
$
(
'.page-header-title'
);
// click the requested delete button
deleteButtons
[
componentIndex
].
click
();
// Click the root edit button
editButtons
=
containerPage
.
$
(
'.nav-actions .edit-button'
);
editButtons
.
first
().
click
();
// click the 'yes' or 'no' button in the prompt
EditHelpers
.
confirmPrompt
(
promptSpy
,
clickNo
);
};
// Expect a request to be made to show the studio view for the container
expect
(
str
.
startsWith
(
lastRequest
().
url
,
'/xblock/locator-container/studio_view'
)).
toBeTruthy
();
AjaxHelpers
.
respondWithJson
(
requests
,
{
html
:
mockContainerXBlockHtml
,
resources
:
[]
});
expect
(
EditHelpers
.
isShowingModal
()).
toBeTruthy
();
deleteComponent
=
function
(
componentIndex
)
{
clickDelete
(
componentIndex
);
AjaxHelpers
.
respondWithJson
(
requests
,
{});
// Expect the correct title to be shown
expect
(
EditHelpers
.
getModalTitle
()).
toBe
(
'Editing: Test Container'
);
//
second to last request contains given component's id (to delete the component)
AjaxHelpers
.
expectJsonRequest
(
requests
,
'DELETE'
,
'/xblock/locator-component-'
+
GROUP_TO_TEST
+
(
componentIndex
+
1
),
null
,
requests
.
length
-
2
);
//
Press the save button and respond with a success message to the save
EditHelpers
.
pressModalButton
(
'.action-save'
);
AjaxHelpers
.
respondWithJson
(
requests
,
{
});
expect
(
EditHelpers
.
isShowingModal
()).
toBeFalsy
(
);
// final request to refresh the xblock info
AjaxHelpers
.
expectJsonRequest
(
requests
,
'GET'
,
'/xblock/locator-container'
);
};
// Expect the last request be to refresh the container page
expect
(
str
.
startsWith
(
lastRequest
().
url
,
'/xblock/locator-container/container_preview'
)).
toBeTruthy
();
AjaxHelpers
.
respondWithJson
(
requests
,
{
html
:
mockUpdatedContainerXBlockHtml
,
resources
:
[]
});
deleteComponentWithSuccess
=
function
(
componentIndex
)
{
deleteComponent
(
componentIndex
);
// Respond to the subsequent xblock info fetch request.
AjaxHelpers
.
respondWithJson
(
requests
,
{
"display_name"
:
updatedDisplayName
}
);
// verify the new list of components within the group
expectComponents
(
getGroupElement
(),
_
.
without
(
allComponentsInGroup
,
allComponentsInGroup
[
componentIndex
])
);
};
// Expect the title to have been updated
expect
(
displayNameElement
.
text
().
trim
()).
toBe
(
updatedDisplayName
);
});
it
(
"can delete the first xblock"
,
function
()
{
it
(
'can inline edit the display name'
,
function
()
{
var
displayNameInput
,
displayNameWrapper
;
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
deleteComponentWithSuccess
(
0
);
displayNameWrapper
=
getDisplayNameWrapper
();
displayNameInput
=
EditHelpers
.
inlineEdit
(
displayNameWrapper
,
updatedDisplayName
);
displayNameInput
.
change
();
// This is the response for the change operation.
AjaxHelpers
.
respondWithJson
(
requests
,
{
});
// This is the response for the subsequent fetch operation.
AjaxHelpers
.
respondWithJson
(
requests
,
{
"display_name"
:
updatedDisplayName
});
EditHelpers
.
verifyInlineEditChange
(
displayNameWrapper
,
updatedDisplayName
);
expect
(
containerPage
.
model
.
get
(
'display_name'
)).
toBe
(
updatedDisplayName
);
});
});
it
(
"can delete a middle xblock"
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
deleteComponentWithSuccess
(
1
);
describe
(
"Editing an xblock"
,
function
()
{
afterEach
(
function
()
{
EditHelpers
.
cancelModalIfShowing
(
);
});
it
(
"can delete the last xblock"
,
function
()
{
it
(
'can show an edit modal for a child xblock'
,
function
()
{
var
editButtons
;
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
deleteComponentWithSuccess
(
NUM_COMPONENTS_PER_GROUP
-
1
);
editButtons
=
containerPage
.
$
(
'.wrapper-xblock .edit-button'
);
// The container should have rendered six mock xblocks
expect
(
editButtons
.
length
).
toBe
(
6
);
editButtons
[
0
].
click
();
// Make sure that the correct xblock is requested to be edited
expect
(
str
.
startsWith
(
lastRequest
().
url
,
'/xblock/locator-component-A1/studio_view'
)).
toBeTruthy
();
AjaxHelpers
.
respondWithJson
(
requests
,
{
html
:
mockXBlockEditorHtml
,
resources
:
[]
});
expect
(
EditHelpers
.
isShowingModal
()).
toBeTruthy
();
});
it
(
"can delete an xblock with broken JavaScript"
,
function
()
{
it
(
'can show an edit modal for a child xblock with broken JavaScript'
,
function
()
{
var
editButtons
;
renderContainerPage
(
this
,
mockBadContainerXBlockHtml
);
containerPage
.
$
(
'.delete-button'
).
first
().
click
();
EditHelpers
.
confirmPrompt
(
promptSpy
);
AjaxHelpers
.
respondWithJson
(
requests
,
{});
// expect the second to last request to be a delete of the xblock
AjaxHelpers
.
expectJsonRequest
(
requests
,
'DELETE'
,
'/xblock/locator-broken-javascript'
,
null
,
requests
.
length
-
2
);
// expect the last request to be a fetch of the xblock info for the parent container
AjaxHelpers
.
expectJsonRequest
(
requests
,
'GET'
,
'/xblock/locator-container'
);
editButtons
=
containerPage
.
$
(
'.wrapper-xblock .edit-button'
);
editButtons
[
0
].
click
();
AjaxHelpers
.
respondWithJson
(
requests
,
{
html
:
mockXBlockEditorHtml
,
resources
:
[]
});
expect
(
EditHelpers
.
isShowingModal
()).
toBeTruthy
();
});
});
it
(
'does not delete when clicking No in prompt'
,
function
()
{
var
numRequests
;
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
numRequests
=
requests
.
length
;
// click delete on the first component but press no
clickDelete
(
0
,
true
);
// all components should still exist
expectComponents
(
getGroupElement
(),
allComponentsInGroup
);
describe
(
"Editing an xmodule"
,
function
()
{
var
mockXModuleEditor
=
readFixtures
(
'mock/mock-xmodule-editor.underscore'
),
newDisplayName
=
'New Display Name'
;
// no requests should have been sent to the server
expect
(
requests
.
length
).
toBe
(
numRequests
);
beforeEach
(
function
()
{
EditHelpers
.
installMockXModule
({
data
:
"<p>Some HTML</p>"
,
metadata
:
{
display_name
:
newDisplayName
}
});
});
it
(
'shows a notification during the delete operation'
,
function
()
{
var
notificationSpy
=
EditHelpers
.
createNotificationSpy
();
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
clickDelete
(
0
);
EditHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Deleting/
);
AjaxHelpers
.
respondWithJson
(
requests
,
{});
EditHelpers
.
verifyNotificationHidden
(
notificationSpy
);
afterEach
(
function
()
{
EditHelpers
.
uninstallMockXModule
();
EditHelpers
.
cancelModalIfShowing
();
});
it
(
'does not delete an xblock upon failure'
,
function
()
{
var
notificationSpy
=
EditHelpers
.
createNotificationSpy
();
it
(
'can save changes to settings'
,
function
()
{
var
editButtons
,
modal
,
mockUpdatedXBlockHtml
;
mockUpdatedXBlockHtml
=
readFixtures
(
'mock/mock-updated-xblock.underscore'
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
clickDelete
(
0
);
EditHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Deleting/
);
AjaxHelpers
.
respondWithError
(
requests
);
EditHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Deleting/
);
expectComponents
(
getGroupElement
(),
allComponentsInGroup
);
editButtons
=
containerPage
.
$
(
'.wrapper-xblock .edit-button'
);
// The container should have rendered six mock xblocks
expect
(
editButtons
.
length
).
toBe
(
6
);
editButtons
[
0
].
click
();
AjaxHelpers
.
respondWithJson
(
requests
,
{
html
:
mockXModuleEditor
,
resources
:
[]
});
modal
=
$
(
'.edit-xblock-modal'
);
expect
(
modal
.
length
).
toBe
(
1
);
// Click on the settings tab
modal
.
find
(
'.settings-button'
).
click
();
// Change the display name's text
modal
.
find
(
'.setting-input'
).
text
(
"Mock Update"
);
// Press the save button
modal
.
find
(
'.action-save'
).
click
();
// Respond to the save
AjaxHelpers
.
respondWithJson
(
requests
,
{
id
:
model
.
id
});
// Respond to the request to refresh
respondWithHtml
(
mockUpdatedXBlockHtml
);
// Verify that the xblock was updated
expect
(
containerPage
.
$
(
'.mock-updated-content'
).
text
()).
toBe
(
'Mock Update'
);
});
});
describe
(
"Duplicating an xblock"
,
function
()
{
var
clickDuplicate
,
duplicateComponentWithSuccess
,
refreshXBlockSpies
;
describe
(
"xblock operations"
,
function
()
{
var
getGroupElement
,
NUM_COMPONENTS_PER_GROUP
=
3
,
GROUP_TO_TEST
=
"A"
,
allComponentsInGroup
=
_
.
map
(
_
.
range
(
NUM_COMPONENTS_PER_GROUP
),
function
(
index
)
{
return
'locator-component-'
+
GROUP_TO_TEST
+
(
index
+
1
);
}
);
clickDuplicate
=
function
(
componentIndex
)
{
getGroupElement
=
function
()
{
return
containerPage
.
$
(
"[data-locator='locator-group-"
+
GROUP_TO_TEST
+
"']"
);
};
// find all duplicate buttons for the given group
var
duplicateButtons
=
getGroupElement
().
find
(
".duplicate-button"
);
expect
(
duplicateButtons
.
length
).
toBe
(
NUM_COMPONENTS_PER_GROUP
)
;
describe
(
"Deleting an xblock"
,
function
()
{
var
clickDelete
,
deleteComponent
,
deleteComponentWithSuccess
,
promptSpy
;
// click the requested duplicate button
duplicateButtons
[
componentIndex
].
click
();
};
beforeEach
(
function
()
{
promptSpy
=
EditHelpers
.
createPromptSpy
();
});
clickDelete
=
function
(
componentIndex
,
clickNo
)
{
duplicateComponentWithSuccess
=
function
(
componentIndex
)
{
refreshXBlockSpies
=
spyOn
(
containerPage
,
"refreshXBlock"
);
// find all delete buttons for the given group
var
deleteButtons
=
getGroupElement
().
find
(
".delete-button"
);
expect
(
deleteButtons
.
length
).
toBe
(
NUM_COMPONENTS_PER_GROUP
);
clickDuplicate
(
componentIndex
);
// click the requested delete button
deleteButtons
[
componentIndex
].
click
();
// verify content of request
AjaxHelpers
.
expectJsonRequest
(
requests
,
'POST'
,
'/xblock/'
,
{
'duplicate_source_locator'
:
'locator-component-'
+
GROUP_TO_TEST
+
(
componentIndex
+
1
),
'parent_locator'
:
'locator-group-'
+
GROUP_TO_TEST
// click the 'yes' or 'no' button in the prompt
EditHelpers
.
confirmPrompt
(
promptSpy
,
clickNo
);
};
deleteComponent
=
function
(
componentIndex
)
{
clickDelete
(
componentIndex
);
AjaxHelpers
.
respondWithJson
(
requests
,
{});
// second to last request contains given component's id (to delete the component)
AjaxHelpers
.
expectJsonRequest
(
requests
,
'DELETE'
,
'/xblock/locator-component-'
+
GROUP_TO_TEST
+
(
componentIndex
+
1
),
null
,
requests
.
length
-
2
);
// final request to refresh the xblock info
AjaxHelpers
.
expectJsonRequest
(
requests
,
'GET'
,
'/xblock/locator-container'
);
};
deleteComponentWithSuccess
=
function
(
componentIndex
)
{
deleteComponent
(
componentIndex
);
// verify the new list of components within the group
expectComponents
(
getGroupElement
(),
_
.
without
(
allComponentsInGroup
,
allComponentsInGroup
[
componentIndex
])
);
};
it
(
"can delete the first xblock"
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
deleteComponentWithSuccess
(
0
);
});
// send the response
AjaxHelpers
.
respondWithJson
(
requests
,
{
'locator'
:
'locator-duplicated-component'
it
(
"can delete a middle xblock"
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
deleteComponentWithSuccess
(
1
);
});
// expect parent container to be refreshed
expect
(
refreshXBlockSpies
).
toHaveBeenCalled
();
};
it
(
"can delete the last xblock"
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
deleteComponentWithSuccess
(
NUM_COMPONENTS_PER_GROUP
-
1
);
});
it
(
"can duplicate the first xblock"
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
duplicateComponentWithSuccess
(
0
);
});
it
(
"can delete an xblock with broken JavaScript"
,
function
()
{
renderContainerPage
(
this
,
mockBadContainerXBlockHtml
);
containerPage
.
$
(
'.delete-button'
).
first
().
click
();
EditHelpers
.
confirmPrompt
(
promptSpy
);
AjaxHelpers
.
respondWithJson
(
requests
,
{});
// expect the second to last request to be a delete of the xblock
AjaxHelpers
.
expectJsonRequest
(
requests
,
'DELETE'
,
'/xblock/locator-broken-javascript'
,
null
,
requests
.
length
-
2
);
// expect the last request to be a fetch of the xblock info for the parent container
AjaxHelpers
.
expectJsonRequest
(
requests
,
'GET'
,
'/xblock/locator-container'
);
});
it
(
"can duplicate a middle xblock"
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
duplicateComponentWithSuccess
(
1
);
});
it
(
'does not delete when clicking No in prompt'
,
function
()
{
var
numRequests
;
it
(
"can duplicate the last xblock"
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
duplicateComponentWithSuccess
(
NUM_COMPONENTS_PER_GROUP
-
1
);
});
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
numRequests
=
requests
.
length
;
it
(
"can duplicate an xblock with broken JavaScript"
,
function
()
{
renderContainerPage
(
this
,
mockBadContainerXBlockHtml
);
containerPage
.
$
(
'.duplicate-button'
).
first
().
click
();
AjaxHelpers
.
expectJsonRequest
(
requests
,
'POST'
,
'/xblock/'
,
{
'duplicate_source_locator'
:
'locator-broken-javascript'
,
'parent_locator'
:
'locator-container'
// click delete on the first component but press no
clickDelete
(
0
,
true
);
// all components should still exist
expectComponents
(
getGroupElement
(),
allComponentsInGroup
);
// no requests should have been sent to the server
expect
(
requests
.
length
).
toBe
(
numRequests
);
});
});
it
(
'shows a notification when duplicating
'
,
function
()
{
var
notificationSpy
=
EditHelpers
.
createNotificationSpy
();
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
clickDuplica
te
(
0
);
EditHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Duplica
ting/
);
AjaxHelpers
.
respondWithJson
(
requests
,
{
"locator"
:
"new_item"
});
EditHelpers
.
verifyNotificationHidden
(
notificationSpy
);
});
it
(
'shows a notification during the delete operation
'
,
function
()
{
var
notificationSpy
=
EditHelpers
.
createNotificationSpy
();
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
clickDele
te
(
0
);
EditHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Dele
ting/
);
AjaxHelpers
.
respondWithJson
(
requests
,
{
});
EditHelpers
.
verifyNotificationHidden
(
notificationSpy
);
});
it
(
'does not duplicate an xblock upon failure'
,
function
()
{
var
notificationSpy
=
EditHelpers
.
createNotificationSpy
();
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
refreshXBlockSpies
=
spyOn
(
containerPage
,
"refreshXBlock"
);
clickDuplicate
(
0
);
EditHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Duplicating/
);
AjaxHelpers
.
respondWithError
(
requests
);
expectComponents
(
getGroupElement
(),
allComponentsInGroup
);
expect
(
refreshXBlockSpies
).
not
.
toHaveBeenCalled
();
EditHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Duplicating/
);
it
(
'does not delete an xblock upon failure'
,
function
()
{
var
notificationSpy
=
EditHelpers
.
createNotificationSpy
();
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
clickDelete
(
0
);
EditHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Deleting/
);
AjaxHelpers
.
respondWithError
(
requests
);
EditHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Deleting/
);
expectComponents
(
getGroupElement
(),
allComponentsInGroup
);
});
});
});
describe
(
'createNewComponent '
,
function
()
{
var
clickNewComponent
;
describe
(
"Duplicating an xblock"
,
function
()
{
var
clickDuplicate
,
duplicateComponentWithSuccess
,
refreshXBlockSpies
;
clickNewComponent
=
function
(
index
)
{
containerPage
.
$
(
".new-component .new-component-type a.single-template"
)[
index
].
click
();
};
clickDuplicate
=
function
(
componentIndex
)
{
it
(
'sends the correct JSON to the server'
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
clickNewComponent
(
0
);
EditHelpers
.
verifyXBlockRequest
(
requests
,
{
"category"
:
"discussion"
,
"type"
:
"discussion"
,
"parent_locator"
:
"locator-group-A"
});
});
// find all duplicate buttons for the given group
var
duplicateButtons
=
getGroupElement
().
find
(
".duplicate-button"
);
expect
(
duplicateButtons
.
length
).
toBe
(
NUM_COMPONENTS_PER_GROUP
);
it
(
'shows a notification while creating'
,
function
()
{
var
notificationSpy
=
EditHelpers
.
createNotificationSpy
();
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
clickNewComponent
(
0
);
EditHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Adding/
);
AjaxHelpers
.
respondWithJson
(
requests
,
{
});
EditHelpers
.
verifyNotificationHidden
(
notificationSpy
);
});
// click the requested duplicate button
duplicateButtons
[
componentIndex
].
click
();
};
it
(
'does not insert component upon failure'
,
function
(
)
{
var
requestCount
;
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
clickNewComponent
(
0
);
requestCount
=
requests
.
length
;
AjaxHelpers
.
respondWithError
(
requests
);
// No new requests should be made to refresh the view
expect
(
requests
.
length
).
toBe
(
requestCount
);
expectComponents
(
getGroupElement
(),
allComponentsInGroup
);
});
duplicateComponentWithSuccess
=
function
(
componentIndex
)
{
refreshXBlockSpies
=
spyOn
(
containerPage
,
"refreshXBlock"
)
;
clickDuplicate
(
componentIndex
);
// verify content of request
AjaxHelpers
.
expectJsonRequest
(
requests
,
'POST'
,
'/xblock/'
,
{
'duplicate_source_locator'
:
'locator-component-'
+
GROUP_TO_TEST
+
(
componentIndex
+
1
),
'parent_locator'
:
'locator-group-'
+
GROUP_TO_TEST
});
describe
(
'Template Picker'
,
function
()
{
var
showTemplatePicker
,
verifyCreateHtmlComponent
,
mockXBlockHtml
=
readFixtures
(
'mock/mock-xblock.underscore'
);
// send the response
AjaxHelpers
.
respondWithJson
(
requests
,
{
'locator'
:
'locator-duplicated-component'
});
showTemplatePicker
=
function
()
{
containerPage
.
$
(
'.new-component .new-component-type a.multiple-templates'
)[
0
].
click
();
// expect parent container to be refreshed
expect
(
refreshXBlockSpies
).
toHaveBeenCalled
();
};
verifyCreateHtmlComponent
=
function
(
test
,
templateIndex
,
expectedRequest
)
{
var
xblockCount
;
renderContainerPage
(
test
,
mockContainerXBlockHtml
);
showTemplatePicker
();
xblockCount
=
containerPage
.
$
(
'.studio-xblock-wrapper'
).
length
;
containerPage
.
$
(
'.new-component-html a'
)[
templateIndex
].
click
();
EditHelpers
.
verifyXBlockRequest
(
requests
,
expectedRequest
);
it
(
"can duplicate the first xblock"
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
duplicateComponentWithSuccess
(
0
);
});
it
(
"can duplicate a middle xblock"
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
duplicateComponentWithSuccess
(
1
);
});
it
(
"can duplicate the last xblock"
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
duplicateComponentWithSuccess
(
NUM_COMPONENTS_PER_GROUP
-
1
);
});
it
(
"can duplicate an xblock with broken JavaScript"
,
function
()
{
renderContainerPage
(
this
,
mockBadContainerXBlockHtml
);
containerPage
.
$
(
'.duplicate-button'
).
first
().
click
();
AjaxHelpers
.
expectJsonRequest
(
requests
,
'POST'
,
'/xblock/'
,
{
'duplicate_source_locator'
:
'locator-broken-javascript'
,
'parent_locator'
:
'locator-container'
});
});
it
(
'shows a notification when duplicating'
,
function
()
{
var
notificationSpy
=
EditHelpers
.
createNotificationSpy
();
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
clickDuplicate
(
0
);
EditHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Duplicating/
);
AjaxHelpers
.
respondWithJson
(
requests
,
{
"locator"
:
"new_item"
});
respondWithHtml
(
mockXBlockHtml
);
expect
(
containerPage
.
$
(
'.studio-xblock-wrapper'
).
length
).
toBe
(
xblockCount
+
1
);
EditHelpers
.
verifyNotificationHidden
(
notificationSpy
);
});
it
(
'does not duplicate an xblock upon failure'
,
function
()
{
var
notificationSpy
=
EditHelpers
.
createNotificationSpy
();
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
refreshXBlockSpies
=
spyOn
(
containerPage
,
"refreshXBlock"
);
clickDuplicate
(
0
);
EditHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Duplicating/
);
AjaxHelpers
.
respondWithError
(
requests
);
expectComponents
(
getGroupElement
(),
allComponentsInGroup
);
expect
(
refreshXBlockSpies
).
not
.
toHaveBeenCalled
();
EditHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Duplicating/
);
});
});
describe
(
'createNewComponent '
,
function
()
{
var
clickNewComponent
;
clickNewComponent
=
function
(
index
)
{
containerPage
.
$
(
".new-component .new-component-type a.single-template"
)[
index
].
click
();
};
it
(
'can add an HTML component without a template'
,
function
()
{
verifyCreateHtmlComponent
(
this
,
0
,
{
"category"
:
"html"
,
it
(
'sends the correct JSON to the server'
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
clickNewComponent
(
0
);
EditHelpers
.
verifyXBlockRequest
(
requests
,
{
"category"
:
"discussion"
,
"type"
:
"discussion"
,
"parent_locator"
:
"locator-group-A"
});
});
it
(
'can add an HTML component with a template'
,
function
()
{
verifyCreateHtmlComponent
(
this
,
1
,
{
"category"
:
"html"
,
"boilerplate"
:
"announcement.yaml"
,
"parent_locator"
:
"locator-group-A"
it
(
'shows a notification while creating'
,
function
()
{
var
notificationSpy
=
EditHelpers
.
createNotificationSpy
();
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
clickNewComponent
(
0
);
EditHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Adding/
);
AjaxHelpers
.
respondWithJson
(
requests
,
{
});
EditHelpers
.
verifyNotificationHidden
(
notificationSpy
);
});
it
(
'does not insert component upon failure'
,
function
()
{
var
requestCount
;
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
clickNewComponent
(
0
);
requestCount
=
requests
.
length
;
AjaxHelpers
.
respondWithError
(
requests
);
// No new requests should be made to refresh the view
expect
(
requests
.
length
).
toBe
(
requestCount
);
expectComponents
(
getGroupElement
(),
allComponentsInGroup
);
});
describe
(
'Template Picker'
,
function
()
{
var
showTemplatePicker
,
verifyCreateHtmlComponent
;
showTemplatePicker
=
function
()
{
containerPage
.
$
(
'.new-component .new-component-type a.multiple-templates'
)[
0
].
click
();
};
verifyCreateHtmlComponent
=
function
(
test
,
templateIndex
,
expectedRequest
)
{
var
xblockCount
;
renderContainerPage
(
test
,
mockContainerXBlockHtml
);
showTemplatePicker
();
xblockCount
=
containerPage
.
$
(
'.studio-xblock-wrapper'
).
length
;
containerPage
.
$
(
'.new-component-html a'
)[
templateIndex
].
click
();
EditHelpers
.
verifyXBlockRequest
(
requests
,
expectedRequest
);
AjaxHelpers
.
respondWithJson
(
requests
,
{
"locator"
:
"new_item"
});
respondWithHtml
(
mockXBlockHtml
);
expect
(
containerPage
.
$
(
'.studio-xblock-wrapper'
).
length
).
toBe
(
xblockCount
+
1
);
};
it
(
'can add an HTML component without a template'
,
function
()
{
verifyCreateHtmlComponent
(
this
,
0
,
{
"category"
:
"html"
,
"parent_locator"
:
"locator-group-A"
});
});
it
(
'can add an HTML component with a template'
,
function
()
{
verifyCreateHtmlComponent
(
this
,
1
,
{
"category"
:
"html"
,
"boilerplate"
:
"announcement.yaml"
,
"parent_locator"
:
"locator-group-A"
});
});
});
});
});
});
});
}
parameterized_suite
(
"Non paged"
,
{
enable_paging
:
false
},
{
initial
:
'mock/mock-container-xblock.underscore'
,
add_response
:
'mock/mock-xblock.underscore'
}
);
parameterized_suite
(
"Paged"
,
{
enable_paging
:
true
,
page_size
:
42
},
{
initial
:
'mock/mock-container-paged-xblock.underscore'
,
add_response
:
'mock/mock-container-paged-after-add-xblock.underscore'
});
});
cms/static/js/views/container.js
View file @
ff1a08cb
...
...
@@ -123,6 +123,10 @@ define(["jquery", "underscore", "js/views/xblock", "js/utils/module", "gettext",
});
},
acknowledgeXBlockDeletion
:
function
(
locator
){
this
.
notifyRuntime
(
'deleted-child'
,
locator
);
},
refresh
:
function
()
{
var
sortableInitializedClass
=
this
.
makeRequestSpecificSelector
(
'.reorderable-container.ui-sortable'
);
this
.
$
(
sortableInitializedClass
).
sortable
(
'refresh'
);
...
...
cms/static/js/views/library_container.js
0 → 100644
View file @
ff1a08cb
define
([
"jquery"
,
"underscore"
,
"js/views/xblock"
,
"js/utils/module"
,
"gettext"
,
"js/views/feedback_notification"
,
"js/views/paging_header"
,
"js/views/paging_footer"
],
function
(
$
,
_
,
XBlockView
,
ModuleUtils
,
gettext
,
NotificationView
,
PagingHeader
,
PagingFooter
)
{
var
LibraryContainerView
=
XBlockView
.
extend
({
// Store the request token of the first xblock on the page (which we know was rendered by Studio when
// the page was generated). Use that request token to filter out user-defined HTML in any
// child xblocks within the page.
requestToken
:
""
,
initialize
:
function
(
options
){
var
self
=
this
;
XBlockView
.
prototype
.
initialize
.
call
(
this
);
this
.
page_size
=
this
.
options
.
page_size
||
10
;
if
(
options
)
{
this
.
page_reload_callback
=
options
.
page_reload_callback
;
}
// emulating Backbone.paginator interface
this
.
collection
=
{
currentPage
:
0
,
totalPages
:
0
,
totalCount
:
0
,
sortDirection
:
"desc"
,
start
:
0
,
_size
:
0
,
bind
:
function
()
{},
// no-op
size
:
function
()
{
return
self
.
collection
.
_size
;
}
};
},
render
:
function
(
options
)
{
var
eff_options
=
options
||
{};
if
(
eff_options
.
block_added
)
{
this
.
collection
.
currentPage
=
this
.
getPageCount
(
this
.
collection
.
totalCount
+
1
)
-
1
;
}
eff_options
.
page_number
=
typeof
eff_options
.
page_number
!==
"undefined"
?
eff_options
.
page_number
:
this
.
collection
.
currentPage
;
return
this
.
renderPage
(
eff_options
);
},
renderPage
:
function
(
options
){
var
self
=
this
,
view
=
this
.
view
,
xblockInfo
=
this
.
model
,
xblockUrl
=
xblockInfo
.
url
();
return
$
.
ajax
({
url
:
decodeURIComponent
(
xblockUrl
)
+
"/"
+
view
,
type
:
'GET'
,
cache
:
false
,
data
:
this
.
getRenderParameters
(
options
.
page_number
),
headers
:
{
Accept
:
'application/json'
},
success
:
function
(
fragment
)
{
self
.
handleXBlockFragment
(
fragment
,
options
);
self
.
processPaging
({
requested_page
:
options
.
page_number
});
if
(
options
.
paging
&&
self
.
page_reload_callback
){
self
.
page_reload_callback
(
self
.
$el
);
}
}
});
},
getRenderParameters
:
function
(
page_number
)
{
return
{
enable_paging
:
true
,
page_size
:
this
.
page_size
,
page_number
:
page_number
};
},
getPageCount
:
function
(
total_count
){
if
(
total_count
==
0
)
return
1
;
return
Math
.
ceil
(
total_count
/
this
.
page_size
);
},
setPage
:
function
(
page_number
)
{
this
.
render
({
page_number
:
page_number
,
paging
:
true
});
},
nextPage
:
function
()
{
var
collection
=
this
.
collection
,
currentPage
=
collection
.
currentPage
,
lastPage
=
collection
.
totalPages
-
1
;
if
(
currentPage
<
lastPage
)
{
this
.
setPage
(
currentPage
+
1
);
}
},
previousPage
:
function
()
{
var
collection
=
this
.
collection
,
currentPage
=
collection
.
currentPage
;
if
(
currentPage
>
0
)
{
this
.
setPage
(
currentPage
-
1
);
}
},
processPaging
:
function
(
options
){
var
$element
=
this
.
$el
.
find
(
'.xblock-container-paging-parameters'
),
total
=
$element
.
data
(
'total'
),
displayed
=
$element
.
data
(
'displayed'
),
start
=
$element
.
data
(
'start'
);
this
.
collection
.
currentPage
=
options
.
requested_page
;
this
.
collection
.
totalCount
=
total
;
this
.
collection
.
totalPages
=
this
.
getPageCount
(
total
);
this
.
collection
.
start
=
start
;
this
.
collection
.
_size
=
displayed
;
this
.
processPagingHeaderAndFooter
();
},
processPagingHeaderAndFooter
:
function
(){
if
(
this
.
pagingHeader
)
this
.
pagingHeader
.
undelegateEvents
();
if
(
this
.
pagingFooter
)
this
.
pagingFooter
.
undelegateEvents
();
this
.
pagingHeader
=
new
PagingHeader
({
view
:
this
,
el
:
this
.
$el
.
find
(
'.container-paging-header'
)
});
this
.
pagingFooter
=
new
PagingFooter
({
view
:
this
,
el
:
this
.
$el
.
find
(
'.container-paging-footer'
)
});
this
.
pagingHeader
.
render
();
this
.
pagingFooter
.
render
();
},
xblockReady
:
function
()
{
XBlockView
.
prototype
.
xblockReady
.
call
(
this
);
this
.
requestToken
=
this
.
$
(
'div.xblock'
).
first
().
data
(
'request-token'
);
},
refresh
:
function
()
{
},
acknowledgeXBlockDeletion
:
function
(
locator
){
this
.
notifyRuntime
(
'deleted-child'
,
locator
);
this
.
collection
.
_size
-=
1
;
this
.
collection
.
totalCount
-=
1
;
// pages are counted from 0 - thus currentPage == 1 if we're on second page
if
(
this
.
collection
.
_size
==
0
&&
this
.
collection
.
currentPage
>=
1
)
{
this
.
setPage
(
this
.
collection
.
currentPage
-
1
);
this
.
collection
.
totalPages
-=
1
;
}
else
{
this
.
pagingHeader
.
render
();
this
.
pagingFooter
.
render
();
}
},
makeRequestSpecificSelector
:
function
(
selector
)
{
return
'div.xblock[data-request-token="'
+
this
.
requestToken
+
'"] > '
+
selector
;
},
sortDisplayName
:
function
()
{
return
"Date added"
;
// TODO add support for sorting
}
});
return
LibraryContainerView
;
});
// end define();
cms/static/js/views/pages/container.js
View file @
ff1a08cb
...
...
@@ -3,10 +3,10 @@
* This page allows the user to understand and manipulate the xblock and its children.
*/
define
([
"jquery"
,
"underscore"
,
"gettext"
,
"js/views/pages/base_page"
,
"js/views/utils/view_utils"
,
"js/views/container"
,
"js/views/xblock"
,
"js/views/components/add_xblock"
,
"js/views/modals/edit_xblock"
,
"js/views/container"
,
"js/views/
library_container"
,
"js/views/
xblock"
,
"js/views/components/add_xblock"
,
"js/views/modals/edit_xblock"
,
"js/models/xblock_info"
,
"js/views/xblock_string_field_editor"
,
"js/views/pages/container_subviews"
,
"js/views/unit_outline"
,
"js/views/utils/xblock_utils"
],
function
(
$
,
_
,
gettext
,
BasePage
,
ViewUtils
,
ContainerView
,
XBlockView
,
AddXBlockComponent
,
function
(
$
,
_
,
gettext
,
BasePage
,
ViewUtils
,
ContainerView
,
PagedContainerView
,
XBlockView
,
AddXBlockComponent
,
EditXBlockModal
,
XBlockInfo
,
XBlockStringFieldEditor
,
ContainerSubviews
,
UnitOutlineView
,
XBlockUtils
)
{
'use strict'
;
...
...
@@ -27,6 +27,10 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
initialize
:
function
(
options
)
{
BasePage
.
prototype
.
initialize
.
call
(
this
,
options
);
this
.
enable_paging
=
options
.
enable_paging
||
false
;
if
(
this
.
enable_paging
)
{
this
.
page_size
=
options
.
page_size
||
10
;
}
this
.
nameEditor
=
new
XBlockStringFieldEditor
({
el
:
this
.
$
(
'.wrapper-xblock-field'
),
model
:
this
.
model
...
...
@@ -35,11 +39,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
if
(
this
.
options
.
action
===
'new'
)
{
this
.
nameEditor
.
$
(
'.xblock-field-value-edit'
).
click
();
}
this
.
xblockView
=
new
ContainerView
({
el
:
this
.
$
(
'.wrapper-xblock'
),
model
:
this
.
model
,
view
:
this
.
view
});
this
.
xblockView
=
this
.
getXBlockView
();
this
.
messageView
=
new
ContainerSubviews
.
MessageView
({
el
:
this
.
$
(
'.container-message'
),
model
:
this
.
model
...
...
@@ -75,6 +75,28 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
}
},
getXBlockView
:
function
(){
var
self
=
this
,
parameters
=
{
el
:
this
.
$
(
'.wrapper-xblock'
),
model
:
this
.
model
,
view
:
this
.
view
};
if
(
this
.
enable_paging
)
{
parameters
=
_
.
extend
(
parameters
,
{
page_size
:
this
.
page_size
,
page_reload_callback
:
function
(
$element
)
{
self
.
renderAddXBlockComponents
();
}
});
return
new
PagedContainerView
(
parameters
);
}
else
{
return
new
ContainerView
(
parameters
);
}
},
render
:
function
(
options
)
{
var
self
=
this
,
xblockView
=
this
.
xblockView
,
...
...
@@ -106,7 +128,8 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
// Re-enable Backbone events for any updated DOM elements
self
.
delegateEvents
();
}
},
block_added
:
options
&&
options
.
block_added
});
},
...
...
@@ -144,7 +167,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
modal
.
edit
(
xblockElement
,
this
.
model
,
{
refresh
:
function
()
{
self
.
refreshXBlock
(
xblockElement
);
self
.
refreshXBlock
(
xblockElement
,
false
);
}
});
},
...
...
@@ -226,7 +249,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
// Inform the runtime that the child has been deleted in case
// other views are listening to deletion events.
xblockView
.
notifyRuntime
(
'deleted-child'
,
parent
.
data
(
'locator'
));
xblockView
.
acknowledgeXBlockDeletion
(
parent
.
data
(
'locator'
));
// Update publish and last modified information from the server.
this
.
model
.
fetch
();
...
...
@@ -235,7 +258,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
onNewXBlock
:
function
(
xblockElement
,
scrollOffset
,
data
)
{
ViewUtils
.
setScrollOffset
(
xblockElement
,
scrollOffset
);
xblockElement
.
data
(
'locator'
,
data
.
locator
);
return
this
.
refreshXBlock
(
xblockElement
);
return
this
.
refreshXBlock
(
xblockElement
,
true
);
},
/**
...
...
@@ -243,17 +266,18 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
* reorderable container then the element will be refreshed inline. If not, then the
* parent container will be refreshed instead.
* @param element An element representing the xblock to be refreshed.
* @param block_added Flag to indicate that new block has been just added.
*/
refreshXBlock
:
function
(
element
)
{
refreshXBlock
:
function
(
element
,
block_added
)
{
var
xblockElement
=
this
.
findXBlockElement
(
element
),
parentElement
=
xblockElement
.
parent
(),
rootLocator
=
this
.
xblockView
.
model
.
id
;
if
(
xblockElement
.
length
===
0
||
xblockElement
.
data
(
'locator'
)
===
rootLocator
)
{
this
.
render
({
refresh
:
true
});
this
.
render
({
refresh
:
true
,
block_added
:
block_added
});
}
else
if
(
parentElement
.
hasClass
(
'reorderable-container'
))
{
this
.
refreshChildXBlock
(
xblockElement
);
}
else
{
this
.
refreshXBlock
(
this
.
findXBlockElement
(
parentElement
));
this
.
refreshXBlock
(
this
.
findXBlockElement
(
parentElement
)
,
block_added
);
}
},
...
...
cms/static/js/views/paging_footer.js
View file @
ff1a08cb
...
...
@@ -38,6 +38,12 @@ define(["underscore", "js/views/baseview"], function(_, BaseView) {
currentPage
=
collection
.
currentPage
+
1
,
pageInput
=
this
.
$
(
"#page-number-input"
),
pageNumber
=
parseInt
(
pageInput
.
val
(),
10
);
if
(
pageNumber
>
collection
.
totalPages
)
{
pageNumber
=
false
;
}
if
(
pageNumber
<=
0
)
{
pageNumber
=
false
;
}
if
(
pageNumber
&&
pageNumber
!==
currentPage
)
{
view
.
setPage
(
pageNumber
-
1
);
}
...
...
cms/static/sass/elements/_pagination.scss
0 → 100644
View file @
ff1a08cb
// studio - elements - pagination
// ==========================
%pagination
{
@include
clearfix
;
display
:
inline-block
;
width
:
flex-grid
(
3
,
12
);
&
.pagination-compact
{
@include
text-align
(
right
);
}
&
.pagination-full
{
display
:
block
;
width
:
flex-grid
(
4
,
12
);
margin
:
$baseline
auto
;
}
.nav-item
{
position
:
relative
;
display
:
inline-block
;
}
.nav-link
{
@include
transition
(
all
$tmg-f2
ease-in-out
0s
);
display
:
block
;
padding
:
(
$baseline
/
4
)
(
$baseline
*
0
.75
);
&
.previous
{
margin-right
:
(
$baseline
/
2
);
}
&
.next
{
margin-left
:
(
$baseline
/
2
);
}
&
:hover
{
background-color
:
$blue
;
border-radius
:
3px
;
color
:
$white
;
}
&
.is-disabled
{
background-color
:
transparent
;
color
:
$gray-l2
;
pointer-events
:
none
;
}
}
.nav-label
{
@extend
.sr
;
}
.pagination-form
,
.current-page
,
.page-divider
,
.total-pages
{
display
:
inline-block
;
}
.current-page
,
.page-number-input
,
.total-pages
{
@extend
%t-copy-base
;
@extend
%t-strong
;
width
:
(
$baseline
*
2
.5
);
margin
:
0
(
$baseline
*
0
.75
);
padding
:
(
$baseline
/
4
);
text-align
:
center
;
color
:
$gray
;
}
.current-page
{
@extend
%ui-depth1
;
position
:
absolute
;
@include
left
(
-
(
$baseline
/
4
));
}
.page-divider
{
@extend
%t-title4
;
@extend
%t-regular
;
vertical-align
:
middle
;
color
:
$gray-l2
;
}
.pagination-form
{
@extend
%ui-depth2
;
position
:
relative
;
.page-number-label
,
.submit-pagination-form
{
@extend
.sr
;
}
.page-number-input
{
@include
transition
(
all
$tmg-f2
ease-in-out
0s
);
border
:
1px
solid
transparent
;
border-bottom
:
1px
dotted
$gray-l2
;
border-radius
:
0
;
box-shadow
:
none
;
background
:
none
;
&
:hover
{
background-color
:
$white
;
opacity
:
0
.6
;
}
&
:focus
{
// borrowing the base input focus styles to match overall app
@include
linear-gradient
(
$paleYellow
,
tint
(
$paleYellow
,
90%
));
opacity
:
1
.0
;
box-shadow
:
0
0
3px
$shadow-d1
inset
;
background-color
:
$white
;
border
:
1px
solid
transparent
;
border-radius
:
3px
;
}
}
}
}
\ No newline at end of file
cms/static/sass/elements/_uploaded-assets.scss
View file @
ff1a08cb
...
...
@@ -28,120 +28,7 @@
}
.pagination
{
@include
clearfix
;
display
:
inline-block
;
width
:
flex-grid
(
3
,
12
);
&
.pagination-compact
{
@include
text-align
(
right
);
}
&
.pagination-full
{
display
:
block
;
width
:
flex-grid
(
4
,
12
);
margin
:
$baseline
auto
;
}
.nav-item
{
position
:
relative
;
display
:
inline-block
;
}
.nav-link
{
@include
transition
(
all
$tmg-f2
ease-in-out
0s
);
display
:
block
;
padding
:
(
$baseline
/
4
)
(
$baseline
*
0
.75
);
&
.previous
{
margin-right
:
(
$baseline
/
2
);
}
&
.next
{
margin-left
:
(
$baseline
/
2
);
}
&
:hover
{
background-color
:
$blue
;
border-radius
:
3px
;
color
:
$white
;
}
&
.is-disabled
{
background-color
:
transparent
;
color
:
$gray-l2
;
pointer-events
:
none
;
}
}
.nav-label
{
@extend
.sr
;
}
.pagination-form
,
.current-page
,
.page-divider
,
.total-pages
{
display
:
inline-block
;
}
.current-page
,
.page-number-input
,
.total-pages
{
@extend
%t-copy-base
;
@extend
%t-strong
;
width
:
(
$baseline
*
2
.5
);
margin
:
0
(
$baseline
*
0
.75
);
padding
:
(
$baseline
/
4
);
text-align
:
center
;
color
:
$gray
;
}
.current-page
{
@extend
%ui-depth1
;
position
:
absolute
;
@include
left
(
-
(
$baseline
/
4
));
}
.page-divider
{
@extend
%t-title4
;
@extend
%t-regular
;
vertical-align
:
middle
;
color
:
$gray-l2
;
}
.pagination-form
{
@extend
%ui-depth2
;
position
:
relative
;
.page-number-label
,
.submit-pagination-form
{
@extend
.sr
;
}
.page-number-input
{
@include
transition
(
all
$tmg-f2
ease-in-out
0s
);
border
:
1px
solid
transparent
;
border-bottom
:
1px
dotted
$gray-l2
;
border-radius
:
0
;
box-shadow
:
none
;
background
:
none
;
&
:hover
{
background-color
:
$white
;
opacity
:
0
.6
;
}
&
:focus
{
// borrowing the base input focus styles to match overall app
@include
linear-gradient
(
$paleYellow
,
tint
(
$paleYellow
,
90%
));
opacity
:
1
.0
;
box-shadow
:
0
0
3px
$shadow-d1
inset
;
background-color
:
$white
;
border
:
1px
solid
transparent
;
border-radius
:
3px
;
}
}
}
@extend
%pagination
;
}
.assets-table
{
...
...
cms/static/sass/elements/_xblocks.scss
View file @
ff1a08cb
...
...
@@ -103,6 +103,37 @@
}
}
.container-paging-header
{
.meta-wrap
{
margin
:
$baseline
$baseline
/
2
;
}
.meta
{
@extend
%t-copy-sub2
;
display
:
inline-block
;
vertical-align
:
top
;
width
:
flex-grid
(
9
,
12
);
color
:
$gray-l1
;
.count-current-shown
,
.count-total
,
.sort-order
{
@extend
%t-strong
;
}
}
.pagination
{
@extend
%pagination
;
}
}
.container-paging-footer
{
.pagination
{
@extend
%pagination
;
}
}
// ====================
//UI: default internal xblock content styles
...
...
cms/static/sass/style-app-extend1-rtl.scss
View file @
ff1a08cb
...
...
@@ -40,6 +40,7 @@
// +Base - Elements
// ====================
@import
'elements/typography'
;
@import
'elements/pagination'
;
// pagination
@import
'elements/icons'
;
// references to icons used
@import
'elements/controls'
;
// buttons, link styles, sliders, etc.
@import
'elements/xblocks'
;
// studio rendering chrome for xblocks
...
...
cms/static/sass/style-app-extend1.scss
View file @
ff1a08cb
...
...
@@ -40,6 +40,7 @@
// +Base - Elements
// ====================
@import
'elements/typography'
;
@import
'elements/pagination'
;
// pagination
@import
'elements/icons'
;
// references to icons used
@import
'elements/controls'
;
// buttons, link styles, sliders, etc.
@import
'elements/xblocks'
;
// studio rendering chrome for xblocks
...
...
cms/templates/container.html
View file @
ff1a08cb
...
...
@@ -31,7 +31,10 @@ from django.utils.translation import ugettext as _
require(["js/factories/container"], function(ContainerFactory) {
ContainerFactory(
${component_templates | n}, ${json.dumps(xblock_info) | n},
"${action}", ${json.dumps(is_unit_page)}
"${action}",
{
isUnitPage: ${json.dumps(is_unit_page)}
}
);
});
</
%
block>
...
...
cms/templates/js/mock/mock-container-paged-after-add-xblock.underscore
0 → 100644
View file @
ff1a08cb
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-details">
<span class="xblock-display-name">Test Container</span>
</div>
<div class="header-actions">
<ul class="actions-list">
</ul>
</div>
</div>
</header>
<article class="xblock-render">
<div class="xblock" data-locator="locator-container" data-request-token="page-render-token"
data-init="MockXBlock" data-runtime-class="StudioRuntime" data-runtime-version="1">
<script type="text/template" id="paging-header-tpl">
<div class="meta-wrap">
<div class="meta">
<%= messageHtml %>
</div>
<nav class="pagination pagination-compact top">
<ol>
<li class="nav-item previous"><a class="nav-link previous-page-link" href="#"><i class="icon-angle-left"></i> <span class="nav-label"><%= gettext("Previous") %></span></a></li>
<li class="nav-item next"><a class="nav-link next-page-link" href="#"><span class="nav-label"><%= gettext("Next") %></span> <i class="icon-angle-right"></i></a></li>
</ol>
</nav>
</div>
</script>
<script type="text/template" id="paging-footer-tpl">
<nav class="pagination pagination-full bottom">
<ol>
<li class="nav-item previous"><a class="nav-link previous-page-link" href="#"><i class="icon-angle-left"></i> <span class="nav-label"><%= gettext("Previous") %></span></a></li>
<li class="nav-item page">
<div class="pagination-form">
<label class="page-number-label" for="page-number"><%= gettext("Page number") %></label>
<input id="page-number-input" class="page-number-input" name="page-number" type="text" size="4" />
</div>
<span class="current-page"><%= current_page + 1 %></span>
<span class="page-divider">/</span>
<span class="total-pages"><%= total_pages %></span>
</li>
<li class="nav-item next"><a class="nav-link next-page-link" href="#"><span class="nav-label"><%= gettext("Next") %></span> <i class="icon-angle-right"></i></a></li>
</ol>
</nav>
</script>
<div class="container-paging-header"></div>
<div class="studio-xblock-wrapper" data-locator="locator-group-A">
<section class="wrapper-xblock level-nesting">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-details">
<a href="#" data-tooltip="Expand or Collapse" class="action expand-collapse expand">
<i class="icon-caret-down ui-toggle-expansion"></i>
<span class="sr">Expand or Collapse</span>
</a>
<span class="xblock-display-name">Group A</span>
</div>
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render">
<div class="xblock" data-request-token="page-render-token">
<div class="studio-xblock-wrapper" data-locator="locator-component-A1">
<section class="wrapper-xblock level-element">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button"></a>
</li>
<li class="action-item action-duplicate">
<a href="#" class="duplicate-button action-button"></a>
</li>
<li class="action-item action-delete">
<a href="#" class="delete-button action-button"></a>
</li>
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render"></article>
</section>
</div>
<div class="studio-xblock-wrapper" data-locator="locator-component-A2">
<section class="wrapper-xblock level-element">
<header class="xblock-header">
<div class="header-actions">
<div class="xblock-header-primary">
<ul class="actions-list">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button"></a>
</li>
<li class="action-item action-duplicate">
<a href="#" class="duplicate-button action-button"></a>
</li>
<li class="action-item action-delete">
<a href="#" class="delete-button action-button"></a>
</li>
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render"></article>
</section>
</div>
<div class="studio-xblock-wrapper" data-locator="locator-component-A3">
<section class="wrapper-xblock level-element">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button"></a>
</li>
<li class="action-item action-duplicate">
<a href="#" class="duplicate-button action-button"></a>
</li>
<li class="action-item action-delete">
<a href="#" class="delete-button action-button"></a>
</li>
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render"></article>
</section>
</div>
<div class="studio-xblock-wrapper" data-locator="locator-component-A4">
<section class="wrapper-xblock level-element">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button"></a>
</li>
<li class="action-item action-duplicate">
<a href="#" class="duplicate-button action-button"></a>
</li>
<li class="action-item action-delete">
<a href="#" class="delete-button action-button"></a>
</li>
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render"></article>
</section>
</div>
<div class="add-xblock-component new-component-item adding"></div>
</div>
</article>
</section>
</div>
<div class="studio-xblock-wrapper" data-locator="locator-group-B">
<section class="wrapper-xblock level-nesting">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-details">
<a href="#" data-tooltip="Expand or Collapse" class="action expand-collapse expand">
<i class="icon-caret-down ui-toggle-expansion"></i>
<span class="sr">Expand or Collapse</span>
</a>
<span class="xblock-display-name">Group B</span>
</div>
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render">
<div class="xblock" data-request-token="page-render-token">
<div class="studio-xblock-wrapper" data-locator="locator-component-B1">
<section class="wrapper-xblock level-element">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button"></a>
</li>
<li class="action-item action-duplicate">
<a href="#" class="duplicate-button action-button"></a>
</li>
<li class="action-item action-delete">
<a href="#" class="delete-button action-button"></a>
</li>
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render"></article>
</section>
</div>
<div class="studio-xblock-wrapper" data-locator="locator-component-B2">
<section class="wrapper-xblock level-element">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button"></a>
</li>
<li class="action-item action-duplicate">
<a href="#" class="duplicate-button action-button"></a>
</li>
<li class="action-item action-delete">
<a href="#" class="delete-button action-button"></a>
</li>
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render"></article>
</section>
</div>
<div class="studio-xblock-wrapper" data-locator="locator-component-B3">
<section class="wrapper-xblock level-element">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button"></a>
</li>
<li class="action-item action-duplicate">
<a href="#" class="duplicate-button action-button"></a>
</li>
<li class="action-item action-delete">
<a href="#" class="delete-button action-button"></a>
</li>
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render"></article>
</section>
</div>
<div class="add-xblock-component new-component-item adding"></div>
</div>
</article>
</section>
<div class="container-paging-footer"></div>
</div>
</article>
cms/templates/js/mock/mock-container-paged-xblock.underscore
0 → 100644
View file @
ff1a08cb
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-details">
<span class="xblock-display-name">Test Container</span>
</div>
<div class="header-actions">
<ul class="actions-list">
</ul>
</div>
</div>
</header>
<article class="xblock-render">
<div class="xblock" data-locator="locator-container" data-request-token="page-render-token"
data-init="MockXBlock" data-runtime-class="StudioRuntime" data-runtime-version="1">
<script type="text/template" id="paging-header-tpl">
<div class="meta-wrap">
<div class="meta">
<%= messageHtml %>
</div>
<nav class="pagination pagination-compact top">
<ol>
<li class="nav-item previous"><a class="nav-link previous-page-link" href="#"><i class="icon-angle-left"></i> <span class="nav-label"><%= gettext("Previous") %></span></a></li>
<li class="nav-item next"><a class="nav-link next-page-link" href="#"><span class="nav-label"><%= gettext("Next") %></span> <i class="icon-angle-right"></i></a></li>
</ol>
</nav>
</div>
</script>
<script type="text/template" id="paging-footer-tpl">
<nav class="pagination pagination-full bottom">
<ol>
<li class="nav-item previous"><a class="nav-link previous-page-link" href="#"><i class="icon-angle-left"></i> <span class="nav-label"><%= gettext("Previous") %></span></a></li>
<li class="nav-item page">
<div class="pagination-form">
<label class="page-number-label" for="page-number"><%= gettext("Page number") %></label>
<input id="page-number-input" class="page-number-input" name="page-number" type="text" size="4" />
</div>
<span class="current-page"><%= current_page + 1 %></span>
<span class="page-divider">/</span>
<span class="total-pages"><%= total_pages %></span>
</li>
<li class="nav-item next"><a class="nav-link next-page-link" href="#"><span class="nav-label"><%= gettext("Next") %></span> <i class="icon-angle-right"></i></a></li>
</ol>
</nav>
</script>
<div class="container-paging-header"></div>
<div class="studio-xblock-wrapper" data-locator="locator-group-A">
<section class="wrapper-xblock level-nesting">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-details">
<a href="#" data-tooltip="Expand or Collapse" class="action expand-collapse expand">
<i class="icon-caret-down ui-toggle-expansion"></i>
<span class="sr">Expand or Collapse</span>
</a>
<span class="xblock-display-name">Group A</span>
</div>
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render">
<div class="xblock" data-request-token="page-render-token">
<div class="studio-xblock-wrapper" data-locator="locator-component-A1">
<section class="wrapper-xblock level-element">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button"></a>
</li>
<li class="action-item action-duplicate">
<a href="#" class="duplicate-button action-button"></a>
</li>
<li class="action-item action-delete">
<a href="#" class="delete-button action-button"></a>
</li>
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render"></article>
</section>
</div>
<div class="studio-xblock-wrapper" data-locator="locator-component-A2">
<section class="wrapper-xblock level-element">
<header class="xblock-header">
<div class="header-actions">
<div class="xblock-header-primary">
<ul class="actions-list">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button"></a>
</li>
<li class="action-item action-duplicate">
<a href="#" class="duplicate-button action-button"></a>
</li>
<li class="action-item action-delete">
<a href="#" class="delete-button action-button"></a>
</li>
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render"></article>
</section>
</div>
<div class="studio-xblock-wrapper" data-locator="locator-component-A3">
<section class="wrapper-xblock level-element">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button"></a>
</li>
<li class="action-item action-duplicate">
<a href="#" class="duplicate-button action-button"></a>
</li>
<li class="action-item action-delete">
<a href="#" class="delete-button action-button"></a>
</li>
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render"></article>
</section>
</div>
<div class="add-xblock-component new-component-item adding"></div>
</div>
</article>
</section>
</div>
<div class="studio-xblock-wrapper" data-locator="locator-group-B">
<section class="wrapper-xblock level-nesting">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-details">
<a href="#" data-tooltip="Expand or Collapse" class="action expand-collapse expand">
<i class="icon-caret-down ui-toggle-expansion"></i>
<span class="sr">Expand or Collapse</span>
</a>
<span class="xblock-display-name">Group B</span>
</div>
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render">
<div class="xblock" data-request-token="page-render-token">
<div class="studio-xblock-wrapper" data-locator="locator-component-B1">
<section class="wrapper-xblock level-element">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button"></a>
</li>
<li class="action-item action-duplicate">
<a href="#" class="duplicate-button action-button"></a>
</li>
<li class="action-item action-delete">
<a href="#" class="delete-button action-button"></a>
</li>
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render"></article>
</section>
</div>
<div class="studio-xblock-wrapper" data-locator="locator-component-B2">
<section class="wrapper-xblock level-element">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button"></a>
</li>
<li class="action-item action-duplicate">
<a href="#" class="duplicate-button action-button"></a>
</li>
<li class="action-item action-delete">
<a href="#" class="delete-button action-button"></a>
</li>
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render"></article>
</section>
</div>
<div class="studio-xblock-wrapper" data-locator="locator-component-B3">
<section class="wrapper-xblock level-element">
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-actions">
<ul class="actions-list">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button"></a>
</li>
<li class="action-item action-duplicate">
<a href="#" class="duplicate-button action-button"></a>
</li>
<li class="action-item action-delete">
<a href="#" class="delete-button action-button"></a>
</li>
<li class="action-item action-drag">
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
</li>
</ul>
</div>
</div>
</header>
<article class="xblock-render"></article>
</section>
</div>
<div class="add-xblock-component new-component-item adding"></div>
</div>
</article>
</section>
<div class="container-paging-footer"></div>
</div>
</article>
cms/templates/library.html
View file @
ff1a08cb
...
...
@@ -22,8 +22,12 @@ from django.utils.translation import ugettext as _
<
%
block
name=
"requirejs"
>
require(["js/factories/library"], function(LibraryFactory) {
LibraryFactory(
${component_templates | n},
${json.dumps(xblock_info) | n}
${component_templates | n}, ${json.dumps(xblock_info) | n},
{
isUnitPage: false,
enable_paging: true,
page_size: 10
}
);
});
</
%
block>
...
...
common/lib/xmodule/xmodule/library_root_xblock.py
View file @
ff1a08cb
...
...
@@ -3,10 +3,10 @@
"""
import
logging
from
.studio_editable
import
StudioEditableModule
from
xblock.core
import
XBlock
from
xblock.fields
import
Scope
,
String
,
List
from
xblock.fragment
import
Fragment
from
xmodule.studio_editable
import
StudioEditableModule
log
=
logging
.
getLogger
(
__name__
)
...
...
@@ -42,29 +42,55 @@ class LibraryRoot(XBlock):
def
author_view
(
self
,
context
):
"""
Renders the Studio preview view
, which supports drag and drop
.
Renders the Studio preview view.
"""
fragment
=
Fragment
()
self
.
render_children
(
context
,
fragment
,
can_reorder
=
False
,
can_add
=
True
)
return
fragment
def
render_children
(
self
,
context
,
fragment
,
can_reorder
=
False
,
can_add
=
False
):
# pylint: disable=unused-argument
"""
Renders the children of the module with HTML appropriate for Studio. If can_reorder is True,
then the children will be rendered to support drag and drop.
"""
contents
=
[]
for
child_key
in
self
.
children
:
# pylint: disable=E1101
context
[
'reorderable_items'
]
.
add
(
child_key
)
paging
=
context
.
get
(
'paging'
,
None
)
children_count
=
len
(
self
.
children
)
# pylint: disable=no-member
item_start
,
item_end
=
0
,
children_count
# TODO sort children
if
paging
:
page_number
=
paging
.
get
(
'page_number'
,
0
)
raw_page_size
=
paging
.
get
(
'page_size'
,
None
)
page_size
=
raw_page_size
if
raw_page_size
is
not
None
else
children_count
item_start
,
item_end
=
page_size
*
page_number
,
page_size
*
(
page_number
+
1
)
children_to_show
=
self
.
children
[
item_start
:
item_end
]
# pylint: disable=no-member
for
child_key
in
children_to_show
:
# pylint: disable=E1101
child
=
self
.
runtime
.
get_block
(
child_key
)
rendered_child
=
self
.
runtime
.
render_child
(
child
,
StudioEditableModule
.
get_preview_view_name
(
child
),
context
)
child_view_name
=
StudioEditableModule
.
get_preview_view_name
(
child
)
rendered_child
=
self
.
runtime
.
render_child
(
child
,
child_view_name
,
context
)
fragment
.
add_frag_resources
(
rendered_child
)
contents
.
append
({
'id'
:
unicode
(
child_key
),
'content'
:
rendered_child
.
content
,
'id'
:
child
.
location
.
to_deprecated_string
(
),
'content'
:
rendered_child
.
content
})
fragment
.
add_content
(
self
.
runtime
.
render_template
(
"studio_render_children_view.html"
,
{
'items'
:
contents
,
'xblock_context'
:
context
,
'can_add'
:
True
,
'can_reorder'
:
True
,
}))
return
fragment
fragment
.
add_content
(
self
.
runtime
.
render_template
(
"studio_render_paged_children_view.html"
,
{
'items'
:
contents
,
'xblock_context'
:
context
,
'can_add'
:
can_add
,
'can_reorder'
:
False
,
'first_displayed'
:
item_start
,
'total_children'
:
children_count
,
'displayed_children'
:
len
(
children_to_show
)
})
)
@property
def
display_org_with_default
(
self
):
...
...
common/lib/xmodule/xmodule/video_module/video_handlers.py
View file @
ff1a08cb
...
...
@@ -155,7 +155,6 @@ class VideoStudentViewHandlers(object):
if
transcript_name
:
# Get the asset path for course
asset_path
=
None
course
=
self
.
descriptor
.
runtime
.
modulestore
.
get_course
(
self
.
course_id
)
if
course
.
static_asset_path
:
asset_path
=
course
.
static_asset_path
...
...
lms/templates/studio_render_paged_children_view.html
0 → 100644
View file @
ff1a08cb
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
%
>
<
%
namespace
name=
'static'
file=
'static_content.html'
/>
% for template_name in ["paging-header", "paging-footer"]:
<script
type=
"text/template"
id=
"${template_name}-tpl"
>
<%
static
:
include
path
=
"js/${template_name}.underscore"
/>
</script>
% endfor
<div
class=
"xblock-container-paging-parameters"
data-start=
"${first_displayed}"
data-displayed=
"${displayed_children}"
data-total=
"${total_children}"
></div>
<div
class=
"container-paging-header"
></div>
% for item in items:
${item['content']}
% endfor
% if can_add:
<div
class=
"add-xblock-component new-component-item adding"
></div>
% endif
<div
class=
"container-paging-footer"
></div>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment