EmbAJAX icon indicating copy to clipboard operation
EmbAJAX copied to clipboard

"EmbAJAXOptionSelect<3>" dynamic list possible ?

Open siegmar opened this issue 3 years ago • 5 comments

Sorry to bother you again.

const char* radio_opts[] = {"option1", "option2", "option3"};

EmbAJAXOptionSelect<3> optionselect("optionselect", radio_opts); EmbAJAXMutableSpan optionselect_d("optionselect_d");

I would like to be able to use EmbAJAXOptionSelect to output my SSID list, which I got from a WiFi.scanNetworks(); and select the appropriate entry. Of course I can solve this by generating a JavaSript dynamically and then outputting it with EmbAJAXScriptedSpan. But maybe there is a more elegant way with your lib. Thanks in advance With best regards Siegmar

siegmar avatar Jan 21 '22 10:01 siegmar

No problem.

This is not currently possible. As a matter of fact, allowing dynamic lists (also at other places in the lib) is something I intend to add. I have begun working on that a bit (branch non_template_container), but allowing that without increasing flash and ram usage for static lists is still giving me a bit of a headache. So, in fact, an EmbAJAXScriptedSpan will be your best bet for the time being.

Thinking about it right now, I suppose it may make sense to create a separate class EmbAJAXDynamicOptionSelect. Maybe I'll find some time to give that a try over the weekend.

Regards Thomas

tfry-git avatar Jan 21 '22 11:01 tfry-git

Thomas, thank you sooo much and sorry.... C++ is not not my world. Simple C , Assembler and Hardware Development is my world. I will pray that you maybe find some time this weekend. ;-) Best regards Siegmar

siegmar avatar Jan 21 '22 12:01 siegmar

All right. Allowing for a dynamic option list seemed like quite a separate problem, after all. Here's a first shot at this. It still has a lot more quick-and-dirty hacks in it that I would like. Among other things, it will simply discard any quotes of backslashes in the labels. The advantage of this initial solution is that this is a stand-alone class that you can start using right away, without any other changes in the lib.

Let me know, what you find missing, then I can look into merging your feedback into a final solution. That is still going to remain a separate class from the more lightweight EmbAJAXOptionSelect, although the name "EmbAJAXOptionSelect2" should probably still be replaced, too.

Long story short: Copy the following into your sketch. I hope usage is self-explanatory, otherwise, ask.

/** @brief Drop-down list of selectable options - experimental alternate version
 *
 *  Drop-down list of selectable options. Most functions of interest are implemented in the base class EmbAJAXOptionSelectBase,
 *  you'll only use this class for the constructor. */
class EmbAJAXOptionSelect2 : public EmbAJAXOptionSelectBase {
public:
    EmbAJAXOptionSelect2(const char*id) : EmbAJAXOptionSelectBase(id, 0), _labels(nullptr), NUM(0), label_revision(1), owning_labels(false) {};
    void print() const override {
        EmbAJAXOptionSelectBase::print(_labels, NUM);
    }
    ~EmbAJAXOptionSelect2() { destroy(); }
    /** Set the given labels. Both the array of labels, and the labels themselves are copied, so they may be temporary.
     *  This is generally the safest option, however, to save on resources, you may consider using setLabelsFromPersistent(), *if* you can guarantee
     *  that the array of labels you pass will remain valid while in use. However, in that case, EmbAJAXOptionSelect may often be a better option. */
    template<size_t N> void setLabels(const char* (&labels)[N]) { setLabels(N, labels); }
    void setLabels(uint8_t N, const char** labels) {
       destroy();
       owning_labels = true;
       _labels = new char*[N];
       for (uint8_t i = 0; i < N; ++i) {
         _labels[i] = new char [strlen(labels[i]) + 1];
         strcpy(_labels[i], labels[i]);
       }
       NUM = N;
       if (_current_option >= NUM) _current_option = NUM - 1;
       label_revision = _driver->setChanged();
       selectOption(_current_option);  // NOTE: current option _always_ need syncing, after a change of labels, too.

       // TODO: HACK for the time being: discard all quotes in labels. This is not meant to stay, but it does allow allow me to send you a self-contained solution
       for(uint8_t i = 0; i < N; ++i) {
          char *p = _labels[i];
          while(true) {
            char c = *p;
            if(!c) break;
            if(c == '\"') *p = '\'';
            else if(c == '\\') *p = '|';
            ++p;
          }
       }
    }
    const char** labels() const { return (const char**) _labels; };
    bool sendUpdates(uint16_t since, bool first) override {
      if ((label_revision + 40000) < since) label_revision = since + 1;  // TODO: Merge with EmbAJAXElement::changed()
      if (label_revision <= since) return EmbAJAXOptionSelectBase::sendUpdates(since, first);

      // I hate duplicating all this code (let alone, sending two change records for this element), but for the time being it is the easiest solution
      // to be re-thought, should a similar need arise for other elements
      if (!first) _driver->printContent(",\n");
      _driver->printContent("{\n\"id\": ");
      _driver->printJSQuoted(_id);
      _driver->printContent(",\n\"changes\": [[\"innerHTML\", \"");
      for(uint8_t i = 0; i < NUM; ++i) {
          // TODO: Reminder to self: Status of the code within is: terrible hack (WRT to quoting).
          _driver->printContent("<option value=\\\"");
          char buf[12];
          _driver->printContent(itoa(i, buf, 10));
          _driver->printContent("\\\">");
          _driver->printContent(_labels[i]);
          _driver->printContent("</option>\\n");
      }
      _driver->printContent("\"]]\n}");

      EmbAJAXOptionSelectBase::sendUpdates(since, false); // NOTE: must come after these changes, else selected option will always be re-set to first
      return true;
    }
private:
    void destroy() {
       if (!owning_labels) return;
       for (uint8_t i = 0; i < NUM; ++i) {
         delete _labels[i];
       }
       delete [] _labels;
    }
    char **_labels;
    uint8_t NUM;
    uint16_t label_revision;
    bool owning_labels;
};

tfry-git avatar Jan 22 '22 12:01 tfry-git

First of all, thank you very much for your effort. I was not lazy, but tried to solve the whole thing with Javascript. At the end unfortunately without success,

For example:

EmbAJAXScriptedSpan test_script("test_script","document.write('<select id="Ultra" onchange="test()"……….….‘);test function () {alert("HelloWorld");this.sendValue('hello');}",test_script_buf,BUFLEN);

Unfortunately, the function test() is not called in the script, so you cannot assign the test_script_buf. But that is perhaps a new topic.

As I have already indicated, I am far away from C++, so that I am already failing to call your class correctly.

An example call would be very helpful.

My data is generated as follows

String WIFI_AP_LIST[WIFI_AP_LIST_MAX];

uint8_t wifi_counter; int n; int i;

n = WiFi.scanNetworks();

for (i = 0; i < n; ++i) { Serial.print(i + 1); Serial.print(": "); Serial.print(WiFi.SSID(i));

  if ( i < WIFI_AP_LIST_MAX )
  {
     WIFI_AP_LIST[i] = WiFi.SSID(i);

     wifi_counter++; 
  }

}

Thanks again for your effort, sorry for my incompetence

Have a nice day With best regards Siegmar

siegmar avatar Feb 03 '22 12:02 siegmar

As to your javascript attempt, I think the problem is here:

[...];test function () {alert("HelloWorld");this.sendValue('hello');}[...]

should be:

[...];function test() {alert("HelloWorld");this.sendValue('hello');}[...]

As I have already indicated, I am far away from C++, so that I am already failing to call your class correctly.

An example call would be very helpful.

Untested:

// near the top, global:
[paste the code posted, above, then:]
EmbAJAXOptionSelect2 ssid_select("ssid_select");

[All other setup code, including an EmbAJAXPage containing ssid_select]

// this should be a global, too, in case it isn't, yet:
String WIFI_AP_LIST[WIFI_AP_LIST_MAX];

void updateNetworks() {
  // we need all labels in an array
  int n = min(WiFi.scanNetworks(), WIFI_AP_LIST_MAX);
  for (int i = 0; i < n; ++i) {
     WIFI_AP_LIST[i] = WiFi.SSID(i);
  }
  ssid_select.setLabels(n, WIFI_AP_LIST);
}

void somewhereElseInYourCode() {
  char *selected_ssid = "NONE";
  int s = ssid_select.selectedOption();
  if (s >= 0)  selected_ssid = WIFI_AP_LIST(s);
}

tfry-git avatar Feb 03 '22 13:02 tfry-git