tag:blogger.com,1999:blog-21585988210711415962023-11-16T02:48:26.115-08:00Teresa @ Magnolia_CMSTeresa Miyarhttp://www.blogger.com/profile/04763321910064824253noreply@blogger.comBlogger19125tag:blogger.com,1999:blog-2158598821071141596.post-43075698628492859762016-03-11T02:49:00.000-08:002016-03-11T02:51:57.184-08:00JCR-SQL2 optimize queries samples<h4>
Queries run on Magnolia 5.x</h4>
<h4>
<br /></h4>
<h4>
1. Search for specific Item type </h4>
i.e. [mgnl:page] instead of using the default one [nt:base]<br />
Example: Search for pages with stkNews template<br />
<br />
select * from [nt:base] as t where isdescendantnode(t, '/demo-project') and t.[mgnl:template]='standard-templating-kit:pages/stkNews'<br />
<br />
This query takes 18ms to return.<br />
<br />
select * from [mgnl:page] as t where isdescendantnode(t, '/demo-project') and t.[mgnl:template]='standard-templating-kit:pages/stkNews'<br />
<br />
This query takes 3ms to run.<br />
<h4>
<br /></h4>
<h4>
2. Avoid the use of isdescendantnode if not necessary.</h4>
Example: Searching in data module, two different types of data with same property<br />
<br />
<span style="font-family: "helvetica";">SELECT * from [nt:base] AS t WHERE ((ISDESCENDANTNODE(t, '/folder1type1') AND [jcr:primaryType] = 'type1') or (ISDESCENDANTNODE(t, '/folder2type2') AND [jcr:primaryType] = 'type2')) and t.categories = 'c6f40746-5be4-44c5-9eb1-0f5e59133350'</span><br />
<span style="font-family: "helvetica";"><br /></span>
<span style="font-family: "helvetica";">This query takes more than 6000ms to run</span><br />
<span style="font-family: "helvetica";"><br /></span>
<span style="font-family: "helvetica";">SELECT * from [nt:base] AS t WHERE [jcr:primaryType] = 'type1' or [jcr:primaryType] = 'type2' and t.categories = 'c6f40746-5be4-44c5-9eb1-0f5e59133350'</span><br />
<span style="font-family: "helvetica";"><br /></span>
<span style="font-family: "helvetica";">This query takes 13ms to run, in this case we are looking for type1 that is just under folder1type1 and type2 that is just in folder2type2, so the isdescendantnode is redundant.</span><br />
<h4>
<span style="font-family: "helvetica";"><br /></span></h4>
<h4>
<span style="font-family: "helvetica";">3. Performance</span></h4>
<span style="font-family: "helvetica";">In order to check the queries performance and see if you need to </span><span style="font-family: "helvetica";">optimize them, see previous post on </span>http://tmiyar.blogspot.com.es/2012/06/queries-logging.html<br />
<h4>
<span style="font-family: "helvetica";"><br /></span></h4>
<h4>
<span style="font-family: "helvetica";">4. query samples</span></h4>
<span style="font-family: "helvetica";">* get latest modified pages in demo-project, here you need to know that any time you modify any component in the page, that page gets its modification date updated. That is why we only need to ask for mgnl:page</span><br />
select * from [mgnl:page] as t where isdescendantnode(t, '/demo-project') order by t.[mgnl:lastModified]<br />
<span style="font-family: "helvetica";">the way to limit the result set is by code </span>query.setLimit(3); before calling query.execute();<br />
<a href="https://www.blogger.com/blogger.g?blogID=2158598821071141596" style="color: #333333; font-family: verdana, helvetica, sans-serif; font-size: 0.9em; line-height: 18px; text-align: justify;"><br /></a>
* find where a control is used in the configuration<br />
select * from [mgnl:contentNode] as t where t.class like '%SelectFieldDefinition'<br />
In this case you can decide to get the result type mgnl:content, you can do it in the jcr query tool.<br />
<br />
* <span style="white-space: pre-wrap;">for queries that rely on a path with wildcards there is no other way that to use isdescendantnode and inschildnode joins that makes queries a bit more complex, in that cases I would recommend to use xpath as is not deprecated, it is faster and I dont think they will remove it.</span><br />
<span style="white-space: pre-wrap;"><br /></span>
<br />
<span style="font-family: "helvetica";"><b>Note</b>: Nodes get cached by Jackrabbit, second time you run the query in our tool will be faster, keep that in mind when doing your tests.</span>Teresa Miyarhttp://www.blogger.com/profile/04763321910064824253noreply@blogger.comtag:blogger.com,1999:blog-2158598821071141596.post-78607298541292419012016-01-22T00:15:00.001-08:002016-01-22T00:26:33.274-08:00How to see the status of timed activations <b>This article is for Magnolia 5.x versions.</b><br />
<h4>
The Need</h4>
It happens that when you decide to send a page for publication, you don't know what is going on until you see the green status dot, things could happen like you may forget you already sent that page for publication and you send it again.<br />
<br />
In order to keep track at all times of the tasks in progress there is a little development you could implement, and I will show you how.<br />
<br />
The aim is to add a new column to the pages app that will show the information like in the image below.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEha6JsI3SbkaGEqPZmskGst9w2JHS8uiAq0lPs6RrZy4Rau62-bV9sOEgc6n34MzkgpfqY7UtwO4vT1BgRNM1qSkeHtiFouAR5js8CMdbtQM87SVj_kkzyd406O8ii7Dh7hs2ftK4XnB7i4/s1600/Screen+Shot+2016-01-21+at+11.38.06.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="129" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEha6JsI3SbkaGEqPZmskGst9w2JHS8uiAq0lPs6RrZy4Rau62-bV9sOEgc6n34MzkgpfqY7UtwO4vT1BgRNM1qSkeHtiFouAR5js8CMdbtQM87SVj_kkzyd406O8ii7Dh7hs2ftK4XnB7i4/s400/Screen+Shot+2016-01-21+at+11.38.06.png" width="400" /></a></div>
<br />
As you can see there is a new column named "Publication Date", in that column we will see either the date of publication, or if is an immediate publication that someone is reviewing we will see "InProgress". We could add the other status like "rejected" but I will just display this two.<br />
<h4>
The Code</h4>
<div>
There is only one class that we need to create, that will extend from <span style="font-family: "monaco"; font-size: 11px;">AbstractColumnFormatter</span>, we will query the "tasks" workspace and display the status. Right now there is API method to get a task directly from the identifier of the node, that is why you will see 2 queries instead of one.</div>
<div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTMoh0GocWaF1wnqAqgLsavMAB4BgBTsk2LSm-H6bq4OTNfwlsOp2A2rSN1WMuD5IVwLFpx2mmbid7KoY5E0wo_DG12ktVHtgjDPQCxBF6HgfqY6R4SpuUriZo8H_fUpFC9cwYA5-182n-/s1600/Screen+Shot+2016-01-21+at+14.48.43.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="167" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTMoh0GocWaF1wnqAqgLsavMAB4BgBTsk2LSm-H6bq4OTNfwlsOp2A2rSN1WMuD5IVwLFpx2mmbid7KoY5E0wo_DG12ktVHtgjDPQCxBF6HgfqY6R4SpuUriZo8H_fUpFC9cwYA5-182n-/s400/Screen+Shot+2016-01-21+at+14.48.43.png" width="400" /></a></div>
<h4>
The Configuration</h4>
<div>
Once you have the class in your project the only thing left is to add it to the treeView of the pages app</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2wRuq1_mPSrKD5uud1rBsuvGiKFHXLuaURjtgs3KiPh-sxcEsVg8Gj0v-a87rBW3dVRHTKpSq70Ewwk2OA6PD5odQh5NEv0dQzoHb92ObbnzodsMO-tMxZ0DddcFH-lzNMn9NUu0Vr_dH/s1600/Screen+Shot+2016-01-21+at+15.15.07.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="208" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2wRuq1_mPSrKD5uud1rBsuvGiKFHXLuaURjtgs3KiPh-sxcEsVg8Gj0v-a87rBW3dVRHTKpSq70Ewwk2OA6PD5odQh5NEv0dQzoHb92ObbnzodsMO-tMxZ0DddcFH-lzNMn9NUu0Vr_dH/s400/Screen+Shot+2016-01-21+at+15.15.07.png" width="400" /></a></div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
The last thing to do is to add read permissions on tasks workspace to the users/groups you want to be able to use this functionality.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
The code is available <a href="https://git.magnolia-cms.com/scm/~tmiyar/pages-activation-status.git" target="_blank">here</a>.</div>
<h4>
To Improve</h4>
<div>
Right now it works just for the website workspace, it can be improved to read the workspace name in the configuration. </div>
<div>
It could be good the show if the activation is recursive.</div>
<div>
In case there are more than one activation scheduled for one item it should be displayed.</div>
<div>
Supper cool addition would be a button to stop an scheduled activation. </div>
<br /></div>
Teresa Miyarhttp://www.blogger.com/profile/04763321910064824253noreply@blogger.comtag:blogger.com,1999:blog-2158598821071141596.post-86408192542668471532014-02-18T00:30:00.000-08:002014-02-18T04:53:04.964-08:00Magnolia Blossom Module, STK Module or both?First a small introduction, quoting from our documentation pages:<br />
<br />
<a href="http://documentation.magnolia-cms.com/display/DOCS/Standard+Templating+Kit" target="_blank">Standard Templating Kit (STK)</a>
<br />
<blockquote class="tr_bq">
<span style="background-color: white; color: #333333; font-family: Arial, Helvetica, FreeSans, sans-serif; font-size: 13px; line-height: 20px;">"The Standard Templating Kit is a production-ready website framework that provides best practices and templates for common use cases. It includes ready-made functionality that can be extended for custom designs and content output. The STK adheres to best practices in CSS, semantic web and HTML, including HTML 5 video and forms support. It also provides a mobile variation which makes your STK-based sites available to mobile devices."</span></blockquote>
<a href="http://documentation.magnolia-cms.com/display/DOCS/Blossom+module" target="_blank">
Blossom Module</a><br />
<blockquote class="tr_bq">
<span style="background-color: white; color: #333333; font-family: Arial, Helvetica, FreeSans, sans-serif; font-size: 13px; line-height: 20px;">"The Blossom module is the Spring integration for Magnolia CMS. Blossom enables you to create editing components that display a high level of dynamic behavior. These components can be used by editors to create truly interactive web pages. For example, the Spring Framework application stack makes it easier to develop integrations with business systems, so that they fetch the information that you want to present in your pages. As Blossom is built on the Spring Web MVC, familiarity with this framework will ensure a smooth experience working with the module."</span></blockquote>
I could be using all the templates, components and functionality provided by STK and create new ones within a Blossom module.<br />
<br />
Let's see how:<br />
<br />
We need to create our project, I always go for creating a webapp project with our <a href="http://wiki.magnolia-cms.com/display/WIKI/Module+QuickStart" target="_blank">maven archetype</a>. Once we have the webapp project we use the archetype to create a <a href="http://documentation.magnolia-cms.com/display/DOCS/Getting+started+with+Blossom" target="_blank">blossom module</a>.<br />
In order to make all the STK functionalities available in our Blossom module we need to modify the file <span style="font-family: Courier New, Courier, monospace; font-size: x-small;">blossom-servlet.xml</span> that has been generated by the maven archetype in the resources folder of our new Blossom module and add the STK renderer context attributes as shown below.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjF65AkVIxGsYSZw-InqeoWiCS4Srh51Qta55FKCOqSnMLnkd3edhjAlPX_C5NJ2rurc4hK1Npuc7htfrsAKa4KTLFklTk5P6j5dWUHY4FztvT00W0JhmPFBIu-RAFnJ8-1sJHk6c_BrLCb/s1600/Screen+Shot+2014-02-17+at+15.29.22.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjF65AkVIxGsYSZw-InqeoWiCS4Srh51Qta55FKCOqSnMLnkd3edhjAlPX_C5NJ2rurc4hK1Npuc7htfrsAKa4KTLFklTk5P6j5dWUHY4FztvT00W0JhmPFBIu-RAFnJ8-1sJHk6c_BrLCb/s1600/Screen+Shot+2014-02-17+at+15.29.22.png" height="251" width="400" /></a></div>
<br />
<br />
This configuration will allow us to access to the SKT functionality from our template scripts.<br />
Now, how we access to that functionality from the templates model/controller?<br />
We can access to them using one of our API methods, see:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIGb2Mug3dn-Y-asW8PW6ojy47N0Nkz-StPej8SqvSx7KXPRspD07KIN4lnUICR8v806VYfPQ1XdRa8VXY72X8fARHj-wWlllCMSLAFaqpEI2xOweYsvA0XYPoZlvWSyQr3ObDdOHRxBsp/s1600/Screen+Shot+2014-02-18+at+09.21.46.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIGb2Mug3dn-Y-asW8PW6ojy47N0Nkz-StPej8SqvSx7KXPRspD07KIN4lnUICR8v806VYfPQ1XdRa8VXY72X8fARHj-wWlllCMSLAFaqpEI2xOweYsvA0XYPoZlvWSyQr3ObDdOHRxBsp/s1600/Screen+Shot+2014-02-18+at+09.21.46.png" /></a></div>
<br />
This would be the easiest way to access to STK functionality as all the components loaded by Guice are already available.<br />
<br />
See how you reference a Blossom component within a STK template<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBGEYmQTaEyHyxJfzlAplB_Cxw6o88QxsCVaTRFwxfrWchlvIjsu-MDQqIbd68caT2SaruTs2TTwx_jEyJqdcVvn9z07kfrB2e5wIRSt0lh7fm6YCRmAxWldgF0nE1mEBXRDDJX9Ahadli/s1600/Screen+Shot+2014-02-18+at+09.27.39.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBGEYmQTaEyHyxJfzlAplB_Cxw6o88QxsCVaTRFwxfrWchlvIjsu-MDQqIbd68caT2SaruTs2TTwx_jEyJqdcVvn9z07kfrB2e5wIRSt0lh7fm6YCRmAxWldgF0nE1mEBXRDDJX9Ahadli/s1600/Screen+Shot+2014-02-18+at+09.27.39.png" height="316" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
See how this component is defined within the Blossom custom module:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgovPCarVfPrBy_l_HRX5tJgBdoYxfDRepQmXHkccM0QCXHk-zvzoCkFaQlgroGiHUTWJ88WGUnSv_TrCsyMKQrwH3TazzHbCEUZclfBQR0NAzsqfnH2DUZBh_PLcrMRcHyuRuZB9HtoKI9/s1600/Screen+Shot+2014-02-18+at+09.29.12.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgovPCarVfPrBy_l_HRX5tJgBdoYxfDRepQmXHkccM0QCXHk-zvzoCkFaQlgroGiHUTWJ88WGUnSv_TrCsyMKQrwH3TazzHbCEUZclfBQR0NAzsqfnH2DUZBh_PLcrMRcHyuRuZB9HtoKI9/s1600/Screen+Shot+2014-02-18+at+09.29.12.png" height="297" width="400" /></a></div>
<br />Teresa Miyarhttp://www.blogger.com/profile/04763321910064824253noreply@blogger.comtag:blogger.com,1999:blog-2158598821071141596.post-77335058151094854512013-10-18T01:29:00.000-07:002013-10-18T01:29:34.344-07:00JCR search text ignoring umlauts, accents<h2>
Problem.</h2>
<div>
Default Magnolia installation demo-project site, comes with a search box on the top right corner</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgy3ILlvpSIZO3O5t8nzWu_MyAwVpAShENUoXSHTLttS6PdkfOKdQHOBT1_YLHT9C4LjOCIkCS9-Oe2feelmChvdFbkhyphenhyphen4NTlemUJ03Uor0mmu1x1WCQ3oppnazORUocLmVOxlRCkDNT2F2/s1600/Screen+Shot+2013-10-18+at+09.47.32.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="37" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgy3ILlvpSIZO3O5t8nzWu_MyAwVpAShENUoXSHTLttS6PdkfOKdQHOBT1_YLHT9C4LjOCIkCS9-Oe2feelmChvdFbkhyphenhyphen4NTlemUJ03Uor0mmu1x1WCQ3oppnazORUocLmVOxlRCkDNT2F2/s400/Screen+Shot+2013-10-18+at+09.47.32.png" width="400" /></a></div>
<div>
<br /></div>
<div>
If you try to search for something like <span style="font-family: Courier New, Courier, monospace;">résumé</span> you would expect to get:</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg11xqd9LhXTRBhJ-7BU1PfG6BcbhLo4EaSeEy1ytVcGA2uqBBBBTIYP0nUCpguty0PkG9DamKQ-nz65ssf5_n5oid5HWHRrGPGv6ITxsfhlQOLC7CbDJjP6D1dEQ3_cpb4kOeGxqfph0Sv/s1600/Screen+Shot+2013-10-18+at+09.49.48.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="75" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg11xqd9LhXTRBhJ-7BU1PfG6BcbhLo4EaSeEy1ytVcGA2uqBBBBTIYP0nUCpguty0PkG9DamKQ-nz65ssf5_n5oid5HWHRrGPGv6ITxsfhlQOLC7CbDJjP6D1dEQ3_cpb4kOeGxqfph0Sv/s320/Screen+Shot+2013-10-18+at+09.49.48.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
But instead you get:</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLRzbEWJtmDW6Fr_p-67jnCYed75Owv9Ru9fx3eh-DVZxMkoKTvStxULFKtOLXCO6f7pATnlW38Waj-ukPnlxYbqXVPnbYPlIdCdo_PoI-tyCcvlYDftI5Wyck5oznoC3jg_7mI0byaxKJ/s1600/Screen+Shot+2013-10-18+at+10.09.11.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="91" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLRzbEWJtmDW6Fr_p-67jnCYed75Owv9Ru9fx3eh-DVZxMkoKTvStxULFKtOLXCO6f7pATnlW38Waj-ukPnlxYbqXVPnbYPlIdCdo_PoI-tyCcvlYDftI5Wyck5oznoC3jg_7mI0byaxKJ/s320/Screen+Shot+2013-10-18+at+10.09.11.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<br />
<h2>
Solution.</h2>
<h3>
1. Configure tomcat encoding.</h3>
<br />
In order to be able to look for text with special characters, first thing you need to do is configure tomcat encoding. The default Tomcat encoding is ISO-8859-1, this encoding does not support some special characters, that means when rendering the text the accents, umlauts won't be displayed.<br />
<br />
This link explains how to add the UTF-8 encoding into your Tomcat configuration:<br />
<a href="http://struts.apache.org/release/2.0.x/docs/how-to-support-utf-8-uriencoding-with-tomcat.html" style="font-family: Helvetica;">http://struts.apache.org/release/2.0.x/docs/how-to-support-utf-8-uriencoding-with-tomcat.html</a><br />
<br />
<h3>
2. Create a new analyzer (optional).</h3>
<br />
You would probably like that when people makes a spelling mistake by missing the accents for example, that they get the results nevertheless, search for "camion" should return results with either "camion" or "camión".<br />
<br />
Have a look at the following link, it explains step by step how to do it: <a href="http://docs.jboss.org/exojcr/1.12.13-GA/developer/en-US/html/ch-jcr-query-usecases.html#JCR.IgnoreAccentSymbols">http://docs.jboss.org/exojcr/1.12.13-GA/developer/en-US/html/ch-jcr-query-usecases.html#JCR.IgnoreAccentSymbols</a><br />
<br />
<br />
<br />
<br />
<br />
<br />Teresa Miyarhttp://www.blogger.com/profile/04763321910064824253noreply@blogger.comtag:blogger.com,1999:blog-2158598821071141596.post-64831210065067399632013-01-16T01:41:00.000-08:002013-01-16T01:41:34.477-08:00Importing tags with GroovyIn this post I use a Groovy script to import custom tags into Magnolia CMS. The script saves the tags in the Data module. You can use them to categorize pages, images and documents.<br />
<br />
Clients often ask me how to import an existing collection of tags into Magnolia CMS. Local governments use tags such as <span style="font-family: inherit;"><i>taxes</i>, <i>transportation</i> or <i>schools</i></span> to categorize their content. Travel websites use geographical terms like <span style="font-family: inherit;"><i>london</i>, <i>paris</i> and <i>bangkok</i></span> to tag places to visit. Such vocabularies grow large over time. Creating them from scratch is a lot of work.<br />
<br />
I wrote a Groovy script that imports tags from XML. The example below finds any <a href="http://www.flickr.com/services/api/flickr.tags.getRelated.html">Flickr tags that are related</a> to the given tag <span style="font-family: inherit;"><i>london</i></span>. You can customize the script to import your own tags. I am not sure why I had not used Groovy much before. Once I tried it I loved it! You will too when you see how simple it is to implement a tag importer.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpPVi9CKY4GoYqfpDaYQbhXVFH2oWt8j63bM9fCk_X1eqGoUiA7zidKhY8gbsXTjUf2PUgGLYLk6rcq8kiTQMEfE5DW4wP-lq18WHzyBgYnAlnDGWQDGbXp5FBfuSfsmfFhig63Y52LRk/s1600/flickr-tags.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgpPVi9CKY4GoYqfpDaYQbhXVFH2oWt8j63bM9fCk_X1eqGoUiA7zidKhY8gbsXTjUf2PUgGLYLk6rcq8kiTQMEfE5DW4wP-lq18WHzyBgYnAlnDGWQDGbXp5FBfuSfsmfFhig63Y52LRk/s1600/flickr-tags.png" /></a></div>
<br />
<br />
Requirements<br />
<ul>
<li>Magnolia CMS 4.5.7 with <a href="http://documentation.magnolia-cms.com/modules/groovy.html">Groovy module</a>. It's in the add-ons folder in the bundle.</li>
<li>Flickr API key. Register for an <a href="http://www.flickr.com/services/api/misc.api_keys.html" target="_blank">API key</a> in order to call the Flickr API.</li>
</ul>
<br />
Creating a Groovy script<br />
<ol>
<li>In Magnolia CMS, go to <b>Tools > Scripts</b> and add a new Groovy script.</li>
<li>Paste the following code into your script.</li>
<li>Check the <b>Is a script?</b> box.</li>
</ol>
<div class="separator" style="clear: both; text-align: left;">
</div>
<pre class="brush:groovy;toolbar:false;gutter:false">tag = 'london'
hm = MgnlContext.getHierarchyManager('data')
// Create a folder in the Data module
flickrTags = ContentUtil.createPath(hm, "/categorization/${tag}", new ItemType("dataFolder"))
hm.save()
itemType = new ItemType('category')
// Connect to Flickr REST API
URL url = new URL("http://api.flickr.com/services/rest/?method=flickr.tags.getRelated&api_key=5f26c50b6e67110809b117da0f2bb94f&tag=${tag}")
HttpURLConnection conn = (HttpURLConnection) url.openConnection()
conn.setRequestMethod("GET")
conn.setRequestProperty("Accept", "application/xml")
if (conn.getResponseCode() != 200) {
throw new RuntimeException("Failed : HTTP error code : "
+ conn.getResponseCode())
}
// Parse the response XML
rsp = new XmlSlurper().parse(conn.getInputStream())
rsp.tags.tag.each{
content = ContentUtil.createPath(hm, "/categorization/${tag}/${it.text()}", itemType)
content.name = it.text()
}
flickrTags.save()
conn.disconnect()
return "done"
</pre>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
Here's what the script does:<br />
<ol>
<li>Create a new folder in the Data module with the parent tag name.</li>
<li>Connect to the Flickr REST API and request all tags related to the parent tag.</li>
<li>Parse the resulting XML.</li>
<li>Create categories under the previously created data folder.</li>
</ol>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEimy9W6IFaTeWA3v72m5AdHrsbQMMKoFaR7OJf71Fmjfrs72v5MLBo-3xIlNiw_QkgsidWN65iJxkkLzf-7mmevFwpJn7KaIT-cNpIPQN2XD92ZRjEEjprxq1f9EMWatTXzDq9CYdlT2gs/s1600/tags-in-magnolia.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEimy9W6IFaTeWA3v72m5AdHrsbQMMKoFaR7OJf71Fmjfrs72v5MLBo-3xIlNiw_QkgsidWN65iJxkkLzf-7mmevFwpJn7KaIT-cNpIPQN2XD92ZRjEEjprxq1f9EMWatTXzDq9CYdlT2gs/s1600/tags-in-magnolia.png" /></a></div>
<br />
Now the tags are available for categorizing content.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQu7_ckFNdY5blwbx0WzLaiuyis8PhseCWUvVKCustlUAhzKhxT9mx2W3hRN7K0KvSb3qcsjX_j8GrVgnDNCGNEbWb-UfoLvrn70F8gy29jZJYFnjTrHzMDswIwhip5i0RHVT1VbB9_VE/s1600/categories-on-page.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQu7_ckFNdY5blwbx0WzLaiuyis8PhseCWUvVKCustlUAhzKhxT9mx2W3hRN7K0KvSb3qcsjX_j8GrVgnDNCGNEbWb-UfoLvrn70F8gy29jZJYFnjTrHzMDswIwhip5i0RHVT1VbB9_VE/s1600/categories-on-page.png" /></a></div>
<br />
<br />
Bear in mind is that there is no error handling in this code to keep it short. This solution has been developed and tested with Magnolia 4.5.7Teresa Miyarhttp://www.blogger.com/profile/04763321910064824253noreply@blogger.comtag:blogger.com,1999:blog-2158598821071141596.post-10720996969884881092012-07-20T00:59:00.000-07:002012-07-20T00:59:00.259-07:00Localized 404 errorsHave you ever needed custom 404 error pages for your Magnolia CMS website? Such a requirement is <a href="http://documentation.magnolia-cms.com/administration/error-page.html">quite straightforward to implement</a> if the site uses only one language. It gets more complicated when multiple languages are used, each with its own domain such as <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">mysite.de</span> and <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">mysite.fr</span>. In such a case <a href="http://documentation.magnolia-cms.com/technical-guide/virtual-uri-mapping.html#Hostbased">host based virtual URI mapping</a> is useful as explained in <a href="http://wiki.magnolia-cms.com/display/WIKI/How+to+setup+a+custom+404+handler">How to set up a custom 404 handler</a>.<br />
<br />
But what if the languages are not host based? What if you don't have <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">.de</span> or <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">.fr</span> domains? The language of the page can be built into the URL such as <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">mysite.com/de/page.html</span> or <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">mysite.com/fr/page.html</span>. In this post I show how to direct the request to a custom 404 error page using a custom filter.<br />
<br />
<b>Tip!</b> I started by investigating if I could configure the exceptions in the web.xml file. I thought the <a href="http://docs.oracle.com/cd/E11035_01/wls100/webapp/web_xml.html#wp1017571"><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">exception-type</span></a> tag might be the answer. Turns out it doesn't work within the Magnolia CMS filter chain. In case you had the same idea I will save you the trouble of going that route.<br />
<br />
The solution is to write a custom filter to handle HTTP errors. The custom filter will extend the default <a href="http://git.magnolia-cms.com/gitweb/?p=magnolia_main.pub.git;a=blob;f=magnolia-core/src/main/java/info/magnolia/cms/filters/AggregatorFilter.java"><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">AggregatorFilter</span></a>. Replace the default class with your custom class in the CMS filter chain in <b>Configuration ></b> <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">/server/filters/cms/aggregator</span>.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6vhZrK_tPaPESXHlM23YFKkqusTwJLQLBkWLUT8KfXr-s2Iz9KoyKVIumfC5zsdXS84gJJf4-uipsDvTmfX3eh4SYK9CJYTjgLKOcW87H63DjmV1flKXRTYX-IwPz3YFvwz34Qt6rFuM/s1600/custom-aggregator-filter.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6vhZrK_tPaPESXHlM23YFKkqusTwJLQLBkWLUT8KfXr-s2Iz9KoyKVIumfC5zsdXS84gJJf4-uipsDvTmfX3eh4SYK9CJYTjgLKOcW87H63DjmV1flKXRTYX-IwPz3YFvwz34Qt6rFuM/s1600/custom-aggregator-filter.png" /></a></div>
<br />
The class code should look like this:<br />
<pre style="background-attachment: initial; background-clip: initial; background-color: #f0f0f0; background-image: initial; background-origin: initial; background-position: initial initial; background-repeat: initial initial; border-bottom-color: rgb(204, 204, 204); border-bottom-style: dashed; border-bottom-width: 1px; border-left-color: rgb(204, 204, 204); border-left-style: dashed; border-left-width: 1px; border-right-color: rgb(204, 204, 204); border-right-style: dashed; border-right-width: 1px; border-top-color: rgb(204, 204, 204); border-top-style: dashed; border-top-width: 1px; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow-x: auto; overflow-y: auto; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;">public class CustomAggregatorFilter extends AggregatorFilter {
private static final Logger log = LoggerFactory.getLogger(CustomAggregatorFilter.class);
@Override
public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException{
boolean success;
try {
success = collect();
}
catch (AccessDeniedException e) {
// don't throw further, simply return error and break filter chain
log.debug(e.getMessage(), e);
if (!response.isCommitted()) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
}
// stop the chain
return;
}
catch (RepositoryException e) {
log.error(e.getMessage(), e);
throw new ServletException(e.getMessage(), e);
}
if (!success) {
log.debug("Resource not found, redirecting request for [{}] to 404 URI", request.getRequestURI());
if (!response.isCommitted()) {
</code><code style="word-wrap: normal;"><span class="Apple-style-span" style="color: red;">if(MgnlContext.getAggregationState().getLocale().getLanguage().equals("en")){
RequestDispatchUtil.dispatch("permanent:/en.html", request, response);
} else {
RequestDispatchUtil.dispatch("permanent:/de.html", request, response);
}</span></code><code style="color: black; word-wrap: normal;">
}
else {
log.info("Unable to redirect to 404 page, response is already committed. URI was {}", request.getRequestURI());
}
// stop the chain
return;
}
chain.doFilter(request, response);
}
}</code></pre>
<br />
Note that the filter only reacts to 404 errors. It checks the language from the aggregation state. This is the same language as used in the URL, such as <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">mysite.com/de/page.html</span>. Based on the language the filter makes a permanent redirect to the German error page that is part of the website. On the error page you can display whatever content you like, send parameters, log statements and so on.<br />
<br />
The filter creates a permanent (301) link. This is the SEO friendly way. Search engines will index the pages while you can still keep track of the errors by setting them in the response the way you decide.<br />
<br />
This is just a demonstration. It could be improved by adding the errors and URLs in the filter configuration, for example.<br />
<br />
Want to learn more? <a href="http://www.magnolia-cms.com/community/magnolia-conference/program/register.html">Register now</a> for Magnolia Conference 2012!Teresa Miyarhttp://www.blogger.com/profile/04763321910064824253noreply@blogger.comtag:blogger.com,1999:blog-2158598821071141596.post-38356476954183121242012-06-19T23:37:00.001-07:002012-06-19T23:37:40.858-07:00How To Export Website Content To ExcelHere is the thing, I have a big report on my site that grew quite big, now is not easy to review it without scrolling up and down.<br />
<br />
Well, how about exporting just that content to an Excel file where we can get a graphic of the data, or maybe sort it by specific column or do complex calculations? Here is a very fast way to produce such file.<br />
<br />
First we need to give a distinct id to our table, and any other data we will need to produce the output we desire, i.e. title of the report, on our template file:<br />
<br />
<code>
<h1 id="toExcelReportTitle">Sample Report Title</h1>
<table id="toExcelTable"</table>
....
</code>
<br />
<br />
Then we will need a button that when on clicked it will send a request to a servlet that will do the export.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgeS6Wf6XkYg-53CtWVor638Vp1RTIYdCghDIO7w5fEtzMY8FCQoD63mRklcR1rHtGmG3teekxt8mjMz6L2pr4y4vdZoMDVAmngOJR-bsXnSPO7N7i-hNQPasj21NYzwYCyj1lMhyNh5YhU/s1600/Screen+shot+2012-05-31+at+9.51.31+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgeS6Wf6XkYg-53CtWVor638Vp1RTIYdCghDIO7w5fEtzMY8FCQoD63mRklcR1rHtGmG3teekxt8mjMz6L2pr4y4vdZoMDVAmngOJR-bsXnSPO7N7i-hNQPasj21NYzwYCyj1lMhyNh5YhU/s1600/Screen+shot+2012-05-31+at+9.51.31+AM.png" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
The JQuery is used to load the HTML of the report table and the data of the report title into the parameters in the form. This way when we click on the save button, it will send this parameters to the servlet we have previously created.<br />
<br />
The servlet will the read this parameters and write them to the response. This response will automatically be rendered into Excel form by setting the Content Type to <code>application/vnd.ms-excel.</code><br />
<code><br /></code><br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGvunwxeNQHJaNlQUo78fOevSJQTJgAJvX7QG3eYxerrroswOujp9Ofhha0DSOYfHDTC9vXizLx2h60cXn3tRMr6gttkheQId8tg8KuO_s2nvfmKxKK_ZB6XuLWtHvuNNeQtbinN8oF27C/s1600/Screen+shot+2012-05-31+at+10.06.54+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGvunwxeNQHJaNlQUo78fOevSJQTJgAJvX7QG3eYxerrroswOujp9Ofhha0DSOYfHDTC9vXizLx2h60cXn3tRMr6gttkheQId8tg8KuO_s2nvfmKxKK_ZB6XuLWtHvuNNeQtbinN8oF27C/s1600/Screen+shot+2012-05-31+at+10.06.54+AM.png" /></a></div>
<br />
And this is a sample of the resulting excel file:<br />
<code><br /></code><br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDolmxOJ6KHR4QrqaWORtqob0HcJFuUfEuOR35K9rE_-UBui_eO7JVJBnOjaNNCUymgZb9yYLi7159CwQZyuaGGcPF2jo4yvK9a6n_bObWvu3mxxITI1NoAc4nac4OGWruVjgxaHcveUNc/s1600/Screen+shot+2012-05-31+at+10.13.29+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="195" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDolmxOJ6KHR4QrqaWORtqob0HcJFuUfEuOR35K9rE_-UBui_eO7JVJBnOjaNNCUymgZb9yYLi7159CwQZyuaGGcPF2jo4yvK9a6n_bObWvu3mxxITI1NoAc4nac4OGWruVjgxaHcveUNc/s320/Screen+shot+2012-05-31+at+10.13.29+AM.png" width="320" /></a></div>
<code><br /></code><br />
By exporting the content this way, we don't need to request the table page again, we will just get the already rendered data and sent it to the servlet that will send the data back as an excel file.Teresa Miyarhttp://www.blogger.com/profile/04763321910064824253noreply@blogger.comtag:blogger.com,1999:blog-2158598821071141596.post-91076232212980883162012-06-12T01:27:00.000-07:002012-06-12T01:27:08.755-07:00Queries LoggingWant to know which queries are run and how log did they take individually?<br />
What about enabling the log for jackrabbit? If we enalbe it, it will tell us something like this.
<br />
<br />
<code>
org.apache.jackrabbit.core.query.QueryImpl: executed in 0.00 s. (select * from mgnl:user where jcr:path = '/system/superuser' or jcr:path like '/system/%/superuser')</code><br />
<br />
There are two ways, easiest and does not need restart of the server:
<br />
You have to go to menu Tools, logging and there set the value for the query class to DEBUG<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLktvuiipy6MjWUos6MHfdvspBuSXyr-z9pt-zcVtlLABN7B-qBLaCUl6u4zLoBkWV-1yQZ7Kq0Qxxp9PCKd4-pqpjPD7yUqJjB1q_1G-D_dKfd5pJlcLeTMi2x1QNhhCBe-RGlnlg8JbV/s1600/logging.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLktvuiipy6MjWUos6MHfdvspBuSXyr-z9pt-zcVtlLABN7B-qBLaCUl6u4zLoBkWV-1yQZ7Kq0Qxxp9PCKd4-pqpjPD7yUqJjB1q_1G-D_dKfd5pJlcLeTMi2x1QNhhCBe-RGlnlg8JbV/s1600/logging.jpg" /></a></div>
<br />
Second way is to extend the log4j.xml file so it logs the queries, good thing about this is that you can make it write the queries in a new file, also, the change wont be lost when you restart the server, but, you will need to restart the server in order to see the new logging output.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPqY6xRN9b_WcGpehPFysi4ZG2AKg8ZdUcPiCk8u2oi2O9yufKLmvcYmfHqko5jitABcTdnulgitvtzD7wriwl6f5lr5tZCwFmV1XnSo4tWtFOitnQX_tHQnfilXB8FGrTp18EaI38yNsg/s1600/Screen+shot+2012-06-08+at+2.54.39+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgPqY6xRN9b_WcGpehPFysi4ZG2AKg8ZdUcPiCk8u2oi2O9yufKLmvcYmfHqko5jitABcTdnulgitvtzD7wriwl6f5lr5tZCwFmV1XnSo4tWtFOitnQX_tHQnfilXB8FGrTp18EaI38yNsg/s1600/Screen+shot+2012-06-08+at+2.54.39+PM.png" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
Note that my log4j.xml is in <code>webapp/WEB-INF/config/default/log4j.xml</code>Teresa Miyarhttp://www.blogger.com/profile/04763321910064824253noreply@blogger.comtag:blogger.com,1999:blog-2158598821071141596.post-29961972773825128392012-05-30T05:25:00.000-07:002012-05-30T05:27:44.030-07:00How to set session timeout of Tomcat within EclipseWell, this is not related directly with Magnolia but in any case I find it useful.<br />
<br />
Usually to set the session timeout, you go to your Tomcat installation, conf folder, open web.xml file and change the default value:<br />
<br />
<code>
<session-config>
<session-timeout>30</session-timeout>
</session-config> </code><br />
<br />
<code></code>Within Eclipse, you will have to go to the workspace folder, servers, then inside the tomcat-config folder you will find the web.xml used by Eclipse.<br />
<br />
Hope this helps.Teresa Miyarhttp://www.blogger.com/profile/04763321910064824253noreply@blogger.comtag:blogger.com,1999:blog-2158598821071141596.post-56623989147820106912012-05-29T03:00:00.000-07:002012-05-29T03:00:31.634-07:00Creating a PDF from Website ContentI recently had a requirement to convert website content to a PDF file. First I thought it was going to be a lot of coding. I would have to make every page component look the way it should on a printable document and generate a nice PDF file from the whole page.<br />
<br />
I did not want to spend too much time. There must be an easier way. How else could the Save as PDF button in Safari do such a good job printing a page without knowing anything about my Web application?<br />
<div>
<br /></div>
<div>
Searching for a shortcut I came across the <a href="http://code.google.com/p/flying-saucer/">Flying Saucer Project</a>:<br />
<blockquote class="tr_bq">
Flying Saucer is an XML/CSS renderer, which means it takes XML files as input, applies formatting and styling using CSS, and generates a rendered representation of that XML as output. The output may go to a PDF file. -- <a href="http://flyingsaucerproject.github.com/flyingsaucer/r8/guide/users-guide-R8.html">Flying Saucer User's Guide</a></blockquote>
Very impressive work! Especially considering how much it can accomplish with very little code and a print style sheet.</div>
<div>
<br /></div>
<div>
Using the Flying Saucer library in a servlet to access the website anonymously worked OK. But when I tried authenticated access Magnolia CMS resources, everything stopped working. Fortunately, this was easily resolved with the <a href="http://flyingsaucerproject.github.com/flyingsaucer/r8/guide/users-guide-R8.html#xil_17">callback</a> class that the <a href="http://code.google.com/p/flying-saucer/">Flying Saucer Project</a> offers. I must confess that I overlooked it first. The <a href="http://flyingsaucerproject.github.com/flyingsaucer/r8/guide/users-guide-R8.html#xil_17">callback</a> class not only allows you to decide how to access the website resources but also lets you decide how to render the images and styles.</div>
<div>
<br /></div>
<div>
The rest of the work such as which elements to display, where to display them, the page size and page breaks are specified in the style sheet using the <a href="http://www.w3.org/TR/css3-page/">W3C Paged Media</a> syntax.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiD0edV0xXUkRV8I6ad1BudBwS3UfRsELPaoN8lyfMzQoukaeDDb5GAoNQtUtwU0jPn2NWonxlcn8Cwbr21d5MBnl_q2mO9E1j2A9SDpKEpGBHEcYNjom5AO4srreLrx9Ixl6GMlfYa9_vd/s1600/Screen+shot+2012-05-29+at+11.55.24+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="280" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiD0edV0xXUkRV8I6ad1BudBwS3UfRsELPaoN8lyfMzQoukaeDDb5GAoNQtUtwU0jPn2NWonxlcn8Cwbr21d5MBnl_q2mO9E1j2A9SDpKEpGBHEcYNjom5AO4srreLrx9Ixl6GMlfYa9_vd/s400/Screen+shot+2012-05-29+at+11.55.24+AM.png" width="400" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<a href="https://docs.google.com/open?id=0B5pxinEhBPNKcGNIamxvRDYxcWs">See linked document</a> on how would the document look like by printing the About section of the demo project with all pages in the same PDF document.<br />
<br />
For this example, I used the standard print.css that comes with the demo-project theme-pop, adding just a style for the images so it does not make a page break in the middle of the image:<br />
<br />
<code>
img {
page-break-inside: avoid;
}
</code>
<br />
<br />
Many thanks to the <a href="http://code.google.com/p/flying-saucer/">Flying Saucer Project</a> developers for providing this amazing utility!</div>Teresa Miyarhttp://www.blogger.com/profile/04763321910064824253noreply@blogger.comtag:blogger.com,1999:blog-2158598821071141596.post-59142433268020278402011-12-19T04:35:00.000-08:002012-06-19T01:15:03.758-07:00Rendering Paragraphs with AJAX, Part III: Autocomplete exampleIn Rendering Paragraphs with AJAX <a href="http://tmiyar.blogspot.com/2011/10/rendering-paragraphs-with-ajax-part-i.html">Part 1</a> and <a href="http://tmiyar.blogspot.com/2011/11/rendering-paragraphs-with-ajax-part-ii.html">Part 2</a> I showed basic examples of using AJAX in paragraphs in Magnolia CMS. In this last post, I demonstrate how to create a predictive search for contacts and how to render a contact's details when it is selected.<br />
<br />
The end result looks like this, hoping it motivates you to read the whole post :-). It is a new paragraph that contains a text box. You type a contact name in the box and the paragraph starts listing matching contacts from the directory.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTkFGoiOWLWfwGDfTQCaoWbmpaaG5SJ1b02tirQc6bnsgG1UcF7L-bvj8VzmFF9IqDtwo0GOzb9PafRwwVuKA90TFs7aMeJaiOQbao9rZCWYVAGSZeq5fDmml0qdnYG_1XqsDgcwPGS18p/s1600/Screen+shot+2011-12-19+at+12.50.54+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTkFGoiOWLWfwGDfTQCaoWbmpaaG5SJ1b02tirQc6bnsgG1UcF7L-bvj8VzmFF9IqDtwo0GOzb9PafRwwVuKA90TFs7aMeJaiOQbao9rZCWYVAGSZeq5fDmml0qdnYG_1XqsDgcwPGS18p/s1600/Screen+shot+2011-12-19+at+12.50.54+PM.png" /></a></div>
<br />
<br />
When you select one of the contacts, a contact paragraph is rendered, displaying full contact information from the directory.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbUBJvF6tq37OFSq-wUuubf8Zv-EI-oKDjRXN58byJ0BjpDtGVG4S-izsGtr-r2iLgNC-8sQLAaZ5LUQmySMMvCXp5R6cNB7v2U4ESkJX8XgCZHA66SoZtpoFHWRo2x9Loxf8XFaIwgjsC/s1600/Screen+shot+2011-12-19+at+12.51.52+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbUBJvF6tq37OFSq-wUuubf8Zv-EI-oKDjRXN58byJ0BjpDtGVG4S-izsGtr-r2iLgNC-8sQLAaZ5LUQmySMMvCXp5R6cNB7v2U4ESkJX8XgCZHA66SoZtpoFHWRo2x9Loxf8XFaIwgjsC/s1600/Screen+shot+2011-12-19+at+12.51.52+PM.png" /></a></div>
<br />
What is needed to achieve this? A paragraph, a model class and of course a nice <a href="http://jqueryui.com/demos/autocomplete/">jQuery Autocomplete widget</a>. The last bit is what makes this example so simple to create.<br />
<br />
<h2>
Paragraph script</h2>
The paragraph script looks like this.<br />
<pre style="background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> [#assign cms=JspTaglibs["cms-taglib"]]
[#-- TODO This link and script MUST BE MOVED to a file into the theme --]
<link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery-ui.css"
rel="stylesheet" type="text/css"/>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.5/jquery.min.js">
</script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js">
</script>
[#-- End TODO --]
[#assign contentLink= mgnl.createLink(content)!]
<script>
jQuery(document).ready(function() {
jQuery("input#autocomplete").autocomplete({
source: function( request, response ) {
jQuery.getJSON('${contentLink}', {term : request.term}, function(data) {
response(data);
});
},
select: function( event, ui ) {
jQuery.get('${contentLink}', { uuid: ui.item.uuid},function(data) {
jQuery('#ajax').html(data);
});
return false;
}
});
});
</script>
[@cms.editBar /]
<div>
Find Contacts: <input id="autocomplete" />
<div id="ajax">
</div>
</div>
</code></pre>
Note that the links to jQuery UI cannot be placed directly in the script as I have done here. The code above is just a demonstration so you can see what files are needed. See <a href="http://jqueryui.com/demos/autocomplete/">jQuery Autocomplete documentation</a> for details.<br />
<br />
The paragraph script renders an input field that has an ID <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">autocomplete</span>. The source for this field is a JSON object provided by the paragraph model via an AJAX call. The JSON object provides contact names starting with the string you type in the input field. When a contact is selected, a second AJAX call asks the paragraph model to render a contact paragraph using the UUID of the selected item.<br />
<br />
<h2>
Autocomplete paragraph model class</h2>
The paragraph model class for autocomplete looks like this.<br />
<pre style="background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> public String execute() {
String uuid = MgnlContext.getParameter("uuid");
//first it will run the autocomplete
if(StringUtils.isEmpty(uuid)) {
return renderAutocomplete();
} else {
return renderParagraph(uuid);
}
}
</code></pre>
There are two method calls in the <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">execute</span> method:<br />
<ul>
<li><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">renderAutocomplete</span> returns a JSON object of contact names and UUIDs.</li>
<li><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">renderParagraph</span> renders a contact paragraph for the selected UUID.</li>
</ul>
<br />
<h3>
renderAutocomplete method</h3>
The <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">renderAutocomplete</span> method looks for the contact information by <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">givenName</span> and then constructs a JSON object with two fields, <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">label</span> and <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">uuid</span>, and sends it to the response.<br />
<pre style="background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> private String renderAutocomplete() {
String autocomplete = "";
String repositoryId = "data";
String dataType = "Contact";
String propertyName = "givenName";
String term = MgnlContext.getParameter("term");
if(StringUtils.isNotEmpty(term)){
String statement = "//element(*, "+ dataType +")[jcr:like(fn:upper-case(@"
+ propertyName + "),'%" + term.toUpperCase() + "%')]";
Collection<Content> nodes = QueryUtil.query(repositoryId, statement , "xpath");
autocomplete +="[";
Iterator<Content> it = nodes.iterator();
while (it.hasNext()) {
Content node = it.next();
autocomplete += "{ \"label\":\"" + NodeDataUtil.getString(node, propertyName)
+ " " + NodeDataUtil.getString(node, "familyName") + "\", \"uuid\": \""
+ node.getUUID() +"\"}";
if(it.hasNext()) {
autocomplete += ",";
}
}
autocomplete +="]";
try {
MgnlContext.getWebContext().getResponse().getOutputStream().print(autocomplete);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return RenderingModel.SKIP_RENDERING;
}
return super.execute();
}
</code></pre>
<br />
<h3>
renderParagraph method</h3>
The <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">renderParagraph</span> method is bit more complex because what we want to render is of type Contact. Instead of using the <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">renderParagraph</span> method from <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">MagnoliaTemplatingUtilities</span>, we have to render it using <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">ParagraphRendererManager</span>.<br />
<pre style="background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> private String renderParagraph(String uuid) {
String repositoryId = "data";
String paragraphName = "contact";
Content ajaxContent = ContentUtil.getContentByUUID(repositoryId, uuid);
if(ajaxContent != null) {
try {
if (ajaxContent != null && ajaxContent.isNodeType(ItemType.CONTENTNODE.getSystemName())) {
MagnoliaTemplatingUtilities.getInstance().renderParagraph(ajaxContent,
MgnlContext.getWebContext().getResponse().getWriter(), paragraphName);
} else if(ajaxContent != null && !ajaxContent.isNodeType(ItemType.CONTENTNODE.getSystemName())) {
//this bit of code is needed as content is not a contentnode, is a datatype from data repo.
Paragraph paragraph = ParagraphManager.getInstance().getParagraphDefinition(paragraphName);
ParagraphRenderer renderer = ParagraphRendererManager.getInstance().getRenderer(paragraph.getType());
renderer.render(ajaxContent, (Paragraph) paragraph, MgnlContext.getWebContext().getResponse().getWriter());
}
} catch (RenderException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return RenderingModel.SKIP_RENDERING;
}
</code></pre>
<br />
<h2>
Contact paragraph model class</h2>
For the Contact paragraph, I just used the <a href="http://svn.magnolia-cms.com/svn/community/modules/standard-templating-kit/tags/standard-templating-kit-1.4.5/magnolia-module-standard-templating-kit/src/main/java/info/magnolia/module/templatingkit/paragraphs/ContactModel.java">one provided by STK</a> and changed the model to get the content from the UUID provided.<br />
<pre style="background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> public String execute() {
String uuid = MgnlContext.getParameter("uuid");
if(StringUtils.isEmpty(uuid)) {
uuid = getContent().getNodeData("contact").getString();
}
HierarchyManager hm = MgnlContext.getHierarchyManager(DataModule.getRepository());
if (!StringUtils.isEmpty(uuid)) {
try {
contact = hm.getContentByUUID(uuid);
} catch (RepositoryException e) {
throw new RuntimeException(e);
}
}
return super.execute();
}
</code></pre>
<a href="http://svn.magnolia-cms.com/svn/community/sandbox/autocomplete/trunk/">Download the code for this example</a> from the Magnolia SVN. It is just a working sample to demonstrate this functionality and might be removed at some point so better grab it quick!Teresa Miyarhttp://www.blogger.com/profile/04763321910064824253noreply@blogger.comtag:blogger.com,1999:blog-2158598821071141596.post-63123779743366799332011-11-29T05:50:00.001-08:002011-11-30T07:51:42.552-08:00Rendering Paragraphs with AJAX, Part IIIn <a href="http://tmiyar.blogspot.com/2011/10/rendering-paragraphs-with-ajax-part-i.html">Rendering Paragraphs with AJAX Part 1</a>, I showed a basic example of an AJAX paragraph in Magnolia CMS. We loaded an paragraph via an AJAX call and rendered it on the page.<br />
<br />
Now we will see how to send parameters on the AJAX call and display changes by using the <a href="http://documentation.magnolia-cms.com/reference/templating.html">model class</a>.<br />
<div>
<br /></div>
<div>
For a use case, suppose you have to display a large amount of content. To make the content easier for the user to browse, you add pagination. You only want to update the paginated content when the user clicks a Previous or Next button.</div>
<div>
<br /></div>
<div>
You need a way to tell the server what page the user is currently on. The server can then provide the previous page or the next page. In order to do this you need a model class, a paragraph definition and a Freemarker template script (.ftl file).</div>
<div>
<br /></div>
<div>
<h2>
Template script</h2>
Add the following code to your Freemarker template script:</div>
<br />
<div>
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace; white-space: pre;"><div id="ajax"></span></div>
<pre><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">[#assign pageIndex = model.pageIndex]
<script>
function next() {
jQuery.get('${ctx.contextPath}${content.@handle}.html',
{index: '${pageIndex}'},
function(data) {
jQuery('#ajax').html(data);
});
}
</script>
<div>
Page: <span id="text">${pageIndex}</span>
</div>
<a href="javascript:next();">NEXT</a>
</div></span></pre>
The content paragraph to be rendered is inside the div element that has an ID <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">ajax</span>. The script assigns the value that comes from the model method call to a variable <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">pageIndex</span>. In the script, an AJAX call reloads the current paragraph, passing a new parameter <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">index</span> with the value we assigned earlier.<br />
<br />
In a nested div element, we write the current value of the variable on the page.<br />
<br />
On the last line, we create a NEXT link that the user can click. The click will execute the AJAX call, run the paragraph model, and return the new <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">pageIndex</span> value.<br />
<br />
<h2>
Model class</h2>
Now let's see what the model class looks like.<br />
<pre><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">public String execute() {
String index = MgnlContext.getParameter("index");
if(StringUtils.isNotEmpty(index)) {
pageIndex = Integer.parseInt(index) +1;
}
return super.execute();
}
public int getPageIndex() {
return pageIndex;
}</span>
</pre>
The class has two methods:<br />
<ul>
<li>Overridden execute method that reads the parameter we sent by clicking NEXT and adds 1 to <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">pageIndex</span>.</li>
<li>getPageIndex method to retrieve the value of the member variable <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">pageIndex</span>.</li>
</ul>
<br />
<ul>
</ul>
<div>
<h2>
Paragraph definition</h2>
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlACBnM4jm_nQxCG1zGkaZFK2KGw_1btz3sKbBLUcK6O8rX8dRp92CylMC5AraTDEOj1TjhtdpPKYDs_Vux0KKzl1cDx9zFlStRYUxm-0C5HXWuPggWHAEXk-Qek5fpFraSXVyAxRPLQO3/s1600/Screen+shot+2011-11-30+at+3.07.14+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlACBnM4jm_nQxCG1zGkaZFK2KGw_1btz3sKbBLUcK6O8rX8dRp92CylMC5AraTDEOj1TjhtdpPKYDs_Vux0KKzl1cDx9zFlStRYUxm-0C5HXWuPggWHAEXk-Qek5fpFraSXVyAxRPLQO3/s1600/Screen+shot+2011-11-30+at+3.07.14+PM.png" /></a></div>
<br /></div>
<br />
The rendered NEXT button looks like this on the page.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBJ7MfNNVtIjabB9mj3HdaPOGHbuT80B9EjoU8wGyUtEkojAk8iJADnjBX527kdKAYLN78qGLZKRYGtYlKt4d_LwmyoPybIkxuUMs9x2CrY2MwCN7qe1f98F-amnw8VuFWIvTZROrxJa9Y/s1600/Screen+shot+2011-11-30+at+2.04.38+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="33" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBJ7MfNNVtIjabB9mj3HdaPOGHbuT80B9EjoU8wGyUtEkojAk8iJADnjBX527kdKAYLN78qGLZKRYGtYlKt4d_LwmyoPybIkxuUMs9x2CrY2MwCN7qe1f98F-amnw8VuFWIvTZROrxJa9Y/s400/Screen+shot+2011-11-30+at+2.04.38+PM.png" width="57" /></a>
</div>Teresa Miyarhttp://www.blogger.com/profile/04763321910064824253noreply@blogger.comtag:blogger.com,1999:blog-2158598821071141596.post-56892904274403406652011-10-27T05:35:00.000-07:002013-01-09T02:35:57.310-08:00Rendering Paragraphs with AJAX, Part IEver wondered how to reload just some paragraphs on your page? Imagine you have pagination and you want to reload just the paginated paragraph when clicking previous/next? Is Magnolia CMS capable of this without complex coding? Yes, and on this post you will find some ways of achieving this.<br />
<br />
First thing to know is, since Magnolia CMS version 4.x paragraphs are autorenderable. What does this mean? It means you can copy the path of a paragraph, paste it in the address bar of your browser and you will see the paragraph rendered. Try the following URL from our demo site, it will render the teaser of the Article page.<br />
<a href="http://demopublic.magnolia-cms.com/demo-project/content/00.html">http://demopublic.magnolia-cms.com/demo-project/content/00.html</a><br />
<br />
What can you do with this? It means you don't need to create a servlet or anything to render a paragraph dynamically on a page. Just a call to the appropriate script will let you load any paragraph on our page.<br />
<br />
Here is a sample using JQuery. Add this code into a .ftl file. You also need to create a paragraph definition and add it to be used in a template.<br />
<pre><script type="text/javascript">
function loadArticleTeaser() {
jQuery.get('${contextPath}/demo-project/main/00.html',function(data) {
jQuery('#ajax').html(data);
});
}
</script><div>
<div id="ajax">
</div><br />
<a href="javascript:loadArticleTeaser()">Load Teaser via AJAX call </a><span class="Apple-style-span" style="font-family: 'DejaVu Sans Mono', courier, monospace; font-size: x-small;"><span class="Apple-style-span" style="line-height: 16px; white-space: nowrap;">
</span></span></div>
</pre>
The JQuery call <a href="http://www.blogger.com/(http://api.jquery.com/jQuery.get/)">(http://api.jquery.com/jQuery.get/)</a> will get the specified URL and, on success, will run the method that loads the result (paragraph) from the AJAX call into a div with ID "ajax". To run the whole thing you just need to click the "Load Teaser via AJAX call" link.<br />
<div>
<br /></div>
<div>
This is a basic sample of an AJAX paragraph. In the next post you will see how to send parameters and process them in the paragraph model class.</div>
Teresa Miyarhttp://www.blogger.com/profile/04763321910064824253noreply@blogger.comtag:blogger.com,1999:blog-2158598821071141596.post-83324991314700185422011-09-27T03:32:00.000-07:002011-09-28T00:07:11.788-07:00Tips & Tricks: Make activation comment mandatoryWith this tip, you can make everybody to write a comment when they send an activation request, though I cannot guarantee that comment would be meaningful!<br />
<br />
Step 1. Set property required=true in activation dialog.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTPXvcw7h6QCnd6rpTzexQqLgd4mCrlwTUdKCZDR7zlY2aASvDKloHjfeYgdi8xWI1EXhc6VLnlFDD2hxWpwpXMMx0K4PeIGOSFYzPznjIkcLxqe2SVMbP0uwhhOPIFqxjw6l-FbWX_sxt/s1600/Screen+shot+2011-09-27+at+11.50.16+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="294" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTPXvcw7h6QCnd6rpTzexQqLgd4mCrlwTUdKCZDR7zlY2aASvDKloHjfeYgdi8xWI1EXhc6VLnlFDD2hxWpwpXMMx0K4PeIGOSFYzPznjIkcLxqe2SVMbP0uwhhOPIFqxjw6l-FbWX_sxt/s320/Screen+shot+2011-09-27+at+11.50.16+AM.png" width="320" /></a></div>
<br />
<br />
Step 2. Add the following code to the saveOnClick property of the dialog.<br />
<br />
<code>
if(document.getElementById('comment').value != '') {window.close();opener.mgnl.workflow.WorkflowWebsiteTree.submitActivation($('mgnlFormMain'));} else {alert('Must write a comment for activation.');}</code>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEGSY0AJIlMHRgRRr9VU0PpfucqKG965pCve1UesOkoZXtlyvHhKJXwhMn58zcrfH61KKlYcgSVIkF5pZfNJ3JTCJZ8Tb1KIYDWK4qVzZv-CEUdEfK_6Ae1JvUL-5rILmxfnvZmWGkpKKS/s1600/Screen+shot+2011-09-27+at+11.52.12+AM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="214" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEGSY0AJIlMHRgRRr9VU0PpfucqKG965pCve1UesOkoZXtlyvHhKJXwhMn58zcrfH61KKlYcgSVIkF5pZfNJ3JTCJZ8Tb1KIYDWK4qVzZv-CEUdEfK_6Ae1JvUL-5rILmxfnvZmWGkpKKS/s400/Screen+shot+2011-09-27+at+11.52.12+AM.png" width="400" /></a></div>
Teresa Miyarhttp://www.blogger.com/profile/04763321910064824253noreply@blogger.comtag:blogger.com,1999:blog-2158598821071141596.post-23572998460738433742011-08-29T02:34:00.000-07:002011-11-24T02:24:54.860-08:00Creating site navigation from data nodes<div>
This solution has been implemented for <a href="http://svn.magnolia-cms.com/view/forge/magnolia-shop/">Magnolia Shop Module</a> (contributed by <a href="http://www.fastforward.ch/">fastforward</a>). </div>
<div>
<br /></div>
<div>
The aim is to create menu navigation based on nodes in the data repository. The navigation should be displayed on an existing site as section navigation. This way, when you navigate to the section, product categories defined in data repository will be used as items in the vertical menu. </div>
<div>
<br /></div>
<div>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgEmP6NJV-tOviv00pDVaKBpwVcRlj-8285c94itMZ1XQJWodmJLz5HBjmym8aQ3A0yFHi_g05ihxofpsOTj2balOJbIyZ9ArtxGJCyr_WQA3Smfif1445O6n48BbTGCOhayiXu3fqE_vE1/s1600/Screen+shot+2011-08-31+at+8.53.21+AM.png"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5646910917784275490" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgEmP6NJV-tOviv00pDVaKBpwVcRlj-8285c94itMZ1XQJWodmJLz5HBjmym8aQ3A0yFHi_g05ihxofpsOTj2balOJbIyZ9ArtxGJCyr_WQA3Smfif1445O6n48BbTGCOhayiXu3fqE_vE1/s400/Screen+shot+2011-08-31+at+8.53.21+AM.png" style="cursor: hand; cursor: pointer; display: block; height: 222px; margin: 0px auto 10px; text-align: center; width: 400px;" /></a>Note that the breadcrumb is also updated with the product categories.</div>
<div>
<br /></div>
<div>
Assuming we know how the default navigation works in STK, we do the following:</div>
<div>
<ol>
<li>Create customized navigation classes. One class will provide the navigation model and the second will define the navigation item that provides us with the item title and URL.
<br />
<br /><div>
See <a href="http://svn.magnolia-cms.com/svn/forge/magnolia-shop/tags/magnolia-shop-product-categories-in-data-26102011/src/main/java/info/magnolia/module/shop/navigation/ProductCategoryNavigationModel.java?view=log"><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">ProductCategoryNavigationModel</span></a> and <a href="http://svn.magnolia-cms.com/svn/forge/magnolia-shop/tags/magnolia-shop-product-categories-in-data-26102011/src/main/java/info/magnolia/module/shop/navigation/ProductCategoryNavigationItem.java?view=log"><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">ProductCategoryNavigationItem</span></a> as examples. The <a href="http://svn.magnolia-cms.com/svn/forge/magnolia-shop/tags/magnolia-shop-product-categories-in-data-26102011/src/main/java/info/magnolia/module/shop/navigation/ProductCategoryNavigationModel.java?view=log"><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">getItems</span></a> method returns the relevant nodes from the <span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">data</span> repository. Constructor parameters for both classes depend on what you need for your implementation. In this particular case the parameter is specific to the shop, the shop name, so that we can create a correct link from the data repository.</div>
</li>
</ol>
<ol start="2">
<li>Create a template model class that instantiates the customized navigation and assigns the model class to our template. Doing it this way, you keep the default navigation for all site content that is not part of the shop.
<br />
<br /><div>
See <a href="http://svn.magnolia-cms.com/svn/forge/magnolia-shop/tags/magnolia-shop-product-categories-in-data-26102011/src/main/java/info/magnolia/module/shop/templates/ShopSingletonParagraphTemplateModel.java?view=log"><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">ShopSingletonParagraphTemplateModel</span></a> where the navigation is instantiated.</div>
<br /><div>
<pre>public ProductCategoryNavigationModel getProductCategoryNavigation() {return new ProductCategoryNavigationModel(getCurrentShop());}</pre>
</div>
<div>
As we are also customizing the breadcrumb, look at the getBreadcrumb method as well where items from the data repository are appended.</div>
<br /><div>
Once we have the model class ready, we add it to our template configuration</div>
<br /><div>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRU4lCoux9vmSx_tJrpNrgXgm4aXPg5ugClE4FVgeqSxedl2O2SvAx16tanKUUCZz6EF5-Ifwkgk3t8gxgQZRNC9gyVuiA5V8SWmEUEvA0XmHeoDcAMi6nHPCTqLr0auFJQqAsBgZWdxf3/s1600/Screen+shot+2011-08-31+at+9.08.55+AM.png"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5646913833128287426" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRU4lCoux9vmSx_tJrpNrgXgm4aXPg5ugClE4FVgeqSxedl2O2SvAx16tanKUUCZz6EF5-Ifwkgk3t8gxgQZRNC9gyVuiA5V8SWmEUEvA0XmHeoDcAMi6nHPCTqLr0auFJQqAsBgZWdxf3/s360/Screen+shot+2011-08-31+at+9.08.55+AM.png" style="cursor: hand; cursor: pointer; display: block; height: 32px; margin: 0px auto 10px; text-align: center; width: 360px;" /></a></div>
</li>
</ol>
<ol start="3">
<li>Freemarker templates.
<br />
<br /><div>
We optionally need a new <a href="http://svn.magnolia-cms.com/svn/forge/magnolia-shop/tags/magnolia-shop-product-categories-in-data-26102011/src/main/resources/shop/templates/global/verticalNavigation.ftl?view=log"><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">verticalNavigation.ftl</span></a>.</div>
<div>
The only change in vertical navigation is to call our custom navigation that we created with a new name:</div>
<div>
<pre>[@renderNavigation navigation=model.productCategoryNavigation /]</pre>
</div>
<div>
And we set the template file in the site configuration of our custom template definition:</div>
<br /><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgesUoU1vPikebglmiCSUcwpx9sh9zwdIBCxwa_N0ti-oRbQrboSAvJO2sIIF6aSJRAB-aiweD1A1GWImIQDRyRc4tG8r_m-pIgyoEexvInmJaAePIGlDh9WlHGvjiLmAtEr9-AXHQzjLIP/s1600/Screen+shot+2011-08-31+at+9.15.40+AM.png"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5646915557511205922" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgesUoU1vPikebglmiCSUcwpx9sh9zwdIBCxwa_N0ti-oRbQrboSAvJO2sIIF6aSJRAB-aiweD1A1GWImIQDRyRc4tG8r_m-pIgyoEexvInmJaAePIGlDh9WlHGvjiLmAtEr9-AXHQzjLIP/s400/Screen+shot+2011-08-31+at+9.15.40+AM.png" style="cursor: hand; cursor: pointer; display: block; height: 128px; margin: 0px auto 10px; text-align: center; width: 362px;" /></a></li>
</ol>
</div>Teresa Miyarhttp://www.blogger.com/profile/04763321910064824253noreply@blogger.comtag:blogger.com,1999:blog-2158598821071141596.post-29189723325904819232011-07-12T06:16:00.001-07:002011-09-27T03:57:39.515-07:00Adding extra styles files to an existing themeThe question raised when creating a new module: how can I add the styles files of my module to the pop theme?<br />
<br />
This guide shows how to add the installation task and how to bootstrap the content, but if you don't want to code anything, at the end of the blog there is an explanation on how to do it directly on the author instance:<br />
<br />
<i><b>Step 1</b></i>, Create the new styles file i.e. myextrastyles.css<br />
<br />
<b><i>Step 2</i></b>, Put the file in the module resources folder following STK theme structure, src/main/resources and the path shown in the following screenshoot:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrE0pYBTLxgWrYUT-irOsNvcEORb0ELuCFtxebqpwvlY9JHyc5CYijZcmLgtMAPSTZnK65I_Vr5VRxE2UXyUGvo_Hkp9CM7Q7QORihBSnyW1Es1adDaKezrSl6Ah_kHe2DtoPb_cBlWafQ/s1600/Screen+shot+2011-07-12+at+10.38.48+AM.png"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5628455102023986370" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrE0pYBTLxgWrYUT-irOsNvcEORb0ELuCFtxebqpwvlY9JHyc5CYijZcmLgtMAPSTZnK65I_Vr5VRxE2UXyUGvo_Hkp9CM7Q7QORihBSnyW1Es1adDaKezrSl6Ah_kHe2DtoPb_cBlWafQ/s320/Screen+shot+2011-07-12+at+10.38.48+AM.png" style="cursor: hand; cursor: pointer; display: block; height: 120px; margin: 0px auto 10px; text-align: center; width: 300px;" /></a><br />
<br />
<br />
<b><i>Step 3</i></b>, Add an installation task on your module versionhandler.<br />
<br />
<pre>protected List getExtraInstallTasks(InstallContext installContext) {
final List installTasks = new ArrayList();
installTasks.addAll(super.getExtraInstallTasks(installContext));
<span class="Apple-style-span" style="color: red;">installTasks.add(new InstallResourcesTask("/templating-kit/themes/pop/css/mystyles.css", </span>
<span class="Apple-style-span" style="color: red;"> "processedCss", STKResourceModel.class.getName()));</span></pre>
<br />
<br />
<b><i>Step 4</i></b>, Update the theme configuration. In order for the theme to find the new style file, the file has to be configured in the theme configuration.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsDHrkFNAZesZXAMmy2iKXkqW_PhwZIE1JIW9PgKYa8trADauIToi9hzK63_q8DAFxmzdZUYsfBurw7F49a_2WOysB7c34IO5PbhqWqjSkUnenq4kY2q7VerTgYzp7n7BICI9A7jb7jxUv/s1600/Screen+shot+2011-07-13+at+9.07.37+AM.png"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5628733321563317490" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsDHrkFNAZesZXAMmy2iKXkqW_PhwZIE1JIW9PgKYa8trADauIToi9hzK63_q8DAFxmzdZUYsfBurw7F49a_2WOysB7c34IO5PbhqWqjSkUnenq4kY2q7VerTgYzp7n7BICI9A7jb7jxUv/s400/Screen+shot+2011-07-13+at+9.07.37+AM.png" style="cursor: hand; cursor: pointer; display: block; height: 142px; margin: 0px auto 10px; text-align: center; width: 400px;" /></a><br />
<br />
<br />
<b><i>Step 5</i></b>, Export the configuration of our file shown in Step 4. Once exported you can place the file (config.modules.standard-templating-kit.config.themes.pop.cssFiles.myextrastyles.xml)<br />
in the bootstrap folder of your module.<br />
<br />
<span style="font-weight: bold;">NOTE</span>: All this can be achieved without any code, you just need to set the file in the theme configuration (Step 4) and upload this new styles file into the resources workspace.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzECZwdkAmxz2bGziYHGoMfu4vDOuyoOGEmdb8FGxRdsgGI5Eo2O9TcEKtz4jVSWjazKzu1Q52ls89ysuK-xi804QwuH3Nic-wBkFreQJyxXyVw2kTnoYGDKMDMGTmuyq_RcW6qJmss-A9/s1600/Screen+shot+2011-07-13+at+9.05.23+AM.png"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5628729724150777682" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzECZwdkAmxz2bGziYHGoMfu4vDOuyoOGEmdb8FGxRdsgGI5Eo2O9TcEKtz4jVSWjazKzu1Q52ls89ysuK-xi804QwuH3Nic-wBkFreQJyxXyVw2kTnoYGDKMDMGTmuyq_RcW6qJmss-A9/s400/Screen+shot+2011-07-13+at+9.05.23+AM.png" style="cursor: hand; cursor: pointer; display: block; height: 307px; margin: 0px auto 10px; text-align: center; width: 333px;" /></a>Teresa Miyarhttp://www.blogger.com/profile/04763321910064824253noreply@blogger.comtag:blogger.com,1999:blog-2158598821071141596.post-26573715810886424312011-03-10T06:39:00.000-08:002011-12-20T00:15:27.891-08:00Adding Flickr images to Magnolia CMSThis is a step-by-step guide on how to create your custom DAM and plug it into Magnolia CMS. Will allow you to add images from external services like Flickr, as well as adding images from other servers using their url (It comes with a forge module!)<br />
<br />
This is a sample handle for external image/video urls, very basic as it does not add the imaging support for now.<br />
<br />
* Requirements - Standard Templating Kit Module<br />
<br />
1. The Asset<br />
<br />
We need to create our own Asset class that will implement the interface <a href="http://nexus.magnolia-cms.com/content/sites/magnolia.public.sites/modules/magnolia-module-standard-templating-kit/1.4.2/standard-templating-kit-parent/magnolia-module-standard-templating-kit/apidocs/info/magnolia/module/templatingkit/dam/Asset.html">Asset</a><br />
<br />
If we want to have exact same behavior as in STK/ETK we will need to find a way to store 'meta data' (caption, description) of our assets, we could use the data module for that matter.<br />
<br />
In this sample we wont store any metadata for the images, nor use the imaging support.<br />
<br />
That leaves us with just one method to implement 'getLink' and this will be the value stored when we open the dialog to select an image.<br />
<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">public String getLink() throws DAMException {</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">return link;</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">}</span><br />
<br />
Creation of link is on the class constructor, as easy as this<br />
<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">link = nodeData.getString();</span><br />
<br />
<br />
<br />
2. The handler.<br />
<br />
Should extend class <a href="http://nexus.magnolia-cms.com/content/sites/magnolia.public.sites/modules/magnolia-module-standard-templating-kit/1.4.2/standard-templating-kit-parent/magnolia-module-standard-templating-kit/apidocs/info/magnolia/module/templatingkit/dam/handlers/AbstractHandler.html">AbstractHandler</a> . We will extend from AbstractHandler that has the methods to read the configuration, that leaves us with two methods to implement as needed: getAsset and getAssetByKey. In this case we implement getAsset to return an instance of our Asset.<br />
<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">final String name = getNodeDataName(node, nodeDataPrefix); </span><br />
<br />
Important thing to note here is that we need to get the name of the nodeData that has the url of our image. When you set an image using the dam controls, you name your node as i.e. 'myimage' and on saving you will get a nodeData with the name you gave plus the name of the dam control used. To make it simpler a call to the method <a href="http://nexus.magnolia-cms.com/content/sites/magnolia.public.sites/modules/magnolia-module-standard-templating-kit/1.4.2/standard-templating-kit-parent/magnolia-module-standard-templating-kit/apidocs/info/magnolia/module/templatingkit/dam/handlers/AbstractHandler.html#getNodeDataName(info.magnolia.cms.core.Content,%20java.lang.String)">getNodeDataName</a> will give you the real name with which your node has been saved. In this sample it will contain the exact url of our image, no need of further processing.<br />
<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">final NodeData nodeData = node.getNodeData(name);</span><br />
<br />
This line above gets the node data, now with the right name.<br />
<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">return new UrlAsset(nodeData);</span><br />
<br />
Here is where we return our asset.<br />
<br />
3. The controls (configuration).<br />
<br />
Need to add the configuration for this external dam. For that we create a new node under our site configuration. There we put the handler we are going to use and the control/s in which we will store the image information. See image:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgp3iyTOEfVKRVAfWbR9CzVqdhWrjJcA_v5DDcWl-5w_diiytyokxg2CuDReLg9PYD2cvOw78mGy7cC30ZpO9gxza-LCsXzRh_G6uf7HmeOjlLwWHfievlDVoc5seWAnsHpshSQnuIVF9th/s1600/Screen+shot+2011-03-18+at+2.46.40+PM.png"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5585416665670385474" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgp3iyTOEfVKRVAfWbR9CzVqdhWrjJcA_v5DDcWl-5w_diiytyokxg2CuDReLg9PYD2cvOw78mGy7cC30ZpO9gxza-LCsXzRh_G6uf7HmeOjlLwWHfievlDVoc5seWAnsHpshSQnuIVF9th/s200/Screen+shot+2011-03-18+at+2.46.40+PM.png" style="cursor: hand; cursor: pointer; height: 105px; width: 200px;" /></a><br />
<br />
<br />
And to finish this post there is a module in Magnolia forge <a href="http://svn.magnolia-cms.com/svn/forge/magnolia-module-urldam/trunk/">magnolia-module-urldam</a> with the code to add an external dam handler.Teresa Miyarhttp://www.blogger.com/profile/04763321910064824253noreply@blogger.comtag:blogger.com,1999:blog-2158598821071141596.post-72648880089630817932010-02-25T05:38:00.000-08:002011-03-22T06:51:07.284-07:00From HTML to XML and beyond!What happens if you need to deliver same content but in different format? Sure you don't want to duplicate content. Well Magnolia CMS provides a feature that with little work lets you deliver same content in any format you need, it could be xml, or even html but optimized for newsletters.<br /><br />How to do this? You need to select a template definition and add sub templates like in the image below.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGVdHjLiddnR9rdE6J9RCPgmqNSuNkpaeeWfPBzsggzlUbwlNzJgVDEyhpk3yig22O4rHm1BTz9t38IOZuK3E7drJ2JaTnXYrJPElsOQLz47G-jCiKAbRSoaeTz32yiRwv2Sp2ZK0yAa3o/s1600-h/Picture+15.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 118px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGVdHjLiddnR9rdE6J9RCPgmqNSuNkpaeeWfPBzsggzlUbwlNzJgVDEyhpk3yig22O4rHm1BTz9t38IOZuK3E7drJ2JaTnXYrJPElsOQLz47G-jCiKAbRSoaeTz32yiRwv2Sp2ZK0yAa3o/s320/Picture+15.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5442178256222234802" /></a><br /><br />This sample is using Magnolia CMS version 3.6 (feature also available for 4.x) with just one sub template defined that will render as xml when I request the page as url.xml and rendered as html when requested as url.html.<br /><br />Because at the moment, sub templates do not work for paragraphs, in the template file that generates the xml content we will have to iterate through the paragraphs.<br /><br />In my sample I used jsp, so I iterated main collection within a scriplet, by first getting the active page and then its children:<br /><br /><span style="font-style:italic;"><span class="Apple-style-span" style="font-size: small;">Content actPage = Resource.getActivePage(request);<br />if(actPage.hasContent("mainColumnParagraphs")) {<br /> //some code<br />}</span></span><br /><br />When using freemarker there is no need to use java:<br /><br /><span style="font-style:italic;"><span class="Apple-style-span" style="font-size: small;">[#list content.main?children as para]<br /> [#assign paragraphContent=para]<br /> [#if para.metaData.template='mgnlTextBox']<br /> [#include "textBox.ftl" ]<br /> [/#if]<br />[/#list]</span></span><br /><br />One of my use cases was to generate a newsletter template for the web and a simpler one that would be supported by the mail clients.Teresa Miyarhttp://www.blogger.com/profile/04763321910064824253noreply@blogger.comtag:blogger.com,1999:blog-2158598821071141596.post-88023752087096353272010-02-25T01:02:00.000-08:002011-03-22T06:53:29.778-07:00Auto generate paragraphsBy default Magnolia CMS STK comes with a mechanism to auto generate paragraphs in main and extras areas. This is very useful when you have the need of a paragraph to be present in all pages using some template. That paragraph could be modifiable or not and could be initialized with some default parameters or not.<br /><br />If we have a look at the class SingletonParagraphTemplateModel, we can see that in the execute method has two method calls, one to generate a single paragraph in the main area (see stkSiteMap template) and the other one to generate any number of paragraphs in the extras area.<br /><br />See below the template configuration for the main area auto generated sitemap paragraph<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoZLikc28DcYQcVQVnFBm7gKfGuhI7hsrwtbUORm0ATK8Un85wDrgdHvR_RTw9hBHO_T4Uzm8bueINVOpJtcOpKDn1jG7undotfUKe5r-xvRECaywy4HKlk_iULdBS4rcaF38HUz-XarP5/s1600-h/Picture+13.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 131px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoZLikc28DcYQcVQVnFBm7gKfGuhI7hsrwtbUORm0ATK8Un85wDrgdHvR_RTw9hBHO_T4Uzm8bueINVOpJtcOpKDn1jG7undotfUKe5r-xvRECaywy4HKlk_iULdBS4rcaF38HUz-XarP5/s320/Picture+13.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5442119377014379314" /></a><br /><br />Nice thing is that you can extend this class or implement your own to generate any paragraph in any area of the page.<br /><br />Below is the template configuration for the extras area auto generated paragraphs.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdygSkj7qk0TpP4krz94GYfgHxDNzr-a-F_XIylNDnDZvixG3Gl-7tXS5J3c5MKNV9V_TLgO_lC0Q4dCbhgr7e1jw8ZuGaIOA67CiRDfpRVE4l8q2Qg3phloaeZKBB6QjnvT_KevZB3DWk/s1600-h/Picture+14.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 131px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjdygSkj7qk0TpP4krz94GYfgHxDNzr-a-F_XIylNDnDZvixG3Gl-7tXS5J3c5MKNV9V_TLgO_lC0Q4dCbhgr7e1jw8ZuGaIOA67CiRDfpRVE4l8q2Qg3phloaeZKBB6QjnvT_KevZB3DWk/s320/Picture+14.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5442121246225198850" /></a>Teresa Miyarhttp://www.blogger.com/profile/04763321910064824253noreply@blogger.com