I had an issue finding one bug I had and as is my nature, I started to doubt internals of Quid are any good. It seems bloated and overcomplicated now.
But the truth is, Quid is production ready for a long time now, which includes tons of checks, verifications, logging and such. It offers automatic handling of async calls, which requires additional layer in the action call.
It occurred to me there might be no silver bullet to application framework like Quid, where few lines of code would do all the work. It's either in pretty much best shape already, or there are technical limitations to do it more easily.
I like in JSX you can nest components, but I use JSON and there's no such thing as nesting without defining children
in the object.
I was really pleased how the integrated help turned out, so I expanded it more.
When I created help documents in a text processor, I always used shapes to focus the user to the part of GUI I was writing about, usually highlighting a button.
When doing this here I had basically three choices: Draw the shape directly in the picture and many variants of basically the same picture (and have to re-do all of them when GUI changes = bad!), omit the highlighting altogether or create some internal system for shapes.
The system wasn't that difficult, many shapes could be done just by styling DIVs, so it didn't take long to set it all up. Honestly, the most difficult part was to design markup to be simple and intuitive enough, but GPT designed nice regular expressions for this purpose, so it was quite painless and even I had to do some compromises, I'm happy with the outcome.
So now there are two modes for image markup: Simple and Complex. Simple, here with caption:
The same in complex mode:
And now with shapes, defined as “rects”. You can use colon and equals sign interchangeably, as well as comma and semicolon, and you don't have to care much about whitespaces:
This will create a red rectangle on top of the image, with line 2px wide and 5px rounded corners. You can also specify a tooltip and an action, creating a clickable image. I was particularly happy about that, it will be very useful.
I wanted keys to be as space-saving as possible, so they're a bit cryptic, but if you know the abilities of a rect
, it should be easy enough to guess them. I'm a bit disappointed I couldn't came up with a reasonable single letter key for everything though.
Here in Czechia we have around 3M addresses, which is few enough to do an offline local validation. In MySQL it's around 250 MB, so if you're limited in database size, you can parse it from files.
Czech Office for Surveying, Mapping and Cadastre offers a ~62 MB ZIP archive with CSV files, one for each municipality, identified by its numeric ID, so you need to know it first.
I've decided to use Czech postal code database, provided by Czech Post as a single CSV files in ZIP archive, so I can search not only by municipality name, but by postal code as well.
Vehicle Identification Numbers (VINs) basically holds a vehicle's identity. It's a unique alphanumeric code assigned to each motor vehicle when it's manufactured, basically a fingerprint.
VIN decoding involves breaking down the 17-character code to unveil the hidden details about a vehicle. While the entire VIN carries valuable information, certain characters have specific meanings:
How VIN works
Hard to decode everything, API for that
Valid VIN returned false on multiple different source code tests
Historic VIN won't pass
I thought I have remote emergency fixes solved, but during my recent travels all over our beloved Japan I found I was wrong.
Koder doesn't support versioning
Slow internet
Problems getting console to work, my mistake (edge://inspect)
If you're using Quid and you're stuck while doing a task, from now on you just press F1 key or a dedicated button in the user interface and Help is on its way.
I've always wanted to have integrated context help, but I kinda never had the right code for it. Well, until now :-)
vcelari/inzerce/sumar/NNNN
Quid already supported quick dev help for a component after selecting an element and pressing Alt+F1, so with help system I also added Alt+Shift+F1 to access Admin Help, where technical aspects are described and it acts basically as a tech docs for the module.
Quiky Editor
I pitched Qedy to a new customer, this time a Czech nonprofit association, who decided to diversify their suppliers in order to prevent a vendor lock. I'm very glad they saw a potential in what I do, so they welcomed me on board.
My responsibility is to build an internal intranet portal for the HQ, while they also maintain an IT system for their member organizations and plan to expand it to tens of thousands of their individual members.
The first task was to digitalize their mail room. They still use regular books with handwritten records, so this will be a major improvement. They also use Excel spreadsheets, which they exchange between departments to retype reference numbers, names and addresses to their own Excel spreadsheets, in which they work afterwards.
It's not just plain tables though, they had a guy who created simple vbs apps, but since he retired, they were looking for a replacement. And because one of their major problem was data sharing, they were OK with rebuilding all the Excel apps into a web app.
For me it's almost a routine, this is a fourth Qedy installment, where I recreated former Office app (Excel, Access). Don't get me wrong, I love Excel and it's capabilities, it's almost a "sandbox app" (in regards to "sandbox games"), but the trend moved on and for a good reason.
I had a quite long run-up, so I was able to do things properly over time, so even after the work orders started to rush in, I didn't have to create a tech debt and kept chugging away. I was even confident to offer more digitization of employee management, which was in paper books as well.
The advantage is most customers have very similar needs, so I already had such modules near completion from previous projects. So when this customer signed up for the module, I was able to offer both of them a fair discount on future modules, because I didn't feel it's fair to get paid twice for the same work.
I know it's a common practice, but honestly I'm more happy they were willing to pay for the development, when there are tons of similar apps out there and the competition is huge.
I've always wanted to have a "Store", where user chooses what components are needed for the project and they're used as the main building blocks for the project.
Reactivity for DOM updates using e.g. Signals
Cloned templates for DOM rendering. Security issues using innerHTML.
Using APIs like <template> and Proxy: template.content.cloneNode(true)
Past few years I've been struggling with a universal data model for Quid. I had it in my mind since Qedy PHP, but something didn't seem right. It was either too complex or not flexible enough.
The general idea is to have Objects as a “data spine” of the whole system, which would be referenceable from any module. Object is pretty much an empty shell, only with display name and validity dates.
Database queries are simpler, because I finally caved and put elem type also in the relation table, so I don't need to JOIN elems
table all the time (for most relations) anymore. It's only a CHAR(1), which made the decision much simpler for me.
Another big step was to include elem_elems
relations into the core itself. Previously I considered it just a helper information for stuff like suggesting right Objects in a particular autocomplete list. Also, this relation table got elem type, this time for the target elem only.
I don't use ORM, so I once again added my favorite debug option for SQL queries – adding a space character at the beginning of the query will print it (with variable names properly replaced by their values, of course), and adding second space will also prevent the query from executing, which is very helpful when debugging UPDATE or DELETE statements, because you can repeat them without any need to rollback data changes.
I figured out another way how to include module management, now as part of all installed “features” in the app, and once again I'm quite happy how it turned out to be. It's somewhat aside, yet important for the app to work properly, which is exactly what I wanted.
The biggest pain was, as usual, permissions. It took me several iterations to achieve what I wanted. Because testing was tedious and I often broke something by fixing something else, I had to write a simple testing method to include all five major groups (visitor, user, assistant, boss, admin) for both list and detail.
I failed to keep it simple, but to be fair, it's probably not possible. I opted for simpler data structure, but I had to create three separate queries for page data (for either list, detail or a new entry), plus two distinct queries for permissions, but ultimately it works just fine.
I also found another way how to deal with processes and even this one is quite as I wanted it to be, despite it's not ideal – I was forced to abuse class.order of the same elem to distinguish between state as Object and state in an Object.
I updated code for generating simple state diagrams in SVG and on top added support for multiple diagrams at once, because one Object can have multiple states (like a purchase could have a delivery status, insurance status and a payment status at the same time).
I used to have a method to test all the different SELECTs, but I rewrote it for practical use and created a simple app, that actually uses those database queries to display valid data. I can use it as an emergency app or backend API, with shared modules between them, and as a CORS proxy. The API will also come in handy, when I'll start implementing autocomplete features into the tool.
For unknown databases it can now also create a database and a directory structure for rapid app init, fetching all the specs from a central repository. I've decided to make it a “Swiss Army knife” of sort for Qedy apps. Because I suspect I'll use it for debugging a lot, there are several debugging features not just for the app (like logs or table contents), but for the tool itself as well.
Because of this, I worked on data import part. Now it can not only import data into selected table, but it can also analyse given dataset and assume the best data type for each column. For numbers it provides min and max value (to determine optimal type and possibility to be unsigned), for strings it finds shortest and longest text (decision: char × varchar + length) and it looks for empty values as well (allow NULLs).
For each column it creates an object with all the parameters, so if there's more than one type possible, it defaults to varchar. Like in the following example, showing gathered data for column `code`:
It also checks value count and for limited number of repeating values it offers it to be an enum (list of values). It stops adding new values after certain number of unique entries, because after that it's not going to be an enum and it would be just a waste of memory.
After I expanded it fairly, it now seems a bit too complex, but still manageable. There are queries with fair number of JOINs, but they do fair amount of stuff, so the “weight” is justified.
It looks like with this shot I may finally hit a bullseye, but only time will tell.
2024 Update: There are some wrinkles here and there, but generally it's IT!
I created the Framework as a guideline how to create web apps “my way”. It's open source and available in many programming languages.
Because the Framework itself is quite bare bone, I wanted to add a layer for rapid web app development. That's how Qedy was born.
Finally, implementing Qedy is Quid, which isn't a framework any more, but rather a final product. Quid doesn't contain building blocks, but final layouts and functionalities.
Because of COVID, restaurants are offering only take-aways. Therefore, I was (t)asked to create a module for lunch orders for employees, in which there are 4 meal choices + soup and salad or dessert each workday. Then it will record payments made by employees to a designated person.
The data model was quite straightforward, it's basically a simple e-shop. And even simpler, because data-wise I can merge orders with payments. So, it all fits into a single database table, with a row per day and employee.
When it was ready for testing, I got two “innocent” follow-ups: It should be possible to define amount for each meal, because some employees order lunches for their family members as well, and payments are not only in cash, but also using meal vouchers (common work benefit here in Czechia).
Because for each row I stored price and not the amount, the change would require either a significant extension of the model, or a complete rework. I could imagine further follow-ups like this, so I opted for the more robust latter choice.
The complete rework rested in adding another dimension, so now every row will be for combination date-employee-type, where type is either food or payment.
I have to admit, I didn't expect the change to be that significant. The amount of code doubled, along with its complexity. When I had hard time to update the current code, I ultimately decided to delete most of it and rewrite it almost from scratch, which helped immensely.
It would save me more than half the time required to complete the task, if I had the whole specification from the day one. I partially blame myself, because I went the easier route without further analysis, where the need for food vouchers would possibly emerge.
Update after two months: Guess what? I got another request, this time to record invoices paid to the meal company and calculate leftover cash. This time the change was quick and easy, thanks to the clever choice I made :-)
I thought more about tags and found out it's even more flexible, than I imagined. All in the sense of the original idea of simplification for QetriX, tags is a generic group of anything.
It works well for users. Now tag is not only a group of users, but it acts as a role as well. You can tag users as “employee”, “customer”, “vip”