PHP-Javascript complex interactions in WordPress

I’ve been stuck on a complex problem for several days and I haven’t found anything to unblock it.

  • I’ve created a wordpress plugin, called keywordize, that allows me to modify keywords from about 400 texts sorted by years. I use Mistral AI for that.
  • I call the plugin within a wordpress page with a shortcode [keywordize]
  • Here is the keywordize.php file :
function keywordize_enqueue_scripts()
{
    wp_enqueue_style('keywordize-style', plugin_dir_url(__FILE__) . 'style.css');
    wp_enqueue_script('keywordize-script', plugin_dir_url(__FILE__) . 'script.js', array('jquery'), null, true);

    // Get data from DB 
    global $wpdb;
    $r_sujets = "SELECT * FROM Sujet ORDER BY AnneeS DESC;";
    $q_sujets = $wpdb -> get_results($r_sujets, ARRAY_A);
    $php_vars = array(
        'sujets' => $q_sujets,
        'pluginDir' => plugin_dir_url(__FILE__),
    );
    wp_localize_script('keywordize-script', 'php_vars', $php_vars );    
}
add_action('wp_enqueue_scripts', 'keywordize_enqueue_scripts');

function keywordize_shortcode()
{
    ob_start(); ?>
    <div id="keywordize">
        <div id="timeout-wrapper">
            Timeout :  <input id="timeout" type="text"/>
        </div>
        <div id="years-select-wrapper">
            Sélection : 
            <select id="years-select">
                <option value=""></option>
            </select>
        </div>
        <div id="submit-wrapper">
            <button id="submit">Submit</button>
        </div>
        <div id="years-select-wrapper"></div>
        <div id="subjects-by-year-wrapper"></div>
        <div id="submit-subjects-by-year"></div>
    </div>
    <div id="update"><?php
        include_once('update.php');
    ?></div>    
<?php
    return ob_get_clean();
}
add_shortcode('keywordize', 'keywordize_shortcode');
  • This php file loads a js file script.js. This file takes the result of my php request, calls Mistral AI on each text to get keywords from the text, then add lines to the page:
jQuery(document).ready(function ($) {
    // get variables from keywordize.php
    const sujets = php_vars.sujets;
    const pluginDir = php_vars.pluginDir;
    console.log("sujets", sujets);

    const tempAnnees = sujets.map(sujet => sujet.AnneeS);
    const annees = [...new Set(tempAnnees)];
    console.log("annees", annees);

    $("#timeout").val(1000);

    annees.forEach(function (annee) {
        $("#years-select").append('<option value="' + annee + '">' + annee + '</option>');
    });

    $("#submit").on('click', function () {
        if ($(this).text() == "Reset") {
            $("#timeout").val(1000);
            $("#years-select").val("");
            $(this).text("Submit");
        } else {
            $(this).text("Reset");
            const annee = $("#years-select").val();
            const timeout = $("#timeout").val();
            $("#subjects-by-year-wrapper").text("");

            const sujetsParAnnee = sujets.filter(function (sujet) {
                return sujet.AnneeS == annee;
            });
            console.log("sujetsParAnnee", sujetsParAnnee);
            $("#subjects-by-year-wrapper").append("<br/><h3>" + sujetsParAnnee.length + " sujets pour l'année " + annee + "</h3>");

            getKeywords(sujetsParAnnee, timeout, function () {
                console.log("FINISHED");
            });
        }

    });

    function getOptions(resume) {
        return options = {
            method: 'POST',
            headers: {
                'Authorization': 'Bearer API_KEY',
                'Content-Type': 'application/json',
                'Accept': 'application/json'
            },
            body: JSON.stringify({
                "model": "mistral-large-2407",
                "messages": [
                    {
                        "role": "user",
                        "content": "Donne moi 4 mots-clés issus de ce texte : " + resume
                    }
                ]
            }),
        }
    }

    function modifyKeywords(submitSubject, idS, keywordsArray) {
        console.log("let's replace keywords of subject " + idS);
    }

    async function getKeywords(sujets, timeout, callback) {
        const url = "https://api.mistral.ai/v1/chat/completions";
        const nbSujets = sujets.length;
        const delay = (ms = timeout) => new Promise(r => setTimeout(r, ms));
        let i = 1;
        for (let sujet of sujets) {

            // Construction des lignes d'accueil de la réponse
            const subjectLineWrapper = document.createElement("div");

            const formWrapper = document.createElement("div");
            formWrapper.setAttribute("class", "form");
            const form = document.createElement("form");
            form.setAttribute("action", "javascript:;");

            const subjectWrapper = document.createElement("div");
            subjectWrapper.setAttribute("class", "subject");
            const subject = document.createElement("div");

            try {
                const resume = sujet.TitreS + "." + sujet.DescriptionS;
                const response = await fetch(url, getOptions(resume));
                if (!response.ok) {
                    throw new Error('Response status: ' + response.status);
                }
                const json = await response.json();
                const keywords = json.choices[0].message.content;
                // console.log(keywords);

                // REGEX sur réponse IA keywords
                const keywordsArray = [];
                const regex = /^[0-9]+. (.*(?:n(?![0-9]+.).*)*)/gm;
                // const regex = new RegExp('^[0-9]+\. (.*(?:\n(?![0-9]+\.).*)*)', 'gm')
                let match;
                while ((match = regex.exec(keywords)) !== null) {
                    keywordsArray.push(match[1].replaceAll("*", ""));
                }
                // console.log("keyword array = ", keywordsArray);

                // Mise en forme et ajout texte des lignes de réponse

                if (i % 2 == 0) subjectLineWrapper.setAttribute("class", "subject-line subject-line-gray");
                else subjectLineWrapper.setAttribute("class", "subject-line subject-line-white");

                const button = document.createElement("input");
                button.setAttribute("type", "submit");
                button.setAttribute("value", "Replace keywords");
                form.appendChild(button);
                formWrapper.appendChild(form);
                form.addEventListener('submit', function (event) {
                    modifyKeywords(event.submitter.value, sujet.IdS, keywordsArray);
                });

                subject.innerHTML += "(" + i + "/" + nbSujets + ") "
                    + sujet.Theme1 + " / "
                    + sujet.Theme2 + " / "
                    + sujet.Theme3 + " / "
                    + sujet.Theme4 + " / "
                    + " => " + keywordsArray;

                subjectLineWrapper.appendChild(subjectWrapper);
                subjectLineWrapper.appendChild(formWrapper);

            } catch (error) {
                subject.setAttribute("class", "subject-red");
                subject.innerHTML += "(" + i + "/" + nbSujets + ") " + sujet.TitreS + " > " + error.message;
                subjectLineWrapper.setAttribute("class", "subject-line subject-line-red");
                subjectLineWrapper.appendChild(subjectWrapper);
                // console.error(error.message);
            }
            subjectWrapper.appendChild(subject);
            document.getElementById("subjects-by-year-wrapper").appendChild(subjectLineWrapper);
            await delay();
            i++;
        };
        callback();
    }
});
  • Everything works perfectly, and I get this result:

Page result after JS treatment

  • My problem is now this one : how to treat each line, so update each subject (I want it one by one to avoid errors), by clicking each button one by one ? I put here the research I’ve made so far :
function modifyKeywords(submitSubject, idS, keywordsArray) {
        console.log("let's replace keywords of subject " + idS);
        $.ajax({
            url: pluginDir + "update.php",
            type: "POST",
            data: { 
                "submitSubject": submitSubject,
                "idS": idS,
                "keywordsArray": keywordsArray,
            },
            error: function (xhr, status, error) {
                console.log("Error: " + error);
            },
            success: function (result, status, xhr) {
                console.log("Success: " + result);
            }
        });
    }
  • So with AJAX, I send some variables to another php file, called update.php :
$submitSubject = $_POST['submitSubject'];
$idS = $_POST['idS'];
$keywordsArray = $_POST['keywordsArray'];
if (isset($idS))
    echo "idS = " . $idS . "<br/>";
  • But I’m stuck here because when I click a button, I obtain only a console output, which is my AJAX success.
Success: idS = 585<br/>
  • Or if I modify update.php with $r_subject = "SELECT * FROM Sujet WHERE IdS = $idS;";echo "request = " . $r_subject . "<br>";:
Success: request = SELECT * FROM Sujet WHERE IdS = 586;<br>
  • Then I have another error when I execute the request, but it will be another question soon on SO 🙂

So what I want to be able to do is to interact with update.php when I click a button. How to display something in my php file update.php without refreshing ? How my php file knows that some modifications have been made by javascript on itself ? I’m lost 🙂

Many thanks

appsflyer-cordova-plugin not opening app installed from XCode

  • XCode Version: 16.0
  • IOS Version: IOS18
  • cordova-appsflyer Version: 6.15.3
  • cordova-ios version: 7.1.0

  • We have an existing app that is published but is limited to countries outside our area.

  • I am trying to integrate Appsflyer to the app and test it on a build installed from XCode on my local phone IOS18.

  • Associated domains has also been added to XCode

  • After clicking the Appsflyer link, it opens a browser and tells me that the app is not available in our country.
    App Not Available

  • The SDK integration based on the Appsflyer Wizard is successful
    enter image description here

select box cell renderer does not update when deleting a react table row

Could you please take a look into the tiny sandbox project?

https://codesandbox.io/p/sandbox/festive-nova-s58h2v

I have a simple react table, each row contains a “raw” data and the same data as a select box value.
when I delete a row, the table is rerendered correctly, the “raw” data is displayed correctly,
but the selected value in the select boxes stay unchanged.
Why?

Thanks in advance!

Is there a better way to make a menu bar similar to my complex solution? [closed]

I am trying to make a menu that looks nice… but to implement it I added a lot of long things, states, and code. It took hundreds of lines to implement it.

So I am asking – are there any alternatives? Or some way(s) I can implement what I have now easier/more efficiently?

This feature: when you click on a choice in the menu you go ‘deeper’, but the deeper window comes from the right, and the previous one goes out to the left.

I am using React and Tailwind CSS.

To see my code here is a link to CodeSandbox.

my react code looks bad, and every time a implement new feature it gets worse

I am trying to make a menu that looks nice little bit, but to implement it I did a lot of things, used many states, a lot of code, and hundreds of lines to implement it,

so I am asking about any alternative or can I implement it easier?

“The feature is when you click on a choice in the menu you go deeper but the deeper window comes from the right, and the previous one goes out to the left”

I use:

  • react
  • HTML
  • tailwind CSS

To see my code here is a link to CodeSandBox.

Vue.js 3 – pass contents of a component’s slot to another slot outside it

I have a component Foo with the following contents:

<Bar>
  <template #option="slotProps" v-if="$slots.option">
    <slot name="option" v-bind="slotProps"></slot>
  </template>
</Bar>

<div>
  <slot :selected="selectedOption"></slot>
</div>

Is there a way to pass the contents of

<slot name="option" v-bind="slotProps"></slot>

to

<slot :selected="selectedOption"></slot>

by default, while still having the ability to override it if needed?

I tried this but it didn’t work

    <slot :selected="selectedOption">
      <template v-if="$slots.option">
        <slot name="option" :selected="selectedOption"></slot>
      </template>
    </slot>

ag-chart How to adjust corner radius of series for stacked bar chart

I try to achieve stacked bar chart with round border as
goal design

here is what I try with the sandbox

    theme: {
      overrides: {
        bar: {
          series: {
            fillOpacity: 0.8,
            cornerRadius: 8,
            itemStyler: (s) => {
              if (s.yKey == "iphone") {
                return {
                  cornerRadius: 999,
                  fillOpacity: 0.1,
                  fill: "#cf5200",
                };
              }
            },
          },
        },
      },
    },

the cornerRadius inside itemStyler is not doing anything

https://codesandbox.io/p/devbox/stacked-bars-forked-6fqld7?workspaceId=ws_33rxSAV11fcwYzWEHLCn6X

Can I get the Cypress API to run `.feature` files as well as `spec` files?

I’ve got a mixture of Cucumber .feature files and normal Cypress .spec.js files.

Normally, they run together fine as I have included:

specPattern: ["cypress/e2e/**/*.spec.js", "cypress/e2e/*.feature"],

in my cypress.config.js file.

However, I want to use Cypress’s Module API, eg:

const mySpecs = [
    `homePage.spec.js`,
    `aboutPage.spec.js`
];

cypress.run({
    browser: 'chrome',
    spec: mySpecs,
});

The above works, but not if I also have a .feature file.

const mySpecs = [
    `homePage.spec.js`,
    `aboutPage.spec.js`,
    `contactPage.feature`
];

I’ve tied modifying the above, eg:

cypress.run({
    browser: 'chrome',
    spec: mySpecs,
    config: {
        specPattern: ["cypress/e2e/**/*.spec.js", "cypress/e2e/*.feature"],
    }
});

and

cypress.run({
    browser: 'chrome',
    specPattern: ["cypress/e2e/**/*.spec.js", "cypress/e2e/*.feature"],
    spec: mySpecs
});

Does anyone know if this is possible?

How to update a ref with a signal in Solid.js to control table scroll?

I’m working with Solid.js and have a table with rows and cells. I’m using signals to manage the selected row and cell indices, but I need to update a ref (reference to the selected cell) based on those signals in order to manipulate the table’s scroll when the content overflows.

The issue is that I don’t want to use querySelector, as I don’t consider it a good practice, and I also want the reference to update according to the state of the signals.

Here is a simplified version of my implementation:

import { batch, Component, createEffect, createSelector, createSignal, For, JSX } from "solid-js";

export const DataGrid: Component = () => {
  const rows = Array.from({ length: 10 });
  const cells = Array.from({ length: 10 });

  const [selectedRowIndex, setSelectedRowIndex] = createSignal(0);
  const [selectedCellIndex, setSelectedCellIndex] = createSignal(0);

  const isSelectedRow = createSelector(selectedRowIndex);
  const isSelectedCell = createSelector(selectedCellIndex);

  const keysToPrevent = ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"];

  const onKeyDown: JSX.EventHandler<HTMLDivElement, KeyboardEvent> = (event) => {
    const { key, ctrlKey } = event;

    if (keysToPrevent.includes(key)) {
      event.preventDefault();
    }

    const rowLength = rows.length;
    const cellLength = cells.length;

    switch (key) {
      case "ArrowUp":
        if (ctrlKey) {
          setSelectedRowIndex(0);
        } else if (selectedRowIndex() > 0) {
          setSelectedRowIndex((v) => v - 1);
        }
        break;

      case "ArrowDown":
        if (ctrlKey) {
          setSelectedRowIndex(rowLength - 1);
        } else if (selectedRowIndex() < rowLength - 1) {
          setSelectedRowIndex((v) => v + 1);
        }
        break;

      case "ArrowLeft":
        if (ctrlKey) {
          setSelectedCellIndex(0);
        } else if (selectedCellIndex() > 0) {
          setSelectedCellIndex((v) => v - 1);
        }
        break;

      case "ArrowRight":
        if (ctrlKey) {
          setSelectedCellIndex(cellLength - 1);
        } else if (selectedCellIndex() < cellLength - 1) {
          setSelectedCellIndex((v) => v + 1);
        }
        break;
    }
  };

  const onMouseDown: JSX.EventHandler<HTMLDivElement, MouseEvent> = (event) => {
    const rowIndex = parseInt(event.currentTarget.dataset.rowIndex!);
    const colIndex = parseInt(event.currentTarget.dataset.colIndex!);

    batch(() => {
      setSelectedRowIndex(rowIndex);
      setSelectedCellIndex(colIndex);
    });
  };

  let tableRef!: HTMLDivElement;
  let selectedCellRef!: HTMLDivElement;

  createEffect(() => {
    selectedRowIndex();
    selectedCellIndex();

    /* I need to update scrollTop and scrollLeft of the table
       when the selected cell changes. */
  });

  return (
    <div class="table" tabIndex={0} onKeyDown={onKeyDown} ref={tableRef}>
      <For each={rows}>
        {(_, rowIndex) => (
          <div class="row" classList={{ selected: isSelectedRow(rowIndex()) }}>
            <For each={cells}>
              {(_, cellIndex) => (
                <div
                  class="cell"
                  classList={{ selected: isSelectedRow(rowIndex()) && isSelectedCell(cellIndex()) }}
                  data-row-index={rowIndex()}
                  data-col-index={cellIndex()}
                  onMouseDown={onMouseDown}
                >
                  row {rowIndex()} - cell {cellIndex()}
                </div>
              )}
            </For>
          </div>
        )}
      </For>
    </div>
  );
};
*,
*::after,
*::before {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

:root {
  --cell-height: 27px;
  --cell-border-color: #caddf7;
}

html {
  font-size: 12px;
  font-family: "Tahoma";
}

a,
input,
button,
select,
textarea {
  font-size: inherit;
  font-family: inherit;
}

body {
  padding: 20px;
}

.table {
  width: 400px;
  height: 400px;
  outline: none;
  overflow: auto;
  border: 1px solid #8ea6c3;
  user-select: none;
}

.row {
  display: flex;
  width: fit-content;
}

.cell {
  width: 100px;
  flex-shrink: 0;
  height: var(--cell-height);
  line-height: var(--cell-height);
  padding-inline: 4px;
  border-right: 1px solid var(--cell-border-color);
  border-bottom: 1px solid var(--cell-border-color);

  &.selected {
    box-shadow: inset 0 0 0 1px blue;
  }
}

Setting dynamically created QML rectangle’s drag.target property via JavaScript

I have dynamically created some QML rectangles on a Canvas. I wish to toggle whether the rectangles can be dragged around or not via the drag.target property, which is declared upon creation inside MouseArea. If the rectangles are draggable, I set drag.target to “parent”. If not, I set it to “undefined”.

The rectangles are stored in an array called verts[]. When I try to access the drag.target property, I get an error:
TypeError: Value is undefined and could not be converted to an object

Here is the function that produces the error. I am feeding it an array of rectangles (verts) and a boolean.

function setVertsDraggable(verts, draggable) {
    for (var i=0; i<verts.length; i++) {
        if (draggable) {
            verts[i].drag.target = "parent”;
        }
        else {
            verts[i].drag.target = "undefined”;
        }
    }
}

I have tried feeding it the values of “parent” and “undefined” in quotes, without quotes, by setting another variable to undefined and passing that variable, and an empty string. Perhaps there are values defined in QML somewhere, such as QtQuick.(SomeClassOfEnums).undefined

But I suspect that the bigger problem is that it is not recognizing vert[i].drag.target as a property. Perhaps my path is wrong. I have tried

vert[i].drag.target
vert[i].MouseArea.drag.target
vert[i].Rectangle.MouseArea.drag.target
vert[i].Rectangle.drag.target

Additionally, I am able to set the color of those rectangles via javascript. The same syntax allows me to set the color without any error:

function setVertsColor(verts, color) {
    for (var i=0;i<verts.length; i++) {
        verts[i].color = color;
    }
}

So if this syntax works with color, what is the problem with the drag.target property?

If it helps, here is the command I use to dynamically create the rectangles:

var component = Qt.createComponent("Vertex.qml");
var vertex = component.createObject(parent, {x: xPos, y: yPos, width: size, color:"yellow"});

And here is the Vertex.qml which contains the definition of the rectangles, called by component.createObject(…)

import QtQuick

Rectangle {
    width: 8
    height: 8
    color: yellow
    property string tag: “"
    MouseArea {
        anchors.fill: parent
        drag.target: undefined
        drag.smoothed: false
        onReleased: {
            polyCanvas.requestPaint()
        }
    }
}

Please let me know if you have any ideas. I’m fresh out of ’em.

Question regarding making multiple buttons changing what embed shows up on one same HTML page

A little backstory before we start, if that’s fine.

I’m currently in high school, and have begun noticing the large influx of unblocked game sites and proxy sites for chromebooks. I decided to make one myself, just to dip my toes in the water, but I decided instead of using Google Sites to do it, I decided to use my very limited knowledge of HTML, CSS, and JS to make it. A practice for my knowledge, if you will.

However, after making proxy pages, I wanted to add some games. However, the amount of game links I have (50+) would be too much to add all individually, with a button containing the link to another page, where the user would be playing via an embed of the site, since I do not want the user actually travelling to the site, which would theoretically cause the administration to figure out the link to the game, and eventually, blocking it.

I just want to know, is there a way to have a long list of buttons, each with a uniqueonclick or id, that when pressed, opens a tab to a singular page (same page if you click any other button), and based on the button pressed on the game hub, decides which game site embed shows up. Kind of confusing, but I hope this is possible!