Webdar 1.0.0
Web user interface to libdar
arriere_boutique.hpp
1/*********************************************************************/
2// webdar - a web server and interface program to libdar
3// Copyright (C) 2013-2025 Denis Corbin
4//
5// This file is part of Webdar
6//
7// Webdar is free software: you can redistribute it and/or modify
8// it under the terms of the GNU General Public License as published by
9// the Free Software Foundation, either version 3 of the License, or
10// (at your option) any later version.
11//
12// Webdar is distributed in the hope that it will be useful,
13// but WITHOUT ANY WARRANTY; without even the implied warranty of
14// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15// GNU General Public License for more details.
16//
17// You should have received a copy of the GNU General Public License
18// along with Webdar. If not, see <http://www.gnu.org/licenses/>
19//
20//----
21// to contact the author: dar.linux@free.fr
22/*********************************************************************/
23
24#ifndef arriere_boutique_HPP
25#define arriere_boutique_HPP
26
27 // C system header files
28#include "my_config.h"
29
30extern "C"
31{
32
33}
34
35 // C++ system header files
36#include <dar/tools.hpp>
37#include <string>
38#include <deque>
39
40 // webdar headers
41#include "body_builder.hpp"
42#include "actor.hpp"
43#include "events.hpp"
44#include "bibliotheque.hpp"
45#include "html_text.hpp"
46#include "html_form.hpp"
47#include "html_form_fieldset.hpp"
48#include "html_form_input.hpp"
49#include "html_form_radio.hpp"
50#include "html_double_button.hpp"
51#include "jsoner.hpp"
52#include "bibliotheque_subconfig.hpp"
53#include "webdar_css_style.hpp"
54#include "html_div.hpp"
55#include "tokens.hpp"
56#include "html_yes_no_box.hpp"
57#include "tooltip_messages.hpp"
58
60
67
89template <class T> class arriere_boutique: public body_builder, public actor, public events
90{
91public:
92 static const std::string changed;
93
94 arriere_boutique(const std::shared_ptr<bibliotheque> & ptr,
96 std::unique_ptr<T> & obj,
97 const std::string ch_event_name,
98 bool add_form_around
99 );
100 arriere_boutique(const arriere_boutique & ref) = delete;
101 arriere_boutique(arriere_boutique && ref) noexcept = delete;
102 arriere_boutique & operator = (const arriere_boutique & ref) = delete;
103 arriere_boutique & operator = (arriere_boutique && ref) noexcept = delete;
104 ~arriere_boutique() = default;
105
107 virtual void on_event(const std::string & event_name) override;
108
110 const jsoner* get_wrapped() const { if(!wrapped) throw WEBDAR_BUG; return wrapped.get(); };
111
112
113protected:
114
116 virtual std::string inherited_get_body_part(const chemin & path,
117 const request & req) override;
118
119
121 virtual void new_css_library_available() override;
122
123
124private:
125 static constexpr const char* event_delete = "delete_conf";
126 static constexpr const char* event_clear = "clear_cur_conf";
127 static constexpr const char* css_float = "arriere_boutique_float";
128 static constexpr const char* css_warn = "arriere_boutique_warn";
129 static constexpr const char* css_margin_above = "arriere_boutique_margin";
130
131 std::string currently_loaded;
132 std::shared_ptr<bibliotheque> biblio;
134 std::string change_event_name;
135 bool ignore_events;
136
137 std::unique_ptr<T> wrapped;
138 jsoner* wrapped_jsoner;
139 body_builder* wrapped_body_builder;
140 events* wrapped_events;
141 bibliotheque_subconfig* wrapped_subconfig;
142
143 html_text warning_message;
144 html_text need_saving;
145 html_form_fieldset config_fs;
146 html_form config_form;
147 html_form_input config_name;
148 html_form listing_form;
149 html_form_fieldset listing_fs;
150 html_form_radio listing;
151 html_double_button delete_selected;
152 html_double_button clear_cur_config;
153 html_div floteur;
154 html_yes_no_box confirm;
155
156 html_form_fieldset around_fs;
157 html_form around_form;
158
159 void load_listing();
160 void set_warning(const std::string & wm);
161 void clear_warning() { warning_message.set_visible(false); };
162 void silently_update_config_name(const std::string & val);
163};
164
165template <class T> const std::string arriere_boutique<T>::changed = "ab_changed";
166
167template <class T> arriere_boutique<T>::arriere_boutique(const std::shared_ptr<bibliotheque> & ptr,
169 std::unique_ptr<T> & obj,
170 const std::string ch_event_name,
171 bool add_form_around):
172 currently_loaded(""),
173 categ(cat),
174 config_form("Save/Save as"),
175 config_fs(""),
176 config_name("Configuration name", html_form_input::text, "", "", webdar_css_style::width_100vw),
177 listing_form("Load selected"),
178 listing_fs("Available configurations"),
179 delete_selected("Delete loaded config", event_delete),
180 clear_cur_config("Clear", event_clear),
181 change_event_name(ch_event_name),
182 around_fs(""),
183 around_form("Update"),
184 ignore_events(false)
185{
186 // non html field settup
187 biblio = ptr;
188 if(!biblio)
189 throw WEBDAR_BUG;
190
191 wrapped = std::move(obj);
192 if(wrapped.get() == nullptr)
193 throw WEBDAR_BUG;
194
195 wrapped_jsoner = dynamic_cast<jsoner*>(wrapped.get());
196 if(wrapped_jsoner == nullptr)
197 throw WEBDAR_BUG; // should be object inheriting from class jsoner
198
199 wrapped_body_builder = dynamic_cast<body_builder*>(wrapped.get());
200 if(wrapped_body_builder == nullptr)
201 throw WEBDAR_BUG; // should be object inheriting from class body_builder
202
203 wrapped_events = dynamic_cast<events*>(wrapped.get());
204 if(wrapped_events == nullptr)
205 throw WEBDAR_BUG; // shold also be object inheriting from class events
206
207 wrapped_subconfig = dynamic_cast<bibliotheque_subconfig*>(wrapped.get());
208 // it is ok if wrapped_subconfig is nullptr, wrapped may not implement
209 // the bibliotheque_subconfig interface
210
211 if(change_event_name == html_form_input::changed)
212 throw WEBDAR_BUG; // event name collides with our own components
213 if(change_event_name == event_delete)
214 throw WEBDAR_BUG; // event name collides with our own components
215 if(change_event_name == html_form_radio::changed)
216 throw WEBDAR_BUG; // event name collides with our own components
217 if(change_event_name == bibliotheque::changed(categ))
218 throw WEBDAR_BUG; // event name collides with our own components
219 if(change_event_name == html_form::changed)
220 throw WEBDAR_BUG; // event name collides with our own components
221
222
223 // html components setup
224 clear_warning();
225 load_listing();
226 need_saving.clear();
227 need_saving.add_text(0, "configuration not saved");
228 need_saving.set_visible(false);
229
230 // adoption tree
231 listing_fs.adopt(&listing);
232 listing_form.adopt(&listing_fs);
233 floteur.adopt(&listing_form);
234
235 floteur.adopt(&delete_selected);
236 floteur.adopt(&clear_cur_config);
237 adopt(&floteur);
238
239 adopt(&warning_message);
240
241 around_fs.adopt(wrapped_body_builder);
242 if(add_form_around)
243 {
244 around_form.adopt(&around_fs);
245 adopt(&around_form);
246 }
247 else
248 adopt(&around_fs);
249
250 config_fs.adopt(&config_name);
251 config_fs.adopt(&need_saving);
252 config_form.adopt(&config_fs);
253 adopt(&config_form);
254 adopt(&confirm);
255
256 // events and actors
257 register_name(changed);
258
259 wrapped_events->record_actor_on_event(this, change_event_name);
260 config_form.record_actor_on_event(this, html_form::changed);
261 delete_selected.record_actor_on_event(this, event_delete);
262 clear_cur_config.record_actor_on_event(this, event_clear);
263 confirm.record_actor_on_event(this, html_yes_no_box::answer_yes);
264
265 listing.record_actor_on_event(this, html_form_radio::changed);
266 ptr->record_actor_on_event(this, bibliotheque::changed(categ));
267
268 // css
269 webdar_css_style::normal_button(delete_selected);
270 delete_selected.add_css_class(webdar_css_style::spacing_vertical);
271 webdar_css_style::normal_button(clear_cur_config);
272 clear_cur_config.add_css_class(webdar_css_style::spacing_vertical);
273 floteur.add_css_class(css_float);
274 warning_message.add_css_class(css_warn);
275 need_saving.add_css_class(css_warn);
276 config_form.add_css_class(css_margin_above);
277
278 // tooltips
279
280 config_name.set_tooltip(TOOLTIP_AB_CONFIG_NAME);
281 listing_fs.set_tooltip(TOOLTIP_AB_SELECT);
282}
283
284template <class T> void arriere_boutique<T>::on_event(const std::string & event_name)
285{
286 if(ignore_events)
287 return;
288
289 if(!biblio)
290 throw WEBDAR_BUG;
291
292 if(event_name == change_event_name)
293 {
294 // the hosted object configuration has been changed by the user
295
296 need_saving.set_visible(true);
297 clear_warning();
298 act(changed);
299 }
300 else if(event_name == html_form::changed)
301 {
302
303 // save as form has changed (-> asked to save as)
304
305 need_saving.set_visible(true);
306 if(config_name.get_value().empty())
307 set_warning("Cannot save a configuration without a name");
308 else
309 {
310 // we use a config temporary variable here
311 // to be sure the save_json() method is called
312 // before the get_using_set() one, as the first
313 // may have impact on the second, some components
314 // making some cleanup (forgetting the unselected
315 // alternatives) within save_json()
316 json config = wrapped_jsoner->save_json();
317
318 try
319 {
320 if(config_name.get_value() != currently_loaded)
321 {
322 currently_loaded = config_name.get_value();
323
324 if(wrapped_subconfig == nullptr)
325 biblio->add_config(categ,
326 config_name.get_value(),
327 config);
328 else
329 biblio->add_config(categ,
330 config_name.get_value(),
331 config,
332 wrapped_subconfig->get_using_set());
333 }
334 else
335 {
336 if(wrapped_subconfig == nullptr)
337 biblio->update_config(categ,
338 currently_loaded,
339 config);
340 else
341 biblio->update_config(categ,
342 currently_loaded,
343 config,
344 wrapped_subconfig->get_using_set());
345 }
346 // these previous alternatives all trigger bibliotheque::changed(categ) event
347 need_saving.set_visible(false);
348 clear_warning();
349 }
350 catch(exception_range & e)
351 {
352 set_warning(libdar::tools_printf("Failed saving configuration as %s: %s",
353 config_name.get_value().c_str(),
354 e.get_message().c_str()));
355 }
356 }
357 }
358 else if(event_name == event_delete)
359 {
360 // user asked to delete the selected configuration
361
362 // asking confirmation
363 confirm.ask_question("Are you sure to delete the loaded configuration?", false);
364 }
365 else if(event_name == html_yes_no_box::answer_yes)
366 {
367 // user confirm configuration deletion
368
369 if(listing.is_selected())
370 {
371 std::string todelete = listing.get_selected_id();
372
373 silently_update_config_name(currently_loaded);
374 biblio->delete_config(categ, todelete);
375 // this triggers bibliotheque::changed(categ) event
376 currently_loaded = "";
377 need_saving.set_visible(true);
378 clear_warning();
379 }
380 else
381 set_warning("Select and load first the configuration to be deleted");
382 }
383 else if(event_name == event_clear)
384 {
385 currently_loaded = "";
386 config_name.set_value("");
387 wrapped_jsoner->clear_json();
388 need_saving.set_visible(true);
389 listing.unset_selected();
390 clear_warning();
391 }
392 else if(event_name == html_form_radio::changed)
393 {
394 // user selected a configuration to load
395
396 ignore_events = true;
397 try
398 {
399 if(listing.is_selected())
400 {
401 currently_loaded = listing.get_selected_id();
402 silently_update_config_name(currently_loaded);
403 wrapped_jsoner->load_json(biblio->fetch_config(categ, currently_loaded));
404 need_saving.set_visible(false);
405 clear_warning();
406 }
407 else
408 set_warning("Select first the configuration to load");
409 }
410 catch(...)
411 {
412 ignore_events = false;
413 throw;
414 }
415 ignore_events = false;
416 act(changed);
417 }
418 else if(event_name == bibliotheque::changed(categ))
419 {
420 ignore_events = true;
421 try
422 {
423 load_listing();
424
425 // repositionning the selected object to the currently loaded config if any
426 if(!currently_loaded.empty())
427 {
428 if(biblio->has_config(categ, currently_loaded))
429 listing.set_selected_id(currently_loaded);
430 else
431 {
432 currently_loaded = "";
433 silently_update_config_name(currently_loaded);
434 }
435 }
436 }
437 catch(...)
438 {
439 ignore_events = false;
440 throw;
441 }
442 ignore_events = false;
443 }
444 else
445 throw WEBDAR_BUG;
446}
447
448template <class T> std::string arriere_boutique<T>::inherited_get_body_part(const chemin & path,
449 const request & req)
450{
451 return get_body_part_from_all_children(path, req);
452}
453
454template <class T> void arriere_boutique<T>::load_listing()
455{
456 std::deque<std::string> content = biblio->listing(categ);
457
458 listing.clear();
459
460 for(std::deque<std::string>::iterator it = content.begin(); it != content.end(); ++it)
461 listing.add_choice(*it, *it);
462
463 listing.unset_selected();
464}
465
467{
468 css tmp;
469 std::unique_ptr<css_library> & csslib = lookup_css_library();
470
471 if(!csslib)
472 throw WEBDAR_BUG;
473
474 if(!csslib->class_exists(css_float))
475 {
476 tmp.clear();
477 tmp.css_border_width(css::bd_all, css::bd_medium);
478 tmp.css_float(css::fl_right);
479 tmp.css_margin_left("1em");
480 csslib->add(css_float, tmp);
481 }
482
483 if(!csslib->class_exists(css_warn))
484 {
485 tmp.clear();
486 tmp.css_color(RED);
487 tmp.css_font_weight_bold();
488 tmp.css_text_shadow("0.1em", "0.1em", "0.1em", "#888888");
489 csslib->add(css_warn, tmp);
490 }
491
492 if(!csslib->class_exists(css_margin_above))
493 {
494 tmp.clear();
495
496 tmp.css_margin_top("2em");
497
498 csslib->add(css_margin_above, tmp);
499 }
500
501
503}
504
505template <class T> void arriere_boutique<T>::set_warning(const std::string & wm)
506{
507 warning_message.clear();
508 warning_message.add_text(0, wm);
509 warning_message.set_visible(true);
510}
511
512template <class T> void arriere_boutique<T>::silently_update_config_name(const std::string & val)
513{
514 bool original_status = ignore_events;
515
516 ignore_events = true;
517 try
518 {
519 config_name.set_value(val);
520 // without ignore_events, this would
521 // trigger the html_form_input::changed event
522 // leading to save the configuration
523 }
524 catch(...)
525 {
526 ignore_events = original_status;
527 throw;
528 }
529 ignore_events = original_status;
530}
531
532#endif
defines bibliotheque class
class of object that are pointed/triggered to by others
Definition: actor.hpp:55
class arriere_boutique provides mean to add/load a given component type to/from a bibliotheque object
Definition: arriere_boutique.hpp:90
virtual std::string inherited_get_body_part(const chemin &path, const request &req) override
inherited from class body_builder
Definition: arriere_boutique.hpp:448
virtual void on_event(const std::string &event_name) override
inherited from class actor
Definition: arriere_boutique.hpp:284
const jsoner * get_wrapped() const
get access to the wrapped object
Definition: arriere_boutique.hpp:110
virtual void new_css_library_available() override
inherited from class body_builder
Definition: arriere_boutique.hpp:466
arriere_boutique(const std::shared_ptr< bibliotheque > &ptr, bibliotheque::category cat, std::unique_ptr< T > &obj, const std::string ch_event_name, bool add_form_around)
Definition: arriere_boutique.hpp:167
class bibliotheque_subconfig is an interface (pure virtual class)
Definition: bibliotheque_subconfig.hpp:50
virtual bibliotheque::using_set get_using_set() const =0
provide a standard mean for an object to tell its configuration relies on other configuration(s)
static std::string changed(category cat)
change event per category
Definition: bibliotheque.cpp:45
category
change event is replaced by a static method with category in argument
Definition: bibliotheque.hpp:63
class body_builder is the root class of object generating HTML body
Definition: body_builder.hpp:99
void adopt(body_builder *obj)
Definition: body_builder.cpp:117
void set_visible(bool mode)
ask for the object to become visible in HTML page or temporarily hidden
Definition: body_builder.cpp:211
void add_css_class(const std::string &name)
set this object with a additional css_class (assuming it is defined in a css_library available for th...
Definition: body_builder.cpp:247
class chemin definition
Definition: chemin.hpp:51
class managing Cascading Style Sheets attributes
Definition: css.hpp:48
void clear()
set css attributes to their default
Definition: css.cpp:116
class events
Definition: events.hpp:52
void record_actor_on_event(actor *ptr, const std::string &name)
record an actor for an given event
Definition: events.cpp:62
void register_name(const std::string &name)
add a new event for actors to register against
Definition: events.cpp:107
exception used to report out or range value or argument
Definition: exceptions.hpp:109
class html_div is the implementation of
Definition: html_div.hpp:46
html_button equivalent with changing path to trigger the button event
Definition: html_double_button.hpp:48
class html_form_fieldset implements HTML fieldset feature
Definition: html_form_fieldset.hpp:51
void set_tooltip(const std::string &val)
surfacing the tooltip feature of html_legend
Definition: html_form_fieldset.hpp:76
class html_form_input implements HTML input feature
Definition: html_form_input.hpp:57
void set_tooltip(const std::string &msg)
set tooltip for the html label of the input form
Definition: html_form_input.hpp:122
class html_form_radio implements HTML "input" of type "radio"
Definition: html_form_radio.hpp:53
const std::string & get_selected_id() const
obtain the id of the selected radio button
Definition: html_form_radio.cpp:122
void set_selected_id(const std::string &id)
set the radio buttons to the item id given in argument
Definition: html_form_radio.cpp:89
bool is_selected() const
returns whether a radio button is selected
Definition: html_form_radio.hpp:87
void unset_selected()
unselect all radio buttons
Definition: html_form_radio.cpp:113
class html_form implements HTML form feature
Definition: html_form.hpp:51
class html_text manage text and header in html document
Definition: html_text.hpp:52
void clear()
clear the whole component value (gets as if it was just created)
Definition: html_text.hpp:79
void add_text(unsigned int level, const std::string &text)
Definition: html_text.cpp:44
html component for user to answer by yes or no to a provided question
Definition: html_yes_no_box.hpp:51
void ask_question(const std::string &message, bool default_value)
make the question to appear to the user
Definition: html_yes_no_box.cpp:74
class jsoner
Definition: jsoner.hpp:73
virtual void load_json(const json &source)=0
setup the components from the json provided information
virtual json save_json() const =0
produce a json structure from the component configuration
virtual void clear_json()=0
instruct the object to get to its default/initial configuration
class holding fields of an HTTP request (method, URI, header, cookies, and so on)
Definition: request.hpp:45
defines the event class
defines jsoner class and class exception_json
the webdar_css_style namespace defines a set of global css objets and some routines to use them
Definition: webdar_css_style.cpp:45
void normal_button(T &obj, bool fullwidth=false)
apply to the given button the css_classe names defined by update_library to get a normal button style
Definition: webdar_css_style.hpp:94
void update_library(css_library &csslib)
update a css_library with all css definitions of this webdar_css_style module
Definition: webdar_css_style.cpp:452