Creating PWA Apps for Smartphones
Building a PWA with the INtex Framework
A step-by-step guide to creating a configuration-driven Progressive Web App.
1. Introduction
The INtex PWA framework is designed to rapidly create data-centric mobile applications. The core principle is a powerful, generic engine (app.js and pwa_v2.php) that is entirely controlled by a single JavaScript configuration file. By simply defining the structure of your data, lists, and forms in a config.js file, you can generate a complete, functional PWA without writing any engine code.
This tutorial will walk you through the process using the "Auftrag Aufgaben" (Order Tasks) app (todoauftrag-config.js) as a comprehensive example.
2. The Heart of the App: The `pwaConfig` Object
Every app starts with a configuration file, like todoauftrag-config.js. This file contains a single global constant, pwaConfig, which is an object that defines every aspect of your app. Let's break it down section by section.
This is the most basic part. It defines the app's name and the icon that will be displayed in the header.
const pwaConfig = {
// 1. Allgemeine App-Informationen
appName: 'INtex Auftrag Aufgaben',
appIcon: 'fa-list-check',
// ...
};
This section tells the app where to find its data.
api: {
endpoint: '../pwa/pwa_v2.php',
table: 'aufgaben',
clientSideFilter: (item) => item.bearbeitungsstatus !== '3 - Erledigt',
},
endpoint: The path to the backend PHP script. This is usually the same for all apps.table: The logical name of the database table you want to work with (e.g., 'aufgaben', 'fahrten'). The backend resolves this to the physical table name.clientSideFilter: A powerful feature. This is a JavaScript function that filters the data *after* it has been received from the server. In this example, it ensures that tasks marked as "Erledigt" (Done) are immediately hidden from the list, providing a snappy user experience.
The list object configures the main screen of your app, where all the data records are displayed.
list: {
searchableFields: ['titel', 'notiztext'],
filterFields: ['adresse_text', 'projekt_text', 'bearbeitungsstatus'],
sortOptions: [
{ key: 'prioritaet', label: 'Nach Priorität', icon: 'fa-arrow-down-wide-short', direction: 'DESC' },
// ... more sort options
],
itemTemplate: (item) => {
// ... HTML and JavaScript logic ...
return `<div class="card" data-id="${item.id}"> ... </div>`;
}
},
searchableFields: An array of field names that the top search bar will query.filterFields: An array of field names used to populate the filter modal. The engine automatically finds all unique values for these fields from the loaded data and presents them as filter options.sortOptions: Defines the sort buttons. Each object creates a button with a label, icon, and specifies the database field (key) anddirectionfor sorting.itemTemplate: This is a JavaScript function that returns an HTML string for a single list item. It receives the fullitemobject from the API, allowing you to build complex and dynamic list entries. Notice the use of resolved lookup fields likeitem.projekt_textand helper functions likeformatDate(). Theonclick="app.showEditItemModal(${item.id})"is crucial for enabling editing.
The actions object allows you to define custom client-side and server-side logic that can be triggered by UI events, like a swipe or a checkbox.
actions: {
swipe: {
enabled: true,
actionName: 'toggleStatus',
confirmation: { /* ... */ }
},
toggleStatus: {
execute: (item, context) => {
const newStatus = context.isChecked ? '3 - Erledigt' : '1 - Neu';
const payload = { bearbeitungsstatus: newStatus };
// ... custom logic for recurring tasks ...
return payload;
}
}
},
swipe: Configures the right-swipe gesture on a list item. It's enabled and linked to the 'toggleStatus' action.toggleStatus: Defines the action itself. Theexecutefunction is the core of the logic. It receives the dataitemand acontextobject (e.g., containingisChecked). It must return apayloadobject, which the engine then sends to the API to update the database record. This example even includes complex business logic for handling recurring tasks.
The forms object defines the structure of the modals for creating (add) and modifying (edit) records.
forms: {
add: {
title: 'Neue Aufgabe',
fields: [
{ name: 'titel', label: 'Titel', type: 'text', required: true },
{ name: 'frist', label: 'Frist', type: 'date' },
{
name: 'projekt',
label: 'Projekt',
type: 'select',
optionsSource: {
type: 'table',
table: 'projekte',
valueField: 'id',
labelField: 'projektbezeichnung'
}
},
{ name: 'notiztext', label: 'Notiz', type: 'editor', config: 'light' }
],
defaultValues: { 'bearbeitungsstatus': '1 - Neu' }
},
edit: {
title: 'Aufgabe bearbeiten',
readOnlyCondition: (item) => item.revisionssicher == 1,
infoBlocks: { /* ... */ }
// ... fields ...
}
},
fields: An array of objects, where each object defines a form field. Key properties include:name: The database column name.label: The user-visible label.type: The input type, e.g.,text,date,number,range,select, or the customeditor.optionsSource: Forselectfields, this defines where the options come from. It can be avaluelistor anothertable.
defaultValues: An object for setting values on new records that are not present as form fields (e.g., setting an initial status).readOnlyCondition: In theeditform, this function determines if the form should be non-editable. It receives the item and returnstrueto lock the form.infoBlocks: In theeditform, this allows you to display non-editable blocks of information, like the linked address in the example.
3. Deployment and Access
How to Access Your App
Once your configuration file is created and placed in the /pwa/ directory, you can access the app using a specific URL pattern.
The URL is constructed as follows: https://[subdomain].yourdomain.com/pwa/index.php?app=[config_filename]
For our example, if the file is named todoauftrag-config.js, you would access it via:
https://my-team.yourdomain.com/pwa/index.php?app=todoauftrag
Note that the .js extension is omitted from the app parameter.
How Subdomains and Databases Work
The framework is designed for a multi-tenant architecture. The subdomain of the URL (e.g., "my-team") is used by the backend to identify the "team" or "client". This team identifier is then used to connect to the correct, isolated database for that specific client.
This means you can host numerous independent client apps on the same codebase, with data separation handled automatically by the backend based on the subdomain in the URL.