FiveTech Support Forums

FiveWin / Harbour / xBase community
Board index FiveWin for Harbour/xHarbour EasyReport Web
Posts: 6984
Joined: Fri Oct 07, 2005 07:07 PM

EasyReport Web

Posted: Sun May 18, 2025 08:37 PM
Hello friends,
I am currently building an HTML/JS version of EasyReport as a local designer.
Goal: Platform-independent, easily extendable, no external tools.

Initial features like area definition, page break, text elements, live preview, and saving as JSON are already implemented.

Everything runs directly in the browser (also offline/USB).
I closely follow the structure of EasyReport (INI/DBF) but use JavaScript & DOM entirely on the frontend.
Best regards,
Otto



https://forums.fivetechsupport.com/viewtopic.php?t=44742
Posts: 6984
Joined: Fri Oct 07, 2005 07:07 PM

Re: EasyReport Web

Posted: Mon May 19, 2025 05:20 PM
Posts: 1818
Joined: Wed Oct 26, 2005 02:49 PM

Re: EasyReport Web

Posted: Thu May 22, 2025 08:49 PM
wow que bien Otto :D :D :D , si necesitas que te ayude para hacer algunas pruebas, cuenta conmigo.
Saludos
LEANDRO AREVALO
BogotĂĄ (Colombia)
https://hymlyma.com
https://hymplus.com/
leandroalfonso111@gmail.com
leandroalfonso111@hotmail.com

[ Turbo Incremental Link64 6.98 Embarcadero 7.70 ] [ FiveWin 25.01 ] [ xHarbour 64 bits) ]
Posts: 6984
Joined: Fri Oct 07, 2005 07:07 PM

Re: EasyReport Web

Posted: Tue May 27, 2025 09:05 PM
Leandro,
I'm sending you a link to download the current version of the software to the email address you provided.
Best regards,
Otto

**The "EasyReport" project is alive – and it has made great progress!**

(https://winhotel-sandbox.com/xbrowse/docs/designerer.jpg[/img)]

## 🧾 Description (derived from the source code):

EasyReport is a browser-based report designer for classic band-oriented reports in the style of FiveWin / EasyReport.
The system runs entirely in an HTML/JS frontend and uses JSON to define the report.

### ✔️ Currently implemented:

* **Page layout with millimeter grid** (A4, customizable margins)
* **Dynamic area management**:
Each area can be freely positioned (`top`, `height`) and edited via click or double-click.
* **Treeview sorting + drag & drop**:
The visual order is managed in the left structure panel – including rearrangement via drag.
* **Design vs. preview mode**:
Toggle between editable structure and print-like preview with one click.
* **Watermark areas (layer function)**:
Areas can be defined as `background` or `foreground` – with different z-index and a dashed outline with transparency in the designer.
* **Edit dialogs for areas and items**:

* Title, height, top position, RepeatFor loops, columns
* Special options like “no chaining” (for background blocks)
* **Label repetition (`repeatFor`)**:
Areas can bind to a JSON data list and repeat automatically – including page breaks.
* **Live export as `report.json`**
The current draft can be saved instantly – as a template or for further processing.

---

## 🧪 In development:

* Support for multipage and dynamic content printing
* Barcode/image elements as drag-in components
* Inline preview of variables (placeholders `{...}`) directly in the designer
* Central layout helpers with rulers and margin lines
* Print rendering (`renderReportForPrint`) for actual printing

---

🧰 The project runs as a pure HTML/JS project – no frameworks, no dependencies.
The development goal is maximum readability, separation of concerns, and cognitive clarity when working with layout logic.
Here’s a simplified version of the description:

The Harbourino style helps you stay organized even in long or complex JavaScript files.
Every function or section gets its own marker, like `→`, so you can immediately see what's happening where in the code.
This makes it easier to make changes, find bugs, or add new ideas later.
Even if someone else reads your code – or you come back to it after a few weeks – you’ll quickly understand how everything fits together.

(https://winhotel-sandbox.com/xbrowse/docs/sourceharbourino.jpg[/img)]
Posts: 1818
Joined: Wed Oct 26, 2005 02:49 PM

Re: EasyReport Web

Posted: Thu May 29, 2025 09:51 PM

Amigo Otto

Archivos recibidos, gracias por el tiempo que dedicas a esto, voy a intentar y cualquier cosa te pido ayuda.

Saludos
LEANDRO AREVALO
BogotĂĄ (Colombia)
https://hymlyma.com
https://hymplus.com/
leandroalfonso111@gmail.com
leandroalfonso111@hotmail.com

[ Turbo Incremental Link64 6.98 Embarcadero 7.70 ] [ FiveWin 25.01 ] [ xHarbour 64 bits) ]
Posts: 6984
Joined: Fri Oct 07, 2005 07:07 PM

Re: EasyReport Web

Posted: Tue Jun 03, 2025 06:49 PM
Now items is working.
Posts: 44162
Joined: Thu Oct 06, 2005 05:47 PM

Re: EasyReport Web

Posted: Wed Jun 04, 2025 07:02 AM
Dear Otto,

great work!

How do you plan to access local files (DBFs, etc) ?

Maybe it should be able to run from a WebView control :idea:

It may be a good idea to test it from FWH/WebView
regards, saludos

Antonio Linares
www.fivetechsoft.com
Posts: 6984
Joined: Fri Oct 07, 2005 07:07 PM

Re: EasyReport Web

Posted: Wed Jun 04, 2025 07:41 AM
Hi Antonio,

thank you for your kind words.

The system is designed to run as pure HTML + JavaScript, loading its data via a local JSON file (rechnung.json). There is no backend required – it's self-contained and completely static, which means:

No server needed – can be opened directly via file:// or hosted via any HTTP server.

Full portability – ideal for use on a USB stick, kiosk mode, or embedded WebView.

All report logic (rendering, page break, formatting) is done client-side.

DBF or external data sources can be converted to JSON beforehand or dynamically injected.

So yes, it works perfectly from a WebView control in FWH or any embedded browser. The user could run this offline, even without a server, which makes it very flexible for internal reporting or in my case hotel automation use cases.


Best regards
Otto
async function loadReportConfig() {
  const res = await fetch("rechnung.json");
  report = await res.json();

  // 📄 Etikettendaten vorbereiten

 reportData = {
  rechnungsnummer: "RE-2024-0012",
  datum: "2024-06-03", // 🔁 neu
  kunde: {
    name: "Max Mustermann",
    strasse: "Teststraße 12", // 🔁 neu
    plz: "9999",              // 🔁 neu
    ort: "Musterstadt"        // 🔁 neu
  },
  artikelListe: [
    { artikel: "Apfel", preis: "1.50" },
    { artikel: "Brot", preis: "2.00" },
    { artikel: "Käse", preis: "3.75" },
    { artikel: "Wurst", preis: "4.10" },
    { artikel: "Milch", preis: "1.20" },
    { artikel: "Kaffee", preis: "5.00" },
    { artikel: "Zucker", preis: "1.10" },
    { artikel: "Butter", preis: "2.80" },
    { artikel: "Eier", preis: "2.30" },
    { artikel: "MĂźsli", preis: "3.90" }
  ],
  summe_netto: "27.65",         // optional, fĂźr MwSt-Anzeige
  summe_mwst: "5.53",           // optional
  summe_brutto: "33.18"         // optional
};
json:

{
  "paper": {
    "top1": 20,
    "top2": 45,
    "pageHeight": 297
  },
  "areas": [
    {
      "id": "area7458",
      "title": "Logo",
      "top": 0,
      "height": 50,
      "printCondition": "always",
      "topDependsOnPrevious": false,
      "disallowChildren": false,
      "items": [
        {
          "type": "text",
          "x": 100,
          "y": 5,
          "value": "MyLogo",
          "fontSize": 60,
          "bold": false,
          "color": "#d9913f",
          "align": "left"
        }
      ]
    },
    {
      "id": "kopf",
      "title": "Rechnungskopf",
      "top": 70,
      "height": 40,
      "topDependsOnPrevious": true,
      "items": [
        {
          "type": "text",
          "x": 0,
          "y": 2,
          "value": "Rechnungsnummer: {rechnungsnummer}",
          "fontSize": 10,
          "bold": true
        },
        {
          "type": "text",
          "x": 0,
          "y": 8,
          "value": "Datum: {datum}",
          "fontSize": 10
        },
        {
          "type": "text",
          "x": 0,
          "y": 16,
          "value": "An: {kunde.name}",
          "fontSize": 10
        },
        {
          "type": "text",
          "x": 0,
          "y": 22,
          "value": "{kunde.strasse}",
          "fontSize": 10
        },
        {
          "type": "text",
          "x": 0,
          "y": 28,
          "value": "{kunde.plz} {kunde.ort}",
          "fontSize": 10
        }
      ],
      "printCondition": "",
      "disallowChildren": false
    },
    {
      "id": "wasser",
      "title": "Wasserzeichen",
      "top": 150,
      "height": 100,
      "disallowChildren": true,
      "items": [
        {
          "type": "text",
          "x": 100,
          "y": 20,
          "value": "KOPIE",
          "fontSize": 70,
          "bold": true,
          "color": "#50ce58",
          "align": "left",
          "fontFamily": "Courier"
        }
      ],
      "printCondition": "",
      "topDependsOnPrevious": false,
      "repeatFor": "artikelListe",
      "columns": 1
    },
    {
      "id": "einleitung",
      "title": "Überschrift / Einleitung",
      "height": 15,
      "printCondition": "always",
      "topDependsOnPrevious": true,
      "items": [
        {
          "type": "text",
          "x": 0,
          "y": 2,
          "value": "Vielen Dank fĂźr Ihre Bestellung",
          "fontSize": 11,
          "bold": true
        }
      ],
      "top": 0
    },
    {
      "id": "artikelblock",
      "title": "Artikelblock",
      "repeatFor": "artikelListe",
      "columns": 1,
      "labelWidth": 160,
      "height": 8,
      "topDependsOnPrevious": true,
      "items": [
        {
          "type": "text",
          "x": 0,
          "y": 2,
          "value": "{artikel}",
          "fontSize": 10,
          "bold": true
        },
        {
          "type": "text",
          "x": 90,
          "y": 2,
          "value": "{preis} €",
          "fontSize": 10
        }
      ],
      "top": 225,
      "printCondition": "",
      "disallowChildren": false
    },
    {
      "id": "summe",
      "title": "Summenbereich",
      "top": 200,
      "height": 20,
      "topDependsOnPrevious": false,
      "items": [
        {
          "type": "text",
          "x": 0,
          "y": 2,
          "value": "Netto: {summe_netto} €",
          "fontSize": 10
        },
        {
          "type": "text",
          "x": 60,
          "y": 2,
          "value": "MwSt (20 %): {summe_mwst} €",
          "fontSize": 10
        },
        {
          "type": "text",
          "x": 120,
          "y": 2,
          "value": "Brutto: {summe_brutto} €",
          "fontSize": 10,
          "bold": true
        }
      ]
    },
    {
      "id": "endtext",
      "title": "Rechnungs-Endtext",
      "top": 325,
      "height": 45,
      "topDependsOnPrevious": true,
      "items": [
        {
          "type": "text",
          "x": 0,
          "y": 2,
          "value": "Vielen Dank für Ihren Einkauf!\n\nBitte überweisen Sie den Gesamtbetrag innerhalb von 14 Tagen auf folgendes Konto:\nEmpfänger: Hotel Bergland\nIBAN: ATxx xxxx xxxx xxxx xxxx\nBIC: XXXXXXXX\nVerwendungszweck: Rechnungsnummer {rechnungsnummer}\n\nBei Fragen stehen wir Ihnen gerne zur Verfügung.\nMit freundlichen Grüßen\nIhr Team vom Hotel Bergland",
          "fontSize": 9
        }
      ]
      }
  ]
}
Posts: 6984
Joined: Fri Oct 07, 2005 07:07 PM

Re: EasyReport Web

Posted: Thu Jun 05, 2025 12:42 AM
//-- HARBOURINO RENDERREPORT_PREVIEW --//
function renderReport_Preview() {
  
  const data = reportData;
  
  // Container leeren und vorbereiten
  -> INIT_LAYOUT_CONTAINER
  
  // Erste Seite anlegen
  -> CURRENTPAGE_INIT_LAYOUT
  
  // 1. AREA-LOOP fĂźr AREA_REPEATLIST und AREA_NORMAL
  report.areas.forEach(area => {
    
          if (area.disallowChildren) {
            return;
          }
          
          // Position und Seitenindex berechnen
          -> CALC_AREA_POSITION_PAGEINFO
          
          // Seite umblättern, wenn pageIndex > current
          $-> NEW_PAGE_IF_NEEDED : mypageIndex = pageIndex;  mypageMargin = pageMargin
          


          // PrĂźfen, ob die Area wiederholend ist
          -> ISREPEATLIST
          
          if (isRepeatList) {
            
            // Wiederholende Einträge (z. B. Etiketten, Zeilen)
            -> AREA_REPEATLIST
            
          } else {
            
            // Einzelbereich rendern
            -> AREA_NORMAL
          }
          
  });
  

  // 2. AREA-LOOP 
  // Wasserzeichenbereiche auf allen Seiten rendern
  -> AREA_WATERMARK

}
Description for your renderReport_Preview() function, including the purpose of each HARBOURINO block:

🧾 Function: renderReport_Preview()
This function is responsible for generating the preview of a report. It dynamically creates all page areas (including repeatable sections, static content, and watermarks) based on the report structure and the content stored in reportData.

🔷 1. INIT_LAYOUT_CONTAINER
Prepares the layout container by clearing its content.
→ Ensures the preview always starts with a clean layout.

🔷 2. CURRENTPAGE_INIT_LAYOUT
Creates the first page (<div class="seite">), including margins and optional background grid.
→ This initializes the visual report canvas.

🔷 3. CALC_AREA_POSITION_PAGEINFO
Calculates for each area:

The vertical position (top)

The corresponding page (pageIndex)
→ This controls layout logic and where page breaks occur.

🔷 4. NEW_PAGE_IF_NEEDED
Creates a new page if the area starts on a different page.
→ Handles automatic page breaks based on content position and available space.

🔷 5. ISREPEATLIST
Checks if the current area is linked to a data list via area.repeatFor.
→ Determines whether to render it as a repeating block (AREA_REPEATLIST) or a single block (AREA_NORMAL).

🔷 6. AREA_REPEATLIST
Used for repeatable areas (e.g., item lists, labels):

Iterates over the dataset (repeatList.forEach)

Calculates row/column position

Renders content for each data entry
→ Ideal for invoices, product lists, badge layouts, etc.

🔷 7. AREA_NORMAL
Used for non-repeating areas:

Renders blocks like address, subject lines, footers

Appears once, positioned by top
→ Good for static or document-level content.

🔷 8. AREA_WATERMARK
Executed after the main rendering:

Applies watermark areas (marked with disallowChildren = true) to all pages

Typically used for labels like "COPY", "DRAFT", "PAID"

Non-editable and rendered in the background

🔶 Special behavior:
Each text item (whether repeat or normal) is filled using fillTextFromData(...), replacing placeholders like {kunde.name} with actual values.

The preview is paginated, and supports multi-page layouts for labels, reports, or invoices.

Layout logic is based on metrics like pageHeight, pageMargin, and area.top.

🧠 Summary:
renderReport_Preview() is the core rendering engine of your report designer.
It transforms the structural blueprint (report.areas) and dataset (reportData) into a live, multi-page visual preview.
Thanks to the HARBOURINO-style block comments, the function is modular, debuggable, and fully compatible with your preprocessor/patcher system.
Posts: 6984
Joined: Fri Oct 07, 2005 07:07 PM

Re: EasyReport Web

Posted: Thu Jun 05, 2025 08:59 AM
Hello,
Up until now, only text elements (`type: "text"`) were supported. With minimal effort, it's now possible to integrate any HTML element — starting with the `image` element.

Example extension in `rechnung.json`:



 
{
   {
      "id": "logoarea",
      "title": "Logo",
      "top": 0,
      "height": 50,
      "printCondition": "always",
      "topDependsOnPrevious": false,
      "disallowChildren": false,
      "items": [
        {
          "type": "image",
          "src": "image/logo.png",
          "x": 100,
          "y": 5,
          "width": 80,
          "height": 40
        },
        {
          "type": "image",
          "src": "image/logo.png",
          "x": 20,
          "y": 5,
          "width": 40,
          "height": 20,
          "rotate": 90
        },{
          "type": "image",
          "src": "image/logo.png",
          "x": 60,
          "y": 5,
          "width": 40,
          "height": 20,
          "rotate": 45
        },
        {
          "type": "text",
          "x": 100,
          "y": 5,
          "value": "WINHOTEL",
          "fontSize": 10,
          "bold": false,
          "color": "#d9913f",
          "align": "left"
        }
      ]
    }
New logic added inside the item loop:
else if (item.type === "image") {
  const img = document.createElement("img");
  img.src = item.src;


  img.style.position = "absolute";
  img.style.left = (item.x ?? 0) + "mm";
  img.style.top = (item.y ?? 0) + "mm";
  img.style.width = (item.width ?? 30) + "mm";
  img.style.height = (item.height ?? 20) + "mm";
  img.style.objectFit = "contain";

  if (item.rotate) {
    img.style.transform = `rotate(${item.rotate}deg)`;
    img.style.transformOrigin = "left top";
  }

  box.appendChild(img);
}
All other HTML elements can be integrated using the same `item.type` logic — including line, hr, barcode, qr, button, div, svg, and many more.

The report remains entirely data-driven and portable.

Still improving, one step at a time.
Best regards,
Otto

Posts: 6755
Joined: Wed Feb 15, 2012 08:25 PM

Re: EasyReport Web

Posted: Thu Jun 05, 2025 11:19 AM

Otto, what a great job, congratulations!

Cristobal Navarro

Hay dos tipos de personas: las que te hacen perder el tiempo y las que te hacen perder la nociĂłn del tiempo

El secreto de la felicidad no estĂĄ en hacer lo que te gusta, sino en que te guste lo que haces
Posts: 6984
Joined: Fri Oct 07, 2005 07:07 PM

Re: EasyReport Web

Posted: Wed Jun 18, 2025 01:19 PM
Hello,
The EasyReport Web prototype natively supports embedded Chart.js diagrams in invoices, reports, and label layouts. This allows bar and pie charts to be easily added via JSON configuration.

Lines are now supported as well.

Best regards,
Otto

Posts: 1818
Joined: Wed Oct 26, 2005 02:49 PM

Re: EasyReport Web

Posted: Wed Jun 18, 2025 01:48 PM
Cada vez mas genial :D :D :D

Felicidades Otto
Saludos
LEANDRO AREVALO
BogotĂĄ (Colombia)
https://hymlyma.com
https://hymplus.com/
leandroalfonso111@gmail.com
leandroalfonso111@hotmail.com

[ Turbo Incremental Link64 6.98 Embarcadero 7.70 ] [ FiveWin 25.01 ] [ xHarbour 64 bits) ]
Posts: 990
Joined: Thu Nov 17, 2005 05:49 PM

Re: EasyReport Web

Posted: Wed Jun 18, 2025 06:09 PM
Hello Otto;

Great effort.

I might be interested on it and even working on its development. To get up to speed, it would help if code variables and comments were in English. Just looking at German variable names that I can't retype, makes progress slower.

English seems to be more the global language for shared development. Just a recommendation.

Questions:
#1 -- Have you seen the free edition of KoolReport https://www.koolreport.com/examples/reports/basic/sales_by_country/ and others like it? Why would it be better to develop a new report engine?

#2 -- One of the things I like about FastReprots is that you can define fields on the report or form source format (.fp3) where the user can click and input data. I use it a lot with forms that shows data on a field but the user is allowed to click on it and change that date, or amount, sign his name or input whatever text. Can that be done with EasyReport? To get that, we would probably have to develop our own browser compatible viewer. What do you think?

Thank you.
Posts: 6984
Joined: Fri Oct 07, 2005 07:07 PM

Re: EasyReport Web

Posted: Wed Jun 18, 2025 07:19 PM
Dear Reinaldo,
You're absolutely right:
Switching to English for variable names and comments is essential for any collaborative or open development.
In the EasyReport prototype, topDependsOnPrevious, repeatList, absoluteTop etc. are already in English, but older parts still contain top1, seitenzahl, bereich etc. — this should absolutely be migrated.

EasyReport is aiming for a visual report builder like FastReport or Crystal Reports — just HTML/JS-based, portable, and browser-native.

KoolReport doesn’t offer:
Page margin control
Visual field layout
Manual reordering
Page breaks
Preview with drag handles
So it’s a different category.



Can EasyReport allow user-editable fields?

Just add in the items:

json
 
{
  "type": "input",
  "x": 10,
  "y": 15,
  "width": 60,
  "height": 10,
  "placeholder": "Enter value"
}
And in code:
else if (item.type === "input") {
  const input = document.createElement("input");
  input.placeholder = item.placeholder || "";
  input.style.position = "absolute";
  input.style.left = item.x + "mm";
  input.style.top = item.y + "mm";
  input.style.width = item.width + "mm";
  input.style.height = item.height + "mm";
  box.appendChild(input);
}
Signature support could be added via <canvas> and pointer events.


As I use Harbourino-style my code ist very short:
area.items?.forEach(item => {
  -> AREAITEM_TEXT
  -> AREAITEM_IMAGE
  -> ITEMS_LINES
  -> AREAITEM_CHART
  -> AREAITEM_INPUT
});

At the moment, EasyReport is still a very personal tool, tailored to our specific setup (WINHOTEL, label printing, receipt layout).
It’s just a bit too early to share code publicly – I hope you understand.

Best regards,
Otto

Continue the discussion