Giter VIP home page Giter VIP logo

webforj's Introduction

webforJ

Quality Gate Status OpenSSF Best Practices Tests Publish Maven Central

A robust and flexible framework that can help you deliver a modern and engaging web user interface with ease. In Java.

  • Event Handling: Handle user interactions and events with ease using the webforJ's event system. Respond to user actions and update the UI accordingly.

  • Component-Based: Create reusable and composable components to build complex UIs. Components encapsulate their own state and logic, providing a modular and maintainable structure for your application.

  • Reliable: Our team in the United States and Europe have been building tools and technologies to help our clients fulfil their needs for over 35 years. We have consistently produced reliable and innovative technologies to ensure our clients have every tool in their kit needed to tackle modern, ever-changing needs.

Documentation

The webforJ's documentation site can be found at this link which contains guides, API references, and examples to help you get started with the webforJ.

The following documentation sections may be useful for those beginning their usage of the DWCJ:

Examples

The webforJ's HelloWorld repository contains a sample program which can be run in GitHub codespaces, Docker, or locally and demonstrates the basics for creating your first DWCJ program. Here is the class created in the sample:

@InlineStyleSheet(/* css */"""
  .mainFrame {
    display: inline-grid;
    gap: 20px;
    margin: 20px;
    padding: 20px;
    border: 1px dashed;
    border-radius: 10px;
  }
""")
@AppTitle("webforJ Hello World")
public class WebforjHelloWorld extends App {
  
  Paragraph hello = new Paragraph("Hello World!");
  Button btn = new Button("Say Hello");

  @Override
  public void run() throws WebforjException {
    Frame mainFrame = new Frame();
    mainFrame.addClassName("mainFrame");

    btn.setTheme(ButtonTheme.PRIMARY)
        .addClickListener(e -> showMessageDialog("Hello World!", "webforJ Message"));

    mainFrame.add(hello, btn);
  }
}

The above program creates some static text and a button which displays a message box when pushed.

Contributing

Contributions to the webforJ project are welcome! If you would like to contribute, please follow the guidelines outlined in the CONTRIBUTING.md file.

License

webforJ is licensed under the MIT License.

webforj's People

Contributors

dependabot[bot] avatar ehandtke avatar github-actions[bot] avatar hyyan avatar kevinhagel avatar khmarbaise avatar matthewhawkins avatar mhawkinsbasis avatar oskan1 avatar prodinrvb avatar stephanwald avatar timongeisbauer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

webforj's Issues

AbstractDwcjPanel::add & Div::add should allow adding more than one control at the same time

For ease of use. AbstractDwcjPanel::add & Div::add should allow adding more than control at the same time

public class Sample extends App {

    @Override
    public void run() throws DwcAppInitializeException {

        AppPanel panel = new AppPanel();

        Button open = new Button("Button 1");
        open.onClick((e) -> {
            // do something
        });

        Button close = new Button("Button 2");
        close.onClick((e) -> {
            // do something
        });

        // add all at once
        panel.add(open, close);
    }
}

[BUG] ListBox why need scroll methods?

DWCjListBox Control has ScrollWheelBehavior() methods, which I think doesn't make sense, because I can add items to my ListBox and my List grows and I can see that, I don't have any option to open or close a List, since all items are directly visible.

so Why do we need scroll methods?

Event sinks @SuppressWarnings({"static-access"})

All sink classes uses the @SuppressWarnings({"static-access"}) annotation to get rid of the compiler warning :

The static field EVENT_NAME should be accessed in a static way

To get around this, Get the event name from com.basis.bbj.proxyif.SysGuiEventConstants

honor visibility and enabled status upon creation

BBj Controls allow to be created initially invisible and/or disabled. See

https://documentation.basis.cloud/BASISHelp/WebHelp/bbjobjects/Window/bbjwindow/bbjwindow_addbutton.htm

The various controls contain a "todo" like for the Button:
https://github.com/DwcJava/engine/blob/f2f310dd6af777e20e30b1e671187c8285ebc8b7/src/main/java/org/dwcj/controls/Button.java#L41-L42

We should default the two variables enabled and visible to "true" rather than "null" and then create the flags accordingly.

https://github.com/DwcJava/engine/blob/f2f310dd6af777e20e30b1e671187c8285ebc8b7/src/main/java/org/dwcj/controls/AbstractDwcControl.java#L29-L30

Remember to fix the "catch up" which will not need to catch up visible and enabled anymore once the control is immediately created as needed (if not "true")

handle RuntimeExceptions in dwcj.bbj

dwcj should receive some proper mechanics to display useful error messages. We will also want to introduce a DEBUG flag in the config that results in more verbose development-time information in the browser, like stack traces etc.

Progress Bar Orientation

Priority Low

Progress bars seem to have constants HORIZONTAL and VERTICAL in their BBj implementation. Don't see their equivalents in the DWCJ version. Should these be added so that they can be used in the get/set orientation functions?

Get: https://documentation.basis.cloud/BASISHelp/WebHelp/bbjobjects/Window/bbjprogressbar/bbjprogressbar_getorientation.htm
Set: https://documentation.basis.cloud/BASISHelp/WebHelp/bbjobjects/Window/bbjprogressbar/bbjprogressbar_setorientation.htm

For compliably with standards change the event listeners naming convention

Currently the naming conversion of an event listener is on plus the name of the event. For instance onClick. Change the naming convention to add plus the naming of event and Listener. for instance addClickListener.
For removing a listener remove plus the name of the event and Listener for instance removeClickListener

Add selected tracking for list controls

Current implementation of list controls does not track which item(s) are selected unless the control has been added to a window. Need to implement tracking of this before the control is added for testing purposes.

ComboBox missing methods

ComboBox class allows me to use methods like "getItemAt".

However, me as a User would then expect to work with indices, therefore methods like "addItemAt", "setItemAt". "removeItemAt", etc should also be implemented.

[BUG] DWCjNumericBoxSpinner wrong return type Object

DWCjNumericBoxSpinner Control all "setMethods" return a NumericBox Object and not NumericBoxSpinner Object.

same for the events: there is NumericBoxEditModifyEvent, yet not NumericBoxSpinnerEditModifyEvent

NumericBoxSpinner missing event...

The NumericBoxSpinner Control has no events implemented. When I use the method onEditModify(), I can't pass as a Parameter the "NumericBoxSpinnerEditModifyEvent", because it doesn't exist. I can only pass the NumericBoxEditModifyEvent, which of course it doesn't work on my NumericBoxSpinner Control (as it suppose to be).

add `Control::isAttached` method

In the Control interface add the isAttached method. The method should return true if the control is attached to a panel, false otherwise.

`CtrlAccessorImpl::create` does not traverse the control parent classes

CtrlAccessorImpl::create should search the control parent classes to check if the create method is defined.

Something like this should work:

    @Override
    @SuppressWarnings("java:S3011") // allow increasing acessibility
    public void create(AbstractControl ctrl, AbstractDwcjPanel panel) throws IllegalAccessException {

        StackTraceElement[] stack = Thread.currentThread().getStackTrace();
        String caller = stack[2].getClassName();

        if (caller.startsWith("org.dwcj.")) {
            try {
                boolean found = false;
                Class<?> clazz = ctrl.getClass();
                while (clazz != null && !found) {
                    Method[] methods = clazz.getDeclaredMethods();
                    for (Method method : methods) {
                        if (method.getName().equals("create") &&
                                method.getParameterCount() == 1 &&
                                method.getParameterTypes()[0]
                                        .equals(Class.forName("org.dwcj.controls.panels.AbstractDwcjPanel"))) {
                            method.setAccessible(true);
                            method.invoke(ctrl, panel);
                            found = true;
                            break;
                        }
                    }

                    if (!found)
                        clazz = clazz.getSuperclass();
                }
            } catch (Exception e) {
                Environment.logError(e);
            }
            return;
        }

        App.consoleLog(caller + YOU_RE_NOT_ALLOWED_TO_ACCESS_THIS_METHOD);
        throw new IllegalAccessException(caller + YOU_RE_NOT_ALLOWED_TO_ACCESS_THIS_METHOD);
    }

Div setStyle and setClassName calls are lost before the div is attached

import org.dwcj.App;
import org.dwcj.controls.label.Label;
import org.dwcj.controls.panels.AppPanel;
import org.dwcj.controls.panels.Div;
import org.dwcj.exceptions.DwcException;


public class Playground extends App {

  @Override
  public void run() throws DwcException {
    AppPanel panel = new AppPanel();
    panel.addClassName("bbj-app-layout-sample");

    Div c = new Div();
    c.addClassName("bbj-header");
    c.setAttribute("test","test");
    c.setStyle("color","red");
    c.add(new Label("test"));
    panel.add(c);

  }
}

Use Java Generics to avoid method overriding in controls

Currently, in each control we have to override a bunch of methods inherited from AbstractControl and AbstractDwcControl to make methods chaining work without asking the developer to cast.

    @Override
    public Button setText(String text) {
        super.setText(text);
        return this;
    }

    @Override
    public Button setVisible(Boolean visible){
        super.setVisible(visible);
        return this;
    }

   ....

This results in a lot of duplications that probably we don't need to maintain. We should use java generics to avoid this.

public interface Control<T> {

    public T setId(String id);

    public String getId();
}
abstract public class AbstractControl<T> implements Control<T> {

    private String id;

    @Override
    public T setId(String id) {
        this.id = id;
        
        // this should be safe
        @SuppressWarnings("unchecked")
        T self = (T) this;

        return self;
    }

    public String getId() {
        return id;
    }
}
public class AbstractDwcControl<T> extends AbstractControl<T> {

    private HashMap<String, String> attributes = new HashMap<String, String>();

    public T setAttribute(String name, String value) {
        attributes.put(name, value);

        // this should be safe
        @SuppressWarnings("unchecked")
        T self = (T) this;

        return self;
    }

    public String getAttribute(String name) {
        return attributes.get(name);
    }
}
public final class Button extends AbstractDwcControl<Button> {

    private String text;

    public Button setText(String text) {
        this.text = text;
        return this;
    }

    public String getText() {
        return text;
    }
}
public class App {
    public static void main(String[] args) {
        Button button = new Button();
        button.setId("button1").setText("Text").setAttribute("name", "attribute1");

        System.out.println(button.getId());
        System.out.println(button.getText());
        System.out.println(button.getAttribute("name"));
    }
}

Initializing a DWCj-Control as a field (class variable) is not working properly

I need to initialize a DWCj-Control inside my run() function in order to work with it.

If I initialize it as a field, then adding it to the AppPanel won't work (but no error will be thrown, so user cannot understand what is going on) (1)

If I use the (1) and also write an Event Method e.g. (onClick, onChange - ButtonPushEvent, etc.) then I get a "cannot launch app" error.

package dwcjsample;

import org.dwcj.App;
import org.dwcj.controls.Button;
import org.dwcj.events.ButtonPushEvent;
import org.dwcj.exceptions.DwcAppInitializeException;
import org.dwcj.panels.AppPanel;

public class ControlFieldInitializeErrorSample extends App {

    private Button button = new Button("hey");

    @Override
    public void run() throws DwcAppInitializeException {
        AppPanel appPanel = new AppPanel();
        appPanel.add(button);

        button.onClick(this::onButtonClick);
        new SampleAppPanel();
    }
    private void onButtonClick(ButtonPushEvent buttonPushEvent){
        button.setText("clicked");
    }
}

the code above doesn't work. But if I use the one below.

package dwcjsample;

import org.dwcj.controls.Button;
import org.dwcj.exceptions.DwcAppInitializeException;
import org.dwcj.panels.AppPanel;

public class StephanMethod extends AppPanel{

    private Button myButton = new Button("myTdddext");

    public StephanMethod() throws DwcAppInitializeException {
        super();
        add(myButton);
    }
}

then add it on the following class:

package dwcjsample;

import org.dwcj.App;
import org.dwcj.exceptions.DwcAppInitializeException;

public class MyClass extends App {
    @Override
    public void run() throws DwcAppInitializeException {
        new StephanMethod();
    }
}

then everything compiles as it suppose to be.

`HtmlContainer::executeAsyncScript` kills the client if the control which uses the HtmlContainer is not yet attached to a panel.

This is more of a question than a bug report.

How to use the HtmlContainer to build a custom control?

If we were able to use the BBjHTMLView directly then it would be up to the custom control to queue the executed scripts and wait until the BBjHTMLView is created then execute them in the client but this is not possible outside the dwcj package.

In case of HtmlContainer::executeAsyncScript, probably it easy to implement this logic in HtmlContainer , currently only the last executed script is re-applied in catchup
In case of HtmlContainer::executeScript this can be an issue because a returned value is expected from the client. Adding the scripts to queue and returning null does not seem to be correct.

Should we throw an exception in case the BBjHTMLView is not created and force the developer to add the custom control as early as possible? What would be the best approach here ? or I'm missing something?

The debug log shows the following:

[23:28:58.853+0100] SYSERR  com.basis.rmi.RMICommException: Exception caught
[23:28:58.854+0100] SYSERR  	at com.basis.bbj.client.rmi.BBjRMIConnection.sendSyncMessage(BBjRMIConnection.java:4)
[23:28:58.854+0100] SYSERR  	at com.basis.bbj.client.rmi.BBjRMIConnection.sendSyncCommand(BBjRMIConnection.java:30)
[23:28:58.854+0100] SYSERR  	at com.basis.bbj.rmi.ServerRMIConnection.access$101(ServerRMIConnection.java:80)
[23:28:58.854+0100] SYSERR  	at com.basis.bbj.rmi.ServerRMIConnection$SyncCallStrategy.doCall(ServerRMIConnection.java:1)
[23:28:58.854+0100] SYSERR  	at com.basis.bbj.rmi.OnDemandBatchStrategy.syncCall(OnDemandBatchStrategy.java:52)
[23:28:58.854+0100] SYSERR  	at com.basis.bbj.rmi.OnDemandBatchStrategy.sendSyncCommand(OnDemandBatchStrategy.java:77)
[23:28:58.854+0100] SYSERR  	at com.basis.bbj.rmi.ServerRMIConnection.sendSyncCommand(ServerRMIConnection.java:24)
[23:28:58.854+0100] SYSERR  	at com.basis.rmi.RMIStub.sendRequestCommand(RMIStub.java:276)
[23:28:58.854+0100] SYSERR  	at com.basis.rmi.StubMethodInvoker.request(StubMethodInvoker.java:43)
[23:28:58.854+0100] SYSERR  	at com.basis.rmi.InstanceProxy.request(InstanceProxy.java:69)
[23:28:58.854+0100] SYSERR  	at com.basis.rmi.shellgen.com_basis_bbj_web_app_server_console_MiniConsoleBridgeShell.terminate(Unknown Source)
[23:28:58.854+0100] SYSERR  	at com.basis.bbj.console.MiniConsole.terminate(MiniConsole.java:117)
[23:28:58.854+0100] SYSERR  	at com.basis.bbj.channel.ChannelZero.closeChannelAndConsole(ChannelZero.java:23)
[23:28:58.854+0100] SYSERR  	at com.basis.bbj.processor.j.a(j.java:522)
[23:28:58.854+0100] SYSERR  	at com.basis.bbj.processor.j.a(j.java:286)
[23:28:58.854+0100] SYSERR  	at com.basis.bbj.processor.instruction.c.bw.a(bw.java:1563)
[23:28:58.854+0100] SYSERR  	at com.basis.bbj.processor.instruction.c.bw.d(bw.java:2446)
[23:28:58.854+0100] SYSERR  	at com.basis.bbj.processor.instruction.c.bw.run(bw.java:829)
[23:28:58.854+0100] SYSERR  	at com.basis.util.common.BasisThread.run(BasisThread.java:75)
[23:28:58.854+0100] SYSERR  Caused by: com.basis.startup.type.BBjException: Channel has been terminated
[23:28:58.854+0100] SYSERR  	at com.basis.bbj.comm.s.handleInputTerminatedWaitForMessage(s.java:1)
[23:28:58.854+0100] SYSERR  	at com.basis.bbj.client.comm.StubAgent.waitForSynchMessage(StubAgent.java:7)
[23:28:58.854+0100] SYSERR  	at com.basis.bbj.client.comm.StubAgent.sendMessageToImplementation(StubAgent.java:12)
[23:28:58.854+0100] SYSERR  	at com.basis.bbj.client.rmi.BBjRMIConnection.sendSyncMessage(BBjRMIConnection.java:22)
[23:28:58.854+0100] SYSERR  	... 18 more
[23:28:58.854+0100] SYSERR  Socket already closed trying to cleanly terminate
[23:28:58.854+0100] INFO    ThinClient:/0.0.0.0:2003 [/127.0.0.1:60488] client terminated
[23:28:58.870+0100] INFO    ThinClient:/0.0.0.0:2003 [/127.0.0.1:63584] socket connection (1 total connections)
[23:28:58.886+0100] INFO    ThinClient:/0.0.0.0:2003 [/127.0.0.1:63584] User 'nobody' successfully logged in.
[23:28:59.046+0100] INFO    DWC components=https://s3-eu-west-1.amazonaws.com/cdn.bbx.kitchen/next
[23:36:26.697+0100] SYSOUT  Memory: [free=236735592,total=544210944,max=4169138176,used=307475352,timestamp=2023-01-03 23:36:26]
[23:51:26.705+0100] SYSOUT  Memory: [free=229105808,total=544210944,max=4169138176,used=315105136,timestamp=2023-01-03 23:51:26]

The following code can be tested in the annotation branch:

/* resources/css/style.css */
.app-panel .BBjWindow > .BBjPanel {
  overflow-y: auto;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  gap: 1rem;
  height: 100%;
}
package org.demo;

import org.dwcj.App;
import org.dwcj.annotations.AppTheme;
import org.dwcj.annotations.AppTitle;
import org.dwcj.annotations.InlineStyleSheet;
import org.dwcj.controls.panels.AppPanel;
import org.dwcj.controls.textbox.TextBox;
import org.dwcj.exceptions.DwcException;

@AppTheme("dark")
@AppTitle("QR Code Generator")
@InlineStyleSheet(value = "css/style.css", local = true, id = "app-bundle")
public class Playground extends App {

  @Override
  public void run() throws DwcException {
    AppPanel panel = new AppPanel();
    panel.addClassName("app-panel");

    String defaultValue = "https://dwcj.org/";

    QRCode qr = new QRCode(defaultValue);
    qr.setSize(200);
    qr.setColor(Color.ORANGE);

    TextBox editBox = new TextBox();
    editBox.setText(defaultValue);
    editBox.setExpanse(TextBox.Expanse.LARGE);
    editBox.setAttribute("placeholder", "Enter a value");
    editBox.onEditModify((e) -> {
      qr.setValue(editBox.getText());
    });

    panel.add(qr, editBox);
  }
}
package org.demo;

import java.awt.Color;
import java.util.UUID;

import org.dwcj.annotations.Attribute;
import org.dwcj.annotations.JavaScript;
import org.dwcj.controls.AbstractControl;
import org.dwcj.controls.htmlcontainer.HtmlContainer;
import org.dwcj.controls.panels.AbstractDwcjPanel;

/**
 * QRCode Generator using Shoelace QRCode component
 * 
 * @author Hyyan Abo Fakher
 */
@JavaScript(id = "shoelace-qr-code", url = "https://cdn.jsdelivr.net/npm/@shoelace-style/[email protected]/dist/shoelace.js", attributes = {
    @Attribute(name = "type", value = "module")
})
final public class QRCode extends AbstractControl {
  private HtmlContainer hv;
  private final String uuid = UUID.randomUUID().toString().substring(0, 8);
  private String value;
  private int size = 65;
  private Color color = Color.BLACK;

  public QRCode() {
    super();
  }

  public QRCode(String value) {
    super();
    this.value = value;
  }

  /**
   * Get the UUID of the toast.
   * 
   * @return the UUID of the toast.
   */
  public String getUUID() {
    return uuid;
  }

  /**
   * Get the value of the QRCode
   * 
   * @return the value of the QRCode
   */
  public String getValue() {
    return value;
  }

  /**
   * Set the value of the QRCode
   * 
   * @param value the value of the QRCode
   */
  public QRCode setValue(String value) {
    this.value = value;
    hv.executeAsyncScript("document.querySelector(\"[data-hv='" + getUUID()
        + "']\").querySelector('sl-qr-code').setAttribute('value', '" + value + "')");
    return this;
  }

  /**
   * Get the size of the QRCode
   * 
   * @return the size of the QRCode
   */
  public int getSize() {
    return size;
  }

  /**
   * Set the size of the QRCode
   * 
   * @param size the size of the QRCode
   */
  public QRCode setSize(int size) {
    this.size = size;
    hv.executeAsyncScript("document.querySelector(\"[data-hv='" + getUUID()
        + "']\").querySelector('sl-qr-code').setAttribute('size', '" + size + "')");
    return this;
  }

  /**
   * Get the color of the QRCode
   * 
   * @return the color of the QRCode
   */
  public Color getColor() {
    return color;
  }

  /**
   * Set the color of the QRCode
   * 
   * @param color the color of the QRCode
   */
  public QRCode setColor(Color color) {
    this.color = color;
    hv.executeAsyncScript("document.querySelector(\"[data-hv='" + getUUID()
        + "']\").querySelector('sl-qr-code').setAttribute('fill', '" + toHexString(color) + "')");
    return this;
  }

  /**
   * Create the control
   * 
   * @param p the parent panel
   */
  protected void create(AbstractDwcjPanel p) {
    super.create(p);

    hv = new HtmlContainer("<sl-qr-code value='" + value + "' size='" + size
        + "' fill='" + toHexString(color) + "'></sl-qr-code>");
    hv.setAttribute("data-hv", getUUID());
    hv.setTabTraversable(false);
    hv.setFocusable(false);

    // add the control to the parent panel
    p.add(hv);
  }

  /**
   * Convert a color to a hex string
   * 
   * @param color the color to convert
   * @return
   */
  private String toHexString(Color color) {
    return String.format("#%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue());
  }
}

Introduce `HasDestroy` interface

Introduce a new HasDestroy interface with two methods destroy and isDestroyed and implement in all controls.

  1. the destroy method should check internally if a control is already destroyed.
  2. If the method is called on a control which has not been attached to panel yet then the creation of the control should be skipped (Track with a flag)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.