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):
...
@@ -237,12 +237,28 @@ def xblock_view_handler(request, usage_key_string, view_name):
if
view_name
==
'reorderable_container_child_preview'
:
if
view_name
==
'reorderable_container_child_preview'
:
reorderable_items
.
add
(
xblock
.
location
)
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.
# Set up the context to be passed to each XBlock's render method.
context
=
{
context
=
{
'is_pages_view'
:
is_pages_view
,
# This setting disables the recursive wrapping of xblocks
'is_pages_view'
:
is_pages_view
,
# This setting disables the recursive wrapping of xblocks
'is_unit_page'
:
is_unit
(
xblock
),
'is_unit_page'
:
is_unit
(
xblock
),
'root_xblock'
:
xblock
if
(
view_name
==
'container_preview'
)
else
None
,
'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
)
fragment
=
get_preview_fragment
(
request
,
xblock
,
context
)
...
...
cms/static/coffee/spec/main.coffee
View file @
ff1a08cb
...
@@ -239,6 +239,7 @@ define([
...
@@ -239,6 +239,7 @@ define([
"js/spec/views/assets_spec"
,
"js/spec/views/assets_spec"
,
"js/spec/views/baseview_spec"
,
"js/spec/views/baseview_spec"
,
"js/spec/views/container_spec"
,
"js/spec/views/container_spec"
,
"js/spec/views/library_container_spec"
,
"js/spec/views/group_configuration_spec"
,
"js/spec/views/group_configuration_spec"
,
"js/spec/views/paging_spec"
,
"js/spec/views/paging_spec"
,
"js/spec/views/unit_outline_spec"
,
"js/spec/views/unit_outline_spec"
,
...
...
cms/static/js/factories/container.js
View file @
ff1a08cb
define
([
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'
,
'js/collections/component_template'
,
'xmodule'
,
'coffee/src/main'
,
'xblock/cms.runtime.v1'
'xblock/cms.runtime.v1'
],
],
function
(
$
,
XBlockInfo
,
ContainerPage
,
ComponentTemplates
,
xmoduleLoader
)
{
function
(
$
,
_
,
XBlockInfo
,
ContainerPage
,
ComponentTemplates
,
xmoduleLoader
)
{
'use strict'
;
'use strict'
;
return
function
(
componentTemplates
,
XBlockInfoJson
,
action
,
isUnitPage
)
{
return
function
(
componentTemplates
,
XBlockInfoJson
,
action
,
options
)
{
var
templates
=
new
ComponentTemplates
(
componentTemplates
,
{
parse
:
true
}),
var
main_options
=
{
mainXBlockInfo
=
new
XBlockInfo
(
XBlockInfoJson
,
{
parse
:
true
});
xmoduleLoader
.
done
(
function
()
{
var
view
=
new
ContainerPage
({
el
:
$
(
'#content'
),
el
:
$
(
'#content'
),
model
:
mainXBlockInfo
,
model
:
new
XBlockInfo
(
XBlockInfoJson
,
{
parse
:
true
})
,
action
:
action
,
action
:
action
,
templates
:
templates
,
templates
:
new
ComponentTemplates
(
componentTemplates
,
{
parse
:
true
})
isUnitPage
:
isUnitPage
};
});
xmoduleLoader
.
done
(
function
()
{
var
view
=
new
ContainerPage
(
_
.
extend
(
main_options
,
options
));
view
.
render
();
view
.
render
();
});
});
};
};
...
...
cms/static/js/factories/library.js
View file @
ff1a08cb
define
([
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'
,
'js/collections/component_template'
,
'xmodule'
,
'coffee/src/main'
,
'xblock/cms.runtime.v1'
'xblock/cms.runtime.v1'
],
],
function
(
$
,
XBlockInfo
,
ContainerPage
,
ComponentTemplates
,
xmoduleLoader
)
{
function
(
$
,
_
,
XBlockInfo
,
ContainerPage
,
ComponentTemplates
,
xmoduleLoader
)
{
'use strict'
;
'use strict'
;
return
function
(
componentTemplates
,
XBlockInfoJson
)
{
return
function
(
componentTemplates
,
XBlockInfoJson
,
options
)
{
var
templates
=
new
ComponentTemplates
(
componentTemplates
,
{
parse
:
true
}),
var
main_options
=
{
mainXBlockInfo
=
new
XBlockInfo
(
XBlockInfoJson
,
{
parse
:
true
});
el
:
$
(
'#content'
),
model
:
new
XBlockInfo
(
XBlockInfoJson
,
{
parse
:
true
}),
templates
:
new
ComponentTemplates
(
componentTemplates
,
{
parse
:
true
}),
action
:
'view'
};
xmoduleLoader
.
done
(
function
()
{
xmoduleLoader
.
done
(
function
()
{
var
view
=
new
ContainerPage
({
var
view
=
new
ContainerPage
(
_
.
extend
(
main_options
,
options
));
el
:
$
(
'#content'
),
model
:
mainXBlockInfo
,
action
:
"view"
,
templates
:
templates
,
isUnitPage
:
false
});
view
.
render
();
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
...
@@ -3,537 +3,560 @@ define(["jquery", "underscore", "underscore.string", "js/common_helpers/ajax_hel
"js/views/pages/container"
,
"js/models/xblock_info"
,
"jquery.simulate"
],
"js/views/pages/container"
,
"js/models/xblock_info"
,
"jquery.simulate"
],
function
(
$
,
_
,
str
,
AjaxHelpers
,
TemplateHelpers
,
EditHelpers
,
ContainerPage
,
XBlockInfo
)
{
function
(
$
,
_
,
str
,
AjaxHelpers
,
TemplateHelpers
,
EditHelpers
,
ContainerPage
,
XBlockInfo
)
{
describe
(
"ContainerPage"
,
function
()
{
function
parameterized_suite
(
label
,
global_page_options
,
fixtures
)
{
var
lastRequest
,
renderContainerPage
,
expectComponents
,
respondWithHtml
,
describe
(
label
+
" ContainerPage"
,
function
()
{
model
,
containerPage
,
requests
,
initialDisplayName
,
var
lastRequest
,
getContainerPage
,
renderContainerPage
,
expectComponents
,
respondWithHtml
,
mockContainerPage
=
readFixtures
(
'mock/mock-container-page.underscore'
),
model
,
containerPage
,
requests
,
initialDisplayName
,
mockContainerXBlockHtml
=
readFixtures
(
'mock/mock-container-xblock.underscore'
),
mockContainerPage
=
readFixtures
(
'mock/mock-container-page.underscore'
),
mockBadContainerXBlockHtml
=
readFixtures
(
'mock/mock-bad-javascript-container-xblock.underscore'
),
mockContainerXBlockHtml
=
readFixtures
(
fixtures
.
initial
),
mockBadXBlockContainerXBlockHtml
=
readFixtures
(
'mock/mock-bad-xblock-container-xblock.underscore'
),
mockXBlockHtml
=
readFixtures
(
fixtures
.
add_response
),
mockUpdatedContainerXBlockHtml
=
readFixtures
(
'mock/mock-updated-container-xblock.underscore'
),
mockBadContainerXBlockHtml
=
readFixtures
(
'mock/mock-bad-javascript-container-xblock.underscore'
),
mockXBlockEditorHtml
=
readFixtures
(
'mock/mock-xblock-editor.underscore'
);
mockBadXBlockContainerXBlockHtml
=
readFixtures
(
'mock/mock-bad-xblock-container-xblock.underscore'
),
mockUpdatedContainerXBlockHtml
=
readFixtures
(
'mock/mock-updated-container-xblock.underscore'
),
beforeEach
(
function
()
{
mockXBlockEditorHtml
=
readFixtures
(
'mock/mock-xblock-editor.underscore'
);
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'
});
});
afterEach
(
function
()
{
beforeEach
(
function
()
{
EditHelpers
.
uninstallMockXBlock
();
var
newDisplayName
=
'New Display Name'
;
});
lastRequest
=
function
()
{
return
requests
[
requests
.
length
-
1
];
};
EditHelpers
.
installEditTemplates
();
TemplateHelpers
.
installTemplate
(
'xblock-string-field-editor'
);
respondWithHtml
=
function
(
html
)
{
TemplateHelpers
.
installTemplate
(
'container-message'
);
var
requestIndex
=
requests
.
length
-
1
;
appendSetFixtures
(
mockContainerPage
);
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
);
});
};
describe
(
"Initial display"
,
function
()
{
EditHelpers
.
installMockXBlock
({
it
(
'can render itself'
,
function
()
{
data
:
"<p>Some HTML</p>"
,
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
metadata
:
{
expect
(
containerPage
.
$
(
'.xblock-header'
).
length
).
toBe
(
9
);
display_name
:
newDisplayName
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'
);
});
it
(
'can show an xblock with an invalid XBlock'
,
function
()
{
initialDisplayName
=
'Test Container'
;
renderContainerPage
(
this
,
mockBadXBlockContainerXBlockHtml
);
expect
(
containerPage
.
$
(
'.wrapper-xblock .level-nesting'
)).
not
.
toHaveClass
(
'is-hidden'
);
expect
(
containerPage
.
$
(
'.ui-loading'
)).
toHaveClass
(
'is-hidden'
);
});
it
(
'inline edits the display name when performing a new action'
,
function
()
{
model
=
new
XBlockInfo
({
renderContainerPage
(
this
,
mockContainerXBlockHtml
,
{
id
:
'locator-container'
,
action
:
'new'
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
()
{
afterEach
(
function
()
{
var
updatedDisplayName
=
'Updated Test Container'
,
EditHelpers
.
uninstallMockXBlock
();
getDisplayNameWrapper
;
afterEach
(
function
()
{
EditHelpers
.
cancelModalIfShowing
();
});
});
getDisplayNameWrapper
=
function
()
{
lastRequest
=
function
()
{
return
containerPage
.
$
(
'.wrapper-xblock-field'
)
;
return
requests
[
requests
.
length
-
1
]
;
};
};
it
(
'can edit itself'
,
function
()
{
respondWithHtml
=
function
(
html
)
{
var
editButtons
,
displayNameElement
;
var
requestIndex
=
requests
.
length
-
1
;
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
AjaxHelpers
.
respondWithJson
(
displayNameElement
=
containerPage
.
$
(
'.page-header-title'
);
requests
,
{
html
:
html
,
"resources"
:
[]
},
// Click the root edit button
requestIndex
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
);
});
it
(
'can inline edit the display name'
,
function
()
{
getContainerPage
=
function
(
options
)
{
var
displayNameInput
,
displayNameWrapper
;
var
default_options
=
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
model
:
model
,
displayNameWrapper
=
getDisplayNameWrapper
();
templates
:
EditHelpers
.
mockComponentTemplates
,
displayNameInput
=
EditHelpers
.
inlineEdit
(
displayNameWrapper
,
updatedDisplayName
);
el
:
$
(
'#content'
)
displayNameInput
.
change
();
};
// This is the response for the change operation.
return
new
ContainerPage
(
_
.
extend
(
options
||
{},
global_page_options
,
default_options
));
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
);
});
});
describe
(
"Editing an xblock"
,
function
()
{
renderContainerPage
=
function
(
test
,
html
,
options
)
{
afterEach
(
function
()
{
requests
=
AjaxHelpers
.
requests
(
test
);
EditHelpers
.
cancelModalIfShowing
();
containerPage
=
getContainerPage
(
options
);
});
containerPage
.
render
();
respondWithHtml
(
html
);
};
it
(
'can show an edit modal for a child xblock'
,
function
()
{
expectComponents
=
function
(
container
,
locators
)
{
var
editButtons
;
// verify expected components (in expected order) by their locators
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
var
components
=
$
(
container
).
find
(
'.studio-xblock-wrapper'
);
editButtons
=
containerPage
.
$
(
'.wrapper-xblock .edit-button'
);
expect
(
components
.
length
).
toBe
(
locators
.
length
);
// The container should have rendered six mock xblocks
_
.
each
(
locators
,
function
(
locator
,
locator_index
)
{
expect
(
editButtons
.
length
).
toBe
(
6
);
expect
(
$
(
components
[
locator_index
]).
data
(
'locator'
)).
toBe
(
locator
);
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 show an edit modal for a child xblock with broken JavaScript'
,
function
()
{
describe
(
"Initial display"
,
function
()
{
var
editButtons
;
it
(
'can render itself'
,
function
()
{
renderContainerPage
(
this
,
mockBadContainerXBlockHtml
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
editButtons
=
containerPage
.
$
(
'.wrapper-xblock .edit-button'
);
expect
(
containerPage
.
$
(
'.xblock-header'
).
length
).
toBe
(
9
);
editButtons
[
0
].
click
();
expect
(
containerPage
.
$
(
'.wrapper-xblock .level-nesting'
)).
not
.
toHaveClass
(
'is-hidden'
);
AjaxHelpers
.
respondWithJson
(
requests
,
{
html
:
mockXBlockEditorHtml
,
resources
:
[]
});
});
expect
(
EditHelpers
.
isShowingModal
()).
toBeTruthy
();
});
});
describe
(
"Editing an xmodule"
,
function
()
{
var
mockXModuleEditor
=
readFixtures
(
'mock/mock-xmodule-editor.underscore'
),
newDisplayName
=
'New Display Name'
;
beforeEach
(
function
()
{
it
(
'shows a loading indicator'
,
function
()
{
EditHelpers
.
installMockXModule
({
requests
=
AjaxHelpers
.
requests
(
this
);
data
:
"<p>Some HTML</p>"
,
containerPage
=
getContainerPage
();
metadata
:
{
containerPage
.
render
();
display_name
:
newDisplayName
expect
(
containerPage
.
$
(
'.ui-loading'
)).
not
.
toHaveClass
(
'is-hidden'
);
}
respondWithHtml
(
mockContainerXBlockHtml
);
expect
(
containerPage
.
$
(
'.ui-loading'
)).
toHaveClass
(
'is-hidden'
);
});
});
});
afterEach
(
function
()
{
it
(
'can show an xblock with broken JavaScript'
,
function
()
{
EditHelpers
.
uninstallMockXModule
();
renderContainerPage
(
this
,
mockBadContainerXBlockHtml
);
EditHelpers
.
cancelModalIfShowing
();
expect
(
containerPage
.
$
(
'.wrapper-xblock .level-nesting'
)).
not
.
toHaveClass
(
'is-hidden'
);
});
expect
(
containerPage
.
$
(
'.ui-loading'
)).
toHaveClass
(
'is-hidden'
);
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
:
[]
});
});
modal
=
$
(
'.edit-xblock-modal'
);
it
(
'can show an xblock with an invalid XBlock'
,
function
()
{
expect
(
modal
.
length
).
toBe
(
1
);
renderContainerPage
(
this
,
mockBadXBlockContainerXBlockHtml
);
// Click on the settings tab
expect
(
containerPage
.
$
(
'.wrapper-xblock .level-nesting'
)).
not
.
toHaveClass
(
'is-hidden'
);
modal
.
find
(
'.settings-button'
).
click
();
expect
(
containerPage
.
$
(
'.ui-loading'
)).
toHaveClass
(
'is-hidden'
);
// 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
it
(
'inline edits the display name when performing a new action'
,
function
()
{
respondWithHtml
(
mockUpdatedXBlockHtml
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
,
{
action
:
'new'
// Verify that the xblock was updated
});
expect
(
containerPage
.
$
(
'.mock-updated-content'
).
text
()).
toBe
(
'Mock Update'
);
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
()
{
describe
(
"
Editing the container"
,
function
()
{
var
clickDelete
,
deleteComponent
,
deleteComponentWithSuccess
,
var
updatedDisplayName
=
'Updated Test Container'
,
promptSpy
;
getDisplayNameWrapper
;
beforeEach
(
function
()
{
afterEach
(
function
()
{
promptSpy
=
EditHelpers
.
createPromptSpy
();
EditHelpers
.
cancelModalIfShowing
();
});
});
clickDelete
=
function
(
componentIndex
,
clickNo
)
{
getDisplayNameWrapper
=
function
()
{
return
containerPage
.
$
(
'.wrapper-xblock-field'
);
};
// find all delete buttons for the given group
it
(
'can edit itself'
,
function
()
{
var
deleteButtons
=
getGroupElement
().
find
(
".delete-button"
);
var
editButtons
,
displayNameElement
;
expect
(
deleteButtons
.
length
).
toBe
(
NUM_COMPONENTS_PER_GROUP
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
displayNameElement
=
containerPage
.
$
(
'.page-header-title'
);
// click the requested delete button
// Click the root edit button
deleteButtons
[
componentIndex
].
click
();
editButtons
=
containerPage
.
$
(
'.nav-actions .edit-button'
);
editButtons
.
first
().
click
();
// click the 'yes' or 'no' button in the prompt
// Expect a request to be made to show the studio view for the container
EditHelpers
.
confirmPrompt
(
promptSpy
,
clickNo
);
expect
(
str
.
startsWith
(
lastRequest
().
url
,
'/xblock/locator-container/studio_view'
)).
toBeTruthy
();
};
AjaxHelpers
.
respondWithJson
(
requests
,
{
html
:
mockContainerXBlockHtml
,
resources
:
[]
});
expect
(
EditHelpers
.
isShowingModal
()).
toBeTruthy
();
deleteComponent
=
function
(
componentIndex
)
{
// Expect the correct title to be shown
clickDelete
(
componentIndex
);
expect
(
EditHelpers
.
getModalTitle
()).
toBe
(
'Editing: Test Container'
);
AjaxHelpers
.
respondWithJson
(
requests
,
{});
//
second to last request contains given component's id (to delete the component)
//
Press the save button and respond with a success message to the save
AjaxHelpers
.
expectJsonRequest
(
requests
,
'DELETE'
,
EditHelpers
.
pressModalButton
(
'.action-save'
);
'/xblock/locator-component-'
+
GROUP_TO_TEST
+
(
componentIndex
+
1
),
AjaxHelpers
.
respondWithJson
(
requests
,
{
});
null
,
requests
.
length
-
2
);
expect
(
EditHelpers
.
isShowingModal
()).
toBeFalsy
(
);
// final request to refresh the xblock info
// Expect the last request be to refresh the container page
AjaxHelpers
.
expectJsonRequest
(
requests
,
'GET'
,
'/xblock/locator-container'
);
expect
(
str
.
startsWith
(
lastRequest
().
url
,
};
'/xblock/locator-container/container_preview'
)).
toBeTruthy
();
AjaxHelpers
.
respondWithJson
(
requests
,
{
html
:
mockUpdatedContainerXBlockHtml
,
resources
:
[]
});
deleteComponentWithSuccess
=
function
(
componentIndex
)
{
// Respond to the subsequent xblock info fetch request.
deleteComponent
(
componentIndex
);
AjaxHelpers
.
respondWithJson
(
requests
,
{
"display_name"
:
updatedDisplayName
}
);
// verify the new list of components within the group
// Expect the title to have been updated
expectComponents
(
expect
(
displayNameElement
.
text
().
trim
()).
toBe
(
updatedDisplayName
);
getGroupElement
(),
});
_
.
without
(
allComponentsInGroup
,
allComponentsInGroup
[
componentIndex
])
);
};
it
(
"can delete the first xblock"
,
function
()
{
it
(
'can inline edit the display name'
,
function
()
{
var
displayNameInput
,
displayNameWrapper
;
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
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
()
{
describe
(
"Editing an xblock"
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
afterEach
(
function
()
{
deleteComponentWithSuccess
(
1
);
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
);
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
);
renderContainerPage
(
this
,
mockBadContainerXBlockHtml
);
containerPage
.
$
(
'.delete-button'
).
first
().
click
();
editButtons
=
containerPage
.
$
(
'.wrapper-xblock .edit-button'
);
EditHelpers
.
confirmPrompt
(
promptSpy
);
editButtons
[
0
].
click
();
AjaxHelpers
.
respondWithJson
(
requests
,
{});
AjaxHelpers
.
respondWithJson
(
requests
,
{
// expect the second to last request to be a delete of the xblock
html
:
mockXBlockEditorHtml
,
AjaxHelpers
.
expectJsonRequest
(
requests
,
'DELETE'
,
'/xblock/locator-broken-javascript'
,
resources
:
[]
null
,
requests
.
length
-
2
);
});
// expect the last request to be a fetch of the xblock info for the parent container
expect
(
EditHelpers
.
isShowingModal
()).
toBeTruthy
();
AjaxHelpers
.
expectJsonRequest
(
requests
,
'GET'
,
'/xblock/locator-container'
);
});
});
});
it
(
'does not delete when clicking No in prompt'
,
function
()
{
describe
(
"Editing an xmodule"
,
function
()
{
var
numRequests
;
var
mockXModuleEditor
=
readFixtures
(
'mock/mock-xmodule-editor.underscore'
),
newDisplayName
=
'New Display Name'
;
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
);
// no requests should have been sent to the server
beforeEach
(
function
()
{
expect
(
requests
.
length
).
toBe
(
numRequests
);
EditHelpers
.
installMockXModule
({
data
:
"<p>Some HTML</p>"
,
metadata
:
{
display_name
:
newDisplayName
}
});
});
});
it
(
'shows a notification during the delete operation'
,
function
()
{
afterEach
(
function
()
{
var
notificationSpy
=
EditHelpers
.
createNotificationSpy
();
EditHelpers
.
uninstallMockXModule
();
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
EditHelpers
.
cancelModalIfShowing
();
clickDelete
(
0
);
EditHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Deleting/
);
AjaxHelpers
.
respondWithJson
(
requests
,
{});
EditHelpers
.
verifyNotificationHidden
(
notificationSpy
);
});
});
it
(
'does not delete an xblock upon failure'
,
function
()
{
it
(
'can save changes to settings'
,
function
()
{
var
notificationSpy
=
EditHelpers
.
createNotificationSpy
();
var
editButtons
,
modal
,
mockUpdatedXBlockHtml
;
mockUpdatedXBlockHtml
=
readFixtures
(
'mock/mock-updated-xblock.underscore'
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
clickDelete
(
0
);
editButtons
=
containerPage
.
$
(
'.wrapper-xblock .edit-button'
);
EditHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Deleting/
);
// The container should have rendered six mock xblocks
AjaxHelpers
.
respondWithError
(
requests
);
expect
(
editButtons
.
length
).
toBe
(
6
);
EditHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Deleting/
);
editButtons
[
0
].
click
();
expectComponents
(
getGroupElement
(),
allComponentsInGroup
);
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
()
{
describe
(
"xblock operations"
,
function
()
{
var
clickDuplicate
,
duplicateComponentWithSuccess
,
var
getGroupElement
,
refreshXBlockSpies
;
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
describe
(
"Deleting an xblock"
,
function
()
{
var
duplicateButtons
=
getGroupElement
().
find
(
".duplicate-button"
);
var
clickDelete
,
deleteComponent
,
deleteComponentWithSuccess
,
expect
(
duplicateButtons
.
length
).
toBe
(
NUM_COMPONENTS_PER_GROUP
)
;
promptSpy
;
// click the requested duplicate button
beforeEach
(
function
()
{
duplicateButtons
[
componentIndex
].
click
();
promptSpy
=
EditHelpers
.
createPromptSpy
();
};
});
clickDelete
=
function
(
componentIndex
,
clickNo
)
{
duplicateComponentWithSuccess
=
function
(
componentIndex
)
{
// find all delete buttons for the given group
refreshXBlockSpies
=
spyOn
(
containerPage
,
"refreshXBlock"
);
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
// click the 'yes' or 'no' button in the prompt
AjaxHelpers
.
expectJsonRequest
(
requests
,
'POST'
,
'/xblock/'
,
{
EditHelpers
.
confirmPrompt
(
promptSpy
,
clickNo
);
'duplicate_source_locator'
:
'locator-component-'
+
GROUP_TO_TEST
+
(
componentIndex
+
1
),
};
'parent_locator'
:
'locator-group-'
+
GROUP_TO_TEST
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
it
(
"can delete a middle xblock"
,
function
()
{
AjaxHelpers
.
respondWithJson
(
requests
,
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
'locator'
:
'locator-duplicated-component'
deleteComponentWithSuccess
(
1
);
});
});
// expect parent container to be refreshed
it
(
"can delete the last xblock"
,
function
()
{
expect
(
refreshXBlockSpies
).
toHaveBeenCalled
();
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
};
deleteComponentWithSuccess
(
NUM_COMPONENTS_PER_GROUP
-
1
);
});
it
(
"can duplicate the first xblock"
,
function
()
{
it
(
"can delete an xblock with broken JavaScript"
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
renderContainerPage
(
this
,
mockBadContainerXBlockHtml
);
duplicateComponentWithSuccess
(
0
);
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
()
{
it
(
'does not delete when clicking No in prompt'
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
var
numRequests
;
duplicateComponentWithSuccess
(
1
);
});
it
(
"can duplicate the last xblock"
,
function
()
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
numRequests
=
requests
.
length
;
duplicateComponentWithSuccess
(
NUM_COMPONENTS_PER_GROUP
-
1
);
});
it
(
"can duplicate an xblock with broken JavaScript"
,
function
()
{
// click delete on the first component but press no
renderContainerPage
(
this
,
mockBadContainerXBlockHtml
);
clickDelete
(
0
,
true
);
containerPage
.
$
(
'.duplicate-button'
).
first
().
click
();
AjaxHelpers
.
expectJsonRequest
(
requests
,
'POST'
,
'/xblock/'
,
{
// all components should still exist
'duplicate_source_locator'
:
'locator-broken-javascript'
,
expectComponents
(
getGroupElement
(),
allComponentsInGroup
);
'parent_locator'
:
'locator-container'
// no requests should have been sent to the server
expect
(
requests
.
length
).
toBe
(
numRequests
);
});
});
});
it
(
'shows a notification when duplicating
'
,
function
()
{
it
(
'shows a notification during the delete operation
'
,
function
()
{
var
notificationSpy
=
EditHelpers
.
createNotificationSpy
();
var
notificationSpy
=
EditHelpers
.
createNotificationSpy
();
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
clickDuplica
te
(
0
);
clickDele
te
(
0
);
EditHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Duplica
ting/
);
EditHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Dele
ting/
);
AjaxHelpers
.
respondWithJson
(
requests
,
{
"locator"
:
"new_item"
});
AjaxHelpers
.
respondWithJson
(
requests
,
{
});
EditHelpers
.
verifyNotificationHidden
(
notificationSpy
);
EditHelpers
.
verifyNotificationHidden
(
notificationSpy
);
});
});
it
(
'does not duplicate an xblock upon failure'
,
function
()
{
it
(
'does not delete an xblock upon failure'
,
function
()
{
var
notificationSpy
=
EditHelpers
.
createNotificationSpy
();
var
notificationSpy
=
EditHelpers
.
createNotificationSpy
();
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
refreshXBlockSpies
=
spyOn
(
containerPage
,
"refreshXBlock"
);
clickDelete
(
0
);
clickDuplicate
(
0
);
EditHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Deleting/
);
EditHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Duplicating/
);
AjaxHelpers
.
respondWithError
(
requests
);
AjaxHelpers
.
respondWithError
(
requests
);
EditHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Deleting/
);
expectComponents
(
getGroupElement
(),
allComponentsInGroup
);
expectComponents
(
getGroupElement
(),
allComponentsInGroup
);
expect
(
refreshXBlockSpies
).
not
.
toHaveBeenCalled
();
});
EditHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Duplicating/
);
});
});
});
describe
(
'createNewComponent '
,
function
()
{
describe
(
"Duplicating an xblock"
,
function
()
{
var
clickNewComponent
;
var
clickDuplicate
,
duplicateComponentWithSuccess
,
refreshXBlockSpies
;
clickNewComponent
=
function
(
index
)
{
clickDuplicate
=
function
(
componentIndex
)
{
containerPage
.
$
(
".new-component .new-component-type a.single-template"
)[
index
].
click
();
};
it
(
'sends the correct JSON to the server'
,
function
()
{
// find all duplicate buttons for the given group
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
var
duplicateButtons
=
getGroupElement
().
find
(
".duplicate-button"
);
clickNewComponent
(
0
);
expect
(
duplicateButtons
.
length
).
toBe
(
NUM_COMPONENTS_PER_GROUP
);
EditHelpers
.
verifyXBlockRequest
(
requests
,
{
"category"
:
"discussion"
,
"type"
:
"discussion"
,
"parent_locator"
:
"locator-group-A"
});
});
it
(
'shows a notification while creating'
,
function
()
{
// click the requested duplicate button
var
notificationSpy
=
EditHelpers
.
createNotificationSpy
();
duplicateButtons
[
componentIndex
].
click
();
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
};
clickNewComponent
(
0
);
EditHelpers
.
verifyNotificationShowing
(
notificationSpy
,
/Adding/
);
AjaxHelpers
.
respondWithJson
(
requests
,
{
});
EditHelpers
.
verifyNotificationHidden
(
notificationSpy
);
});
it
(
'does not insert component upon failure'
,
function
(
)
{
duplicateComponentWithSuccess
=
function
(
componentIndex
)
{
var
requestCount
;
refreshXBlockSpies
=
spyOn
(
containerPage
,
"refreshXBlock"
)
;
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
clickNewComponent
(
0
);
clickDuplicate
(
componentIndex
);
requestCount
=
requests
.
length
;
AjaxHelpers
.
respondWithError
(
requests
);
// verify content of request
// No new requests should be made to refresh the view
AjaxHelpers
.
expectJsonRequest
(
requests
,
'POST'
,
'/xblock/'
,
{
expect
(
requests
.
length
).
toBe
(
requestCount
);
'duplicate_source_locator'
:
'locator-component-'
+
GROUP_TO_TEST
+
(
componentIndex
+
1
),
expectComponents
(
getGroupElement
(),
allComponentsInGroup
);
'parent_locator'
:
'locator-group-'
+
GROUP_TO_TEST
});
});
describe
(
'Template Picker'
,
function
()
{
// send the response
var
showTemplatePicker
,
verifyCreateHtmlComponent
,
AjaxHelpers
.
respondWithJson
(
requests
,
{
mockXBlockHtml
=
readFixtures
(
'mock/mock-xblock.underscore'
);
'locator'
:
'locator-duplicated-component'
});
showTemplatePicker
=
function
()
{
// expect parent container to be refreshed
containerPage
.
$
(
'.new-component .new-component-type a.multiple-templates'
)[
0
].
click
();
expect
(
refreshXBlockSpies
).
toHaveBeenCalled
();
};
};
verifyCreateHtmlComponent
=
function
(
test
,
templateIndex
,
expectedRequest
)
{
it
(
"can duplicate the first xblock"
,
function
()
{
var
xblockCount
;
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
renderContainerPage
(
test
,
mockContainerXBlockHtml
);
duplicateComponentWithSuccess
(
0
);
showTemplatePicker
();
});
xblockCount
=
containerPage
.
$
(
'.studio-xblock-wrapper'
).
length
;
containerPage
.
$
(
'.new-component-html a'
)[
templateIndex
].
click
();
it
(
"can duplicate a middle xblock"
,
function
()
{
EditHelpers
.
verifyXBlockRequest
(
requests
,
expectedRequest
);
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"
});
AjaxHelpers
.
respondWithJson
(
requests
,
{
"locator"
:
"new_item"
});
respondWithHtml
(
mockXBlockHtml
);
EditHelpers
.
verifyNotificationHidden
(
notificationSpy
);
expect
(
containerPage
.
$
(
'.studio-xblock-wrapper'
).
length
).
toBe
(
xblockCount
+
1
);
});
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
()
{
it
(
'sends the correct JSON to the server'
,
function
()
{
verifyCreateHtmlComponent
(
this
,
0
,
{
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
"category"
:
"html"
,
clickNewComponent
(
0
);
EditHelpers
.
verifyXBlockRequest
(
requests
,
{
"category"
:
"discussion"
,
"type"
:
"discussion"
,
"parent_locator"
:
"locator-group-A"
"parent_locator"
:
"locator-group-A"
});
});
});
});
it
(
'can add an HTML component with a template'
,
function
()
{
it
(
'shows a notification while creating'
,
function
()
{
verifyCreateHtmlComponent
(
this
,
1
,
{
var
notificationSpy
=
EditHelpers
.
createNotificationSpy
();
"category"
:
"html"
,
renderContainerPage
(
this
,
mockContainerXBlockHtml
);
"boilerplate"
:
"announcement.yaml"
,
clickNewComponent
(
0
);
"parent_locator"
:
"locator-group-A"
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",
...
@@ -123,6 +123,10 @@ define(["jquery", "underscore", "js/views/xblock", "js/utils/module", "gettext",
});
});
},
},
acknowledgeXBlockDeletion
:
function
(
locator
){
this
.
notifyRuntime
(
'deleted-child'
,
locator
);
},
refresh
:
function
()
{
refresh
:
function
()
{
var
sortableInitializedClass
=
this
.
makeRequestSpecificSelector
(
'.reorderable-container.ui-sortable'
);
var
sortableInitializedClass
=
this
.
makeRequestSpecificSelector
(
'.reorderable-container.ui-sortable'
);
this
.
$
(
sortableInitializedClass
).
sortable
(
'refresh'
);
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 @@
...
@@ -3,10 +3,10 @@
* This page allows the user to understand and manipulate the xblock and its children.
* 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"
,
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/models/xblock_info"
,
"js/views/xblock_string_field_editor"
,
"js/views/pages/container_subviews"
,
"js/views/unit_outline"
,
"js/views/utils/xblock_utils"
],
"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
,
EditXBlockModal
,
XBlockInfo
,
XBlockStringFieldEditor
,
ContainerSubviews
,
UnitOutlineView
,
XBlockUtils
)
{
XBlockUtils
)
{
'use strict'
;
'use strict'
;
...
@@ -27,6 +27,10 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
...
@@ -27,6 +27,10 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
initialize
:
function
(
options
)
{
initialize
:
function
(
options
)
{
BasePage
.
prototype
.
initialize
.
call
(
this
,
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
({
this
.
nameEditor
=
new
XBlockStringFieldEditor
({
el
:
this
.
$
(
'.wrapper-xblock-field'
),
el
:
this
.
$
(
'.wrapper-xblock-field'
),
model
:
this
.
model
model
:
this
.
model
...
@@ -35,11 +39,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
...
@@ -35,11 +39,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
if
(
this
.
options
.
action
===
'new'
)
{
if
(
this
.
options
.
action
===
'new'
)
{
this
.
nameEditor
.
$
(
'.xblock-field-value-edit'
).
click
();
this
.
nameEditor
.
$
(
'.xblock-field-value-edit'
).
click
();
}
}
this
.
xblockView
=
new
ContainerView
({
this
.
xblockView
=
this
.
getXBlockView
();
el
:
this
.
$
(
'.wrapper-xblock'
),
model
:
this
.
model
,
view
:
this
.
view
});
this
.
messageView
=
new
ContainerSubviews
.
MessageView
({
this
.
messageView
=
new
ContainerSubviews
.
MessageView
({
el
:
this
.
$
(
'.container-message'
),
el
:
this
.
$
(
'.container-message'
),
model
:
this
.
model
model
:
this
.
model
...
@@ -75,6 +75,28 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
...
@@ -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
)
{
render
:
function
(
options
)
{
var
self
=
this
,
var
self
=
this
,
xblockView
=
this
.
xblockView
,
xblockView
=
this
.
xblockView
,
...
@@ -106,7 +128,8 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
...
@@ -106,7 +128,8 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
// Re-enable Backbone events for any updated DOM elements
// Re-enable Backbone events for any updated DOM elements
self
.
delegateEvents
();
self
.
delegateEvents
();
}
},
block_added
:
options
&&
options
.
block_added
});
});
},
},
...
@@ -144,7 +167,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
...
@@ -144,7 +167,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
modal
.
edit
(
xblockElement
,
this
.
model
,
{
modal
.
edit
(
xblockElement
,
this
.
model
,
{
refresh
:
function
()
{
refresh
:
function
()
{
self
.
refreshXBlock
(
xblockElement
);
self
.
refreshXBlock
(
xblockElement
,
false
);
}
}
});
});
},
},
...
@@ -226,7 +249,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
...
@@ -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
// Inform the runtime that the child has been deleted in case
// other views are listening to deletion events.
// 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.
// Update publish and last modified information from the server.
this
.
model
.
fetch
();
this
.
model
.
fetch
();
...
@@ -235,7 +258,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
...
@@ -235,7 +258,7 @@ define(["jquery", "underscore", "gettext", "js/views/pages/base_page", "js/views
onNewXBlock
:
function
(
xblockElement
,
scrollOffset
,
data
)
{
onNewXBlock
:
function
(
xblockElement
,
scrollOffset
,
data
)
{
ViewUtils
.
setScrollOffset
(
xblockElement
,
scrollOffset
);
ViewUtils
.
setScrollOffset
(
xblockElement
,
scrollOffset
);
xblockElement
.
data
(
'locator'
,
data
.
locator
);
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
...
@@ -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
* reorderable container then the element will be refreshed inline. If not, then the
* parent container will be refreshed instead.
* parent container will be refreshed instead.
* @param element An element representing the xblock to be refreshed.
* @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
),
var
xblockElement
=
this
.
findXBlockElement
(
element
),
parentElement
=
xblockElement
.
parent
(),
parentElement
=
xblockElement
.
parent
(),
rootLocator
=
this
.
xblockView
.
model
.
id
;
rootLocator
=
this
.
xblockView
.
model
.
id
;
if
(
xblockElement
.
length
===
0
||
xblockElement
.
data
(
'locator'
)
===
rootLocator
)
{
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'
))
{
}
else
if
(
parentElement
.
hasClass
(
'reorderable-container'
))
{
this
.
refreshChildXBlock
(
xblockElement
);
this
.
refreshChildXBlock
(
xblockElement
);
}
else
{
}
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) {
...
@@ -38,6 +38,12 @@ define(["underscore", "js/views/baseview"], function(_, BaseView) {
currentPage
=
collection
.
currentPage
+
1
,
currentPage
=
collection
.
currentPage
+
1
,
pageInput
=
this
.
$
(
"#page-number-input"
),
pageInput
=
this
.
$
(
"#page-number-input"
),
pageNumber
=
parseInt
(
pageInput
.
val
(),
10
);
pageNumber
=
parseInt
(
pageInput
.
val
(),
10
);
if
(
pageNumber
>
collection
.
totalPages
)
{
pageNumber
=
false
;
}
if
(
pageNumber
<=
0
)
{
pageNumber
=
false
;
}
if
(
pageNumber
&&
pageNumber
!==
currentPage
)
{
if
(
pageNumber
&&
pageNumber
!==
currentPage
)
{
view
.
setPage
(
pageNumber
-
1
);
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 @@
...
@@ -28,120 +28,7 @@
}
}
.pagination
{
.pagination
{
@include
clearfix
;
@extend
%pagination
;
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
;
}
}
}
}
}
.assets-table
{
.assets-table
{
...
...
cms/static/sass/elements/_xblocks.scss
View file @
ff1a08cb
...
@@ -103,6 +103,37 @@
...
@@ -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
//UI: default internal xblock content styles
...
...
cms/static/sass/style-app-extend1-rtl.scss
View file @
ff1a08cb
...
@@ -40,6 +40,7 @@
...
@@ -40,6 +40,7 @@
// +Base - Elements
// +Base - Elements
// ====================
// ====================
@import
'elements/typography'
;
@import
'elements/typography'
;
@import
'elements/pagination'
;
// pagination
@import
'elements/icons'
;
// references to icons used
@import
'elements/icons'
;
// references to icons used
@import
'elements/controls'
;
// buttons, link styles, sliders, etc.
@import
'elements/controls'
;
// buttons, link styles, sliders, etc.
@import
'elements/xblocks'
;
// studio rendering chrome for xblocks
@import
'elements/xblocks'
;
// studio rendering chrome for xblocks
...
...
cms/static/sass/style-app-extend1.scss
View file @
ff1a08cb
...
@@ -40,6 +40,7 @@
...
@@ -40,6 +40,7 @@
// +Base - Elements
// +Base - Elements
// ====================
// ====================
@import
'elements/typography'
;
@import
'elements/typography'
;
@import
'elements/pagination'
;
// pagination
@import
'elements/icons'
;
// references to icons used
@import
'elements/icons'
;
// references to icons used
@import
'elements/controls'
;
// buttons, link styles, sliders, etc.
@import
'elements/controls'
;
// buttons, link styles, sliders, etc.
@import
'elements/xblocks'
;
// studio rendering chrome for xblocks
@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 _
...
@@ -31,7 +31,10 @@ from django.utils.translation import ugettext as _
require(["js/factories/container"], function(ContainerFactory) {
require(["js/factories/container"], function(ContainerFactory) {
ContainerFactory(
ContainerFactory(
${component_templates | n}, ${json.dumps(xblock_info) | n},
${component_templates | n}, ${json.dumps(xblock_info) | n},
"${action}", ${json.dumps(is_unit_page)}
"${action}",
{
isUnitPage: ${json.dumps(is_unit_page)}
}
);
);
});
});
</
%
block>
</
%
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 _
...
@@ -22,8 +22,12 @@ from django.utils.translation import ugettext as _
<
%
block
name=
"requirejs"
>
<
%
block
name=
"requirejs"
>
require(["js/factories/library"], function(LibraryFactory) {
require(["js/factories/library"], function(LibraryFactory) {
LibraryFactory(
LibraryFactory(
${component_templates | n},
${component_templates | n}, ${json.dumps(xblock_info) | n},
${json.dumps(xblock_info) | n}
{
isUnitPage: false,
enable_paging: true,
page_size: 10
}
);
);
});
});
</
%
block>
</
%
block>
...
...
common/lib/xmodule/xmodule/library_root_xblock.py
View file @
ff1a08cb
...
@@ -3,10 +3,10 @@
...
@@ -3,10 +3,10 @@
"""
"""
import
logging
import
logging
from
.studio_editable
import
StudioEditableModule
from
xblock.core
import
XBlock
from
xblock.core
import
XBlock
from
xblock.fields
import
Scope
,
String
,
List
from
xblock.fields
import
Scope
,
String
,
List
from
xblock.fragment
import
Fragment
from
xblock.fragment
import
Fragment
from
xmodule.studio_editable
import
StudioEditableModule
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
...
@@ -42,29 +42,55 @@ class LibraryRoot(XBlock):
...
@@ -42,29 +42,55 @@ class LibraryRoot(XBlock):
def
author_view
(
self
,
context
):
def
author_view
(
self
,
context
):
"""
"""
Renders the Studio preview view
, which supports drag and drop
.
Renders the Studio preview view.
"""
"""
fragment
=
Fragment
()
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
=
[]
contents
=
[]
for
child_key
in
self
.
children
:
# pylint: disable=E1101
paging
=
context
.
get
(
'paging'
,
None
)
context
[
'reorderable_items'
]
.
add
(
child_key
)
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
)
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
)
fragment
.
add_frag_resources
(
rendered_child
)
contents
.
append
({
contents
.
append
({
'id'
:
unicode
(
child_key
),
'id'
:
child
.
location
.
to_deprecated_string
(
),
'content'
:
rendered_child
.
content
,
'content'
:
rendered_child
.
content
})
})
fragment
.
add_content
(
self
.
runtime
.
render_template
(
"studio_render_children_view.html"
,
{
fragment
.
add_content
(
'items'
:
contents
,
self
.
runtime
.
render_template
(
"studio_render_paged_children_view.html"
,
{
'xblock_context'
:
context
,
'items'
:
contents
,
'can_add'
:
True
,
'xblock_context'
:
context
,
'can_reorder'
:
True
,
'can_add'
:
can_add
,
}))
'can_reorder'
:
False
,
return
fragment
'first_displayed'
:
item_start
,
'total_children'
:
children_count
,
'displayed_children'
:
len
(
children_to_show
)
})
)
@property
@property
def
display_org_with_default
(
self
):
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):
...
@@ -155,7 +155,6 @@ class VideoStudentViewHandlers(object):
if
transcript_name
:
if
transcript_name
:
# Get the asset path for course
# Get the asset path for course
asset_path
=
None
course
=
self
.
descriptor
.
runtime
.
modulestore
.
get_course
(
self
.
course_id
)
course
=
self
.
descriptor
.
runtime
.
modulestore
.
get_course
(
self
.
course_id
)
if
course
.
static_asset_path
:
if
course
.
static_asset_path
:
asset_path
=
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