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
138cd459
Commit
138cd459
authored
May 29, 2014
by
Andy Armstrong
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #3819 from edx/andya/edit-containers
Allow editing of container xblocks/xmodules
parents
172c6d15
6b3e100c
Hide whitespace changes
Inline
Side-by-side
Showing
44 changed files
with
1016 additions
and
514 deletions
+1016
-514
CHANGELOG.rst
+2
-0
cms/djangoapps/contentstore/views/item.py
+8
-20
cms/djangoapps/contentstore/views/preview.py
+1
-6
cms/djangoapps/contentstore/views/tests/test_container_page.py
+1
-5
cms/djangoapps/contentstore/views/tests/test_unit_page.py
+2
-2
cms/static/coffee/src/views/module_edit.coffee
+5
-1
cms/static/js/models/xblock_info.js
+1
-1
cms/static/js/spec/views/container_spec.js
+1
-1
cms/static/js/spec/views/pages/container_spec.js
+71
-9
cms/static/js/spec/views/unit_spec.js
+95
-3
cms/static/js/spec_helpers/edit_helpers.js
+8
-2
cms/static/js/spec_helpers/modal_helpers.js
+12
-5
cms/static/js/views/container.js
+3
-2
cms/static/js/views/modals/edit_xblock.js
+7
-8
cms/static/js/views/pages/container.js
+10
-4
cms/static/sass/_base.scss
+1
-0
cms/static/sass/elements/_controls.scss
+1
-0
cms/static/sass/elements/_xblocks.scss
+63
-10
cms/static/sass/views/_container.scss
+40
-3
cms/static/sass/views/_unit.scss
+46
-6
cms/templates/container.html
+11
-2
cms/templates/container_xblock_component.html
+37
-20
cms/templates/js/mock/mock-container-page.underscore
+10
-3
cms/templates/js/mock/mock-container-xblock.underscore
+226
-210
cms/templates/js/mock/mock-unit-page-child-container.underscore
+37
-0
cms/templates/js/mock/mock-updated-container-xblock.underscore
+18
-0
cms/templates/studio_container_wrapper.html
+0
-39
cms/templates/studio_xblock_wrapper.html
+52
-28
cms/templates/widgets/sequence-edit.html
+0
-51
common/lib/xmodule/xmodule/js/src/sequence/edit.coffee
+0
-7
common/lib/xmodule/xmodule/js/src/vertical/edit.coffee
+0
-7
common/lib/xmodule/xmodule/js/src/wrapper/edit.coffee
+0
-10
common/lib/xmodule/xmodule/split_test_module.py
+22
-17
common/lib/xmodule/xmodule/studio_editable.py
+16
-7
common/lib/xmodule/xmodule/tests/test_split_module.py
+39
-2
common/lib/xmodule/xmodule/tests/test_vertical.py
+11
-0
common/lib/xmodule/xmodule/vertical_module.py
+12
-1
common/lib/xmodule/xmodule/wrapper_module.py
+0
-4
common/static/sass/_mixins.scss
+8
-8
common/test/acceptance/pages/studio/component_editor.py
+66
-0
common/test/acceptance/pages/studio/container.py
+21
-4
common/test/acceptance/pages/studio/utils.py
+1
-1
common/test/acceptance/tests/test_studio_container.py
+44
-2
lms/templates/studio_render_children_view.html
+7
-3
No files found.
CHANGELOG.rst
View file @
138cd459
...
...
@@ -14,6 +14,8 @@ Blades: Remove Video player outline. BLD-975.
Blades: Fix Youtube regular expression in video player editor. BLD-967.
Studio: Support editing of containers. STUD-1312.
Blades: Fix displaying transcripts on touch devices. BLD-1033.
Blades: Tolerance expressed in percentage now computes correctly. BLD-522.
...
...
cms/djangoapps/contentstore/views/item.py
View file @
138cd459
...
...
@@ -199,25 +199,6 @@ def xblock_view_handler(request, usage_key_string, view_name):
# change not authored by requestor but by xblocks.
store
.
update_item
(
xblock
,
None
)
elif
view_name
==
'student_view'
and
xblock_has_own_studio_page
(
xblock
):
context
=
{
'runtime_type'
:
'studio'
,
'container_view'
:
False
,
'read_only'
:
is_read_only
,
'root_xblock'
:
xblock
,
}
# For non-leaf xblocks on the unit page, show the special rendering
# which links to the new container page.
html
=
render_to_string
(
'container_xblock_component.html'
,
{
'xblock_context'
:
context
,
'xblock'
:
xblock
,
'locator'
:
usage_key
,
})
return
JsonResponse
({
'html'
:
html
,
'resources'
:
[],
})
elif
view_name
in
(
unit_views
+
container_views
):
is_container_view
=
(
view_name
in
container_views
)
...
...
@@ -245,8 +226,15 @@ def xblock_view_handler(request, usage_key_string, view_name):
# the component div. Note that the container view recursively adds headers
# into the preview fragment, so we don't want to add another header here.
if
not
is_container_view
:
fragment
.
content
=
render_to_string
(
'component.html'
,
{
# For non-leaf xblocks, show the special rendering which links to the new container page.
if
xblock_has_own_studio_page
(
xblock
):
template
=
'container_xblock_component.html'
else
:
template
=
'component.html'
fragment
.
content
=
render_to_string
(
template
,
{
'xblock_context'
:
context
,
'xblock'
:
xblock
,
'locator'
:
usage_key
,
'preview'
:
fragment
.
content
,
'label'
:
xblock
.
display_name
or
xblock
.
scope_ids
.
block_type
,
})
...
...
cms/djangoapps/contentstore/views/preview.py
View file @
138cd459
...
...
@@ -180,12 +180,7 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False):
'is_root'
:
is_root
,
'is_reorderable'
:
is_reorderable
,
}
# For child xblocks with their own page, render a link to the page
if
xblock_has_own_studio_page
(
xblock
)
and
not
is_root
:
template
=
'studio_container_wrapper.html'
else
:
template
=
'studio_xblock_wrapper.html'
html
=
render_to_string
(
template
,
template_context
)
html
=
render_to_string
(
'studio_xblock_wrapper.html'
,
template_context
)
frag
=
wrap_fragment
(
frag
,
html
)
return
frag
...
...
cms/djangoapps/contentstore/views/tests/test_container_page.py
View file @
138cd459
...
...
@@ -137,8 +137,6 @@ class ContainerPageTestCase(StudioPageTestCase):
"""
empty_child_container
=
ItemFactory
.
create
(
parent_location
=
self
.
vertical
.
location
,
category
=
'split_test'
,
display_name
=
'Split Test'
)
ItemFactory
.
create
(
parent_location
=
empty_child_container
.
location
,
category
=
'html'
,
display_name
=
'Split Child'
)
self
.
validate_preview_html
(
empty_child_container
,
self
.
reorderable_child_view
,
can_reorder
=
False
,
can_edit
=
False
,
can_add
=
False
)
...
...
@@ -148,9 +146,7 @@ class ContainerPageTestCase(StudioPageTestCase):
"""
empty_child_container
=
ItemFactory
.
create
(
parent_location
=
self
.
vertical
.
location
,
category
=
'split_test'
,
display_name
=
'Split Test'
)
ItemFactory
.
create
(
parent_location
=
empty_child_container
.
location
,
category
=
'html'
,
display_name
=
'Split Child'
)
modulestore
(
'draft'
)
.
convert_to_draft
(
self
.
vertical
.
location
)
draft_empty_child_container
=
modulestore
(
'draft'
)
.
convert_to_draft
(
empty_child_container
.
location
)
self
.
validate_preview_html
(
draft_empty_child_container
,
self
.
reorderable_child_view
,
can_reorder
=
True
,
can_edit
=
Fals
e
,
can_add
=
False
)
can_reorder
=
True
,
can_edit
=
Tru
e
,
can_add
=
False
)
cms/djangoapps/contentstore/views/tests/test_unit_page.py
View file @
138cd459
...
...
@@ -60,7 +60,7 @@ class UnitPageTestCase(StudioPageTestCase):
ItemFactory
.
create
(
parent_location
=
child_container
.
location
,
category
=
'html'
,
display_name
=
'grandchild'
)
self
.
validate_preview_html
(
child_container
,
'student_view'
,
can_reorder
=
True
,
can_edit
=
Fals
e
,
can_add
=
False
)
can_reorder
=
True
,
can_edit
=
Tru
e
,
can_add
=
False
)
def
test_draft_child_container_preview_html
(
self
):
"""
...
...
@@ -74,4 +74,4 @@ class UnitPageTestCase(StudioPageTestCase):
modulestore
(
'draft'
)
.
convert_to_draft
(
self
.
vertical
.
location
)
draft_child_container
=
modulestore
(
'draft'
)
.
get_item
(
child_container
.
location
)
self
.
validate_preview_html
(
draft_child_container
,
'student_view'
,
can_reorder
=
True
,
can_edit
=
Fals
e
,
can_add
=
False
)
can_reorder
=
True
,
can_edit
=
Tru
e
,
can_add
=
False
)
cms/static/coffee/src/views/module_edit.coffee
View file @
138cd459
...
...
@@ -15,7 +15,11 @@ define ["jquery", "underscore", "gettext", "xblock/runtime.v1",
@
render
()
loadDisplay
:
->
XBlock
.
initializeBlock
(
@
$el
.
find
(
'.xblock-student_view'
))
# Not all components render an inline student view, e.g. child containers which
# instead render a link to a separate container page.
xblockElement
=
@
$el
.
find
(
'.xblock-student_view'
)
if
xblockElement
.
length
>
0
XBlock
.
initializeBlock
(
xblockElement
)
createItem
:
(
parent
,
payload
,
callback
=->
)
->
payload
.
parent_locator
=
parent
...
...
cms/static/js/models/xblock_info.js
View file @
138cd459
...
...
@@ -11,7 +11,7 @@ define(["backbone", "js/utils/module"], function(Backbone, ModuleUtils) {
"is_container"
:
null
,
"data"
:
null
,
"metadata"
:
null
,
"children"
:
[]
"children"
:
null
}
});
return
XBlockInfo
;
...
...
cms/static/js/spec/views/container_spec.js
View file @
138cd459
...
...
@@ -11,7 +11,7 @@ define([ "jquery", "js/spec_helpers/create_sinon", "js/spec_helpers/view_helpers
getDragHandle
,
dragComponentVertically
,
dragComponentAbove
,
verifyRequest
,
verifyNumReorderCalls
,
respondToRequest
,
notificationSpy
,
rootLocator
=
'
testCourse/branch/draft/split_test/splitFFF
'
,
rootLocator
=
'
locator-container
'
,
containerTestUrl
=
'/xblock/'
+
rootLocator
,
groupAUrl
=
"/xblock/locator-group-A"
,
...
...
cms/static/js/spec/views/pages/container_spec.js
View file @
138cd459
...
...
@@ -7,6 +7,7 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
model
,
containerPage
,
requests
,
mockContainerPage
=
readFixtures
(
'mock/mock-container-page.underscore'
),
mockContainerXBlockHtml
=
readFixtures
(
'mock/mock-container-xblock.underscore'
),
mockUpdatedContainerXBlockHtml
=
readFixtures
(
'mock/mock-updated-container-xblock.underscore'
),
mockXBlockEditorHtml
=
readFixtures
(
'mock/mock-xblock-editor.underscore'
);
beforeEach
(
function
()
{
...
...
@@ -14,8 +15,8 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
appendSetFixtures
(
mockContainerPage
);
model
=
new
XBlockInfo
({
id
:
'
testCourse/branch/draft/block/verticalFFF
'
,
display_name
:
'Test
Unit
'
,
id
:
'
locator-container
'
,
display_name
:
'Test
Container
'
,
category
:
'vertical'
});
containerPage
=
new
ContainerPage
({
...
...
@@ -51,13 +52,14 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
});
};
describe
(
"
Basic
display"
,
function
()
{
describe
(
"
Initial
display"
,
function
()
{
it
(
'can render itself'
,
function
()
{
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
expect
(
containerPage
.
$el
.
select
(
'.xblock-header'
)).
toBeTruthy
();
expect
(
containerPage
.
$
(
'.wrapper-xblock'
)).
not
.
toHaveClass
(
'is-hidden'
);
expect
(
containerPage
.
$
(
'.no-container-content'
)).
toHaveClass
(
'is-hidden'
);
});
it
(
'shows a loading indicator'
,
function
()
{
requests
=
create_sinon
.
requests
(
this
);
containerPage
.
render
();
...
...
@@ -67,6 +69,66 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
});
});
describe
(
"Editing the container"
,
function
()
{
var
newDisplayName
=
'New Display Name'
;
beforeEach
(
function
()
{
edit_helpers
.
installMockXBlock
({
data
:
"<p>Some HTML</p>"
,
metadata
:
{
display_name
:
newDisplayName
}
});
});
afterEach
(
function
()
{
edit_helpers
.
uninstallMockXBlock
();
edit_helpers
.
cancelModalIfShowing
();
});
it
(
'can edit itself'
,
function
()
{
var
editButtons
,
modalElement
,
updatedTitle
=
'Updated Test Container'
;
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
// Click the root edit button
editButtons
=
containerPage
.
$
(
'.nav-actions .edit-button'
);
editButtons
.
first
().
click
();
modalElement
=
edit_helpers
.
getModalElement
();
// Expect a request to be made to show the studio view for the container
expect
(
lastRequest
().
url
).
toBe
(
'/xblock/locator-container/studio_view'
);
create_sinon
.
respondWithJson
(
requests
,
{
html
:
mockContainerXBlockHtml
,
resources
:
[]
});
expect
(
edit_helpers
.
isShowingModal
()).
toBeTruthy
();
// Expect the correct title to be shown
expect
(
modalElement
.
find
(
'.modal-window-title'
).
text
()).
toBe
(
'Editing: Test Container'
);
// Press the save button and respond with a success message to the save
edit_helpers
.
pressModalButton
(
'.action-save'
);
create_sinon
.
respondWithJson
(
requests
,
{
});
expect
(
edit_helpers
.
isShowingModal
()).
toBeFalsy
();
// Expect the last request be to refresh the container page
expect
(
lastRequest
().
url
).
toBe
(
'/xblock/locator-container/container_preview'
);
create_sinon
.
respondWithJson
(
requests
,
{
html
:
mockUpdatedContainerXBlockHtml
,
resources
:
[]
});
// Expect the title and breadcrumb to be updated
expect
(
containerPage
.
$
(
'.page-header-title'
).
text
().
trim
()).
toBe
(
updatedTitle
);
expect
(
containerPage
.
$
(
'.page-header .subtitle a'
).
last
().
text
().
trim
()).
toBe
(
updatedTitle
);
});
});
describe
(
"Editing an xblock"
,
function
()
{
var
newDisplayName
=
'New Display Name'
;
...
...
@@ -87,10 +149,10 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
it
(
'can show an edit modal for a child xblock'
,
function
()
{
var
editButtons
;
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
editButtons
=
containerPage
.
$
(
'.edit-button'
);
// The container
renders six mock xblocks, so there should be an equal number of edit button
s
editButtons
=
containerPage
.
$
(
'.
wrapper-xblock .
edit-button'
);
// The container
should have rendered six mock xblock
s
expect
(
editButtons
.
length
).
toBe
(
6
);
editButtons
.
first
()
.
click
();
editButtons
[
0
]
.
click
();
// Make sure that the correct xblock is requested to be edited
expect
(
lastRequest
().
url
).
toBe
(
'/xblock/locator-component-A1/studio_view'
...
...
@@ -125,10 +187,10 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
var
editButtons
,
modal
,
mockUpdatedXBlockHtml
;
mockUpdatedXBlockHtml
=
readFixtures
(
'mock/mock-updated-xblock.underscore'
);
renderContainerPage
(
mockContainerXBlockHtml
,
this
);
editButtons
=
containerPage
.
$
(
'.edit-button'
);
// The container
renders six mock xblocks, so there should be an equal number of edit button
s
editButtons
=
containerPage
.
$
(
'.
wrapper-xblock .
edit-button'
);
// The container
should have rendered six mock xblock
s
expect
(
editButtons
.
length
).
toBe
(
6
);
editButtons
.
first
()
.
click
();
editButtons
[
0
]
.
click
();
create_sinon
.
respondWithJson
(
requests
,
{
html
:
mockXModuleEditor
,
resources
:
[]
...
...
cms/static/js/spec/views/unit_spec.js
View file @
138cd459
define
([
"jquery"
,
"underscore"
,
"jasmine"
,
"coffee/src/views/unit"
,
"js/models/module_info"
,
"js/spec_helpers/create_sinon"
,
"js/spec_helpers/edit_helpers"
,
"jasmine-stealth"
],
function
(
$
,
_
,
jasmine
,
UnitEditView
,
ModuleModel
,
create_sinon
,
edit_helpers
)
{
var
requests
,
unitView
,
initialize
,
respondWithHtml
,
verifyComponents
,
i
;
var
requests
,
unitView
,
initialize
,
lastRequest
,
respondWithHtml
,
verifyComponents
,
i
,
mockXBlockEditorHtml
=
readFixtures
(
'mock/mock-xblock-editor.underscore'
);
respondWithHtml
=
function
(
html
,
requestIndex
)
{
create_sinon
.
respondWithJson
(
...
...
@@ -13,6 +14,7 @@ define(["jquery", "underscore", "jasmine", "coffee/src/views/unit", "js/models/m
initialize
=
function
(
test
)
{
var
mockXBlockHtml
=
readFixtures
(
'mock/mock-unit-page-xblock.underscore'
),
mockChildContainerHtml
=
readFixtures
(
'mock/mock-unit-page-child-container.underscore'
),
model
;
requests
=
create_sinon
.
requests
(
test
);
model
=
new
ModuleModel
({
...
...
@@ -25,11 +27,13 @@ define(["jquery", "underscore", "jasmine", "coffee/src/views/unit", "js/models/m
model
:
model
});
// Respond with renderings for the two xblocks in the unit
// Respond with renderings for the two xblocks in the unit
(the second is itself a child container)
respondWithHtml
(
mockXBlockHtml
,
0
);
respondWithHtml
(
mock
XBlock
Html
,
1
);
respondWithHtml
(
mock
ChildContainer
Html
,
1
);
};
lastRequest
=
function
()
{
return
requests
[
requests
.
length
-
1
];
};
verifyComponents
=
function
(
unit
,
locators
)
{
var
components
=
unit
.
$
(
".component"
);
expect
(
components
.
length
).
toBe
(
locators
.
length
);
...
...
@@ -174,5 +178,93 @@ define(["jquery", "underscore", "jasmine", "coffee/src/views/unit", "js/models/m
test_link_disabled_during_ajax_call
(
draft_states
[
i
]);
}
});
describe
(
"Editing an xblock"
,
function
()
{
var
newDisplayName
=
'New Display Name'
;
beforeEach
(
function
()
{
edit_helpers
.
installMockXBlock
({
data
:
"<p>Some HTML</p>"
,
metadata
:
{
display_name
:
newDisplayName
}
});
});
afterEach
(
function
()
{
edit_helpers
.
uninstallMockXBlock
();
edit_helpers
.
cancelModalIfShowing
();
});
it
(
'can show an edit modal for a child xblock'
,
function
()
{
var
editButtons
;
initialize
(
this
);
editButtons
=
unitView
.
$
(
'.edit-button'
);
// The container renders two mock xblocks
expect
(
editButtons
.
length
).
toBe
(
2
);
editButtons
[
1
].
click
();
// Make sure that the correct xblock is requested to be edited
expect
(
lastRequest
().
url
).
toBe
(
'/xblock/loc_2/studio_view'
);
create_sinon
.
respondWithJson
(
requests
,
{
html
:
mockXBlockEditorHtml
,
resources
:
[]
});
expect
(
edit_helpers
.
isShowingModal
()).
toBeTruthy
();
});
});
describe
(
"Editing an xmodule"
,
function
()
{
var
mockXModuleEditor
=
readFixtures
(
'mock/mock-xmodule-editor.underscore'
),
newDisplayName
=
'New Display Name'
;
beforeEach
(
function
()
{
edit_helpers
.
installMockXModule
({
data
:
"<p>Some HTML</p>"
,
metadata
:
{
display_name
:
newDisplayName
}
});
});
afterEach
(
function
()
{
edit_helpers
.
uninstallMockXModule
();
edit_helpers
.
cancelModalIfShowing
();
});
it
(
'can save changes to settings'
,
function
()
{
var
editButtons
,
modal
,
mockUpdatedXBlockHtml
;
mockUpdatedXBlockHtml
=
readFixtures
(
'mock/mock-updated-xblock.underscore'
);
initialize
(
this
);
editButtons
=
unitView
.
$
(
'.edit-button'
);
// The container renders two mock xblocks
expect
(
editButtons
.
length
).
toBe
(
2
);
editButtons
[
1
].
click
();
create_sinon
.
respondWithJson
(
requests
,
{
html
:
mockXModuleEditor
,
resources
:
[]
});
modal
=
$
(
'.edit-xblock-modal'
);
// 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
create_sinon
.
respondWithJson
(
requests
,
{
id
:
'mock-id'
});
// Respond to the request to refresh
respondWithHtml
(
mockUpdatedXBlockHtml
);
// Verify that the xblock was updated
expect
(
unitView
.
$
(
'.mock-updated-content'
).
text
()).
toBe
(
'Mock Update'
);
});
});
});
});
cms/static/js/spec_helpers/edit_helpers.js
View file @
138cd459
...
...
@@ -9,11 +9,17 @@ define(["jquery", "underscore", "js/spec_helpers/create_sinon", "js/spec_helpers
var
installMockXBlock
,
uninstallMockXBlock
,
installMockXModule
,
uninstallMockXModule
,
mockComponentTemplates
,
installEditTemplates
,
showEditModal
,
verifyXBlockRequest
;
installMockXBlock
=
function
()
{
installMockXBlock
=
function
(
mockResult
)
{
window
.
MockXBlock
=
function
(
runtime
,
element
)
{
return
{
var
block
=
{
runtime
:
runtime
};
if
(
mockResult
)
{
block
.
save
=
function
()
{
return
mockResult
;
};
}
return
block
;
};
};
...
...
cms/static/js/spec_helpers/modal_helpers.js
View file @
138cd459
...
...
@@ -7,6 +7,7 @@ define(["jquery", "js/spec_helpers/view_helpers"],
getModalElement
,
isShowingModal
,
hideModalIfShowing
,
pressModalButton
,
cancelModal
,
cancelModalIfShowing
;
...
...
@@ -37,12 +38,16 @@ define(["jquery", "js/spec_helpers/view_helpers"],
}
};
cancelModal
=
function
(
modal
)
{
var
modalElement
,
cancelB
utton
;
pressModalButton
=
function
(
selector
,
modal
)
{
var
modalElement
,
b
utton
;
modalElement
=
getModalElement
(
modal
);
cancelButton
=
modalElement
.
find
(
'.action-cancel:visible'
);
expect
(
cancelButton
.
length
).
toBe
(
1
);
cancelButton
.
click
();
button
=
modalElement
.
find
(
selector
+
':visible'
);
expect
(
button
.
length
).
toBe
(
1
);
button
.
click
();
};
cancelModal
=
function
(
modal
)
{
pressModalButton
(
'.action-cancel'
,
modal
);
};
cancelModalIfShowing
=
function
(
modal
)
{
...
...
@@ -52,9 +57,11 @@ define(["jquery", "js/spec_helpers/view_helpers"],
};
return
$
.
extend
(
view_helpers
,
{
'getModalElement'
:
getModalElement
,
'installModalTemplates'
:
installModalTemplates
,
'isShowingModal'
:
isShowingModal
,
'hideModalIfShowing'
:
hideModalIfShowing
,
'pressModalButton'
:
pressModalButton
,
'cancelModal'
:
cancelModal
,
'cancelModalIfShowing'
:
cancelModalIfShowing
});
...
...
cms/static/js/views/container.js
View file @
138cd459
define
([
"jquery"
,
"underscore"
,
"js/views/xblock"
,
"js/utils/module"
,
"gettext"
,
"js/views/feedback_notification"
],
function
(
$
,
_
,
XBlockView
,
ModuleUtils
,
gettext
,
NotificationView
)
{
var
reorderableClass
=
'.reorderable-container'
,
sortableInitializedClass
=
'.ui-sortable'
,
studioXBlockWrapperClass
=
'.studio-xblock-wrapper'
;
var
ContainerView
=
XBlockView
.
extend
({
...
...
@@ -8,7 +9,7 @@ define(["jquery", "underscore", "js/views/xblock", "js/utils/module", "gettext",
xblockReady
:
function
()
{
XBlockView
.
prototype
.
xblockReady
.
call
(
this
);
var
reorderableContainer
=
this
.
$
(
reorderableClass
),
alreadySortable
=
this
.
$
(
'.ui-sortable'
),
alreadySortable
=
this
.
$
(
sortableInitializedClass
),
newParent
,
oldParent
,
self
=
this
;
...
...
@@ -113,7 +114,7 @@ define(["jquery", "underscore", "js/views/xblock", "js/utils/module", "gettext",
},
refresh
:
function
()
{
this
.
$
(
reorderable
Class
).
sortable
(
'refresh'
);
this
.
$
(
sortableInitialized
Class
).
sortable
(
'refresh'
);
}
});
...
...
cms/static/js/views/modals/edit_xblock.js
View file @
138cd459
...
...
@@ -111,13 +111,9 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal",
},
getTitle
:
function
()
{
var
displayName
=
this
.
xblockElement
.
find
(
'.xblock-header .header-details'
).
text
().
trim
();
// If not found, try the old unit page style rendering
var
displayName
=
this
.
xblockInfo
.
get
(
'display_name'
);
if
(
!
displayName
)
{
displayName
=
this
.
xblockElement
.
find
(
'.component-header'
).
text
().
trim
();
if
(
!
displayName
)
{
displayName
=
gettext
(
'Component'
);
}
displayName
=
gettext
(
'Component'
);
}
return
interpolate
(
gettext
(
"Editing: %(title)s"
),
{
title
:
displayName
},
true
);
},
...
...
@@ -180,13 +176,16 @@ define(["jquery", "underscore", "gettext", "js/views/modals/base_modal",
findXBlockInfo
:
function
(
xblockWrapperElement
,
defaultXBlockInfo
)
{
var
xblockInfo
=
defaultXBlockInfo
,
xblockElement
;
xblockElement
,
displayName
;
if
(
xblockWrapperElement
.
length
>
0
)
{
xblockElement
=
xblockWrapperElement
.
find
(
'.xblock'
);
displayName
=
xblockWrapperElement
.
find
(
'.xblock-header .header-details'
).
text
().
trim
();
xblockInfo
=
new
XBlockInfo
({
id
:
xblockWrapperElement
.
data
(
'locator'
),
courseKey
:
xblockWrapperElement
.
data
(
'course-key'
),
category
:
xblockElement
.
data
(
'block-type'
)
category
:
xblockElement
.
data
(
'block-type'
),
display_name
:
displayName
});
}
return
xblockInfo
;
...
...
cms/static/js/views/pages/container.js
View file @
138cd459
...
...
@@ -46,6 +46,7 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification",
}
else
{
noContentElement
.
removeClass
(
'is-hidden'
);
}
self
.
refreshTitle
();
loadingElement
.
addClass
(
'is-hidden'
);
self
.
delegateEvents
();
}
...
...
@@ -60,6 +61,12 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification",
return
this
.
xblockView
.
model
.
urlRoot
;
},
refreshTitle
:
function
()
{
var
title
=
this
.
$
(
'.xblock-header .header-details span'
).
first
().
text
().
trim
();
this
.
$
(
'.page-header-title'
).
text
(
title
);
this
.
$
(
'.page-header .subtitle a'
).
last
().
text
(
title
);
},
onXBlockRefresh
:
function
(
xblockView
)
{
this
.
addButtonActions
(
xblockView
.
$el
);
this
.
xblockView
.
refresh
();
...
...
@@ -177,10 +184,9 @@ define(["jquery", "underscore", "gettext", "js/views/feedback_notification",
*/
refreshXBlock
:
function
(
xblockElement
)
{
var
parentElement
=
xblockElement
.
parent
(),
rootLocator
=
this
.
xblockView
.
model
.
id
,
xblockLocator
=
xblockElement
.
data
(
'locator'
);
if
(
xblockLocator
===
rootLocator
)
{
this
.
render
();
rootLocator
=
this
.
xblockView
.
model
.
id
;
if
(
xblockElement
.
length
===
0
||
xblockElement
.
data
(
'locator'
)
===
rootLocator
)
{
this
.
render
({
});
}
else
if
(
parentElement
.
hasClass
(
'reorderable-container'
))
{
this
.
refreshChildXBlock
(
xblockElement
);
}
else
{
...
...
cms/static/sass/_base.scss
View file @
138cd459
...
...
@@ -334,6 +334,7 @@ p, ul, ol, dl {
.navigation-link
{
@extend
%cont-truncated
;
display
:
inline-block
;
vertical-align
:
bottom
;
// correct for extra padding in FF
max-width
:
250px
;
&
.navigation-current
{
...
...
cms/static/sass/elements/_controls.scss
View file @
138cd459
...
...
@@ -230,6 +230,7 @@
vertical-align
:
middle
;
.action-button
{
@include
transition
(
all
$tmg-f3
linear
0s
);
display
:
block
;
border-radius
:
3px
;
padding
:
(
$baseline
/
4
)
(
$baseline
/
2
);
...
...
cms/static/sass/elements/_xblocks.scss
View file @
138cd459
...
...
@@ -15,11 +15,8 @@
}
// UI: xblock header
.xblock-header
{
.xblock-header
-primary
{
@include
box-sizing
(
border-box
);
@include
ui-flexbox
();
@extend
%ui-align-center-flex
;
justify-content
:
space-between
;
border-bottom
:
1px
solid
$gray-l4
;
border-radius
:
(
$baseline
/
5
)
(
$baseline
/
5
)
0
0
;
min-height
:
(
$baseline
*
2
.5
);
...
...
@@ -28,17 +25,73 @@
.header-details
{
@extend
%cont-truncated
;
@extend
%ui-justify-left-flex
;
@include
ui-flexbox
();
width
:
flex-grid
(
6
,
12
);
display
:
inline-block
;
width
:
50%
;
vertical-align
:
middle
;
}
.header-actions
{
@include
ui-flexbox
();
@extend
%ui-justify-right-flex
;
width
:
flex-grid
(
6
,
12
);
display
:
inline-block
;
width
:
49%
;
vertical-align
:
middle
;
text-align
:
right
;
}
}
.xblock-header-secondary
{
overflow
:
hidden
;
border-top
:
1px
solid
$gray-l3
;
background-color
:
$gray-l5
;
padding
:
(
$baseline
/
2
)
$baseline
;
.meta-info
{
display
:
inline-block
;
vertical-align
:
middle
;
width
:
65%
;
font-style
:
italic
;
color
:
$gray
;
}
.actions-list
{
width
:
34%
;
display
:
inline-block
;
vertical-align
:
middle
;
text-align
:
right
;
.action-item
{
display
:
inline-block
;
.action-button
{
@include
transition
(
all
$tmg-f3
linear
0s
);
display
:
block
;
width
:
auto
;
height
:
(
$baseline
*
1
.5
);
border-radius
:
3px
;
padding
:
3px
(
$baseline
/
2
)
0
(
$baseline
/
2
);
color
:
$gray-l1
;
&
:hover
{
background-color
:
$blue
;
color
:
$gray-l6
;
}
.action-button-text
{
display
:
inline-block
;
vertical-align
:
middle
;
padding
:
0
1px
;
text-transform
:
uppercase
;
}
&
.delete-button
:hover
{
background-color
:
$gray-l1
;
}
}
[
class
^=
"icon-"
]
{
display
:
inline-block
;
vertical-align
:
middle
;
}
}
}
}
}
...
...
cms/static/sass/views/_container.scss
View file @
138cd459
...
...
@@ -12,13 +12,42 @@
.mast
{
border-bottom
:
none
;
padding-bottom
:
0
;
.page-header
{
@extend
%t-title
;
@include
font-size
(
28
);
@include
line-height
(
32
);
.subtitle
.navigation-link
{
color
:
$gray
;
&
:hover
{
color
:
$blue
;
}
}
}
}
.wrapper-mast
.mast.has-navigation
.nav-actions
{
bottom
:
-
(
$baseline
*.
75
);
.nav-item
.button
{
.nav-item
{
.edit-button
{
@include
blue-button
;
@extend
%t-action4
;
padding
:
(
$baseline
/
4
)
(
$baseline
/
2
);
text-align
:
center
;
.action-button-text
{
display
:
inline-block
;
vertical-align
:
middle
;
}
[
class
^=
"icon-"
]
{
display
:
inline-block
;
vertical-align
:
middle
;
}
}
}
}
...
...
@@ -140,6 +169,10 @@ body.view-container .content-primary {
}
.xblock-header
{
display
:
block
;
}
.xblock-header-primary
{
@include
ui-flexbox
();
margin-bottom
:
0
;
border-bottom
:
none
;
...
...
@@ -168,6 +201,10 @@ body.view-container .content-primary {
}
.xblock-header
{
display
:
block
;
}
.xblock-header-primary
{
display
:
flex
;
margin-bottom
:
0
;
border-bottom
:
1px
solid
$gray-l4
;
...
...
@@ -183,7 +220,7 @@ body.view-container .content-primary {
// STATE: xBlock containers styled as rows.
&
.xblock-type-container
{
.xblock-header
{
.xblock-header
-primary
{
margin-bottom
:
0
;
border-bottom
:
0
;
border-radius
:
(
$baseline
/
5
);
...
...
cms/static/sass/views/_unit.scss
View file @
138cd459
...
...
@@ -750,13 +750,15 @@ body.unit {
body
.unit
{
.component-actions
{
.component-actions
,
.xblock-header-secondary
.actions-list
{
.action-item
{
display
:
inline-block
;
margin
:
(
$baseline
/
4
)
0
(
$baseline
/
4
)
(
$baseline
/
4
);
.action-button
{
@include
transition
(
all
$tmg-f3
linear
0s
);
display
:
block
;
padding
:
0
$baseline
/
2
;
width
:
auto
;
...
...
@@ -770,10 +772,10 @@ body.unit {
}
.action-button-text
{
padding-left
:
1px
;
vertical-align
:
bottom
;
display
:
inline-block
;
vertical-align
:
middle
;
padding
:
0
1px
;
text-transform
:
uppercase
;
line-height
:
17px
;
}
&
.delete-button
:hover
{
...
...
@@ -783,7 +785,7 @@ body.unit {
[
class
^=
"icon-"
]
{
display
:
inline-block
;
vertical-align
:
bottom
;
vertical-align
:
middle
;
}
}
}
...
...
@@ -1128,7 +1130,7 @@ body.unit .xblock-type-container {
}
}
.xblock-header
{
.xblock-header
-primary
{
border-bottom
:
0
;
border-radius
:
(
$baseline
/
5
);
...
...
@@ -1178,6 +1180,44 @@ body.unit .component.editing {
}
}
// UI: special case xblock with no preview, ex. experiment blocks
body
.unit
.component
{
.wrapper-component-action-header.nopreview
{
position
:
relative
;
border-bottom
:
0
;
}
.xblock-header-secondary
{
overflow
:
hidden
;
border-top
:
1px
solid
$gray-l3
;
background-color
:
$gray-l5
;
padding
:
(
$baseline
/
2
);
.meta-info
{
display
:
inline-block
;
vertical-align
:
middle
;
width
:
65%
;
padding-left
:
(
$baseline
/
4
);
font-style
:
italic
;
color
:
$gray
;
}
.actions-list
{
display
:
inline-block
;
vertical-align
:
middle
;
width
:
33%
;
text-align
:
right
;
.action-item
{
margin
:
0
;
}
}
}
}
body
.view-unit
.main-column
.unit-body
,
body
.view-container
{
...
...
cms/templates/container.html
View file @
138cd459
...
...
@@ -54,9 +54,9 @@ main_xblock_info = {
<div
class=
"wrapper-mast wrapper"
data-location=
""
data-display-name=
""
data-category=
""
>
<header
class=
"mast has-actions has-navigation"
>
<header
class=
"mast has-actions has-navigation
has-subtitle
"
>
<h1
class=
"page-header"
>
<small
class=
"navigation navigation-parents"
>
<small
class=
"navigation navigation-parents
subtitle
"
>
% for ancestor in ancestor_xblocks:
<
%
ancestor_url =
xblock_studio_url(ancestor)
...
...
@@ -68,11 +68,20 @@ main_xblock_info = {
% endfor
<a
href=
"#"
class=
"navigation-link navigation-current"
>
${xblock.display_name_with_default | h}
</a>
</small>
<span
class=
"page-header-title"
>
${xblock.display_name_with_default | h}
</span>
</h1>
<nav
class=
"nav-actions"
>
<h3
class=
"sr"
>
${_("Page Actions")}
</h3>
<ul>
% if not unit_publish_state == 'public':
<li
class=
"action-item action-edit nav-item"
>
<a
href=
"#"
class=
"button edit-button action-button"
>
<i
class=
"icon-pencil"
></i>
<span
class=
"action-button-text"
>
${_("Edit")}
</span>
</a>
</li>
% endif
</ul>
</nav>
</header>
...
...
cms/templates/container_xblock_component.html
View file @
138cd459
...
...
@@ -4,24 +4,41 @@ from contentstore.views.helpers import xblock_studio_url
%
>
<
%
namespace
name=
'static'
file=
'static_content.html'
/>
<section
class=
"wrapper-xblock xblock-type-container level-element"
data-locator=
"${xblock.location}"
data-course-key=
"${xblock.location.course_key}"
>
<header
class=
"xblock-header"
>
<div
class=
"header-details"
>
${xblock.display_name_with_default}
</div>
<div
class=
"header-actions"
>
<ul
class=
"actions-list"
>
<li
class=
"action-item action-view"
>
<a
href=
"${xblock_studio_url(xblock)}"
class=
"action-button"
>
## Translators: this is a verb describing the action of viewing more details
<span
class=
"action-button-text"
>
${_('View')}
</span>
<i
class=
"icon-arrow-right"
></i>
</a>
</li>
<li
class=
"action-item action-drag"
>
<span
data-tooltip=
"${_('Drag to reorder')}"
class=
"drag-handle action"
></span>
</li>
</ul>
</div>
</header>
<section
class=
"wrapper wrapper-xblock wrapper-component-action-header nopreview"
data-locator=
"${locator}"
data-course-key=
"${xblock.location.course_key}"
>
<div
class=
"component-header"
>
${xblock.display_name_with_default}
</div>
<ul
class=
"component-actions"
>
<li
class=
"action-item action-edit"
>
<a
href=
"#"
class=
"edit-button action-button"
>
<i
class=
"icon-pencil"
></i>
<span
class=
"action-button-text"
>
${_("Edit")}
</span>
</a>
</li>
<li
class=
"action-item action-duplicate"
>
<a
href=
"#"
data-tooltip=
"${_("
Duplicate
")}"
class=
"duplicate-button action-button"
>
<i
class=
"icon-copy"
></i>
<span
class=
"sr"
>
${_("Duplicate")}
</span>
</a>
</li>
<li
class=
"action-item action-delete"
>
<a
href=
"#"
data-tooltip=
"${_("
Delete
")}"
class=
"delete-button action-button"
>
<i
class=
"icon-trash"
></i>
<span
class=
"sr"
>
${_("Delete")}
</span>
</a>
</li>
</ul>
</section>
<div
class=
"xblock-header-secondary"
>
<div
class=
"meta-info"
>
${_('This block contains multiple components.')}
</div>
<ul
class=
"actions-list"
>
<li
class=
"action-item action-view"
>
<a
href=
"${xblock_studio_url(xblock)}"
class=
"action-button"
>
## Translators: this is a verb describing the action of viewing more details
<span
class=
"action-button-text"
>
${_('View')}
</span>
<i
class=
"icon-arrow-right"
></i>
</a>
</li>
</ul>
</div>
<span
data-tooltip=
"${_('Drag to reorder')}"
class=
"drag-handle action"
></span>
cms/templates/js/mock/mock-container-page.underscore
View file @
138cd459
...
...
@@ -3,15 +3,22 @@
<div class="wrapper-mast wrapper" data-location="" data-display-name="" data-category="">
<header class="mast has-actions has-navigation">
<h1 class="page-header">
<small class="navigation navigation-parents">
<small class="navigation navigation-parents
subtitle
">
<a href="/unit/TestCourse/branch/draft/block/vertical8eb" class="navigation-link navigation-parent">Unit 1</a>
<a href="#" class="navigation-link navigation-current">
Nested Vertical Test
</a>
<a href="#" class="navigation-link navigation-current">
Test Container
</a>
</small>
<span class="page-header-title">Test Container</span>
</h1>
<nav class="nav-actions">
<h3 class="sr">Page Actions</h3>
<ul>
<li class="action-item action-edit nav-item">
<a href="#" class="button edit-button action-button">
<i class="icon-pencil"></i>
<span class="action-button-text">${_("Edit")}</span>
</a>
</li>
</ul>
</nav>
</header>
...
...
@@ -22,7 +29,7 @@
<section class="content-area">
<article class="content-primary window">
<section class="wrapper-xblock level-page studio-xblock-wrapper" data-locator="
TestCourse/branch/draft/block/vertical131
">
<section class="wrapper-xblock level-page studio-xblock-wrapper" data-locator="
locator-container
">
</section>
<div class="no-container-content is-hidden">
<p>This page has no content yet.</p>
...
...
cms/templates/js/mock/mock-container-xblock.underscore
View file @
138cd459
<header class="xblock-header"></header>
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-details">
<span>Test Container</span>
</div>
<div class="header-actions">
<ul class="actions-list">
</ul>
</div>
</div>
</header>
<article class="xblock-render">
<div class="xblock" data-block-type="vertical" data-locator="locator-container">
<div class="xblock" data-block-type="vertical" data-locator="locator-container"
data-init="MockXBlock" data-runtime-class="StudioRuntime" data-runtime-version="1">
<ol class="reorderable-container">
<li class="studio-xblock-wrapper is-draggable" data-locator="testCourse/branch/draft/split_test/splitFFF">
<div class="xblock" data-block-type="vertical">
<ol class="reorderable-container">
<li class="studio-xblock-wrapper is-draggable" data-locator="locator-group-A">
<section class="wrapper-xblock level-nesting" data-locator="locator-group-A">
<header class="xblock-header">
<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>
</header>
<li class="studio-xblock-wrapper is-draggable" data-locator="locator-group-A">
<section class="wrapper-xblock level-nesting" data-locator="locator-group-A">
<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>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-block-type="vertical">
<ol class="reorderable-container">
<li class="studio-xblock-wrapper is-draggable" data-locator="locator-component-A1">
<section class="wrapper-xblock level-element"
data-locator="locator-component-A1">
<header class="xblock-header">
<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>
</header>
<article class="xblock-render"></article>
</section>
</li>
<li class="studio-xblock-wrapper is-draggable" data-locator="locator-component-A2">
<section class="wrapper-xblock level-element"
data-locator="locator-component-A2">
<article class="xblock-render">
<div class="xblock" data-block-type="vertical">
<ol class="reorderable-container">
<li class="studio-xblock-wrapper is-draggable" data-locator="locator-component-A1">
<section class="wrapper-xblock level-element"
data-locator="locator-component-A1">
<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>
</li>
<li class="studio-xblock-wrapper is-draggable" data-locator="locator-component-A2">
<section class="wrapper-xblock level-element"
data-locator="locator-component-A2">
<header class="xblock-header">
<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>
</header>
<article class="xblock-render"></article>
</section>
</li>
<li class="studio-xblock-wrapper is-draggable" data-locator="locator-component-A3">
<section class="wrapper-xblock level-element"
data-locator="locator-component-A3">
<header class="xblock-header">
<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>
</header>
<article class="xblock-render"></article>
</section>
</li>
</ol>
<div class="add-xblock-component new-component-item adding"></div>
</div>
</article>
</section>
</li>
<li class="studio-xblock-wrapper is-draggable" data-locator="locator-group-B">
<section class="wrapper-xblock level-nesting" data-locator="locator-group-B">
<header class="xblock-header">
<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>
</header>
<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>
</li>
<li class="studio-xblock-wrapper is-draggable" data-locator="locator-component-A3">
<section class="wrapper-xblock level-element"
data-locator="locator-component-A3">
<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>
</li>
</ol>
<div class="add-xblock-component new-component-item adding"></div>
</div>
</article>
</section>
</li>
<li class="studio-xblock-wrapper is-draggable" data-locator="locator-group-B">
<section class="wrapper-xblock level-nesting" data-locator="locator-group-B">
<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>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-block-type="vertical">
<ol class="reorderable-container">
<li class="studio-xblock-wrapper is-draggable" data-locator="locator-component-B1">
<section class="wrapper-xblock level-element"
data-locator="locator-component-B1">
<article class="xblock-render">
<div class="xblock" data-block-type="vertical">
<ol class="reorderable-container">
<li class="studio-xblock-wrapper is-draggable" data-locator="locator-component-B1">
<section class="wrapper-xblock level-element"
data-locator="locator-component-B1">
<header class="xblock-header">
<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>
</header>
<article class="xblock-render"></article>
</section>
</li>
<li class="studio-xblock-wrapper is-draggable" data-locator="locator-component-B2">
<section class="wrapper-xblock level-element"
data-locator="locator-component-B2">
<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>
</li>
<li class="studio-xblock-wrapper is-draggable" data-locator="locator-component-B2">
<section class="wrapper-xblock level-element"
data-locator="locator-component-B2">
<header class="xblock-header">
<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>
</header>
<article class="xblock-render"></article>
</section>
</li>
<li class="studio-xblock-wrapper is-draggable" data-locator="locator-component-B3">
<section class="wrapper-xblock level-element"
data-locator="locator-component-B3">
<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>
</li>
<li class="studio-xblock-wrapper is-draggable" data-locator="locator-component-B3">
<section class="wrapper-xblock level-element"
data-locator="locator-component-B3">
<header class="xblock-header">
<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>
</header>
<article class="xblock-render"></article>
</section>
</li>
</ol>
<div class="add-xblock-component new-component-item adding"></div>
</div>
</article>
</section>
</li>
</ol>
</div>
<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>
</li>
</ol>
<div class="add-xblock-component new-component-item adding"></div>
</div>
</article>
</section>
</li>
</ol>
</div>
...
...
cms/templates/js/mock/mock-unit-page-child-container.underscore
0 → 100644
View file @
138cd459
<section class="wrapper wrapper-xblock wrapper-component-action-header nopreview" data-locator="locator-child-container">
<div class="component-header">
Test Child Container
</div>
<ul class="component-actions">
<li class="action-item action-edit">
<a href="#" class="edit-button action-button">
<i class="icon-pencil"></i>
<span class="action-button-text">Edit</span>
</a>
</li>
<li class="action-item action-duplicate">
<a href="#" data-tooltip="Duplicate" class="duplicate-button action-button">
<i class="icon-copy"></i>
<span class="sr">Duplicate</span>
</a>
</li>
<li class="action-item action-delete">
<a href="#" data-tooltip="Delete" class="delete-button action-button">
<i class="icon-trash"></i>
<span class="sr">Delete</span>
</a>
</li>
</ul>
</section>
<div class="xblock-header-secondary">
<div class="meta-info">This block contains multiple components.</div>
<ul class="actions-list">
<li class="action-item action-view">
<a href="/container/locator-child-container" class="action-button">
<span class="action-button-text">View</span>
<i class="icon-arrow-right"></i>
</a>
</li>
</ul>
</div>
<span data-tooltip="Drag to reorder" class="drag-handle action"></span>
cms/templates/js/mock/mock-updated-container-xblock.underscore
0 → 100644
View file @
138cd459
<header class="xblock-header">
<div class="xblock-header-primary">
<div class="header-details">
<span>Updated Test Container</span>
</div>
<div class="header-actions">
<ul class="actions-list">
</ul>
</div>
</div>
</header>
<article class="xblock-render">
<div class="xblock" data-block-type="vertical" data-locator="locator-container"
data-init="MockXBlock" data-runtime-class="StudioRuntime" data-runtime-version="1">
<ol class="reorderable-container">
</ol>
</div>
</article>
cms/templates/studio_container_wrapper.html
deleted
100644 → 0
View file @
172c6d15
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
from
contentstore
.
views
.
helpers
import
xblock_studio_url
%
>
<
%
namespace
name=
'static'
file=
'static_content.html'
/>
% if is_reorderable:
<li
class=
"studio-xblock-wrapper is-draggable"
data-locator=
"${xblock.location}"
>
% else:
<div
class=
"studio-xblock-wrapper"
>
% endif
<section
class=
"wrapper-xblock xblock-type-container level-element"
data-locator=
"${xblock.location}"
>
<header
class=
"xblock-header"
>
<div
class=
"header-details"
>
${xblock.display_name_with_default}
</div>
<div
class=
"header-actions"
>
<ul
class=
"actions-list"
>
<li
class=
"action-item action-view"
>
<a
href=
"${xblock_studio_url(xblock)}"
class=
"action-button"
>
## Translators: this is a verb describing the action of viewing more details
<span
class=
"action-button-text"
>
${_('View')}
</span>
<i
class=
"icon-arrow-right"
></i>
</a>
</li>
% if not xblock_context['read_only'] and is_reorderable:
<li
class=
"action-item action-drag"
>
<span
data-tooltip=
"${_('Drag to reorder')}"
class=
"drag-handle action"
></span>
</li>
% endif
</ul>
</div>
</header>
</section>
% if is_reorderable:
</li>
% else:
</div>
% endif
cms/templates/studio_xblock_wrapper.html
View file @
138cd459
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
%
>
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
from
contentstore
.
views
.
helpers
import
xblock_studio_url
%
>
<
%
xblock_url =
xblock_studio_url(xblock)
show_inline =
xblock.has_children
and
not
xblock_url
section_class =
"level-nesting"
if
show_inline
else
"
level-element
"
collapsible_class =
"is-collapsible"
if
xblock
.
has_children
else
""
%
>
% if not is_root:
% if is_reorderable:
...
...
@@ -7,16 +17,13 @@
<div
class=
"studio-xblock-wrapper"
data-locator=
"${xblock.location}"
>
% endif
<
%
section_class =
"level-nesting"
if
xblock
.
has_children
else
"
level-element
"
collapsible_class =
"is-collapsible"
if
xblock
.
has_children
else
""
%
>
<section
class=
"wrapper-xblock ${section_class} ${collapsible_class}"
data-course-key=
"${xblock.location.course_key}"
>
% endif
<header
class=
"xblock-header"
>
<header
class=
"xblock-header"
>
<div
class=
"xblock-header-primary"
>
<div
class=
"header-details"
>
% if
xblock.has_children
:
% if
show_inline
:
<a
href=
"#"
data-tooltip=
"${_('Expand or Collapse')}"
class=
"action expand-collapse collapse"
>
<i
class=
"icon-caret-down ui-toggle-expansion"
></i>
<span
class=
"sr"
>
${_('Expand or Collapse')}
</span>
...
...
@@ -26,39 +33,56 @@
</div>
<div
class=
"header-actions"
>
<ul
class=
"actions-list"
>
% if not xblock_context['read_only']:
% if not
xblock.has_children
:
<li
class=
"action-item action-edit"
>
<a
href=
"#"
class=
"edit-button action-button"
>
<i
class=
"icon-pencil"
></i>
<span
class=
"action-button-text"
>
${_("Edit")}
</span>
</a>
</li>
<li
class=
"action-item action-duplicate"
>
<a
href=
"#"
data-tooltip=
"${_("
Duplicate
")}"
class=
"duplicate-button action-button"
>
% if not xblock_context['read_only']
and not is_root
:
% if not
show_inline
:
<li
class=
"action-item action-edit"
>
<a
href=
"#"
class=
"edit-button action-button"
>
<i
class=
"icon-pencil"
></i>
<span
class=
"action-button-text"
>
${_("Edit")}
</span>
</a>
</li>
<li
class=
"action-item action-duplicate"
>
<a
href=
"#"
data-tooltip=
"${_("
Duplicate
")}"
class=
"duplicate-button action-button"
>
<i
class=
"icon-copy"
></i>
<span
class=
"sr"
>
${_("Duplicate")}
</span>
</a>
</li>
<li
class=
"action-item action-delete"
>
<a
href=
"#"
data-tooltip=
"${_("
Delete
")}"
class=
"delete-button action-button"
>
</a>
</li>
<li
class=
"action-item action-delete"
>
<a
href=
"#"
data-tooltip=
"${_("
Delete
")}"
class=
"delete-button action-button"
>
<i
class=
"icon-trash"
></i>
<span
class=
"sr"
>
${_("Delete")}
</span>
</a>
</li>
</a>
</li>
% endif
% if
not is_root and
is_reorderable:
<li
class=
"action-item action-drag"
>
<span
data-tooltip=
"${_('Drag to reorder')}"
class=
"drag-handle action"
></span>
</li>
% if is_reorderable:
<li
class=
"action-item action-drag"
>
<span
data-tooltip=
"${_('Drag to reorder')}"
class=
"drag-handle action"
></span>
</li>
% endif
% endif
</ul>
</div>
</header>
</div>
% if xblock_url and not is_root:
<div
class=
"xblock-header-secondary"
>
<div
class=
"meta-info"
>
${_('This block contains multiple components.')}
</div>
<ul
class=
"actions-list"
>
<li
class=
"action-item action-view"
>
<a
href=
"${xblock_url}"
class=
"action-button"
>
## Translators: this is a verb describing the action of viewing more details
<span
class=
"action-button-text"
>
${_('View')}
</span>
<i
class=
"icon-arrow-right"
></i>
</a>
</li>
</ul>
</div>
% endif
</header>
% if is_root or not xblock_url:
<article
class=
"xblock-render"
>
${content}
</article>
% endif
% if not is_root:
</section>
...
...
cms/templates/widgets/sequence-edit.html
View file @
138cd459
<section
class=
"sequence-edit"
>
<section
class=
"filters wip"
>
<ul>
<li>
<h2>
Sort:
</h2>
<select>
<option
value=
""
>
Linear Order
</option>
<option
value=
""
>
Recently Modified
</option>
<option
value=
""
>
Type
</option>
<option
value=
""
>
Alphabetically
</option>
</select>
</li>
<li>
<h2>
Filter:
</h2>
<select>
<option
value=
""
>
All content
</option>
<option
value=
""
>
Videos
</option>
<option
value=
""
>
Problems
</option>
<option
value=
""
>
Labs
</option>
<option
value=
""
>
Tutorials
</option>
<option
value=
""
>
HTML
</option>
</select>
<a
href=
"#"
class=
"more"
>
More
</a>
</li>
<li
class=
"search"
>
<input
type=
"search"
name=
""
id=
""
value=
""
placeholder=
"Search"
/>
</li>
</ul>
</section>
<div
class=
"content"
>
<section
class=
"modules"
>
<ol>
<li>
<ol
id=
"sortable"
>
% for child in module.get_children():
<li
class=
"${module.scope_ids.block_type}"
>
<a
href=
"#"
class=
"module-edit"
data-id=
"${child.location}"
data-type=
"${child.js_module_name}"
data-preview-type=
"${child.module_class.js_module_name}"
>
${child.display_name_with_default}
</a>
<a
href=
"#"
class=
"draggable"
>
handle
</a>
</li>
%endfor
</ol>
</li>
</ol>
</section>
</div>
<
%
include
file=
"metadata-edit.html"
/>
</section>
common/lib/xmodule/xmodule/js/src/sequence/edit.coffee
View file @
138cd459
class
@
SequenceDescriptor
extends
XModule
.
Descriptor
constructor
:
(
@
element
)
->
@
$tabs
=
$
(
@
element
).
find
(
"#sequence-list"
)
@
$tabs
.
sortable
(
update
:
(
event
,
ui
)
=>
@
update
()
)
save
:
->
children
:
$
(
'#sequence-list li a'
,
@
element
).
map
((
idx
,
el
)
->
$
(
el
).
data
(
'id'
)).
toArray
()
common/lib/xmodule/xmodule/js/src/vertical/edit.coffee
View file @
138cd459
class
@
VerticalDescriptor
extends
XModule
.
Descriptor
constructor
:
(
@
element
)
->
@
$items
=
$
(
@
element
).
find
(
".vert-mod"
)
@
$items
.
sortable
(
update
:
(
event
,
ui
)
=>
@
update
()
)
save
:
->
children
:
$
(
'.vert-mod div'
,
@
element
).
map
((
idx
,
el
)
->
$
(
el
).
data
(
'id'
)).
toArray
()
common/lib/xmodule/xmodule/js/src/wrapper/edit.coffee
deleted
100644 → 0
View file @
172c6d15
class
@
WrapperDescriptor
extends
XModule
.
Descriptor
constructor
:
(
@
element
)
->
console
.
log
'WrapperDescriptor'
@
$items
=
$
(
@
element
).
find
(
".vert-mod"
)
@
$items
.
sortable
(
update
:
(
event
,
ui
)
=>
@
update
()
)
save
:
->
children
:
$
(
'.vert-mod div'
,
@
element
).
map
((
idx
,
el
)
->
$
(
el
).
data
(
'id'
)).
toArray
()
common/lib/xmodule/xmodule/split_test_module.py
View file @
138cd459
...
...
@@ -8,12 +8,13 @@ from webob import Response
from
xmodule.progress
import
Progress
from
xmodule.seq_module
import
SequenceDescriptor
from
xmodule.studio_editable
import
StudioEditableModule
from
xmodule.x_module
import
XModule
,
module_attr
from
lxml
import
etree
from
xblock.core
import
XBlock
from
xblock.fields
import
Scope
,
Integer
,
ReferenceValueDict
from
xblock.fields
import
Scope
,
Integer
,
String
,
ReferenceValueDict
from
xblock.fragment
import
Fragment
log
=
logging
.
getLogger
(
'edx.'
+
__name__
)
...
...
@@ -23,6 +24,13 @@ class SplitTestFields(object):
"""Fields needed for split test module"""
has_children
=
True
display_name
=
String
(
display_name
=
"Display Name"
,
help
=
"This name appears in the horizontal navigation at the top of the page."
,
scope
=
Scope
.
settings
,
default
=
"Experiment Block"
)
user_partition_id
=
Integer
(
help
=
"Which user partition is used for this test"
,
scope
=
Scope
.
content
...
...
@@ -45,7 +53,7 @@ class SplitTestFields(object):
@XBlock.needs
(
'user_tags'
)
# pylint: disable=abstract-method
@XBlock.wants
(
'partitions'
)
class
SplitTestModule
(
SplitTestFields
,
XModule
):
class
SplitTestModule
(
SplitTestFields
,
XModule
,
StudioEditableModule
):
"""
Show the user the appropriate child. Uses the ExperimentState
API to figure out which child to show.
...
...
@@ -177,21 +185,10 @@ class SplitTestModule(SplitTestFields, XModule):
Renders the Studio preview by rendering each child so that they can all be seen and edited.
"""
fragment
=
Fragment
()
contents
=
[]
for
child
in
self
.
descriptor
.
get_children
():
rendered_child
=
self
.
runtime
.
get_module
(
child
)
.
render
(
'student_view'
,
context
)
fragment
.
add_frag_resources
(
rendered_child
)
contents
.
append
({
'id'
:
child
.
location
.
to_deprecated_string
(),
'content'
:
rendered_child
.
content
})
fragment
.
add_content
(
self
.
system
.
render_template
(
'vert_module.html'
,
{
'items'
:
contents
}))
# Only render the children when this block is being shown as the container
root_xblock
=
context
.
get
(
'root_xblock'
)
if
root_xblock
and
root_xblock
.
location
==
self
.
location
:
self
.
render_children
(
context
,
fragment
,
can_reorder
=
False
)
return
fragment
def
student_view
(
self
,
context
):
...
...
@@ -296,3 +293,11 @@ class SplitTestDescriptor(SplitTestFields, SequenceDescriptor):
makes it use module.get_child_descriptors().
"""
return
True
@property
def
non_editable_metadata_fields
(
self
):
non_editable_fields
=
super
(
SplitTestDescriptor
,
self
)
.
non_editable_metadata_fields
non_editable_fields
.
extend
([
SplitTestDescriptor
.
due
,
])
return
non_editable_fields
common/lib/xmodule/xmodule/studio_editable.py
View file @
138cd459
...
...
@@ -5,25 +5,34 @@ Mixin to support editing in Studio.
class
StudioEditableModule
(
object
):
"""
Helper methods for supporting Studio editing of xblocks.
Helper methods for supporting Studio editing of xblocks/xmodules.
This class is only intended to be used with an XModule, as it assumes the existence of
self.descriptor and self.system.
"""
def
render_
reorderable_children
(
self
,
context
,
fragment
):
def
render_
children
(
self
,
context
,
fragment
,
can_reorder
=
False
,
can_add
=
False
,
view_name
=
'student_view'
):
"""
Renders children with the appropriate HTML structure for drag and drop.
Renders the children of the module with HTML appropriate for Studio. If can_reorder is True,
then the children will be rendered to support drag and drop.
"""
contents
=
[]
for
child
in
self
.
get_display_items
():
context
[
'reorderable_items'
]
.
add
(
child
.
location
)
rendered_child
=
child
.
render
(
'student_view'
,
context
)
for
child
in
self
.
descriptor
.
get_children
():
# pylint: disable=E1101
if
can_reorder
:
context
[
'reorderable_items'
]
.
add
(
child
.
location
)
child_module
=
self
.
system
.
get_module
(
child
)
# pylint: disable=E1101
rendered_child
=
child_module
.
render
(
view_name
,
context
)
fragment
.
add_frag_resources
(
rendered_child
)
contents
.
append
({
'id'
:
child
.
location
.
to_deprecated_string
(),
'content'
:
rendered_child
.
content
})
fragment
.
add_content
(
self
.
system
.
render_template
(
"studio_render_children_view.html"
,
{
fragment
.
add_content
(
self
.
system
.
render_template
(
"studio_render_children_view.html"
,
{
# pylint: disable=E1101
'items'
:
contents
,
'xblock_context'
:
context
,
'can_add'
:
can_add
,
'can_reorder'
:
can_reorder
,
}))
common/lib/xmodule/xmodule/tests/test_split_module.py
View file @
138cd459
...
...
@@ -43,7 +43,7 @@ class SplitTestModuleTest(XModuleXmlImportTest):
xml
.
HtmlFactory
(
parent
=
split_test
,
url_name
=
'split_test_cond1'
,
text
=
'HTML FOR GROUP 1'
)
self
.
course
=
self
.
process_xml
(
course
)
course_seq
=
self
.
course
.
get_children
()[
0
]
self
.
course_sequence
=
self
.
course
.
get_children
()[
0
]
self
.
module_system
=
get_test_system
()
def
get_module
(
descriptor
):
...
...
@@ -71,7 +71,7 @@ class SplitTestModuleTest(XModuleXmlImportTest):
)
self
.
module_system
.
_services
[
'partitions'
]
=
self
.
partitions_service
# pylint: disable=protected-access
self
.
split_test_module
=
course_seq
.
get_children
()[
0
]
self
.
split_test_module
=
self
.
course_sequence
.
get_children
()[
0
]
self
.
split_test_module
.
bind_for_student
(
self
.
module_system
,
self
.
split_test_module
.
_field_data
)
# pylint: disable=protected-access
@ddt.data
((
'0'
,
'split_test_cond0'
),
(
'1'
,
'split_test_cond1'
))
...
...
@@ -147,3 +147,40 @@ class SplitTestModuleTest(XModuleXmlImportTest):
self
.
assertEquals
(
fields
.
get
(
'user_partition_id'
),
'0'
)
self
.
assertIsNotNone
(
fields
.
get
(
'group_id_to_child'
))
self
.
assertEquals
(
len
(
children
),
2
)
def
test_render_studio_view
(
self
):
"""
Test the rendering of the Studio view.
"""
# The split_test module should render both its groups when it is the root
reorderable_items
=
set
()
context
=
{
'runtime_type'
:
'studio'
,
'container_view'
:
True
,
'reorderable_items'
:
reorderable_items
,
'root_xblock'
:
self
.
split_test_module
,
}
html
=
self
.
module_system
.
render
(
self
.
split_test_module
,
'student_view'
,
context
)
.
content
self
.
assertIn
(
'HTML FOR GROUP 0'
,
html
)
self
.
assertIn
(
'HTML FOR GROUP 1'
,
html
)
# When rendering as a child, it shouldn't render either of its groups
reorderable_items
=
set
()
context
=
{
'runtime_type'
:
'studio'
,
'container_view'
:
True
,
'reorderable_items'
:
reorderable_items
,
'root_xblock'
:
self
.
course_sequence
,
}
html
=
self
.
module_system
.
render
(
self
.
split_test_module
,
'student_view'
,
context
)
.
content
self
.
assertNotIn
(
'HTML FOR GROUP 0'
,
html
)
self
.
assertNotIn
(
'HTML FOR GROUP 1'
,
html
)
def
test_settings
(
self
):
"""
Test the settings configuration.
"""
non_editable_metadata_fields
=
self
.
split_test_module
.
non_editable_metadata_fields
self
.
assertIn
(
SplitTestDescriptor
.
due
,
non_editable_metadata_fields
)
self
.
assertNotIn
(
SplitTestDescriptor
.
display_name
,
non_editable_metadata_fields
)
common/lib/xmodule/xmodule/tests/test_vertical.py
View file @
138cd459
...
...
@@ -54,9 +54,20 @@ class VerticalModuleTestCase(BaseVerticalModuleTest):
"""
Test the rendering of the Studio view
"""
# Vertical shouldn't render children on the unit page
context
=
{
'runtime_type'
:
'studio'
,
'container_view'
:
False
,
}
html
=
self
.
module_system
.
render
(
self
.
vertical
,
'student_view'
,
context
)
.
content
self
.
assertNotIn
(
self
.
test_html_1
,
html
)
self
.
assertNotIn
(
self
.
test_html_2
,
html
)
# Vertical should render reorderable children on the container page
reorderable_items
=
set
()
context
=
{
'runtime_type'
:
'studio'
,
'container_view'
:
True
,
'reorderable_items'
:
reorderable_items
,
}
html
=
self
.
module_system
.
render
(
self
.
vertical
,
'student_view'
,
context
)
.
content
...
...
common/lib/xmodule/xmodule/vertical_module.py
View file @
138cd459
...
...
@@ -30,7 +30,10 @@ class VerticalModule(VerticalFields, XModule, StudioEditableModule):
Renders the Studio preview view, which supports drag and drop.
"""
fragment
=
Fragment
()
self
.
render_reorderable_children
(
context
,
fragment
)
# For the container page we want the full drag-and-drop, but for unit pages we want
# a more concise version that appears alongside the "View =>" link.
if
context
.
get
(
'container_view'
):
self
.
render_children
(
context
,
fragment
,
can_reorder
=
True
,
can_add
=
True
)
return
fragment
def
render_view
(
self
,
context
,
template_name
):
...
...
@@ -82,3 +85,11 @@ class VerticalDescriptor(VerticalFields, SequenceDescriptor):
# TODO (victor): Does this need its own definition_to_xml method? Otherwise it looks
# like verticals will get exported as sequentials...
@property
def
non_editable_metadata_fields
(
self
):
non_editable_fields
=
super
(
VerticalDescriptor
,
self
)
.
non_editable_metadata_fields
non_editable_fields
.
extend
([
VerticalDescriptor
.
due
,
])
return
non_editable_fields
common/lib/xmodule/xmodule/wrapper_module.py
View file @
138cd459
...
...
@@ -20,7 +20,3 @@ class WrapperDescriptor(VerticalDescriptor):
module_class
=
WrapperModule
has_children
=
True
js
=
{
'coffee'
:
[
resource_string
(
__name__
,
'js/src/vertical/edit.coffee'
)]}
js_module_name
=
"VerticalDescriptor"
common/static/sass/_mixins.scss
View file @
138cd459
...
...
@@ -54,19 +54,19 @@
// extends - justify-content right for display:flex alignment in older browsers
%ui-justify-right-flex
{
-webkit-box-pack
:
end
;
-moz-box-pack
:
end
;
-ms-flex-pack
:
end
;
-webkit-justify-content
:
end
;
-webkit-box-pack
:
flex-
end
;
-moz-box-pack
:
flex-
end
;
-ms-flex-pack
:
flex-
end
;
-webkit-justify-content
:
flex-
end
;
justify-content
:
flex-end
;
}
// extends - justify-content left for display:flex alignment in older browsers
%ui-justify-left-flex
{
-webkit-box-pack
:
start
;
-moz-box-pack
:
start
;
-ms-flex-pack
:
start
;
-webkit-justify-content
:
start
;
-webkit-box-pack
:
flex-
start
;
-moz-box-pack
:
flex-
start
;
-ms-flex-pack
:
flex-
start
;
-webkit-justify-content
:
flex-
start
;
justify-content
:
flex-start
;
}
...
...
common/test/acceptance/pages/studio/component_editor.py
0 → 100644
View file @
138cd459
from
bok_choy.page_object
import
PageObject
from
selenium.webdriver.common.keys
import
Keys
from
selenium.webdriver.common.action_chains
import
ActionChains
from
utils
import
click_css
class
ComponentEditorView
(
PageObject
):
"""
A :class:`.PageObject` representing the rendered view of a component editor.
This class assumes that the editor is our default editor as displayed for xmodules.
"""
BODY_SELECTOR
=
'.xblock-editor'
def
__init__
(
self
,
browser
,
locator
):
"""
Args:
browser (selenium.webdriver): The Selenium-controlled browser that this page is loaded in.
locator (str): The locator that identifies which xblock this :class:`.xblock-editor` relates to.
"""
super
(
ComponentEditorView
,
self
)
.
__init__
(
browser
)
self
.
locator
=
locator
def
is_browser_on_page
(
self
):
return
self
.
q
(
css
=
'{}[data-locator="{}"]'
.
format
(
self
.
BODY_SELECTOR
,
self
.
locator
))
.
present
def
_bounded_selector
(
self
,
selector
):
"""
Return `selector`, but limited to this particular `ComponentEditorView` context
"""
return
'{}[data-locator="{}"] {}'
.
format
(
self
.
BODY_SELECTOR
,
self
.
locator
,
selector
)
def
url
(
self
):
"""
Returns None because this is not directly accessible via URL.
"""
return
None
def
get_setting_entry_index
(
self
,
label
):
"""
Returns the index of the setting entry with given label (display name) within the Settings modal.
"""
# TODO: will need to handle tabbed "Settings" in future (current usage is in vertical, only shows Settings.
setting_labels
=
self
.
q
(
css
=
self
.
_bounded_selector
(
'.metadata_edit .wrapper-comp-setting .setting-label'
))
for
index
,
setting
in
enumerate
(
setting_labels
):
if
setting
.
text
==
label
:
return
index
return
None
def
set_field_value_and_save
(
self
,
label
,
value
):
"""
Set the field with given label (display name) to the specified value, and presses Save.
"""
index
=
self
.
get_setting_entry_index
(
label
)
elem
=
self
.
q
(
css
=
self
.
_bounded_selector
(
'.metadata_edit div.wrapper-comp-setting input.setting-input'
))[
index
]
# Click in the field, delete the value there.
action
=
ActionChains
(
self
.
browser
)
.
click
(
elem
)
for
_x
in
range
(
0
,
len
(
elem
.
get_attribute
(
'value'
))):
action
=
action
.
send_keys
(
Keys
.
BACKSPACE
)
# Send the new text, then Tab to move to the next field (so change event is triggered).
action
.
send_keys
(
value
)
.
send_keys
(
Keys
.
TAB
)
.
perform
()
click_css
(
self
,
'a.action-save'
)
common/test/acceptance/pages/studio/container.py
View file @
138cd459
...
...
@@ -3,7 +3,7 @@ Container page in Studio
"""
from
bok_choy.page_object
import
PageObject
from
bok_choy.promise
import
Promise
from
bok_choy.promise
import
Promise
,
EmptyPromise
from
.
import
BASE_URL
from
selenium.webdriver.common.action_chains
import
ActionChains
...
...
@@ -15,15 +15,24 @@ class ContainerPage(PageObject):
"""
Container page in Studio
"""
NAME_SELECTOR
=
'a.navigation-current'
def
__init__
(
self
,
browser
,
unit_
locator
):
def
__init__
(
self
,
browser
,
locator
):
super
(
ContainerPage
,
self
)
.
__init__
(
browser
)
self
.
unit_locator
=
unit_
locator
self
.
locator
=
locator
@property
def
url
(
self
):
"""URL to the container page for an xblock."""
return
"{}/container/{}"
.
format
(
BASE_URL
,
self
.
unit_locator
)
return
"{}/container/{}"
.
format
(
BASE_URL
,
self
.
locator
)
@property
def
name
(
self
):
titles
=
self
.
q
(
css
=
self
.
NAME_SELECTOR
)
.
text
if
titles
:
return
titles
[
0
]
else
:
return
None
def
is_browser_on_page
(
self
):
...
...
@@ -91,6 +100,14 @@ class ContainerPage(PageObject):
# Click the confirmation dialog button
click_css
(
self
,
'a.button.action-primary'
,
0
)
def
edit
(
self
):
self
.
q
(
css
=
'.edit-button'
)
.
first
.
click
()
EmptyPromise
(
lambda
:
self
.
q
(
css
=
'.xblock-studio_view'
)
.
present
,
'Wait for the Studio editor to be present'
)
.
fulfill
()
return
self
class
XBlockWrapper
(
PageObject
):
...
...
common/test/acceptance/pages/studio/utils.py
View file @
138cd459
...
...
@@ -5,7 +5,7 @@ from bok_choy.promise import Promise
from
selenium.webdriver.common.action_chains
import
ActionChains
def
click_css
(
page
,
css
,
source_index
,
require_notification
=
True
):
def
click_css
(
page
,
css
,
source_index
=
0
,
require_notification
=
True
):
"""
Click the button/link with the given css and index on the specified page (subclass of PageObject).
...
...
common/test/acceptance/tests/test_studio_container.py
View file @
138cd459
"""
Acceptance tests for Studio related to the container page.
"""
from
..pages.studio.auto_auth
import
AutoAuthPage
from
..pages.studio.overview
import
CourseOutlinePage
from
..fixtures.course
import
CourseFixture
,
XBlockFixtureDesc
from
.helpers
import
UniqueCourseTest
from
..pages.studio.component_editor
import
ComponentEditorView
from
unittest
import
skip
class
ContainerBase
(
UniqueCourseTest
):
...
...
@@ -85,13 +89,17 @@ class ContainerBase(UniqueCourseTest):
)
.
install
()
def
go_to_container_page
(
self
,
make_draft
=
False
):
unit
=
self
.
go_to_unit_page
(
make_draft
)
container
=
unit
.
components
[
0
]
.
go_to_container
()
return
container
def
go_to_unit_page
(
self
,
make_draft
=
False
):
self
.
outline
.
visit
()
subsection
=
self
.
outline
.
section
(
'Test Section'
)
.
subsection
(
'Test Subsection'
)
unit
=
subsection
.
toggle_expand
()
.
unit
(
'Test Unit'
)
.
go_to
()
if
make_draft
:
unit
.
edit_draft
()
container
=
unit
.
components
[
0
]
.
go_to_container
()
return
container
return
unit
def
verify_ordering
(
self
,
container
,
expected_orderings
):
xblocks
=
container
.
xblocks
...
...
@@ -131,6 +139,7 @@ class DragAndDropTest(ContainerBase):
expected_ordering
)
@skip
(
"Sporadically drags outside of the Group."
)
def
test_reorder_in_group
(
self
):
"""
Drag Group A Item 2 before Group A Item 1.
...
...
@@ -303,3 +312,36 @@ class DeleteComponentTest(ContainerBase):
{
self
.
group_b
:
[
self
.
group_b_item_1
,
self
.
group_b_item_2
]},
{
self
.
group_empty
:
[]}]
self
.
delete_and_verify
(
self
.
group_a_item_1_action_index
,
expected_ordering
)
class
EditContainerTest
(
ContainerBase
):
"""
Tests of editing a container.
"""
__test__
=
True
def
modify_display_name_and_verify
(
self
,
component
):
"""
Helper method for changing a display name.
"""
modified_name
=
'modified'
self
.
assertNotEqual
(
component
.
name
,
modified_name
)
component
.
edit
()
component_editor
=
ComponentEditorView
(
self
.
browser
,
component
.
locator
)
component_editor
.
set_field_value_and_save
(
'Display Name'
,
modified_name
)
self
.
assertEqual
(
component
.
name
,
modified_name
)
def
test_edit_container_on_unit_page
(
self
):
"""
Test the "edit" button on a container appearing on the unit page.
"""
unit
=
self
.
go_to_unit_page
(
make_draft
=
True
)
component
=
unit
.
components
[
0
]
self
.
modify_display_name_and_verify
(
component
)
def
test_edit_container_on_container_page
(
self
):
"""
Test the "edit" button on a container appearing on the container page.
"""
container
=
self
.
go_to_container_page
(
make_draft
=
True
)
self
.
modify_display_name_and_verify
(
container
)
lms/templates/studio_render_children_view.html
View file @
138cd459
<ol
class=
"reorderable-container"
>
% if can_reorder:
<ol
class=
"reorderable-container"
>
% endif
% for item in items:
${item['content']}
% endfor
</ol>
% if not xblock_context['read_only']:
% if can_reorder:
</ol>
% endif
% if can_add and not xblock_context['read_only']:
<div
class=
"add-xblock-component new-component-item adding"
></div>
% endif
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