Rebuilding the fence [Updated]

We’ve noticed two things about the default Alfresco login screen that cause issues for our users:

  • It’s not particularly informative–folks that have never visited our site before often don’t know what it’s about
  • It’s essentially impossible to log in to the Share interface from a mobile device such as an iPad or iPhone

So, with the help of a couple of consultants from Cignex (thanks Sanket and Rajesh!), we’ve turned the login screen from:

Default Alfresco Login Screen

into:

New Research Hub Login Screen

This post explains how to make this change in Alfresco EE 3.4.2, but it should give you an idea how to do it for other versions. Diff-ing the code below with the slingshot-login.ftl in your version might help point out the changes you need to make.

Update 7/14/2011: Updated this post to clean up some of the code and fix a few bugs, including making it look more like the share interface that appears once the user logs in. Also:

  • Added the include-navigation.html for the text that now appears in the blue bar.
  • Added a snippet of javascript so that people get a message that reads “To access [URL], please log in first.”whenever they land on the login page after following a link to a particular asset on the site (e.g., https://alfresco.company.com/page/site/test/dashboard).

slingshot-login.ftl

The first task is to modify the {alfresco home}/tomcat/webapps/share/WEB-INF/classes/alfresco/templates/org/alfresco/global/slingshot-login.ftl file to look like:

<#include "../include/alfresco-template.ftl" />
<@templateHeader>
   <@link rel="stylesheet" type="text/css" href="${url.context}/res/themes/${theme}/login.css" />
</@>

<@templateBody>

<!-- Login screen mods: Javascript to include the content of the left and right columns, so we
can update them without having to restart Alfresco-->
<script type="text/javascript">

	/***********************************************
	* Ajax Includes script- © Dynamic Drive DHTML code library (www.dynamicdrive.com)
	* This notice MUST stay intact for legal use
	* Visit Dynamic Drive at http://www.dynamicdrive.com/ for full source code
	***********************************************/

	//To include a page, invoke ajaxinclude("afile.htm") in the BODY of page
	//Included file MUST be from the same domain as the page displaying it.

	var rootdomain="http://"+window.location.hostname

	function ajaxinclude(url) {
		var page_request = false
		if (window.XMLHttpRequest) // if Mozilla, Safari etc
			page_request = new XMLHttpRequest()
		else if (window.ActiveXObject){ // if IE
			try {
				page_request = new ActiveXObject("Msxml2.XMLHTTP")
			}
			catch (e){
				try{
					page_request = new ActiveXObject("Microsoft.XMLHTTP")
				}
				catch (e){}
			}
		}
		else
			return false

		page_request.open('GET', url, false) //get page synchronously
		page_request.send(null)
		writecontent(page_request)
	}
	
	function writecontent(page_request){
		if (window.location.href.indexOf("http")==-1 || page_request.status==200)
			document.write(page_request.responseText)
	}
	
</script>

<div id="wrapper">
	<!-- Login screen mods: Banner across the top.  White-and-blue styling and logo are being taken care of in the login.css -->
	<div class="banner">
	  <a class="logo"></a>
	</div>
	
	<!-- Login screen mods: Blue bar across top -->
	<script type="text/javascript">
		//Using the javascript here to include the content we want to be able to update easily
		ajaxinclude("/about/include-navigation.html")
	</script>
	<noscript>
		<div id="navigaton">
			<p>University of California • Berkeley</p>
		</div>
	</noscript>
	
	<!--content-->
	<div class="content">

		<!-- error on non-regular login -->
		<script type="text/javascript">
		if(!(endsWith (window.location.href, "/page/") || endsWith (window.location.href, "/page/site-index") || endsWith (window.location.href, "page/type/login?error=true") )) {
			document.write( "<div class=\"direct_site_url\"><p>To access "+window.location.href+", please log in first.</p></div>");
		}
		
		function endsWith(str, suffix) {
			return str.indexOf(suffix, str.length - suffix.length) !== -1;
		}
		</script>
		<!-- error on non-regular login -->

	    <!--Login screen mods: left column content -->
	    <div class="left-col">
		   <div class="cont-container">
		   	<script type="text/javascript">
		   		//Using the javascript here to include the content we want to be able to update easily
				ajaxinclude("/about/include-left.html")
			</script>
			<noscript>
				<!--putting in something generic but sensible for folks with Javascript disabled -->
				<h1>Welcome to the UC Berkeley Research Hub</h1>
				<p>Please enable Javascript in order to see more information about the Research Hub, or visit the <a href="/about/">about</a> page.</p>
			</noscript>
		   </div>
		</div>
	    <!-- end of left column content-->

	    <!-- Login screen mods: right column content-->
		<div class="right-col">
			<div class="cont-container">
				<!-- Login screen mods: Box in upper-right, being used for login screen -->
				<h1>Sign In</h1>
   				<div id="alflogin">
				<#if PORTLET>
      				<div class="login-portlet">${msg("message.login-portal")}</div>
				<#else>
					<form id="loginform" accept-charset="UTF-8" method="post" action="${url.context}/page/dologin" onsubmit="return alfLogin();">
						<div class="cont-signin">
							<p><label id="txt-username" for="username"></label>  <input type="text" id="username" name="username" maxlength="255" style="width:130px" value="<#if lastUsername??>${lastUsername?html}</#if>" />
							</p>
							<p><label id="txt-password" for="password"></label>  <input type="password" id="password" name="password" autocomplete="off" maxlength="255" style="width:130px"/>
							</p>
							<p><input type="submit" id="btn-login" class="login-button" value="Login" style="font-weight: bold;"/></p>

							<p>Use your CalNet ID to access Research Hub <!-- <br>
							Don't have one? Apply for <a href="http://calnet.berkeley.edu/" target="_blank" class="gust-acc">Guest account</a> -->
							</p>
							<input type="hidden" id="success" name="success" value="${successUrl?html}"/>
							<input type="hidden" name="failure" value="<#assign link>${url.context}/page/type/login</#assign>${link?html}?error=true"/>

						</div>
					</form>
				</#if>
				</div>
			</div>
			<div class="cont-container">
				<!-- Login screen mods: Box in lower-right, being used for news items -->
				<script type="text/javascript">
			   		//Using the javascript here to include the content we want to be able to update easily
					ajaxinclude("/about/include-right.html")
				</script>
				<noscript>
					<h1>Research Hub News</h1>
					<!-- putting in something generic but sensible for folks with Javascript disabled -->
					<p>Please enable Javascript in order to see news about the Research Hub.</p>
				</noscript>
		   </div>
		</div>
		<!--end of right column content-->
		<div class="clear"></div>

	</div>
	 <!-- end of content-->
	  <!-- Login screen mods: footer-->
	  <div id="footer">
	     <p><a href="#">Research Hub</a><span>|</span><a href="#">University of California, Berkeley</a><span>|</span><a href="#">Terms of Service </a><span>|</span><a href="#">Privacy </a></p>
	  </div>
	   <!-- end of footer-->
</div>

   <script type="text/javascript">//<![CDATA[
   function alfLogin()
   {
      YAHOO.util.Dom.get("btn-login").setAttribute("disabled", true);
      return true;
   }

   YAHOO.util.Event.onContentReady("alflogin", function()
   {
      var Dom = YAHOO.util.Dom;

      // Prevent the Enter key from causing a double form submission
      var form = Dom.get("loginform");
      if (form)
      {
         // add the event to the form and make the scope of the handler this form.
         YAHOO.util.Event.addListener(form, "submit", this._submitInvoked, this, true);
         var fnStopEvent = function(id, keyEvent)
         {
            if (form.getAttribute("alflogin") == null)
            {
               form.setAttribute("alflogin", true);
            }
         }

         var enterListener = new YAHOO.util.KeyListener(form,
         {
            keys: YAHOO.util.KeyListener.KEY.ENTER
         }, fnStopEvent, "keydown");
         enterListener.enable();

         // set I18N labels
         Dom.get("txt-username").innerHTML = Alfresco.util.message("label.username") + ":";
         Dom.get("txt-password").innerHTML = Alfresco.util.message("label.password") + ":";
         Dom.get("btn-login").value = Alfresco.util.message("button.login");
 
	//set focus
       Dom.get("success").value += window.location.hash;
       Dom.get(<#if lastUsername??>"password"<#else>"username"</#if>).focus();
     }
      
      // generate and display main login panel
//      var panel = new YAHOO.widget.Overlay(YAHOO.util.Dom.get("alflogin"), 
//      {
//         modal: false,
//         draggable: false, // NOTE: Don't change to "true"
//         fixedcenter: true,
//         close: false,
//         visible: true,
//         iframe: false
//      });
//      panel.render(document.body);
      
//      Dom.get("success").value += window.location.hash;
//      Dom.get(<#if lastUsername??>"password"<#else>"username"</#if>).focus();
   });

<#if url.args["error"]??>
   Alfresco.util.PopupManager.displayPrompt(
   {
      title: Alfresco.util.message("message.loginfailure"),
      text: Alfresco.util.message("message.loginautherror"),
      buttons: [
      {
         text: Alfresco.util.message("button.ok"),
         handler: function error_onOk()
         {
            this.destroy();
            YAHOO.util.Dom.get("username").focus();
            YAHOO.util.Dom.get("username").select();
         },
         isDefault: true
      }]
   });

</#if>
   //]]></script>
</@>
</body>
</html>

There are a couple of things to point out in this code:

  • It uses the “Ajax Includes” script from http://www.dynamicdrive.com/ to include the content in the left and right columns. This allows us to simply update the snippets of HTML at /about/include-left.html and /about/include-right.html whenever we want to update the messages on the homescreen. If we didn’t do that, we’d have to modify slingshot-login.ftl and restart Alfresco each time we needed to make a change. That’s obviously not an option in production environments!
  • It’s a fairly basic layout–all of the arrangement and styling is taken care of in the {alfresco home}/tomcat/webapps/share/themes/default/login.css file, below.

login.css

As I mentioned above, most of the layout and styling takes place in the {alfresco home}/tomcat/webapps/share/themes/default/login.css file. The idea was to make the 3 boxes on the login page (left, upper-right, and lower-right) look as much as possible like the dashlets that users will see once they log in. To do that, the login.css file looks like:

/*  Login screen mods: Styling the body to look as much as possible like the
body of the pages once you've logged into share*/
body {
  background: #d9dde0;
  padding: 0px;
  margin: 0px;
  font: normal normal normal 13px/1.231 arial, helvetica, clean, sans-serif;
  font-family: Arial, sans-serif;
  font-size: 81%;
}

/*  Login screen mods: Making sure the content goes the full width of the
screen */
#doc3 {
  margin: 0;
}

#wrapper {
  margin: 0 0px 10px 0px;
  min-width: 835px;
  width:100%;
  background: #fff;
}

/*  Login screen mods: Banner across the top */
.banner{
  background-color: #ffffff;
}

.banner .logo{
  background: url(./images/logo.gif) no-repeat;
  width: 178px;
  height: 48px;
  display: block;
}

/*  Login screen mods: styling the text in the blue bar in the 
header to look the same as in share. */
#navigaton{
  min-height: 2em;
  background: #6ca5ce;
  padding: 6px 10px;
}

#navigaton p{
  color:#C7DBEB;
  font-family: Helvetica, Arial, sans-serif;
  font-size: 146.5%;
  font-weight: normal;
}

.direct_site_url {
  font-weight:bold;
  font-size:125%;
  color:red;
  margin-bottom: 20px;
  width: 100%;
  /*text-align:center;*/
}

.clear{
  padding: 0px;
  margin: 0px;
  line-height: 0px;
  font-size: 0px;
  height: 0px;
  clear: both;
}

/*  Login screen mods: Left and right columns. Right column
is fixed to 250 pixels, left column grows to fill the rest
 of the screen */
.left-col{
  float: left;
  margin-right: 270px;
}

.right-col{
  width:250px;
  float: left;
  padding: 0 0 0 20px;
  margin-left: -270px;
}

/*  Login screen mods: Styling the boxes of content to look
like Share dashlets */
.content{
  padding: 20px;
}

.cont-container{
  border: 1px solid #bababa;
  margin-bottom: 20px;
}

.cont-container-sub{
  padding: 10px;
}

.cont-container-sub p{
  padding: 0px;
  margin: 0px 0 5px 0;
  color: #6a7580;
}

/*  Login screen mods: modifying the "H1" style to look like
 the header for a Share dashlet */
.cont-container h1{
  background: #b9c8d4 url(./images/heading-bg.gif) repeat-x;
  margin: 0px;
  padding: 5px 10px;
  color: #2d2e2f;
  font-size: 100%;
}

/*  Login screen mods: Styling the sign-in box */
.cont-signin{
  padding: 10px;
  background: #fffbdd;
}

.cont-signin p{
  padding: 0px;
  margin: 0px 0 5px 0;
  color: #515354;
  clear: both;
}

.cont-signin p label{
  width: 75px;
  display: inline-block;
  font-weight: bold;
}

.cont-signin p input.textbx{
  background: #fff;
  border: 1px solid #cad9e2;
}

.cont-signin p input.login-button{
/*  background: #f0f0f0;
  border: 1px solid #e4eadf;
  cursor: pointer;
  color: #515354;
  font-weight: bold;*/
  float: right;
  margin-right: 5px;
  margin-bottom: 10px;

}

/*  Login screen mods: Styling the lower-right box */
.cont-hub-news{
  padding: 10px;
  background: #f9fcfd;
}

.cont-hub-news p{
  padding: 0px;
  margin: 0px 0 5px 0;
  color: #515d6b;
}

a.hub-news{
  color: #6ca5ce;
  text-decoration: none;
}

/*  Login screen mods: Styling the footer */
#footer{
  border-top: 1px solid #dfdfdf;
  background: #f2f2f2;
}

#footer p{
  margin: 0px;
  padding: 15px 0;
  width: 50%;
  margin: 0 auto;
  border-left: 1px solid #dfdfdf;
  border-right: 1px solid #dfdfdf;
  background: #fff;
  text-align: center;
  color: #191919;
  font-size: 12px;
}

#footer p a{
  color: #191919;
  text-decoration: none;
}

#footer p span{
  padding: 0 5px;
}

One thing to point out in this code:

Include files

The final piece of the puzzle are the include-navigation, include-left.html, and include-right.html. These are simply files in the web server’s directory on our system–in our case, that’s /var/www/html (note that that’s not in the Alfresco home at all). While they contain HTML, they’re not complete HTML documents. Here’s an example of how they should be structured in order to look right on the homepage:

include-navigation.html

<div id="navigaton">
	<p><span style="color:white">Header</span> Words</p>
</div>

include-left.html

<h1>Left box header</h1>
<div class="cont-container-sub">
	  <p>Left box contents</p>
</div>

include-right.html

<h1>Lower-right box header</h1>
<div class="cont-hub-news">
	<p>Lower-right box contents</p>
</div>

Maintenance

A note regarding maintenance: As both slingshot-login.ftl and login.css are within the deployed share.war file, you should be sure to set things up so these customizations are not lost when you re-deploy the share.war archive. In our case, we use a custom .amp file to do this, and a bunch of other customizations. That’s a topic for future post, though.

Advertisements
This entry was posted in Tweaks. Bookmark the permalink.

3 Responses to Rebuilding the fence [Updated]

  1. Great blog..was testing our company Share using an ipad and noticed the difficult login. After login i spotted jeff potts tweet in the twitter feed dashlet about this blog. Looking forward to test this in a dev environment.

  2. Paul Holmes-Higgin says:

    Just FYI, the Team and current HEAD Community builds fix the iPad issues. In fact, it works rather nicely 🙂

    Great blog!

    Keep your eyes open for the stuff that’s going into the Community code at the moment – we’re making it much easier to extend Share. There will be pure configuration ways of adding Doc Library actions, right through to extending or overriding Share/Surf components.

    • icrew says:

      Paul:

      Glad to hear that–thanks for letting me know! It’s been kind of annoying to have that not work.

      To be honest, our primary motivation was to be able to put more information on the home page (we’re setting up our instance as a self-serve site at our university), the iPhone/iPad compatibility was a really nice added bonus.

      We’re also very excited about all the coming changes in Share–it looks like a LOT of that will be really useful to us.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s