Skip to content

Commit f1cbfda

Browse files
committed
[exercise,exercise2] refactor
* align exercise & exercise2 to match each other * separate html element creation/update/removal from metadata stuff * simplify functions for creating/removing exercises, clicking controls by using metadata over thml elements * clarify exercise (question + answer) vs solution (just answer) wording * refresh exercises' solution visiblity
1 parent b77e03b commit f1cbfda

File tree

3 files changed

+198
-299
lines changed

3 files changed

+198
-299
lines changed

src/jupyter_contrib_nbextensions/nbextensions/exercise/main.js

Lines changed: 89 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@
77
December 6, 2017 @jcb91: use bootstrap 'hidden' class to play nicely with collapsible_headings
88
December 30, 2015: update to 4.1
99
Update december 22, 2015:
10-
Added the metadata solution_first to mark the beginning of an exercise. It is now possible to have several consecutive exercises.
11-
Update october 21-27,2015:
10+
Added the metadata solution_first to mark the beginning of an exercise. It is now possible to have several consecutive exercises.
11+
Update october 21-27,2015:
1212
1- the extension now works with the multicell API, that is
1313
- several cells can be selected either via the rubberband extension
1414
- or via Shift-J (select next) or Shift-K (select previous) keyboard shortcuts
15-
(probably Shit-up and down will work in a near future)
15+
(probably Shit-up and down will work in a near future)
1616
Note: previously, the extension required the selected cells to be marked with a "selected" key in metadata. This is no more necessary with the new API.
1717
Then clicking on the toolbar button transforms these cells into a "solution" which is hidden by default
18-
** Do not forget to keep the Shift key pressed down while clicking on the menu button
18+
** Do not forget to keep the Shift key pressed down while clicking on the menu button
1919
(otherwise selected cells will be lost)**
2020
2- the "state" of solutions, hidden or shown, is saved and restored at reload/restart. We use the "solution" metadata to store the current state.
2121
3- A small issue (infinite loop when a solution was defined at the bottom edge of the notebook have been corrected)
@@ -34,169 +34,116 @@ define([
3434
* handle click event
3535
*
3636
* @method click_solution_lock
37-
* @param ev {Event} jquery event
37+
* @param evt {Event} jquery event
3838
*/
39-
function click_solution_lock(ev) {
40-
var cell=IPython.notebook.get_selected_cell();
41-
var cell_index = IPython.notebook.get_selected_index();
42-
var ncells = IPython.notebook.ncells();
43-
var is_locked = cell.element.find('#lock').hasClass('fa-plus-square-o');
44-
if (is_locked == true) {
45-
cell.element.find('#lock').removeClass('fa-plus-square-o');
46-
cell.element.find('#lock').addClass('fa-minus-square-o');
47-
cell.metadata.solution = "shown";
48-
IPython.notebook.select_next();
49-
cell = IPython.notebook.get_selected_cell();
50-
while (cell_index++<ncells & cell.metadata.solution !=undefined & cell.metadata.solution_first !=true) {
51-
cell.element.removeClass('hidden');
52-
cell.metadata.solution = "shown";
53-
IPython.notebook.select_next();
54-
cell = IPython.notebook.get_selected_cell();
55-
}
56-
} else {
57-
cell.element.find('#lock').removeClass('fa-minus-square-o');
58-
cell.element.find('#lock').addClass('fa-plus-square-o');
59-
cell.metadata.solution = "hidden"
60-
IPython.notebook.select_next();
61-
cell = IPython.notebook.get_selected_cell();
62-
while (cell_index++<ncells & cell.metadata.solution !=undefined & cell.metadata.solution_first !=true) {
63-
cell.element.addClass('hidden');
64-
cell.metadata.solution = "hidden"
65-
IPython.notebook.select_next();
66-
cell = IPython.notebook.get_selected_cell();
67-
}
39+
function click_solution_lock(evt) {
40+
var cell = IPython.notebook.get_selected_cell();
41+
var is_locked = cell.metadata.solution === 'hidden';
42+
cell.metadata.solution = is_locked ? 'shown' : 'hidden';
43+
element_set_locked(cell, !is_locked);
44+
cell = IPython.notebook.get_next_cell(cell);
45+
while (cell !== null && cell.metadata.solution !== undefined && !cell.metadata.solution_first) {
46+
cell.element.toggleClass('hidden', !is_locked);
47+
cell.metadata.solution = is_locked ? 'shown' : 'hidden';
48+
cell = IPython.notebook.get_next_cell(cell);
6849
}
69-
}
50+
}
7051

7152
/**
72-
* Hide solutions
53+
* Create or Remove an exercise in selected cells
7354
*
74-
* @method hide_solutions
55+
* @method create_remove_exercise
7556
*
7657
*/
77-
function hide_solutions() {
78-
// first check if lock symbol is already present in selected cell, if yes, remove it
79-
var lcells=IPython.notebook.get_selected_cells(); //list of selected cells
80-
if (typeof IPython.notebook.get_selected_indices == "undefined") { //noteboox 4.1.x
81-
var icells=IPython.notebook.get_selected_cells_indices(); // corresponding indices 4.1.x version
82-
}
83-
else { //notebook 4.0.x
84-
var icells=IPython.notebook.get_selected_indices(); // corresponding indices
85-
}
58+
function create_remove_exercise() {
59+
var lcells = IPython.notebook.get_selected_cells();
8660
// It is possible that no cell is selected
87-
if (lcells.length==0) {alert("Exercise extension: \nPlease select some cells..."); return};
61+
if (lcells.length < 1) {
62+
alert("Exercise extension: \nPlease select some cells...");
63+
return;
64+
}
8865

89-
var cell=lcells[0];
90-
var has_lock = cell.element.find('#lock').is('div');
91-
if (has_lock === true) {
92-
cell.element.find('#lock').remove();
66+
var cell = lcells[0];
67+
if (cell.metadata.solution_first) {
68+
remove_element(cell);
9369
delete cell.metadata.solution_first;
94-
while (cell.metadata.solution != undefined & cell.metadata.solution_first !=true ) {
70+
while (cell !== null && cell.metadata.solution !== undefined && !cell.metadata.solution_first) {
9571
delete cell.metadata.solution;
9672
cell.element.removeClass('hidden');
97-
IPython.notebook.select_next();
98-
cell = IPython.notebook.get_selected_cell()
99-
}
100-
} else {
101-
/*(jfb) --- I do not understand this part... --- It looks for the first selected cell, but we already have the list of selected cells lcells
102-
// find first cell with solution
103-
var start_cell_i; // = undefined
104-
var cells = IPython.notebook.get_cells();
105-
for(var i in cells){
106-
var cell = cells[i];
107-
if (typeof cell.metadata.selected != undefined && cell.metadata.selected === true) {
108-
start_cell_i = i;
109-
console.log("selected start cell:", i);
110-
break
73+
cell = IPython.notebook.get_next_cell(cell);
11174
}
11275
}
113-
IPython.notebook.select(start_cell_i);
114-
*/
115-
// if (cell.metadata.selected == true) { // (jfb) no metadata "selected"
116-
var el = $('<div id="lock" class="fa fa-plus-square-o">');
117-
cell.element.prepend(el);
118-
cell.metadata.solution_first=true;
119-
cell.metadata.solution = "hidden";
120-
cell.element.css({"background-color": "#ffffff"});
121-
el.click(click_solution_lock);
122-
for (var k = 1; k < lcells.length; k++){
123-
cell = lcells[k];
124-
//console.log("new cell:", icells[k]);
125-
cell.element.css({"background-color": "#ffffff"});
126-
cell.element.addClass('hidden');
127-
cell.metadata.solution = "hidden";
128-
}
76+
else {
77+
cell.metadata.solution_first = true;
78+
cell.metadata.solution = 'hidden';
79+
add_element(cell);
80+
for (var k = 1; k < lcells.length; k++) {
81+
cell = lcells[k];
82+
cell.element.addClass('hidden');
83+
cell.metadata.solution = 'hidden';
12984
}
130-
IPython.notebook.select(icells[0]); //select first cell in the list
13185
}
132-
133-
function load_ipython_extension(){
134-
IPython.toolbar.add_buttons_group([
135-
IPython.keyboard_manager.actions.register ({
136-
help : 'Exercise: Create/Remove solutions',
137-
icon : 'fa-mortar-board',
138-
handler : function () {
139-
//console.log(IPython.notebook.get_selected_cells())
140-
hide_solutions();
141-
}
142-
}, 'hide_solutions', 'exercise')
143-
]);
86+
}
14487

145-
/**
146-
* load css file and append to document
147-
*
148-
* @method load_css
149-
* @param name {String} filenaame of CSS file
150-
*
88+
/**
89+
* Add a lock control to the given cell
15190
*/
152-
var load_css = function (name) {
153-
var link = document.createElement("link");
154-
link.type = "text/css";
155-
link.rel = "stylesheet";
156-
link.href = requirejs.toUrl(name);
157-
document.getElementsByTagName("head")[0].appendChild(link);
158-
};
91+
function add_element(cell) {
92+
var ctrl = cell.element.find('.exercise');
93+
if (ctrl.length > 0) return ctrl;
94+
var locked = cell.metadata.solution === 'hidden';
95+
ctrl = $('<div class="exercise fa">')
96+
.prependTo(cell.element)
97+
.on('click', click_solution_lock);
98+
element_set_locked(cell, locked);
99+
return ctrl;
100+
}
159101

160-
load_css('./main.css');
102+
function remove_element(cell) {
103+
cell.element.find('.exercise').remove();
104+
}
161105

162-
// ***************** Keyboard shortcuts ******************************
163-
var add_cmd_shortcuts = {
164-
'Alt-S': {
165-
help: 'Define Solution (Exercise)',
166-
help_index: 'ht',
167-
handler: function(event) {
168-
hide_solutions();
169-
return false;
170-
}
171-
}
106+
function element_set_locked(cell, locked) {
107+
return cell.element.find('.exercise')
108+
.toggleClass('fa-plus-square-o', locked)
109+
.toggleClass('fa-minus-square-o', !locked);
172110
}
173-
IPython.keyboard_manager.command_shortcuts.add_shortcuts(add_cmd_shortcuts);
174-
175-
/**
176-
* Display existing solutions at startup
177-
*
178-
*/
179-
var cells = IPython.notebook.get_cells();
180-
var found_solution = false;
181-
for(var i in cells){
182-
var cell = cells[i];
183-
if (found_solution == true && typeof cell.metadata.solution != "undefined" && cell.metadata.solution_first !=true) {
184-
cell.element.toggleClass('hidden', cell.metadata.solution === 'hidden');
185-
} else {
186-
found_solution = false
187-
}
188111

189-
if (found_solution == false && typeof cell.metadata.solution != "undefined") {
190-
if (cell.metadata.solution=="hidden") var el = $('<div id="lock" class="fa fa-plus-square-o">');
191-
else var el = $('<div id="lock" class="fa fa-minus-square-o">');
192-
cell.element.prepend(el);
193-
el.click( click_solution_lock);
194-
found_solution = true;
195-
}
112+
function refresh_exercises() {
113+
var in_exercise = false;
114+
IPython.notebook.get_cells().forEach(function(cell) {
115+
if (in_exercise && cell.metadata.solution !== undefined && !cell.metadata.solution_first) {
116+
cell.element.toggleClass('hidden', cell.metadata.solution === 'hidden');
117+
} else {
118+
in_exercise = false;
119+
}
120+
if (!in_exercise && cell.metadata.solution !== undefined) {
121+
in_exercise = true;
122+
add_element(cell);
123+
}
124+
});
196125
}
197126

198-
}
127+
function load_ipython_extension() {
128+
// add css
129+
$('<link rel="stylesheet" type="text/css">')
130+
.attr('href', requirejs.toUrl('./main.css'))
131+
.appendTo('head');
132+
133+
// Hide/display existing solutions at startup
134+
events.on('notebook_loaded.Notebook', refresh_exercises);
135+
if (IPython.notebook._fully_loaded) refresh_exercises();
199136

137+
var action_name = IPython.keyboard_manager.actions.register({
138+
help : 'Exercise: Create/Remove exercise',
139+
help_index: 'ht',
140+
icon : 'fa-mortar-board',
141+
handler : create_remove_exercise
142+
}, 'create_remove_exercise', 'exercise');
143+
144+
IPython.toolbar.add_buttons_group([action_name]);
145+
IPython.keyboard_manager.command_shortcuts.add_shortcuts({'Alt-S': action_name}});
146+
}
200147

201148
return {
202149
load_ipython_extension: load_ipython_extension,

src/jupyter_contrib_nbextensions/nbextensions/exercise2/main.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
.exercise2 {
2+
display: flex;
3+
width: 100%;
4+
flex-direction: row;
5+
align-content: flex-end;
6+
}
17

28
.onoffswitch {
39
display: inline;

0 commit comments

Comments
 (0)