Repository: oppia/oppia

Issue:
Make communication between progress-nav and interactions more transparent and maintainable
The new progress-nav (#4265,#4365) has a Submit button which is supposed to act as the Submit button for the card's interaction if `show_nav_submit_button=True` (some interactions, such as MultipleChoiceInput, don't need a Submit button so that flag is `False`).

1. Every interaction that uses the Submit button needs to have a callback attached for `EVENT_PROGRESS_NAV_SUBMITTED`, which is broadcast from the progress-nav when the user clicks on the Submit button (the interaction then performs its `submitAnswer` action). This is repetitive, we want that the interaction creator doesn't have to manually define the callback for each interaction. This is also unnecessary because `submitAnswer` is originally defined on `ConversationSkinDirective`, so the progress nav should not need to go through the interaction directive to call it.
1. Similarly, every interaction needs to call `setAnswerValidity` whenever the answer changes in order to control whether or not the submit button is enabled or disabled.
1. The `ItemSelectionInput` is a special case that has `show_nav_submit_button=False`, but if there are `>1` answer selections then the progress-nav still needs to show the Submit button. We want to remove this special case.



Discussion:
Some additional notes/clarification on point 1: there should be no dev-written code for the Submit button in particular for all of the existing interactions -- or, alternatively, there should be some backend/frontend test that ensures the conditions specified always hold (e.g., maybe pull the standard button into its own directive and check that the interaction includes that code snippet exactly).
After discussing with @seanlip, here is an approach:

1. Create a new `CurrentAnswerService`:
    * Private Fields (with getter & setter): `[answer, rulesService, validityCheckFn, forceHideSubmitButton (bool), onSubmitCallback (optional)]`
    * All fields are set when a new interaction is initialized.
    * Every time the answer changes in an interaction, the interaction also sets the answer field.

2. There is already a function `submitAnswer` in `ConversationSkinDirective`.  We should modify it to get the current answer from `CurrentAnswerService` and submit it.
    * If the answer is submitted directly from the interaction (e.g., in the MultipleChoice interaction), the interaction should delegate directly to this `submitAnswer` function (as it currently does).
    * If the answer is submitted from `ProgressNavDirective`, call `submitAnswer` on `ConversationSkinDirective` (as opposed to the current impl. of broadcasting to the interaction first). Also, call `onSubmitCallback`if one is set. Also, we can now remove `EVENT_PROGRESS_NAV_SUBMITTED` completely. Also, remove `setAnswerValidity` from each interaction directive.

3. In addition to its `rulesService`, each interaction should define a `configService` with the following properties:
    * `validityCheckFn`: returns a bool for whether the answer is valid or not
    * `showSubmitCheckFn`: returns a bool for whether or not to ever show a Submit button (this is known at the time of creation and doesn’t depend on the students’ answer).

4. How to enforce that each interaction directive updates `CurrentAnswerService` properly each time the answer changes?
    * On `init()`, the interaction zeroises the `CurrentAnswerService` to break all previous bindings to the answer, and then calls `CurrentAnswer.getBindableCurrentAnswer()` which returns a dict with a single key-value pair for the answer. The interaction then proceeds to modify the answer in this dict. This allows the answer to be shared directly with `CurrentAnswerService` at all times.
@seanlip @AllanYangZhou , With reference to the 3 point of the very first comment, do we need to add submit button for `ItemSelectionInput` interaction as well to remove the special case when there is exactly 1 answer selection? Thanks!
Ah, no, that doesn't need to happen. The special case can be accommodated using the more general framework outlined in @AllanYangZhou's latest comment. 
Hi @LanJosh , would you want to try taking a look at this?
Sounds good! I've got exams the next couple days but I can look at it this weekend
If no one is looking at this may I have a look ?
@LanJosh are you still looking into this? Otherwise, go ahead @bansalnitish 
Hi @bansalnitish, I'm still looking into right now! slowly, but hopefully surely :)
Hi @LanJosh , Is there any progress so far? I'm thinking of taking it up.
Hi @vibhor98 I've only gotten parts of the CurrentAnswerService written as of yet. I'm pretty lost on what has to be done for each interaction in terms of the configService and updating the CurrentAnswerService. It's fine if you want to take this up, I'll probably look for a simpler issue to learn Angular. 
Hi @vibhor98
I was already up on this issue :smile:
No problem @bansalnitish 
Thank you @vibhor98 :smile: 
Hi @bansalnitish, what is the status of this issue? 
Hi @seanlip, I was currently busy on the blocking bugs and my speech recognition PR. So if someone else has to take, can go. I am currently unassigning it to myself.
Thanks!
Noted, thanks @bansalnitish!

@AllanYangZhou -- would you mind taking a look at this?
The above approach I described is flawed. The purpose of the `currentAnswerService` is to store the learner answer (and some related data) so that when the student presses 'Submit' from the progress nav, we can execute `submitAnswer` from `conversationSkinDirective`.

I've implemented an initial version of this that works fine for most interactions, but I've realized that for the `CodeRepl` interaction the expected behavior is that pressing 'Submit' performs 2 actions:
1. Calls `submitAnswer`
2. Runs (executes) the code

Currently this is handled because the interaction listens for `EVENT_PROGRESS_NAV_SUBMITTED` so it knows when to run the code and submit the answer--removing this would remove our ability to run the code on submission.

One solution is to implement a service that a submit callback function (rather than storing the current answer). But I'm not sure if that's better/clearer than our current implementation.
Interesting -- thanks for investigating, @AllanYangZhou!

One idea might be to separate out the buttons into a "Test Run" button and a "Submit Code" button. Would that work, do you think?
For `CodeRepl`, there is already a separate "Test Run" button. We just have the added functionality of running 1 more time on submit. But we could just remove that functionality.
Oh, I see. Hmm, that's not so nice I think.

It's basically the interaction performing its own local action onSubmit, right? I guess we need some clean way for the submit button to trigger that.

(Do you have a specific question here, btw?)
OK, I've just updated the approach description above. I think it makes sense to allow the interaction to attach a callback to `currentAnswerService` that will be triggered along with submission.
Sounds good, thanks!
@AllanYangZhou are you still working on this?

Resolving patch:
diff --git a/extensions/interactions/base.py b/extensions/interactions/base.py
--- a/extensions/interactions/base.py
+++ b/extensions/interactions/base.py
@@ -112,8 +112,10 @@ class BaseInteraction(object):
     can_have_solution = None
     # Whether to show a Submit button in the progress navigation area. This is
     # a generic submit button so do not use this if special interaction-specific
-    # behavior is required. The interaction JS must also handle the
-    # EVENT_PROGRESS_NAV_SUBMITTED event broadcast by this Submit button.
+    # behavior is required. The interaction directive must also register
+    # callbacks with CurrentInteractionService which handle answer submission
+    # when the button is clicked and control the enabling/disabling of the
+    # submit button.
     show_generic_submit_button = False
 
     # Temporary cache for the rule definitions.


Test patch:
diff --git a/core/templates/dev/head/pages/exploration_editor/editor_tab/test_interaction_modal_directive.html b/core/templates/dev/head/pages/exploration_editor/editor_tab/test_interaction_modal_directive.html
--- a/core/templates/dev/head/pages/exploration_editor/editor_tab/test_interaction_modal_directive.html
+++ b/core/templates/dev/head/pages/exploration_editor/editor_tab/test_interaction_modal_directive.html
@@ -32,7 +32,7 @@
   <br>
   <div class="submit-answer-btn">
     <md-button class="oppia-learner-confirm-button protractor-test-submit-answer-button"
-               ng-disabled="!interactionAnswerIsValid"
+               ng-disabled="isSubmitButtonDisabled()"
                ng-click="onSubmitAnswerFromButton()"
                translate="I18N_INTERACTIONS_SUBMIT">
     </md-button>