/*
* Copyright 2014 cruxframework.org.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.cruxframework.crux.core.client.screen.binding;
import org.cruxframework.crux.core.client.Crux;
import org.cruxframework.crux.core.client.html5.api.MutationObserver;
import org.cruxframework.crux.core.client.html5.api.MutationObserver.LoadCallback;
import org.cruxframework.crux.core.client.html5.api.MutationObserver.MutationRecord;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.logical.shared.AttachEvent;
import com.google.gwt.user.client.ui.IsWidget;
/**
* Interface for data binding between dataObjects and widgets in this view.
* @author Thiago da Rosa de Bustamante
*
*/
public abstract class PropertyBinder<T, W extends IsWidget>
{
protected DataObjectBinder<T> dataObjectBinder;
protected W widget;
private boolean boundToAttribute;
@SuppressWarnings("unchecked")
protected void bind(IsWidget w)
{
this.widget = (W) w;
listenDOMChanges();
}
protected void setDataObjectBinder(DataObjectBinder<T> dataObjectBinder)
{
this.dataObjectBinder = dataObjectBinder;
}
protected boolean isBound()
{
return widget != null;
}
protected void observeDOMChanges()
{
widget.asWidget().addAttachHandler(new AttachEvent.Handler()
{
private MutationObserver mutationObserver;
@Override
public void onAttachOrDetach(AttachEvent event)
{
if (event.isAttached())
{
MutationObserver.load(new MutationObserver.Callback()
{
@Override
public void onChanged(JsArray<MutationRecord> mutations, MutationObserver observer)
{
notifyChanges();
}
}, new LoadCallback()
{
@Override
public void onLoaded(MutationObserver mo)
{
Element uiObject = getUiElement();
if (uiObject != null)
{
mutationObserver = mo;
mutationObserver.observe(uiObject, true, true, true);
}
}
@Override
public void onError(String message)
{
Crux.getErrorHandler().handleError(Crux.getMessages().errorLoadingMutationObserver());
}
});
}
else if (mutationObserver != null)
{
mutationObserver.disconnect();
mutationObserver = null;
}
}
});
}
protected void observeChangeEvents()
{
widget.asWidget().addAttachHandler(new AttachEvent.Handler()
{
private boolean listeningEvent = false;
@Override
public void onAttachOrDetach(AttachEvent event)
{
if (event.isAttached() && !listeningEvent)
{
Element uiObject = getUiElement();
if (uiObject != null)
{
observeChangeEvents(uiObject, PropertyBinder.this);
}
listeningEvent = true;
}
}
});
}
protected native void observeChangeEvents(Element el, PropertyBinder<T, W> binder)/*-{
el.addEventListener('change', function(e){
binder.@org.cruxframework.crux.core.client.screen.binding.PropertyBinder::notifyChanges()();
});
}-*/;
protected void notifyChanges()
{
dataObjectBinder.updateObjectAndNotifyChanges(this);
}
/**
* Implements DOM listening to enable Crux to update bound dataObject when
* DOM changes.
*/
protected void listenDOMChanges()
{
if (isBoundToAttribute())
{
observeDOMChanges();
}
else
{
observeChangeEvents();
}
}
protected void setBoundToAttribute(boolean boundToAttribute)
{
this.boundToAttribute = boundToAttribute;
}
/**
* Inform if this property binding references an HTML attribute or property.
* It is important for considerable performance improvements, as it allow us to avoid
* dirty checking to implement data binding.
* @return true if a binding reference an HTML attribute.
*/
protected boolean isBoundToAttribute()
{
return boundToAttribute;
}
/**
* Transfer data from dataObject to target widget
* @param dataObject
*/
public abstract void copyTo(T dataObject);
/**
* Transfer data from given widget to target dataObject
* @param dataObject
*/
public abstract void copyFrom(T dataObject);
/**
* Retrieve the target {@link Element} that is associated to this binding.
* Default to the bound widget element
* @return the Element
*/
public Element getUiElement()
{
return widget!=null?widget.asWidget().getElement():null;
}
}