EmbAJAX
EmbAJAX copied to clipboard
"EmbAJAXOptionSelect<3>" dynamic list possible ?
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
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
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
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;
};
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
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);
}