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

MVC Pattern and Data Binding #25

Closed
uniquejack opened this issue May 15, 2019 · 4 comments
Closed

MVC Pattern and Data Binding #25

uniquejack opened this issue May 15, 2019 · 4 comments
Labels
data binding enhancement New feature or request
Milestone

Comments

@uniquejack
Copy link

uniquejack commented May 15, 2019

Is there any way to see this feature implemented just like AngularJS or CoherentGT?

Some examples:
https://www.w3schools.com/angular/angular_databinding.asp
https://coherent-labs.com/Documentation/cpp-gt/df/dfb/_h_t_m_l_data_binding.html

Also would be cool to create DataTemplates through bindings and converters.

@mikke89
Copy link
Owner

mikke89 commented May 15, 2019

This looks really interesting to me, I think I'd like something like this myself. Thanks for the suggestion. I think the Coherent approach can serve as a nice inspiration. Are there any particular features here you think are the most important?

Also, if you have any suggestions for what the interface could look like, that would be great.

@mikke89 mikke89 added the enhancement New feature or request label May 15, 2019
@uniquejack
Copy link
Author

uniquejack commented May 15, 2019

If you want to use the Coherent approach then I would do something like this:

class Page
{
    std::string Text;
}

// Somewhere in the initialization of the UI
void RocketInterface::Bind(Context* context)
{
   // Bind Text
   Rocket::RegisterProperty(context, "Text", Page::Text);
}

void RocketInterface::LoadRml(std::string file)
{
   auto document = rocketContext_->Load(file);

   if (document == nullptr)
      return;

   document->SetDataContext(new Page());
}
....

<p>{{Text}}</p>

It's very important to implement a ViewModel class that has a PropertyChange notifier so whenever a property change, we can manually tell the UI to update a single property or the whole model. Example:

void Page::UpdateIfNeeded()
{
   if (!TextChanged)
      return;

  // Update everything
  PageHandle_->UpdateWholeModel();
  // Or update a single property
  PageHandle_->UpdateProperty("Text"); 

  TextChanged = false;
}

the binding might be a two-way type, that means that the UI can change the value through a setter. Example:

<input type="text" value="{{StringName}}/>

I would do a few changes in the DataGrid control as well

<datagrid bind-items="{{ServerList}}" bind-selected-index="{{SelectedServer}}">
   <datagridcolumn header="Server Name" bind-value={{ServerName}}/>
   <datagridcolumn header="Num. Players" bind-value={{NumPlayers}}/>
   <datagridcolumn header="Max. Players" bind-value{{MaxPlayers}}/>
</datagrid>

....

class ServerPage
{
   std::vector<ServerInfo> ServerList;
   size_t SelectedServer;
}

struct ServerInfo
{
   std::string ServerName;
   int NumPlayers;
   int MaxPlayers;
};

void Bind(Context* context)
{
   Rocket::RegisterProperty(context, "ServerName", ServerInfo::ServerName);
   Rocket::RegisterProperty(context, "NumPlayers", ServerInfo::NumPlayers);
   Rocket::RegisterProperty(context, "MaxPlayers", ServerInfo::MaxPlayers);

   Rocket::RegisterProperty(context, "ServerList", ServerPage::ServerList);
   Rocket::RegisterProperty(context, "SelectedServer", ServerPage::SelectedServer);
}

Would be also nice having a style binding like this:

<div data-bind-style-visibility="{{IsVisible}}"/>

In this case we are binding a boolean to a visibility converter. LibRocket in this particular case automatically converts the boolean to a string, but this is not always the case. Sometimes for particular values we might need some kind of Converters.

Here we have two choices:
1 - Using a scripting language just like CoherentGT does:
<div data-bind-style-visibility="{{IsVisible}} ? 'visible' : 'collapsed'"/>
2 - Implement BaseValueConverter class

class BooleanVisibilityConverter : BaseValueConverter
{
public:
   virtual void Convert(void* value, Rocket::Variant& target)
   {
      bool IsVibile = *reinterpret_cast<bool*>(value);
      target = (IsVisible) ? "visible" : "collpased";
   }
}

...

<div data-bind-style-visibility="{{IsVisible, converter='BooleanVisibilityConverter'}}"/>

Another example for a possible converter:

class UpperCaseFormatter : BaseValueConverter
{
public:
   virtual void Convert(void* value, Rocket::Variant& target)
   {
      std::string text = *reinterpret_cast<std::string*>(value);
      target = std::toupper(text);
   }
}

...

<p data-bind-value="{{Text, converter='UpperCaseFormatter'}}"/>

Personally I prefer using converters as we don't always want to use scripting languages in libRocket, but both are valid ways to implement the thing.

Last thing we would need to implement are the event triggers:

class Page
{
public:
   void OnButtonClick()
   {

   }
}

void Bind(Context* context)
{
   Rocket::RegisterMethod(context, "OnButtonClick", Page::OnButtonClick);
}

....

<button data-bind-click="OnButtonClick"/>

Generally the binding will be

<div data-bind-[eventname]="commandName"/>

EDIT: Ideally we should keep the code separated from the view. That's the logic behind it.
Using bindings would make our life much easier.

EDIT2: Converters aren't a priority though as we could just avoid them by binding get() and set() methods for the properties rather than binding the properties directly:

RegisterProperty(ctx, &Page::GetText, &Page::SetText, "Text");

@mikke89
Copy link
Owner

mikke89 commented May 16, 2019

Thanks for the writeup, looks like a good start to me!

Yes, I also think whichever solution we go for, all the capabilities should be available from the C++-API.

You seem to have thought quite well through this, do you wish to give this a go? Otherwise, I can take a stab at this, but it might be awhile before I have time to really look at this.

@mikke89
Copy link
Owner

mikke89 commented Nov 16, 2020

We've had support for data bindings in the master branch for a while now. Closing this now, however, users are still encouraged to provide feedback and bug reports in new issues.

@mikke89 mikke89 closed this as completed Nov 16, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
data binding enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants