package org.itsnat.droid.impl.browser; import android.content.Context; import android.view.View; import android.view.ViewGroup; import org.itsnat.droid.HttpRequestResult; import org.itsnat.droid.OnHttpRequestListener; import org.itsnat.droid.Page; import org.itsnat.droid.impl.dom.ParsedResourceXMLDOM; import org.itsnat.droid.impl.dom.layout.DOMElemView; import org.itsnat.droid.impl.dom.layout.DOMScript; import org.itsnat.droid.impl.dom.layout.DOMScriptRemote; import org.itsnat.droid.impl.dom.layout.XMLDOMLayout; import org.itsnat.droid.impl.dom.layout.XMLDOMLayoutPage; import org.itsnat.droid.impl.domparser.XMLDOMParserContext; import org.itsnat.droid.impl.domparser.XMLDOMRegistry; import org.itsnat.droid.impl.domparser.layout.XMLDOMLayoutParser; import org.itsnat.droid.impl.util.MapLight; import org.itsnat.droid.impl.util.MiscUtil; import org.itsnat.droid.impl.xmlinflated.layout.InflatedXMLLayoutPageImpl; import org.itsnat.droid.impl.xmlinflater.layout.page.XMLInflaterLayoutPage; import java.util.List; import java.util.Map; /** * Created by jmarranz on 29/10/14. */ public class FragmentLayoutInserter { public ItsNatDocImpl itsNatDoc; public FragmentLayoutInserter(ItsNatDocImpl itsNatDoc) { this.itsNatDoc = itsNatDoc; } public void insertPageFragment(ViewGroup parentView, String markup, View viewRef,XMLDOMParserContext xmlDOMParserContext) { // Llamado desde un page No ItsNat setInnerXMLInsertPageFragment(parentView, parentView.getClass().getName(),markup,viewRef,xmlDOMParserContext); } public void setInnerXML(ViewGroup parentView, String parentClassName, String markup, View viewRef,XMLDOMParserContext xmlDOMParserContext) { // parentClassName viene del servidor y puede ser nulo, es el className original puesto en el template y se envía cuando ha sido usado para el pre-parseo con metadatos de beanshell en el caso de // contener el template algún atributo remoto, evitamos así usar un class name absoluto en este caso que es que lo que devuelve parentView.getClass().getName() pues no lo encontraríamos en el caché // Si no hay algún atributo remoto no se envía y es nulo porque no se necesita (es llamado el método setInnerXML sin className), if (parentClassName == null) parentClassName = parentView.getClass().getName(); setInnerXMLInsertPageFragment(parentView, parentClassName,markup,viewRef, xmlDOMParserContext); } private void setInnerXMLInsertPageFragment(ViewGroup parentView, String parentClassName, String markup, View viewRef,XMLDOMParserContext xmlDOMParserContext) { // Este método es llamado por el setInnerXML generado por ItsNat Server pero también por los métodos de usuario appendFragment e insertFragment // Si el fragmento a insertar es suficientemente grande el rendimiento de setInnerXML puede ser varias veces superior // a hacerlo elemento a elemento, atributo a atributo con la API debido a la lentitud de Beanshell // Por ejemplo 78ms con setInnerXML (parseando markup) y 179ms con beanshell puro PageImpl page = itsNatDoc.getPageImpl(); InflatedXMLLayoutPageImpl inflatedLayoutPage = page.getInflatedXMLLayoutPageImpl(); XMLDOMLayoutPage xmlDOMLayoutPageParent = inflatedLayoutPage.getXMLDOMLayoutPage(); Context ctx = itsNatDoc.getContext(); XMLDOMLayoutPage xmlDOMLayout = wrapAndParseMarkupFragment(parentClassName, markup,xmlDOMLayoutPageParent,page.getItsNatServerVersion(),xmlDOMParserContext); DOMElemView rootDOMElemView = (DOMElemView)xmlDOMLayout.getRootDOMElement(); // Gracias al parentView añadido siempre esperamos un DOMView, nunca un DOMMerge List<DOMScript> domScriptList = xmlDOMLayout.getDOMScriptList(); XMLInflaterLayoutPage xmlLayoutInflaterPage = page.getXMLInflaterLayoutPage(); ViewGroup falseParentView = (ViewGroup) xmlLayoutInflaterPage.insertFragment(rootDOMElemView,xmlDOMLayout); // Los XML ids, los inlineHandlers etc habrán quedado memorizados int indexRef = viewRef != null ? parentView.indexOfChild(viewRef) : -1; while (falseParentView.getChildCount() > 0) { View child = falseParentView.getChildAt(0); falseParentView.removeViewAt(0); if (indexRef >= 0) { parentView.addView(child, indexRef); indexRef++; } else parentView.addView(child); } executeScriptList(domScriptList); } public static XMLDOMLayoutPage wrapAndParseMarkupFragment(String parentClassName, String markup,XMLDOMLayoutPage xmlDOMLayoutPageParent,String itsNatServerVersion,XMLDOMParserContext xmlDOMParserContext) { // Preparamos primero el markup añadiendo un false parentView que luego quitamos, el false parentView es necesario // para declarar el namespace android, el false parentView será del mismo tipo que el de verdad para que los // LayoutParams se hagan bien. StringBuilder newMarkup = new StringBuilder(); newMarkup.append("<" + parentClassName); newMarkup.append(" xmlns:android=\"http://schemas.android.com/apk/res/android\""); MapLight<String, String> namespaceMap = xmlDOMLayoutPageParent.getRootNamespacesByPrefix(); for (Map.Entry<String, String> entry : namespaceMap.getEntryList()) { newMarkup.append(" xmlns:" + entry.getKey() + "=\"" + entry.getValue() + "\""); } newMarkup.append(">"); newMarkup.append(markup); newMarkup.append("</" + parentClassName + ">"); markup = newMarkup.toString(); XMLDOMRegistry xmlDOMRegistry = xmlDOMParserContext.getXMLDOMRegistry(); ParsedResourceXMLDOM<XMLDOMLayout> resourceXMLDOM = xmlDOMRegistry.buildXMLDOMLayoutAndCachingByMarkupAndResDesc(markup,null, itsNatServerVersion, XMLDOMLayoutParser.LayoutType.PAGE_FRAGMENT, xmlDOMParserContext); XMLDOMLayoutPage xmlDOMLayout = (XMLDOMLayoutPage)resourceXMLDOM.getXMLDOM(); return xmlDOMLayout; } private void executeScriptList(List<DOMScript> domScriptList) { if (domScriptList == null) return; for (DOMScript script : domScriptList) { String code = script.getCode(); if (code != null) { itsNatDoc.eval(code); } else if (script instanceof DOMScriptRemote) { final DOMScriptRemote scriptRemote = (DOMScriptRemote)script; // Es el caso de llamada por el usuario directamente a appendFragment/insertFragment(...) no es el caso de llamada setInnerXML de BS generado pues se carga en multihilo OnHttpRequestListener listener = new OnHttpRequestListener() { @Override public void onRequest(Page page,HttpRequestResult response) { String code = response.getResponseText(); itsNatDoc.eval(code); scriptRemote.setCode(code); // Creo que no sirve para nada pero por si acaso } }; String src = scriptRemote.getSrc(); itsNatDoc.downloadScript(src,listener); // Se carga asíncronamente sin un orden claro } else { // Es un DOMScriptInline con code a null, es imposible throw MiscUtil.internalError(); } } } }