Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"EmbAJAXOptionSelect<3>" dynamic list possible ? #37

Open
siegmar opened this issue Jan 21, 2022 · 5 comments
Open

"EmbAJAXOptionSelect<3>" dynamic list possible ? #37

siegmar opened this issue Jan 21, 2022 · 5 comments

Comments

@siegmar
Copy link

siegmar commented Jan 21, 2022

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

@tfry-git
Copy link
Owner

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

@siegmar
Copy link
Author

siegmar commented Jan 21, 2022

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

@tfry-git
Copy link
Owner

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;
};

@siegmar
Copy link
Author

siegmar commented Feb 3, 2022

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

@tfry-git
Copy link
Owner

tfry-git commented Feb 3, 2022

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);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants