I'm a software engineer with Master degree in Computer Science. I'm experienced in JavaEE and other web technologies around Java. I enjoy studying code and design patterns and experimenting with new technologies and approaches, which may improve the quality of the code. Ondrej has posted 1 posts at DZone. You can read more from them at their website. View Full User Profile

Transparently Transfer Data from Server Script to Client JavaScript

09.06.2012
| 4886 views |
  • submit to reddit

When developing a web application, sometimes I needed to pass information from the server to a JavaScript component at the moment when the page is built. I believe that a general solution is to use a <script> element in the HTML page, which initializes the component with data and is generated by a server-side script (PHP, Groovy, Python, etc.). However, when writing reusable JavaScript components, this is not a maintainable solution.

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.

I always try to improve what appears cumbersome, so in my projects I'm using a declarative approach within HTML body. This approach reverses the responsibility of initializing JavaScript components from the generated page to an external js file. Therefore, the only <script> element placed within the HTML page is a link to the external js file, which calls an initializer function after the document is loaded.

The 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 in the HTML page (JavaScript is static, not generated)
Therefore configuration from the server script can be passed to the HTML page (rendered in the page), but not to static JavaScript scripts.

We want to create encapsulated HTML+ JavaScript components, which can be inserted once or multiple times into a generated HTML page and can be passed as a configuration object from the 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
Both requirements make the solution a bit cumbersome, but understanding this solution will help you understand the idea.

Description:

  1. For each piece of HTML code (which places the component into generated page), generate a random textual identifier
    • def randomId = getRandomId('my-javascript-button-')
  2. 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>
  3. 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) {
      }
  4. 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} ); 

Results:

  • JavaScript code, which can be efficiently passed configuration
  • HTML element and its configuration are visually separated in the 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? Can't 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 the above code is not a valid XHTML code, we can get really close to something like this without breaking XHTML rules. In fact, we can use a nested invisible element, which would be accessible in the 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:

  1. 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>
  2. 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>
  3. If you need to store nested objects you may also nest elements or even nest a 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.
  4. In an 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 teh "config" element and read values from the DOM subtree of this element.
  5. The initializer function will be executed once after html page is loaded, (e.g. using jQuery's $(document).ready() ). It is best 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 the 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 has 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 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 the Simple JSON solution, this 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 an 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>
JSON should be compliant with XHTML and should not break its structure. If we're not sure, it is better to put the JSON code into a CDATA block.
Published at DZone with permission of its author, Ondrej Mihalyi. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)