Search code examples
c++visual-studio-2010embedhtmlwt

Can multiple WT application run on the same webpage?


so recently I asked a question to see if Can multiple WT applications run on same port? and the answer was a yes, (+1 to Jorge Núñez for that awesome answer). However, now I am trying to take his solution a step further to see if multiple WT applications can be run on the same page, by embedding them in a kind of host WT application. What I have done is created a host WT application that has an accessor for its root() and has a WTabWidget. Then in the CreateHostApplication function I created tabs for the 2 test applications, which also have accessors for their root(), and added their root() to the tab that belongs to my host application, then after all the apps were added to their individual tabs, I returned the host.

The cool part is that the widgets from the test applications showed up in their tabs as I expected, what I didn't expect is that the connect() calls to connect buttons to functions failed. So the widgets are functional as far as clicking them and editing text in boxes goes, but they do nothing else as they are not connected to any custom functions.

After debugging this for a bit, am pretty sure the connect calls failed on the test apps because they are not actually hosted by the browser, which brings me here. I have not been able to find a solution to this problem, is there a way to get the connect calls functional with this setup?

The code below is the solution by Jorge Núñez, with the above mentioned modifications. I am using visual studio2010 for development. Thank you in advance for any help!

#include <Wt/WApplication>

#include <Wt/WBreak>          
#include <Wt/WContainerWidget>
#include <Wt/WLineEdit>       
#include <Wt/WPushButton>     
#include <Wt/WText>           
#include <Wt/WException>      
#include <Wt/WLogger>         
#include <Wt/WServer>         
#include <Wt/WTabWidget>

using namespace Wt;


class Host : public Wt::WApplication
{
public:
  Host(const Wt::WEnvironment& env);
  WContainerWidget* GetRoot()
  {
    return root();
  }
};
Host::Host(const Wt::WEnvironment& env) : Wt::WApplication(env)
{

}

class TestApp1 : public Wt::WApplication
{
public:
  TestApp1(const Wt::WEnvironment& env, const std::string& title);
  WContainerWidget* GetRoot()
  {
    return root();
  }

private:
  Wt::WLineEdit* _name_edit;
  Wt::WText* _greeting;

  void Greet();
};
TestApp1::TestApp1(const Wt::WEnvironment& env, const std::string& title) : Wt::WApplication(env)
{
  setTitle(title);

  root()->addWidget(new Wt::WText("Your name, please ? "));
  _name_edit = new Wt::WLineEdit(root());

  Wt::WPushButton* button = new Wt::WPushButton("Greet me.", root());
  root()->addWidget(new Wt::WBreak());

  _greeting = new Wt::WText(root());
  button->clicked().connect(this, &TestApp1::Greet);
}
void TestApp1::Greet()
{
  _greeting->setText("Hello there, " + _name_edit->text());
}

class TestApp2 : public Wt::WApplication
{
public:
  TestApp2(const Wt::WEnvironment& env, const std::string& title);
  WContainerWidget* GetRoot()
  {
    return root();
  }

private:    Wt::WLineEdit *_name_edit;
            Wt::WText *_greeting;

            void greet();
};
TestApp2::TestApp2(const Wt::WEnvironment& env, const std::string& title) : Wt::WApplication(env)
{
  setTitle(title);

  root()->addWidget(new Wt::WText("Your name, please ? "));
  _name_edit = new Wt::WLineEdit(root());

  Wt::WPushButton* button = new Wt::WPushButton("Say goodbye.", root());
  root()->addWidget(new Wt::WBreak());

  _greeting = new Wt::WText(root());
  button->clicked().connect(this, &TestApp2::greet);
}
void TestApp2::greet()
{
  _greeting->setText("Goodbye, " + _name_edit->text());
}

Wt::WTabWidget* tab_widget;
Wt::WApplication* CreateHostApplication(const Wt::WEnvironment& env)
{
  Host* host = new Host(env);

  WContainerWidget* root = host->GetRoot();

  tab_widget = new WTabWidget(root);

  //Create tab for the app
  WContainerWidget* Tab_TestApp1 = new WContainerWidget();
  //Get a pointer to the ACE tab
  tab_widget->addTab(Tab_TestApp1, "Test Application 1", Wt::WTabWidget::LoadPolicy::PreLoading);
  //Create app
  TestApp1* test_app_1 = new TestApp1(env, "Test Application 1");
  //Add app root to the tab
  Tab_TestApp1->addWidget(test_app_1->GetRoot());

  //Create tab for the app
  WContainerWidget* Tab_TestApp2 = new WContainerWidget();
  //Get a pointer to the ACE tab
  tab_widget->addTab(Tab_TestApp2, "Test Application 2", Wt::WTabWidget::LoadPolicy::PreLoading);
  //Create app
  TestApp2* test_app_2 = new TestApp2(env, "Test Application 2");
  //Add app root to the tab
  Tab_TestApp2->addWidget(test_app_2->GetRoot());

  return host;
}
Wt::WApplication* CreateTestApp1(const Wt::WEnvironment& env)
{
  return new TestApp1(env, "Test Application 1");
}
Wt::WApplication* CreateTestApp2(const Wt::WEnvironment& env)
{
  return new TestApp2(env, "Test Application 2");
}

int TestWRun(int argc, char* argv[],
  Wt::ApplicationCreator host_application,
  std::vector<Wt::ApplicationCreator> applications)
{
  try 
  {
    // use argv[0] as the application name to match a suitable entry
    // in the Wt configuration file, and use the default configuration
    // file (which defaults to /etc/wt/wt_config.xml unless the environment
    // variable WT_CONFIG_XML is set)
    Wt::WServer server(argv[0],"");

    // WTHTTP_CONFIGURATION is e.g. "/etc/wt/wthttpd"
    server.setServerConfiguration(argc, argv, WTHTTP_CONFIGURATION);

    // add a single entry point, at the default location (as determined
    // by the server configuration's deploy-path)
    server.addEntryPoint(Wt::Application, host_application);

    unsigned int num_apps = applications.size();
    for(unsigned int cur_app = 0; cur_app < num_apps; ++cur_app)
    {
      server.addEntryPoint(Wt::Application, applications[cur_app], "/" + boost::lexical_cast<std::string>(cur_app));
    }

    if (server.start()) 
    {
      int sig = Wt::WServer::waitForShutdown(argv[0]);

      std::cerr << "Shutdown (signal = " << sig << ")" << std::endl;
      server.stop();
    }
  } 
  catch (Wt::WServer::Exception& e) 
  {
    std::cerr << e.what() << "\n";
    return 1;
  } 
  catch (std::exception& e) 
  {
    std::cerr << "exception: " << e.what() << "\n";
    return 1;
  }
}
int main(int argc, char** argv)
{
  std::vector<Wt::ApplicationCreator> applications;
  applications.push_back(&CreateTestApp1);
  applications.push_back(&CreateTestApp2);

  return TestWRun(argc, argv, &CreateHostApplication,  applications);
}

Solution

  • While it's possible with Wt, your approach is not 100% correct. AFAIK you have two options:

    1. Use Wt's widgetset mode. See how the chat widget is integrated on Wt's home page: these are two independent Wt applications, that are shown simultaneously on the same page, and that are both active. Wt's widgetset mode is best compared to the Google maps widget: you put a small placeholder (a div + some JS) on a webpage where you want the application to be rendered. See examples/wt-home, examples/feature/widgetset
    2. There is no problem at all in Wt to reuse widgets from one application in another application. Put the part that you want to reuse all together in a WContainerWidget (or a WCompositeWidget), and integrate that in your tabwidgets. Actually, that resembles pretty much to what you're doing now, but instead of taking the root() of TestApp1, organise your code so that you'd only put a single widget in the root of TestApp1, and use the same widget in the tabs of the Host application. In createApplication you must not instantiate more than one WApplication object. Widgetgallery uses this approach to integrate parts of the charts example.