MdMasud

WordPress, Laravel, Flutter

Blogs

  • Custom Cursor Navigation in SwiperJS – What I Built and How I Fixed It

    Today, I was working on a custom slider using SwiperJS, and I wanted to do something a bit more interactive than the usual arrows or dots.

    Instead of showing fixed navigation arrows, I thought it would be more fun to show floating “Next” and “Previous” text that follow the cursor, depending on which side of the slider you’re hovering. Kind of like a tail that hints where the slider will move if you click.

    It turned out to be a really nice effect – but getting it right wasn’t completely straightforward. Here’s how I made it work, and what issues came up along the way.

    What I Was Trying to Do

    • Show “Next” on the right side of the slider
    • Show “Previous” on the left
    • Make the text follow the cursor with a smooth animation (like a trailing effect)
    • Hide the floating text when hovering buttons inside each slide (so it doesn’t get in the way)

    The First Hurdle: Cursor Trail Jumping or Sticking

    At first, I tried updating the cursor-following text using transform: translate(...) inside mousemove, and it seemed okay. But it felt jumpy, and sometimes the text would just snap to the corner or get stuck.

    After a bit of testing, I realised the issue was that I was mixing transform with left/top positioning. That doesn’t play well when you’re trying to animate smoothly using requestAnimationFrame.

    The Fix: Left & Top + Easing Transition

    To get that smooth “trailing” or “wave” motion, I switched to updating left and top with easing. That way, the text glides behind the mouse rather than sticking to it like glue.

    This made a big difference. Now the text follows the cursor in a fluid way – not jerky, not laggy, just smooth.

    floatX += (mouseX - floatX) * 0.1;
    floatY += (mouseY - floatY) * 0.1;

    Next Issue: Hovering Over Buttons

    Each slide had a button in the bottom-right corner (like “Book Now”). But when you hovered over that button, the floating “Next”/”Previous” text would still be there — not ideal for usability or design.

    So I added a simple fix: detect when the user is hovering the button (or the caption area), and hide the floating text during that time.

    $(document).on('mouseenter', '.v-pageHead__caption', function () {
    	hoveringButton = true;
    	$('.floating-nav').css('opacity', 0);
    });
    
    $(document).on('mouseleave', '.v-pageHead__caption', function () {
    	hoveringButton = false;
    });

    Now, when you’re over the button, the cursor tail disappears. Clean and and tidy.

    Keeping It Clean: Only Run If Slider Is Active

    One more thing I wanted to avoid was unnecessary code running on pages where there was only one slide — or no slider at all.

    So I wrapped everything inside a conditional check like this:

    if ($('.v-pageHead__slide', '.v-pageHead__slider').length > 1) {
    	// Only run this whole block if more than 1 slide
    }

    That way, no Swiper is initialised unless needed, and no extra listeners or animations are running in the background.

    Finally, This was a fun little UX tweak — a bit fiddly at times, but the end result feels smooth and adds a nice touch. It’s the kind of detail that makes a site feel that bit more alive.

    If you’re using Swiper and fancy something a bit cooler than the usual arrows, give it a try! And if you want a peek at the code, just give me a shout here.

  • How I Replaced a Static PHP Product Array with a WordPress Custom Post Type + ACF + Database Sync

    How I Replaced a Static PHP Product Array with a WordPress Custom Post Type + ACF + Database Sync

    If you’ve hardcoded product data into a PHP file and later needed it to be editable from the admin panel without breaking your existing database logic — here’s exactly how I handled that.

    The Problem

    We had a static array inside a PHP class:

    $merch = TCA_Pos::MERCH;

    Which populated a merch section like this:

    <div class="merch-product" data-item-id="<?= $item['id'] ?>">
    	<span class="name"><?= $item['name'] ?></span>
    	...
    </div>

    This array was also linked (by ID) to entries in a custom table: wp_pos_order_merch.

    Updating a product required editing PHP and coordinating updates in the database. That had to change.

    The Goal

    • Move product data into WordPress Custom Post Type (merch)
    • Use ACF for cost and options fields
    • Allow admin users to manage everything from the backend
    • Keep existing database entries consistent by syncing old item_id values

    Step-by-Step: What I Did

    1. Created the CPT

    register_post_type('merch', [
    	'label' => 'Merch',
    	'public' => false,
    	'show_ui' => true,
    	'show_in_menu' => true,
    	'supports' => ['title', 'editor', 'thumbnail'],
    	'capability_type' => 'post',
    	'map_meta_cap' => true,
    	'menu_icon' => 'dashicons-cart',
    ]);

    2. Created ACF Fields

    • cost (number)
    • options (repeater of text values)

    3. Wrote an Importer Script

    This CLI script:

    • Deletes all old merch posts
    • Re-creates them from the static array
    • Captures a mapping between the old static id and the new post_id
    • Updates wp_pos_order_merch accordingly
    $wp_ref_ID_array[] = ['db_id' => $item['id'], 'new_id' => $post_id];
    
    foreach ($wp_ref_ID_array as $ref) {
    	$wpdb->update('wp_pos_order_merch', ['item_id' => $ref['new_id']], ['item_id' => $ref['db_id']]);
    }

    4. Replaced Front-End Static Array

    Now, instead of loading TCA_Pos::MERCH, I fetch merch posts dynamically:

    $posts = get_posts(['post_type' => 'merch', 'numberposts' => -1]);
    
    foreach ($posts as $post) {
    	$merch[] = [
    		'id' => $post->ID,
    		'name' => $post->post_title,
    		'cost' => get_field('cost', $post->ID),
    		'options' => array_column(get_field('options', $post->ID) ?: [], 'option'),
    	];
    }

    Why This Works

    • Data is now fully managed via WordPress admin
    • Existing item_id relationships remain valid
    • It’s scalable: new merch, price changes, sizes — no more code edits
    • It’s safe: your legacy table remains usable

    What Brought You Here

    If you’re searching for:

    • “How to migrate static PHP array to WordPress CPT”
    • “Sync CPT with custom database table”
    • “Use ACF to replace static data”
    • “WordPress CLI content importer with ID remapping”

    …this solution gives you a clean, flexible, future-proof way to get it done.

    If you need to update your current site or modernise how your data is managed get in touch, I’d be happy to help.

  • How I Automatically Routed Service Requests to the Right Branch in WordPress Using Gravity Forms, ACF, and Postcode Matching

    How I Automatically Routed Service Requests to the Right Branch in WordPress Using Gravity Forms, ACF, and Postcode Matching

    One of the most rewarding things about working with WordPress is finding elegant ways to connect the front end with real-world business logic. Recently, I built a feature for a client that automatically routes customer service requests to the correct branch of the company — based entirely on the postcode entered in a Gravity Form.

    Here’s a breakdown of how I did it using Gravity Forms, Advanced Custom Fields (ACF), and some clean PHP logic.

    The Problem: Which Branch Covers This Area?

    The client has multiple branches across the UK, each covering a specific set of postcode areas — like EH12, AB10, G1, etc. When a customer submits a service request through the form on their website, the business needs that request to be automatically forwarded to the branch responsible for that postcode.

    Manual routing wasn’t an option. It had to be smart, scalable, and easy to manage.

    The Stack

    • WordPress (custom theme)
    • Gravity Forms for form handling
    • Advanced Custom Fields (ACF) to attach postcode coverage info to taxonomy terms
    • Custom PHP logic to handle the postcode matching and data routing

    The Data Structure

    Each branch is stored as a custom taxonomy term (location) in WordPress. Each term has an ACF repeater field called postcode_area_coverage, and inside that repeater, a subfield named code holds the outward postcode areas like EH12, G1, AB10.

    So the structure looks like this:

    location (taxonomy term)
    └── postcode_area_coverage (repeater)
    ├── code: EH1
    ├── code: EH2
    └── code: EH12

    The Gravity Form

    The form collects the usual user details, including a postcode field. Behind the scenes, I validate the format of the postcode, extract the outward part (everything before the space), and match it against the postcode coverage of each branch.

    Once matched, I automatically store the matched branch in a hidden Gravity Forms field. That field can then be used to:

    • Route notifications
    • Display confirmation messages
    • Track analytics

    Extracting the Outward Code from a Full Postcode

    if (preg_match('/^([A-Z]{1,2}[0-9][0-9A-Z]?)/', strtoupper(trim($postcode)), $matches)) {
        $outward = $matches[1]; // e.g. "EH12" from "EH12 5AB"
    }

    This snippet takes the full UK postcode submitted in the form and extracts the outward code (first part). That’s what I use to match against each branch’s coverage.

    Matching the Postcode to a Branch via ACF Repeater

    $terms = get_terms(['taxonomy' => 'location', 'hide_empty' => false]);
    foreach ($terms as $term) {
        $coverage = get_field('postcode_area_coverage', 'location_' . $term->term_id);
        foreach ($coverage as $row) {
            if (strtoupper($row['code']) === $outward) {
                return $term->term_id; // Match found
            }
        }
    }

    Here, I loop through each location taxonomy term, check their ACF postcode coverage, and find a match with the user’s outward postcode. Once matched, I return that branch’s ID.

    Updating a Gravity Form Field After Submission

    GFAPI::update_entry_field($entry['id'], self::LOCATION_SELECTED_FROM_POSTCODE, $branch_id);

    After finding the correct branch, I updated a hidden field in the submitted Gravity Form entry so it stores which branch should handle the request. This value is later used to route notifications.

    At the End

    • The business now handles customer requests faster and more efficiently.
    • Postcode coverage can be updated by non-technical users directly in the WordPress admin.
    • The system is scalable — just add a new location with postcode areas, and it’s instantly supported.

    It’s always fun to bridge the gap between user input and smart business logic. This was a great example of how custom taxonomy, ACF, and Gravity Forms can work together to create something genuinely useful — with no need for heavyweight plugins or complex integrations.

  • Automating Postcode Area Coverage in WordPress with ACF and CSV Import

    In a recent project, I had to bulk-import postcode data into a WordPress site using Advanced Custom Fields (ACF). Each postcode had to be linked to a custom taxonomy term (location), and each term had a repeater field named postcode_area_coverage, which stores multiple entries under a subfield called code.

    The data came from a simple CSV file provided by the business manager. Instead of manually entering data term by term, I wrote a script that matched postcodes to taxonomy terms based on email addresses and updated everything programmatically.

      The Data Format

      The CSV I received looked like this:

      EH1,,branch1@email.com  
      EH2,,branch1@email.com  
      AB10,,branch2@email.com  
      

      The logic was straightforward: match each postcode to the correct location using the associated email, then populate the repeater field accordingly.

      I wrote a PHP script that handled the following:

      • Loaded all location terms from the taxonomy.
      • Built a reference map using each term’s ACF email field.
      • Read the CSV file row by row.
      • Matched each row’s email to a location term.
      • Grouped the postcodes by term ID.
      • Updated the postcode_area_coverage repeater using update_field().

      The Code.

      <?php
      
      include('wp-load.php'); // adjust path as needed to load WordPress context
      
      $location_ref = [];
      $taxonomy = 'location';
      
      // Step 1: Build reference map of emails to term IDs
      $locations = get_terms([
          'taxonomy' => $taxonomy,
          'hide_empty' => false,
      ]);
      
      if (!empty($locations) && !is_wp_error($locations)) {
          foreach ($locations as $location) {
              $email_field = get_field('email', 'location_' . $location->term_id);
              if ($email_field) {
                  $location_ref[trim($email_field)] = [
                      'term' => $location->term_id,
                      'email' => trim($email_field)
                  ];
              }
          }
      }
      
      $postcodes_csv = 'postcode.csv'; // path to your CSV file
      $data_array = [];
      $orphan_postcode = [];
      
      if (($fh = fopen($postcodes_csv, 'r')) !== false) {
          while (($data = fgetcsv($fh, 1000, ',')) !== false) {
              $postcode = trim($data[0] ?? '');
              $email = trim($data[2] ?? '');
      
              if ($postcode) {
                  if ($email && isset($location_ref[$email])) {
                      $term_id = $location_ref[$email]['term'];
                      $data_array[$term_id][] = ['code' => $postcode]; // 'code' is the ACF subfield
                  } else {
                      $orphan_postcode[] = $postcode;
                  }
              }
          }
          fclose($fh);
      }
      
      // Step 2: Update each term’s repeater field
      foreach ($data_array as $term_id => $postcodes) {
          update_field('postcode_area_coverage', $postcodes, $taxonomy . '_' . $term_id);
      }
      
      // Optional: Printing orphan postcodes for debugging
      if (!empty($orphan_postcode)) {
          echo "Postcodes with no matching email:<br>";
          echo implode(', ', $orphan_postcode);
      }
      
      

      What This Does

      • Reads the CSV line by line.
      • Trims and sanitises inputs to avoid matching issues.
      • Maps each row to the correct taxonomy term using the email ACF field.
      • Builds an array in ACF repeater format ([ ['code' => 'EH1'], ... ]).
      • Uses update_field() to push postcode data into each term.

      Why I Like This Approach

      • It’s fast and scalable.
      • It makes use of existing ACF structures — no custom database tables or plugins needed.
      • It’s repeatable: anytime the business updates postcode data, I just run the script again.

      This was a small but satisfying piece of dev work — a perfect example of automating the boring stuff. If you’re working with taxonomy terms, ACF, or bulk content in WordPress, scripting these kinds of updates can save hours of time and prevent manual errors.

    • How to Keep Dropdown Submenu Open After Gravity Form AJAX Submit

      While working on a WordPress site, I embedded a Gravity Form inside a dropdown submenu of the navigation menu. The submenu appears on hover, and inside it, users can submit an enquiry form via Gravity Forms using AJAX.

      Everything worked fine, until the form was successfully submitted.

      Good : If validation failed, the form stayed open and errors were visible.

      Bad : But when the form successfully submitted, the confirmation message would briefly flash — and the submenu would immediately disappear, making it look like nothing happened.

      This wasn’t a great user experience, especially if users didn’t realise their form had gone through.

      Why It Happens

      When Gravity Forms submits via AJAX, it dynamically replaces the form’s content with the confirmation message. This causes a DOM update that disrupts the hover state of the submenu, especially if it’s tied to :hover CSS.

      So, the submenu disappears — not because of an error, but because the browser no longer sees the user as “hovering” during or right after the form update.

      The Solution

      The fix is to manually reapply a class to keep the submenu open when the form is successfully submitted, then remove that class when the user moves the mouse away.

      You don’t need any complex JavaScript frameworks — just a few lines of jQuery and a little CSS.

      jQuery(document).ready(function($) {
       var menuItem = $('#menu-item-1798'); // ID of the parent menu item
      
       // When the form is successfully submitted via AJAX
       $(document).on('gform_confirmation_loaded', function(event, formId) {
        if (formId === 5) { // Replace with your actual form ID
         menuItem.addClass('keep-open');
        }
       });
      
       // When user moves their mouse out of the menu item, close it
       menuItem.on('mouseleave', function() {
        if (menuItem.hasClass('keep-open')) {
         menuItem.removeClass('keep-open');
        }
       });
      });

      Optional CSS

      Add proper style to your stylesheet to ensure the menu stays visible when the keep-open class is active.

      The Output

      With this small enhancement:

      • The submenu remains open after a successful Gravity Form submission.
      • Users can clearly see the confirmation message.
      • The menu closes cleanly once the user moves away.

       It works beautifully without any plugin overhead or redesigns.

      Have you run into a similar problem? Got a cleaner approach ? Drop your thoughts in the comments!

    • Why Neovim is a Game-Changer for Developers

      When I first started using Neovim, it felt overwhelming, especially since I was accustomed to code editors like VS Code and NetBeans for development. However, after a few days of exploration and practice, I began to see a significant boost in my productivity. For developers seeking a powerful, customisable, and efficient text editor, Neovim offers a unique advantage over many other editors. While the learning curve can be steep for newcomers, the benefits of using Neovim, especially when configured properly, are immense. Here’s why Neovim can supercharge your development workflow.

      1. Supercharge Productivity with Shorthand Commands

      Neovim thrives on modal editing—its command, insert, and visual modes allow developers to perform complex text manipulations with minimal keystrokes. Custom mappings and plugins make it possible to:

      • Jump to specific lines, blocks, or characters effortlessly.
      • Perform multi-line edits in seconds.
      • Create macros to repeat tasks, saving countless hours during repetitive coding tasks.

      With practice, these features can cut down editing time significantly.

      Basic Shorthand Commands

      To truly harness the power of Neovim, you can start with these essential commands. They cover everything from navigation to editing and can significantly reduce the time spent on repetitive tasks:

      Navigation

      • h,j,k, Move the cursor left, down, up and right respectively
      • w Move the cursor to the beginning of the next word.
      • b Move the cursor to the beginning of the previous word.
      • e Move the cursor to the end of the current word.
      • gg Move the cursor to the beginning of the file.
      • G Move the cursor to the end of the file.

      Editing

      • i Enter insert mode at the current cursor position.
      • I Enter insert mode at the beginning of the current line.
      • a Enter insert mode after the current cursor position.
      • A Enter insert mode at the end of the current line.
      • o Insert a new line below the current line and enter insert mode.
      • O Insert a new line above the current line and enter insert mode.
      • r Replace the character under the cursor.
      • R Enter replace mode.
      • x Delete the character under the cursor.
      • X Delete the character before the cursor.
      • dd Delete the current line.
      • yy Copy(yank) the current line.
      • p or P Paste after/before the cursor.
      • u Undo the last change.
      • Ctrl - r Redo the last undone change.
      • Esc Exit insert mode.

      Saving and quitting

      • :w Save the current file.
      • :q Quit the editor.
      • :wq Save and quit the editor.
      • :q! Quit without saving changes.

      Splitting windows

      • :split Split the window horizontally.
      • :vsplit Split the window vertically.
      • :q Close the current window.

      Search

      • /{pattern} Search for the specified pattern.
      • n Jump to the next match of the current search pattern.
      • N Jump to the previous match of the current search pattern.

      Help

      • :help {pattern} Search for the specified pattern.
      • :help user-manual Open the Neovim user manual in a browser.

      If you’re new to Neovim, check out this list for a deeper dive into mastering these commands.

      2. Perfect Search and Navigation

      Neovim provides a robust built-in search system with support for:

      • Regex searches to find specific patterns in your code.
      • Search highlighting to spot results instantly.
      • Search history, so you can quickly repeat previous queries.

      Combined with plugins like Telescope, searching across files and projects becomes effortless. Whether it’s locating a method, function, or a deeply nested file, Neovim ensures you never lose your way.

      3. Efficient Code Exploration

      Navigating large files or projects can be daunting, but Neovim makes it intuitive:

      • Use tags to jump to function or variable definitions with :tag.
      • Explore file structures with tree-based plugins like NvimTree or Fern.
      • Jump between related code blocks, functions, or variables with plugins like Hop or EasyMotion.

      For large-scale projects, these features significantly reduce the time spent scrolling and searching.

      4. Unmatched Customisability

      Neovim’s Lua-based configuration allows developers to tailor their setup to suit specific needs. This flexibility enables:

      • Custom keybindings to streamline frequently used commands.
      • Personalized color schemes for a visually pleasing workspace.
      • Integration with modern development tools like LSP (Language Server Protocol) and treesitter for enhanced syntax highlighting and code intelligence.

      The ability to fine-tune every aspect of your editor ensures it works exactly the way you want it to.

      5. Plugin Ecosystem

      Neovim’s active community continually develops plugins that extend its functionality:

      • Auto-completion: With tools like nvim-cmp, get intelligent code suggestions.
      • Git Integration: View changes inline or manage repositories directly using Fugitive or Lazygit.
      • Debugging: Plugins like nvim-dap enable a full debugging environment within Neovim.

      These plugins elevate Neovim into a full-fledged IDE.

      6. Lightweight and Fast

      Unlike many modern editors that can be resource-intensive, Neovim remains lightweight and fast. It starts up quickly and can handle large files without lag, making it an excellent choice for both local and remote development.

      7. Cross-Platform Compatibility

      Neovim runs on macOS, Linux, and Windows, ensuring a consistent development environment regardless of the operating system. With its terminal-based interface, you can even use it over SSH for remote work.

      8. A Tool That Grows With You

      Neovim’s steep learning curve is an investment that pays off. The more you use it, the more efficient you become. Over time, you’ll discover advanced features like:

      • Buffer management for working on multiple files.
      • Command chaining for executing complex tasks with a single keypress.
      • Extending functionality with Lua scripting.

      Neovim is more than just an editor—it’s a productivity powerhouse for developers. If you’re looking to elevate your development workflow, I highly recommend giving Neovim a try. Although it might feel challenging at first, like it did for me, the productivity gains and customisation potential make it an invaluable tool for developers.

    • Welcome to My Developer Blog

      Welcome to My Developer Blog

      Hello

      Welcome to My Personal Blog on the World of Web Development! I’m excited to share this space with you—a hub for all things web development. Whether you’re a fellow developer, a student diving into code for the first time, or a business owner curious about what makes a website tick, you’re in the right place.

      On this blog, I’ll be covering a range of development topics, from the basics to more advanced projects and tips.
      Here’s a glimpse of what you can expect:

      WordPress Development:

      Tips, tricks, and tutorials on everything from theme customization to plugin development.

      PHP and Laravel

      Insights into backend development, focusing on PHP fundamentals and building robust applications with Laravel.

      JavaScript & jQuery

      From beginner-friendly JavaScript guides to advanced use of jQuery, we’ll explore the most versatile language in web development.

      React & Next.js

      Frontend frameworks like React and Next.js will feature heavily, especially when it comes to building dynamic, user-friendly interfaces.

      HTML, CSS & Bootstrap

      We’ll cover essentials, design tips, and responsive layout strategies that form the backbone of any website.

      I’ll also share project ideas, troubleshooting tips, and the lessons I learn along the way. Whether you’re interested in practical how-tos, deep dives into frameworks, or ways to stay updated in this ever-evolving field, I hope my blog becomes a valuable resource for you.