From 376dc8cd0fd641a31dcc6f9abeccae1083cf7d0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannis=20Mo=C3=9Fhammer?= Date: Thu, 12 Sep 2013 17:26:29 +0200 Subject: [PATCH] Document form and container behaviour and simplify Now the whole page gets refreshed on container changes, if we encounter issues with that we can improve it afterwards or roll back some cahnges already made in previous commits refs #4611 --- doc/container_component.md | 142 +++++++++++++++ doc/form.md | 19 +- doc/form_elements.md | 74 +++++++- .../controllers/ListController.php | 2 +- .../views/scripts/list/downtimes.phtml | 5 +- .../views/scripts/list/hosts-compact.phtml | 1 - .../views/scripts/list/hosts.phtml | 1 - .../views/scripts/list/notifications.phtml | 3 - .../views/scripts/list/services-compact.phtml | 5 +- .../views/scripts/list/services.phtml | 5 +- .../application/views/scripts/show/host.phtml | 1 + public/js/icinga/componentLoader.js | 2 +- public/js/icinga/componentRegistry.js | 6 +- public/js/icinga/components/container.js | 163 +++++++----------- public/js/icinga/components/form.js | 8 +- public/js/icinga/components/mainDetailGrid.js | 34 ++-- public/js/icinga/icinga.js | 37 ++-- public/js/main.js | 2 +- test/js/test/icinga/componentTest.js | 2 +- .../test/icinga/components/containerTest.js | 58 ++----- test/js/test/icinga/registryTest.js | 17 +- test/js/testlib/historymock.js | 5 + 22 files changed, 377 insertions(+), 215 deletions(-) create mode 100644 doc/container_component.md diff --git a/doc/container_component.md b/doc/container_component.md new file mode 100644 index 000000000..ee0128d4f --- /dev/null +++ b/doc/container_component.md @@ -0,0 +1,142 @@ +# The Container Component (app/container) + +The container component is the most basic building block for icingaweb. Even when displaying an empty controller, +you always have at least two containers in your viewport which are implicitly created: The main and the detail container. + +Container handle the following tasks: + +* Updating the url part responsible for the container +* Handling Url changes like they occur when the browser history is used by synchronizing their content with the + associated Url part +* Informing subcomponents about changes in the container + + +## The Container Api + +You can find the sourcecode for containers along with jsdoc comments at *./public/js/icinga/components/container.js*. +Here we will discuss the most important calls and their synopsis: + +### Accessing Containers: + +The container component returns a 'Container' object which allows you to access responsible containers for dom nodes via +the following methods: + +* using `new Container($myDomNodes)` which returns a stateless container object wrapping the container responsible for + the first node in $myDomNodes +* using `Container.getMainContainer()` or `Container.getDetailContainer()` which remove the main or detail container + (this one is stateful with a few notes, read on) + +**Note:** `new Container($('#icingamain')) != Container.getMainContainer()`, but +`(new Container($('#icingamain'))).containerDom == Container.getMainContainer().containerDom` + +** Example #1 getting the container responsible for a dom node ** + +**HTML** + +
+
+ Some kind of node +
+
+
+ Some other kind of node +

+ Insert your lorem ipsum here +

+
+
+
+ +**JS**: + + require(['jquery', 'app/container'], function($, Container) { + var firstContainer = new Container($('.myNode')); // firstContainer wraps '#icingamain' + var mainContainer = Container.getMainContainer(); // also wraps '#icingamain' + var secondContainer = new Container($('.myNode p')); // #somecontainer is wrapped by secondContainer + + firstContainer.someProperty = 'What a nice property!'; + mainContainer.someState = 'I have some state'; + console.log(firstContainer.someProperty); // return 'What a nice property' + console.log(main.someProperty); // return 'undefined' + console.log(Container.getMainContainer().someState) // return 'I have some state' when page hasn't been refreshed + }); + +## Containers And The Browser Url + +As noted before (and indicated by the `getMainContainer()` and `getDetailContainer()` function), the main and detail +container have a special role. Considering the following Url: + + http://my.monitoringhost.org/icingaweb/monitoring/list/host?page=4&detail=%2Fmonitoring%2Fshow%2Fhost%3Fhost%3Dlocalhost + +This URL displays the 4th page of your host list in the main container (monitoring/list/host?page=4) and the host information +for localhost in the detail container (monitoring/show/host?host=localhost). When you split this Url up in logical pieces +it looks like this: + + http://my.monitoringhost.org/icingaweb/monitoring/list/host?page=4&detail=%2Fmonitoring%2Fshow%2Fhost%3Fhost%3Dlocalhost + \___________ _______________________/\_________ ______________/ \_ ____/\________________ _______________________/ + \/ \/ \/ \/ + 1. Base URL 2.Main container URL and Query 3.Detail param 4. Encoded detail URL and params + +1. **Base URL** : I don't think this needs much explanation. +2. **Main container URL and query** : This is the *normal* part of your Url and denotes the controller route that is + being displayed in your main container +3. **Detail parameter**: This parameter will be ignored by the main container and used for rendering the detail container, + if omitted there's simple no detail view to be displayed +4 **Encoded detail URL**: The value of the "detail" parameter is the Url (without the base Url) that returns the content + of the detail area + + +### Updating A Container's Url + +If you want your container to display content from a different Url, you can use the *replaceDomFromUrl()* on your +Container object: + +**Example #2 Updating A Containers URL** + +**HTML:** + +
+
+
+
+
+
+ +**JS:** + + // this loads the page with the new main container + require(['jquery', 'app/container'], function($, Container) { + new Container('#mainSub').replaceDomFormUrl('/another/url'); + } + + // this loads the page with the new detail container + require(['jquery', 'app/container'], function($, Container) { + new Container('#detailSub').replaceDomFormUrl('/another/url'); + } + + // this does NOT work: + require(['jquery', 'app/container'], function($, Container) { + Container.getMainContainer().replaceDomFormUrl('/another/url'); + // will never be reached due to a reload + Container.getMainContainer().replaceDomFormUrl('/another/url2'); + } + + // this loads the page with both main and detail changed (this is rarely needed and should be avoided) + require(['icinga', 'jquery', 'app/container'], function('Icinga', $, Container) { + // it's better to use this: + var mainContainer = Container.getMainContainer(); + var detailContainer = Container.getDetailContainer(); + + mainContainer.updateContainerHref('/another/url'); // first update the main container href + detailContainer.updateContainerHref('/another/url2'); // update the detail href + + var url = mainContainer.getContainerHref(detailContainer.getContainerHref()); // fetch the new url + Icinga.replaceBodyFromUrl(url); // and update manual + } + +This results in the URL changing to './another/url?detail=%2Fanother%2Fdetail%2Furl. +The advantage of using a Container instance with the subelements (i.e. '\#mainSub') over calling getMain/DetailContainer +directly is that you don't need to know in what container your view is displayed - when you move 'mainSub' into the +detail container, the detail container would be updated afterwards. + +**NOTE**: You should read the '...' section in order to understand why you shouldn't do it like in this example diff --git a/doc/form.md b/doc/form.md index befbdf0d7..aedfada80 100644 --- a/doc/form.md +++ b/doc/form.md @@ -11,10 +11,6 @@ Forms are basically Zend_Form classes with Zend_Form_Element items as controls. To ensure common functionallity and control dependent fields Icinga 2 Web provides sub classes to build forms on that. -![Basic form design][form1] - -*(Methods and attributes are exemplary and does not reflect the full class implementation)* - ### Key design #### Build of forms @@ -28,6 +24,20 @@ In order to let icingaweb create a submit button for you (which is required for method) you have to call the *setSubmitLabel($label)* method, which will add a Zend_Form_Element_Submit element to your form. +#### Client side behaviour + +A few methods in our Form implementation don't affect the rendering, but the behaviour of a form. + +* **Automatic submission of fields via *\Icinga\Web\Form::enableAutoSubmit(array $forms)*** + All form element ids passed in the $forms array will cause a submission of the form when changed. This normally + doesn't cause validation errors, as the form is not really seen as submitted by the 'isSubmittedAndValid', but allows + you to update your form when necessary. For example, when you have a select box whose selection affects which form elements + should be shown, you can use `$form->enableAutoSubmit(array('myForm'))`. +* **User confirmation when discarding forms.** When a user wants to leave the current page despite having unsaved changes, + a popup will appear and asks the user if he **really** wants to leave. This is implemented by the *app/form* componenent + and enabled by default. If you don't want this, you can call *setIgnoreChangeDiscarding($bool)* on your form. + + #### Calling is *isSubmittedAndValid()* *isSubmittedAndValid()* is used to check whether the form is ready to be processed or not. @@ -40,7 +50,6 @@ If the form has been updated, but not submitted (for example, because the a butt some fields in the form) the form is repopulated but not validated at this time. is SubmittedAndValid() returns false in this case, but no errors are added to the created form. - In order to be able to use isSubmittedAndValid, you have to define a submitbutton in the form. This is done with the *setSubmitLabel(string)* function, with the first parameter being the label set to the submit button. diff --git a/doc/form_elements.md b/doc/form_elements.md index eb550bfed..3949b0338 100644 --- a/doc/form_elements.md +++ b/doc/form_elements.md @@ -3,7 +3,9 @@ On top of the elements provided by the Zend Framework, Icinga 2 Web ships its own to offer additional functionality. The following is a list of these classes, as well as descriptions of the functionality they offer. -## DateTimePicker +## Elements + +### DateTimePicker `Icinga\Web\Form\Element\DateTimePicker` represents a control that allows the user to select date/time and to display the date and time with a user specified format. Internally the element returns the input as Unix timestamp after @@ -63,4 +65,74 @@ on the timezone set by the user. ) ) +## Validators +### WritablePathValidator + +This *Icinga\Web\Form\Validator\WritablePathValidator* validator tests a given (string-)input for being a valid writable +path. Normally it just tests for an existing, writable path but when setRequireExistence() is called, the path must +exist on form submission and be writable. + +**Example usage of writablePathValidator + + use \Icinga\Web\Form\Validator\WritablePathValidator; + $txtLogPath = new Zend_Form_Element_Text( + array( + 'name' => 'logging_app_target', + 'label' => 'Application Log Path', + 'helptext' => 'The logfile to write the icingaweb debug logs to.' + . 'The webserver must be able to write at this location', + 'required' => true, + 'value' => $logging->get('target', '/var/log/icingaweb.log') + ) + ); + $txtLogPath->addValidator(new WritablePathValidator()); + + +### DateTimeValidator + +The *Icinga\Web\Form\Validator\DateTimeValidator* validator allows you to validate an input against a set of datetime +patterns. On successful validation, it either gives a valid pattern via getValidPattern, or null if the entered time +is a timestamp. The above DateTimePicker utilizes this validator and should be used instead of directly using the validator. + + +## Decorators + +### ConditionalHidden Decorator + +The `Icinga\Web\Form\Decorator\ConditionalHidden` allows you to hide a form element with the 'conditional' attribute for +users that don't have JavaScript enabled (the form is rendered in a \