The problem is that the component may be used multiple times (or even not at all), but every time the component is included in the HTML page, the same initialization script is executed. It would work if every component was identified by a unique ID, however, this does not seem to be a good practice.
As I always try to improve what appears cumbersome, in my projects I'm using a declarative approach within HTML body. This approach reverses the responsibility of initializing JavaScript component from the generated page to an external js file. Therefore, the only <script> element placed within the HTML page is a link to external js file, which calls an initializer function after document is loaded.
Final solution may look like this:
<div class="my-javascript-button">
<div class="config" style="display: none;">
<div class="title">Go to www.google.com</div>
<a class="url" href="http://www.google.com"/>
</div>
</div>
or
<div class="my-javascript-button">
<div class="config" style="display:none;">
<!-- JSON object: -->
{ "title" : "Go to www.google.com",
"url" : "http://www.google.com"
}
</div>
</div>
To read about best way how to code it, jump directly to Golden Mean Solution.
Description of the Problem:
- Data in server script, which generates page dynamically (PHP, Ruby, Python,...)
- HTML page generated by the script
- Javascript included into HTML page (JavaScript is static, not generated)
We want to create encapsulated HTML+ JavaScript components, which can be inserted once or multiple times into generated HTML page and can be passed a configuration object from server side script to configure their behavior.
Simple solution using JSON (still cumbersome)
Requirements
- requires JSON and random generation support in server script
- requires piece of javascript code in HTML page
Description
- For each piece of HTML code (which places the component into generated page), generate a random textual identifier
def randomId = getRandomId('my-javascript-button-')
- Mark that piece of HTML with randomId, either by attribute id, or as css class, or other attribute:
<div class="my-javascript-button ${randomId}">
... rest of html goes here
</div>
- In the external JavaScript file for the component, create an initializer function for this configuration type, which takes two parameters: randomId to identify the target html element, and an object holding the configuration:
function initializeMyJavascriptButton(id, config) {
}
- Call the initializer function from the page, passing in randomId and script object encoded to JSON:
- example in grails GSP:
initializeMyJavascriptButton("${randomId}" , ${config as JSON} );
- example in grails GSP:
Results
- JavaScript code, which can be efficiently passed configuration
- HTML element and its configuration are visually separated in HTML source code as HTML and JavaScript blocks - they are coupled by generated random identifier
Declarative Solution using invisible DOM structure
The simple JSON solution works, but is not easily maintainable nor readable. Generated identifiers - do we really need this? Cannot we just mark all components with a common class? Where is the configuration of the component, in a different HTML block? Why not put it directly at the place where the component is declared, as its attributes, or at least in nested block? Also, generating JavaScript code is not a neat idea, is there a way to put all code into a separate file, or even a library?After all, the basic question: Why do we need JavaScript just to declare a component together with its attributes, when HTML is rich enough to do it?
Imagine this declaration:
<div class="my-javascript-button" title="
Go to www.google.com" url="http://www.google.com">
</div>
See what I mean?
Although above code is not a valid XHTML code, we can get really close to something like it while not breaking XHTML rules. In fact, we can use a nested invisible element, which would be accessible in DOM tree from JavaScript, but will not affect how the page is rendered.
Requirements
- custom technique in server script to generate HTML elements to reflect data on server
- custom technique to read values from html elements in the page
Both these custom techniques can be extracted into a server-side and JavaScript API, which could be reused.
Description
- For each piece of HTML code (which places the component into generated page), create a nested div element with special class "config", which is given css style "display: none;" :
-
div with class "config" will be invisible in the document and will not have any impact on how the page is generated:
<div class="my-javascript-button">
<div class="config" style="display: none;">
</div>
</div>
-
div with class "config" will be invisible in the document and will not have any impact on how the page is generated:
- Inside div with class "config", you may put any DOM elements, identify them using class attribute, and then assign a value to them, either as one of valid attributes, or as a body of the element:
<div class="my-javascript-button">
<div class="config" style="display: none;">
<div class="title">My title</div>
<a class="url" href="http://my.url"/>
<ul class="listOfTargets" >
<li>target1</li>
<li>target2</li>
</ul>
</div>
</div> - If you need to store nested objects, you may also nest elements, or even nest list of elements. You may in fact create any HTML structure, which will somehow store desired values. You may make it more readable using appropriate HTML elements (ul for lists, a for urls), or always use div elements, as more appropriate
- In external JavaScript file, define an initializer, which can be executed to initialize configuration for all target elements in the html page. This initializer will find all the target elements, and for each of them it will find "config" element and read values from DOM subtree of this element
- The initializer function will be executed once after html page is loaded, (e.g. using jQuery's $(document).ready() ). Best is to execute the initializer within the external JavaScript file and ensure that it is executed only once (you may use a global variable, or mark all config elements on first execution, so that config is read only once)
Results
- configuration is always stored within the target html element, regardless if the browser supports javascript (although this is rather irrelevant, as the configuration has no use without javascript)
- configuration is declarative, therefore it can be parsed with any alternative technique or even with external HTML analyzer (e.g. in unit tests)
- coupling between the html element and its configuration is visible using DOM inspector right within the html element
- no standard way of writing and reading configuration to and from html elements
- may be slower than direct conversion of server data to JSON
As this solution is pretty nice and readable, it may be sufficient or even desired in some cases. However, it brings some cons compared to the Simple JSON solution: it is easier to read the code, but takes longer to write it, and also reading the configuration by JavaScript may be slower than parsing JSON.
Therefore, as always, there is a golden mean that would solve everything. Or is there?
Golden Mean Solution - declarative approach with JSON
The idea here is to standardize the way how data is stored within the nested config element used in the Declarative solution. Instead of writing config using DOM elements, we would use JSON, which can be easily read by JavaScript.
Requirements
- As in Simple JSON solution, requires JSON support in server script, but does not rely on random numbers and does not require putting any JavaScript to initialize a component
- As in the Declarative solution, requires some custom code. However, this time, no special technique is necessary on server side, and on client side, only a function to retrieve configuration block within a component declaration is necessary (couple of lines of reusable JavaScript code)
Description
This solution is similar to the Declarative solution. In fact, it is also declarative, but uses one simple trick together with JSON:
Instead of encoding data to a HTML structure, we may encode the configuration into JSON and put it as a body of the config element. like this:
<div class="my-javascript-button">
<div class="config" style="display: none;">
${it as JSON}
</div>
</div>
No comments:
Post a Comment