Wizard templates can now indicate they are mandatory

This will only style them in a special way and influence sorting. Plugins still need
to ensure that a step is not skipped through the use of the onWizardTabChange callback
in the view model.
This commit is contained in:
Gina Häußge 2015-08-21 18:24:25 +02:00
parent 990a44c7c9
commit 08c14e2e45
5 changed files with 60 additions and 7 deletions

View file

@ -171,7 +171,7 @@ onAfterTabChange
getAdditionalControls
Your viewmodel may return additional custom control definitions for inclusion on the "Control" tab of OctoPrint's
interface. See :ref:`the custom control feature<sec-features-custom_controls>`.
interface. See :ref:`the custom control feature<sec-features-custom_controls>`.
onSettingsShown
Called when the settings dialog is shown.
@ -189,6 +189,27 @@ onUserSettingsShown
onUserSettingsHidden
Called when the user settings dialog is hidden.
onWizardDetails
Called with the response from the wizard detail API call initiated before opening the wizard dialog. Will contain
the data from all :class:`~octoprint.plugin.WizardPlugin` implementations returned by their :meth:`~octoprint.plugin.WizardPlugin.get_wizard_details`
method, mapped by the plugin identifier.
onWizardTabChange
Called before the wizard tab/step is changed, with the ids of the current and the next tab as parameters. Return false
in order to prevent the tab change, e.g. if the wizard step is mandatory and not yet completed by the user. Take a look at
the "Core Wizard" plugin bundled with OctoPrint and the ACL wizard step in particular for an example on how to use this.
onAfterWizardTabChange
Called after the wizard tab/step is changed, with the id of the current tab as parameter.
onBeforeWizardFinish
Called before executing the finishing of the wizard. Return false here to stop the actual finish, e.g. if some step is
still incomplete.
onWizardFinish
Called after executing the finishing of the wizard and before closing the dialog. Return ``reload`` here in order to
instruct OctoPrint to reload the UI after the wizard closes.
In order to hook into any of those callbacks, just have your viewmodel define a function named accordingly, e.g.
to get called after all viewmodels have been bound during application startup, implement a function ``onAllBound``
on your viewmodel, taking a list of all bound viewmodels:

View file

@ -252,8 +252,10 @@ class TemplatePlugin(OctoPrintPlugin, ReloadNeedingPlugin):
Wizards
Plugins may define wizard dialogs to display to the user if necessary (e.g. in case of missing information that
needs to be queried from the user to make the plugin work). Note that with the current implementations, all
wizard dialogs will always be sorted alphabetically by their ``name``. A wizard dialog provided through a
plugin will only be displayed if the plugin reports the wizard as being required through :meth:`~octoprint.plugin.WizardPlugin.is_wizard_required`.
wizard dialogs will be will always be sorted by their ``mandatory`` attribute (which defaults to ``False``) and then
alphabetically by their ``name``. Hence, mandatory wizard steps will come first, sorted alphabetically, then the
optional steps will follow, also alphabetically. A wizard dialog provided through a plugin will only be displayed
if the plugin reports the wizard as being required through :meth:`~octoprint.plugin.WizardPlugin.is_wizard_required`.
Please also refer to the :class:`~octoprint.plugin.WizardPlugin` mixin for further details on this.
The included template must be called ``<plugin identifier>_wizard.jinja2`` (e.g. ``myplugin_wizard.jinja2``) unless
@ -263,6 +265,14 @@ class TemplatePlugin(OctoPrintPlugin, ReloadNeedingPlugin):
The wrapper div and the link in the wizard navigation will have the additional classes and styles applied as defined
via the supplied configuration supplied through :func:`get_template_configs`.
.. note::
A note about ``mandatory`` wizard steps: In the current implementation, marking a wizard step as
mandatory will *only* make it styled accordingly. It is the task of the :ref:`view model <sec-plugins-viewmodels>`
to actually prevent the user from skipping the dialog by implementing the ``onWizardTabChange``
callback and returning ``false`` there if it is detected that the user hasn't yet filled in the
wizard step.
Generic
Plugins may also inject arbitrary templates into the page of the web interface itself, e.g. in order to
add overlays or dialogs to be called from within the plugin's javascript code.
@ -410,6 +420,17 @@ class TemplatePlugin(OctoPrintPlugin, ReloadNeedingPlugin):
* - styles_content
- Like ``styles`` but only applied to the content pane itself.
``wizard`` type
.. list-table::
:widths: 5 95
* - mandatory
- Whether the wizard step is mandatory (True) or not (False). Optional,
defaults to False. If set to True, OctoPrint will sort visually mark
the step as mandatory in the UI (bold in the navigation and a little
alert) and also sort it into the first half.
.. note::
As already outlined above, each template type has a default template name (i.e. the default navbar template

View file

@ -76,6 +76,9 @@ class CoreWizardPlugin(octoprint.plugin.AssetPlugin,
def _get_acl_wizard_name(self):
return "Access Control"
def _get_acl_additional_wizard_template_data(self):
return dict(mandatory=self._is_acl_wizard_required())
@octoprint.plugin.BlueprintPlugin.route("/acl", methods=["POST"])
def acl_wizard_api(self):
from flask import request

View file

@ -59,7 +59,7 @@ def index():
tab=dict(add="append", key="name"),
settings=dict(add="custom_append", key="name", custom_add_entries=lambda missing: dict(section_plugins=(gettext("Plugins"), None)), custom_add_order=lambda missing: ["section_plugins"] + missing),
usersettings=dict(add="append", key="name"),
wizard=dict(add="append", key="name"),
wizard=dict(add="append", key="name", key_extractor=lambda d, k: "0:{}".format(d[0]) if "mandatory" in d[1] and d[1]["mandatory"] else "1:{}".format(d[0])),
generic=dict(add="append", key=None)
)
@ -178,7 +178,7 @@ def index():
return ["firstrunstart"] + existing + missing + ["firstrunend"]
template_sorting["wizard"] = dict(add="custom_insert", key="name", custom_insert_entries=lambda missing: dict(), custom_insert_order=custom_insert_order)
template_sorting["wizard"].update(dict(add="custom_insert", custom_insert_entries=lambda missing: dict(), custom_insert_order=custom_insert_order))
templates["wizard"]["entries"] = dict(
firstrunstart=(gettext("Start"), dict(template="dialogs/wizard/firstrun_start.jinja2", _div="wizard_firstrun_start")),
firstrunend=(gettext("Finish"), dict(template="dialogs/wizard/firstrun_end.jinja2", _div="wizard_firstrun_end")),

View file

@ -19,7 +19,7 @@
class="{% if mark_active %}active{% set mark_active = False %}{% endif %} {% if "classes_link" in data %}{{ data.classes_link|join(' ') }}{% elif "classes" in data %}{{ data.classes|join(' ') }}{% endif %}"
{% if "styles_link" in data %} style="{{ data.styles_link|join(', ') }}" {% elif "styles" in data %} style="{{ data.styles|join(', ') }}" {% endif %}
>
<a href="#{{ data._div }}" data-toggle="tab">{{ entry }}</a>
<a href="#{{ data._div }}" data-toggle="tab">{% if "mandatory" in data and data.mandatory %}<strong>{{ entry }}</strong>{% else %}{{ entry }}{% endif %}</a>
</li>
{% if "custom_bindings" not in data or data["custom_bindings"] %}<!-- /ko -->{% endif %}
{% endif %}
@ -37,7 +37,12 @@
class="tab-pane {% if mark_active %}active{% set mark_active = False %}{% endif %} {% if classes_content in data %}{{ data.classes_content|join(' ') }}{% elif classes in data %}{{ data.classes|join(' ') }}{% endif %}"
{% if "styles_content" in data %} style="{{ data.styles_content|join(', ') }}" {% elif styles in data %} style="{{ data.styles|join(', ') }}" {% endif %}
>
{% include data.template ignore missing %}
{% if "mandatory" in data and data.mandatory %}
<div class="alert">{% trans %}
<strong>Mandatory Step!</strong> You need to fill this out now.
{% endtrans %}</div>
{% endif %}
{% include data.template ignore missing %}
</div>
{% if "custom_bindings" not in data or data["custom_bindings"] %}<!-- /ko -->{% endif %}
{% endif %}
@ -54,5 +59,8 @@
<div class="pull-left">
<button class="btn button-previous" name="previous">{{ _('Previous') }}</button>
</div>
<div class="text-center" style="line-height: 20px; padding: 4px 12px;">
{{ _('Unless otherwise noted, you may just skip any wizard page by clicking "Next" or "Finish".') }}
</div>
</div>
</div>