1

I'm trying to use knockout for a fast template rendering of JSON data received from a request with jQuery.

Basically, I would load the page with already pre-rendered HTML content (so I can show the content, and if the user's browser has the javascript disabled, I don't pretend to have all the functions working but at least show the basic content).

The next time a user clicks the link, instead of reloading the page, I submit a get-request with Ajax and get some json back. That is the data I should render in the place of the old content.

The problem is really simple: I integrated the knockout template with my HTML markup but when the page loads after calling ko.applyBindings(myviewmodel) I get all the pre-rendered content removed. This is because my model doesn't have any item to render.

Is there a way to use pre-rendered data for HTML requests and knockout templating for Ajax requests only?

Jacob Schoen
  • 14,034
  • 15
  • 82
  • 102
Stefano
  • 3,213
  • 9
  • 60
  • 101
  • You could make the pre-rendered content part of a `div` with a visible binding, and the template `div` with an opposite visible binding, that you set with the ajax request. – Kyeotic Nov 13 '12 at 17:13
  • could you please explain your idea better? I don't understand what "visible binding" means. – Stefano Nov 13 '12 at 17:20

2 Answers2

3

Here is a fiddle demonstrating how a visible binding can show/hide templates. Note that if you deselect Knockout from the list on the left, the Welcome section still shows up properly. Click the button to mimick an ajax request, and see the template section show up.

A visible binding is a standard Knockout binding that controls whether a control displays or not. It looks like this:

<div data-bind="visible: welcome">

Where welcome is an observable property on your viewmodel.

If this still isn't clear, I strongly recommend the Knockout Interactive Tutorials, they will cover this and other basic usage.

Kyeotic
  • 19,697
  • 10
  • 71
  • 128
  • so you are basically suggesting to render two times the data. The first is the pre-rendered data on the server side and the second on the client side. Then bind the two data to a property that changes when I do an ajax requests to hide the old data. Right? – Stefano Nov 13 '12 at 18:00
  • Yes. You will be rendering this data no matter how you do it. This isn't two times more than some other method: any method that shows one thing before the ajax, and another thing after will have to render both. – Kyeotic Nov 13 '12 at 18:46
  • I implemented a test case with my site and your solution and it seems to work. However, I'm not really happy to render the data two times and hide what I don't need. I really hope in the future knockout will have some native-support for pre-rendered html data, however this seems to be the only fix at the moment – Stefano Nov 13 '12 at 18:54
  • @Stefano we misunderstood each other. I thought you were talking about the template below re-rendering after the ajax call. This is necessary, since its contents will be updating. Knockout does not re-render the top OR the bottom when it activates though, the contents remains as is. Knockout isn't hiding the bottom, it already has `display:none` so that it will be hidden even when javascript is disabled. You shouldn't worry about this, it is very performant. No system will be able to update the template with ajax results without re-rendering, this is a weakness of knockout. – Kyeotic Nov 13 '12 at 21:31
  • what I meant with my reply was that take for example a table: I have 6 rows but with your method (that is great anyway) I have to pre-render the html markup with the rows and then knockout will render 6 other rows hiding the firsts. So in total I have 12 rows. Now think 1000 rows: I will have 2000 rows, and so on. Your method is great and works but I prefer to delete the pre-rendered content at the first ajax request with the jQuery empty() method and after use the classic knockout steps to render the data. What knockout should have natively is some sort of support for html alredy rendered. – Stefano Nov 13 '12 at 21:36
  • @Stefano I'm confused why you are pre-rendering rows in a section of HTML that you are not showing. Wasn't the template section supposed to be *hidden* until the ajax call comes back? – Kyeotic Nov 13 '12 at 22:48
  • yes it would be hidden but not the no-javascript part. So when I make an ajax request I will show the script template and hide the serverside rendered content, however I will still have the double of rows I should have. I just saw angularjs from google and I'm right now asking myself if it can use prerendered html – Stefano Nov 13 '12 at 23:09
  • @Stefano Still really confused. Is the "no-javascript" part the same as the post-ajax template? Where are these double rows coming from? If the data is the same before and after an ajax request *why do you need an ajax request*? – Kyeotic Nov 13 '12 at 23:19
  • the html markup is the same obviously but the data is different. An example could be a paginated-table. At first load the page 1 is shown with the data, then when switching to page 2 instead of refresh the page I only get the JSON data to render – Stefano Nov 14 '12 at 12:54
  • @Stefano If you are changing the data, a re-render happens. This is *always* the case, regardless of framework. This is not "rendering twice." It is rendering one-per-data. However, only the HTML that was changed will re-render, not the whole page. Knockout won't re-render anything but the parts that changed either. – Kyeotic Nov 14 '12 at 17:34
  • exactly, but this means that the table rows pre-rendered by the server will stay there (hidden). So, there are the hidden rows + the new rows that are rendered by knockout when the json data is received – Stefano Nov 14 '12 at 17:54
  • @Stefano No, the pre-rendered rows will not stay there. Knockout will remove them when it adds the new rows from the ajax request. I can demonstrate this in a fiddle if you would like. – Kyeotic Nov 14 '12 at 17:58
  • for "remove" do you mean from the DOM? with firebug I can still see them. Maybe I've done something wrong, could you please do an example with jsFiddle? – Stefano Nov 14 '12 at 18:07
  • @Stefano Sure. Can you add the HTML structure you want to use to your question? – Kyeotic Nov 14 '12 at 19:34
  • I finally managed to print serverside the content, then at the first ajax request with jquery I remove the elements and then I initialize knockout bindings. This allow me to support javascript-disabled users – Stefano Nov 15 '12 at 17:09
0

Instead of trying to do two separate things, can you not do the following:

  1. Define your viewmodel with an observable property that will hold the data for your content.
  2. On initial page load, initialise the observable property with your pre-rendered data (retrieved from a hidden field perhaps?).
  3. Bind your content element to the observable using your template.

Then when you make your ajax requests, just update the data in the observable property with the data retrieved from your request, and the UI should update automatically.

Edit:

Here's a quick jsFiddle demonstrating the concept (comment javascript out to simulate disabled javascript).

<input id="initialstate" type="hidden" value="4,5,6" />

<ul id="content" data-bind="template: { name: 'item-template', foreach:data }">
    <li>1</li>
    <li>2</li>
    <li>3</li>    
</ul>

<script type="text/html" id="item-template">
    <li data-bind="text: $data"></li>
</script>​

var viewModel = (function()
{
    var self = {};

    self.data = {};

    function init() {
        $('#content').empty(); 
        self.data = ko.observableArray($('#initialstate').val().split(','));
    }

    init();

    return self;
}());

ko.applyBindings(viewModel);
Brett Postin
  • 11,215
  • 10
  • 60
  • 95
  • my initial idea was to initialize the viewmodel data from a json string pre-rendered in the html markup. This however has the problem that when the javascript is disabled the page has no content – Stefano Nov 13 '12 at 18:01
  • You could still render your initial html inside the content element. If javascript is disabled your initial content will remain. If javascript is enabled your content element will bind to your initialised observable using your template. – Brett Postin Nov 13 '12 at 18:19
  • your jsFiddle example shows nothing when the javascript is disabled. However your solution of removing the elements from the DOM if the javascript is enabled it not that bad but maybe expensive in terms of performances? Anyway, thank you – Stefano Nov 13 '12 at 18:58
  • @Stefano I did notice that but try testing it outside of jsFiddle. Either way you get the idea... – Brett Postin Nov 13 '12 at 19:01
  • See this link also [Remove all child elements of a DOM node in JavaScript?](http://stackoverflow.com/a/3955238/295813). Performance shouldn't be an issue using innerHTML = "". Alternatively you could use jQuery.empty() – Brett Postin Nov 13 '12 at 19:04
  • I think your method is the best about performances because I don't have to render the html twice like with the Tyrsius solution. However, the drawback of your solution is that I have to duplicate twice the code used for my content: one for the markup rendered server-side and one for the script template. This means that it will be more difficult to track eventually the future edits of my code. But at the moment knockout doesn't offer native support for pre-rendered html so your solution seems the best – Stefano Nov 13 '12 at 21:40
  • The server side code used to generate the pre-rendered html could also be used to generate your template. That way you eliminate the maintenance issue of having to update two places. – Brett Postin Nov 13 '12 at 22:02
  • I'm using symfony and twig as backend, I don't ser how I could have only one html markup for the server and client side – Stefano Nov 13 '12 at 22:46
  • Sorry i'm not familiar with either of those so i can't really help further. However you are injecting the html markup into the content element, why not refactor that code out so that it can also inject the template markup in between the – Brett Postin Nov 13 '12 at 23:00