Jump to content

The Upright Man

Members
  • Posts

    8
  • Joined

  • Last visited

Everything posted by The Upright Man

  1. First, I am new to Processwire, rarely do much with PHP and find myself hitting Google for every living thing, and this is my first attempt at using HTMX! So, this is my attempt at rolling up a whole server-side UI-lib from scratch! Very much a WIP! HTMX I wanted to share this idea though because it's finally reached the "exciting" stage so as to get some feedback and suggestions on the design. Basically, I want really dynamic UI tools for an upcoming project and I can't stand having to divide code between 3rd party UI libraries in the browser and exchanging data with processwire on the backend. You end up with html files, css files, front end classes, back end classes, and maybe a bunch of jQuery spaghetti and random PHP files to tie it all together. So, I decided to put the whole thing server-side using HTMX. HTMX and Processwire work together beautifully, each complementing the other. HTMX is a small javascript library that allows any html element to make ajax calls, and directly inserts the results into the DOM as HTML rather than json. You can do some event handling to call javascript and a few other goodies as well. It's incredibly powerful! So, I first made some basic "smart tags" using HTMX markup as Hannacode elements. This looks similar to HTML but takes care of boilerplate easily. Since Processwire is page based, I set up a /forms/ page to allow urlsegments, then pass a method name as the segment! Each child page is a different form with its own subclass in php. You can layout the form in HTML with a couple of hannacode helpers and a few standard fields. The page in processwire defines all your layout and form elements. JSON Forms To allow a more natural description of the forms and allow easy nesting (such as for layouts), you can describe a form using a modified json. I say "modified" because you have to strip out the quotation marks to be legal hannacode, so it's all one big string. I changed the regex in hannacode's parser to allow newlines, which allows you to have some sanity in defining it. The resulting "big string" is run through a regexx that guesses at where the quotation marks should go, making it valid json, which is fed to php's json parser. The objects in the json are just telling the backend code what hannacode functions to call to generate the final html output. Here is the Body element for my login screen. The "formheader" is a hannacode routine that displays a standard graphic at the top of the form (you can specify an URL of a different one) and it grabs the title and prompt from fields in the form itself. You can change these at run-time! There is also an "extra_tabs" field that lists the names of other tabs to load when loading this one. I've not finished the code to add/remove tabs programmatically, but that's next. Just finished state vars! [[form size=small json="[ { new:formheader }, { new:input, id:username, type:text, label:Username }, { new:input, id:password, type: password, label: Password }, { new:layout, class:formbottom, contents: [ { new: button, action:login, text: Login }, { new: a, opentab: newuser, text: Reset Password, info: Enter your email below and we'll figure it out! } ] } ]" ]] Displaying A Form Currently, I am only doing modal forms which cover the display. I will add in-line forms soon through similar methods. To get a form to pop-up, you can either display a button that pops up the form, or you can do it directly. The direct method just outputs a blank div with the "load" trigger set to make a post request the moment the div is loaded. This is also how form initialization works - there is a hidden element in every form that calls the init method when it is loaded, and this event only fires once unless you replace that element in the DOM. The destination will be a static div creating an overlay layer on the website <div #alertbox> that normally does not display (empty divs take up no space until I put content in it). I tell HTMX to output the HTML it receives by just adding hx-target="#alertbox" to the html element that is making the POST. The form background has CSS that makes it the width and height of the screen, giving you a lightbox display for the form. A button works the same way except that the hx-trigger="click" (the default for buttons) instead of load. Closing the form is just replacing the contents of #alertbox with more empty. My login button can appear anywhere I have [[button type='loginout']] on a page! The only reason I reload the underlying page on a login is because the rest of the site isn't upgraded to HTMX yet. There is no need for redirects and other messy tricks. You click the button and the form appears without a page load delay! Your position on the site does not change. You don't have to store an URL to go back to it or anything like that. Form Tabs Part of my end goal involves dynamic tab generation/creation. So, rather than holding each tab in the DOM at once and changing which tab is visible through javascript, I completely replace the form body interior when changing tabs. This means I can reuse IDs between forms! It also means more intelligence can be offloaded to the server side, and I can share data between tabs easily (see below). Talking to Processwire Since the forms are on /form/<formname>/ I set up a convention where you append a <methodname>/ to this URL, and optionally an initializer segment (mainly to accept an encrypted verification link through email - email templates are so easy in Processwire!). You can then drop a php file in the "form" subdirectory of your templates, named the same as your form name. This subclasses a base PHP class. Every control on your form has a name and id (I keep them the same for personal sanity), and this name will be the method name called when you interact with the form. A button named "submit" on form "foo" would call /form/foo/submit/ when you click it with all the form elements included! If you edit the "summary" text field in this form, you will get a post on /form/foo/summary/ with the updated value. If there is no method defined for this, the system still quietly updates the stored copies of the variables. Define the method only if you want to have server-side validation, search, update drop-down lists, etc. HTMX can add and remove elements from your screen as it responds to the post messages. Form Logic The Form.php template (which I've duct-taped together) is called when we get a request for a form. This then auto-loads the correct sub-class file, creates an instance of this class, and calls the method named in the URL. Inside the form is a hidden div with a list of hidden input fields that provide form state plus basic utility functions to call from inside the subclasses that power the actual forms. HTMX automatically sends the values of all form elements when the request comes from a form element. I wrap them in a DIV with a unique ID just that HTMX knows where to put them on the page. I'm currently replacing the entire variable list. Soon, this will add/replace elements one at a time in the post response rather than dumping the whole list with each response. You can attach additional elements to the request by ID as well, so if you want the value of some field that isn't in the form, htmx will supply its value in the post via hx-include="#myelem". I just do it the slow way for debugging purposes. My Form.php's constructor eats up any POST variables and shoves them into my hidden DIV, storing state in the DOM elements. They'll be returned to us at the next POST. I made a simple get/set API in Form.php to manage the variables. By default, variables are loaded and stored with the name of the tab auto-prefixed to the variable, so you always get your own tab's version of the variable. You can also override this to look at variables in other forms, or just give a random name to create a namespace you can share variables under. Some basic lifecycle methods are supported. Your form is told to "unload" before you switch away to another tab, the "init" method is sent after the "open" method so your controls are on-screen. You have separate close and cancel methods so you can do a different cleanup when you close a form compared to cancelling the form, etc. Here is an example "init" method that initializes a form to some default values while also saving the contents even when you switch between tabs. public function init() { parent::init(); $email = $this->getEmailAddr($this->extdata); // validates as well if (isset($email)) { list($username,$domain) = explode("@", $email); $this->debug("Email address is $email"); $this->setField("email", $email, true); // true means to disable the element $this->setField("username", $this->getVar('username') ?? $username); $this->setField("displayname", $this->getVar('displayname') ?? ucfirst($username)); $this->setField("password", $this->getVar('password')); } else { $this->setTitle("Error"); $this->setInfo("Invalid Link!"); $this->close(5); return "Error"; // replaces button text } } Class Files So, here is the entire class file to handle logging in. The "I forgot my password" option just switches tabs to enter your email, so all that logic is in a separate form. The default close delay is 2 seconds, just so you can see the message. On error, you get to see it for 5 seconds. The "reloadpage" method inserts a <script> tag into the page that reloads using javascript. <?php namespace ProcessWire; class VRForm_login extends VRForm { // logout public function logout() { session()->logout(); $this->close(); echo "Success!"; // overwrites button text } // Catch the close, not destroy. Don't reload page on cancel public function afterclose() { $this->debug("called afterclose"); $this->reloadPage(); } // Processwire does all the magic public function login() { // Attempt to log in the user $user = session()->login($this->getVar('username'), $this->getVar('password')); // Check if login was successful if ($user) { // Login successful $this->setTitle("Login Succeeded!"); $this->setInfo("Welcome back<br>{$user->displayname}!"); $this->close(); } else { // Login failed $this->setTitle("Login Failed!"); $this->setInfo("Try again, or reset your password using the link below"); } } Conclusion As you see, it's fairly easy to set an ID on just about anything and then replace it in your webpage with data that comes from processwire. It's kind of in a glued together hack state right now with Form.php in need of some refactoring at 287 lines and another 142 lines in the form generator that handles the json bits, tab handling, etc. So, the stack is the subclass form handler -> Login.php base class -> jsonmangler.php (which may be split soon) -> Hannacode elements. Comments and advice is welcome. I'm not really planning on releasing it, but if there is interest I would certainly release it ... its just kinda in a massive state of flux at the moment if you know what I mean.
  2. $mailcheck = $modules->get('EmailVerification'); I keep getting a null response from my page template when it hits this line, resulting in an error 500. The module is installed in processwire. Any idea?
  3. OK, your screenshots don't look like plain UIKIT to me (what mine is set to). A few things to check ... First, check the Module->Configure for AdminTheme stuff. Make sure the right ones are installed and configured correctly. I think at least some of this is stored in the database, so deleting files and upgrade/downgrade stuff is going to miss what is in your database. Second, Have you tried opening the floating messages that are in the way in Chome's inspector (or Firefox, whatever). This will show you the CSS/JS in charge of positioning the elements. It will tell you what file it loaded those from, which tells you what files are the problem.
  4. OK, I have 3 simple textarea fields on my page. Each contains raw HTML, no filtering. If I edit the textarea and save it, it immediately reverts to the previous content and I have to edit twice and save twice. I think the issue is that I am testing a custom login screen. I never log out. I do however login again, and even though the login is successful, it refuses to save on the admin page. Is this due to a changing session key or something? Is there some way to work around this? The whole idea was to be able to easily make edits, and every test basically logs me out without telling me I am logged out. I get the same problem working in the admin interface. Does it eventually time out the login? I have been working on code in the textbox for awhile and it seems like if I wait too long to save, I get the login box and everything resets to the previously saved state. This is basically what is happening when I test the login screen, except that since I'm logged in, I never get the login box. It just fails to save and shows me the old data. Data loss due to these issues is extremely frustrating! Any ideas on how to make the admin interface behave when logging in again or when it times out?
  5. Is it possible to make a hanna code tag that spans multiple lines? For example .. [[ my-hanna-tag var1=value1 var2=value2 var3=value3 ]] I'm not seeing the code that is causing the CRs to break the parser. Ideas? OK, I got it working by adding a quick hack to TextFormatterHannaCode.module, in function formatValue(), add $value = preg_replace('/\n+/', ' ', $value);
  6. The "Add To Cart" buttons don't do anything on that site. They just sit there.
  7. Ok. Site is done with Ajax. I'm looking for whatever magic javascript call (preferably jQuery) I need to make to fixup the parts of the page that were loaded dynamically. These pieces don't have event hooks and such to trigger the front page editor. I have tried loading the jquery adapter for ckeditor and that makes $('.ckeditor').ckeditor() work without throwing an error, but that's not setting the hooks to pull up edit mode. I'm missing 1 more thing. Anyone know?
×
×
  • Create New...