As of 2.5, all widgets are derived from BaseRefreshWdg. This refresh widget is a new style widget which has some added functionality allowing to to be "smart" enough to refresh itself. It also standardizes the interface for passing construction parameters to the widget. All new-style widgets take kwargs (keyword arguments) as argumets to the constructor.
widget = MyWidget(option1=value1, option2=value2)
All new style widgets defines a class constant ARGS_KEYS which return a dictionary of defined and allowable arguments:
ARGS_KEYS = { "mode": { 'description': "The mode of this widget", 'type': 'SelectWdg', 'values': 'insert|edit|view', 'default': 'insert', 'category': 'Options' }, "search_type": { 'description': "SType that will be inserted or edited", 'category': 'Options', 'order': 0, }, "title": { 'description': "The title to appear at the top of the layout", 'category': 'Options', }, "view": { 'description': "View of item to be edited", 'category': 'Options', 'order': 1, } }
TACTIC provides the ability to create your own widgets and integrate them seamlessly into the TACTIC interface.
There are 3 main types of widgets:
Widget: A widget derived from a the base Widget class is a free standing widget that requires no parent widget. Examples can be found in tactic.ui.widget.
Table Element Widget: An element widget is a widget that needs expected to be put inside a TableLayoutWdg. Examples can be found in tactic.ui.table.
Input Widget: An input widget is a widget that requires one or more values to put entered or extracted. Examples can be found in tactic.ui.input.
You can create your own custom widgets in Tactic that become completely integrated in the user interface.
All widgets are derived from the base Widget (pyasm.web.Widget) class. But starting from version 2.5, we encourage devlopers to derive from the BaseRefreshWdg in tactic.ui.common. This class defines the fundamental functionality required for all widgets that appear in TACTIC. To create your own widget, you can derive from this class.
In order to start showing how custom widgets can be created, we will start with the base "Hello World" widget. Create a folder called "widgets" and then create a new file called "hello_world_wdg.py" in this new folder. The following is a recommended folder structure:
home
|_ apache
|_ custom
__init__.py < - this file is empty |_ widgets __init__.py <- this file contains this line : from hello_world_wdg import *
hello_world_wdg.py
In the file, add the following lines:
__all__ = ['HelloWorldWdg'] from tactic.ui.common import BaseRefreshWdg class HelloWorldWdg(BaseRefreshWdg): def get_display(my): return "Hello World"
In order for TACTIC to be able to use this class,TACTIC must be able to see this file: this "custom" folder must be either in the PYTHONPATH or in sys.path of the TACTIC process (you can alternatively, use any class that complies with Python's module handling.
You can also use the python_path variable in the TACTIC config file to add paths to the sys.path dictionary like this:
<services>
<python_path>/home/apache/custom</python_path>
...
</services>
In order to view this widget quickly, you can open up the javascript editory and type:
spt.panel.load("main_body", "widgets.hello_world_wdg.HelloWorldWdg");
and press the "Run" button. You should see the following:
Note that the title does not change. This is something that the link will do automatically.
We could format the widge a litlle more using some basic HTML widgets.
__all__ = ['HelloWorldWdg2'] from tactic.ui.common import BaseRefreshWdg from pyasm.web import DivWdg class HelloWorldWdg2(BaseRefreshWdg): def get_display(my): top = DivWdg() top.add_style("font-size: 15px") top.add_style("margin: 30px") top.add_style("padding: 30px") top.add_style("width: 150px") top.add_style("text-align: center") top.add_style("border: solid 1px black") top.add("Hello World") return top
Adding this to a file called hello_world_wdg2.py. Put it in /home/apache/custom/widgets/. Edit the __init__.py file in this folder, the content would look like this :
from hello_world_wdg import *
from hello_world_wdg2 import *
and then in TACTIC Script Editor (Press the shortcut key 9 to bring it up), type:
spt.panel.load("main_body","widgets.hello_world_wdg2.HelloWorldWdg2");
Pressing the "Run" button gives:
Here we introduce the basic HTML widget DivWdg. The add_style() allows you to add arbitrary CSS styles to the widget. There are various operations that can be added to HTML widgets that are useful for formatting the layout of the page. These methods include:
set_attr(name, value)
add_style(name, value)
add_class(css_class)
add_event(event, js_action)
There are few useful predefined widgets that sit on top of HtmlElement:
DivWdg
SpanWdg
Table
These are all based of of HtmlElement which are basic html elements and provide a thin layer above HTML. HtmlElement also defines a number of static constructors to address most HTML elements:
HtmlElement.br()
HtmlElement.p()
HtmlElement.br()
These return variations of HtmlElement that represent the different HTML elements. These are useful for laying out a complex widget. All HTML elements and their properties are accessible from these.
You can add other predefined widget, for example, the CalendarWdg
__all__ = ['HelloWorldWdg3'] from tactic.ui.common import BaseRefreshWdg from pyasm.web import DivWdg from tactic.ui.widget import CalendarWdg class HelloWorldWdg3(Widget): def get_display(my): top = DivWdg() top.add_style("font-size: 15px") top.add_style("margin: 30px") top.add_style("padding: 30px") top.add_style("width: 200px") top.add_style("text-align: center") top.add_style("border: solid 1px black") top.add("Hello World") calendar = CalendarWdg() top.add(calendar) return top
Adding this to a file called hello_world_wdg3.py and then in javascript editor, type:
Pressing the "Run" button gives:
This adds one of the predefined widget "CalendarWdg". Widgets are hierarchical and can be added to other widgets. Any widget can embed any other widget within it's display. This provides a very flexible archictecure for building up complex hierarchical widgets.
There is a special class of widgets that are designed to be used in conjuntion with TableLayoutWdg, the primary widget used for laying out tabular data. These widgets should be derived from BaseTableElementWdg, which extends the basic Widget class with a number of specific methods.
The TableLayoutWdg uses it's child widgets slightly differently than most widgets. It creates a single widget for each column and calls the get_display() method repeatedly for each row; each row representing a single sobject. Each element widgets does have knowledge of all of the sobjects, however, for each row, there will be a current sobject set. This means that the widgets get_display() method will be called repeatedly for each row. So, instead of operating on a list of widgets, the table element widget should get the current widget using the "get_current_widget()" method.
The following is a simple example of a table element widget.
from pyasm.web import DivWdg from tactic.ui.common import BaseTableElementWdg class MyElementWdg(BaseTableElementWdg): def get_display(my): sobject = my.get_current_sobject() first_name = sobject.get_value("first_name") last_name = sobject.get_value("last_name") div = DivWdg() div.add("%s %s" % (first_name, last_name) ) return div
The class is almost identical to a regular class, except that it is derived from BaseTableElementWdg and that it uses get_current_sobject() to get the current sobject being drawn. This widget still has access to all of the sobjects in all of the rows, through get_sobjects(), if this is necessary.
To test this, save the code above in a file called my_element_wdg.py and enter this into the javascript editor:
This only works in 2.6: in 2.5, you have to create the view in the widget config table
var config = " \ <config><test> \ <element name='name'> \ <display class='custom.my_element_wdg.MyElementWdg'/> \ </element> \ </test></config>"; var args = { 'search_type': 'sthpw/login', 'view': 'test', 'config_xml': config, 'do_search': 'true' }; spt.panel.load("main_body", "tactic.ui.panel.TableLayoutWdg", args);
Pressing the "Run" button gives:
Your custom table element widget completely integrates within the TACTIC interface. You can add other widgets by expanding the config definition.
var config = " \ <config><test> \ <element name='preview'/> \ <element name='name'> \ <display class='custom.my_element_wdg.MyElementWdg'/> \ </element> \ <element name='email'/> \ </test></config>"; var args = { 'search_type': 'sthpw/login', 'view': 'test', 'config_xml': config, 'do_search': 'true' }; spt.panel.load("main_body", "tactic.ui.panel.TableLayoutWdg", args);
This adds a preview and an email column (which are predefined for sthpw/login search type) and appear with your custom widget.
This example describes how to create your own BaseTableElementWdg to execute a server-side command. The user can type some words in the text field, and then click on the "Action" button. The words will be written as the content of a file in the /tmp folder of the server. In the tactic config file, tactic-conf.xml in <TACTIC_DATA_DIR>/config, let's say the python_path is '/home/apache/custom'. You can create a file called custom_wdg.py and __init__.py in it.
Here is the content of__init__.py:
from custom_wdg import *
Here is the content of custom_wdg.py:
__all__ = ['CustomToolElementWdg','CustomCmd'] from tactic.ui.common import BaseTableElementWdg from tactic.ui.widget import ActionButtonWdg from pyasm.web import HtmlElement, SpanWdg from pyasm.widget import TextWdg from pyasm.command import Command class CustomToolElementWdg(BaseTableElementWdg): def get_display(my): top = DivWdg() top.add_class('spt_custom_tool_top') text = TextWdg('user_input') action_button = ActionButtonWdg(title='Action', tip='Write a file in /tmp based on the data in the text field') action_button.add_behavior({'type':'click_up', 'cbjs_action': '''var server = TacticServerStub.get(); try { var top = bvr.src_el.getParent(".spt_custom_tool_top"); var values = spt.api.get_input_values(top, null, false); # this path is assumed importable in your Python environment server.execute_cmd('custom_wdg.CustomCmd', values); } catch(e) { alert(spt.exception.handler(e)); } '''}) top.add(SpanWdg('Input:', css='small')) top.add(text) top.add(HtmlElement.br()) top.add(action_button) return top class CustomCmd(Command): def execute(my): text = my.kwargs.get('user_input') f = open('/tmp/my_file.txt','w') f.write(text) f.close()
If you click the first "Action" button, a file with "Hello !!!" will be created. On clicking the second "Action" button, the file content will be replaced with the word "example".