惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Y
Y Combinator Blog
GbyAI
GbyAI
U
Unit 42
WordPress大学
WordPress大学
Last Week in AI
Last Week in AI
P
Proofpoint News Feed
D
DataBreaches.Net
N
Netflix TechBlog - Medium
H
Hackread – Cybersecurity News, Data Breaches, AI and More
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
C
Check Point Blog
Martin Fowler
Martin Fowler
月光博客
月光博客
MongoDB | Blog
MongoDB | Blog
MyScale Blog
MyScale Blog
The Cloudflare Blog
Apple Machine Learning Research
Apple Machine Learning Research
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
M
MIT News - Artificial intelligence
云风的 BLOG
云风的 BLOG
罗磊的独立博客
B
Blog RSS Feed
J
Java Code Geeks
The GitHub Blog
The GitHub Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
🛠️ Developer's Guide: Mastering Programmatic Row Injectio...
Explorer · 2026-05-11 · via DEV Community

Explorer

Overview

For developers building complex Joget applications, the Advance Grid (based on PQGrid) offers immense flexibility. While standard data entry is straightforward, there are scenarios—like generating monthly timesheets or project schedules—where you need to programmatically inject dozens of rows and perform real-time calculations. This guide dives deep into using JavaScript, Moment.js, and the PQGrid API to automate grid management.

How It Works

The technical implementation centers on a few core developer concepts:

  1. Date Iteration: Using moment.js to loop through a range.
  2. PQGrid DataModel Manipulation: Accessing the grid's internal dataModel to read and write cell values.
  3. Asynchronous Row Handling: Because UI triggers (like adding a row) take time to reflect in the DOM, we use Promises and setTimeout to ensure data is only written once the row exists.
  4. PQGrid Refresh: Manually updating the .pq-td-div and the underlying hidden textarea that Joget uses to persist grid data to the database.

Where to Use in Joget

  • Form Builder: Add a Custom HTML element to your form.
  • Dependencies: Ensure your Joget environment has access to jQuery (standard) and include the Moment.js library via CDN as shown in the code.

Full Code

<input value='Apply' type='button' id="apply" class="form-button" style="float: right;" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>

<script>
  $(document).ready(function () {
    /**
     * Programmatically sets a value in a specific cell of the Advance Grid
     * @param {Object} grid - The jQuery object of the grid field
     * @param {number} rowIndx - Row index (starting from 0)
     * @param {number} colIndx - Column index (mapping to the columnKey array)
     * @param {string} colValue - The value to be set
     */
    function setValue(grid, rowIndx, colIndx, colValue) {
      return new Promise(resolve => {
        setTimeout(() => {
          var DM = $(grid).find(".pq_grid").pqGrid("option", "dataModel");
          var data = DM.data;

          // Access the hidden JSON string stored in the last column of the row
          var json = data[rowIndx][data[rowIndx].length - 1];
          var obj;
          try {
            obj = JSON.parse(json);
          } catch (e) {
            console.error('Error parsing JSON:', e);
            resolve();
            return;
          }

          var colKey = DM.columnKey[colIndx];
          obj[colKey] = colValue;

          // Update dataModel
          json = JSON.stringify(obj);
          DM.data[rowIndx][colIndx + 1] = colValue; // Index + 1 because column 0 is the row index
          DM.data[rowIndx][data[rowIndx].length - 1] = json;

          // Sync with the hidden table (Joget's persistence layer)
          var indexKey = DM.data[rowIndx][0];
          var tbl = $(grid).find('.table_container table');
          tbl.find("tr.key_" + indexKey).find("[column_key=" + colKey + "]").text(colValue);
          tbl.find("tr.key_" + indexKey).find("textarea").val(json);

          // Update visual UI cell
          var colCell = $(grid).find("tr[pq-row-indx=" + rowIndx + "] .pq-grid-cell[pq-col-indx=" + (colIndx + 1) + "]");
          $(colCell).find(".pq-td-div").html('<div class="subform-cell-value"><span>' + colValue + '</span></div>');

          resolve();
        }, 100);
      });
    }

    $("#apply").click(async function () {
      var gridWidget = $(FormUtil.getField("statusgrid")).find(".pq_grid");

      // 1. Clear existing data and UI rows
      gridWidget.pqGrid("option", "dataModel.data", []);
      $(FormUtil.getField("statusgrid")).find('.table_container table tbody tr.grid-row').remove();

      // 2. Business Logic Validation
      var BillNo = $('select[name="if_bill_exist"]').val();
      if (BillNo !== null) {
        alert("This TimeSheet bill is already Created");
        return;
      }

      var advgrid = FormUtil.getField("statusgrid");
      var fromDate = $('input[name="from_date"]');
      var toDate = $('input[name="to_date"]');
      var newFromDate = moment(fromDate.val(), 'DD-MM-YYYY');
      var newToDate = moment(toDate.val(), 'DD-MM-YYYY');

      var normalHours = $('select[name="normal_hours"]');
      var WeekendOverTimeH = $('[name="weekend_overtime_hour"]');
      var overTimeHour = $('#overtime_hours');

      var selectedCheckboxValues = [];
      $("input[name='select_overtime_hour_type']:checked").each(function () {
        selectedCheckboxValues.push($(this).val());
      });

      var rowIndex = 0;
      var promises = [];

      let sum_of_Normal_working_hours = 0;
      let sum_total_days = 0;
      let sum = 0;
      let weekendsum = 0;
      let publicsum = 0;

      // 3. Row Generation Loop
      for (var date = moment(newFromDate); date.isSameOrBefore(newToDate); date.add(1, 'days')) {
        var dayOfWeek = date.format('dddd');
        var isWeekHoliday = $('#week_holiday_chosen ul li:eq(0)').text().toLowerCase().split(';').includes(dayOfWeek.toLowerCase());

        var hour = (isWeekHoliday || !selectedCheckboxValues.includes('Normal Hour')) ? '' : normalHours.val();
        var day_calc = (isWeekHoliday) ? '' : 1;
        var weekend_overtime_hour = (isWeekHoliday && selectedCheckboxValues.includes('Weekend OverTime')) ? WeekendOverTimeH.val() : '';
        var overtime_hour = (!isWeekHoliday && selectedCheckboxValues.includes('OverTime Hour')) ? overTimeHour.val() : '';

        // Add row via Joget's native icon trigger
        advgrid.find(".ui-icon-circle-plus").trigger("click");

        // 4. Batch Updates via Promises
        promises.push(setValue(advgrid, rowIndex, 0, date.format('MMM'))); // Month
        promises.push(setValue(advgrid, rowIndex, 1, date.date()));        // Day
        promises.push(setValue(advgrid, rowIndex, 2, dayOfWeek);          // Weekday
        promises.push(setValue(advgrid, rowIndex, 3, date.year()));       // Year
        promises.push(setValue(advgrid, rowIndex, 4, date.format('DD-MM-YYYY'))); // Full Date
        promises.push(setValue(advgrid, rowIndex, 5, hour));
        promises.push(setValue(advgrid, rowIndex, 6, day_calc));
        promises.push(setValue(advgrid, rowIndex, 7, overtime_hour));
        promises.push(setValue(advgrid, rowIndex, 8, weekend_overtime_hour));
        promises.push(setValue(advgrid, rowIndex, 14, date.format('YYYYMMDD'))); // Unique ID

        // Aggregate totals for the parent form
        sum_of_Normal_working_hours += parseFloat(hour) || 0;
        sum_total_days += parseFloat(day_calc) || 0;
        sum += parseFloat(overtime_hour) || 0;
        weekendsum += parseFloat(weekend_overtime_hour) || 0;

        rowIndex++;
      }

      await Promise.all(promises);
      Calculate({
        sum_of_Normal_working_hours,
        sum_total_days,
        sum,
        weekendsum,
        publicsum: 0 // logic to be added based on public_holiday field
      });
    });

    function Calculate(obj) {
      $('[name=total_normal_working_hours]').val(obj.sum_of_Normal_working_hours);
      $('[name=total_days]').val(obj.sum_total_days);
      $('[name=total_overtime_hour]').val(obj.sum);
      $('[name=total_weekend_overtime_hour]').val(obj.weekendsum);
    }
  });
</script>

Enter fullscreen mode Exit fullscreen mode

Example Use Cases

  • 📅 Timesheet Automation: Generate a row for every day of the month based on a selection.
  • 📊 Financial Projections: Populate a grid with 12 months of empty rows for budgeting.
  • Inventory Audits: Generate rows for all items in a specific category to be checked.

Customization Tips

  • ⚙️ Column Keys: Ensure the column keys in the PQGrid dataModel match the order of your setValue calls.
  • ⚙️ Masking/Placeholders: If using database-linked data, replace statusgrid with your specific grid ID.
  • ⚙️ Timeout Duration: The 100ms delay in setValue is a safe buffer; for large grids (50+ rows), you may need to optimize the injection logic.

Key Benefits

  • Developer Control: Bypass manual entry constraints and manipulate data directly in the grid's memory.
  • Rich UX: Instant feedback for users with automatic summaries.
  • Standardization: Enforces date formats and calculation logic client-side.

🔒 Security Note

Since this is a client-side script, developers should treat this as a UX Enhancement only. Always implement Server-Side Validation (BeanShell) to verify that the submitted totals match the grid data, as client-side values can be modified by end-users via the console.