Browse Source

Start implementing data view 'for' (highly WIP)

Michael Ragazzon 6 years ago
parent
commit
d568cfe18b

+ 4 - 0
Include/RmlUi/Core/BaseXMLParser.h

@@ -63,6 +63,9 @@ class RMLUICORE_API BaseXMLParser
 		/// Get the line number of the last open tag in the stream.
 		int GetLineNumberOpenTag() const;
 
+		// Treats all the inner contents of the currently parsing element as CData, including children nodes.
+		void TreatElementContentAsCDATA();
+
 		/// Called when the parser finds the beginning of an element tag.
 		virtual void HandleElementStart(const String& name, const XMLAttributes& attributes);
 		/// Called when the parser finds the end of an element tag.
@@ -105,6 +108,7 @@ class RMLUICORE_API BaseXMLParser
 		int line_number;
 		int line_number_open_tag;
 		int open_tag_depth;
+		bool treat_content_as_cdata;
 
 		// The element attributes being read.
 		XMLAttributes attributes;

+ 6 - 2
Include/RmlUi/Core/DataController.h

@@ -40,8 +40,12 @@ namespace Core {
 class Element;
 class DataModel;
 
+class RMLUICORE_API DataController {
 
-class DataControllerAttribute {
+};
+
+
+class DataControllerAttribute : public DataController {
 public:
 	DataControllerAttribute(const DataModel& model, const String& in_attribute_name, const String& in_value_name);
 
@@ -69,7 +73,7 @@ private:
 };
 
 
-class DataControllers {
+class RMLUICORE_API DataControllers {
 public:
 
 	void AddController(Element* element, DataControllerAttribute&& controller) {

+ 133 - 14
Include/RmlUi/Core/DataModel.h

@@ -31,6 +31,7 @@
 
 #include "Header.h"
 #include "Types.h"
+#include "Traits.h"
 #include "Variant.h"
 #include "StringUtilities.h"
 #include "DataView.h"
@@ -41,6 +42,8 @@ namespace Core {
 
 class DataBinding;
 class DataMember;
+class DataContainer;
+class Element;
 
 
 class DataModel {
@@ -54,6 +57,9 @@ public:
 		return GetValue(name, variant) && variant.GetInto<T>(out_value);
 	}
 
+	String ResolveVariableName(const String& raw_name, Element* parent) const;
+
+
 	using Bindings = UnorderedMap<String, UniquePtr<DataBinding>>;
 	Bindings bindings;
 
@@ -61,12 +67,15 @@ public:
 	DataViews views;
 
 	using DataMembers = SmallUnorderedMap<String, UniquePtr<DataMember>>;
-	using DataTypes = UnorderedMap<String, DataMembers>;
-
+	using DataTypes = UnorderedMap<int, DataMembers>;
 	DataTypes data_types;
-};
 
+	using DataContainers = UnorderedMap<String, UniquePtr<DataContainer>>;
+	DataContainers containers;
 
+	using ScopedAliases = UnorderedMap< Element*, SmallUnorderedMap<String, String> >;
+	mutable ScopedAliases aliases;
+};
 
 class DataBinding {
 public:
@@ -88,7 +97,6 @@ private:
 	void* ptr;
 };
 
-
 class DataMember {
 public:
 	virtual ~DataMember() = default;
@@ -114,6 +122,100 @@ private:
 	}
 };
 
+
+
+
+class DataBindingContext {
+public:
+	struct Item {
+		String name;
+		int index = -1;
+	};
+	DataBindingContext(std::vector<Item>&& in_items) : items(std::move(in_items)), it(items.begin()) {}
+
+	operator bool() const { return it != items.end(); }
+
+	const Item& Next() {
+		RMLUI_ASSERT(it != items.end());
+		return *(it++);
+	}
+private:
+	std::vector<Item> items;
+	std::vector<Item>::iterator it;
+};
+
+
+class DataContainer {
+public:
+	DataContainer(void* ptr) : ptr(ptr) {}
+	virtual ~DataContainer() = default;
+
+	inline bool Get(Variant& out_value, DataBindingContext& context) {
+		return Get(ptr, out_value, context);
+	}
+	inline bool Set(const Variant& in_value, DataBindingContext& context) {
+		return Set(ptr, in_value, context);
+	}
+	inline int Size() {
+		return Size(ptr);
+	}
+
+protected:
+	virtual bool Get(const void* container_ptr, Variant& out_value, DataBindingContext& context) = 0;
+	virtual bool Set(void* container_ptr, const Variant& in_value, DataBindingContext& context) = 0;
+	virtual int Size(void* container_ptr) = 0;
+
+private:
+	void* ptr;
+};
+
+
+template<typename T>
+class DataContainerDefault : public DataContainer {
+public:
+	DataContainerDefault(void* ptr) : DataContainer(ptr) {}
+
+private:
+	bool Get(const void* container_ptr, Variant& out_value, DataBindingContext& context) override {
+		if (!context)
+		{
+			RMLUI_ERROR;
+			return false;
+		}
+		auto& item = context.Next();
+		auto& container = *static_cast<const T*>(container_ptr);
+		if (item.index < 0 || item.index >= (int)container.size())
+		{
+			Log::Message(Log::LT_WARNING, "Data container index out of bounds.");
+			return false;
+		}
+		out_value = container[item.index];
+		return true;
+	}
+	bool Set(void* container_ptr, const Variant& in_value, DataBindingContext& context) override {
+		if (!context)
+		{
+			RMLUI_ERROR;
+			return false;
+		}
+		auto& item = context.Next();
+		auto& container = *static_cast<T*>(container_ptr);
+		if (item.index < (int)container.size() || item.index >= (int)container.size())
+		{
+			Log::Message(Log::LT_WARNING, "Data container index out of bounds.");
+			return false;
+		}
+		container[item.index] = in_value.Get< typename T::value_type >();
+		return true;
+	}
+	int Size(void* container_ptr) override {
+		return (int)static_cast<T*>(container_ptr)->size();
+	}
+};
+
+
+
+
 class DataBindingMember : public DataBinding {
 public:
 	DataBindingMember(void* object, DataMember* member) : DataBinding(object), member(member) {}
@@ -149,11 +251,13 @@ private:
 };
 
 
+
+template <typename Object>
 class DataTypeHandle {
 public:
 	DataTypeHandle(DataModel::DataMembers* members) : members(members) {}
 
-	template <typename Object, typename MemberType>
+	template <typename MemberType>
 	DataTypeHandle& RegisterMember(String name, MemberType Object::* member_ptr)
 	{
 		RMLUI_ASSERT(members);
@@ -179,15 +283,24 @@ public:
 		return *this;
 	}
 
-	DataModelHandle& BindTypeValue(String name, String type_name, void* object)
+	template <typename Container>
+	DataModelHandle& BindContainer(String name, Container* object)
 	{
 		RMLUI_ASSERT(model);
-		// Todo: We can make this type safe, removing the need for type_name.
-		//   Make this a templated function, create another templated "family" class which assigns
-		//   a unique id for each new type encountered, look up the type name there. Or use the ID as
-		//   the look-up key.
+		//using T = Container::value_type;
+
+		model->containers.emplace(name, std::make_unique<DataContainerDefault<Container>>(object));
+		return *this;
+	}
 
-		auto it = model->data_types.find(type_name);
+	template <typename T>
+	DataModelHandle& BindTypeValue(String name, T* object)
+	{
+		RMLUI_ASSERT(model);
+
+		int id = Family< typename std::remove_pointer< T >::type >::Id();
+
+		auto it = model->data_types.find(id);
 		if (it != model->data_types.end())
 		{
 			auto& members = it->second;
@@ -203,11 +316,17 @@ public:
 		return *this;
 	}
 
-	DataTypeHandle RegisterType(String name)
+	template <typename T>
+	DataTypeHandle<T> RegisterType()
 	{
 		RMLUI_ASSERT(model);
-		auto result = model->data_types.emplace(name, DataModel::DataMembers() );
-		return DataTypeHandle(&result.first->second);
+		int id = Family< T >::Id();
+		auto result = model->data_types.emplace(id, DataModel::DataMembers() );
+		if (!result.second) {
+			RMLUI_ERRORMSG("Type already registered.");
+			return DataTypeHandle<T>(nullptr);
+		}
+		return DataTypeHandle<T>(&result.first->second);
 	}
 
 	void UpdateControllers() {

+ 37 - 9
Include/RmlUi/Core/DataView.h

@@ -41,8 +41,11 @@ class Element;
 class ElementText;
 class DataModel;
 
+class RMLUICORE_API DataView {
 
-class DataViewText {
+};
+
+class DataViewText : public DataView {
 public:
 	DataViewText(const DataModel& model, ElementText* in_element, const String& in_text, size_t index_begin_search = 0);
 
@@ -68,7 +71,7 @@ private:
 
 
 
-class DataViewAttribute {
+class DataViewAttribute : public DataView {
 public:
 	DataViewAttribute(const DataModel& model, Element* element, const String& binding_name, const String& attribute_name);
 
@@ -84,7 +87,7 @@ private:
 };
 
 
-class DataViewStyle {
+class DataViewStyle : public DataView {
 public:
 	DataViewStyle(const DataModel& model, Element* element, const String& binding_name, const String& property_name);
 
@@ -100,7 +103,7 @@ private:
 };
 
 
-class DataViewIf {
+class DataViewIf : public DataView {
 public:
 	DataViewIf(const DataModel& model, Element* element, const String& binding_name);
 
@@ -115,7 +118,26 @@ private:
 };
 
 
-class DataViews {
+class DataViewFor : public DataView {
+public:
+	DataViewFor(const DataModel& model, Element* element, const String& binding_name, const String& rml_contents);
+
+	inline operator bool() const {
+		return !binding_name.empty() && element;
+	}
+	bool Update(const DataModel& model);
+
+private:
+	ObserverPtr<Element> element;
+	String binding_name;
+	String rml_contents;
+	ElementAttributes attributes;
+
+	std::vector<Element*> elements;
+};
+
+
+class RMLUICORE_API DataViews {
 public:
 
 	void AddView(DataViewText&& view) {
@@ -124,11 +146,14 @@ public:
 	void AddView(DataViewAttribute&& view) {
 		attribute_views.push_back(std::move(view));
 	}
+	void AddView(DataViewStyle&& view) {
+		style_views.push_back(std::move(view));
+	}
 	void AddView(DataViewIf&& view) {
 		if_views.push_back(std::move(view));
 	}
-	void AddView(DataViewStyle&& view) {
-		style_views.push_back(std::move(view));
+	void AddView(DataViewFor&& view) {
+		for_views.push_back(std::move(view));
 	}
 
 	bool Update(const DataModel& model)
@@ -138,9 +163,11 @@ public:
 			result |= view.Update(model);
 		for (auto& view : attribute_views)
 			result |= view.Update(model);
+		for (auto& view : style_views)
+			result |= view.Update(model);
 		for (auto& view : if_views)
 			result |= view.Update(model);
-		for (auto& view : style_views)
+		for (auto& view : for_views)
 			result |= view.Update(model);
 		return result;
 	}
@@ -148,8 +175,9 @@ public:
 private:
 	std::vector<DataViewText> text_views;
 	std::vector<DataViewAttribute> attribute_views;
-	std::vector<DataViewIf> if_views;
 	std::vector<DataViewStyle> style_views;
+	std::vector<DataViewIf> if_views;
+	std::vector<DataViewFor> for_views;
 };
 
 }

+ 6 - 0
Include/RmlUi/Core/Element.h

@@ -575,6 +575,12 @@ public:
 	/// Return the computed values of the element's properties. These values are updated as appropriate on every Context::Update.
 	const ComputedValues& GetComputedValues() const;
 
+
+
+	// TODO: Temporary, make private / remove.
+	DataModel* GetDataModel() { return data_model; }
+	void SetDataModel(DataModel* new_data_model) { data_model = new_data_model; }
+
 protected:
 	void Update(float dp_ratio);
 	void Render();

+ 23 - 0
Include/RmlUi/Core/Traits.h

@@ -71,6 +71,29 @@ public:
 	}
 };
 
+class RMLUICORE_API FamilyBase {
+protected:
+	static int GetNewId() {
+		static int id = 0;
+		return id++;
+	}
+	template<typename T>
+	static int GetId() {
+		static int id = GetNewId();
+		return id;
+	}
+};
+
+template<typename T>
+class Family : FamilyBase {
+public:
+	// Get a unique ID for a given type.
+	// Note: IDs will be unique across DLL-boundaries even for the same type.
+	static int Id() {
+		return GetId< typename std::remove_cv< typename std::remove_reference< T >::type >::type >();
+	}
+};
+
 }
 }
 

+ 14 - 0
Include/RmlUi/Core/TypeConverter.h

@@ -54,6 +54,20 @@ public:
 	static bool Convert(const SourceType& src, DestType& dest);
 };
 
+template<typename T>
+inline String ToString(const T& value, String default_value = String()) {
+	String result = default_value;
+    TypeConverter<T, String>::Convert(value, result);
+	return result;
+}
+
+template<typename T>
+inline T FromString(const String& string, T default_value = T()) {
+    T result = default_value;
+    TypeConverter<String, T>::Convert(string, result);
+    return result;
+}
+
 
 // Some more complex types are defined in cpp-file
 

+ 8 - 3
Samples/basic/databinding/data/databinding.rml

@@ -167,10 +167,15 @@ form h2
 	<p>Data binding demo. We rate this a good old {{rating}}!</p>
 	<input type="range" name="rating" min="0" max="100" step="1" value="50" data-attr-value="rating"/>
 	<div data-if="good_rating">Thanks for the awesome rating!</div>
-	<div data-for="invader : invaders">
-		<h1>{{invader.name}}</h1>
-		<img data-attr-sprite="invader.sprite" data-style-image-color="invader.color"/>
+	<h1>{{invader.name}}</h1>
+	<img data-attr-sprite="invader.sprite" data-style-image-color="invader.color"/>
+	<div data-for="indices">
+		<h1>{{it}}</h1>
 	</div>
+	<!--<div data-for="invader : invaders">
+		<h1>{{it.name}}</h1>
+		<img data-attr-sprite="it.sprite" data-style-image-color="it.color"/>
+	</div>-->
 </panel>
 <tab>Decorators</tab>
 <panel id="decorators">

+ 7 - 3
Samples/basic/databinding/src/main.cpp

@@ -118,6 +118,8 @@ struct MyData {
 	Invader invader{ "Delightful invader", "icon-invader", "red" };
 
 	std::vector<Invader> invaders;
+
+	std::vector<int> indices = { 1, 2, 3, 4, 5 };
 } my_data;
 
 Rml::Core::DataModelHandle my_model;
@@ -132,12 +134,14 @@ bool SetupDataBinding(Rml::Core::Context* context)
 	my_model.BindValue("rating", &my_data.rating);
 	my_model.BindValue("good_rating", &my_data.good_rating);
 
-	auto invader_type = my_model.RegisterType("Invader");
+	auto invader_type = my_model.RegisterType<Invader>();
 	invader_type.RegisterMember("name", &Invader::name);
 	invader_type.RegisterMember("sprite", &Invader::sprite);
 	invader_type.RegisterMember("color", &Invader::color);
 
-	my_model.BindTypeValue("invader", "Invader", &my_data.invader);
+	my_model.BindTypeValue("invader", &my_data.invader);
+
+	my_model.BindContainer("indices", &my_data.indices);
 
 	return true;
 }
@@ -151,7 +155,7 @@ std::unique_ptr<DemoWindow> demo_window;
 void GameLoop()
 {
 	my_model.UpdateControllers();
-	my_data.good_rating = int(my_data.rating > 50);
+	my_data.good_rating = (my_data.rating > 50);
 	my_model.UpdateViews();
 
 	demo_window->Update();

+ 11 - 2
Source/Core/BaseXMLParser.cpp

@@ -44,6 +44,7 @@ BaseXMLParser::BaseXMLParser()
 	buffer_used = 0;
 	buffer_size = 0;
 	open_tag_depth = 0;
+	treat_content_as_cdata = false;
 }
 
 BaseXMLParser::~BaseXMLParser()
@@ -63,6 +64,7 @@ void BaseXMLParser::Parse(Stream* stream)
 {
 	xml_source = stream;
 	buffer_size = DEFAULT_BUFFER_SIZE;
+	treat_content_as_cdata = false;
 
 	buffer = (unsigned char*) malloc(buffer_size);
 	read = buffer;
@@ -107,6 +109,11 @@ void BaseXMLParser::HandleData(const String& RMLUI_UNUSED_PARAMETER(data))
 	RMLUI_UNUSED(data);
 }
 
+void BaseXMLParser::TreatElementContentAsCDATA()
+{
+	treat_content_as_cdata = true;
+}
+
 void BaseXMLParser::ReadHeader()
 {
 	if (PeekString((unsigned char*) "<?"))
@@ -177,6 +184,8 @@ bool BaseXMLParser::ReadOpenTag()
 	// Increase the open depth
 	open_tag_depth++;
 
+	treat_content_as_cdata = false;
+
 	// Opening tag; send data immediately and open the tag.
 	if (!data.empty())
 	{
@@ -233,11 +242,11 @@ bool BaseXMLParser::ReadOpenTag()
 		}
 	}
 
-	// Check if this tag needs to processed as CDATA.
+	// Check if this tag needs to be processed as CDATA.
 	if (section_opened)
 	{
 		String lcase_tag_name = StringUtilities::ToLower(tag_name);
-		if (cdata_tags.find(lcase_tag_name) != cdata_tags.end())
+		if (treat_content_as_cdata || cdata_tags.find(lcase_tag_name) != cdata_tags.end())
 		{
 			if (ReadCDATA(lcase_tag_name.c_str()))
 			{

+ 51 - 0
Source/Core/DataModel.cpp

@@ -37,6 +37,29 @@ bool DataModel::GetValue(const String& name, Variant& out_value) const
 {
 	bool success = true;
 
+	auto pos = name.find('[');
+	if (pos != String::npos)
+	{
+		auto pos_end = name.find(']', pos + 2);
+		if(pos_end != String::npos)
+		{
+			const String container_name = name.substr(0, pos);
+			const int index = FromString(name.substr(pos + 1, pos_end - pos + 1), -1);
+			if (index >= 0)
+			{
+				auto it_container = containers.find(container_name);
+				if (it_container != containers.end())
+				{
+					DataBindingContext context({ DataBindingContext::Item{ "", index} });
+
+					if (it_container->second->Get(out_value, context))
+						return true;
+				}
+			}
+		}
+		return false;
+	}
+
 	auto it = bindings.find(name);
 	if (it != bindings.end())
 	{
@@ -78,5 +101,33 @@ bool DataModel::SetValue(const String& name, const Variant& value) const
 	return success;
 }
 
+String DataModel::ResolveVariableName(const String& raw_name, Element* parent) const
+{
+	auto it = bindings.find(raw_name);
+	if (it != bindings.end())
+		return raw_name;
+
+	Element* ancestor = parent;
+	while (ancestor && ancestor->GetDataModel() == this)
+	{
+		auto it_element = aliases.find(ancestor);
+		if (it_element != aliases.end())
+		{
+			auto& alias_names = it_element->second;
+			auto it_alias_name = alias_names.find(raw_name);
+			if (it_alias_name != alias_names.end())
+			{
+				return it_alias_name->second;
+			}
+		}
+
+		ancestor = ancestor->GetParentNode();
+	}
+
+	Log::Message(Log::LT_WARNING, "Could not find variable name '%s' in data model.", raw_name.c_str());
+
+	return String();
+}
+
 }
 }

+ 53 - 0
Source/Core/DataView.cpp

@@ -59,6 +59,8 @@ DataViewText::DataViewText(const DataModel& model, ElementText* in_parent_elemen
 		DataEntry entry;
 		entry.index = text.size();
 		entry.binding_name = (String)StringUtilities::StripWhitespace(StringView(in_text.data() + begin_name, in_text.data() + end_name));
+		entry.binding_name = model.ResolveVariableName(entry.binding_name, in_parent_element);
+
 		data_entries.push_back(std::move(entry));
 
 		previous_close_brackets = end_name + 2;
@@ -220,5 +222,56 @@ bool DataViewIf::Update(const DataModel& model)
 	return result;
 }
 
+
+
+DataViewFor::DataViewFor(const DataModel& model, Element* element, const String& binding_name, const String& in_rml_content) 
+	: element(element->GetObserverPtr()), binding_name(binding_name), rml_contents(in_rml_content)
+{
+	attributes = element->GetAttributes();
+	attributes.erase("data-for");
+	element->SetProperty(PropertyId::Display, Property(Style::Display::None));
+	Update(model);
+}
+
+
+
+bool DataViewFor::Update(const DataModel& model)
+{
+	bool result = false;
+	bool value = false;
+
+	const int size = model.containers.find(binding_name)->second->Size();
+	const int num_elements = (int)elements.size();
+
+	for (int i = 0; i < Math::Max(size, num_elements); i++)
+	{
+		if (i >= num_elements)
+		{
+			ElementPtr new_element_ptr = Factory::InstanceElement(nullptr, element->GetTagName(), element->GetTagName(), attributes);
+			new_element_ptr->SetDataModel((DataModel*)(&model));
+			Element* new_element = element->GetParentNode()->InsertBefore(std::move(new_element_ptr), element.get());
+			elements.push_back(new_element);
+
+			auto& alias_map = model.aliases.emplace(new_element, SmallUnorderedMap<String, String>()).first->second;
+			alias_map["it"] = binding_name + "[" + ToString(i) + "]";
+
+			elements[i]->SetInnerRML(rml_contents);
+
+			RMLUI_ASSERT(i < (int)elements.size());
+		}
+		if (i >= size)
+		{
+			elements[i]->GetParentNode()->RemoveChild(elements[i]).reset();
+			model.aliases.erase(elements[i]);
+			elements[i] = nullptr;
+		}
+	}
+
+	if (num_elements > size)
+		elements.resize(size);
+
+	return result;
+}
+
 }
 }

+ 17 - 15
Source/Core/Factory.cpp

@@ -240,6 +240,8 @@ ElementPtr Factory::InstanceElement(Element* parent, const String& instancer_nam
 			ElementUtilities::BindEventAttributes(element.get());
 
 
+			// TODO: Relies on parent, a bit hacky.
+			if (parent && !parent->HasAttribute("data-for"))
 			{
 				// Look for the data-model attribute or otherwise copy it from the parent element
 				auto it = attributes.find("data-model");
@@ -295,14 +297,6 @@ ElementPtr Factory::InstanceElement(Element* parent, const String& instancer_nam
 								else
 									Log::Message(Log::LT_WARNING, "Could not add data-attr controller to element '%s'.", parent->GetAddress().c_str());
 							}
-							else if (data_type == "if")
-							{
-								DataViewIf view(*data_model, element.get(), value_bind_name);
-								if (view)
-									data_model->views.AddView(std::move(view));
-								else
-									Log::Message(Log::LT_WARNING, "Could not add data-if view to element '%s'.", parent->GetAddress().c_str());
-							}
 							else if (data_type == "style")
 							{
 								const String property_name = name.substr(5 + data_type.size() + 1);
@@ -313,6 +307,14 @@ ElementPtr Factory::InstanceElement(Element* parent, const String& instancer_nam
 								else
 									Log::Message(Log::LT_WARNING, "Could not add data-style view to element '%s'.", parent->GetAddress().c_str());
 							}
+							else if (data_type == "if")
+							{
+								DataViewIf view(*data_model, element.get(), value_bind_name);
+								if (view)
+									data_model->views.AddView(std::move(view));
+								else
+									Log::Message(Log::LT_WARNING, "Could not add data-if view to element '%s'.", parent->GetAddress().c_str());
+							}
 						}
 					}
 				}
@@ -367,21 +369,24 @@ bool Factory::InstanceElementText(Element* parent, const String& text)
 
 		// Attempt to instance the element.
 		XMLAttributes attributes;
-		ElementPtr element = Factory::InstanceElement(parent, "#text", "#text", attributes);
-		if (!element)
+		ElementPtr element_ptr = Factory::InstanceElement(parent, "#text", "#text", attributes);
+		if (!element_ptr)
 		{
 			Log::Message(Log::LT_ERROR, "Failed to instance text element '%s', instancer returned nullptr.", translated_data.c_str());
 			return false;
 		}
 
 		// Assign the element its text value.
-		ElementText* text_element = rmlui_dynamic_cast< ElementText* >(element.get());
+		ElementText* text_element = rmlui_dynamic_cast< ElementText* >(element_ptr.get());
 		if (!text_element)
 		{
-			Log::Message(Log::LT_ERROR, "Failed to instance text element '%s'. Found type '%s', was expecting a derivative of ElementText.", translated_data.c_str(), rmlui_type_name(*element));
+			Log::Message(Log::LT_ERROR, "Failed to instance text element '%s'. Found type '%s', was expecting a derivative of ElementText.", translated_data.c_str(), rmlui_type_name(*element_ptr));
 			return false;
 		}
 
+		// Add to active node.
+		Element* element = parent->AppendChild(std::move(element_ptr));
+
 		// See if this text element uses data bindings.
 		bool data_view_added = false;
 		if (DataModel* data_model = element->data_model)
@@ -404,9 +409,6 @@ bool Factory::InstanceElementText(Element* parent, const String& text)
 
 		if(!data_view_added)
 			text_element->SetText(translated_data);
-
-		// Add to active node.
-		parent->AppendChild(std::move(element));
 	}
 
 	return true;

+ 22 - 0
Source/Core/XMLNodeHandlerDefault.cpp

@@ -64,6 +64,11 @@ Element* XMLNodeHandlerDefault::ElementStart(XMLParser* parser, const String& na
 	// Move and append the element to the parent
 	Element* result = parent->AppendChild(std::move(element));
 
+	if (result->GetDataModel() && attributes.count("data-for") == 1)
+	{
+		parser->TreatElementContentAsCDATA();
+	}
+
 	return result;
 }
 
@@ -84,6 +89,23 @@ bool XMLNodeHandlerDefault::ElementData(XMLParser* parser, const String& data)
 	
 	RMLUI_ASSERT(parent);
 
+	if (auto data_model = parent->GetDataModel())
+	{
+		if(auto attribute = parent->GetAttribute("data-for"))
+		{
+			String value_bind_name = attribute->Get<String>();
+
+			DataViewFor view(*data_model, parent, value_bind_name, data);
+			if (view)
+				data_model->views.AddView(std::move(view));
+			else
+				Log::Message(Log::LT_WARNING, "Could not add data-for view to element '%s'.", parent->GetAddress().c_str());
+
+			return true;
+		}
+	}
+
+
 	// Parse the text into the element
 	return Factory::InstanceElementText(parent, data);
 }