Narrative changes

This commit is contained in:
jamesagnew 2014-04-14 16:26:00 -04:00
parent bdd5297603
commit 2c52f78eb5
17 changed files with 280 additions and 49 deletions

View File

@ -409,6 +409,11 @@
</executions> </executions>
</plugin> </plugin>
</plugins> </plugins>
<resources>
<resource>
<filtering>true</filtering>
</resource>
</resources>
</build> </build>
</project> </project>

View File

@ -216,7 +216,8 @@ class ModelScanner {
private void scanBlock(Class<? extends IResourceBlock> theClass, Block theBlockDefinition) { private void scanBlock(Class<? extends IResourceBlock> theClass, Block theBlockDefinition) {
ourLog.debug("Scanning resource block class: {}", theClass.getName()); ourLog.debug("Scanning resource block class: {}", theClass.getName());
String resourceName = theBlockDefinition.name(); String resourceName = theBlockDefinition.name(); // TODO: remove name
resourceName = theClass.getCanonicalName();
if (isBlank(resourceName)) { if (isBlank(resourceName)) {
throw new ConfigurationException("Block type @" + Block.class.getSimpleName() + " annotation contains no name: " + theClass.getCanonicalName()); throw new ConfigurationException("Block type @" + Block.class.getSimpleName() + " annotation contains no name: " + theClass.getCanonicalName());
} }

View File

@ -9,6 +9,8 @@ import java.lang.annotation.Target;
@Target(value= {ElementType.TYPE}) @Target(value= {ElementType.TYPE})
public @interface Block { public @interface Block {
String name(); // TODO: remove
@Deprecated
String name() default "";
} }

View File

@ -6,6 +6,8 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import ca.uhn.fhir.model.api.IElement; import ca.uhn.fhir.model.api.IElement;
import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
import ca.uhn.fhir.model.dstu.resource.Patient;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(value= {ElementType.FIELD}) @Target(value= {ElementType.FIELD})
@ -32,4 +34,14 @@ public @interface Child {
Class<? extends IElement>[] type() default {}; Class<? extends IElement>[] type() default {};
// Not implemented
// /**
// * This value is used when extending a built-in model class and defining a
// * field to replace a field within the built-in class. For example, the {@link Patient}
// * resource has a {@link Patient#getName() name} field, but if you wanted to extend Patient and
// * provide your own implementation of {@link HumanNameDt} (most likely your own subclass of
// * HumanNameDt which adds extensions of your choosing) you could do that using a replacement field.
// */
// String replaces() default "";
} }

View File

@ -9,6 +9,7 @@ import java.io.InputStream;
import java.io.StringReader; import java.io.StringReader;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Properties; import java.util.Properties;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
@ -114,16 +115,15 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
myClassToNarrativeName = new HashMap<Class<?>, String>(); myClassToNarrativeName = new HashMap<Class<?>, String>();
myNameToNarrativeTemplate = new HashMap<String, String>(); myNameToNarrativeTemplate = new HashMap<String, String>();
String propFileName = getPropertyFile(); List<String> propFileName = getPropertyFile();
if (isBlank(propFileName)) {
throw new ConfigurationException("Property file name can not be null");
}
try { try {
if (myApplyDefaultDatatypeTemplates) { if (myApplyDefaultDatatypeTemplates) {
loadProperties(DefaultThymeleafNarrativeGenerator.NARRATIVES_PROPERTIES); loadProperties(DefaultThymeleafNarrativeGenerator.NARRATIVES_PROPERTIES);
} }
loadProperties(propFileName); for (String next : propFileName) {
loadProperties(next);
}
} catch (IOException e) { } catch (IOException e) {
throw new ConfigurationException("Can not load property file " + propFileName, e); throw new ConfigurationException("Can not load property file " + propFileName, e);
} }
@ -144,7 +144,7 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
myInitialized = true; myInitialized = true;
} }
protected abstract String getPropertyFile(); protected abstract List<String> getPropertyFile();
/** /**
* If set to <code>true</code> (which is the default), most whitespace will * If set to <code>true</code> (which is the default), most whitespace will
@ -209,6 +209,8 @@ public abstract class BaseThymeleafNarrativeGenerator implements INarrativeGener
} }
private void loadProperties(String propFileName) throws IOException { private void loadProperties(String propFileName) throws IOException {
ourLog.debug("Loading narrative properties file: {}", propFileName);
Properties file = new Properties(); Properties file = new Properties();
InputStream resource = loadResource(propFileName); InputStream resource = loadResource(propFileName);

View File

@ -1,6 +1,8 @@
package ca.uhn.fhir.narrative; package ca.uhn.fhir.narrative;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import java.util.List;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
@ -42,8 +44,8 @@ public class CustomThymeleafNarrativeGenerator extends BaseThymeleafNarrativeGen
} }
@Override @Override
public String getPropertyFile() { public List<String> getPropertyFile() {
return myPropertyFile; return Collections.singletonList(myPropertyFile);
} }
} }

View File

@ -1,13 +1,47 @@
package ca.uhn.fhir.narrative; package ca.uhn.fhir.narrative;
import java.util.ArrayList;
import java.util.List;
import ca.uhn.fhir.model.dstu.resource.Conformance;
import ca.uhn.fhir.rest.server.RestfulServer;
public class DefaultThymeleafNarrativeGenerator extends BaseThymeleafNarrativeGenerator implements INarrativeGenerator { public class DefaultThymeleafNarrativeGenerator extends BaseThymeleafNarrativeGenerator implements INarrativeGenerator {
static final String NARRATIVES_PROPERTIES = "classpath:ca/uhn/fhir/narrative/narratives.properties"; static final String NARRATIVES_PROPERTIES = "classpath:ca/uhn/fhir/narrative/narratives.properties";
static final String HAPISERVER_NARRATIVES_PROPERTIES = "classpath:ca/uhn/fhir/narrative/narratives-hapiserver.properties";
private boolean myUseHapiServerConformanceNarrative;
@Override @Override
protected String getPropertyFile() { protected List<String> getPropertyFile() {
return NARRATIVES_PROPERTIES; List<String> retVal=new ArrayList<String>();
retVal.add(NARRATIVES_PROPERTIES);
if (myUseHapiServerConformanceNarrative) {
retVal.add(HAPISERVER_NARRATIVES_PROPERTIES);
}
return retVal;
}
/**
* If set to <code>true</code> (default is <code>false</code>) a special custom narrative for the
* {@link Conformance} resource will be provided, which is designed to be used with
* HAPI {@link RestfulServer} instances. This narrative provides a friendly search
* page which can assist users of the service.
*/
public void setUseHapiServerConformanceNarrative(boolean theValue) {
myUseHapiServerConformanceNarrative=theValue;
}
/**
* If set to <code>true</code> (default is <code>false</code>) a special custom narrative for the
* {@link Conformance} resource will be provided, which is designed to be used with
* HAPI {@link RestfulServer} instances. This narrative provides a friendly search
* page which can assist users of the service.
*/
public boolean isUseHapiServerConformanceNarrative() {
return myUseHapiServerConformanceNarrative;
} }
} }

View File

@ -19,6 +19,7 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.narrative.INarrativeGenerator; import ca.uhn.fhir.narrative.INarrativeGenerator;
import ca.uhn.fhir.rest.method.BaseMethodBinding; import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.method.ConformanceMethodBinding; import ca.uhn.fhir.rest.method.ConformanceMethodBinding;
@ -49,6 +50,8 @@ public abstract class RestfulServer extends HttpServlet {
private boolean myUseBrowserFriendlyContentTypes; private boolean myUseBrowserFriendlyContentTypes;
private ISecurityManager securityManager; private ISecurityManager securityManager;
private BaseMethodBinding myServerConformanceMethod; private BaseMethodBinding myServerConformanceMethod;
private String myServerName = "HAPI FHIR Server";
private String myServerVersion = VersionUtil.getVersion();
public RestfulServer() { public RestfulServer() {
myFhirContext = new FhirContext(); myFhirContext = new FhirContext();
@ -56,7 +59,7 @@ public abstract class RestfulServer extends HttpServlet {
} }
public void addHapiHeader(HttpServletResponse theHttpResponse) { public void addHapiHeader(HttpServletResponse theHttpResponse) {
theHttpResponse.addHeader("X-CatchingFhir", "HAPI FHIR " + VersionUtil.getVersion()); theHttpResponse.addHeader("X-CatchingFhir", "Powered by HAPI FHIR " + VersionUtil.getVersion());
} }
@Override @Override
@ -431,4 +434,40 @@ public abstract class RestfulServer extends HttpServlet {
} }
} }
/**
* Gets the server's name, as exported in conformance profiles exported by the server. This
* is informational only, but can be helpful to set with something appropriate.
*
* @see RestfulServer#setServerName(StringDt)
*/
public String getServerName() {
return myServerName;
}
/**
* Gets the server's name, as exported in conformance profiles exported by the server. This
* is informational only, but can be helpful to set with something appropriate.
*
* @see RestfulServer#setServerName(StringDt)
*/
public void setServerName(String theServerName) {
myServerName = theServerName;
}
/**
* Gets the server's version, as exported in conformance profiles exported by the server. This
* is informational only, but can be helpful to set with something appropriate.
*/
public void setServerVersion(String theServerVersion) {
myServerVersion = theServerVersion;
}
/**
* Gets the server's version, as exported in conformance profiles exported by the server. This
* is informational only, but can be helpful to set with something appropriate.
*/
public String getServerVersion() {
return myServerVersion;
}
} }

View File

@ -27,7 +27,6 @@ import ca.uhn.fhir.rest.param.IQueryParameter;
import ca.uhn.fhir.rest.server.ResourceBinding; import ca.uhn.fhir.rest.server.ResourceBinding;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.util.ExtensionConstants; import ca.uhn.fhir.util.ExtensionConstants;
import ca.uhn.fhir.util.VersionUtil;
public class ServerConformanceProvider { public class ServerConformanceProvider {
@ -45,8 +44,8 @@ public class ServerConformanceProvider {
} }
Conformance retVal = new Conformance(); Conformance retVal = new Conformance();
retVal.getSoftware().setName("HAPI FHIR Server"); retVal.getSoftware().setName(myRestfulServer.getServerName());
retVal.getSoftware().setVersion(VersionUtil.getVersion()); retVal.getSoftware().setVersion(myRestfulServer.getServerVersion());
Rest rest = retVal.addRest(); Rest rest = retVal.addRest();
rest.setMode(RestfulConformanceModeEnum.SERVER); rest.setMode(RestfulConformanceModeEnum.SERVER);

View File

@ -0,0 +1,25 @@
<div>
<div class="hapiHeaderText" th:text="${resource.software.name} + ' ' + ${resource.software.version}" />
<div>
This is a RESTful server implementing the FHIR RESTful protocol, powered by HAPI.
You may invoke this server using the RESTful patterns described in the
FHIR specification. Documentation is available below.
</div>
<div th:each="resource : ${resource.restFirstRep.resource}">
<h1 th:text="${resource.type.value}"/>
</div>
<!--/*-->
<th:block th:each="prefix : ${resource.prefix}" th:text="${prefix.value} + ' '">Dr</th:block>
<th:block th:each="givenName : ${resource.given}" th:text="${givenName.value} + ' '">John</th:block>
<b th:each="familyName : ${resource.family}" th:text="${#strings.toUpperCase(familyName.value)} + ' '">SMITH</b>
<th:block th:each="suffix : ${resource.suffix}" th:text="${suffix.value} + ' '">Jr</th:block>
<!--*/-->
</div>

View File

@ -0,0 +1,8 @@
################################################
# Resources
################################################
conformance.class=ca.uhn.fhir.model.dstu.resource.Conformance
conformance.narrative=classpath:ca/uhn/fhir/narrative/ConformanceHapiServer.html

View File

@ -222,24 +222,7 @@ public class ResourceWithExtensionsA extends BaseResource {
return ca.uhn.fhir.util.ElementUtil.allPopulatedChildElements(theType ); // TODO: implement return ca.uhn.fhir.util.ElementUtil.allPopulatedChildElements(theType ); // TODO: implement
} }
@Override
public void setId(IdDt theId) {
myId=theId;
}
@Override
public IdDt getId() {
return myId;
}
@Override
public ContainedDt getContained() {
throw new IllegalStateException();
}
@Override
public NarrativeDt getText() {
throw new IllegalStateException();
}
} }

View File

@ -2,12 +2,15 @@ package ca.uhn.fhir.context;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.util.List;
import org.junit.Test; import org.junit.Test;
import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.resource.Profile; import ca.uhn.fhir.model.dstu.resource.Profile;
import ca.uhn.fhir.model.dstu.resource.Profile.ExtensionDefn; import ca.uhn.fhir.model.dstu.resource.Profile.ExtensionDefn;
import ca.uhn.fhir.model.dstu.resource.Profile.Structure; import ca.uhn.fhir.model.dstu.resource.Profile.Structure;
import ca.uhn.fhir.model.dstu.resource.Profile.StructureElement;
import ca.uhn.fhir.model.dstu.resource.ValueSet; import ca.uhn.fhir.model.dstu.resource.ValueSet;
import ca.uhn.fhir.model.dstu.valueset.DataTypeEnum; import ca.uhn.fhir.model.dstu.valueset.DataTypeEnum;
@ -57,8 +60,9 @@ public class RuntimeResourceDefinitionTest {
ourLog.info(ctx.newXmlParser().encodeResourceToString(profile)); ourLog.info(ctx.newXmlParser().encodeResourceToString(profile));
assertEquals(1, profile.getStructure().get(0).getElement().get(0).getDefinition().getType().size()); List<StructureElement> element = profile.getStructure().get(0).getElement();
assertEquals("Resource", profile.getStructure().get(0).getElement().get(0).getDefinition().getType().get(0).getCode().getValue()); assertEquals(1, element.get(0).getDefinition().getType().size());
assertEquals("Resource", element.get(0).getDefinition().getType().get(0).getCode().getValue());
ExtensionDefn ext = profile.getExtensionDefn().get(1); ExtensionDefn ext = profile.getExtensionDefn().get(1);
assertEquals("b1/1", ext.getCode().getValue()); assertEquals("b1/1", ext.getCode().getValue());
@ -71,19 +75,19 @@ public class RuntimeResourceDefinitionTest {
assertEquals(DataTypeEnum.EXTENSION, ext.getDefinition().getType().get(1).getCode().getValueAsEnum()); assertEquals(DataTypeEnum.EXTENSION, ext.getDefinition().getType().get(1).getCode().getValueAsEnum());
assertEquals("#b1/2/2", ext.getDefinition().getType().get(1).getProfile().getValueAsString()); assertEquals("#b1/2/2", ext.getDefinition().getType().get(1).getProfile().getValueAsString());
assertEquals("ResourceWithExtensionsA.extension", profile.getStructure().get(0).getElement().get(1).getPath().getValue()); assertEquals("ResourceWithExtensionsA.extension", element.get(1).getPath().getValue());
assertEquals("ResourceWithExtensionsA.extension", profile.getStructure().get(0).getElement().get(2).getPath().getValue()); assertEquals("ResourceWithExtensionsA.extension", element.get(2).getPath().getValue());
assertEquals("ResourceWithExtensionsA.extension", profile.getStructure().get(0).getElement().get(3).getPath().getValue()); assertEquals("ResourceWithExtensionsA.extension", element.get(3).getPath().getValue());
assertEquals("ResourceWithExtensionsA.extension", profile.getStructure().get(0).getElement().get(4).getPath().getValue()); assertEquals("ResourceWithExtensionsA.extension", element.get(4).getPath().getValue());
assertEquals("ResourceWithExtensionsA.modifierExtension", profile.getStructure().get(0).getElement().get(5).getPath().getValue()); assertEquals("ResourceWithExtensionsA.modifierExtension", element.get(5).getPath().getValue());
assertEquals(DataTypeEnum.EXTENSION, profile.getStructure().get(0).getElement().get(1).getDefinition().getType().get(0).getCode().getValueAsEnum()); assertEquals(DataTypeEnum.EXTENSION, element.get(1).getDefinition().getType().get(0).getCode().getValueAsEnum());
assertEquals("url", profile.getStructure().get(0).getElement().get(1).getSlicing().getDiscriminator().getValue()); assertEquals("url", element.get(1).getSlicing().getDiscriminator().getValue());
assertEquals(DataTypeEnum.EXTENSION, profile.getStructure().get(0).getElement().get(2).getDefinition().getType().get(0).getCode().getValueAsEnum()); assertEquals(DataTypeEnum.EXTENSION, element.get(2).getDefinition().getType().get(0).getCode().getValueAsEnum());
assertEquals("#f1", profile.getStructure().get(0).getElement().get(2).getDefinition().getType().get(0).getProfile().getValueAsString()); assertEquals("#f1", element.get(2).getDefinition().getType().get(0).getProfile().getValueAsString());
assertEquals("ResourceWithExtensionsA.identifier", profile.getStructure().get(0).getElement().get(9).getPath().getValue()); assertEquals("ResourceWithExtensionsA.identifier", element.get(12).getPath().getValue());
} }

View File

@ -3,6 +3,7 @@ package ca.uhn.fhir.narrative;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Date; import java.util.Date;
import org.hamcrest.core.StringContains; import org.hamcrest.core.StringContains;
@ -14,6 +15,7 @@ import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.dstu.composite.NarrativeDt; import ca.uhn.fhir.model.dstu.composite.NarrativeDt;
import ca.uhn.fhir.model.dstu.composite.QuantityDt; import ca.uhn.fhir.model.dstu.composite.QuantityDt;
import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt; import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.dstu.resource.Conformance;
import ca.uhn.fhir.model.dstu.resource.DiagnosticReport; import ca.uhn.fhir.model.dstu.resource.DiagnosticReport;
import ca.uhn.fhir.model.dstu.resource.Observation; import ca.uhn.fhir.model.dstu.resource.Observation;
import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.model.dstu.resource.Patient;
@ -29,6 +31,7 @@ public class DefaultThymeleafNarrativeGeneratorTest {
@Before @Before
public void before() { public void before() {
gen = new DefaultThymeleafNarrativeGenerator(); gen = new DefaultThymeleafNarrativeGenerator();
gen.setUseHapiServerConformanceNarrative(true);
gen.setIgnoreFailures(false); gen.setIgnoreFailures(false);
gen.setIgnoreMissingTemplates(false); gen.setIgnoreMissingTemplates(false);
} }
@ -49,6 +52,16 @@ public class DefaultThymeleafNarrativeGeneratorTest {
ourLog.info(output); ourLog.info(output);
} }
@Test
public void testGenerateServerConformance() throws DataFormatException {
Conformance value = new FhirContext().newXmlParser().parseResource(Conformance.class, new InputStreamReader(getClass().getResourceAsStream("/server-conformance-statement.xml")));
String output = gen.generateNarrative(value).getDiv().getValueAsString();
ourLog.info(output);
}
@Test @Test
public void testGenerateDiagnosticReport() throws DataFormatException { public void testGenerateDiagnosticReport() throws DataFormatException {
DiagnosticReport value = new DiagnosticReport(); DiagnosticReport value = new DiagnosticReport();

View File

@ -106,6 +106,7 @@ public class ResfulServerMethodTest {
builder.setConnectionManager(connectionManager); builder.setConnectionManager(connectionManager);
ourClient = builder.build(); ourClient = builder.build();
} }
@AfterClass @AfterClass

View File

@ -0,0 +1,101 @@
<Conformance xmlns="http://hl7.org/fhir">
<software>
<name value="HAPI FHIR Server"/>
<version value="${project.version}"/>
</software>
<rest>
<mode value="server"/>
<resource>
<type value="Profile"/>
<operation>
<code value="read"/>
</operation>
<operation>
<code value="search-type"/>
</operation>
</resource>
<resource>
<type value="Patient"/>
<operation>
<code value="search-type"/>
</operation>
<operation>
<code value="history-type"/>
</operation>
<operation>
<code value="create"/>
</operation>
<operation>
<code value="update"/>
</operation>
<operation>
<code value="history-instance"/>
</operation>
<operation>
<code value="delete"/>
</operation>
<operation>
<code value="read"/>
</operation>
<operation>
<code value="vread"/>
</operation>
<searchParam>
<name value="identifier"/>
<type value="token"/>
</searchParam>
<searchParam>
<name value="dob"/>
<type value="date"/>
</searchParam>
<searchParam>
<name value="aaa"/>
<type value="string"/>
<chain value="bbb">
<extension url="http://hl7api.sourceforge.net/hapi-fhir/extensions.xml#chainRequired">
<valueBoolean value="false"/>
</extension>
<extension url="http://hl7api.sourceforge.net/hapi-fhir/extensions.xml#alsoChain">
<extension url="http://hl7api.sourceforge.net/hapi-fhir/extensions.xml#chainRequired">
<valueBoolean value="false"/>
</extension>
</extension>
</chain>
</searchParam>
<searchParam>
<name value="name1"/>
<type value="string"/>
<chain value="name2">
<extension url="http://hl7api.sourceforge.net/hapi-fhir/extensions.xml#chainRequired">
<valueBoolean value="false"/>
</extension>
</chain>
</searchParam>
<searchParam>
<name value="param1"/>
<type value="string"/>
</searchParam>
<searchParam>
<name value="withIncludes"/>
<type value="string"/>
</searchParam>
<searchParam>
<name value="ids"/>
<type value="token"/>
</searchParam>
<searchParam>
<name value="dateRange"/>
<type value="date"/>
</searchParam>
</resource>
<resource>
<type value="DiagnosticReport"/>
<operation>
<code value="update"/>
</operation>
<operation>
<code value="delete"/>
</operation>
</resource>
</rest>
</Conformance>

View File

@ -240,7 +240,7 @@
#macro ( childExtensionTypes $childExtensionTypes ) #macro ( childExtensionTypes $childExtensionTypes )
#foreach ( $extensionType in $childExtensionTypes ) #foreach ( $extensionType in $childExtensionTypes )
#if ( $extensionType.hasChildExtensions ) #if ( $extensionType.hasChildExtensions )
@Block(name="${extensionType.name}") @Block()
public static class ${extensionType.nameType} implements IExtension { public static class ${extensionType.nameType} implements IExtension {
#foreach ( $childExtensionSubtype in $extensionType.childExtensions ) #foreach ( $childExtensionSubtype in $extensionType.childExtensions )
@ -276,7 +276,7 @@
* ${blockChild.definition} * ${blockChild.definition}
* </p> * </p>
*/ */
@Block(name="${blockChild.name}") @Block()
public static class ${blockChild.className} extends BaseElement implements IResourceBlock { public static class ${blockChild.className} extends BaseElement implements IResourceBlock {
#childVars( $blockChild.children ) #childVars( $blockChild.children )