Example 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:/***********************************/control/getForm";

  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://***************************"+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
>>> > > >
>>> > >
>>>
>>

Reply via email to