FJML is a JSON based markup language which translates JSON files into Flet UI for web, mobile and desktop applications.
{
"Header":{
"program_name":"Example",
"action_import":{
"import":"Actions",
"from":".path.to.func.file"
},
},
"Controls":[
{
"var_name":"msg",
"control_type":"Text",
"settings":{
"value":"Hello World",
"size":20,
"color":{"code_refs":"colors", "attr":"green"},
"weight":{
"control_type":"FontWeight",
"attr":"W_700"
}
}
},
{
"var_name":"msg_display",
"control_type":"Container",
"settings":{
"content":{"refs":"msg"},
"alignment":{"control_type":"alignment.center"},
"border_radius":10,
"padding":{
"control_type":"padding.symmetric",
"settings":{"horizontal":10, "vertical":8}
},
"width":200,
"ink":true,
"ink_color":{"code_refs":"colors", "attr":"grey"}
}
}
],
"UI":[
{
"route":"/",
"settings":{
"controls":[
{"refs":"msg_display"}
],
"horizontal_alignment":{"code_refs":"cross_align"},
"vertical_alignment":{"code_refs":"main_align"}
}
}
]
}from fjml import data_types as dt
import flet as ft
class Colors:
green: str = ft.colors.GREEN_600
grey: str = ft.colors.GREY_200
class Actions(dt.EventContainer):
def _page_setup(self):
...
def _imports(self):
self.colors: Colors = Colors()
self.cross_align: str = ft.CrossAxisAlignment.CENTER
self.main_align: str = ft.MainAxisAlignment.CENTERfrom fjml import load_program, Compiler, data_types as dt
from path.to.program import Actions
import flet as ft
class Paths:
PROGRAM: str = "path\\to\\program_folder"
COMPILED: str = "path\\to\\compiled_program\\compiled.fjml"
class App:
def __init__(self, compile_run: bool = False) -> None:
if compile_run:
compiler: Compiler = Compiler(Paths.PROGRAM, Paths.COMPILED)
compiler.compile()
async def run(self, page: ft.Page):
page = load_program(Paths.COMPILED, Actions)
page.go("/")
if __name__ == "__main__":
app: App = App(compile_run=True)
ft.app(target=app.run)FJML comes with 2 CLI commands and each have specific parameters:
-
registry:The registry command is a categorical command which automatically modifies the registry file which contains of all Flet controls of your current Flet installation.
Choice Value Type Action delete strdeletes registry file reset strresets registry file Example:
fjml registry update
The reset command should be used after installation to ensure proper functioning of fjml
-
make:The make command generates an FJML folder containing all the needed files for running your project. make has one mandatory parameter,
--name, and one optional parameter,--path.--namegenerates the name of your project while--pathdirects where that project is generated. If path is not entered it will use the current directory.Example:
fjml make --name Hello World
FJML allows the use of python code to perform actions such as API calls, function calls, etc. via the EventContainer Abstract Base Class.
- The main format of the Actions class which inherits from the
EventContainerlooks like this:from fjml.data_types import EventContainer class Actions(EventContainer): def _page_setup(self): ''' a custom page setup function to initialize your page object with data ''' def _imports(self): ''' an import function used to run operations outside of the page before rendering the UI ''' #you can then add custom functions to be used throughout the FJML code
EventContainer also includes multiple built-in helper classes and functions to help create programs
-
-
See Flet Page Docs
-
See Flet Page Docs
-
See Flet Page Docs.
-
Name Attributes Return Description dict_to_control control: dt.ControlDictdt.ControlTypeAllows creating controls using FJML syntax inside the EventContainer.-
class Actions(dt.EventContainer): def make_control(self) -> ft.Control: return self.dict_to_control({ "control_type":"Container", "settings":{ "alignment":{"control_type":"alignment.center"}, "content":{ "control_type":"Text", "settings":{ "value":"Hello World", "size":18 } } } })
-
-
Name Attributes Return Description group_assign obj: Any', 'attribute_map: Mapping[str, Any]Noneallows assigning multiple attributes to an object at once -
class Data: f_name: str l_name: str age: int class Actions(dt.EventContainer): def fill_data(self) -> Data: data: Data = Data() self.group_assign( data, { "f_name":"John", "l_name":"Doe", "age":21 } ) return data
-
-
Methods Attributes Return Description add name: str,obj: AnyNoneadds an object to the eval statement's locals delete name: strNonedeletes an object from the eval statement's locals mass_add data: Mapping[str, Any]Noneadds multiple objects to the eval statement's locals mass_delete data: Sequence[str]Nonedeletes multiple objects from the eval statement's locals data NoneMapping[str, Any]returns a copy of all preset locals in the eval statement's locals
-
Methods Attributes Return Description set_object name: str,obj: AnyCallableNoneadds any callable object to the bucket, so it can be called inside the UI code call_object name: str,kwargs: dict[str, Any]Anycalls the object with the necessary key word arguments. (used when object is called within the UI code) delete_object name: strNonedeletes the object from the bucket - ⠀
{ "call":"get_text", "settings":{ "index":1 } }
- ⠀
-
Methods Attributes Return Description add name: str,obj: AnyNoneadds a property to be used as a code_refsinside the UI codecontains name: strboolused to check if a name is registered as property call name: str,operation: str,set_val: AnyNoneUses the property operations (set, get, del) to either set an object using the set_val parameter, get by just giving the name or deletion using the del operation.
-
This class's main use is to register functions to be called to set up what ever API, environment, etc. when the UI starts up.
Methods Attributes Return Description add_func func: Callable,parameters: Sequence[Any]Noneadds a function to the class mass_add_func items: Sequence[tuple[Callable, Sequence[Any]]]Noneadds multiple functions to the class call_functions NoneNonecalls all functions added to the class
-
this class is used primarily for retrieving styles set inside the style sheet. This is mainly used in the "_unpack" designator via the
{"styling":"xyz"}mapping value.Methods Attributes Return Description get_style path: strdt.JsonDictgets the style from a style sheet by name
-
This class is used to generate and register Flet views. Its main use is in the Flet's
Page.on_route_changeevent.Methods Attributes Return Description set_view route_name: str,view_settings: dt.ControlSettingsNoneadds a UIViewsto the compiled_model UI mapping attributeadd_view view: ft.ViewNoneadds a Flet view control to the page views make_view view_model: UIViewsft.Viewgenerates a Flet view control from a UIViewstype
-
{
"Header":{
//Used to declare certain values
},
"Imports":[
// Used to import controls from other files
],
"Controls":[
// Used to assign controls to variables
],
"UI":[
// Used to define route views
]
}This format separates the header data, imports, controls and display UI.
-
Headers is primarily map based and thus requires one to use keys and values unlike the list based forms like the rest are. The keys in this block consists of:
-
Value Type Example str"extra"stores the name of the folder which contains the FJML imports
-
Value Type Example str"Hello World"stores the name of the program
-
Value Type Example str"style_sheet"Stores the name of the FJML style sheet.
-
Value Type Example dt.JsonDict{"from":"...", "import":"..."}This dictionary imports the action class using the format:
Key Value Type Example from str".import_path.func"import str"Action"full example:
{ "action_import":{ "from":".import_path.func", "import":"Action" } }from .import_path.func import Action
The import statement is run as if it was run in your main file.
-
Value Type Example Sequence[dt.JsonDict][{"from":"...", "import":"...", "using":"..."}, ...]The value of this key consists of using a sequence of dictionaries which help import multiple controls at once:
Key Value Type Example from str.custom_controlsimport Union[str, Sequence[str]]"CustomBtn"or["CustomBtn", "CustomTxt"]using Optional[str]"CC"full example:
{ "extensions":[ { "from":".custom_controls", "import":["CustomBtn", "CustomTxt"], "using":"CC" } ] }Using extension imports like this is equivalent to:
-
from . import custom_controls as CC from CC import CustomBtn, CustomTxt
The use of these controls in FJML code will now have to use controls like so:
CC.CustomBtn -
from .custom_controls import CustomBtn, CustomTxt
-
-
-
Imports are called using the file name of the UI file defined inside the import folder defined by the key
"source". If the import folder includes different folders the use of the key"folder"can be used to indicate the specific folder inside the main import folder where you want to import from. e.g: -{"source:"container_ui"}-{source:["user_ui", "admin_ui"], "folder":"person_ui_folder"} -
controls can be named using the
"var_name"key and then can be called and used in the python"Action"class using"self.name_text"or in another control using a dictionary using the"refs"key and the control name as its value. e.g:{"refs":"name_text"}Format example:{ "var_name":"foo", "control_type":"imported/registered control name", "settings":{ ... } } -
The UI section is used to define the route views used by you program. These views use the format of:
{ "route":"/Home", "settings":{ //any view settings needed. P.S. the route parameter will always be ignored if set in the settings block. } }and are contained in a Sequence
With imports the JSON structure is similar except that it only has the "Controls" container. Using controls from other files is still possible once all dependencies are also imported into the main file.
Imported file format:
{
"Controls":[]
}With style sheets you are able to create styles for use later in your program. You are able to section off your styles by names and sub-names and thus call them using the format {name}.{sub_name}.{sub_sub_name}.... This format can go on forever if needed but can increase rendering time of your program if you go too deep.
Style sheet Format:
{
"name_1":{
"sub_name":{
"height":200,
"width":500
}
},
"name_2":{
"sub_name":{
"height":200,
"width":500
}
}
}styles can be used by then adding the "_unpack" attribute inside the control's "settings" dictionary with the dictionary formats:
{"styles":"{name}.{sub-name}"}- or
{"styles":"{name}.{sub-name} {name1}.{sub_name}"}if multiple styles are needed
-
class Action(EventContainer): def _imports(self) -> None: #register callable object self.object_bucket.set_object("calc_width", self.calc_width) def calc_width(self, height: int) -> int: return height*2
// call object { "control_type":"Container", "settings":{ "width":{ "call":"calc_width", "settings":{ "height":200 } } } }
-
{ "control_type":"Column", "settings":{ "controls":{ "control_type":"loop", "depth":1, "iterator":[1,2,3,4,5], "control":{ "control_type":"Text", "settings":{ "value":{ "control_type":"loop_index", "idx":[0] } } } } } } -
{ "var_name":"name", //<- Here "control_type":"Text", "settings":{ "value":"John Doe", "size":18 } }this can be accessed inside the Actions class using
self.nameand if control is defined asself.nameinside Actions it can be called in FJML using{"refs":"name"} -
Allows the use of FJML variables to be referenced in the same file or else where without the need for constant importing
{ "var_name":"name", //<- Here "control_type":"Text", "settings":{ "value":"John Doe", "size":18 } } { "var_name":"text_container", "control_type":"Container", "settings":{ "content":{"refs":"name"}, "padding":6 } }Allows the variables defined in python code to be accessed and used inside FJML code
class Actions(EventContainer): def _imports(self) -> None: self.text_size: int = 16
{ "var_name":"name", //<- Here "control_type":"Text", "settings":{ "value":"John Doe", "size":{"code_refs":"text_size"} } }The
idxkey works for both dictionaries and index based sequences.{ "var_name":"name", "control_type":"Text", "settings":{ "value":"John Doe", "size":18 } } { "var_name":"get_display_name", "control_type":"Text", "settings":{ "value":{ "refs":"name", "attr":"value" }, "size":18 } }-
class TextSizes: data: list[Union[dict[str, int], int]] = [18, {"name":16}] class Actions(EventContainer): def _imports(self) -> None: self.text_sizes: TextSizes = TextSizes()
{ "var_name":"name", "control_type":"Text", "settings":{ "value":"John Doe", "size":{ "code_refs":"text_sizes", "group":[ {"attr":"data"}, {"idx":1}, {"idx":"name"} ] } } }
-
-
In order to link your action class to FJML code you must import the in the
Headercontainer using the key,action_import.-
{ "Header":{ ..., "action_import":{ "import":"Actions", "from":".ui_test_program.func" } }, "Imports":[...], "Controls":[...], "UI":[...] }
All action imports must exist in an importable path and be written as if it was run in the
main.pyfile. -
-
FJML allows you multiple ways to define and add custom controls to your project. This is done by using the
"extensions"key inside the"Header":{ "Header":{ ..., "extensions":[ { "using":"fm", "import":["Buttons", "Switches"], "from":"flet_material" } ] }, "Imports":[...], "Controls":[ { "var_name":"switch", "control_type":"fm.Switches", "settings":{} } ], "UI":[...] }All imports must already be installed or exist in an importable path.
from fjml import load_program, Compiler, data_types as dt
import flet as ft
class Paths:
PROGRAM: str = "path\\to\\program_folder"
COMPILED: str = "path\\to\\compiled_program\\compiled.fjml"
class App:
def __init__(self, compile_run: bool = False) -> None:
if not compile_run:
return
compiler: Compiler = Compiler(Paths.PROGRAM, Paths.COMPILED)
compiler.compile()
async def run(self, page: ft.Page):
page = load_program(Paths.COMPILED, page)
page.go("/")
if __name__ == "__main__":
app: App = App(compile_run=True)
ft.app(target=app.run)