Flexview Guide
What is Flexview?
Flexview is a new tab on protocols that allows for custom read/write or viewing of data in a protocol. This can be used to give the user an interactive way to create/change/update data in a protocol, in a way that may be more complex than just changing a value in a table. This can essentially function like an applet, that is local to the protocol, and gains and changes data to the protocol in question.



When to Use
Not all protocols will need a flexview, in fact most wont. But if you ever need to edit data in a protocol, and want to give a more interactive, or easier way for the user to do mass changes without editing single table values you can write a flexview to do this for you. This acts as an applet/report in a way, you have access to all of our apis, invokables (starting 2.5), and can use this data to do mass changes, or even single changes. You can also use flexview as a means to preview the data in a more user friendly way, such as a document. Flexview can also be used to make printable reports with data inside of a protocol.
How to Use
To create a Flexview the first thing you would want to do is head to builders
→ protocols
, and from this view select the Flex View Designer
Tab which will allow you to start creating a flex-view for this protocol.

Once you select the Flex View Designer, you will be greeted with a screen similar to the one below.

FV1: The Page Elements section is a list of widgets that are installed onto the customer repo via the config, that are drag and droppable onto the report. For Example if you want to add a header to a file, you would just drag header over, and type the header in. Same for paragraph, images etc. All other things that are a little more complex and require code, would be done in a Generic Renderer (which can be turned into a widget in the future.)
FV2: The initialContext function is where we would gather our protocol data and set up our context object. For example, in the image above, we are fetching the protocol_instance data, and then for this report, I only care about one field in the protocol called Report JSON, so i extract that, and add it to the context for easy access. You could grab all resource_vals if you need them, this context can be designed and setup however you want.
FV3: The Header section is used for when you want to print this report. This would show up on the header of every page in the printed report above the content. You would normally create a header widget, and drag and drop that here, and all reports would be able to share it.
FV4: This is the report body. This is the bulk of the Flexview Designer, you would drag and drop all the content you need in here, and create any Generic Renders or Custom Widgets you may need in order to suite the customer needs. In this scenario we have a Generic Renderer, which you can see looks very similar to the code editor inside of an applet or report. This renderer has access to the context (as you can see it being passed into the onRender) as well as the html element that the widget will be renderering onto. Starting in 2.5 you will also have access to all reportApp api’s and invokables in invokables.js
Creating Custom Widgets
So most of the time alot of the Generic Renderers
you make could be used throughout multiple reports. So in that scenario we would want to make it into a Custom Widget (similar to the other drag and droppable items on the Page Elements
tab on the right.). In order to do this, there is applet code available, that will allow you to design all the different life cycles of the widget, and then save it, which will create the tab and that widget will now be a drag and droppable option.

Report applet Code Dump
<div style="margin:10px;"> <div id="widgetlist"> <table> <tr><td>Tabs</td><td><select id=widgets style="width:150px;" onChange="select(this)"></select></td></tr> <tr><td>Add Widget</td> <td> Tab <input id=newTab type=text style="width:100px"/> Widget <input id=newWidget type=text style="width:100px;"/> <input type=button style="width:50px;" value="Add" onClick="add()"/> </td></tr> </table> </div><br/> <table> <tr><td> onWidgetRender(el, context) <br/> <textarea id=onWidgetRender class=codepanesmall> el.innerHTML = context.protocolId </textarea><br/> </td><td> Widget View <div id=onWidgetRenderTarget class=renderpanewidget></div> </td></tr> <tr><td> onDesignRender(el, context) <br/> <textarea id=onDesignRender class=codepane> el.innerHTML = context.protocolId </textarea> </td><td> Design View<br/> <div id=onDesignRenderTarget class=renderpane></div> </td></tr> <tr><td> onExecuteRender(el, context) <br/> <textarea id=onExecuteRender class=codepane> el.innerHTML = context.protocolId </textarea><br/> </td><td> Execute View <div id=onExecuteRenderTarget class=renderpane></div> </td></tr> <tr><td> onPersist(el, context) <br/> <textarea id=onPersist class=codepanesmall> return { id } </textarea><br/> </td><td> </td></tr> <tr> </table> <input type=button class=button value=Execute onclick="execute()"/> <input type=button class=button value=Save onclick="save()"/> <div class=sectiontitle>JSON</div> <textarea id=json class=jsonpane></textarea> <script> let config = {} let selectedTab = '' let selectedWidget = '' function init() { ReportApp.utils.get("/api/configuration/esp", null, function(d) { setup(d.config.eln) }, function(e) { reportInteractionApi.toast("Error",e,"error") }) } function save() { config.tabs.forEach(t => { if(t.name == selectedTab) { t.content.forEach(w => { if(w.title == selectedWidget) { w.onDesignRender = $("#onDesignRender").val() w.onExecuteRender = $("#onExecuteRender").val() w.onWidgetRender = $("#onWidgetRender").val() w.onPersist = $("#onPersist").val() } }) } }) let data = { "config": { "eln": config }} ReportApp.utils.post("/api/configuration/esp", data, function(d) { reportInteractionApi.toast("Saved","Saved modifications to config","success") }, function(e) { reportInteractionApi.toast("Error",e,"error") }) } function setup(c, selectedTab, selectedWidget) { config = c let s = $("#widgets") s.empty().append($("<option>", { text: "(select)" })) config.tabs.forEach(t => { t.content.forEach(w => { s.append($("<option>", { widget: w.title, tab: t.name, text: t.name + ":" + w.title, selected: (selectedTab == t.name && selectedWidget == w.title) })) }) }) if(selectedTab && selectedWidget) select(s[0]) } function select(s) { let opt = s.selectedOptions[0] selectedTab = opt.getAttribute("tab") selectedWidget = opt.getAttribute("widget") config.tabs.forEach(t => { if(t.name == selectedTab) { t.content.forEach(w => { if(w.title == selectedWidget) { $("#onDesignRender").val(w.onDesignRender) $("#onExecuteRender").val(w.onExecuteRender) $("#onWidgetRender").val(w.onWidgetRender) $("#onPersist").val(w.onPersist) } }) } }) } function add() { let tabToAdd = $("#newTab").val() let widgetToAdd = $("#newWidget").val() let foundTab = null let foundWidget = null config.tabs.forEach(t => { if(t.name == selectedTab) { foundTab = t t.content.forEach(w => { if(w.title == widgetToAdd) foundWidget = w }) } }) if(foundWidget) { reportInteractionApi.toast("Error","Widget already exists","error") return } if(foundTab == null) { //add new tab foundTab = { content : [], name: tabToAdd } config.tabs.push(foundTab) } foundWidget = { title: widgetToAdd, onDesignRender: '//onDesignRender', onExecuteRender: '//onExecuteRender', onPersist: '//onPersist\nreturn { id }', onWidgetRender: '//onWidgetRender' } foundTab.content.push(foundWidget) setup(config, tabToAdd, widgetToAdd) } function execute() { let code = executeCode("onDesignRender","onDesignRenderTarget") code += ",\n" + executeCode("onExecuteRender","onExecuteRenderTarget") code += ",\n" + executeCode("onWidgetRender","onWidgetRenderTarget") code += ",\n\"onPersist\":" + JSON.stringify($("#onPersist").val(),null,4) $("#json").val(code) } function executeCode(codeId,targetId) { try { let el = document.getElementById(targetId) const fn = new Function('context', 'data', 'el', $("#" + codeId).val()) fn({ 'protocolId':'123' }, null, el) } catch(e) { reportInteractionApi.toast("Error",e,"error") } return "\"" + codeId + "\": " + JSON.stringify($("#" + codeId).val(),null,4) + "" } init() </script> <style> .sectiontitle { font-size: 20px; font-weight:bold; } .button { width:150px; background-color: #0e76ba; color: white; } .codepane { height:300px;width:600px;border:1px solid lightgray; } .renderpane { height:300px;width:600px;border:1px solid lightgray; } .jsonpane { height:300px;width:800px;border:1px solid lightgray; } .codepanesmall { width:600px;height:100px;width:600px;border:1px solid lightgray; } .renderpanesmall { height:100px;width:600px;border:1px solid lightgray; } .renderpanewidget { height:45px;width:300px;border:1px solid lightgray; font-family: 'Open Sans', sans-serif; font-size:14px; font-weight:bold; } .jsonpanesmall { height:100px;width:800px;border:1px solid lightgray; } </style> </div>