Marko - Streaming to a subpart of the main template

49 views
Skip to first unread message

julien....@gmail.com

unread,
Nov 5, 2015, 12:36:00 PM11/5/15
to RaptorJS
Hi,

I started playing around with Marko these past days and while I still struggle with some of the concepts I think it's pretty cool and quite easy to use.

One of the concept where I'm a bit stuck is the following.

I'm trying to render a stream of questions within the main template (index.marko). I managed to quite abstract this and make the template be flexible regarding the amount of questions it will eventually display (I don't know that number beforehand and I don't want to know it -> no hardcoding items when delivered to the client).

So I got my stream open and that works well, but everytime I do a flush when I have a satisfying amount of questions ready to be shown (aka at least 1), it does render the whole template again, meaning I end up with this HTML:

<head>
</head>
<body>
<h1>test Async Rendering</h1>
<div>
<div>
<div>
<meta charset="UTF-8">
<title>marko-express</title>
<link media="all" type="text/css" rel="stylesheet" href="/static/reset.css">
<link media="all" type="text/css" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<link media="all" type="text/css" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap-theme.min.css">
<link media="all" type="text/css" rel="stylesheet" href="/static/style.css">
<script type="text/javascript">
<h1>test Async Rendering</h1>
<div>
<div>
<div>
<meta charset="UTF-8">
<title>marko-express</title>
<link media="all" type="text/css" rel="stylesheet" href="/static/reset.css">
<link media="all" type="text/css" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<link media="all" type="text/css" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap-theme.min.css">
<link media="all" type="text/css" rel="stylesheet" href="/static/style.css">
<script type="text/javascript">
<h1>test Async Rendering</h1>
<div>
<div>
<div>
</body>

How do I end up with something like this is where I think I went in the wrong direction for the abstraction:

<head><meta charset="UTF-8">
<title>marko-express</title>
<link media="all" type="text/css" rel="stylesheet" href="/static/reset.css">
<link media="all" type="text/css" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<link media="all" type="text/css" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap-theme.min.css">
</head>
<body>
<div>
<div>
<div>
<div>
<div>
<div>
<div>
<div>
<div>
<script src="https://code.jquery.com/jquery-1.11.1.min.js">
<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js">
<script src="http://localhost:62847/browser-refresh.js">
</body>


I use the default index.marko provided by the repo so here is the internal of body:
<body>
   
<app-header title="test Async Rendering"/>

   
<include template="./questions.marko" template-data="data" />

   
<script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
   
<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>

   
<!--
    Page will automatically refresh any time a template is modified
    if launched using the browser-refresh Node.js process launcher:
    https://github.com/patrick-steele-idem/browser-refresh
    -->

   
<browser-refresh/>
</body>

and here is the relevant part of the logic:

function renderOnlyQuestions(questions, res) {
   
return questionTemplater.render({
        provider
: function(args, callback) {
            callback
(null, questions);
       
}
   
}, res);
}

function flushRender(questions, res) {
    indexTemplate
.render({}, res);
    renderOnlyQuestions
(questions, res);
}

function deferAnswer(questionTemplates, asyncOut, defer){
    setTimeout
(
       
function() {
            flushRender
(questionTemplates, asyncOut);
       
}, (defer ? Math.floor(Math.random() * 5000 + 1000) : 0)
   
);
}

app
.use('/static', serveStatic(__dirname + '/static'));

app
.get('/', function(req, res) {
   
var form = {};
   
var questionTemplates = [];
   
var out = require('async-writer').create(res);
   
var asyncOut = out.beginAsync();

   
for (var i = 0; i < 100; i++) {
        form
= getForm(i);

       
if (form) {
            questionTemplates
= prepareTemplates(form);
            console
.log(questionTemplates);
            deferAnswer
(questionTemplates, asyncOut, true);
       
} else {
            console
.log('no more questions to fetch');
           
break;
       
}
   
}

    setTimeout
(function(){
        asyncOut
.end();
       
out.end();
   
}, 8000);
});

Thanks for your help, please let me know if the issue is not clear, I'm deep into it so I may not express the simpler concerns. Also I'm not sure if I'm the right group, sorry if I'm not but I couldn't find other sources for help with Marko

julien....@gmail.com

unread,
Nov 5, 2015, 12:38:30 PM11/5/15
to RaptorJS
I guess my real question is how can I keep alive the main template while populating a stream within it?

julien....@gmail.com

unread,
Nov 5, 2015, 1:01:42 PM11/5/15
to RaptorJS
Oh and the async fragment of questions.marko

<async-fragment name="questionsList" data-provider="data.provider" var="data">
   
<div for="question in data; status-var=loop">
       
<h4> This should be ${loop.getIndex()+1} question </h4>
       
<div id="${question.id}">
           
<include template="${question.template}" template-data="question"/>
       
</div>
   
</div>
</async-fragment>


Patrick Steele-Idem

unread,
Nov 5, 2015, 1:37:02 PM11/5/15
to RaptorJS
Hi Julien,

Sometimes things are easier to explain in code so I created a sample app just for you! Please take a look at the following project:

If you start the server and open the page in the server you should see HTML search results be streamed back to the browser with an artificial delay between each page of search results. Please take a look at the code and I'll be happy to answer any questions you may have. Also, please join us in the Gitter chat room: https://gitter.im/marko-js/marko

Cheers!

--Patrick

julien....@gmail.com

unread,
Nov 6, 2015, 4:35:43 AM11/6/15
to RaptorJS
Hi Patrick,

thanks for pointing me there.

So if I get that correctly what I need to do is to delegate the async responsibility to my component and not to the server? Doesn't that leave the responsibility of rendering to the client?
Maybe I got that wrong, but in my current approach there is a prerender in the server no?

function prepareTemplates(form) {
   
var prepared = [];
    _
.each(form, function(question) {
       
var questionTemplate = require('./components/' + question.type + '/template.marko');
        question
.template = questionTemplate;
        prepared
.push(question);
   
});

   
return prepared;
}

in my server.js file

I'm quite concerned by performance in this approach so I'd like as much as possible avoiding client work. Please correct me if my understanding is wrong.

Cheers

Patrick Steele-Idem

unread,
Nov 6, 2015, 11:10:49 AM11/6/15
to RaptorJS
Hey Julien,

In the sample project that I shared all of the HTML is being rendered on the server. There is no client-side rendering and you can verify that by viewing the source for the page and you will see that the server is providing all of the HTML. I created a separate custom tag/component to handle streaming the search results because I felt that was the cleanest solution, but it is not the only option. Custom tags/components are "isomorphic" and can be rendered on either the server or in the browser.

Does that clarify?

--Patrick
Reply all
Reply to author
Forward
0 new messages