UNPKG

jointjs

Version:

JavaScript diagramming library

778 lines (703 loc) 32.7 kB
<!DOCTYPE html> <html> <head> <link rel="canonical" href="http://www.jointjs.com/" /> <meta name="description" content="Create interactive diagrams in JavaScript easily. JointJS plugins for ERD, Org chart, FSA, UML, PN, DEVS, LDM diagrams are ready to use." /> <meta name="keywords" content="JointJS, JavaScript, diagrams, diagramming library, UML, charts" /> <link href="http://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700" rel="stylesheet" type="text/css" /> <link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet"> <link rel="stylesheet" href="css/tutorial.css" /> <link rel="stylesheet" href="../node_modules/prismjs/themes/prism.css"> <!-- Dependencies: --> <script src="../node_modules/jquery/dist/jquery.js"></script> <script src="../node_modules/lodash/lodash.js"></script> <script src="../node_modules/backbone/backbone.js"></script> <link rel="stylesheet" type="text/css" href="../build/joint.min.css" /> <script type="text/javascript" src="../build/joint.min.js"></script> <title>JointJS - JavaScript diagramming library - Getting started.</title> </head> <body class="language-javascript tutorial-page"> <script> SVGElement.prototype.getTransformToElement = SVGElement.prototype.getTransformToElement || function(toElement) { return toElement.getScreenCTM().inverse().multiply(this.getScreenCTM()); }; </script> <div class="tutorial"> <h2>Special Attributes</h2> <p>This is the first article of the intermediate section of the JointJS tutorial. <a href="intermediate.html">Return to index.</a></p> <p>Special attributes are JointJS-specific attributes that offer functionality beyond that of native SVG attributes. We have already mentioned them when talking about <a href="elements.html#element-styling">element styling</a>, and seen them in action when adding <a href="links.html#link-arrowheads">link arrowheads</a>. They become much more important when we talk about creating custom <a href="custom-elements.html">elements</a>, <a href="custom-links.html">links</a> and <a href="link-labels.html#label-styling">link labels</a>.</p> <p>The main way to define the styling of diagram elements, links, and labels in JointJS is through <code>attrs</code> objects. If the attributes passed are standard SVG attributes, they are merely passed down to the individual SVGElements of the shape; then it is the job of the browser to apply them to the elements and render the shapes in the requested manner when asked to do so by JointJS View classes. However, if JointJS encounters one of its special attributes, it takes over with custom logic in order to offer advanced functionality; the results are then encoded back as standard SVG attributes.</p> <p>All JointJS special attributes use camelCase naming. For consistency, it is thus very strongly recommended that you make use JointJS's ability to translate camelCase into native SVG's kebab-case, and use camelCase when setting native SVG attributes as well (i.e. <code>strokeWidth</code> instead of native <code>'stroke-width'</code>).</p> <p>A list of native SVG attributes can be found elsewhere on the Internet; have a look, for example, at <a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute">MDN's SVG Attribute reference</a>. In this section of the tutorial, we want to show you what additional things JointJS allows you to do.</p> <h3 id="relative-dimensions">Relative Dimensions</h3> <p>One of the most common requests when working with SVG is to set the dimensions of SVGElements relatively. JointJS allows you to do that with a suite of <code>ref</code> attributes. These attributes allow you to size shape subelements as a percentage of the dimensions of the shape's model. Moreover, since all calculations are programmatic and do not rely on browser's bbox measurements, using these attributes does not impact performance of your app. (<a href="#text-relative-dimensions">There is also a view-based method if you need to work with text; it is explained in the following section.</a>)</p> <ul> <li><a href="/docs/jointjs#dia.attributes.refWidth" target="_blank"><code>refWidth</code></a> and <a href="/docs/jointjs#dia.attributes.refHeight" target="_blank"><code>refHeight</code></a> - sets the width of the subelement relative to model bbox.</li> <li><a href="/docs/jointjs#dia.attributes.refX" target="_blank"><code>refX</code></a> and <a href="/docs/jointjs#dia.attributes.refY" target="_blank"><code>refY</code></a> - sets the coordinates of the top-left corner of the subelement relative to the top-left corner of model bbox. Percentages are interpreted relative to model bbox.</li> Stacks with the native <code>x</code>/<code>y</code> attribute. <li><a href="/docs/jointjs#dia.attributes.refCx" target="_blank"><code>refCx</code></a> and <a href="/docs/jointjs#dia.attributes.refCy" target="_blank"><code>refCy</code></a> - sets the coordinates of the circle/ellipse center. Percentages are interpreted relative to model bbox.</li> Can be used alongside <code>refX</code>/<code>refY</code>. <li><a href="/docs/jointjs#dia.attributes.refRx" target="_blank"><code>refRx</code></a> and <a href="/docs/jointjs#dia.attributes.refRy" target="_blank"><code>refRy</code></a> - sets the radius of the ellipse relative to model bbox dimensions. Percentages are interpreted relative to model bbox. Note that for backwards compatibility, setting <code>'100%'</code> here means that the <i>radius</i> will be 100% of model size while the diameter of the ellipse in that direction will be 200% of model size. Thus, if you want the ellipse to fit into the model, use <code>'50%'</code>.</li> <li><a href="/docs/jointjs#dia.attributes.refR" target="_blank"><code>refR</code></a> - sets the radius of the circle relative to the length of the shorter side of model bbox. Percentages are interpreted relative to model bbox. Note that for backwards compatibility, setting <code>'100%'</code> here means that the <i>radius</i> will be 100% of the length of model side. If you want the circle to fit inside the model, use <code>'50%'</code>. There is also <a href="/docs/jointjs#dia.attributes.refRCircumscribed" target="_blank"><code>refRCircumscribed</code></a>, which sets the radius of the circle relative to the longest diagonal of model bbox.</li> </ul> <p>Let us see the relative attributes in action. We define a <a href="custom-elements.html">custom element type</a> named <code>CustomElement</code> as a subtype of <code>joint.dia.Element</code>. We want it to have three SVGElements - a red-tinted <code>ellipse</code> named <code>e</code>, a green-tinted <code>rect</code> named <code>r</code>, and a blue-tinted <code>circle</code> named <code>c</code>, respectively. The <code>outline</code> SVGRectElement shows us the reference bbox of the element model. In the example, we use JointJS transitions to vary the dimensions of <code>element</code> from (40,40) to (270,100). (We also adjust position to make sure the element stays in the visible area of our paper.) Notice that the subelements of the shape adjust automatically as the size of the reference model changes:</p> <div class="paper" id="paper-special-attributes-relative-dimensions"></div> <pre><code>var CustomElement = joint.dia.Element.define('examples.CustomElement', { attrs: { e: { strokeWidth: 1, stroke: '#000000', fill: 'rgba(255,0,0,0.3)' }, r: { strokeWidth: 1, stroke: '#000000', fill: 'rgba(0,255,0,0.3)' }, c: { strokeWidth: 1, stroke: '#000000', fill: 'rgba(0,0,255,0.3)' }, outline: { refX: 0, refY: 0, refWidth: '100%', refHeight: '100%', strokeWidth: 1, stroke: '#000000', strokeDasharray: '5 5', strokeDashoffset: 2.5, fill: 'none' } } }, { markup: [{ tagName: 'ellipse', selector: 'e' }, { tagName: 'rect', selector: 'r' }, { tagName: 'circle', selector: 'c' }, { tagName: 'rect', selector: 'outline' }] }); var element = new CustomElement(); element.attr({ e: { refRx: '50%', refRy: '25%', refCx: '50%', refCy: 0, refX: '-50%', refY: '25%' }, r: { refX: '100%', x: -10, // additional x offset refY: '100%', y: -10, // additional y offset refWidth: '50%', refHeight: '50%', }, c: { refRCircumscribed: '50%', refCx: '50%', refCy: '50%' } });</code></pre> <p>JointJS source code: <a href="js/special-attributes-relative-dimensions.js" target="_blank">special-attributes-relative-dimensions.js</a></p> <h3 id="text-relative-dimensions">Relative Dimensions Based on Text</h3> <p>An advanced application of relative attributes is using them to set the dimensions of shape subelements based on the dimensions of bboxes of rendered JointJS views. This is especially valuable when you need to base the position and size of shape components on <code>&lt;text&gt;</code> subelements since JointJS is not able to work with them programmatically. Note that because this method relies on browser measurements, it is noticeably slower and less precise than the <a href="#relative-dimensions">model-based method</a> mentioned above; you should use that method for subelements that can be modeled by JointJS.</p> <p>The key is the <code>ref</code> special attribute:</p> <ul> <li><a href="/docs/jointjs#dia.attributes.ref" target="_blank"><code>ref</code></a> - a selector reference to the subelement whose measured bbox should be used as the base of relative attributes.</li> </ul> <p>We define a <a href="custom-elements.html">custom element type</a> named <code>CustomTextElement</code> as a subtype of <code>joint.dia.Element</code>. It is very similar to <code>CustomElement</code> defined above, except this time, all subelements refer to a new <code>text</code> component named <code>label</code>. In the example, we use JointJS transitions to vary the text content of <code>label</code>. Notice that the subelements of the shape adjust automatically as the size of label changes due to the added characters:</p> <div class="paper" id="paper-special-attributes-text-relative-dimensions"></div> <pre><code>var CustomTextElement = joint.dia.Element.define('examples.CustomTextElement', { attrs: { label: { textAnchor: 'middle', textVerticalAnchor: 'middle', fontSize: 48 }, e: { strokeWidth: 1, stroke: '#000000', fill: 'rgba(255,0,0,0.3)' }, r: { strokeWidth: 1, stroke: '#000000', fill: 'rgba(0,255,0,0.3)' }, c: { strokeWidth: 1, stroke: '#000000', fill: 'rgba(0,0,255,0.3)' }, outline: { ref: 'label', refX: 0, refY: 0, refWidth: '100%', refHeight: '100%', strokeWidth: 1, stroke: '#000000', strokeDasharray: '5 5', strokeDashoffset: 2.5, fill: 'none' } } }, { markup: [{ tagName: 'ellipse', selector: 'e' }, { tagName: 'rect', selector: 'r' }, { tagName: 'circle', selector: 'c' }, { tagName: 'text', selector: 'label' }, { tagName: 'rect', selector: 'outline' }] }); var element = new CustomTextElement(); element.attr({ label: { text: 'Hello, World!' }, e: { ref: 'label', refRx: '50%', refRy: '25%', refCx: '50%', refCy: 0, refX: '-50%', refY: '25%' }, r: { ref: 'label', refX: '100%', x: -10, // additional x offset refY: '100%', y: -10, // additional y offset refWidth: '50%', refHeight: '50%', }, c: { ref: 'label', refRCircumscribed: '50%', // c is already centered at label anchor } });</code></pre> <p>JointJS source code: <a href="js/special-attributes-text-relative-dimensions.js" target="_blank">special-attributes-text-relative-dimensions.js</a></p> <h3 id="link-arrowheads">Link Arrowheads</h3> <p>Two special attributes are in charge of link arrowheads. <a href="links.html#link-arrowheads">We already mentioned them in the basic tutorial:</a></p> <ul> <li><a href="/docs/jointjs#dia.attributes.sourceMarker" target="_blank"><code>sourceMarker</code></a> and <a href="/docs/jointjs#dia.attributes.targetMarker" target="_blank"><code>targetMarker</code></a> - sets an arrowhead of requested SVGElement <code>'type'</code> to the start/end of the link. The other properties passed in the object are expected to be SVG attributes for that SVGElement.</li> </ul> <p>Link arrowhead special attributes expect an object with <i>native SVG attributes</i>. This means that they are not able to understand JointJS special attributes, and they cannot make use of JointJS's camelCase translation. In order to better communicate these restrictions to programmers, we strongly encourage the use of quotation marks around all arrowhead marker properties (i.e. <code>'type'</code> and not <code>type</code>; <code>'stroke-width'</code> and not <code>strokeWidth</code>).</p> <p>The <code>'type'</code> of the arrowhead can be any valid SVGElement type. The following example shows how to create a link with two simple <code>&lt;path&gt;</code> arrowheads.</p> <p>The two arrowheads have the same path data commands, despite pointing in opposite directions; this is because all <code>targetMarker</code> values are automatically rotated by 180 degrees. The path commands' coordinate system has origin at the tip of the link and is rotated according to the slope of the link at that point. This means that you can design all your arrowheads as if they were pointing left and towards the point <code>0,0</code> in local coordinates, and then rely on JointJS's automatic arrowhead rotation.</p> <p>Note that, in general, if the <code>'fill'</code> and <code>'stroke'</code> colors are not specified, they are adopted from the <code>line.stroke</code> attribute.</p> <div class="paper" id="paper-links-arrowheads-path"></div> <pre><code>link.attr({ line: { sourceMarker: { // hour hand 'type': 'path', 'd': 'M 20 -10 0 0 20 10 Z' }, targetMarker: { // minute hand 'type': 'path', 'stroke': 'green', 'stroke-width': 2 'fill': 'yellow', 'd': 'M 20 -10 0 0 20 10 Z' } } });</code></pre> <p>JointJS source code: <a href="js/links-arrowheads-path.js" target="_blank">links-arrowheads-path.js</a></p> <p>To create an <code>&lt;image&gt;</code> arrowhead, you need to provide an URL with the path to your image to the <code>'xlink:href'</code> property, and then specify the desired <code>'width'</code> and <code>'height'</code>. Keep in mind that both of your markers should reference images pointing <q>left</q>; the image for <code>targetMarker</code> will be automatically rotated by 180 degrees by JointJS, and then both markers will match the gradient of the link path. Remember to reposition the image in the <code>'y'</code> axis (by <code>-1/2</code> the value of <code>'height'</code>) if you need the arrowhead to be centered.</p> <div class="paper" id="paper-links-arrowheads-image"></div> <pre><code>link.attr({ line: { sourceMarker: { 'type': 'image', 'xlink:href': 'http://cdn3.iconfinder.com/data/icons/49handdrawing/24x24/left.png', 'width': 24, 'height': 24, 'y': -12 }, targetMarker: { 'type': 'image', 'xlink:href': 'http://cdn3.iconfinder.com/data/icons/49handdrawing/24x24/left.png', 'width': 24, 'height': 24, 'y': -12 } } });</code></pre> <p>JointJS source code: <a href="js/links-arrowheads-image.js" target="_blank">links-arrowheads-image.js</a></p> <p>To create an arrowhead of arbitrary SVGElement type, simply specify the <code>'type'</code> appropriately, and then provide native SVG attributes to style it. Keep in mind that <code>'circle'</code> and <code>'ellipse'</code> SVG elements have origin in their center; they need to be repositioned with the <code>'cx'</code> attribute (by the value of <code>'r'</code>) if you do not want them to overflow the end of the link.</p> <div class="paper" id="paper-special-attributes-link-arrowheads"></div> <pre><code>link.attr({ line: { sourceMarker: { 'type': 'rect', 'width': 50, 'height': 10, 'y': -5, 'fill': 'rgba(255,0,0,0.3)', 'stroke': 'black' }, targetMarker: { 'type': 'circle', 'r': 10, 'cx': 10, 'fill': 'rgba(0,255,0,0.3)', 'stroke': 'black' } } });</code></pre> <p>JointJS source code: <a href="js/special-attributes-link-arrowheads.js" target="_blank">special-attributes-link-arrowheads.js</a></p> <h3 id="link-relative-position">Relative Position on Links</h3> <p>Several special attributes on links allow you to position subelements relatively to the link's connection path:</p> <ul> <li><a href="/docs/jointjs#dia.attributes.connection" target="_blank"><code>connection</code></a> - if set to <code>true</code>, the subelement will follow the link connection path. Applicable only to <code>&lt;path&gt;</code> subelements.</li> <li><a href="/docs/jointjs#dia.attributes.atConnectionLengthKeepGradient" target="_blank"><code>atConnectionLength</code></a> - sets the absolute distance from the beginning of the path at which the anchor of the subelement should be placed. Negative distances are counted from the end of the connection path. Rotates subelement according to link gradient at requested length.</li> <li><a href="/docs/jointjs#dia.attributes.atConnectionRatioKeepGradient" target="_blank"><code>atConnectionRatio</code></a> - sets the relative distance from the beginning of the path at which the anchor of the subelement should be placed. Accepts numbers between <code>0</code> and <code>1</code>. Rotates subelement according to link gradient at requested ratio.</li> </ul> <p>These attributes are ideal for adding symbols and arrowheads on the connection path and have them rotate according to path slope. Let us illustrate with a <a href="custom-links.html">custom link type</a>:</p> <div class="paper" id="paper-special-attributes-link-relative-position"></div> <pre><code>var CustomLink = joint.dia.Link.define('examples.CustomLink', { attrs: { line: { connection: true, fill: 'none', stroke: 'orange', strokeWidth: 2, sourceMarker: { 'type': 'circle', 'r': 4, 'fill': 'white', 'stroke': 'orange', 'stroke-width': '2' }, targetMarker: { 'type': 'circle', 'r': 4, 'fill': 'white', 'stroke': 'orange', 'stroke-width': '2' } }, arrowhead: { d: 'M -20 -10 0 0 -20 10 Z', fill: 'orange', stroke: 'none' }, symbol: { d: 'M -20 -20 20 20', stroke: 'black', targetMarker: { 'type': 'path', 'd': 'M 0 0 10 -5 10 5 Z', 'fill': 'black', 'stroke': 'none' } } } }, { markup: [{ tagName: 'path', selector: 'line' }, { tagName: 'path', selector: 'arrowhead' }, { tagName: 'path', selector: 'symbol' }] }); var link = new CustomLink(); link.attr({ symbol: { atConnectionRatio: 0.25 }, arrowhead: { atConnectionRatio: 0.75, } });</code></pre> <p>JointJS source code: <a href="js/special-attributes-link-relative-position.js" target="_blank">special-attributes-link-relative-position.js</a></p> <h3 id="link-subelement-labels">Link Subelement Labels</h3> <p>Special attributes also allow us to create custom labeling subelements - both those that adjust their rotation according to the underlying link path and those that always keep the same angle. However, keep in mind that link subelements are more difficult to set up and use dynamically than <a href="link-labels.html">link labels</a> (JointJS does not come with a built-in API to do this), and that they have a very important limitation when used this way:</p> <ul> <li>They cannot use the <code>ref</code> attribute. This means that they are unable to automatically set their width and height values based on the browser-calculated dimensions of the text bbox. It is up to the programmer to provide approximate width and height values to accommodate the label text.</li> </ul> <p>For this reason, we recommend everyone to use the <a href="link-labels.html">link label API</a>.</p> <p>Link subelement labels that do not keep link connection gradient are enabled with the following two special attributes:</p> <ul> <li><a href="/docs/jointjs#dia.attributes.atConnectionLengthIgnoreGradient" target="_blank"><code>atConnectionLengthIgnoreGradient</code></a> - sets the absolute distance from the beginning of the path at which the anchor of the subelement should be placed. Negative distances are counted from the end of the connection path. Does not rotate subelement according to path gradient.</li> <li><a href="/docs/jointjs#dia.attributes.atConnectionRatioIgnoreGradient" target="_blank"><code>atConnectionRatioIgnoreGradient</code></a> - sets the relative distance from the beginning of the path at which the anchor of the subelement should be placed. Accepts numbers between <code>0</code> and <code>1</code>. Does not rotate subelement according to path gradient.</li> </ul> <p>(The <a href="#link-relative-position">previously introduced</a> <code>atConnectionLength</code>/<code>atConnectionRatio</code> attributes are actually aliases for more verbose names <code>atConnectionLengthKeepGradient</code>/<code>atConnectionRatioKeepGradient</code>.) In both cases (that is, whether we keep link gradient or not), offsetting of link subelement labels is achieved by clever use of the native <code>x</code> and <code>y</code> SVG attributes.</p> <p>Let us illustrate with the following demo. A <a href="custom-links.html">custom link type</a> is defined with link label subelements positioned in such a way as to emulate the functionality shown in the link label <a href="link-labels.html#label-position">position</a> and <a href="link-labels.html#label-offset">offset</a> examples. The red asterisk marks the reference point of the offset subelements on the link connection path.</p> <div class="paper" id="paper-special-attributes-link-subelement-labels"></div> <pre><code>var CustomLink = joint.dia.Link.define('examples.CustomLink', { attrs: { line: { connection: true, fill: 'none', stroke: '#333333', strokeWidth: 2, strokeLinejoin: 'round', targetMarker: { 'type': 'path', 'd': 'M 10 -5 0 0 10 5 z' } }, relativeLabel: { textAnchor: 'middle', textVerticalAnchor: 'middle', fill: 'black', fontSize: 12 }, relativeLabelBody: { x: -15, y: -10, width: 30, height: 20, fill: 'white', stroke: 'black' }, absoluteLabel: { textAnchor: 'middle', textVerticalAnchor: 'middle', fill: 'black', fontSize: 12 }, absoluteLabelBody: { x: -15, y: -10, width: 30, height: 20, fill: 'white', stroke: 'black' }, absoluteReverseLabel: { textAnchor: 'middle', textVerticalAnchor: 'middle', fill: 'black', fontSize: 12 }, absoluteReverseLabelBody: { x: -15, y: -10, width: 30, height: 20, fill: 'white', stroke: 'black' }, offsetLabelPositive: { textAnchor: 'middle', textVerticalAnchor: 'middle', fill: 'black', fontSize: 12 }, offsetLabelPositiveBody: { width: 120, height: 20, fill: 'white', stroke: 'black' }, offsetLabelNegative: { textAnchor: 'middle', textVerticalAnchor: 'middle', fill: 'black', fontSize: 12 }, offsetLabelNegativeBody: { width: 120, height: 20, fill: 'white', stroke: 'black' }, offsetLabelAbsolute: { textAnchor: 'middle', textVerticalAnchor: 'middle', fill: 'black', fontSize: 12 }, offsetLabelAbsoluteBody: { width: 140, height: 20, fill: 'white', stroke: 'black' } } }, { markup: [{ tagName: 'path', selector: 'line' }, { tagName: 'rect', selector: 'relativeLabelBody' }, { tagName: 'text', selector: 'relativeLabel' }, { tagName: 'rect', selector: 'absoluteLabelBody' }, { tagName: 'text', selector: 'absoluteLabel' }, { tagName: 'rect', selector: 'absoluteReverseLabelBody' }, { tagName: 'text', selector: 'absoluteReverseLabel' }, { tagName: 'rect', selector: 'offsetLabelPositiveBody' }, { tagName: 'text', selector: 'offsetLabelPositive' }, { tagName: 'rect', selector: 'offsetLabelNegativeBody' }, { tagName: 'text', selector: 'offsetLabelNegative' }, { tagName: 'rect', selector: 'offsetLabelAbsoluteBody' }, { tagName: 'text', selector: 'offsetLabelAbsolute' }] }); var link = new CustomLink(); link.attr({ relativeLabel: { atConnectionRatio: 0.25, text: '0.25' }, relativeLabelBody: { atConnectionRatio: 0.25 }, absoluteLabel: { atConnectionLength: 150, text: '150' }, absoluteLabelBody: { atConnectionLength: 150 }, absoluteReverseLabel: { atConnectionLength: -100, text: '-100' }, absoluteReverseLabelBody: { atConnectionLength: -100 }, offsetLabelPositive: { atConnectionRatio: 0.66, y: 40, text: 'keepGradient: 0,40' }, offsetLabelPositiveBody: { atConnectionRatio: 0.66, x: -60, // 0 + -60 y: 30 // 40 + -10 }, offsetLabelNegative: { atConnectionRatio: 0.66, y: -40, text: 'keepGradient: 0,-40' }, offsetLabelNegativeBody: { atConnectionRatio: 0.66, x: -60, // 0 + -60 y: -50 // -40 + -10 }, offsetLabelAbsolute: { atConnectionRatioIgnoreGradient: 0.66, x: -40, y: 80, text: 'ignoreGradient: -40,80' }, offsetLabelAbsoluteBody: { atConnectionRatioIgnoreGradient: 0.66, x: -110, // -40 + -70 y: 70 // 80 + -10 } });</code></pre> <p>JointJS source code: <a href="js/special-attributes-link-subelement-labels.js" target="_blank">special-attributes-link-subelement-labels.js</a></p> <p><a href="events.html">In the next section of the intermediate tutorial, we will find out how to make use of JointJS events to support user interaction.</a></p> </div><!--end tutorial--> <script src="../node_modules/prismjs/prism.js"></script> <script src="js/special-attributes-relative-dimensions.js"></script> <script src="js/special-attributes-text-relative-dimensions.js"></script> <script src="js/links-arrowheads-path.js"></script> <script src="js/links-arrowheads-image.js"></script> <script src="js/special-attributes-link-arrowheads.js"></script> <script src="js/special-attributes-link-relative-position.js"></script> <script src="js/special-attributes-link-subelement-labels.js"></script> </body> </html>