| 396 | |
| 397 | == A View of Sahana-Eden == |
| 398 | Their are a number of bits and pieces that make up the view and understanding what they are and how to interact with them is essential when developing Sahana-Eden. So taking the screenshot below of a standard screen, listing all of the instances of a particular resource. |
| 399 | [[Image("Resource details.png")]] |
| 400 | The various pieces that make up the screen are: |
| 401 | * Main menu |
| 402 | * Sub menu or the menu specific to the model |
| 403 | * Title |
| 404 | * Action Buttons |
| 405 | * Sub title |
| 406 | * dataTable |
| 407 | The menus (the first two bits) are common for all screens with the model specific menu being defined in the models/01_menu.py file. The rest of the view lies within the content div. |
| 408 | === The screen title and subtitle === |
| 409 | The first element in the content is the Title, this is a simple H2 tag but it has been nicely formatted by the style sheet to stand out. The title can set up with a simple assignment as follows: |
| 410 | |
| 411 | {{{output["title"] = "List of Assessment Series"}}} |
| 412 | Within the controller logic the output variable is returned by the call to the s3_rest_controller() and it is also available in the post processor function, so this line of code can be included in either the preprocessor or after the call to {{{s3_rest_controller()}}}. |
| 413 | |
| 414 | The output variable is a simple dictionary and so setting up a key in this dictionary with the value of “title” is sufficient. But this piece of code is not at all friendly for people who work in another language. The string needs to be translatable which is done by passing it to the T() function. |
| 415 | |
| 416 | {{{output["title"] = T("List of Assessment Series")}}} |
| 417 | The same string may be used in a number of different screens and so to help with consistency the string can be defined in one place and then reused. This is done in the model |
| 418 | |
| 419 | {{{s3.crud_strings[tablename] = Storage( |
| 420 | title_list = T("List of Assessment Series"), |
| 421 | )}}} |
| 422 | |
| 423 | The {{{tablename}}} variable has been set up to the name of the table. Other strings can be added here, so it becomes a convenient holder for all the string to be used by the model. Finally, because the title is such a common requirement in a form if a title_list value is set up for a list view then that string will be automatically used. That means that the {{{output["title"]}}} assignment is not necessary unless you want to use a different string. |
| 424 | |
| 425 | That's quite a bit of background to only produce a title, but what you have learnt for the title is the same for the subtitle. The special crud name for a subtitle belonging to a list view is {{{subtitle_list}}}, so all you need to do is define that in the {{{s3.crud_strings}}} variable, and should you want to override the default value then assign the new value to {{{output["subtitle"]}}}. |
| 426 | |
| 427 | === Crud generated buttons === |
| 428 | Their are a number of buttons that are automatically added to the screen depending upon the context. For a list the add resource button will be displayed. If you want this button then you don't need to do anything. However, if you wish to suppress the button from being displayed then the following will help: |
| 429 | {{{ |
| 430 | s3mgr.configure(tablename, |
| 431 | listadd=False, |
| 432 | ) |
| 433 | }}} |
| 434 | This code will go in the controller before the call to {{{s3_rest_controller()}}}. |
| 435 | |
| 436 | === dataTable === |
| 437 | This displays all of the resources and adds buttons to open (or edit) and delete the selected resource. Again all of this comes as part of the framework and can whilst the default settings are often sufficient it is possible to adjust the the way the dataTable looks by changing various settings. |
| 438 | == Resource Details == |
| 439 | Opening a resource on the dataTable will then display details about this resource. From the workflow perspective a resource consists of a number of actions that can be performed on the resource. These actions might, for example display the resource data, generate more data or trigger off more action. The screenshot below of a standard screen, showing the details of a particular resource. |
| 440 | [[Image("List of Resources.png")]] |
| 441 | The various pieces that make up the screen are remarkably similar to the first screen. The main additions are: |
| 442 | * rHeader |
| 443 | * tabs (within the rHeader) |
| 444 | This particular screen also has some extra details after the subtitle. This was added by creating a new view based on the standard list view but adding an extra slot to hold the help information about the list. |
| 445 | === rHeader === |
| 446 | The rHeader is an area on the screen to display summary details about the resource. The details of the rHeader will be set up in a function and the function can then be passed as a parameter to {{{s3_rest_controller()}}}. The rHeader callback function receives an {{{S3Request}}} object and should return the HTML which will be displayed on the screen. Typically the HTML returned would be a DIV object, with one HTML element within this being the tabs. |
| 447 | === tabs === |
| 448 | These are a convenient mechanism for displaying the options (links) available, and where appropriate they can follow the workflow of the resource. The tabs are set up by building a list of tuples, each tuple consists of the label followed by the action. This list is then passed to {{{rheader_tabs()}}}, which renders it into HTML ready to be added to the rHeader DIV. |
| 449 | |
| 450 | So the key to the tabs is the list of tuples, an example of one looks like this: |
| 451 | {{{ |
| 452 | if auth.s3_has_permission("create", "survey_series"): |
| 453 | tabs = [(T("Series Details"), None), |
| 454 | (T("New Assessment"), "newAssessment/"), |
| 455 | (T("Completed Assessments"), "complete"), |
| 456 | (T("Series Summary"),"summary"), |
| 457 | (T("Series Graph"),"graph"), |
| 458 | (T("Series Map"),"map"), |
| 459 | ] |
| 460 | }}} |
| 461 | The labels that appear on the screen are wrapped in a T() so that they can be translated. The action is either a string that relates to the type of action that will be performed, or None which means that the default action will be performed. |
| 462 | The actions though are the real power of the tabs. They can be any from: |
| 463 | * A default value |
| 464 | * A component of the resource, |
| 465 | * Another controller or |
| 466 | * A method. |
| 467 | === default tab action === |
| 468 | The default action will be an edit of the full resource details if the user has sufficient permissions otherwise it will be a read of the resource details. |
| 469 | === component tab action === |
| 470 | In the example given the database has a table called survey_complete and so complete is a component of the series resource. This will then display, in a list (dataTable), all the complete records that belong to the resource that had been selected. The component records can then be edited or deleted and this is all managed with the main resource UI, providing a consistent interface for the user. |
| 471 | === another controller tab action === |
| 472 | Not all parts of the workflow are coupled as tightly as a resource component relationship. For such cases it might be easier to switch to a completely different controller. To ensure that the consistency of the interface is retained the same rHeader should be used, but as already explained setting that up is a cinch. In the example above newAssessment is an example of an external controller. Note that in the tab it is followed by a slash. |
| 473 | === method tab action === |
| 474 | The method will be called by the same controller so it will inherit the pre and post processing. It is typically a function that will work with the same table but needs to perform something beyond the basic CRUD actions. In the example above summary, graph and map are all method actions. Because they are not controller functions they need to be set up a little differently. The method will be set up with the models (not the controllers) and the outline of the method is: |
| 475 | {{{ |
| 476 | def seriesMap(r, **attr): |
| 477 | |
| 478 | # retain the rheader |
| 479 | rheader = attr.get("rheader", None) |
| 480 | if rheader: |
| 481 | rheader = rheader(r) |
| 482 | output = dict(title=T("Do Something with the Template"), rheader=rheader) |
| 483 | else: |
| 484 | output = dict() |
| 485 | crud_strings = response.s3.crud_strings["survey_series"] |
| 486 | |
| 487 | # code specific to this method - creates the map |
| 488 | |
| 489 | output["title"] = crud_strings.title_map |
| 490 | output["subtitle"] = crud_strings.subtitle_map |
| 491 | output["form"] = form |
| 492 | output["map"] = map |
| 493 | |
| 494 | response.view = "survey/series_map.html" |
| 495 | return output |
| 496 | }}} |
| 497 | This method now needs to be linked to the controller, again this code should be in the model. |
| 498 | {{{ |
| 499 | s3mgr.model.set_method("survey", "series", method="map", action=seriesMap) |
| 500 | }}} |
| 501 | Because the method gets and then stores the rHeader it is possible for the call to originate from different controllers but the UI will remain consistent. |