Import contacts using Google’s API

Maybe the most important thing about this post is how to use HTTP based APIs in a simple way -using a wrapper for cURL PHP extension. Most of the examples available on the Web are kind of out-of-date, based on the idea that there is no a better abstraction layer.

Requirements / Example environment configuration.

This example was originally built for CakePHP, but as you can see, it can be used or migrated to several frameworks – the key is to use the PEAR component. Note: I believe that this example will make most of the example out there crappy – and I hope it, so other developers could use it.

 
// PEAR Package HTTP_Request2
// http://pear.php.net/manual/en/package.http.http-request2.php
require_once('HTTP' . DS . 'Request2.php');
 
class GoogleImportComponent extends Object {
 
	private $username = null;
	private $password = null;
	private $token = null;
 
	/**
	 * Authenticate user credential against provider services
	 *
	 * @param string $username User's login
	 * @param string $password User's account password
	 * @see http://code.google.com/apis/contacts/docs/3.0/developers_guide_protocol.html
	 */
	function authenticate($username, $password) {
		$this->username = $username;
		$this->password = $password;
 
		$hrc = new HTTP_Request2('https://www.google.com/accounts/ClientLogin', HTTP_Request2::METHOD_POST);
		$hrc->setAdapter('HTTP_Request2_Adapter_Curl');
		$hrc->setConfig('ssl_verify_peer', false);
		$hrc->addPostParameter('accountType', 'HOSTED_OR_GOOGLE');
		$hrc->addPostParameter('service', 'cp');
		$hrc->addPostParameter('source', 'MY_APP_ID');
		$hrc->addPostParameter('Email', $this->username);
		$hrc->addPostParameter('Passwd', $this->password);
 
		$response = $hrc->send();
		$body = $response->getBody();
		if ($response->getStatus() == 200) {
			$this->token = substr($body, strpos($body, 'Auth=') + 5);
		}
		else {
			throw new Exception($response->getReasonPhrase(), $response->getStatus());
		}
	}
 
	/**
	 * Return the contacts' email list
	 *
	 * @return array
	 * @see http://code.google.com/apis/contacts/docs/3.0/developers_guide_protocol.html
	 */
	function get() {
		$addresses = array();
 
		$hrc = new HTTP_Request2("http://www.google.com/m8/feeds/contacts/{$this->username}/full", HTTP_Request2::METHOD_GET);
		$hrc->setHeader('Authorization', "GoogleLogin auth={$this->token}");
		$response = $hrc->send();
		if ($response->getStatus() == 200) {
			$xml = simplexml_load_string(utf8_encode($response->getBody()));
			$xmladd =  $xml->xpath("//gd:email/@address");
			foreach ($xmladd as $value) {
				$data = get_object_vars($value);
				$addrs = $data['@attributes']['address'];
				if ($addrs != $this->username) {
					$addresses[] = $addrs;
				}
			}
		}
		else {
			throw new Exception($response->getReasonPhrase(), $response->getStatus());
		}
 
		return $addresses;
	}
}

That’s it. It is not a huge amount of code with HTTP protocol related instructions – like the ones you can get for free. It relies on HTTP_Request2 PEAR module, that even if it’s on beta, works like a charm.


Search Portlet

Few months ago I was developing a project on top of JBoss Portal. Unfortunately, JBoss Portal 2.7.x is a nice Portlet Container but a crappy CMS/Search engine -even when it is supported on Apache’s JackRabbit. However, I was forced to develop a search portlet. It wasn’t easy, and you will probably find that it is not a perfect solution. However I really hope you find this Search Portlet really useful.

SearchPortlet.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package portal.portlets;
 
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
 
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.GenericPortlet;
import javax.portlet.PortletConfig;
import javax.portlet.PortletException;
import javax.portlet.PortletRequestDispatcher;
import javax.portlet.PortletSecurityException;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.UnavailableException;
import javax.portlet.WindowState;
 
import org.apache.log4j.Logger;
import org.jboss.portal.cms.CMS;
import org.jboss.portal.cms.CMSException;
import org.jboss.portal.cms.Command;
import org.jboss.portal.cms.model.File;
import org.jboss.portal.core.cms.ui.CMSPortlet;
import org.jboss.portal.search.FederatedQuery;
import org.jboss.portal.search.QueryConversionException;
import org.jboss.portal.search.federation.SearchFederation;
import org.jboss.portal.search.impl.jcr.JCRQuery;
import org.jboss.portal.search.impl.jcr.JCRQueryConverter;
 
public class SearchPortlet extends CMSPortlet {
 
  private static Logger log = Logger.getLogger(SearchPortlet.class);
  private SearchFederation SearchService;
  private CMS CMSService;
 
  public void init(PortletConfig config) throws PortletException {
    super.init(config);
  }
 
  public void destroy() {
    super.destroy();
  }
 
  /*
   * (non-Javadoc)
   *
   * @see javax.portlet.Portlet#init()
   */
  @Override
  public void init() throws PortletException {
    this.SearchService = (SearchFederation) getPortletContext().getAttribute("SearchFederationService");
    this.CMSService = (CMS) getPortletContext().getAttribute("CMS");
 
    if (this.SearchService == null) {
      throw new PortletException("Unable to start search service");
    }
 
    if (this.CMSService == null) {
      throw new PortletException("CMS services not available");
    }
 
    super.init();
  }
 
  /*
   * (non-Javadoc)
   *
   * @see javax.portlet.GenericPortlet#doView(javax.portlet.RenderRequest,
   * javax.portlet.RenderResponse)
   */
  @Override
  protected void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException, UnavailableException {
 
    String portalInstance = request.getWindowID().substring(0, request.getWindowID().indexOf('/', 1));
    PortletRequestDispatcher prd = null;
    String operation = (String) request.getParameter("op");
 
    if (operation != null && operation.equals("search")) {
      String queryString = (String) request.getParameter("query");
      if (queryString != null && queryString.length() > 0) {
        request.setAttribute("files", this.getFiles(queryString, portalInstance));
        prd = this.getPortletContext().getRequestDispatcher("/WEB-INF/jsp/results.jsp");
      } else {
        prd = getPortletContext().getRequestDispatcher("/WEB-INF/jsp/search.jsp");
      }
      prd.include(request, response);
    } else if (operation != null && operation.equals("display")) {
      super.doView(request, response);
    } else {
      prd = getPortletContext().getRequestDispatcher("/WEB-INF/jsp/search.jsp");
      prd.include(request, response);
    }
  }
 
  /*
   * (non-Javadoc)
   *
   * @see javax.portlet.Portlet#processAction(javax.portlet.ActionRequest,
   * javax.portlet.ActionResponse)
   */
  @Override
  public void processAction(ActionRequest request, ActionResponse response) throws PortletException, PortletSecurityException, IOException {
    String profile;
 
    String operation = (String) request.getParameter("op");
    if (operation != null && operation.length() > 0) {
      if (operation.equals("ask")) {
        response.setWindowState(WindowState.NORMAL);
      } else {
        response.setWindowState(WindowState.MAXIMIZED);
      }
      response.setRenderParameter("op", operation);
    } else {
      response.setRenderParameter("op", "ask");
      response.setWindowState(WindowState.NORMAL);
    }
 
    if (operation.equals("search")) {
      String queryString = (String) request.getParameter("query");
      if (queryString != null && queryString.length() > 0) {
        response.setRenderParameter("query", queryString);
      }
    }
  }
 
  private List getFiles(String queryString, String portal) {
    List files = null;
    List filteredFiles = new ArrayList();
 
    if (queryString != null && queryString.length() > 0) {
      FederatedQuery query = new FederatedQuery(queryString);
      JCRQueryConverter converter = new JCRQueryConverter();
 
      try {
        Command searchCommand = CMSService.getCommandFactory().createSearchCommand((JCRQuery) converter.convert(query));
        files = (List) CMSService.execute(searchCommand);
 
        // You can filter files if needed by reviewing its path under the CMS
        // structure, by name, or any other criteria that fits your needs.
 
      } catch (CMSException e) {
        log.error("CMS Error", e);
      } catch (QueryConversionException e) {
        log.warn("Conversion Error", e);
      }
    }
 
    return files;
  }
}

I used two JSP pages to support this portlet.

  • search.jsp – Search form – the standard portlet view
  • results.jsp – Results list page – appears with the portlet MAXIMIZED and displays CMS files that matches the criteria

search.jsp – (WP-Syntax does not support JSP)

1
2
3
4
5
6
7
8
<h2>Search</h2>
<form action="<%=contactActionURL%>" method="POST">
  <fieldset style="border: none;">
    <input name="op" type="hidden" value="search" />
    <input name="query" type="text" />
    <input type="submit" value="Search" />
  </fieldset>
</form>

results.jsp – (WP-Syntax does not support JSP)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<%@ page import="org.jboss.portal.cms.model.File"%>
<%@ page import="java.util.List"%>
<%@ page language="java" extends="org.jboss.portal.core.servlet.jsp.PortalJsp"%>
<%@ taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet"%>
<%
  List<File> files = (List<File>) request.getAttribute("files");
  if (files != null && files.size() > 0) { %>
<h1>RESULTADOS DE B&Uacute;QUEDA</h1>
  <%
    for (int i = 0; i < files.size(); i++) {
        File file = files.get(i);
  %>
  <h2><%= file.getName() %></h2>
  <p><% if (file.getDescription() != null && file.getDescription().length() > 0) { %><%= file.getDescription() %><% } %><a href="<portlet:actionURL><portlet:param name="op" value="display" /><portlet:param name="file" value="<%= file.getBasePath() %>" /></portlet:actionURL>">Ver m&aacute;s...</a></p>
  <hr />
  <%
    }
  %>
<% } else { %>
<h1>NO HAY RESULTADOS</h1>
<% } %>
<p align="center"><a href="<portlet:actionURL><portlet:param name="op" value="ask" /></portlet:actionURL>">Volver</a></p>

Include the following lines on your jboss-portlet.xml file.

1
2
3
4
5
6
7
8
9
10
11
<service>
  <service-name>CMS</service-name>
  <service-class>org.jboss.portal.cms.CMS</service-class>
  <service-ref>:service=CMS</service-ref>
</service>
 
<service>
  <service-name>SearchFederationService</service-name>
  <service-class>org.jboss.portal.search.federation.SearchFederation</service-class>
  <service-ref>:service=SearchFederationService</service-ref>
</service>

And finally add the following lines on your portlet.xml file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<portlet>
  <description>DESC</description>
  <portlet-name>SearchPortlet</portlet-name>
  <display-name>NAME</display-name>
  <portlet-class>portal.portlets.SearchPortlet</portlet-class>
  <supports>
    <mime-type>text/html</mime-type>
    <portlet-mode>VIEW</portlet-mode>
  </supports>
  <resource-bundle>Resource</resource-bundle>
  <portlet-info>
    <title>SEARCH</title>
  </portlet-info>
  <portlet-preferences>
    <preference>
      <name>indexpage</name>
      <value>/default/index.html</value>
      </preference>
    <preference>
      <name>setBrowserTitle</name>
      <value>false</value>
    </preference>
  </portlet-preferences>
</portlet>
 
<filter>
  <filter-name>JBoss Portlet Filter</filter-name>
  <filter-class>org.jboss.portlet.filter.JBossPortletFilter</filter-class>
  <lifecycle>ACTION_PHASE</lifecycle>
  <lifecycle>RENDER_PHASE</lifecycle>
</filter>
 
<filter-mapping>
  <filter-name>JBoss Portlet Filter</filter-name>
  <portlet-name>SearchPortlet</portlet-name>
</filter-mapping>

When criticism becomes feedback

YARGHHH!It is curious how all of us take criticism in a bad way. Most of the times, and even more during hard times like this we live now, businesses and partners are criticize every second, like on how they address their customers needs, on how they handle the crisis, on how the company performs financially, on how we react to the situations, and even how senior management is affected -because sometimes senior management is not affected at all.

During hard times we, of course, feel additional pressure on every duty we have to accomplish, on both personal and professional fields. So it is not difficult to explode and to fail on our communication methods and/or messages. Express our disagreement as a criticism could not be the best way, but it is common and widely used one. The problem is that communication is always between at least two actors, sender and receiver (source and destination), and probably our criticism will affect others directly. However, a problem is always a source of opportunities. Receivers, those who got criticized, can get the very best of every criticism and evolve it into feedback. There is no criticism without foundation, even for those how want to emotionally affect others.

As project managers we need to be the example, we need to effectively communicate information to every relevant stakeholder but, something that is not fully promoted is that we also have to handle the inputs from a 2-way process. It is not about giving and giving, it is about receiving and processing too.


Now as Product Developer

It has been a quite long time since I wrote my last post about project management. I even changed the whole look and feel of my Blog to fit (a little bit) closer the things I wrote here.

The reason is simple, I changed my job. I resigned from my previous Account Manager position at Studiocom (an interactive and digital WPP agency). It was difficult to leave that position (and such a great team) but it was a required decision from a professional perspective.

So I moved to a new position as Product Developer at Torrenegra Labs. I’ve been working at Torrenegra Labs for the last two months on a new business development that I’m sure will make me better person and integral Project Manager.

I’m currently learning about their management methods and of course and because I’m a techie guy, I’m looking at their code too :-)   – they have create their own PHP Framework on top of CakePHP, including JSON and SOAP Webservices and other cool stuff – I’m pushing hard to share most of those nice CakePHP extensions back to the community. You should take a look to their great product once it gets released – check Alexander’s Blog – The Making of LetMeGo.

I’ve been setting all the things to start a new huge initiative but also learning about their framework, coding style and development methodology (a.k.a RRapido). I’ve take the time to know the people -the most important asset on this and every company. It has been an inspiring time and now its time to rock-n-roll.

Starting next week, I will be in charge of my own top secret project that I’m pretty sure will shake the market -as it happens for most of the Torrenegra products (like Voice123 and for sure LetMeGo). It is an exciting time for me, and I hope you enjoy the future posts about the project progress.


JBoss Portal Fail!

Ok, I do not like to write about useless stuff, but this time, I got into deep troubles because one of my clients ask me to implement a portal page on top of JBoss Portal. My first thought was “Ok, if it is as good as JBoss Application Server, it should be good enough”.

So I downloaded it -the 2.7.2 bundled version. I started working on it, simple configuration and customization. I even bought a book, because, I still do believe that books are good for reference.

Oh Dear God! What a SHAME, the book JBoss Portal Server Development [Packt Publishing] is nothing more than printed version of the online Reference Guide. I read the customers reviews before get the book but to be honest, I found websites with useful tips and post. I believe I will donate the book… for recycling.

About the product, I don’ t have enough word to express my frustration.

Content Management

When you read on JBoss Portal documentation it is not a CMS, it really means it doesn’t even work to store content at all.

  • CMS performance will decrease dramatically as you load and modifies your content. BIG BIG FAIL! TIP JCR performance gets BETTER if you switch the repo to the FS instead of using databases.
  • CMS doesn’ t have Search functionality. Even when JCR has a great index analyser, JBoss Portal fails. I downloaded (svn co) the 2.7.2 version source code from the repos. “core-search” module doesn’t compile. Ant scripts are too fragmented and hard to read. You’ll have to replace and FIX all the jar dependencies to make it work. Note: I didn’t test it, but probably repo works flawless on 2.6.8 version -looking at the scripts and code it could be possible. Once you make it work, the deploy target will not work. Server will consume too memory and then CRASH!
  • Portal Pages and CMS content are not related. I built my own search portlet -fragment of code is at the end of this post. It is not easy to match Portal Pages with CMS content -I didn’ t tried it, however I’m pretty sure it is possible.

Documentation

Official docs are good enough -not great but good to be a Open Source project, however Wiki is a shame, its confusing and incomplete. There are a lot of unexplained things like how to get sources, and how to compile, troubleshooting, and stuff like that.

PortalSwap is a FAIL… it is supposed to be a community place to share portlets but there are like four simple portlets.

Community and DevTeam Support

Well, I was contacted -in some how- by the DevTeam on twitter. The truth, I “tweet” my frustration several weeks ago! and they noticed – At the beginning I was angry about the product but then I realized that IT IS FOR FREE, so let’s try to change and improve things! I wrote a nice email to the DevTeam. All I want is to get paid (from my client) and also to contribute to the project.

I’m still waiting for the answer from DevTeam, without mention that the “Twitter Support” was more like a complain than a “Branding Strategy”. So there is no community support, and DevTeam doesn’t know about Customer Care. My suggestion is, next time you contact a frustrated developer, try to convince to “help DevTeam” instead of debate!

As a recap, JBoss AS is a great product, JBoss Portal is seems to me like a “work in progress” – Maybe it is a students project!

Search Portlet Source

The original code have been edited -please let me know if it works for you.

package xxx;
 
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
 
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.PortletConfig;
import javax.portlet.PortletException;
import javax.portlet.PortletPreferences;
import javax.portlet.PortletRequestDispatcher;
import javax.portlet.PortletSecurityException;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.UnavailableException;
import javax.portlet.WindowState;
 
import org.apache.log4j.Logger;
import org.jboss.portal.cms.CMS;
import org.jboss.portal.cms.CMSException;
import org.jboss.portal.cms.Command;
import org.jboss.portal.cms.model.File;
import org.jboss.portal.core.cms.ui.CMSPortlet;
import org.jboss.portal.search.FederatedQuery;
import org.jboss.portal.search.QueryConversionException;
import org.jboss.portal.search.federation.SearchFederation;
import org.jboss.portal.search.impl.jcr.JCRQuery;
import org.jboss.portal.search.impl.jcr.JCRQueryConverter;
 
public class SearchPortlet extends CMSPortlet {
 
	private static Logger log = Logger.getLogger(SearchPortlet.class);
	private SearchFederation SearchService;
	private CMS CMSService;
 
	public void init(PortletConfig config) throws PortletException {
		super.init(config);
	}
 
	public void destroy() {
		super.destroy();
	}
 
	/*
	 * (non-Javadoc)
	 *
	 * @see javax.portlet.Portlet#init()
	 */
	@Override
	public void init() throws PortletException {
		this.SearchService = (SearchFederation) getPortletContext().getAttribute("SearchFederationService");
		this.CMSService = (CMS) getPortletContext().getAttribute("CMS");
 
		if (this.SearchService == null) {
			throw new PortletException("Search Service Not Available");
		}
 
		if (this.CMSService == null) {
			throw new PortletException("CMS Not Available");
		}
 
		super.init();
	}
 
	/*
	 * (non-Javadoc)
	 *
	 * @see javax.portlet.GenericPortlet#doView(javax.portlet.RenderRequest,
	 * javax.portlet.RenderResponse)
	 */
	@Override
	protected void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException, UnavailableException {
 
		String portalInstance = request.getWindowID().substring(0, request.getWindowID().indexOf('/', 1));
		PortletRequestDispatcher prd = null;
 
		String operation = (String) request.getParameter("op");
 
		if (operation != null &amp;&amp; operation.equals("search")) {
			String queryString = (String) request.getParameter("query");
			if (queryString != null &amp;&amp; queryString.length() &gt; 0) {
				request.setAttribute("files", this.getFiles(queryString, portalInstance));
				prd = this.getPortletContext().getRequestDispatcher("/WEB-INF/jsp/results.jsp");
			} else {
				prd = getPortletContext().getRequestDispatcher("/WEB-INF/jsp/search.jsp");
			}
			prd.include(request, response);
		} else if (operation != null &amp;&amp; operation.equals("display")) {
			super.doView(request, response);
		} else {
			prd = getPortletContext().getRequestDispatcher("/WEB-INF/jsp/search.jsp");
			prd.include(request, response);
		}
	}
 
	/*
	 * (non-Javadoc)
	 *
	 * @see javax.portlet.Portlet#processAction(javax.portlet.ActionRequest,
	 * javax.portlet.ActionResponse)
	 */
	@Override
	public void processAction(ActionRequest request, ActionResponse response) throws PortletException, PortletSecurityException, IOException {
 
		String operation = (String) request.getParameter("op");
		PortletPreferences prefs = request.getPreferences();
 
		if (operation == null || operation.equalsIgnoreCase("ask")) {
			response.setRenderParameter("op", "ask");
			//FIXME response.setWindowState(WindowState.NORMAL);
		}
		else {
			//FIXME response.setWindowState(WindowState.MAXIMIZED);
			response.setRenderParameter("op", operation);	
 
			if (operation.equals("search")) {
				String queryString = (String) request.getParameter("query");
				if (queryString != null &amp;&amp; queryString.length() &gt; 0) {
					response.setRenderParameter("query", queryString);
				}
			}
 
			if (operation.equals("display")) {
				String uri = request.getParameter("uri");
				response.setRenderParameter("uri", uri);
				super.processAction(request, response);
			}
		}
	}
 
	private List getFiles(String queryString, String portal) {
		List files = null;
 
		if (queryString != null &amp;&amp; queryString.length() &gt; 0) {
			FederatedQuery query = new FederatedQuery(queryString);
			JCRQueryConverter converter = new JCRQueryConverter();
 
			try {
				Command searchCommand = CMSService.getCommandFactory().createSearchCommand((JCRQuery) converter.convert(query));
				files = (List) CMSService.execute(searchCommand);
				//FIXME: "portal" parameter was used to filter files depending on the portal instance but it was removed for this example!
			} catch (CMSException e) {
				log.error("CMS Error", e);
			} catch (QueryConversionException e) {
				log.warn("Conversion Error", e);
			}
		}
 
		return (files == null) new ArrayList() : files;
	}
}

Include the following lines on your jboss-portlet.xml file.

<service>
	<service-name>CMS</service-name>
	<service-class>org.jboss.portal.cms.CMS</service-class>
	<service-ref>:service=CMS</service-ref>
</service>
 
<service>
	<service-name>SearchFederationService</service-name>
	<service-class>org.jboss.portal.search.federation.SearchFederation</service-class>
	<service-ref>:service=SearchFederationService</service-ref>
</service>

And do not forget to include the following lines on your portlet.xml file.

<portlet>
	<description>DESC</description>
	<portlet-name>SearchPortlet</portlet-name>
	<display-name>NAME</display-name>
	<portlet-class>xxx.SearchPortlet</portlet-class>
	<supports>
		<mime-type>text/html</mime-type>
		<portlet-mode>VIEW</portlet-mode>
	</supports>
	<resource-bundle>Resource</resource-bundle>
	<portlet-info>
		<title>SEARCH</title>
	</portlet-info>
	<portlet-preferences>
		<preference>
			<name>indexpage</name>
			<value>/default/index.html</value>
			</preference>
		<preference>
			<name>setBrowserTitle</name>
			<value>false</value>
		</preference>
	</portlet-preferences>
</portlet>
 
<filter>
	<filter-name>JBoss Portlet Filter</filter-name>
	<filter-class>org.jboss.portlet.filter.JBossPortletFilter</filter-class>
	<lifecycle>ACTION_PHASE</lifecycle>
	<lifecycle>RENDER_PHASE</lifecycle>
</filter>
 
<filter-mapping>
	<filter-name>JBoss Portlet Filter</filter-name>
	<portlet-name>SearchPortlet</portlet-name>
</filter-mapping>

Please THIS IS NOT A PERFECT SOLUTION, but it is a solution. It uses the existing CMSPortlet to display the content -and this approach causes an issue -due to WindowState changes. I hope someone find it useful and improves it. Feel free to contact me if you have any question.