Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Create system to help create interactive web tutorials #346

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
39 changes: 39 additions & 0 deletions source/web/Div.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,45 @@ namespace web {
}
}

/// Remove the old widget, putting the new widget in its place (i.e., same index)
/// @param the Widget to remove
/// @param the Widget to add
void ReplaceChild(Widget & old_child, Widget new_child) override {
// ensure child is present
emp_assert(1 == std::count(
std::begin(m_children),
std::end(m_children),
old_child
));
// unregister and remove child
Unregister(*std::find(
std::begin(m_children),
std::end(m_children),
old_child
));
m_children.emplace(std::find(
std::begin(m_children),
std::end(m_children),
old_child
), new_child);
m_children.erase(
std::remove(
std::begin(m_children),
std::end(m_children),
old_child
),
std::end(m_children)
);

old_child->parent = nullptr;
// update info for new child
new_child->parent = this;
Register(new_child);
new_child->DoActivate(false);
// render changes
if (state == Widget::ACTIVE) ReplaceHTML();
}

void DoActivate(bool top_level=true) override {
for (auto & child : m_children) child->DoActivate(false);
internal::WidgetInfo::DoActivate(top_level);
Expand Down
5 changes: 5 additions & 0 deletions source/web/Document.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ namespace web {
info->Append(new_widget);
return new_widget;
}
template <class... T> web::Input AddInput(T &&... args) {
web::Input new_widget(std::forward<T>(args)...);
info->Append(new_widget);
return new_widget;
}
template <class... T> web::Table AddTable(T &&... args) {
web::Table new_widget(std::forward<T>(args)...);
info->Append(new_widget);
Expand Down
2 changes: 1 addition & 1 deletion source/web/Input.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ namespace web {
if (value != "") HTML << " value=\"" << value << "\""; // Add a current value if there is one.
if (step != "") HTML << " step=\"" << step << "\""; // Add a step if there is one.
HTML << " id=\"" << id << "\""; // Indicate ID.
HTML << " onchange=\"" << onchange_info << "\""; // Indicate action on change.
HTML << " oninput=\"" << onchange_info << "\""; // Indicate action on change.
HTML << ">" << label << "</input>"; // Close and label the Input.
}

Expand Down
111 changes: 84 additions & 27 deletions source/web/Listeners.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
*/


// NEW CODE

#ifndef EMP_WEB_LISTENERS_H
#define EMP_WEB_LISTENERS_H

Expand All @@ -27,43 +29,60 @@ namespace web {
/// Track a set of JavaScript Listeners with their callback IDs.
class Listeners {
private:
std::map<std::string, size_t> listeners; ///< Map triggers to callback IDs
std::map<std::string, std::map<std::string, size_t>> listeners; ///< Map triggers to list of callback IDs. The outer map associates event names with an inner map, which maps handler IDs to their callbacks.

public:
Listeners() { ; }
Listeners(const Listeners &) = default;
Listeners & operator=(const Listeners &) = default;

/// How many listeners are we tracking?
size_t GetSize() const { return listeners.size(); }
/// How many listeners are we tracking?
// Should we just store this?
size_t GetSize() const {
size_t count = 0;
for (auto event_pair : listeners)
for (auto handler_pair : event_pair.second) {
count++;
}
return count;
}

/// Use a pre-calculated function ID with a new listener.
Listeners & Set(const std::string & name, size_t fun_id) {
emp_assert(!Has(name));
listeners[name] = fun_id;
Listeners & Set(const std::string & event_name, size_t fun_id, std::string handler_id="default") {
emp_assert(!HasHandler(event_name, handler_id));
listeners[event_name][handler_id] = fun_id;
return *this;
}

/// Calculate its own function ID with JSWrap.
template <typename... Ts>
Listeners & Set(const std::string & name, const std::function<void(Ts... args)> & in_fun) {
emp_assert(!Has(name));
listeners[name] = JSWrap(in_fun);
Listeners & Set(const std::string & event_name, const std::function<void(Ts... args)> & in_fun, std::string handler_id="default") {
emp_assert(!HasHandler(event_name, handler_id));
listeners[event_name][handler_id] = JSWrap(in_fun);
return *this;
}

/// Determine if a specified listener exists.
/// Determine if any listener exists with the specified event name.
bool Has(const std::string & event_name) const {
return listeners.find(event_name) != listeners.end();
}

/// Get the ID associated with a specific listener.
size_t GetID(const std::string & event_name) {
emp_assert(Has(event_name));
return listeners[event_name];
/// Determine if any listener exists with the specified event name and handler ID.
bool HasHandler(const std::string & event_name, std::string handler_id = "default") const {
if (listeners.find(event_name) != listeners.end()) {
const std::map<std::string, size_t>& m = listeners.at(event_name);
return m.find(handler_id) != m.end();
}
return false;
}

/// Get the ID associated with a specific listener.
size_t GetID(const std::string & event_name, std::string handler_id = "default") {
emp_assert(HasHandler(event_name, handler_id));
return listeners[event_name][handler_id];
}

const std::map<std::string, size_t> & GetMap() const {
const std::map<std::string, std::map<std::string, size_t>> & GetMap() const {
return listeners;
}

Expand All @@ -73,46 +92,84 @@ namespace web {
listeners.clear();
}

/// Remove a specific listener.
/// Remove all listeners with the given event name.
void Remove(const std::string & event_name) {
// @CAO: Delete function to be called.
listeners.erase(event_name);
}

/// Remove the listener with the given event name and handler ID.
void Remove(const std::string & event_name, std::string handler_id = "default") {
// @CAO: Delete function to be called.
if (Has(event_name))
{
listeners[event_name].erase(handler_id);
std::cout << "removed listener " << handler_id << std::endl;
}
}

/// Apply all of the listeners being tracked.
void Apply(const std::string & widget_id) {
void Apply(const std::string & widget_id, bool add_before_onclick = false) {
// Find the current object only once.
#ifdef __EMSCRIPTEN__
EM_ASM_ARGS({
var id = UTF8ToString($0);
emp_i.cur_obj = $( '#' + id );
}, widget_id.c_str());
if(add_before_onclick){
EM_ASM({
var onclick_handler = emp_i.onclick;
emp_i.removeProp('onclick');
});
}
#endif

for (auto event_pair : listeners) {
#ifdef __EMSCRIPTEN__
EM_ASM_ARGS({
var name = UTF8ToString($0);
emp_i.cur_obj.on( name, function(evt) { emp.Callback($1, evt); } );
}, event_pair.first.c_str(), event_pair.second);
#else
std::cout << "Setting '" << widget_id << "' listener '" << event_pair.first
<< "' to '" << event_pair.second << "'.";
for (auto handler_pair : event_pair.second) {
size_t & fun_id = handler_pair.second;
#ifdef __EMSCRIPTEN__
EM_ASM_ARGS({
var name = UTF8ToString($0);
emp_i.cur_obj.on( name, function(evt) { emp.Callback($1, evt); } );
}, event_pair.first.c_str(), fun_id);
if(add_before_onclick){
EM_ASM({
emp_i.click(onclick_handler);
});
}
#else
std::cout << "Setting '" << widget_id << "' listener '" << event_pair.first
<< "' to '" << fun_id << "'.";
#endif
}
}
}


/// Apply a SPECIFIC listener.
static void Apply(const std::string & widget_id,
const std::string event_name,
size_t fun_id) {
size_t fun_id, bool add_before_onclick = false) {
#ifdef __EMSCRIPTEN__
EM_ASM_ARGS({
var id = UTF8ToString($0);
var name = UTF8ToString($1);
if($3){
var onclick_handler = $('#' + id).prop('onclick');
if(onclick_handler){
$('#' + id).prop('onclick', null);
alert('bumping onclick back ' + '$(#' + id + ').prop(onclick, null)');
}
}
$( '#' + id ).on( name, function(evt) { emp.Callback($2, evt); } );
}, widget_id.c_str(), event_name.c_str(), fun_id);
if($3){
if(onclick_handler){
//$('#' + id).click(onclick_handler);
//$('#' + id).on('click', onclick_handler);
//$('#' + id).attr('onclick', 'onclick_handler();');
}
}
}, widget_id.c_str(), event_name.c_str(), fun_id, add_before_onclick);
#else
std::cout << "Setting '" << widget_id << "' listener '" << event_name
<< "' to function id '" << fun_id << "'.";
Expand Down
10 changes: 6 additions & 4 deletions source/web/Table.h
Original file line number Diff line number Diff line change
Expand Up @@ -537,13 +537,15 @@ namespace web {
}

/// Apply CSS to appropriate component based on current state.
void DoListen(const std::string & event_name, size_t fun_id) override {
parent_t::DoListen(event_name, fun_id);
void DoListen(const std::string & event_name, size_t fun_id,
const std::string handler_id="default",
bool add_before_onclick = false) override {
parent_t::DoListen(event_name, fun_id, handler_id, add_before_onclick);
}

public:
TableWidget(size_t r, size_t c, const std::string & in_id="")
: WidgetFacet(in_id), cur_row(0), cur_col(0)
TableWidget(size_t r, size_t c, const std::string & in_id=""):
WidgetFacet(in_id), cur_row(0), cur_col(0)
{
emp_assert(r > 0 && c > 0); // Ensure that we have rows and columns!
info = new internal::TableInfo(in_id);
Expand Down
Loading