Example of Angular-Ofbiz formField Service import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { of as observableOf,Observable } from 'rxjs'; import { OfbizForm, OfbizFormFieldsBase, OfbizTexboxField, OfbizDisplayField, OfbizDateTimeField, OfbizDropdownField, OfbizHiddenField } from '../Types/ofbiz-forms'; import { tap, map } from 'rxjs/operators'; import { FormControl, Validators, FormGroup } from '@angular/forms';
@Injectable({ providedIn: 'root' }) export class OfbizFormService { baseUrl:string = "https://**********"; constructor(private http: HttpClient) { } getForm(options:any):Observable<any>{ console.log(options); return this.http.get<OfbizForm>(this.baseUrl,options) .pipe( tap(() => console.log('HTTP request executed')), map(resp => resp) ); } toFormGroup(formFields: OfbizFormFieldsBase<any>[]){ let group:any = {}; formFields.forEach(formField => { group[formField.name] = formField.required ? new FormControl(formField.value || '',Validators.required) : new FormControl(formField.value || ''); }); return new FormGroup(group); } getFormFields(rawFields:any[]):Observable<any>{ let fields:OfbizFormFieldsBase<any>[]=[]; rawFields.forEach((field,i) => { let currentValue = null; if(field.value){ currentValue = field.value; }else{ currentValue = field.currentValue; } switch(field.type){ case "hidden": console.log("This is a display"); let hiddenField = new OfbizHiddenField({ key:field.id, label:field.title, value:field.value, name:field.name, order:i, required:field.required, }); fields.push(hiddenField); break; case "display": console.log("This is a display"); let displayfield = new OfbizDisplayField({ key:field.id, label:field.title, value:field.value, name:field.name, order:i, required:field.required, }); fields.push(displayfield); break; case "text": console.log("This is a input"); let textBoxField = new OfbizTexboxField({ key:field.id, label:field.title, value:field.value, name:field.name, order:i, required:field.required, }); fields.push(textBoxField); break; case 'datetime': console.log("This is a input"); let datetimeField = new OfbizDateTimeField({ key:field.id, label:field.title, value:field.value, name:field.name, order:i, required:field.required, }); fields.push(datetimeField); break; case "dropdown": let dropDownField = new OfbizDropdownField({ key:field.id, label:field.title, value:field.value, name:field.name, order:i, required:field.required, options:this.convertOptions(field.allOptionValues), }); fields.push(dropDownField); break; } }); fields.sort((a, b) => a.order - b.order); return observableOf(fields); } convertOptions(allOptionValues:any[]){ let options = []; allOptionValues.forEach(allOptionValue => { let entry = {}; entry['value'] = allOptionValue.key; entry['key'] = allOptionValue.description; options.push(entry); }); return options; } submitForm(form:any,target:string):Observable<any>{ console.log(target); let url:string = "https://api-homeinit.edupen.co.za/homeinit/control/ "+target; const httpOptions = { headers:new HttpHeaders({ 'Content-Type':'application/json' }) } return this.http.post(url,form,httpOptions) .pipe( map(resp =>resp) ); } } Gavin On Tue, Dec 17, 2019 at 5:18 PM Gavin Mabie <kwikst...@gmail.com> wrote: > 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 >>> > > > >>> > > >>> >>