Here's a more complete snippet to get a form - in Groovy. if(formLocation) { String delegatorName = delegator.getDelegatorName(); if (delegatorName.contains("default#")) { delegatorName = "default"; } DispatchContext dispatchContext = dispatcher.getDispatchContext(); ModelReader entityModelReader = ModelReader.getModelReader(delegatorName); ModelForm form = FormFactory.getFormFromLocation(formLocation, formName, entityModelReader, dispatchContext); if(form) { form.runFormActions(context); target = form.getTarget(context,form.getTargetType()); targetWindow = form.getTargetWindow(context); defaultMapName = form.getDefaultMapName();
fieldList = form.getFieldList(); fieldList.each{field -> } for(ModelFormField modelFormField in fieldList) { ModelFormField.FieldInfo currentFieldInfo = modelFormField.getFieldInfo(); if (currentFieldInfo != null) { ModelFormField fieldInfoFormField = currentFieldInfo.getModelFormField(); if (fieldInfoFormField != null) { fieldInfoFormField.setModelForm(form); } } else { throw new IllegalArgumentException("Error rendering form, a field has no FieldInfo, ie no sub-element for the type of field for field named: " + modelFormField.getName()); } } List<ModelFormField> tempFieldList = FastList.newInstance(); tempFieldList.addAll(fieldList); //Debug.logInfo("fieldList size: "+form.useWhe+" tempFieldList size: "+tempFieldList.size(),""); for (int j = 0; j < tempFieldList.size(); j++) { ModelFormField modelFormField = tempFieldList.get(j); if (form.useWhenFields.contains(modelFormField.getName())) { boolean shouldUse1 = modelFormField.shouldUse(context); for (int i = j+1; i < tempFieldList.size(); i++) { ModelFormField curField = tempFieldList.get(i); if (curField.getName() != null && curField.getName().equals(modelFormField.getName())) { boolean shouldUse2 = curField.shouldUse(context); if (shouldUse1 == shouldUse2) { tempFieldList.remove(i--); } } else { continue; } } } } Set<String> alreadyRendered = new TreeSet<String>(); List<ModelFormField> hiddenIgnoredFieldList = form.getHiddenIgnoredFields(context, alreadyRendered, tempFieldList, -1); //renderHiddenIgnoreFields for(ModelFormField modelFormField in hiddenIgnoredFieldList) { fieldMap = [:]; fieldMap.title = modelFormField.getTitle(context); fieldMap.name = modelFormField.getParameterName(context); fieldMap.action = modelFormField.getAction(context); fieldMap.event = modelFormField.getEvent(); fieldMap.id = modelFormField.getCurrentContainerId(context); fieldMap.value = modelFormField.getEntry(context); fieldMap.required = modelFormField.getRequiredField(); ModelFormField.FieldInfo fieldInfo = modelFormField.getFieldInfo(); switch(fieldInfo.getFieldType()) { case ModelFormField.FieldInfo.HIDDEN : fieldMap.type = "hidden"; break; case ModelFormField.FieldInfo.IGNORED : fieldMap.type = "ignored"; break; case ModelFormField.FieldInfo.DISPLAY : fieldMap.type = "display"; break; case ModelFormField.FieldInfo.DISPLAY_ENTITY : fieldMap.type = "displayEntity"; break; case ModelFormField.FieldInfo.HYPERLINK : fieldMap.type = "hyperlink"; break; } if(fieldInfo.getFieldType() != ModelFormField.FieldInfo.IGNORED) { fields.add(fieldMap); } } Iterator<ModelFormField> fieldIter = tempFieldList.iterator(); ModelFormField lastFormField = null; ModelFormField currentFormField = null; ModelFormField nextFormField = null; if (fieldIter.hasNext()) { currentFormField = fieldIter.next(); //ModelFormField.FieldInfo fieldInfo = currentFormField.getFieldInfo(); //Debug.logInfo("currentFormField : "+currentFormField.getName(),""); }else { lastFormField = currentFormField; currentFormField = null; } if (fieldIter.hasNext()) { nextFormField = fieldIter.next(); } while(fieldIter.hasNext()) { currentFormField = fieldIter.next(); //Debug.logInfo("currentFormField : "+currentFormField.getName(),""); ModelFormField.FieldInfo fieldInfo = currentFormField.getFieldInfo(); if (fieldInfo.getFieldType() == ModelFormField.FieldInfo.HIDDEN || fieldInfo.getFieldType() == ModelFormField.FieldInfo.IGNORED) { //Debug.logInfo("continuing : HIDDEN/IGNORED",""); continue; } if (alreadyRendered.contains(currentFormField.getName())) { //Debug.logInfo("continuing : alreadyRendered",""); continue; } if (!currentFormField.shouldUse(context)) { if (UtilValidate.isNotEmpty(lastFormField)) { currentFormField = lastFormField; } continue; } alreadyRendered.add(currentFormField.getName()); fieldMap = getFieldMap(fieldInfo,context); fieldMap.each{key, val -> } fields.add(fieldMap); } } } Gavin On Tue, Dec 17, 2019 at 5:05 PM Gavin Mabie <kwikst...@gmail.com> wrote: > Hi Gil > > I used a type=groovy service to resolve the form location eg. > /* > *@paramater formLocaction (where the form resides in the system) > *@parameter formName (passed as request parameter) > * returns an Ofbiz form entity with all the normal functionalties > associated with ModelForm > * this is returned to the http request as a JSON objecct > */ > ModelForm form = FormFactory.getFormFromLocation(formLocation, formName, > entityModelReader, dispatchContext); > > Angular handles the response through a formField service which reads each > Ofbiz formfields (JSON object) and creates a Angular reactive form from > there. The Angualr formFields Service mirrors the Ofbiz field types etc. > > Regards > > Gavin > > > > > > > > On Tue, Dec 17, 2019 at 4:21 PM Gil Portenseigne < > gil.portensei...@nereide.fr> wrote: > >> Hello Gavin, >> >> Thanks for four feedback, that's very interesting ! >> >> Could you elaborate about the OFBiz forms usage you did in your angular >> implementation ? >> You just used view-map with simple screen/form ? >> >> Cheers ! >> >> Gil >> >> Le 12:26 - mardi 17 déc., Gavin Mabie a écrit : >> > Hi Taher >> > >> > I've been using Angular for custom apps over the past year. This is my >> > approach: >> > >> > 1. Ofbiz deployed as an AppServer: >> > 1.1 Ofbiz controller resolves API request calls; >> > 1.2 Ofbiz Service Engine executes Services; >> > 1.3 Entity Engine for persistence; >> > 1.4 Returns JSON object - including Ofbiz context;; >> > 1.5 Ofbiz Forms used for additional business logic, fields etc; >> > 1.6 Ofbiz Screens not used at all. >> > >> > 2. Angular (On Apache HTTPD or Ionic/Cordova/JQueryMobile or even >> > JAVAFX(haven't tried this, but it's possible)): >> > 2.1 Typescripting types for Ofbiz entities used; >> > 2.2 Angular services for API calls corresponding to controller >> > request-mappings; >> > 2.3 Dynamic Angular Forms - based on Ofbiz Form defs; >> > 2.4 Other static content; >> > >> > 3. Authentication >> > 3.1 With JWT; >> > 3.2 Sessionless & no cookies; >> > 3.3 Ofbiz LoginWorker & Permission Engine for authorization; >> > >> > The big takeaway here is that Ofbiz Screens aren't used at all. Ofbiz >> > Forms are used to set fields, execute services and deal with issues like >> > locale etc. >> > >> > Cheers >> > >> > gavin >> > >> > >> > >> > >> > On Sat, Dec 14, 2019 at 6:52 PM Taher Alkhateeb < >> slidingfilame...@gmail.com> >> > wrote: >> > >> > > Hello Gil, >> > > >> > > Great research on the subject, thank you for sharing. >> > > >> > > I could be wrong here, but at a first glance it seems you want to >> > > essentially create a tag "<update-area ..." which essentially renders >> > > another screen dynamically upon clicking / activating the URL. If my >> > > understanding is correct, then most likely they way you want to >> > > implement this is probably some web request to the backend which >> > > renders back a partial screen that you insert into the DOM right? >> > > >> > > If what I describe above is close to your idea, then I think the >> > > implementation might be relying on the server to do the work of >> > > painting instead of relying on the browser to do the heavy lifting. >> > > This also only works with one widget, which is the URL widget as >> > > opposed to having a general purpose dynamic behavior in the system >> > > (assuming this is desired). >> > > >> > > However, having a general purpose dynamic behavior means we need a >> > > method to bind variables to values at the front end without consulting >> > > the back-end. This reduces the load on the server and provides a >> > > faster / richer experience to the user. But it would be too painful to >> > > rely on plain javascript or jQuery to achieve such a result which is >> > > the reason why frameworks like React, Vue, and others emerged as >> > > modern SPA frameworks. Adopting an SPA framework would provide dynamic >> > > behavior (everywhere) instead of certain widgets, and it can be >> > > expanded to even include page navigation and whatnot. Of course this >> > > is more work than what you're suggesting here. but if we continue with >> > > such small improvements, you might end up having lots of javascript >> > > (maybe that's already the case) which might increase the difficulty of >> > > adopting an SPA framework in the future. >> > > >> > > So it comes down to this question (which I don't necessarily have an >> > > answer to): >> > > >> > > Do you want an SPA framework now or in the future, or do you want to >> > > continue with status quo into the future? If you want an SPA >> > > framework, then we should minimize the usage of custom javascript >> > > everywhere and adopt a framework that completely replaces all the >> > > javascript that currently exists for all the widgets. If not, then we >> > > can proceed with your proposition and any others in the future knowing >> > > that an overhaul is not needed. >> > > >> > > Cheers, >> > > >> > > Taher Alkhateeb >> > > >> > > On Fri, Dec 13, 2019 at 6:52 PM Gil Portenseigne >> > > <gil.portensei...@nereide.fr> wrote: >> > > > >> > > > Chapter One: How to manage the updating area >> > > > >> > > > Hello, >> > > > >> > > > After different discussions already listed by Taher [1-9], Leila, >> > > > Nicolas and me tried another approach. >> > > > Instead of analyzing how to implement different functionalities >> offered >> > > > by modern js framework, we did analyzed how end user use, in >> general, >> > > > OFBiz and where we, as an integrator, waste more time to create a >> > > > screen. >> > > > >> > > > To help on this huge task, we set some basic rules : >> > > > * Work only on screens supported by the theme, defined mainly >> in xml >> > > > * This concerns only screens used for back-office applications, >> > > > oriented to manage data >> > > > * A developer does not have to know all of js language (or >> other) >> > > > but can concentrate on the process/view with the end user to >> > > > manage a data >> > > > >> > > > >> > > > After a first brainstorm, we have identified three major cases : >> > > > 1. Navigation and data display >> > > > 2. View event result (data modification, calculation service, >> ...) >> > > > 3. Update an area to refresh data (after data modification) >> > > > >> > > > Case 1 and 2 are easy and currently managed by OFBiz (and missing >> stuff >> > > > will be simple to add), we concentrate our attention on case 3. >> > > > >> > > > To update an area, we follow this pattern >> > > > >> > > > 1. We start from a context that display different information >> > > > >> > > > 2. That context display a submit form, use a link or another >> > > > mechanism to call an OFBiz event >> > > > >> > > > 3. After receiving the event return, we refresh the desired area >> > > > with parameters that can come from origin context or from event >> > > > return. >> > > > >> > > > >> > > > Currently with the screen widget, we can use within a form the >> element >> > > > `<on-event-update-area event-type="submit" area-id="" >> area-target=""/>`. >> > > > >> > > > The problem with this use, is that your form needs to know the >> origin >> > > > context, to identify what are the areas to update and what are the >> > > > target to use for the refresh. >> > > > >> > > > So your form needs to know where it comes from, what information >> need to >> > > > be updated in OFBiz and what will be updated after. >> > > > >> > > > This increases complexity for the developer in the way that current >> form >> > > > implementation manages : >> > > > * the data and target to communicate with the server >> > > > * the behaviour (refreshment) to apply when receiving server >> response. >> > > > >> > > > Example : >> > > > <form name="EditPartyRoleCustomScreen" type="single" >> > > target="createPartyRole"> >> > > > <field name="partyId"><hidden/></field> >> > > > <field name="roleTypeId">... >> > > > <on-event-update-area event-type="submit" >> > > area-id="PartyRoles_area" >> > > > area-target="PartyRolesCustom"> >> > > > <parameter param-name="partyId" >> > > from-field="parameters.partyId"/> >> > > > </on-event-update-area> >> > > > </form> >> > > > >> > > > If you want to reuse the same form, you need to create another >> screen >> > > > with a new form to redefine the on-event-update-area (for instance >> > > > create a PartyRole). >> > > > >> > > > We change the thinking, because since it is the starting context >> that >> > > > better knows itself, it's the starting context that will realize the >> > > > updating operation. The starting context is the screen/menu that >> call >> > > > this form. >> > > > >> > > > In general a form is contained in a screen (classic) that is called >> > > > through a link. So we move the idea on this link : >> > > > >> > > > <link target="CreatePartyRole" >> link-type="layered-modal"> >> > > > <parameter param-name="partyId" >> > > from-field="customerParty.partyId"/> >> > > > <update-area area-target="ResumeInfoCustomer" >> > > area-id="xxx"> >> > > > <parameter param-name="partyId" >> > > from-field="customerParty.partyId"/> >> > > > </update-area> >> > > > </link> >> > > > >> > > > And the form : >> > > > >> > > > <form name="EditPartyRole" type="single" >> > > target="createPartyRole"> >> > > > <field name="partyId"><hidden/></field> >> > > > <field name="roleTypeId">... >> > > > </form> >> > > > >> > > > With this logic you can define a new usage of >> createPartyRole >> > > > without redefining a form just : >> > > > >> > > > <link target="CreatePartyRole" >> link-type="layered-modal"> >> > > > <parameter param-name="partyId" >> > > from-field="partyRelationship.partyIdTo"/> >> > > > <update-area area-target="MyRelationAndDetail" >> > > area-id="xxx"> >> > > > <parameter param-name="partyId" >> > > from-field="partyRelationship.partyIdTo"/> >> > > > <parameter param-name="partyRelationTypeId" >> > > value="IRL_LIKE"/> >> > > > </update-area> >> > > > </link> >> > > > >> > > > After some use we identified as pro and con feedback : >> > > > * updating form is reusable and contains only code related to >> the >> > > > form action >> > > > * link being in origin context, the developer knows where he is >> and >> > > > where he wants to go >> > > > * Menu oriented management offers a quick vision on how the >> screen >> > > will works >> > > > >> > > > * update-area seems to be a too technical name >> > > > * we always have to manage area to update manually >> > > > * too many areas to update become a headache and not only for >> the >> > > developer >> > > > >> > > > We did not explain how we have done it, to try to focus the >> discussion >> > > > on the principles. >> > > > >> > > > It would be a pleasure to have some criticism of this approach, and >> we >> > > > would try, in a second chapter to introduce other concepts that >> appeared >> > > > after the screens were made more dynamic and others to lowers the >> > > > identified cons. >> > > > >> > > > Thanks, >> > > > >> > > > The Néréide Team >> > > > >> > > > [1] https://s.apache.org/rf94 >> > > > [2] https://s.apache.org/g5zr >> > > > [3] https://s.apache.org/XpBO >> > > > [4] https://s.apache.org/YIL1 >> > > > [5] https://s.apache.org/836D >> > > > [6] https://s.apache.org/DhyB >> > > > [7] https://s.apache.org/Lv9E >> > > > [8] https://s.apache.org/zKIo >> > > > [9] https://s.apache.org/D6jx >> > > > >> > > >> >