Re: [Cppcms-users] template plugins
Brought to you by:
artyom-beilis
|
From: Artyom B. <art...@ya...> - 2012-01-05 11:02:08
|
Hi,
First of all before I'll answer any specific points I'll summarize my answer.
General Concepts Notes
=======================
Skin = namespace
View = class
Template = class-member-function
Even thou in general different "skins" are originally
used for "skinning" purposes, it is not a mandatory
way to see the things.
Each plugin should have its own skin (or namespace) and
its content can be rendered they way you need.
Limitations
===========
Currently cppcms_tmpl_cc does not generate separate h and cpp file
and thus you can't derive views (classes) in one skin (namespac)
from other. There is other way to reuses "shared-skin", I'll show it
later.
Making simple plugin
=====================
There are two ways to do it. One, related to CppCMS 0.99.11
and other to the latest version in trunk. Lets start with current 0.99.11.
Our main page
<% skin basic_skin %>
<% view basic_view uses content::basic_content %>
<html>
...
<body>
...
<!--- aux part-->
<% foreach p in plugings %>
<% item %><%= p.rendered_aux_html_content | raw %><% end %>
<% end %>
...
<!--- Main part-->
<% foreach p in plugings %>
<% item %>
<div id="plugin_<%=p.id %>" >
<%= p.rendered_main_html_content | raw %>
</div>
<% end %>
<% end %>
...
</body>
<% end view %>
...
<% end skin %>
Now this is what plugin's template should contain
<% skin plugin_a %>
<% view main_html uses plugin_a_content::some_content_b %>
<% template render() %>
<!-- Notice render function -->
some plugin specific html
that does something it needs
<% end template %>
<% view aux_html uses plugin_a_content::some_content_a %>
<% template render() %>
<!-- Notice render function -->
some plugin specific html
that does something it needs
<% end template %>
<% end view %>
Now how we integrate all this.
Let's say that our plugin should provide following API
class plugin {
public:
virtual std::string main_html(cppcms::applicatin *app) = 0;
virtual std::string aux_html(cppcms::application *app) = 0;
};
Now when we render each plugin we do following:
main_application.cpp
content::basic_content c;
// prepare c
...
// render plugins
for(int i=0;i<loaded_plugins.size();i++) {
c.plugins[i].rendered_main_html_content = loaded_plugins[i]->main_html(this);
c.plugins[i].rendered_aux_html_content = loaded_plugins[i]->aux_html(this);
}
render("basic_skin","basic_view",c);
And plugin looks like this:
std::string main_html(cppcms::applicatin *app)
{
plugin_a_content::some_content_b c;
...
std::ostringstream buf;
app->render("plugin_a","main_html",c,buf);
return buf.str();
}
std::string aux_html(cppcms::applicatin *app)
{
plugin_a_content::some_content_a c;
...
std::ostringstream buf;
app->render("plugin_a","aux_html",c,buf);
return buf.str();
}
That's it...
Now in CppCMS 0.99.12 (trunk) you can do following
==================================================
There is a new command <% render ... %>
http://art-blog.no-ip.info/wikipp/en/page/cppcms_1x_templates_comm#Rendering.other.views
Our main page
<% skin basic_skin %>
<% view basic_view uses content::basic_content %>
<html>
...
<body>
...
<!--- aux part-->
<% foreach p in plugings %>
<% item %><% render p->name(), "aux_html" with p->aux_html_content() %><% end %>
<% end %>
...
<!--- Main part-->
<% foreach p in plugings %>
<% item %>
<div id="plugin_<%=p->id() %>" >
<% render p->name(), "main_html" with p->main_html_content() %>
</div>
<% end %>
<% end %>
...
</body>
<% end view %>
...
<% end skin %>
Where p is actually a pointer to plugin object.
Now the plugin skin would look the same. skipping
Now how we integrate all this.
Let's say that our plugin should provide little bit
different api:
class plugin {
public:
virtual void prepare() = 0;
virtual cppcms::base_content &main_html_content() = 0;
virtual cppcms::base_content &aux_html_content() = 0;
virtua std::string name() = 0;
};
Now when we render each plugin we do following:
basic_content {
...
// pointers to plugins
std::vector<plugin *> plugins;
}
main_application.cpp
content::basic_content c;
// prepare c
...
for(p in plugins)
p->prepare();
render("basic_skin","basic_view",c);
And plugin looks like this:
class my_plugin : public plugin {
public:
virtual void prepare()
{
... setup main_ and aux_...
}
virtual std::string name() { return "plugin_a"; }
virtual cppcms::basic_content &main_html_content() { return main_; }
virtual cppcms::basic_content &aux_html_content() { return aux_; }
private:
plugin_a_content::some_content_b aux_;
plugin_a_content::some_content_a main_;
};
std::string main_html(cppcms::applicatin *app)
{
plugin_a_content::some_content_b c;
...
std::ostringstream buf;
app->render("plugin_a","main_html",c,buf);
return buf.str();
}
std::string aux_html(cppcms::applicatin *app)
{
plugin_a_content::some_content_a c;
...
std::ostringstream buf;
app->render("plugin_a","aux_html",c,buf);
return buf.str();
}
==========================================================
Of course there are more variansts (for 0.99.12/trunk).
For example:
plugin {
void render_main(cppcms::application &);
void render_aux(cppcms::application &);
}
Where plugin executed like
render_main(cppcms::application &app)
{
app.render("plugin_a","main_html",my_content);
}
And in the template code you cakk
<% foreach p in plugins %>
<% item %>
<% c++ p->render_main(content.app()); %>
<% end item %>
<% end foreach %>
According to your imagination
==========================================
Derivation
==========================================
Now, reusing a skin. Consider that your plugin
wants to extend basic_skin::basic_view (i.e. derive from it).
And have control over entire page.
It is much more tricky.
You can't derive from it as cppcms_tmpl_cc does not generate
header files, what you can do is to do follwoing
Instead of writing <% skin basic_skin %>
You use nameless skin <% skin %>
And for example when you compile main skin
Main view cpp:
cppcms_tmpl_cc -s basic_skin basic_view.tmpl some_other_main_views.tmpl -o basic_skin.cpp
g++ basic_skin.cpp -o libbasic_skin.so
cppcms_tmpl_cc -s plugin_a basic_view.tmpl plugin_a.tmpl -o plugin_a.cpp
g++ plugin_code.cpp plugin_a.cpp -o libplugin_a.so
Notice basic_view.tmpl appears twice but once under skin=namespace
"basic_skin" and the second time under skin=namespace plugin_a.
This copies c++ code, and requires plugin to actually use
application's skin code, it is not so good but it is tolerable,
at least untill cppcms_tmpl_cc will support generation of h and cpp
files.
============================
Now all the above should go to wiki :-)
==========================================================
Now particular answers
================================
>
>Oh, up to today I have been using 0.99.10.
>I see you released 0.99.11 ten days ago:
>http://art-blog.no-ip.info/cppcms/blog/post/90
>In your blog, you don't give any details on how / why the views have been
>refactored, and the implication, if any, for existing code
>(e.g. what is now possible that wasn't before?)
>
This class was not existing:
http://art-blog.no-ip.info/cppcms_ref_v0_99/classcppcms_1_1views_1_1pool.html
All the operations on skins should be done via cppcms::application, the code
was bad and many parts where declared as private.
Now it is publicly usable
>
>
>So, back to my original question: can you guesstimate the release dates for
>1.0 and 1.1?
>>From yesterday's post, I guesstimate that 1.0.0 RC1 will be released this
>month, and the final 1.0.0 in February at the latest
>When would the first beta 1.1 be released? Early spring?
>And the first 1.1.0 RC?
>I don't mind developing against a beta release. I guess it'll be stable enough
>for a live release of my own application late this year.....
>
I don't know, 1.0 probably in a month... 1.1 would start immediatly
when it become stable as 1.2 probably few month afterward.
>
>> On the other hand if I spend now lots of time today on plugin framework
>> and tomorrow on other very important feature I'll never release a stable
>> version and this is not good for the project. It was in beta stage for
>> too long time.
>
>Well, I was not asking for you to implement the whole plugin framework right
>now, before the release 1.0.0.
>However, it seems that I am missing a single method in order to complete my
>own plugin framework: how to attach new template includes. And again, I am not
>the first one to ask about it.
> See below.
>So, what I was asking is, in terms of additional code before the code freeze,
>can we have that extra method to complete our own frameworks? (unless, again,
>I am doing things the wrong way, and what I am asking is possible another
>way...)
>
I hope the notes above answer on what you ask.
>--
>
>I started replying to the rest of your comments, but I guess the discussion
>will be clearer if I do my best to state as clearly as possible what I am
>after.
>
> [snip]
>
> Right now, the
> important is that you understand what I am after.
> The core of the application is defining the general layout of the template, and
> tells where the content from different plugins shall be inserted.
>
I think example above clarifies this
>> Only thing you should care is to make sure each as its own skin name and
>> not collide with its other.
>
>(=> with *each* other)
>
>Err... This is a major departure from what I was trying to do. Maybe I don't
>understand the way you use the term 'skin'. For me, 'skin' refers to the
>general look and appearance and colour scheme of the web site. A plugin
>implementing a block of content would output this content in a way that's
>congruent with the site's skin/theme.
>
> ...
>Again, for me skin == theme. Only one is operative for any page request.
>Or do you mean that a single page can be assembled from the output of different
>skins?
>
If you have doughs take a look on skin as separate unit, namespace
not certainly the style.
>http://art-blog.no-ip.info/wikipp/en/page/cppcms_1x_templates_gen
>I quote from above:
> "Each skin should implement the virtual function render(),
> unless it is already implemented in its parent."
>Do you mean that a skin can have parents??
>In the wiki page above, you mention a views hierarchy (which, I think, I
>understand) but not a skin hierarchy!
>
>
Ok this is a mistake, I ment "Each view should implemen", skins are namespaces
they do not have inheritence.
>For reference, my problem appears to me to be very, very similar to that of
>Julian Pietron,
>
> [snip]
>
> I still fail to see how I can tell each plugin to render their specific
>parts according to their own data and own templates, and to pass the result
>back to the core application which will render the whole page and send it to
>the user.
>
I think that code above explains it
>How would
>views_pool::static_instance().render(...)
>interact with the core template/view/skin?
It was changed since to:
cppcms::views::pool::instance().render(skin_name,view_name,content,output_stream)
It would render: a view in skin using content to the stream :-)
I home it is clear now.
>
>
>> > 4. Note: you can't load and unload two plugins simultaneously as it would
>> > not be thread safe,
>
>I don't understand what you mean by "loading two plugins simultaneously".
>
I think it is out of date note.
>
>Basically, what follows is the implementation side of the simple example I
>posted above:
>
>// core content.hpp
>struct main : public cppcms::base_content {
> std::string title;
> std::string content;
>}
>
>// plugin: plugin_content.hpp
>#include "core/content.hpp"
>namespace content {
>struct example_page : public main {
> std::string my_test;
>};
>} // namespace content.
>
>As you see, the plugin content are sub-classes of the main content.
See examples above.
>In the thread mentioned above, Julian asked:
>
> [snip]
>
>
>which obviously registers the view within the pool.
>
>And what I was originally asking, is a method to add a template to an already
>registered skin/view.
Not possible to do it directly today. See a workaround above.
You need to create a new skin and reuse some templates.
>
>> > 2) On the other hand, you seemed to say that it was not possible, because
>> > it was difficult to do it right, especially considering race conditions
>> > in a multi- threaded web server, leading to a crash when one thread is
>> > trying to render a sub-template from a plugin that was just disabled in
>> > another thread.
>>
>> The cppcms::views::pool::render(...) is thread safe
>> as it holds shared lock during any rendering process. If you
>> would try to unload a skin during this period you would be blocked
>> till **all** rendering operations will complete and
>> then you'll be able to load/unload the view
>
>Julian asked:
>
>> > Would it be possible to register a view at runtime?
>
>You replied:
>
>> > Not sure. It would required making views loading
>> > and unloading be thread safe and make the interaction
>> > with actual DLL quite complicated.
>
>
>But that was 18 months ago, so maybe things have changed since then...
>
DLLs are supported, and views loading is thread safe now.
>If I can achieve the end result above, then I would have passed a very major
>hurdle.
>
>Sorry for being slow to catch up with you, and for the length of this email.
>
I think the example above provides the solution.
Correct me if I wrong.
>Thanks, as always,
>
>Augustin.
>
>
Best Regards,
Artyom
|