From 6c0aec41a40d973e7eedadfe3a6e69fd48c4734d Mon Sep 17 00:00:00 2001 From: Thomas Friedrichsmeier Date: Sun, 12 Jan 2020 12:45:32 +0100 Subject: [PATCH 01/18] Create a template-free alternative to EmbAJAXContainer --- EmbAJAX.h | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/EmbAJAX.h b/EmbAJAX.h index e5b6ef2..bdaf419 100644 --- a/EmbAJAX.h +++ b/EmbAJAX.h @@ -27,7 +27,6 @@ class EmbAJAXOutputDriverBase; class EmbAJAXElement; -class EmbAJAXContainerBase; class EmbAJAXPageBase; /** @brief Abstract base class for anything shown on an EmbAJAXPage @@ -81,6 +80,7 @@ class EmbAJAXBase { } protected: template friend class EmbAJAXContainer; +friend class EmbAJAXElementList; virtual void setBasicProperty(uint8_t num, bool status) {}; static EmbAJAXOutputDriverBase *_driver; @@ -542,6 +542,43 @@ template class EmbAJAXHideableContainer : public EmbAJAXElement { EmbAJAXContainer _childlist; }; +/** @brief A container for a group of elements + * + * This is a modernized alternative to EmbAJAXContainer, without requiring a template parameter. + */ +class EmbAJAXElementList : public EmbAJAXBase { +public: +// TODO: How to create a safe factory function that will automatically derive the correct childcount, given a const array of children? + EmbAJAXElementList(size_t childcount, EmbAJAXBase **children) : EmbAJAXBase() { + NUM = childcount; + _children = children; + } + void print() const override { + EmbAJAXBase::printChildren(_children, NUM); + } + bool sendUpdates(uint16_t since, bool first) override { + return EmbAJAXBase::sendUpdates(_children, NUM, since, first); + } + /** Recursively look for a child (hopefully, there is only one) of the given id, and return a pointer to it. */ + EmbAJAXElement* findChild(const char*id) const override final { + return EmbAJAXBase::findChild(_children, NUM, id); + } + size_t size() { + return NUM; + } + EmbAJAXBase* getChild(const size_t index) { + return _children[NUM]; + } +protected: + void setBasicProperty(uint8_t num, bool status) override { + for (size_t i = 0; i < NUM; ++i) { + _children[i]->setBasicProperty(num, status); + } + } + EmbAJAXBase** _children; + size_t NUM; +}; + /** @brief A set of radio buttons (mutally exclusive buttons), e.g. for on/off, or low/mid/high, etc. * * You can insert either the whole group into an EmbAJAXPage at once, or - for more flexbile From 6aa6335eb841b73d238a467c54388a4f56482ef3 Mon Sep 17 00:00:00 2001 From: Thomas Friedrichsmeier Date: Sun, 19 Jan 2020 10:53:55 +0100 Subject: [PATCH 02/18] This EmbAJAXElementList c'tor looks fairly promising, but I'll have to test what it means for code size. --- EmbAJAX.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/EmbAJAX.h b/EmbAJAX.h index bdaf419..6a4a783 100644 --- a/EmbAJAX.h +++ b/EmbAJAX.h @@ -553,6 +553,18 @@ class EmbAJAXElementList : public EmbAJAXBase { NUM = childcount; _children = children; } + template EmbAJAXElementList(T... elements) : EmbAJAXBase() { + NUM = sizeof...(elements); + _children = new EmbAJAXBase*[sizeof...(elements)] {(EmbAJAXBase*) elements...}; + } +/* + template static EmbAJAXElementList* _new(EmbAJAXBase* const (&children)[N]) { + return new EmbAJAXElementList(N, children); + } + template static EmbAJAXElementList* _new2(T... elements) { + EmbAJAXBase** dummy = new EmbAJAXBase*[sizeof...(elements)] {(EmbAJAXBase*) elements...}; + return new EmbAJAXElementList(sizeof...(elements), dummy); + } */ void print() const override { EmbAJAXBase::printChildren(_children, NUM); } From 790e6ad9ba38b3ae43517f90ccb7291b9e84e0a5 Mon Sep 17 00:00:00 2001 From: Thomas Friedrichsmeier Date: Sun, 19 Jan 2020 15:04:41 +0100 Subject: [PATCH 03/18] Make variadic c'tor type-safe --- EmbAJAX.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/EmbAJAX.h b/EmbAJAX.h index 6a4a783..3c1ffc1 100644 --- a/EmbAJAX.h +++ b/EmbAJAX.h @@ -553,9 +553,10 @@ class EmbAJAXElementList : public EmbAJAXBase { NUM = childcount; _children = children; } - template EmbAJAXElementList(T... elements) : EmbAJAXBase() { - NUM = sizeof...(elements); - _children = new EmbAJAXBase*[sizeof...(elements)] {(EmbAJAXBase*) elements...}; +// Note: "first" forces all args to be EmbAJAXBase + template EmbAJAXElementList(EmbAJAXBase* first, T... elements) : EmbAJAXBase() { + NUM = sizeof...(elements) + 1; + _children = new EmbAJAXBase*[sizeof...(elements) + 1] {first, elements...}; } /* template static EmbAJAXElementList* _new(EmbAJAXBase* const (&children)[N]) { From 57643aec952887d7fe7739a3ae0ed4a3af12e807 Mon Sep 17 00:00:00 2001 From: Thomas Friedrichsmeier Date: Sun, 19 Jan 2020 22:27:51 +0100 Subject: [PATCH 04/18] Adds some more constness. This can help save some bytes --- EmbAJAX.cpp | 10 +++++----- EmbAJAX.h | 40 ++++++++++++++++------------------------ 2 files changed, 21 insertions(+), 29 deletions(-) diff --git a/EmbAJAX.cpp b/EmbAJAX.cpp index b032189..c144a25 100644 --- a/EmbAJAX.cpp +++ b/EmbAJAX.cpp @@ -171,13 +171,13 @@ void EmbAJAXElement::printTextInput(uint SIZE, const char* _value) const { //////////////////////// EmbAJAXContainer //////////////////////////////////// -void EmbAJAXBase::printChildren(EmbAJAXBase** _children, uint NUM) const { +void EmbAJAXBase::printChildren(EmbAJAXBase* const* _children, uint NUM) const { for (uint i = 0; i < NUM; ++i) { _children[i]->print(); } } -bool EmbAJAXBase::sendUpdates(EmbAJAXBase** _children, uint NUM, uint16_t since, bool first) { +bool EmbAJAXBase::sendUpdates(EmbAJAXBase* const* _children, uint NUM, uint16_t since, bool first) { for (uint i = 0; i < NUM; ++i) { bool sent = _children[i]->sendUpdates(since, first); if (sent) first = false; @@ -185,7 +185,7 @@ bool EmbAJAXBase::sendUpdates(EmbAJAXBase** _children, uint NUM, uint16_t since, return !first; } -EmbAJAXElement* EmbAJAXBase::findChild(EmbAJAXBase** _children, uint NUM, const char*id) const { +EmbAJAXElement* EmbAJAXBase::findChild(EmbAJAXBase* const* _children, uint NUM, const char*id) const { for (uint i = 0; i < NUM; ++i) { EmbAJAXElement* child = _children[i]->toElement(); if (child) { @@ -512,7 +512,7 @@ void EmbAJAXOptionSelectBase::updateFromDriverArg(const char* argname) { //////////////////////// EmbAJAXPage ///////////////////////////// -void EmbAJAXBase::printPage(EmbAJAXBase** _children, uint NUM, const char* _title, const char* _header_add) const { +void EmbAJAXBase::printPage(EmbAJAXBase* const* _children, uint NUM, const char* _title, const char* _header_add) const { _driver->printHeader(true); _driver->printContent(""); if (_title) _driver->printContent(_title); @@ -564,7 +564,7 @@ void EmbAJAXBase::printPage(EmbAJAXBase** _children, uint NUM, const char* _titl _driver->printContent("\n</FORM></BODY></HTML>\n"); } -void EmbAJAXBase::handleRequest(EmbAJAXBase** _children, uint NUM, void (*change_callback)()) { +void EmbAJAXBase::handleRequest(EmbAJAXBase* const* _children, uint NUM, void (*change_callback)()) { char conversion_buf[ARDUJAX_MAX_ID_LEN]; // handle value changes sent from client diff --git a/EmbAJAX.h b/EmbAJAX.h index 3c1ffc1..9d0692e 100644 --- a/EmbAJAX.h +++ b/EmbAJAX.h @@ -87,15 +87,15 @@ friend class EmbAJAXElementList; static char itoa_buf[8]; /** Filthy trick to keep (template) implementation out of the header. See EmbAJAXContainer::printChildren() */ - void printChildren(EmbAJAXBase** children, uint num) const; + void printChildren(EmbAJAXBase* const* children, uint num) const; /** Filthy trick to keep (template) implementation out of the header. See EmbAJAXContainer::sendUpdates() */ - bool sendUpdates(EmbAJAXBase** children, uint num, uint16_t since, bool first); + bool sendUpdates(EmbAJAXBase* const* children, uint num, uint16_t since, bool first); /** Filthy trick to keep (template) implementation out of the header. See EmbAJAXContainer::findChild() */ - EmbAJAXElement* findChild(EmbAJAXBase** children, uint num, const char*id) const; + EmbAJAXElement* findChild(EmbAJAXBase* const* children, uint num, const char*id) const; /** Filthy trick to keep (template) implementation out of the header. See EmbAJAXPage::print() */ - void printPage(EmbAJAXBase** children, uint num, const char* _title, const char* _header) const; + void printPage(EmbAJAXBase* const* children, uint num, const char* _title, const char* _header) const; /** Filthy trick to keep (template) implementation out of the header. See EmbAJAXPage::handleRequest() */ - void handleRequest(EmbAJAXBase** children, uint num, void (*change_callback)()); + void handleRequest(EmbAJAXBase* const* children, uint num, void (*change_callback)()); }; /** @brief Abstract base class for output drivers/server implementations @@ -549,23 +549,15 @@ template<size_t NUM> class EmbAJAXHideableContainer : public EmbAJAXElement { class EmbAJAXElementList : public EmbAJAXBase { public: // TODO: How to create a safe factory function that will automatically derive the correct childcount, given a const array of children? - EmbAJAXElementList(size_t childcount, EmbAJAXBase **children) : EmbAJAXBase() { - NUM = childcount; - _children = children; - } + EmbAJAXElementList(size_t childcount, EmbAJAXBase* const* children) : + EmbAJAXBase(), + _children(children), + NUM(childcount) {} // Note: "first" forces all args to be EmbAJAXBase - template<typename... T> EmbAJAXElementList(EmbAJAXBase* first, T... elements) : EmbAJAXBase() { - NUM = sizeof...(elements) + 1; - _children = new EmbAJAXBase*[sizeof...(elements) + 1] {first, elements...}; - } -/* - template<size_t N> static EmbAJAXElementList* _new(EmbAJAXBase* const (&children)[N]) { - return new EmbAJAXElementList(N, children); - } - template<typename... T> static EmbAJAXElementList* _new2(T... elements) { - EmbAJAXBase** dummy = new EmbAJAXBase*[sizeof...(elements)] {(EmbAJAXBase*) elements...}; - return new EmbAJAXElementList(sizeof...(elements), dummy); - } */ + template<class... T> EmbAJAXElementList(EmbAJAXBase* first, T*... elements) : + EmbAJAXBase(), + _children(new EmbAJAXBase*[sizeof...(elements) + 1] {first, elements...}), + NUM(sizeof...(elements) + 1) {} void print() const override { EmbAJAXBase::printChildren(_children, NUM); } @@ -580,7 +572,7 @@ class EmbAJAXElementList : public EmbAJAXBase { return NUM; } EmbAJAXBase* getChild(const size_t index) { - return _children[NUM]; + return _children[index]; } protected: void setBasicProperty(uint8_t num, bool status) override { @@ -588,8 +580,8 @@ class EmbAJAXElementList : public EmbAJAXBase { _children[i]->setBasicProperty(num, status); } } - EmbAJAXBase** _children; - size_t NUM; + EmbAJAXBase* const* _children; + const size_t NUM; }; /** @brief A set of radio buttons (mutally exclusive buttons), e.g. for on/off, or low/mid/high, etc. From 54cb2bfcd2c6ab6a23ea83a0ef30b0c8c3053538 Mon Sep 17 00:00:00 2001 From: Thomas Friedrichsmeier <thomas.friedrichsmeier@kdemail.net> Date: Mon, 10 Jan 2022 17:55:04 +0100 Subject: [PATCH 05/18] More work on EmbAJAXElementList - Add a ctor taking a static array of elements - Use in more places This is still WIP, cleanup and testing needed --- EmbAJAX.cpp | 6 +-- EmbAJAX.h | 142 +++++++++++++++++++++++++++------------------------- 2 files changed, 78 insertions(+), 70 deletions(-) diff --git a/EmbAJAX.cpp b/EmbAJAX.cpp index c144a25..740d647 100644 --- a/EmbAJAX.cpp +++ b/EmbAJAX.cpp @@ -565,17 +565,17 @@ void EmbAJAXBase::printPage(EmbAJAXBase* const* _children, uint NUM, const char* } void EmbAJAXBase::handleRequest(EmbAJAXBase* const* _children, uint NUM, void (*change_callback)()) { - char conversion_buf[ARDUJAX_MAX_ID_LEN]; + char conversion_buf[EMBAJAX_MAX_ID_LEN]; // handle value changes sent from client - uint16_t client_revision = atoi(_driver->getArg("revision", conversion_buf, ARDUJAX_MAX_ID_LEN)); + uint16_t client_revision = atoi(_driver->getArg("revision", conversion_buf, EMBAJAX_MAX_ID_LEN)); if (client_revision > _driver->revision()) { // This could happen on overflow, or if the server has rebooted, but not the client. // Setting revision to 0, here, means that all elements are considered changed, and will be // synced to the client. client_revision = 0; } - const char *id = _driver->getArg("id", conversion_buf, ARDUJAX_MAX_ID_LEN); + const char *id = _driver->getArg("id", conversion_buf, EMBAJAX_MAX_ID_LEN); EmbAJAXElement *element = (id[0] == '\0') ? 0 : findChild(id); if (element) { element->updateFromDriverArg("value"); diff --git a/EmbAJAX.h b/EmbAJAX.h index 9d0692e..8570fd8 100644 --- a/EmbAJAX.h +++ b/EmbAJAX.h @@ -23,7 +23,7 @@ #include <Arduino.h> -#define ARDUJAX_MAX_ID_LEN 16 +#define EMBAJAX_MAX_ID_LEN 16 class EmbAJAXOutputDriverBase; class EmbAJAXElement; @@ -74,7 +74,7 @@ class EmbAJAXBase { HTMLAllowed=7 }; /** Find child element of this one, with the given id. Returns 0, if this is not a container, or - * does not have such a child. @see EmbAJAXContainer, and @see EmbAJAXHideableContainer. */ + * does not have such a child. @see EmbAJAXElementList, and @see EmbAJAXHideableContainer. */ virtual EmbAJAXElement* findChild(const char*id) const { return 0; } @@ -86,11 +86,11 @@ friend class EmbAJAXElementList; static EmbAJAXOutputDriverBase *_driver; static char itoa_buf[8]; - /** Filthy trick to keep (template) implementation out of the header. See EmbAJAXContainer::printChildren() */ + /** Filthy trick to keep (template) implementation out of the header. See EmbAJAXElementList::printChildren() */ void printChildren(EmbAJAXBase* const* children, uint num) const; - /** Filthy trick to keep (template) implementation out of the header. See EmbAJAXContainer::sendUpdates() */ + /** Filthy trick to keep (template) implementation out of the header. See EmbAJAXElementList::sendUpdates() */ bool sendUpdates(EmbAJAXBase* const* children, uint num, uint16_t since, bool first); - /** Filthy trick to keep (template) implementation out of the header. See EmbAJAXContainer::findChild() */ + /** Filthy trick to keep (template) implementation out of the header. See EmbAJAXElementList::findChild() */ EmbAJAXElement* findChild(EmbAJAXBase* const* children, uint num, const char*id) const; /** Filthy trick to keep (template) implementation out of the header. See EmbAJAXPage::print() */ void printPage(EmbAJAXBase* const* children, uint num, const char* _title, const char* _header) const; @@ -177,7 +177,7 @@ class EmbAJAXOutputDriverBase { * @param header_add a custom string to add to the HTML header section, e.g. a CSS definition. */ #define MAKE_EmbAJAXPage(name, title, header_add, ...) \ EmbAJAXBase* name##_elements[] = {__VA_ARGS__}; \ - EmbAJAXPage<sizeof(name##_elements)/sizeof(EmbAJAXBase*)> name(name##_elements, title, header_add); + EmbAJAXPage name(name##_elements, title, header_add); /** @brief A static chunk of HTML * @@ -277,7 +277,7 @@ class EmbAJAXElement : public EmbAJAXBase { bool basicProperty(uint8_t num) const { return (_flags & (1 << num)); } -template<size_t NUM> friend class EmbAJAXPage; +friend class EmbAJAXPage; friend class EmbAJAXBase; const char* _id; void setChanged(); @@ -474,7 +474,7 @@ friend class EmbAJAXCheckButton; const char* _name; }; -/** @brief Base class for groups of objects */ +/** @brief Base class for groups of objects. Deprecated. Use EmbAJAXElementList, instead. */ template<size_t NUM> class EmbAJAXContainer : public EmbAJAXBase { public: EmbAJAXContainer(EmbAJAXBase *children[NUM]) : EmbAJAXBase() { @@ -496,68 +496,34 @@ template<size_t NUM> class EmbAJAXContainer : public EmbAJAXBase { _children[i]->setBasicProperty(num, status); } } -template<size_t> friend class EmbAJAXHideableContainer; + EmbAJAXContainer() {}; EmbAJAXBase** _children; }; -/** @brief A list of objects that can be hidden, completely - * - * This is _essentially_ an EmbAJAXContainer with an id. The one advantage that this - * class has other EmbAJAXContainer, is that it can be hidden _completely_, including - * any EmbAJAXStatic objects inside it. On the client, the children of this element - * are encapsulated in a \<div> element. Other than this, it should behave identical - * to EmbAJAXContainer. - * - * You do _not_ need this class to hide an EmbAJAXContainer that contains only EmbAJAXElement - * derived objects, or standalone EmbAJAXElement objects. - * - * @note This is _not_ a derived class of EmbAJAXContainer, to avoid adding virtual - * inheritance just for this. */ -template<size_t NUM> class EmbAJAXHideableContainer : public EmbAJAXElement { -public: - EmbAJAXHideableContainer(const char* id, EmbAJAXBase *children[NUM]) : EmbAJAXElement(id) { - _childlist = EmbAJAXContainer<NUM>(children); - } - void print() const override { - _driver->printContent("<div"); - _driver->printAttribute("id", _id); - _driver->printContent(">"); - _childlist.print(); - _driver->printContent("</div>"); - } - EmbAJAXElement* findChild(const char* id) const override { - return _childlist.findChild(id); - } - bool sendUpdates(uint16_t since, bool first) override { - bool sent = EmbAJAXElement::sendUpdates(since, first); - bool sent2 = _childlist.sendUpdates(since, first && !sent); - return sent || sent2; - } -protected: - void setBasicProperty(uint8_t num, bool status) override { - EmbAJAXElement::setBasicProperty(num, status); - _childlist.setBasicProperty(num, status); - } - EmbAJAXContainer<NUM> _childlist; -}; - /** @brief A container for a group of elements * * This is a modernized alternative to EmbAJAXContainer, without requiring a template parameter. */ class EmbAJAXElementList : public EmbAJAXBase { public: -// TODO: How to create a safe factory function that will automatically derive the correct childcount, given a const array of children? + /** constructor taking a static array of elements */ + //EmbAJAXElementList(const EmbAJAXListCtorHelperBase & list) : _children(list._children), NUM(list.childcount) {}; + /** constructor taking a static array of elements */ + template<size_t N> constexpr EmbAJAXElementList(EmbAJAXBase* (&children)[N]) : _children(children), NUM(N) {}; + /** constructor taking an array of elements with a size that cannot be determined at compile time. In this case, you'll have to specify the size, as the first parameter */ EmbAJAXElementList(size_t childcount, EmbAJAXBase* const* children) : EmbAJAXBase(), _children(children), NUM(childcount) {} +#ifndef EMBAJAX_NO_OPERATOR_NEW + /** constructor taking list of pointers to elements */ // Note: "first" forces all args to be EmbAJAXBase template<class... T> EmbAJAXElementList(EmbAJAXBase* first, T*... elements) : EmbAJAXBase(), _children(new EmbAJAXBase*[sizeof...(elements) + 1] {first, elements...}), NUM(sizeof...(elements) + 1) {} +#endif void print() const override { EmbAJAXBase::printChildren(_children, NUM); } @@ -568,20 +534,61 @@ class EmbAJAXElementList : public EmbAJAXBase { EmbAJAXElement* findChild(const char*id) const override final { return EmbAJAXBase::findChild(_children, NUM, id); } - size_t size() { + size_t size() const { return NUM; } - EmbAJAXBase* getChild(const size_t index) { + EmbAJAXBase* getChild(const size_t index) const { return _children[index]; } protected: +friend class EmbAJAXHideableContainer; + EmbAJAXElementList() : NUM(0) {}; void setBasicProperty(uint8_t num, bool status) override { for (size_t i = 0; i < NUM; ++i) { _children[i]->setBasicProperty(num, status); } } EmbAJAXBase* const* _children; - const size_t NUM; + size_t NUM; // TODO: make me const, again +}; + +/** @brief A list of objects that can be hidden, completely + * + * This is _essentially_ an EmbAJAXElementList with an id. The one advantage that this + * class has other EmbAJAXElementList, is that it can be hidden _completely_, including + * any EmbAJAXStatic objects inside it. On the client, the children of this element + * are encapsulated in a \<div> element. Other than this, it should behave identical + * to EmbAJAXElementList. + * + * You do _not_ need this class to hide an EmbAJAXElementList that contains only EmbAJAXElement + * derived objects, or standalone EmbAJAXElement objects. + * + * @note This is _not_ a derived class of EmbAJAXElementList, to avoid adding virtual + * inheritance just for this. */ +class EmbAJAXHideableContainer : public EmbAJAXElement { +public: + template<int NUM> EmbAJAXHideableContainer(const char* id, EmbAJAXBase *(&children)[NUM]) : EmbAJAXElement(id), _childlist(children) {} + void print() const override { + _driver->printContent("<div"); + _driver->printAttribute("id", _id); + _driver->printContent(">"); + _childlist.print(); + _driver->printContent("</div>"); + } + EmbAJAXElement* findChild(const char* id) const override { + return _childlist.findChild(id); + } + bool sendUpdates(uint16_t since, bool first) override { + bool sent = EmbAJAXElement::sendUpdates(since, first); + bool sent2 = _childlist.sendUpdates(since, first && !sent); + return sent || sent2; + } +protected: + void setBasicProperty(uint8_t num, bool status) override { + EmbAJAXElement::setBasicProperty(num, status); + _childlist.setBasicProperty(num, status); + } + EmbAJAXElementList _childlist; }; /** @brief A set of radio buttons (mutally exclusive buttons), e.g. for on/off, or low/mid/high, etc. @@ -589,24 +596,25 @@ class EmbAJAXElementList : public EmbAJAXBase { * You can insert either the whole group into an EmbAJAXPage at once, or - for more flexbile * layouting - retrieve the individual buttons using() button, and insert them into the page * as independent elements. */ -template<size_t NUM> class EmbAJAXRadioGroup : public EmbAJAXContainer<NUM>, public EmbAJAXRadioGroupBase { +template<size_t N> class EmbAJAXRadioGroup : public EmbAJAXElementList, public EmbAJAXRadioGroupBase { public: /** ctor. * @param id_base the "base" id. Internally, radio buttons with id_s id_base0, id_base1, etc. will be created. * @param options labels for the options. Note: The @em array of options may be a temporary, but the option-strings themselves will have to be persistent! * @param selected_option index of the default option. 0 by default, for the first option, may be > NUM, for * no option selected by default. */ - EmbAJAXRadioGroup(const char* id_base, const char* options[NUM], uint8_t selected_option = 0) : EmbAJAXContainer<NUM>(), EmbAJAXRadioGroupBase() { - for (uint8_t i = 0; i < NUM; ++i) { + EmbAJAXRadioGroup(const char* id_base, const char* options[N], uint8_t selected_option = 0) : EmbAJAXElementList(), EmbAJAXRadioGroupBase() { + for (uint8_t i = 0; i < N; ++i) { char* childid = childids[i]; - strncpy(childid, id_base, ARDUJAX_MAX_ID_LEN-4); + strncpy(childid, id_base, EMBAJAX_MAX_ID_LEN-4); itoa(i, &(childid[strlen(childid)]), 10); buttons[i] = EmbAJAXCheckButton(childid, options[i], i == selected_option); buttons[i].radiogroup = this; buttonpointers[i] = &buttons[i]; } _current_option = selected_option; - EmbAJAXContainer<NUM>::_children = buttonpointers; // Hm, why do I need to specify EmbAJAXContainer<NUM>::, explicitly? + _children = buttonpointers; + NUM = N; _name = id_base; } /** Select / check the option at the given index. All other options in this radio group will become deselected. */ @@ -627,9 +635,9 @@ template<size_t NUM> class EmbAJAXRadioGroup : public EmbAJAXContainer<NUM>, pub return 0; } private: - EmbAJAXCheckButton buttons[NUM]; /** NOTE: Internally, the radio groups allocates individual check buttons. This is the storage space for those. */ - EmbAJAXBase* buttonpointers[NUM]; - char childids[NUM][ARDUJAX_MAX_ID_LEN]; + EmbAJAXCheckButton buttons[N]; /** NOTE: Internally, the radio groups allocates individual check buttons. This is the storage space for those. */ + EmbAJAXBase* buttonpointers[N]; // remove me? + char childids[N][EMBAJAX_MAX_ID_LEN]; /// TODO remove me? ids already available in the element list. would allow removal of templating. int8_t _current_option; void selectOption(EmbAJAXCheckButton* which) override { _current_option = -1; @@ -683,7 +691,7 @@ template<size_t NUM> class EmbAJAXOptionSelect : public EmbAJAXOptionSelectBase const char* _labels[NUM]; }; -/** @brief Absrract internal helper class +/** @brief Absrract internal helper class // TODO: can we remove this, now? * * Needed for internal reasons. Refer to EmbAJAXPage, instead. */ class EmbAJAXPageBase { @@ -698,13 +706,13 @@ class EmbAJAXPageBase { * print() (for page loads) adn handleRequest() (for AJAX calls) to be called on requests. By default, * both page loads, and AJAX are handled on the same URL, but the first via GET, and the second * via POST. */ -template<size_t NUM> class EmbAJAXPage : public EmbAJAXContainer<NUM>, public EmbAJAXPageBase { +class EmbAJAXPage : public EmbAJAXElementList, public EmbAJAXPageBase { public: /** Create a web page. * @param children list of elements on the page * @param title title (may be 0). This string is not copied, please do not use a temporary string. * @param header_add literal text (may be 0) to be added to the header, e.g. CSS (linked or in-line). This string is not copied, please do not use a temporary string). */ - EmbAJAXPage(EmbAJAXBase* children[NUM], const char* title, const char* header_add = 0) : EmbAJAXContainer<NUM>(children) { + template<size_t NUM> EmbAJAXPage(EmbAJAXBase* (&children)[NUM], const char* title, const char* header_add = 0) : EmbAJAXElementList(children) { _title = title; _header_add = header_add; } @@ -715,7 +723,7 @@ template<size_t NUM> class EmbAJAXPage : public EmbAJAXContainer<NUM>, public Em /** Serve the page including headers and all child elements. You should arrange for this function to be called, whenever * there is a GET request to the desired URL. */ void print() const override { - EmbAJAXBase::printPage(EmbAJAXContainer<NUM>::_children, NUM, _title, _header_add); + EmbAJAXBase::printPage(_children, NUM, _title, _header_add); } /** Handle AJAX client request. You should arrange for this function to be called, whenever there is a POST request * to whichever URL you served the page itself, from. @@ -726,7 +734,7 @@ template<size_t NUM> class EmbAJAXPage : public EmbAJAXContainer<NUM>, public Em * This way, an update can be sent back to the client, immediately, for a smooth UI experience. * (Otherwise the client will be updated on the next poll). */ void handleRequest(void (*change_callback)()=0) override { - EmbAJAXBase::handleRequest(EmbAJAXContainer<NUM>::_children, NUM, change_callback); + EmbAJAXBase::handleRequest(_children, NUM, change_callback); } protected: const char* _title; From c3938ee5cf5b62844e29f51f138c429d2efc519d Mon Sep 17 00:00:00 2001 From: Thomas Friedrichsmeier <thomas.friedrichsmeier@kdemail.net> Date: Mon, 10 Jan 2022 20:56:03 +0100 Subject: [PATCH 06/18] Remove obsolete EmbAJAXPageBase class --- EmbAJAX.cpp | 6 +++--- EmbAJAX.h | 40 +++++++++++------------------------ EmbAJAXOutputDriverESPAsync.h | 4 ++-- EmbAJAXOutputDriverGeneric.h | 4 ++-- 4 files changed, 19 insertions(+), 35 deletions(-) diff --git a/EmbAJAX.cpp b/EmbAJAX.cpp index 740d647..185677e 100644 --- a/EmbAJAX.cpp +++ b/EmbAJAX.cpp @@ -512,7 +512,7 @@ void EmbAJAXOptionSelectBase::updateFromDriverArg(const char* argname) { //////////////////////// EmbAJAXPage ///////////////////////////// -void EmbAJAXBase::printPage(EmbAJAXBase* const* _children, uint NUM, const char* _title, const char* _header_add) const { +void EmbAJAXPage::print() const { _driver->printHeader(true); _driver->printContent("<HTML><HEAD><TITLE>"); if (_title) _driver->printContent(_title); @@ -564,7 +564,7 @@ void EmbAJAXBase::printPage(EmbAJAXBase* const* _children, uint NUM, const char* _driver->printContent("\n</FORM></BODY></HTML>\n"); } -void EmbAJAXBase::handleRequest(EmbAJAXBase* const* _children, uint NUM, void (*change_callback)()) { +void EmbAJAXPage::handleRequest(void (*change_callback)()) { char conversion_buf[EMBAJAX_MAX_ID_LEN]; // handle value changes sent from client @@ -589,6 +589,6 @@ void EmbAJAXBase::handleRequest(EmbAJAXBase* const* _children, uint NUM, void (* _driver->printContent("{\"revision\": "); _driver->printContent(itoa(_driver->revision(), conversion_buf, 10)); _driver->printContent(",\n\"updates\": [\n"); - sendUpdates(_children, NUM, client_revision, true); + sendUpdates(client_revision, true); _driver->printContent("\n]}\n"); } diff --git a/EmbAJAX.h b/EmbAJAX.h index 8570fd8..c0b8b24 100644 --- a/EmbAJAX.h +++ b/EmbAJAX.h @@ -27,7 +27,7 @@ class EmbAJAXOutputDriverBase; class EmbAJAXElement; -class EmbAJAXPageBase; +class EmbAJAXPage; /** @brief Abstract base class for anything shown on an EmbAJAXPage * @@ -86,16 +86,13 @@ friend class EmbAJAXElementList; static EmbAJAXOutputDriverBase *_driver; static char itoa_buf[8]; + // TODO: remove these /** Filthy trick to keep (template) implementation out of the header. See EmbAJAXElementList::printChildren() */ void printChildren(EmbAJAXBase* const* children, uint num) const; /** Filthy trick to keep (template) implementation out of the header. See EmbAJAXElementList::sendUpdates() */ bool sendUpdates(EmbAJAXBase* const* children, uint num, uint16_t since, bool first); /** Filthy trick to keep (template) implementation out of the header. See EmbAJAXElementList::findChild() */ EmbAJAXElement* findChild(EmbAJAXBase* const* children, uint num, const char*id) const; - /** Filthy trick to keep (template) implementation out of the header. See EmbAJAXPage::print() */ - void printPage(EmbAJAXBase* const* children, uint num, const char* _title, const char* _header) const; - /** Filthy trick to keep (template) implementation out of the header. See EmbAJAXPage::handleRequest() */ - void handleRequest(EmbAJAXBase* const* children, uint num, void (*change_callback)()); }; /** @brief Abstract base class for output drivers/server implementations @@ -122,7 +119,7 @@ class EmbAJAXOutputDriverBase { * * @param change_callback See EmbAJAXPage::handleRequest() for details. */ - virtual void installPage(EmbAJAXPageBase *page, const char *path, void (*change_callback)()=0) = 0; + virtual void installPage(EmbAJAXPage *page, const char *path, void (*change_callback)()=0) = 0; /** Insert this hook into loop(). Takes care of the appropriate server calls, if needed. */ virtual void loopHook() = 0; @@ -604,6 +601,7 @@ template<size_t N> class EmbAJAXRadioGroup : public EmbAJAXElementList, public E * @param selected_option index of the default option. 0 by default, for the first option, may be > NUM, for * no option selected by default. */ EmbAJAXRadioGroup(const char* id_base, const char* options[N], uint8_t selected_option = 0) : EmbAJAXElementList(), EmbAJAXRadioGroupBase() { + EmbAJAXBase* buttonpointers[N]; for (uint8_t i = 0; i < N; ++i) { char* childid = childids[i]; strncpy(childid, id_base, EMBAJAX_MAX_ID_LEN-4); @@ -636,13 +634,12 @@ template<size_t N> class EmbAJAXRadioGroup : public EmbAJAXElementList, public E } private: EmbAJAXCheckButton buttons[N]; /** NOTE: Internally, the radio groups allocates individual check buttons. This is the storage space for those. */ - EmbAJAXBase* buttonpointers[N]; // remove me? - char childids[N][EMBAJAX_MAX_ID_LEN]; /// TODO remove me? ids already available in the element list. would allow removal of templating. + char childids[N][EMBAJAX_MAX_ID_LEN]; /** NOTE: Child ids are not copied by EmbAJAXElement. This is the storage space for them */ // TODO: Can we remove those, by using a recursive lookup scheme, instead? (groupid.buttonid) int8_t _current_option; void selectOption(EmbAJAXCheckButton* which) override { _current_option = -1; for (uint8_t i = 0; i < NUM; ++i) { - if (which == buttonpointers[i]) { + if (which == _children[i]) { _current_option = i; } else { buttons[i].setChecked(false); @@ -691,22 +688,13 @@ template<size_t NUM> class EmbAJAXOptionSelect : public EmbAJAXOptionSelectBase const char* _labels[NUM]; }; -/** @brief Absrract internal helper class // TODO: can we remove this, now? - * - * Needed for internal reasons. Refer to EmbAJAXPage, instead. */ -class EmbAJAXPageBase { -public: - virtual void handleRequest(void (*change_callback)()=0) = 0; - virtual void printPage() = 0; -}; - /** @brief The main interface class * * This is the main interface class. Create a web-page with a list of elements on it, and arrange for * print() (for page loads) adn handleRequest() (for AJAX calls) to be called on requests. By default, * both page loads, and AJAX are handled on the same URL, but the first via GET, and the second * via POST. */ -class EmbAJAXPage : public EmbAJAXElementList, public EmbAJAXPageBase { +class EmbAJAXPage : public EmbAJAXElementList { public: /** Create a web page. * @param children list of elements on the page @@ -716,15 +704,13 @@ class EmbAJAXPage : public EmbAJAXElementList, public EmbAJAXPageBase { _title = title; _header_add = header_add; } - /** Duplication of print(), needed for internal reasons. Use print(), instead! */ - void printPage() override { + /** Duplication of print(), needed for internal reasons. Use print(), instead! */ // TODO: no, it isn't any more. deprecate + void printPage() const { print(); - } + }; /** Serve the page including headers and all child elements. You should arrange for this function to be called, whenever * there is a GET request to the desired URL. */ - void print() const override { - EmbAJAXBase::printPage(_children, NUM, _title, _header_add); - } + void print() const override; /** Handle AJAX client request. You should arrange for this function to be called, whenever there is a POST request * to whichever URL you served the page itself, from. * @@ -733,9 +719,7 @@ class EmbAJAXPage : public EmbAJAXElementList, public EmbAJAXPageBase { * response to the change, you should specify this function, and handle the change inside it. * This way, an update can be sent back to the client, immediately, for a smooth UI experience. * (Otherwise the client will be updated on the next poll). */ - void handleRequest(void (*change_callback)()=0) override { - EmbAJAXBase::handleRequest(_children, NUM, change_callback); - } + void handleRequest(void (*change_callback)()=0); protected: const char* _title; const char* _header_add; diff --git a/EmbAJAXOutputDriverESPAsync.h b/EmbAJAXOutputDriverESPAsync.h index be1c8a7..ee448a6 100644 --- a/EmbAJAXOutputDriverESPAsync.h +++ b/EmbAJAXOutputDriverESPAsync.h @@ -60,14 +60,14 @@ class EmbAJAXOutputDriverESPAsync : public EmbAJAXOutputDriverBase { _request->arg(name).toCharArray (buf, buflen); return buf; } - void installPage(EmbAJAXPageBase *page, const char *path, void (*change_callback)()=0) override { + void installPage(EmbAJAXPage *page, const char *path, void (*change_callback)()=0) override { _server->on(path, [=](AsyncWebServerRequest* request) { _request = request; _response = 0; if (_request->method() == HTTP_POST) { // AJAX request page->handleRequest(change_callback); } else { // Page load - page->printPage(); + page->print(); } _request->send(_response); _request = 0; diff --git a/EmbAJAXOutputDriverGeneric.h b/EmbAJAXOutputDriverGeneric.h index 9406bc5..4050646 100644 --- a/EmbAJAXOutputDriverGeneric.h +++ b/EmbAJAXOutputDriverGeneric.h @@ -58,12 +58,12 @@ class EmbAJAXOutputDriverGeneric : public EmbAJAXOutputDriverBase { _server->arg(name).toCharArray (buf, buflen); return buf; } - void installPage(EmbAJAXPageBase *page, const char *path, void (*change_callback)()=0) override { + void installPage(EmbAJAXPage *page, const char *path, void (*change_callback)()=0) override { _server->on(path, [=]() { if (_server->method() == HTTP_POST) { // AJAX request page->handleRequest(change_callback); } else { // Page load - page->printPage(); + page->print(); } }); } From de150cced33b15d176d3bc4ba1a8ca8aeb0d1cd8 Mon Sep 17 00:00:00 2001 From: Thomas Friedrichsmeier <thomas.friedrichsmeier@kdemail.net> Date: Mon, 10 Jan 2022 21:20:11 +0100 Subject: [PATCH 07/18] Deprecate obsolete classes / functions --- EmbAJAX.h | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/EmbAJAX.h b/EmbAJAX.h index fb72d21..517d9d3 100644 --- a/EmbAJAX.h +++ b/EmbAJAX.h @@ -25,6 +25,16 @@ #define EMBAJAX_MAX_ID_LEN 16 +#if __cplusplus >= 201402L +#define EMBAJAX_DEPRECATED [[deprecated]] +#elif defined(__GNUC__) || defined(__clang__) +#define EMBAJAX_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) +#define EMBAJAX_DEPRECATED __declspec(deprecated) +#else +#define EMBAJAX_DEPRECATED +#endif + class EmbAJAXOutputDriverBase; class EmbAJAXElement; class EmbAJAXPage; @@ -86,7 +96,7 @@ friend class EmbAJAXElementList; static EmbAJAXOutputDriverBase *_driver; static char itoa_buf[8]; - // TODO: remove these + // Note: The following can be moved into EmbAJAXElementList once EmbAJAXContainer is removed for good /** Filthy trick to keep (template) implementation out of the header. See EmbAJAXElementList::printChildren() */ void printChildren(EmbAJAXBase* const* children, size_t num) const; /** Filthy trick to keep (template) implementation out of the header. See EmbAJAXElementList::sendUpdates() */ @@ -474,9 +484,9 @@ friend class EmbAJAXCheckButton; }; /** @brief Base class for groups of objects. Deprecated. Use EmbAJAXElementList, instead. */ -template<size_t NUM> class EmbAJAXContainer : public EmbAJAXBase { +template<size_t NUM> class EMBAJAX_DEPRECATED EmbAJAXContainer : public EmbAJAXBase { public: - EmbAJAXContainer(EmbAJAXBase *children[NUM]) : EmbAJAXBase() { + EMBAJAX_DEPRECATED EmbAJAXContainer(EmbAJAXBase *children[NUM]) : EmbAJAXBase() { _children = children; } void print() const override { @@ -707,7 +717,7 @@ class EmbAJAXPage : public EmbAJAXElementList { _header_add = header_add; } /** Duplication of print(), needed for internal reasons. Use print(), instead! */ // TODO: no, it isn't any more. deprecate - void printPage() const { + EMBAJAX_DEPRECATED void printPage() const { print(); }; /** Serve the page including headers and all child elements. You should arrange for this function to be called, whenever From 1e630d0572951b11dd2a2d79f28672e5c7ea4dcd Mon Sep 17 00:00:00 2001 From: Thomas Friedrichsmeier <thomas.friedrichsmeier@kdemail.net> Date: Mon, 10 Jan 2022 21:32:17 +0100 Subject: [PATCH 08/18] Add convenience constructors to page and hideable container. --- EmbAJAX.h | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/EmbAJAX.h b/EmbAJAX.h index 517d9d3..59deb23 100644 --- a/EmbAJAX.h +++ b/EmbAJAX.h @@ -584,6 +584,12 @@ class EmbAJAXHideableContainer : public EmbAJAXElement { _childlist.print(); _driver->printContent("</div>"); } + /** constructor taking an array of elements with a size that cannot be determined at compile time. In this case, you'll have to specify the size, as the first parameter */ + EmbAJAXHideableContainer(const char* id, size_t childcount, EmbAJAXBase* const* children) : EmbAJAXElement(id), _childlist(childcount, children) {} +#ifndef EMBAJAX_NO_OPERATOR_NEW + /** constructor taking list of pointers to elements */ + template<class... T> EmbAJAXHideableContainer(const char* id, EmbAJAXBase* first, T*... elements) : EmbAJAXElement(id), _childlist(first, elements...) {} +#endif EmbAJAXElement* findChild(const char* id) const override { return _childlist.findChild(id); } @@ -712,10 +718,16 @@ class EmbAJAXPage : public EmbAJAXElementList { * @param children list of elements on the page * @param title title (may be 0). This string is not copied, please do not use a temporary string. * @param header_add literal text (may be 0) to be added to the header, e.g. CSS (linked or in-line). This string is not copied, please do not use a temporary string). */ - template<size_t NUM> EmbAJAXPage(EmbAJAXBase* (&children)[NUM], const char* title, const char* header_add = 0) : EmbAJAXElementList(children) { - _title = title; - _header_add = header_add; - } + template<size_t NUM> EmbAJAXPage(EmbAJAXBase* (&children)[NUM], const char* title, const char* header_add = 0) : + EmbAJAXElementList(children), _title(title), _header_add(header_add) {} + /** constructor taking an array of elements with a size that cannot be determined at compile time. In this case, you'll have to specify the size, as the first parameter */ + EmbAJAXPage(size_t childcount, EmbAJAXBase* const* children, const char* title, const char* header_add = 0) : + EmbAJAXElementList(childcount, children), _title(title), _header_add(header_add) {} +#ifndef EMBAJAX_NO_OPERATOR_NEW + /** constructor taking list of pointers to elements */ + template<class... T> EmbAJAXPage(EmbAJAXBase* first, T*... elements, const char* title, const char* header_add = 0) : + EmbAJAXElementList(first, elements...), _title(title), _header_add(header_add) {} +#endif /** Duplication of print(), needed for internal reasons. Use print(), instead! */ // TODO: no, it isn't any more. deprecate EMBAJAX_DEPRECATED void printPage() const { print(); From ba1823d6051f99dcea91117c596e25486f062419 Mon Sep 17 00:00:00 2001 From: Thomas Friedrichsmeier <thomas.friedrichsmeier@kdemail.net> Date: Mon, 10 Jan 2022 22:04:01 +0100 Subject: [PATCH 09/18] Update examples --- README.md | 1 + examples/Styling/Styling.ino | 2 +- examples/Visibility/Visibility.ino | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fc60b0e..ba79afd 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ The following additional features may be of interest (supported as of now): - Allows to insert your own custom CSS for styling (no styling in the lib). - See the "Styling"-example for techniques that can be used to define styling - Elements can be hidden, inputs can be disabled from the server (EmbAJAXBase::setVisible(), setEnabled()). +- Support for developing custom controls in client-side javascript (EmbAJAXScriptedSpan) ### Hardware support diff --git a/examples/Styling/Styling.ino b/examples/Styling/Styling.ino index 95d6cf9..f8da483 100644 --- a/examples/Styling/Styling.ino +++ b/examples/Styling/Styling.ino @@ -84,7 +84,7 @@ MAKE_EmbAJAXPage(page, "EmbAJAX example - Styling", new EmbAJAXPushButton("my_button1", "Nothing", handleButton), new EmbAJAXPushButton("my_button2", "Round", handleButton), new EmbAJAXPushButton("my_button3", "Nothing", handleButton), - new EmbAJAXHideableContainer<3> ("my_div", new EmbAJAXBase*[3] { + new EmbAJAXHideableContainer ("my_div", new EmbAJAXBase*[3] { new EmbAJAXStatic("<p>White on black</p>"), new EmbAJAXPushButton("my_button4", "Nothing", handleButton), new EmbAJAXPushButton("my_button5", "Nothing", handleButton), diff --git a/examples/Visibility/Visibility.ino b/examples/Visibility/Visibility.ino index 1eab1e3..6c02466 100644 --- a/examples/Visibility/Visibility.ino +++ b/examples/Visibility/Visibility.ino @@ -37,10 +37,10 @@ void buttonPressed(EmbAJAXPushButton*) { } EmbAJAXPushButton button("button", "I can count", buttonPressed); EmbAJAXBase* container1_contents[] = {&statics[0], &check, &statics[1], &optionselect, &statics[2], &slider, &statics[3]}; -EmbAJAXContainer<7> container1(container1_contents); +EmbAJAXElementList container1(container1_contents); EmbAJAXBase* container2_contents[] = {&statics[0], &button, &statics[1], &text, &statics[2], &radio, &statics[3]}; -EmbAJAXHideableContainer<7> container2("hideable", container2_contents); +EmbAJAXHideableContainer container2("hideable", container2_contents); // Define the page MAKE_EmbAJAXPage(page, "EmbAJAX example - Visibility", "", From 27450142b0d298ef35de357d1c4023da49fda927 Mon Sep 17 00:00:00 2001 From: Thomas Friedrichsmeier <thomas.friedrichsmeier@kdemail.net> Date: Wed, 12 Jan 2022 22:22:29 +0100 Subject: [PATCH 10/18] - Fix crash due to array of group button pointers going out of scope. - Add constexpr for some more constructors. --- EmbAJAX.h | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/EmbAJAX.h b/EmbAJAX.h index 59deb23..3aeda33 100644 --- a/EmbAJAX.h +++ b/EmbAJAX.h @@ -516,19 +516,17 @@ template<size_t NUM> class EMBAJAX_DEPRECATED EmbAJAXContainer : public EmbAJAXB */ class EmbAJAXElementList : public EmbAJAXBase { public: - /** constructor taking a static array of elements */ - //EmbAJAXElementList(const EmbAJAXListCtorHelperBase & list) : _children(list._children), NUM(list.childcount) {}; /** constructor taking a static array of elements */ template<size_t N> constexpr EmbAJAXElementList(EmbAJAXBase* (&children)[N]) : _children(children), NUM(N) {}; /** constructor taking an array of elements with a size that cannot be determined at compile time. In this case, you'll have to specify the size, as the first parameter */ - EmbAJAXElementList(size_t childcount, EmbAJAXBase* const* children) : + constexpr EmbAJAXElementList(size_t childcount, EmbAJAXBase* const* children) : EmbAJAXBase(), _children(children), NUM(childcount) {} #ifndef EMBAJAX_NO_OPERATOR_NEW /** constructor taking list of pointers to elements */ // Note: "first" forces all args to be EmbAJAXBase - template<class... T> EmbAJAXElementList(EmbAJAXBase* first, T*... elements) : + template<class... T> constexpr EmbAJAXElementList(EmbAJAXBase* first, T*... elements) : EmbAJAXBase(), _children(new EmbAJAXBase*[sizeof...(elements) + 1] {first, elements...}), NUM(sizeof...(elements) + 1) {} @@ -619,7 +617,6 @@ template<size_t N> class EmbAJAXRadioGroup : public EmbAJAXElementList, public E * @param selected_option index of the default option. 0 by default, for the first option, may be > NUM, for * no option selected by default. */ EmbAJAXRadioGroup(const char* id_base, const char* options[N], uint8_t selected_option = 0) : EmbAJAXElementList(), EmbAJAXRadioGroupBase() { - EmbAJAXBase* buttonpointers[N]; for (uint8_t i = 0; i < N; ++i) { char* childid = childids[i]; strncpy(childid, id_base, EMBAJAX_MAX_ID_LEN-4); @@ -652,6 +649,7 @@ template<size_t N> class EmbAJAXRadioGroup : public EmbAJAXElementList, public E } private: EmbAJAXCheckButton buttons[N]; /** NOTE: Internally, the radio groups allocates individual check buttons. This is the storage space for those. */ + EmbAJAXBase* buttonpointers[N]; /** NOTE: ... and, unfortunately, we need a separate persistent array of pointers... char childids[N][EMBAJAX_MAX_ID_LEN]; /** NOTE: Child ids are not copied by EmbAJAXElement. This is the storage space for them */ // TODO: Can we remove those, by using a recursive lookup scheme, instead? (groupid.buttonid) int8_t _current_option; void selectButton(EmbAJAXCheckButton* which) override { @@ -718,14 +716,14 @@ class EmbAJAXPage : public EmbAJAXElementList { * @param children list of elements on the page * @param title title (may be 0). This string is not copied, please do not use a temporary string. * @param header_add literal text (may be 0) to be added to the header, e.g. CSS (linked or in-line). This string is not copied, please do not use a temporary string). */ - template<size_t NUM> EmbAJAXPage(EmbAJAXBase* (&children)[NUM], const char* title, const char* header_add = 0) : + template<size_t NUM> constexpr EmbAJAXPage(EmbAJAXBase* (&children)[NUM], const char* title, const char* header_add = 0) : EmbAJAXElementList(children), _title(title), _header_add(header_add) {} /** constructor taking an array of elements with a size that cannot be determined at compile time. In this case, you'll have to specify the size, as the first parameter */ - EmbAJAXPage(size_t childcount, EmbAJAXBase* const* children, const char* title, const char* header_add = 0) : + constexpr EmbAJAXPage(size_t childcount, EmbAJAXBase* const* children, const char* title, const char* header_add = 0) : EmbAJAXElementList(childcount, children), _title(title), _header_add(header_add) {} #ifndef EMBAJAX_NO_OPERATOR_NEW /** constructor taking list of pointers to elements */ - template<class... T> EmbAJAXPage(EmbAJAXBase* first, T*... elements, const char* title, const char* header_add = 0) : + template<class... T> constexpr EmbAJAXPage(EmbAJAXBase* first, T*... elements, const char* title, const char* header_add = 0) : EmbAJAXElementList(first, elements...), _title(title), _header_add(header_add) {} #endif /** Duplication of print(), needed for internal reasons. Use print(), instead! */ // TODO: no, it isn't any more. deprecate From a52813c089a136ea226cb4b7d6a5040e0ed89008 Mon Sep 17 00:00:00 2001 From: Thomas Friedrichsmeier <thomas.friedrichsmeier@kdemail.net> Date: Wed, 12 Jan 2022 22:24:32 +0100 Subject: [PATCH 11/18] Fix comment. --- EmbAJAX.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EmbAJAX.h b/EmbAJAX.h index 3aeda33..c95ddcd 100644 --- a/EmbAJAX.h +++ b/EmbAJAX.h @@ -649,7 +649,7 @@ template<size_t N> class EmbAJAXRadioGroup : public EmbAJAXElementList, public E } private: EmbAJAXCheckButton buttons[N]; /** NOTE: Internally, the radio groups allocates individual check buttons. This is the storage space for those. */ - EmbAJAXBase* buttonpointers[N]; /** NOTE: ... and, unfortunately, we need a separate persistent array of pointers... + EmbAJAXBase* buttonpointers[N]; /** NOTE: ... and, unfortunately, we need a separate persistent array of pointers... */ char childids[N][EMBAJAX_MAX_ID_LEN]; /** NOTE: Child ids are not copied by EmbAJAXElement. This is the storage space for them */ // TODO: Can we remove those, by using a recursive lookup scheme, instead? (groupid.buttonid) int8_t _current_option; void selectButton(EmbAJAXCheckButton* which) override { From 37e187d4a18cd5c0a8bb8f5ef09bc44b95aa1412 Mon Sep 17 00:00:00 2001 From: Thomas Friedrichsmeier <thomas.friedrichsmeier@kdemail.net> Date: Sun, 12 Mar 2023 22:56:51 +0100 Subject: [PATCH 12/18] Continue work on non-templated containers --- EmbAJAX.h | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/EmbAJAX.h b/EmbAJAX.h index c95ddcd..8af40c9 100644 --- a/EmbAJAX.h +++ b/EmbAJAX.h @@ -26,13 +26,13 @@ #define EMBAJAX_MAX_ID_LEN 16 #if __cplusplus >= 201402L -#define EMBAJAX_DEPRECATED [[deprecated]] +#define EMBAJAX_DEPRECATED(WHEN, WHY) [[deprecated]](WHY) #elif defined(__GNUC__) || defined(__clang__) -#define EMBAJAX_DEPRECATED __attribute__((deprecated)) +#define EMBAJAX_DEPRECATED(WHEN, WHY) __attribute__((deprecated)) #elif defined(_MSC_VER) -#define EMBAJAX_DEPRECATED __declspec(deprecated) +#define EMBAJAX_DEPRECATED(WHEN, WHY) __declspec(deprecated) #else -#define EMBAJAX_DEPRECATED +#define EMBAJAX_DEPRECATED(WHEN, WHY) #endif class EmbAJAXOutputDriverBase; @@ -178,13 +178,15 @@ class EmbAJAXOutputDriverBase { uint16_t next_revision; }; -/** Convenience macro to set up an EmbAJAXPage, without counting the number of elements for the template. See EmbAJAXPage::EmbAJAXPage() +EMBAJAX_DEPRECATED(2023_03_12, "Use EmbAJAXPage constructor, direclty") inline int MAKE_EmbAJAXPageDeprecated() { return 0; }; +/** DEPRECATED: Convenience macro to set up an EmbAJAXPage, without counting the number of elements for the template. See EmbAJAXPage::EmbAJAXPage(). * @param name Variable name of the page instance * @param title HTML Title * @param header_add a custom string to add to the HTML header section, e.g. a CSS definition. */ #define MAKE_EmbAJAXPage(name, title, header_add, ...) \ EmbAJAXBase* name##_elements[] = {__VA_ARGS__}; \ - EmbAJAXPage name(name##_elements, title, header_add); + EmbAJAXPage name(name##_elements, title, header_add); \ + int name##_warning = MAKE_EmbAJAXPageDeprecated(); /** @brief A static chunk of HTML * @@ -484,9 +486,9 @@ friend class EmbAJAXCheckButton; }; /** @brief Base class for groups of objects. Deprecated. Use EmbAJAXElementList, instead. */ -template<size_t NUM> class EMBAJAX_DEPRECATED EmbAJAXContainer : public EmbAJAXBase { +template<size_t NUM> class EMBAJAX_DEPRECATED(2023_03_12, "Use EmbAJAXElementList, instead") EmbAJAXContainer : public EmbAJAXBase { public: - EMBAJAX_DEPRECATED EmbAJAXContainer(EmbAJAXBase *children[NUM]) : EmbAJAXBase() { + EMBAJAX_DEPRECATED(2023_03_12, "Use EmbAJAXElementList, instead") EmbAJAXContainer(EmbAJAXBase *children[NUM]) : EmbAJAXBase() { _children = children; } void print() const override { @@ -523,14 +525,13 @@ class EmbAJAXElementList : public EmbAJAXBase { EmbAJAXBase(), _children(children), NUM(childcount) {} -#ifndef EMBAJAX_NO_OPERATOR_NEW /** constructor taking list of pointers to elements */ -// Note: "first" forces all args to be EmbAJAXBase + // Note: "first" forces all args to be EmbAJAXBase template<class... T> constexpr EmbAJAXElementList(EmbAJAXBase* first, T*... elements) : EmbAJAXBase(), _children(new EmbAJAXBase*[sizeof...(elements) + 1] {first, elements...}), NUM(sizeof...(elements) + 1) {} -#endif +#warning proper d'tor! void print() const override { EmbAJAXBase::printChildren(_children, NUM); } @@ -549,7 +550,7 @@ class EmbAJAXElementList : public EmbAJAXBase { } protected: friend class EmbAJAXHideableContainer; - EmbAJAXElementList() : NUM(0) {}; + constexpr EmbAJAXElementList(size_t N) : _children(nullptr), NUM(N) {}; void setBasicProperty(uint8_t num, bool status) override { for (size_t i = 0; i < NUM; ++i) { _children[i]->setBasicProperty(num, status); @@ -584,10 +585,9 @@ class EmbAJAXHideableContainer : public EmbAJAXElement { } /** constructor taking an array of elements with a size that cannot be determined at compile time. In this case, you'll have to specify the size, as the first parameter */ EmbAJAXHideableContainer(const char* id, size_t childcount, EmbAJAXBase* const* children) : EmbAJAXElement(id), _childlist(childcount, children) {} -#ifndef EMBAJAX_NO_OPERATOR_NEW /** constructor taking list of pointers to elements */ template<class... T> EmbAJAXHideableContainer(const char* id, EmbAJAXBase* first, T*... elements) : EmbAJAXElement(id), _childlist(first, elements...) {} -#endif + EmbAJAXElement* findChild(const char* id) const override { return _childlist.findChild(id); } @@ -616,7 +616,8 @@ template<size_t N> class EmbAJAXRadioGroup : public EmbAJAXElementList, public E * @param options labels for the options. Note: The @em array of options may be a temporary, but the option-strings themselves will have to be persistent! * @param selected_option index of the default option. 0 by default, for the first option, may be > NUM, for * no option selected by default. */ - EmbAJAXRadioGroup(const char* id_base, const char* options[N], uint8_t selected_option = 0) : EmbAJAXElementList(), EmbAJAXRadioGroupBase() { + EmbAJAXRadioGroup(const char* id_base, const char* options[N], uint8_t selected_option = 0) : EmbAJAXElementList(N), EmbAJAXRadioGroupBase() { +#warning port to element list properly for (uint8_t i = 0; i < N; ++i) { char* childid = childids[i]; strncpy(childid, id_base, EMBAJAX_MAX_ID_LEN-4); @@ -626,8 +627,7 @@ template<size_t N> class EmbAJAXRadioGroup : public EmbAJAXElementList, public E buttonpointers[i] = &buttons[i]; } _current_option = selected_option; - _children = buttonpointers; - NUM = N; + _children = buttonpointers; _name = id_base; } /** Select / check the option at the given index. All other options in this radio group will become deselected. */ @@ -721,13 +721,12 @@ class EmbAJAXPage : public EmbAJAXElementList { /** constructor taking an array of elements with a size that cannot be determined at compile time. In this case, you'll have to specify the size, as the first parameter */ constexpr EmbAJAXPage(size_t childcount, EmbAJAXBase* const* children, const char* title, const char* header_add = 0) : EmbAJAXElementList(childcount, children), _title(title), _header_add(header_add) {} -#ifndef EMBAJAX_NO_OPERATOR_NEW /** constructor taking list of pointers to elements */ - template<class... T> constexpr EmbAJAXPage(EmbAJAXBase* first, T*... elements, const char* title, const char* header_add = 0) : + template<class... T> constexpr EmbAJAXPage(const char* title, const char* header_add, EmbAJAXBase* first, T*... elements) : EmbAJAXElementList(first, elements...), _title(title), _header_add(header_add) {} -#endif - /** Duplication of print(), needed for internal reasons. Use print(), instead! */ // TODO: no, it isn't any more. deprecate - EMBAJAX_DEPRECATED void printPage() const { + + /** Duplication of print(), historically needed for internal reasons. Use print(), instead! */ + EMBAJAX_DEPRECATED(2023_03_12, "Use print(), instead") void printPage() const { print(); }; /** Serve the page including headers and all child elements. You should arrange for this function to be called, whenever From d77a4b4779c21efdee290215ce818a2a3c118b4a Mon Sep 17 00:00:00 2001 From: Thomas Friedrichsmeier <thomas.friedrichsmeier@kdemail.net> Date: Sun, 12 Mar 2023 23:01:26 +0100 Subject: [PATCH 13/18] Elaborate comment --- EmbAJAX.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EmbAJAX.h b/EmbAJAX.h index 8af40c9..fabb6e6 100644 --- a/EmbAJAX.h +++ b/EmbAJAX.h @@ -531,7 +531,7 @@ class EmbAJAXElementList : public EmbAJAXBase { EmbAJAXBase(), _children(new EmbAJAXBase*[sizeof...(elements) + 1] {first, elements...}), NUM(sizeof...(elements) + 1) {} -#warning proper d'tor! +#warning proper d'tor! Perhaps const init arrays should not be accepted at all? EmbAJAXPage c'tor should be the only API entry point where we need backwards compatibility void print() const override { EmbAJAXBase::printChildren(_children, NUM); } From 556d8e0ed8647f1d1c1cf422cbe1c7b60a929e48 Mon Sep 17 00:00:00 2001 From: Thomas Friedrichsmeier <thomas.friedrichsmeier@kdemail.net> Date: Sun, 30 Apr 2023 18:14:26 +0200 Subject: [PATCH 14/18] New draft for EmbAJAXElementList c'tor; small refinements --- EmbAJAX.h | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/EmbAJAX.h b/EmbAJAX.h index 7e00735..f0df091 100644 --- a/EmbAJAX.h +++ b/EmbAJAX.h @@ -232,7 +232,7 @@ class EmbAJAXOutputDriverBase { uint16_t next_revision; }; -EMBAJAX_DEPRECATED(2023_03_12, "Use EmbAJAXPage constructor, direclty") inline int MAKE_EmbAJAXPageDeprecated() { return 0; }; +EMBAJAX_DEPRECATED("v0.3.0", "Use EmbAJAXPage constructor, directly") inline int MAKE_EmbAJAXPageDeprecated() { return 0; }; /** DEPRECATED: Convenience macro to set up an EmbAJAXPage, without counting the number of elements for the template. See EmbAJAXPage::EmbAJAXPage(). * @param name Variable name of the page instance * @param title HTML Title @@ -526,7 +526,7 @@ class EmbAJAXCheckButton : public EmbAJAXElement { bool _checked; const char* _label; template<size_t NUM> friend class EmbAJAXRadioGroup; - EmbAJAXCheckButton() : EmbAJAXElement("") {}; + EmbAJAXCheckButton() : EmbAJAXElement(null_string) {}; EmbAJAXRadioGroupBase* radiogroup; }; @@ -540,9 +540,9 @@ friend class EmbAJAXCheckButton; }; /** @brief Base class for groups of objects. Deprecated. Use EmbAJAXElementList, instead. */ -template<size_t NUM> class EMBAJAX_DEPRECATED(2023_03_12, "Use EmbAJAXElementList, instead") EmbAJAXContainer : public EmbAJAXBase { +template<size_t NUM> class EMBAJAX_DEPRECATED("v0.3.0", "Use EmbAJAXElementList, instead") EmbAJAXContainer : public EmbAJAXBase { public: - EMBAJAX_DEPRECATED(2023_03_12, "Use EmbAJAXElementList, instead") EmbAJAXContainer(EmbAJAXBase *children[NUM]) : EmbAJAXBase() { + EMBAJAX_DEPRECATED("v0.3.0", "Use EmbAJAXElementList, instead") EmbAJAXContainer(EmbAJAXBase *children[NUM]) : EmbAJAXBase() { _children = children; } void print() const override { @@ -579,13 +579,20 @@ class EmbAJAXElementList : public EmbAJAXBase { EmbAJAXBase(), _children(children), NUM(childcount) {} - /** constructor taking list of pointers to elements */ - // Note: "first" forces all args to be EmbAJAXBase + /** constructor taking list of pointers to elements template<class... T> constexpr EmbAJAXElementList(EmbAJAXBase* first, T*... elements) : EmbAJAXBase(), _children(new EmbAJAXBase*[sizeof...(elements) + 1] {first, elements...}), - NUM(sizeof...(elements) + 1) {} -#warning proper d'tor! Perhaps const init arrays should not be accepted at all? EmbAJAXPage c'tor should be the only API entry point where we need backwards compatibility + NUM(sizeof...(elements) + 1) {} */ + // This version would allow to do convient stuff like EmbAJAXList("static html", new EmbAJAXSomething(), "static", ...) + // One downside is that this makes it really hard to understand, which elements are "owned" where (destruction) + template<class... T> constexpr EmbAJAXElementList(T*... elements) : + EmbAJAXBase(), + _children(new EmbAJAXBase*[sizeof...(elements)]{toElement(elements)...}), + NUM(sizeof...(elements)) {} + EmbAJAXBase* toElement(EmbAJAXBase *e) const { return e; } + EmbAJAXBase* toElement(const char *e) const { return new EmbAJAXStatic(e); } + void print() const override { EmbAJAXBase::printChildren(_children, NUM); } @@ -611,7 +618,7 @@ friend class EmbAJAXHideableContainer; } } EmbAJAXBase* const* _children; - size_t NUM; // TODO: make me const, again + const size_t NUM; }; /** @brief A list of objects that can be hidden, completely @@ -659,7 +666,7 @@ class EmbAJAXHideableContainer : public EmbAJAXElement { /** @brief A set of radio buttons (mutally exclusive buttons), e.g. for on/off, or low/mid/high, etc. * * You can insert either the whole group into an EmbAJAXPage at once, or - for more flexbile - * layouting - retrieve the individual buttons using() button, and insert them into the page + * layouting - retrieve the individual buttons using button(), and insert them into the page * as independent elements. */ template<size_t N> class EmbAJAXRadioGroup : public EmbAJAXElementList, public EmbAJAXRadioGroupBase { public: @@ -779,7 +786,7 @@ class EmbAJAXPage : public EmbAJAXElementList { EmbAJAXElementList(first, elements...), _title(title), _header_add(header_add), _min_interval(min_interval) {} /** Duplication of print(), historically needed for internal reasons. Use print(), instead! */ - EMBAJAX_DEPRECATED(2023_03_12, "Use print(), instead") void printPage() const { + EMBAJAX_DEPRECATED("v0.3.0", "Use print(), instead") void printPage() const { print(); }; /** Serve the page including headers and all child elements. You should arrange for this function to be called, whenever From 03274bd2c975c50d5b2938fc0bfffea1fae09ef5 Mon Sep 17 00:00:00 2001 From: Thomas Friedrichsmeier <thomas.friedrichsmeier@kdemail.net> Date: Sun, 30 Apr 2023 23:15:32 +0200 Subject: [PATCH 15/18] Tweak constructors some more --- EmbAJAX.cpp | 15 ++++--------- EmbAJAX.h | 42 ++++++++++++++++++------------------ docs/ChangeLog.md | 2 ++ examples/Styling/Styling.ino | 8 +++---- 4 files changed, 31 insertions(+), 36 deletions(-) diff --git a/EmbAJAX.cpp b/EmbAJAX.cpp index 5f2eec5..eb88704 100644 --- a/EmbAJAX.cpp +++ b/EmbAJAX.cpp @@ -156,13 +156,6 @@ void EmbAJAXConnectionIndicator::print() const { ////////////////////////////// EmbAJAXElement ///////////////////////////// -/** @param id: The id for the element. Note that the string is not copied. Do not use a temporary string in this place. Also, do keep it short. */ -EmbAJAXElement::EmbAJAXElement(const char* id) : EmbAJAXBase() { - _id = id; - _flags = 1 << EmbAJAXBase::Visibility | 1 << EmbAJAXBase::Enabledness; - revision = 1; -} - bool EmbAJAXElement::sendUpdates(uint16_t since, bool first) { if (!changed(since)) return false; if (!first) _driver->printContent(",\n"); @@ -535,7 +528,7 @@ void EmbAJAXPage::print() const { #endif _driver->printHeader(true); - _driver->printFormatted("<!DOCTYPE html>\n<HTML><HEAD><TITLE>", PLAIN_STRING(_title), "\n\n", PLAIN_STRING(_header_add), + "\n", PLAIN_STRING(p.header_add), "\n
\n"); // NOTE: The nasty thing about autocomplete is that it does not trigger onChange() functions, but also the // "restore latest settings after client reload" is questionable in our use-case. diff --git a/EmbAJAX.h b/EmbAJAX.h index 71417ac..861dad4 100644 --- a/EmbAJAX.h +++ b/EmbAJAX.h @@ -300,7 +300,7 @@ class EmbAJAXConnectionIndicator : public EmbAJAXBase { class EmbAJAXElement : public EmbAJAXBase { public: /** @param id: The id for the element. Note that the string is not copied. Do not use a temporary string in this place. Also, do keep it short. */ - EmbAJAXElement(const char* id); + constexpr EmbAJAXElement(const char* id) : _id(id), _flags(1 << EmbAJAXBase::Visibility | 1 << EmbAJAXBase::Enabledness), revision(1) {} const char* id() const { return _id; @@ -584,20 +584,13 @@ class EmbAJAXElementList : public EmbAJAXBase { EmbAJAXBase(), _children(children), NUM(childcount) {} - /** constructor taking list of pointers to elements - template constexpr EmbAJAXElementList(EmbAJAXBase* first, T*... elements) : - EmbAJAXBase(), - _children(new EmbAJAXBase*[sizeof...(elements) + 1] {first, elements...}), - NUM(sizeof...(elements) + 1) {} */ - // This version would allow to do convient stuff like EmbAJAXList("static html", new EmbAJAXSomething(), "static", ...) - // One downside is that this makes it really hard to understand, which elements are "owned" where (destruction) + /** Constructor taking list of pointers to elements. For convenience, you can also insert static fragments directly, like this: + @code{.cpp}EmbAJAXList("static html", new EmbAJAXSomething(), "some more html", ...)@endcode + in this case, the string literals will be wrapped into EmbAJAXStatic, automatically. */ template constexpr EmbAJAXElementList(T*... elements) : EmbAJAXBase(), _children(new EmbAJAXBase*[sizeof...(elements)]{toElement(elements)...}), NUM(sizeof...(elements)) {} - EmbAJAXBase* toElement(EmbAJAXBase *e) const { return e; } - EmbAJAXBase* toElement(const char *e) const { return new EmbAJAXStatic(e); } - void print() const override { EmbAJAXBase::printChildren(_children, NUM); } @@ -624,6 +617,9 @@ friend class EmbAJAXHideableContainer; } EmbAJAXBase* const* _children; const size_t NUM; +private: + EmbAJAXBase* toElement(EmbAJAXBase *e) const { return e; } + EmbAJAXBase* toElement(const char *e) const { return new EmbAJAXStatic(e); } }; /** @brief A list of objects that can be hidden, completely @@ -641,16 +637,16 @@ friend class EmbAJAXHideableContainer; * inheritance just for this. */ class EmbAJAXHideableContainer : public EmbAJAXElement { public: - template EmbAJAXHideableContainer(const char* id, EmbAJAXBase *(&children)[NUM]) : EmbAJAXElement(id), _childlist(children) {} + template constexpr EmbAJAXHideableContainer(const char* id, EmbAJAXBase *(&children)[NUM]) : EmbAJAXElement(id), _childlist(children) {} void print() const override { _driver->printFormatted("
"); _childlist.print(); _driver->printContent("
"); } /** constructor taking an array of elements with a size that cannot be determined at compile time. In this case, you'll have to specify the size, as the first parameter */ - EmbAJAXHideableContainer(const char* id, size_t childcount, EmbAJAXBase* const* children) : EmbAJAXElement(id), _childlist(childcount, children) {} + constexpr EmbAJAXHideableContainer(const char* id, size_t childcount, EmbAJAXBase* const* children) : EmbAJAXElement(id), _childlist(childcount, children) {} /** constructor taking list of pointers to elements */ - template EmbAJAXHideableContainer(const char* id, EmbAJAXBase* first, T*... elements) : EmbAJAXElement(id), _childlist(first, elements...) {} + template constexpr EmbAJAXHideableContainer(const char* id, T*... elements) : EmbAJAXElement(id), _childlist(elements...) {} EmbAJAXElement* findChild(const char* id) const override { return _childlist.findChild(id); @@ -776,19 +772,25 @@ template class EmbAJAXOptionSelect : public EmbAJAXOptionSelectBase * via POST. */ class EmbAJAXPage : public EmbAJAXElementList { public: + struct Params { + constexpr Params(const char* title = null_string, const char* header_add = null_string, uint16_t min_interval=100) : title(title), header_add(header_add), min_interval(min_interval){}; + const char* title; + const char* header_add; + uint16_t min_interval; + }; + /** Create a web page. * @param children list of elements on the page * @param title title (may be 0). This string is not copied, please do not use a temporary string. * @param header_add literal text (may be 0) to be added to the header, e.g. CSS (linked or in-line). This string is not copied, please do not use a temporary string). * @param min_interval minimum interval (ms) between two requests sent by a single client. A lower value may reduce latency at the cost of traffic/CPU. */ template constexpr EmbAJAXPage(EmbAJAXBase* (&children)[NUM], const char* title, const char* header_add = 0, uint16_t min_interval=100) : - EmbAJAXElementList(children), _title(title), _header_add(header_add), _min_interval(min_interval) {} + EmbAJAXElementList(children), p(Params(title, header_add, min_interval)) {} /** constructor taking an array of elements with a size that cannot be determined at compile time. In this case, you'll have to specify the size, as the first parameter */ constexpr EmbAJAXPage(size_t childcount, EmbAJAXBase* const* children, const char* title, const char* header_add = 0, uint16_t min_interval=100) : - EmbAJAXElementList(childcount, children), _title(title), _header_add(header_add), _min_interval(min_interval) {} + EmbAJAXElementList(childcount, children), p(Params(title, header_add, min_interval)) {} /** constructor taking list of pointers to elements */ - template constexpr EmbAJAXPage(const char* title, const char* header_add, uint16_t min_interval, EmbAJAXBase* first, T*... elements) : - EmbAJAXElementList(first, elements...), _title(title), _header_add(header_add), _min_interval(min_interval) {} + template constexpr EmbAJAXPage(Params p, T*... elements) : EmbAJAXElementList(elements...), p(p) {} /** Duplication of print(), historically needed for internal reasons. Use print(), instead! */ EMBAJAX_DEPRECATED("v0.3.0", "Use print(), instead") void printPage() const { @@ -813,9 +815,7 @@ class EmbAJAXPage : public EmbAJAXElementList { return(_latest_ping && (_latest_ping + latency_ms > millis())); } protected: - const char* _title; - const char* _header_add; - uint16_t _min_interval; + Params p; uint64_t _latest_ping = 0; }; diff --git a/docs/ChangeLog.md b/docs/ChangeLog.md index 24bd900..07f8829 100644 --- a/docs/ChangeLog.md +++ b/docs/ChangeLog.md @@ -1,4 +1,6 @@ -- Changes in version 0.3.0 -- UNRELEASED +X Clean up +x TODO: More constexpr c'tors X TODO: Fix EmbAJAXValidatingTextInput (did it ever work?) -- Changes in version 0.2.0 -- 2023-04-29 diff --git a/examples/Styling/Styling.ino b/examples/Styling/Styling.ino index f8da483..07c07d6 100644 --- a/examples/Styling/Styling.ino +++ b/examples/Styling/Styling.ino @@ -84,11 +84,11 @@ MAKE_EmbAJAXPage(page, "EmbAJAX example - Styling", new EmbAJAXPushButton("my_button1", "Nothing", handleButton), new EmbAJAXPushButton("my_button2", "Round", handleButton), new EmbAJAXPushButton("my_button3", "Nothing", handleButton), - new EmbAJAXHideableContainer ("my_div", new EmbAJAXBase*[3] { - new EmbAJAXStatic("

White on black

"), + new EmbAJAXHideableContainer ("my_div", + "

White on black

", new EmbAJAXPushButton("my_button4", "Nothing", handleButton), - new EmbAJAXPushButton("my_button5", "Nothing", handleButton), - }), + new EmbAJAXPushButton("my_button5", "Nothing", handleButton) + ), new EmbAJAXStatic("

Styling examples for other elements

"), new EmbAJAXCheckButton("my_checkbox", "Checkbox control with custom box moved to the right-hand side"), new EmbAJAXStatic("

A short slider oriented vertically

"), From a701a3ff4eb074cb1e40070e3d14b9b98c7959bf Mon Sep 17 00:00:00 2001 From: Thomas Friedrichsmeier Date: Sun, 30 Apr 2023 23:21:10 +0200 Subject: [PATCH 16/18] Avoid reorder (fix compilation on overly pedantic compiler) --- EmbAJAX.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EmbAJAX.h b/EmbAJAX.h index 861dad4..5aff13f 100644 --- a/EmbAJAX.h +++ b/EmbAJAX.h @@ -349,8 +349,8 @@ class EmbAJAXElement : public EmbAJAXBase { } friend class EmbAJAXPage; friend class EmbAJAXBase; - byte _flags; const char* _id; + byte _flags; void setChanged(); bool changed(uint16_t since); /** Filthy trick to keep (template) implementation out of the header. See EmbAJAXTextInput::print() */ From 8b0e7708a8dc08d6812f5e9674d99ead182a6818 Mon Sep 17 00:00:00 2001 From: Thomas Friedrichsmeier Date: Tue, 2 May 2023 17:05:47 +0200 Subject: [PATCH 17/18] More constexpr constructors --- EmbAJAX.h | 33 +++++++++++++++++---------------- docs/ChangeLog.md | 3 ++- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/EmbAJAX.h b/EmbAJAX.h index 5aff13f..2b2f9bc 100644 --- a/EmbAJAX.h +++ b/EmbAJAX.h @@ -148,10 +148,8 @@ friend class EmbAJAXElementList; */ class EmbAJAXOutputDriverBase { public: - EmbAJAXOutputDriverBase() { - _revision = 1; - next_revision = _revision; - } + // Note: Making this constexpr would require useless initialization of _buf before C++20 + EmbAJAXOutputDriverBase() : _revision(1), next_revision(1) {} virtual void printHeader(bool html) = 0; virtual void printContent(const char *content) = 0; @@ -252,9 +250,7 @@ EMBAJAX_DEPRECATED("v0.3.0", "Use EmbAJAXPage constructor, directly") inline int class EmbAJAXStatic : public EmbAJAXBase { public: /** ctor. Note: Content string is not copied. Don't make this a temporary. */ - EmbAJAXStatic(const char* content) { - _content = content; - } + constexpr EmbAJAXStatic(const char* content) : _content(content) {} void print() const override { _driver->printContent(_content); } @@ -275,10 +271,8 @@ class EmbAJAXConnectionIndicator : public EmbAJAXBase { * * @param content_ok Value to show for OK state. May contain HTML markup. Default is "OK" on a green background. * @param content_ok Value to show for broken state. May contain HTML markup. Default is "FAIL" on a green background. */ - EmbAJAXConnectionIndicator(const char* content_ok = default_ok, const char* content_fail = default_fail) { - _content_ok = content_ok; - _content_fail = content_fail; - } + constexpr EmbAJAXConnectionIndicator(const char* content_ok = default_ok, const char* content_fail = default_fail) : + _content_ok(content_ok), _content_fail(content_fail) {} void print() const override; static constexpr const char* default_ok = {"OK"}; static constexpr const char* default_fail = {"FAIL"}; @@ -362,9 +356,8 @@ friend class EmbAJAXBase; /** @brief An HTML span element with content that can be updated from the server (not the client) */ class EmbAJAXMutableSpan : public EmbAJAXElement { public: - EmbAJAXMutableSpan(const char* id) : EmbAJAXElement(id) { - _value = 0; - } + constexpr EmbAJAXMutableSpan(const char* id) : EmbAJAXElement(id), _value(0) {} + void print() const override; const char* value(uint8_t which = EmbAJAXBase::Value) const override; const char* valueProperty(uint8_t which = EmbAJAXBase::Value) const override; @@ -772,6 +765,7 @@ template class EmbAJAXOptionSelect : public EmbAJAXOptionSelectBase * via POST. */ class EmbAJAXPage : public EmbAJAXElementList { public: + /** @See EmbAJAXPage(). Struct to encapsulate the constructor arguments. */ struct Params { constexpr Params(const char* title = null_string, const char* header_add = null_string, uint16_t min_interval=100) : title(title), header_add(header_add), min_interval(min_interval){}; const char* title; @@ -786,10 +780,17 @@ class EmbAJAXPage : public EmbAJAXElementList { * @param min_interval minimum interval (ms) between two requests sent by a single client. A lower value may reduce latency at the cost of traffic/CPU. */ template constexpr EmbAJAXPage(EmbAJAXBase* (&children)[NUM], const char* title, const char* header_add = 0, uint16_t min_interval=100) : EmbAJAXElementList(children), p(Params(title, header_add, min_interval)) {} - /** constructor taking an array of elements with a size that cannot be determined at compile time. In this case, you'll have to specify the size, as the first parameter */ + /** constructor taking an array of elements with a size that cannot be determined at compile time. + * @param childcount Number of elements + * @param children array of pointer to elements. + * For the other parameters, see above. */ constexpr EmbAJAXPage(size_t childcount, EmbAJAXBase* const* children, const char* title, const char* header_add = 0, uint16_t min_interval=100) : EmbAJAXElementList(childcount, children), p(Params(title, header_add, min_interval)) {} - /** constructor taking list of pointers to elements */ + /** constructor taking list of pointers to elements. + * @param p page parameters. These are the same as in the other constructors, but encapsulated into a struct to allow for future expansion. + * @param elements page elements. These are passed as either EmbAJAXBase* or as contant strings. The latter are wrapped into + * EmbAJAXStatic, automatically. + */ template constexpr EmbAJAXPage(Params p, T*... elements) : EmbAJAXElementList(elements...), p(p) {} /** Duplication of print(), historically needed for internal reasons. Use print(), instead! */ diff --git a/docs/ChangeLog.md b/docs/ChangeLog.md index 07f8829..3c7b1df 100644 --- a/docs/ChangeLog.md +++ b/docs/ChangeLog.md @@ -1,5 +1,6 @@ -- Changes in version 0.3.0 -- UNRELEASED -X Clean up +x TODO: Adjust radio group, option select +X TODO: Clean up x TODO: More constexpr c'tors X TODO: Fix EmbAJAXValidatingTextInput (did it ever work?) From 592e37648870a4a6aa029e6fc59489be0cc985f1 Mon Sep 17 00:00:00 2001 From: Thomas Friedrichsmeier Date: Thu, 4 May 2023 08:42:57 +0200 Subject: [PATCH 18/18] Convert radio group to non-templated (with compatiblity layer, for now) --- EmbAJAX.h | 103 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 66 insertions(+), 37 deletions(-) diff --git a/EmbAJAX.h b/EmbAJAX.h index 2b2f9bc..a6d0686 100644 --- a/EmbAJAX.h +++ b/EmbAJAX.h @@ -503,7 +503,7 @@ class EmbAJAXMomentaryButton : public EmbAJAXPushButton { uint16_t _timeout; }; -class EmbAJAXRadioGroupBase; +class EmbAJAXRadioGroupImpl; /** @brief A checkable (option) button. * @@ -523,18 +523,9 @@ class EmbAJAXCheckButton : public EmbAJAXElement { private: bool _checked; const char* _label; -template friend class EmbAJAXRadioGroup; +friend class EmbAJAXRadioGroupImpl; EmbAJAXCheckButton() : EmbAJAXElement(null_string) {}; - EmbAJAXRadioGroupBase* radiogroup; -}; - -/** @brief abstract base for EmbAJAXRadioGroup, needed for internal reasons. */ -class EmbAJAXRadioGroupBase { -protected: - EmbAJAXRadioGroupBase() {}; -friend class EmbAJAXCheckButton; - virtual void selectButton(EmbAJAXCheckButton* which) = 0; - const char* _name; + EmbAJAXRadioGroupImpl* radiogroup; }; /** @brief Base class for groups of objects. Deprecated. Use EmbAJAXElementList, instead. */ @@ -659,34 +650,35 @@ class EmbAJAXHideableContainer : public EmbAJAXElement { /** @brief A set of radio buttons (mutally exclusive buttons), e.g. for on/off, or low/mid/high, etc. * - * You can insert either the whole group into an EmbAJAXPage at once, or - for more flexbile - * layouting - retrieve the individual buttons using button(), and insert them into the page - * as independent elements. */ -template class EmbAJAXRadioGroup : public EmbAJAXElementList, public EmbAJAXRadioGroupBase { + * Note: This class is provided to keep old code working, but will be removed in the future. + * You can use all members of this class, but please instantiate it as EmbAJAXRadioGroup. + */ +class EmbAJAXRadioGroupImpl : public EmbAJAXElementList { public: - /** ctor. - * @param id_base the "base" id. Internally, radio buttons with id_s id_base0, id_base1, etc. will be created. - * @param options labels for the options. Note: The @em array of options may be a temporary, but the option-strings themselves will have to be persistent! - * @param selected_option index of the default option. 0 by default, for the first option, may be > NUM, for - * no option selected by default. */ - EmbAJAXRadioGroup(const char* id_base, const char* options[N], uint8_t selected_option = 0) : EmbAJAXElementList(N), EmbAJAXRadioGroupBase() { -#warning port to element list properly - for (uint8_t i = 0; i < N; ++i) { + EmbAJAXRadioGroupImpl(const char* id_base, int8_t N_options, const char** options, uint8_t selected_option = 0) : EmbAJAXElementList(N_options) { + auto buttons = new EmbAJAXBase*[N_options]; + childids = new char[N_options][EMBAJAX_MAX_ID_LEN]; // NOTE: this could be RAM optimized + for (uint8_t i = 0; i < N_options; ++i) { char* childid = childids[i]; strncpy(childid, id_base, EMBAJAX_MAX_ID_LEN-4); itoa(i, &(childid[strlen(childid)]), 10); - buttons[i] = EmbAJAXCheckButton(childid, options[i], i == selected_option); - buttons[i].radiogroup = this; - buttonpointers[i] = &buttons[i]; + auto button = new EmbAJAXCheckButton(childid, options[i], i == selected_option); + button->radiogroup = this; + buttons[i] = button; } _current_option = selected_option; - _children = buttonpointers; + _children = buttons; _name = id_base; - } + }/* + ~EmbAJAXRadioGroup() { + for (uint8_t i = 0; i < NUM; ++i) delete _children[i]; + delete _children; + delete childids; + }*/ /** Select / check the option at the given index. All other options in this radio group will become deselected. */ void selectOption(uint8_t num) { for (uint8_t i = 0; i < NUM; ++i) { - buttons[i].setChecked(i == num); + static_cast(static_cast(_children[i]))->setChecked(i == num); } _current_option = num; // NOTE: might be outside of range, but that's ok, signifies "none selected" } @@ -697,26 +689,63 @@ template class EmbAJAXRadioGroup : public EmbAJAXElementList, public E /** @returns a representation of an individual option element. You can use this to insert the individual buttons * at arbitrary positions in the page layout. */ EmbAJAXBase* button(uint8_t num) { - if (num < NUM) return (&buttons[num]); - return 0; + if (num < NUM) return (_children[num]); + return nullptr; } private: - EmbAJAXCheckButton buttons[N]; /** NOTE: Internally, the radio groups allocates individual check buttons. This is the storage space for those. */ - EmbAJAXBase* buttonpointers[N]; /** NOTE: ... and, unfortunately, we need a separate persistent array of pointers... */ - char childids[N][EMBAJAX_MAX_ID_LEN]; /** NOTE: Child ids are not copied by EmbAJAXElement. This is the storage space for them */ // TODO: Can we remove those, by using a recursive lookup scheme, instead? (groupid.buttonid) +friend class EmbAJAXCheckButton; + const char* _name; + typedef char idstring[EMBAJAX_MAX_ID_LEN]; + idstring *childids; /** NOTE: Child ids are not copied by EmbAJAXElement. This is the storage space for them */ + // TODO: Can we remove those, by using a recursive lookup scheme, instead? (groupid.buttonid) int8_t _current_option; - void selectButton(EmbAJAXCheckButton* which) override { + void selectButton(EmbAJAXCheckButton* which) { _current_option = -1; for (uint8_t i = 0; i < NUM; ++i) { if (which == _children[i]) { _current_option = i; } else { - buttons[i].setChecked(false); + static_cast(static_cast(_children[i]))->setChecked(false); } } } }; +/** @brief A set of radio buttons (mutally exclusive buttons), e.g. for on/off, or low/mid/high, etc. + * + * You can insert either the whole group into an EmbAJAXPage at once, or - for more flexbile + * layouting - retrieve the individual buttons using button(), and insert them into the page + * as independent elements. + * + * Note: Template parameter is retained for backwards compatility, only, and will be removed in a future release. + * Almost all member functions are defined in EmbAJAXRadioGroupImpl, for the same reason. In new code, you should + * still use EmbAJAXRadioGroup, but _without_ any template parameter specified. Eventually, EmbAJAXRadioGroupImpl + * will be merged into this class, and your code will continue working, unchanged. + * + * Do _not_ specifiy a template parameter. + */ +template class EmbAJAXRadioGroup : public EmbAJAXRadioGroupImpl { +public: + /** ctor. + * @param id_base the "base" id. Internally, radio buttons with ids id_base0, id_base1, etc. will be created. + * @param options labels for the options. Note: The @em array of options may be a temporary, but the option-strings themselves will have to be persistent! + * @param selected_option index of the default option. 0 by default, for the first option, may be > NUM, for + * no option selected by default. */ + EMBAJAX_DEPRECATED("v0.3.0", "Use template free constructor, instead") EmbAJAXRadioGroup(const char* id_base, const char* options[N], uint8_t selected_option = 0) : EmbAJAXRadioGroupImpl(id_base, N, options, selected_option) {}; + EMBAJAX_DEPRECATED("v0.3.0", "Use EmbAJAXRadioGroup without specifying a template parameter") EmbAJAXRadioGroup(const char* id_base, int8_t N_options, const char** options, uint8_t selected_option = 0) : EmbAJAXRadioGroupImpl(id_base, N_options, options, selected_option) {}; +}; + +/* Specialization in order not to nag about template parameter, when none is specified. */ +template<> class EmbAJAXRadioGroup<0> : public EmbAJAXRadioGroupImpl { +public: + /** ctor. + * @param id_base the "base" id. Internally, radio buttons with ids id_base0, id_base1, etc. will be created. + * @param options labels for the options. Note: The @em array of options may be a temporary, but the option-strings themselves will have to be persistent! + * @param selected_option index of the default option. 0 by default, for the first option, may be > NUM, for + * no option selected by default. */ + EmbAJAXRadioGroup(const char* id_base, int8_t N_options, const char** options, uint8_t selected_option = 0) : EmbAJAXRadioGroupImpl(id_base, N_options, options, selected_option) {}; +}; + /** @brief Abstract base class for EmbAJAXOptionSelect. */ class EmbAJAXOptionSelectBase : public EmbAJAXElement { public: