Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

470
LINES

< > BotCompany Repo | #1030726 // SimpleCRUD_v2 - create/read/update/delete for a concept class with simple fields [Swing]

JavaX fragment (include) [tags: use-pretranspiled]

Transpiled version (35238L) is out of date.

sclass SimpleCRUD_v2<A extends Concept> extends JConceptsTable<A> {
  JPanel buttons, panel;
  Set<S> unshownFields; // not shown in table or form
  Set<S> excludeFieldsFromEditing;
  S modifiedField; // field to hold last-modified timestamp
  TableSearcher tableSearcher;
  Set<S> multiLineFields; // string fields that should be shown as text areas
  Set<S> dontDuplicateFields;
  int formFixer = 12; // stupid value to make submit button appear
  bool showBackRefs;
  int maxRefsToShow = 3;
  bool showClassNameSelectors;
  bool allowNewFields;
  int newFieldsToShow = 3;
  bool emptyStringsToNull; // convert all empty string values to nulls (also means dropping the field if it's dynamic)
  int formLabelsWidth = 100; // just another bug fixer
  settable bool showSearchBar = true;
  settable bool showAddButton = true;
  settable bool showEditButton = true;
  settable bool showDeleteButton = true;
  settable bool showDuplicateButton = true;
  settable bool iconButtons;
  settable bool editWindowScrollable;
  settable bool scrollableButtons; // if you have a lot of them
  
  // put a component in here to fill the space left of the buttons
  SingleComponentPanel scpButtonFiller;
  
  JComponent wrappedButtonBar;
  
  // After user has submitted edit/create window
  event conceptSaved(A concept);

  *(Class<A> conceptClass) { super(conceptClass); }
  *(Concepts concepts, Class<A> conceptClass) { super(concepts, conceptClass); }
  
  SimpleCRUD_v2<A> show(S frameTitle) {
    make();
    showFrame(frameTitle, panel);
    this;
  }
  
  SimpleCRUD_v2<A> show() {
    ret show(entityNamePlural());
  }
  
  SimpleCRUD_v2<A> showMaximized() { show(); maximizeFrame(panel); ret this; }
  
  JPanel makePanel() { ret make(); }
  JPanel make() {
    db();
    framesBot();
    ret make_dontStartBots();
  }
  
  swappable MapSO itemToMap_inner(A a) {
    ret super.itemToMap_base(a);
  }
  
  MapSO itemToMap_base(A a) {
    MapSO map = itemToMap_inner(a);
    if (map == null) null;
    ret putAll(map, moreSpecialFieldsForItem(a));
  }
  
  // shown on the right (usually)
  swappable MapSO moreSpecialFieldsForItem(A a) {
    MapSO map = litorderedmap();
    if (showBackRefs) {
      Cl<Concept> refs = allBackRefs(a);
      if (nempty(refs)) {
        refs = sortedByConceptID(refs);
        int more = l(refs)-maxRefsToShow;
        map.put("Referenced by",
          joinWithComma(takeFirst(maxRefsToShow, refs))
        + (more > 0 ? ", " + more + " more" : ""));
      }
    }
    ret map;
  }
  
  public JPanel make_dontStartBots() { ret (JPanel) visualize(); }
  
  cachedVisualize {
    dropFields = asList(unshownFields);
    makeTable();
    swing {
      JButton deleteButton = !showDeleteButton ? null
        : tableDependButton(table, makeDeleteButton(r {
          L<A> l = selectedConcepts();
          withDBLock(concepts, r { for (A c : l) c.delete() });
        }));

      buttons = jRightAlignedLine(flattenToList(
        !showAddButton ? null : makeAddButton(r newConcept),
        !showEditButton || !showDuplicateButton ? null : tableDependButton(table, makeDuplicateButton(r {
          duplicateConcept(selectedConcept())
        })),
        !showEditButton ? null : tableDependButton(table, makeEditButton(r {
          editConcept(selectedConcept())
        })),
        deleteButton,
      ));
      
      wrappedButtonBar = wrapButtonBar(centerAndEastWithMargin(scpButtonFiller = singleComponentPanel(), buttons));
      
      if (showSearchBar) {
        tableSearcher = tableWithSearcher2(table, withMargin := true);
        panel = centerAndSouthWithMargin(tableSearcher.panel, wrappedButtonBar);
      } else
        panel = centerAndSouthWithMargin(table, wrappedButtonBar);
      
      var fEdit = voidfunc(int row) {
        editConcept(getItem(row))
      };
      tablePopupMenuItem(table, "Edit", fEdit);
      onDoubleClick(table, fEdit);
      tablePopupMenuFirst(table, (menu, row) -> {
        Concept c = getItem(row);
        if (c != null)
          addMenuItem(menu, "Delete " + quote(shorten(str(c))), rThread {
            deleteConcept(c);
          });
      });
    } // end of swing
    ret panel;
  }
  
  swappable JComponent wrapButtonBar(JComponent buttonBar) {
    if (scrollableButtons)
      ret jBorderlessHigherHorizontalScrollPane(vstack(buttonBar));
    ret buttonBar;
  }    

  swappable void newConcept() {
    duplicateConcept(null);
  }
  
  swappable A createDuplicate(A c) {
    A clone = (A) unlisted(_getClass(c));
    ccopyFieldsExcept(c, clone, dontDuplicateFields);
    ret clone;
  }
    
  void duplicateConcept(A oldConcept) {
    new EditWindow ew;
    if (oldConcept == null)
      ew.item = unlisted(conceptClass);
    else
      ew.item = createDuplicate(oldConcept);

    csetAll(ew.item, filters);
    makeComponents(ew);
    
    // save action
    F0<Bool> r = func -> bool {
      try {
        selectAfterUpdate(ew.item);
        concepts.register(ew.item);
        saveData(ew);
      } catch print e {
        infoBox(e);
        false;
      }
      true;
    };
    temp tempSetMCOpt(formLayouter1_fixer2 := formFixer);
    S title = "New " + entityName();
    title += appendBracketed(javaClassDescForItem(ew.item));
    
    renameSubmitButton("Create", showAForm(title,
      toObjectArray(listPlus(ew.matrix, r))));
  }

  void editConcept(A c) {
    if (c == null) ret;
    new EditWindow ew;
    ew.item = c;
    makeComponents(ew);
    F0<Bool> r = func -> bool {
      try {
        // concept class was changed, replace object
        if (ew.item != c) {
          print("Replacing object: " + c + " => " + ew.item);
          replaceConceptAndUpdateRefs(c, ew.item);
        }
        saveData(ew);
      } catch print e {
        infoBox(e);
        false;
      }
      true;
    };
    temp tempSetMCOpt(formLayouter1_fixer2 := formFixer);
    renameSubmitButton("Save", showAForm("Edit " + entityName() + " #" + c.id,
      toObjectArray(listPlus(ew.matrix, r))));
  }
  
  JComponent fieldComponent(A c, S field) {
    Class type = getFieldType(c.getClass(), field);
    Type genericType = genericFieldType(conceptClass, field);
    if (type == Concept.Ref.class)
      type = getTypeArgumentAsClass(genericType);
    
    O value = cget(c, field);
    if (type == null) type = _getClass(value);
    //print("Field type: " + field + " => " + type);
    if (type == bool.class)
      ret jCenteredCheckBox(isTrue(value));
    else if (contains(multiLineFields, field) || containsNewLines(optCast S(value)))
      ret makeTextArea((S) value);
    else if (isSubtype(type, Concept))
      ret jcomboboxFromConcepts_str(concepts, type, (Concept) value);
    ifclass SecretValue
    else if (type == SecretValue.class)
      ret jpassword(strOrEmpty(getVar(value/SecretValue)));
    endif
    else if (isUneditableFieldType(type))
      ret jlabel(structureOrText_crud(value));
    else try {
      ret autoComboBox(structureOrText_crud(value), new TreeSet<S>(map structureOrText_crud(collect(list(concepts, conceptClass), field))));
    } catch e {
      printException(e);
      ret jTextField(structureOrText_crud(value));
    }
  }
  
  void saveComponent(A c, S field, JComponent comp) {
    comp = unwrap(comp);
    ifdef SimpleCRUD_v2_debug
      printVars_str("saveComponent", +field, +comp);
    endifdef
    
    Class type = fieldType(c, field);
    Type genericType = genericFieldType(conceptClass, field);
    if (type == Concept.Ref.class)
      type = getTypeArgumentAsClass(genericType);
      
    ifclass SecretValue
      if (type == SecretValue.class) {
        S text = getTextTrim((JPasswordField) comp);
        cset(c, field, empty(text) ? null : SecretValue(text));
      } else
    endif
    if (comp instanceof JTextComponent) {
      S text = trimIf(!comp instanceof JTextArea, getText((JTextComponent) comp));
      O value = postProcessValue(text);
      O converted = convertToField(value, c.getClass(), field);
      ifdef SimpleCRUD_v2_debug
        printVars_str("saveComponent", +field, +text, +value, +converted);
      endifdef
      cset(c, field, converted);
    }
    else if (comp cast JComboBox) {
      S text = getTextTrim(comp);
      if (isSubtype(type, Concept))
        cset(c, field, getConcept(concepts, parseFirstLong(text)));
      else {
        O value = postProcessValue(text);
        cset(c, field, convertToField(value, c.getClass(), field));
      }
    } else if (comp instanceof JCheckBox)
      cset(c, field, isChecked((JCheckBox) comp));
    ifclass ImageChooser
      else if (comp instanceof ImageChooser)
        cUpdatePNGFile(c, field, comp/ImageChooser.getImage(), false);
    endif
  }
  
  swappable Cl<S> editableFieldsForItem(A c) {
    if (excludeFieldsFromEditing != null && modifiedField != null) excludeFieldsFromEditing.add(modifiedField);
    ret listWithoutSet(
      conceptFieldsInOrder(c),
      joinSets(excludeFieldsFromEditing, unshownFields, keys(filters)));
  }
  
  void excludeFieldsFromEditing(S... fields) {
    excludeFieldsFromEditing = setPlus(excludeFieldsFromEditing, fields);
  }
  
  
  Cl<Class<? extends A>> possibleClasses() {
    ret (Cl) moveItemFirst(conceptClass, dropTypeParameter(sortClassesByNameIC(myNonAbstractClassesImplementing(conceptClass))));
  }
  
  JComboBox<Class<? extends A>> classSelectorComponent(A c) {
    ret setComboBoxRenderer(jTypedComboBox(possibleClasses(), _getClass(c)),
      customToStringListCellRenderer shortClassName());
  }
  
  class NewField {
    JTextField tfName = jtextfield();
    JTextField tfValue = jtextfield();
    JComboBox cbRef = jcomboboxFromConcepts_str(concepts, conceptClass);
    JComboBox cbType = jcombobox("String", "Reference");
    SingleComponentPanel scpValue = singleComponentPanel();
    
    JPanel panel() {
      onChangeAndNow(cbType, r updateSCP);
      ret jhgridWithSpacing(
        withToolTip("Name for new field", withLabel("Name", tfName)),
        withLabel("Value", westAndCenterWithMargin(cbType, scpValue)));
    }
    
    S typeStr() { ret getText(cbType); }
    
    void updateSCP {
      scpValue.setComponent(eqic(typeStr(), "Reference")
        ? cbRef
        : withToolTip("Contents of new field", tfValue));
    }
    
    S field() { ret gtt(tfName); }
    O value() {
      ret eqic(typeStr(), "Reference")
        ? getConcept(concepts, parseFirstLong(getText(cbRef)))
        : gtt(tfValue);
    }
  }
  
  class EditWindow {
    A item;
    Map<S, JComponent> componentsByField = litorderedmap();
    new L matrix; // label, component, label, component, ...
    JComboBox<Class<? extends A>> classSelector;
    new L<NewField> newFields;
  }
  
  // override the following two methods to customize edit window
  
  void makeComponents(EditWindow ew) {
    // class selector
    
    if (showClassNameSelectors) {
      addAll(ew.matrix, makeLabel("Java Class"), ew.classSelector = classSelectorComponent(ew.item));
      onChange(ew.classSelector, r {
        Class<? extends A> oldClass = _getClass(ew.item);
        Class<? extends A> newClass = getSelectedItem_typed(ew.classSelector);
        if (oldClass == newClass) ret;
        A oldItem = ew.item;
        ew.item = unlisted(newClass);
        ccopyFields(oldItem, ew.item);
      });
    }
      
    // regular fields
    
    for (S field : editableFieldsForItem(ew.item)) {
      JComponent c = fieldComponent(ew.item, field);
      ew.componentsByField.put(field, c);
      addAll(ew.matrix, makeLabel(field), c);
    }
      
    // new fields
    
    if (allowNewFields && newFieldsToShow > 0) {
      addAll(ew.matrix, " ", jlabel()); // spacing
      for i to newFieldsToShow: {
        new NewField nf;
        ew.newFields.add(nf);
        addAll(ew.matrix, makeLabel(""/*"New field"*/), nf.panel());
      }
    }
  }
  
  void saveData(EditWindow ew) {
    // save regular fields
    
    for (S field, JComponent component : ew.componentsByField)
      if (isIdentifier(field))
        saveComponent(ew.item, field, component);
        
    // save new fields
    
    for (NewField nf : ew.newFields) {
      S field = nf.field();
      O value = nf.value();
      if (nempty(field) && notNullOrEmptyString(value))
        cset(ew.item, field, value);
    }
        
    if (modifiedField != null) cset(ew.item, modifiedField, now());
    
    conceptSaved(ew.item);
  }
  
  O postProcessValue(O o) {
    if (emptyStringsToNull && eq(o, "")) null;
    ret o;
  }
  
  // labels on left hand side of form
  JComponent makeLabel(S label) {
    ret jMinWidthAtLeast(formLabelsWidth, jlabel(label));
  }
  
  swappable JComponent showAForm(S title, O... parts) {
    JForm form = JForm(parts).disposeFrameOnSubmit();
    var panel = form.visualize();
    showFrame(title, editWindowScrollable ? jscroll_vertical(panel) : panel);
    ret panel;
  }
  
  bool isUneditableFieldType(Class type) {
    ret isSubclassOfAny(type, Map, Cl, Pair);
  }
  
  void hideField(S field) { hideFields(field); }
  void hideFields(S... fields) {
    unshownFields = createOrAddToSet(unshownFields, fields);
  }
  
  selfType entityName(S name) { entityName = if0_const(name); this; }
  
  swappable S entityName() { ret shortClassName(conceptClass); }
  swappable S entityNamePlural() { ret plural(entityName()); }
  swappable S entityNameLower() { ret firstToLower(entityName()); }
  swappable S entityNamePluralLower() { ret firstToLower(entityNamePlural()); }
  
  JPanel buttons() { visualize(); ret buttons; }
  
  void addButtonInFront(S text, Runnable r) {
    addButtonInFront(jbutton(text, r));
  }
  
  void addButton(S text, Runnable r) {
    addButton(jbutton(text, r));
  }
  
  void addButton(JComponent c) {
    buttons().add(c);
  }
  
  void addButtonInFront(JComponent c) {
    addComponentInFront(buttons(), c);
  }
  
  JButton makeAddButton(Runnable r) { ret makeButton(
    "Add...", "Create a new " + entityNameLower(), #1103069, r); }
  JButton makeDeleteButton(Runnable r) { ret makeButton(
    "Delete", "Delete selected " + entityNamePluralLower(), #1103067, r); }
  JButton makeEditButton(Runnable r) { ret makeButton(
    "Edit...", "Edit selected " + entityNameLower(), #1103068, r); }
  JButton makeDuplicateButton(Runnable r) { ret makeButton(
    "Duplicate...", "Duplicate selected " + entityNameLower(), #1103070, r); }
  
  JButton makeButton(S text, S toolTip default null, S iconID, Runnable r) {
    if (iconButtons)
      ret toolTip(or2(toolTip, text), jimageButtonScaledToWidth(16, iconID, text, r));
    else
      ret toolTip(toolTip, jbutton(text, r));
  }
  
  void multiLineField(S field) {
    multiLineFields = addOrCreate(multiLineFields, field);
  }
  
  swappable JComponent makeTextArea(S text) {
    ret typeWriterTextArea(text);
  }
  
  void setButtonFiller(JComponent c) {
    scpButtonFiller.set(c);
  }
  
  TableSearcher tableSearcher() {
    visualize();
    ret tableSearcher;
  }
} // end of SimpleCRUD_v2

Author comment

Began life as a copy of #1006540

download  show line numbers  debug dex  old transpilations   

Travelled to 5 computer(s): bhatertpkbcr, ekrmjmnbrukm, mqqgnosmbjvj, pyentgdyhuwx, vouqrxazstgt

No comments. add comment

Snippet ID: #1030726
Snippet name: SimpleCRUD_v2 - create/read/update/delete for a concept class with simple fields [Swing]
Eternal ID of this version: #1030726/134
Text MD5: 6b0e0bbefd12dcfc2bd0c6b01ade872e
Author: stefan
Category: javax / concepts / gui
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2023-10-15 13:29:11
Source code size: 15416 bytes / 470 lines
Pitched / IR pitched: No / No
Views / Downloads: 579 / 1474
Version history: 133 change(s)
Referenced in: [show references]