WebCenter Spaces – steps to change the template and skin

April 22, 2014

By: Andreja Sambolec – Application Consultant

Create a new template and skin

If you want to change the default template and skin for your Spaces application, the first step is to create a new template and skin based on the OOTB template which looks similar to the design you want, and apply them to the Spaces application.

In our example we want to have the menu on the top of the page so we created a new template based on the ‘WebCenter Spaces Top Navigation’ template.

 

You can create a new template and skin from the ‘Resources’ tab (Administration-> Spaces-> select Space and select ‘Edit space’).

You can apply your new template and skin using the General tab:

skins1

The default menu looks like this:

skins2

We want to have something like this:

skins3

And this is how the drop-down menu should look:

skins4

 Change the template

We need to change the template because the logo icon has to be on the left side of the menu.

The best way to find what has to be changed is:

  • Create a new portal or ADF application in jDeveloper
  • Create a new .jspx page
  • Copy the templates source code and paste to .jspx file

Now you can easy see the structure of the template and decide what to change.

  1. Using browser ‘Developer Tools’ you can find which component holds the menu:
    skins5
  2. Using jDeveloper you can see the <af:decorativeBox  id =‘db1’ > component  contains  the Global navigation menu and the logo. The component with id set to ‘pt_pgl4’ holds the Top navigation menu.
  3. We decided to replace decorativeBox component with panelGroupLayout component.
    1. Replace af:decorativeBox with af:panelGroupLayut
    2. Delete facet:center
    3. Set layout property for panelGroupLayout to ‘horizontal’
    4. Create two panelGroupLayout components inside of ‘db1’ to hold the logo icon and the menu
    5. Set layout to ‘vertical’ for the second panelGroupLayout
    6. Cut and paste <cust:panelCustomizable id=”pcust1″> inside of the first panelGroupLayout
    7. Cut and paste  <af:panelGroupLayout id=”gtbppgl3″> inside of the second panelGroupLayout
    8. Cut and paste  <af:panelGroupLayout id=”pt_pgl4″> inside of the second panelGroupLayout
    9. Delete the component with id ‘gtbppbl1’
      skins6

This is the result:
skins7

  1. Place the Search input box to the right side:
    1. Using Chrome Developer tool you can see that we need to change the component with id= ‘pcust2’  because this component holds the menu:
      skins8
    2. You will see that the task-flow is used for the search component and it’s placed as the first component inside of ‘pcust2 ‘ so we just need to move it to the last place:
      skins9

New order:
skins10

 

  1. Change Spaces logo using Spaces Settings tab
    skins12
  2. Search for ‘gtbrspmxi1’ component and change the width and the height property for af:image component to 74px and also change the source property to “#{WCPrepareImageURL[spaceContext.space[WCAppContext.currentScope.name].metadata.logo][‘LOGO75’]}”skins13

 

  1. Set the property ‘rendered’ to false for ‘gtbrspmxgl2’ (to hide the link next to logo icon)

The result:
skins14

Change the skin

Next we want to change the menus background color.

The background color for the global menu should be in white.

Steps to change:

  1. Using browser Developer Tool, check which style is used to apply this color

When we deselect the background image for .WCSiteTemplateBackground, .x1r4,

we don’t see the  background color  anymore  on the top of the page .
skins15

  1. So, open the source of your custom skin and add this:

.WCSiteTemplateBackground{

background-image:none;

}
skins16

When you are checking out the styles for some components, you will not see the name of the component but some code, for example, .x106 (the skin is compressed).

You can apply changes using those compressed names or if you know, for example, which component is used you can try with af|commandLink. You could also open the page fragment in jdeveloper and check which component and style class is used and use it in your skin to add changes.

 

Change the navigation model

We also want to add some new pages, so we are going to change the navigation model.

  1. Create a new one based on default one (you can check which model is currently used using the General tab when you are editing your spaces app)
    skins17
  2. Edit your newly created navigation model (add links for your pages – you can create pages later and update Path for links)
    skins18
    skins19
    skins20
  1. Select custom navigation model as the default one for your spaces app
    skins21
    skins22We currently don’t see any sub-menus, so next we are going to change the menu component in our template.

 

Change the Top Menu

 

  1. Using the browser Developer Tool, you can see the menu is placed in the component with id  ‘T:spcNavPanel’.

Search for this component in your template file and you can see a region (task-flow) is used to display the menu.

We are going to replace this region with this code (loop through the navigation model and its children):

  <div class=menu>

    <c:set var=”navNodes”    value=”${navigationContext.defaultNavigationModel.listModel[‘startNode=/, includeStartNode=false’]}” scope=”session”/>

    <ul id=topnav>

      <c:forEach var=”menu” varStatus=”vs” items=”${navNodes}”>

        <li><a href=“/webcenter${menu.goLinkPrettyUrl}”> ${menu.title}</a>

           <c:if test=”${not empty menu.children}”>

             <div class=“submenu”><table width=100%><tr><td>

                <ol class=“blocks-four-up”>

                <c:forEach var=”child” items=”#{menu.children}”>

                 <li>

<p><a href=“/webcenter${child.goLinkPrettyUrl}”>

${child.title}

</a></p>

                 <c:if test=”${not empty child.children}”>

                 <ol>

                 <forEach var=”child1″ items=”#{child.children}”>

                 <li><a href=/webcenter${child1.goLinkPrettyUrl}”>

${child1.title}

</a></li>

                 </c:forEach>

                 </ol>

                 </c:if>

                 </li>

                 </c:forEach>

                 </ol>

              </td></tr></table></div>

            </c:if>

         </li>

      </c:forEach>

    </ul>

  </div>

 

You can find the meaning of EL expressions related to Navigation in this   document:

http://docs.oracle.com/cd/E23943_01/webcenter.1111/e10148/jpsdg_app_els.htm#CHDCBJGJ

 

 

  1. Add this to your skin:

 

.menu ul#topnav{

margin-top: 18px;

padding: 0;

float: left;

width: 880px;

position: absolute;

liststyle: none;

position: relative;

fontsize: 1.2em;

background:url(http://1-ps.googleusercontent.com/h/www.htmldrive.net/edit_media/2010/201007/20100728/Horizontal%20Subnav/images/xtopnav_s.gif.pagespeed.ic.v1mnjAEAt5.webp) repeat-x;

}

.menu ul#topnav li .submenu{  

padding: 15px 0;

position: absolute;

left: 0; top:35px;

display: none;

width: 760px;

background: #282828;;

color: #fff;

z-index:1;

-moz-border-radius-bottomright: 5px;

-khtml-border-radius-bottomright: 5px;

-webkit-border-bottom-right-radius: 5px;

-moz-border-radius-bottomleft: 5px;

-khtml-border-radius-bottomleft: 5px;

-webkit-border-bottom-left-radius: 5px;

}

.menu ul#topnav li{

float: left;

list-style: none;

}

.menu ul#topnav li:hover.submenu{

display:block;

}

.menu ul#topnav li a {

padding: 10px 15px;

display: block;

text-decoration: none;

color: #f0f0f0;

}

.menu.blocks-four-up{

list-style: none;

position:relative:;

}

.menu.blocks-four-up>li {

float: left;

width: 25%;

}

.menu#topnav.blocks-four-up>li ol li{

float: none;

}

.menu ul#topnav li .submenu a {

padding: 10px 0;

display: inline;

color: #f0f0f0;

text-decoration: none;

}

 

  1. To display four (4) submenus in one row, we have to use nth-child selector. If we include the selector in our skin file it’s not going to work, so we can just add it to the template:

<af:resource type=”css”>

.blocks-four-up>li:nth-child(4n+1)

{clear:left;}

</af:resource>

The result:
skins23

 

Next, we want to display the ‘More Info’ menu at the bottom.

How do we know which menu we are going to use as a bottom menu?  We can add some attribute (menuType) to the menu using the navigation model:

skins24

Change the template :

<c:forEach var=”child” items=”#{menu.children}”>

<li>

<c:if test=”${child.attributes.menuType != ‘bottom’}”>

    <p>

    <a href=/webcenter${child.goLinkPrettyUrl}>${child.title}</a>

    </p>

<c:if test=”${not empty child.children}”>

<ol>

    <c:forEach var=”child1″ items=”#{child.children}”>

    <li>

    <a href=/webcenter${child1.goLinkPrettyUrl}>

    ${child1.title}</a>

    </li>

    </c:forEach>

</ol>

</c:if>

</c:if>

<c:if test=”${child.attributes.menuType == ‘bottom’}”>

    <c:set var=”bottomMenu” value=”${child}” scope=”session”/>

</c:if>

</li>

</c:forEach>

</ol>

<p class=“menu_bottom_title”>

 ${bottomMenu.title}

</p>

<ul class=“menu_botom_links”>

<c:forEach var=”child1″ items=”#{bottomMenu.children}”>

    <li>

    <a href=“/webcenter${child1.goLinkPrettyUrl}”>

    ${child1.title}</a>

    </li>

</c:forEach>

</ul>

 

Add/change this to the skin:

.menu ul#topnav li  .submenu.menu_bottom_title{

clear: both;

margin: 0 8px .5em 12px;

padding: .75em 27px 0;

border-top: 1px solid #5e5f62;

font-weight: normal;

font-size: 1.230769em;

line-height: 1.125;

}

.menu.blocks-four-up>li ol{

-webkit-padding-start: 0;

}

.menu.blocks-four-up>li {

float: left;

width: 25%;

padding-bottom: 5px;

}

skins25

Using the same directions, we are going to add some images on the right side of the menu:

  • Create a new link and add attribute ‘menuType’ with value set to ‘image’.
  • Add attribute ‘imageUrl’ with the value which points to your image:
    skins26

Change your template (apply changes related to image in red color):

<td width=85%><ol class=blocks-four-up>

<c:forEach var=”child” items=”#{menu.children}”>

    <li>

    <c:if test=”${ child.attributes.menuType != ‘bottom’ &amp;&amp; child.attributes.menuType != ‘image’}”>

        <p><a href=/webcenter${child.goLinkPrettyUrl}> ${child.title}</a></p>

    <c:if test=”${not empty child.children}”>

    <ol>

    <c:forEach var=”child1″ items=”#{child.children}”>

        <li>

        <a href=/webcenter${child1.goLinkPrettyUrl}> ${child1.title}</a>

        </li>

    </c:forEach>

    </ol>

    </c:if>

    </c:if>

    <c:if test=”${child.attributes.menuType == ‘bottom’}”>

        <c:set var=”bottomMenu” value=”${child}” scope=”session”/>

    </c:if>

    <c:if test=”${child.attributes.menuType == ‘image’}”>

        <c:set var=”linkimage” value=”${child.attributes.imageUrl}” scope=”session”/>

    </c:if>

    </li>

    </c:forEach>

    </ol>

    <p class=menu_bottom_title> ${bottomMenu.title} </p>

    <ul class=menu_botom_links>

    <c:forEach var=”child1″ items=”#{bottomMenu.children}”>

        <li>

        <a href=/webcenter${child1.goLinkPrettyUrl}> ${child1.title}</a>

        </li>

    </c:forEach>

    </ul>

</td>

<td width=“15%” valign=“top”><img src=“${linkimage}”/></td> 

 

 

Finally, we want the submenu to cover the entire page.

To do this:

  1. Create a new div container as child of ‘pt_pgl5’ component:

<af:panelGroupLayout id=”pt_pgl5″ layout=”vertical” styleClass=”WCSiteTemplateBackground”>

            <div id=submenu/>

<af:panelGroupLayout id=”pt_pbl1″ layout=”vertical” inlineStyle=”width:#{attrs.templateFixedWidth}px;margin:0px auto;padding-top:8px;”>

 

We are going to clone the submenu in a mouse over action with this container.

  1. Add javascript to the template:

<af:resource type=“javascript>

function showSubmenu(e){

var childrens = e.parentNode.children;

var  len = childrens.length;

document.getElementById(submenu).innerHTML =;

for (i = 0; i &lt; len; i++) {

if(childrens[i].className==submenu){

newNode=childrens[i].cloneNode(true);

document.getElementById(submenu).appendChild(newNode);

}

}}

</af:resource>

 

  1. Add onMouseOver event to the link

<a href=/webcenter${menu.goLinkPrettyUrl}     onmouseover=showSubmenu(this)>

 

  1. Add this to css:

.menu ul#topnav li:hover.submenu{

display:none;

}

div#submenu.blocks-four-up>li ol li{

float: none;

}

div#submenu.blocks-four-up>li {

float: left;

width: 25%;

padding-bottom: 5px;

}

div#submenu.blocks-four-up{

list-style: none;

position:relative:;

}

div#submenu.blocks-four-up>li ol{

-webkit-padding-start: 0;

}

div#submenu.menu_bottom_title{

clear: both;

margin: 0 8px .5em 12px;

padding: .75em 27px 0;

border-top: 1px solid #5e5f62;

font-weight: normal;

font-size: 1.230769em;

line-height: 1.125;

}

div#submenu div{

padding: 15px 0;

position: relative;

left: 0;

width:1000px;

display: block;

background: #282828;

color: #fff;

margin: 0 auto;

 

}

 

div#submenu  {

width:100%;

top: 90px;

position: absolute;

z-index: 3;

background: #282828;

}

 

div#submenu li{

margin-bottom: .3076923em;

}

 

div#submenu p a{

font-weight:bold

}

 

div#submenu p {

margin-bottom: 3px;

}

div#submenu a{

padding: 10px 0;

display: inline;

color: #f0f0f0;

text-decoration: none;

}

 

div#submenu div>ol {

min-height: 109px;

list-style: none;

}

div#submenu div>ol ol {

padding:0px;

list-style: none;}

div#submenu div>table tr td ol ol {

padding:0px;

list-style: none;

}

skins28

You also need to add an event to clear the container when exiting from the submenu.

Those are the basic steps to change the template and skin of your Portal application.

Using a browser Developer Tool enables you to discover which component has to be changed or what to change in your skin.

 

Any questions? Feel free to contact us.

 

 


WebCenter CK Editor Customization – WebCenter Portal

February 18, 2014

By: Andreja Sambolec – Application Consultant

For creating new blogs or wikis, the WebCenter CK Editor (text editor) is used. Using CK Editor we have options to edit text, add tables, embed images and videos…

In this demonstration we are going to embed video.

You can edit the HTML and add the <iframe> component, but it will be deleted after saving changes or switching between tabs.

To avoid this, we customized the task-flow which contains this editor.

The page fragment we changed is
oracle.webcenter.doclib.view.jsf.taskflow.richTextEditor.editHTML.jsff.

We figured that the problem is caused by the converter added to <rte:editor> component so we deleted it (No other problems arise because of those changes):

<rte:editor id=”rte value=”#{pageFlowScope.rte.HTMLTextArea}”
converter=”#{pageFlowScope.rte.HTMLConverter}” />

Next we wanted to have an icon for embedding videos to enhance and simplify the user experience.

embedvideo

When a user clicks on the icon a list of videos will be displayed inside of a dialog box:

ckeditor

When a user clicks on the OK button, the video should be added as part of the iframe in the editor.

The Tag Library used for this component is rte-taglib.jar, we need change this library.

Changed files are:

  • ckeditor.js (add new plugin)

j.video={init:function(m){m.addCommand(‘video’, new a.dialogCommand(‘video’));m.ui.addButton(‘Video’,{label:m.lang.video,icon:this.path+’images/video_qualifier.png’,command:’video’});a.dialog.add(‘video’, this.path + ‘dialogs/video.js’);}};j.add(‘video’, j.video);

  • config.js (declare a new plugin):

config.extraPlugins = ‘wclinkexist,wclinknew,wcimage,video,snippet,tools’; config.toolbar_WC = [ …. [‘WCLinkExist’,’WCLinkNew’,’WCImage’,’Video’,’-‘], …. ];

  • Lang/en.js (new labels for the video plugin)

…..

video : ‘Embed Video’,

videodialog :

{

title                   : ‘Select video’,

},

……

To display the video dialog, we created a new plugin and added it to the plugins folder

(META-INF\adf\oracle\webcenter\doclib\rte\jsLibs\ckeditor\plugins\video).

‘video’ folder contains two subfolders: ‘images’ folder (contains icon image to open the dialog  box) and ‘dialog’ folder (contains dialog.js file with logic to display videos and create <iframe>  in the editor when closing the dialog box)

Now to add some content inside of the dialog box and pass some value to the editor, you can check the source of other plugins as help (I used the Colordialog plugin).

This is the code snippet to add <iframe> in the editor when the user clicks on the OK button:

var dialog = this,

data = {},

iframe = c.createElement( ‘iframe’ ),

div = c.createElement(‘div’);

obj.commitContent( data );

div.setAttribute(‘class’, ‘video’);

iframe.setAttribute( ‘src’, ‘urlValue’);

iframe.setAttribute( ‘width’, ‘620’ );

iframe.setAttribute( ‘width’, ‘349’ );

iframe.setAttribute( ‘frameborder’, ‘0’);

iframe.appendTo(div);

a.insertElement(div);

This was done in WebCenter Portal 11.1.1.8 but is compatible with 11.1.1.7 and 11.1.1.6., as well.

If you have any questions, or would like to talk to someone from TEAM, contact us!


WebCenter Portal – Access to WC Spaces Services over REST running on Oracle VM from your Mobile Application

December 18, 2013

By: Andreja Sambolec – Application Consultant

Developing ADF Mobile application – accessing WC services running on VM

If you are using the Oracle SOA Suite/BPM Suite VirtualBox Appliance (Pre-Built Developer VM for Oracle VM VirtualBox) and want to start developing an ADF Mobile application accessing WC Spaces REST services running on this VM, you have to make sure:

  • Your mobile device and local machine have access to the same wireless network
  • NAT is selected as the available network mode on your VM

portal1

  • To check Port Forwarding

potral2

Now you can access WebCenter Spaces from your mobile device or from your local machine using IP address of your local machine: http://192.168.1.33:8888/rest/api/resourceIndex

In your ADF Mobile application, create a new connection:

portal3

If you want use programmatic approach for Spaces REST Service invocation, use the following:

RestServiceAdapter restServiceAdapter = Model.createRestServiceAdapter();
restServiceAdapter.clearRequestProperties();
restServiceAdapter.setConnectionName(“WCRest”);
restServiceAdapter.setRequestType(RestServiceAdapter.REQUEST_TYPE_GET);
String theUsername = “weblogic”;
String thePassword = “welcome1”;
String userPassword = theUsername + “:” + thePassword;
String encoding = new sun.misc.BASE64Encoder().encode(userPassword.getBytes());
restServiceAdapter.addRequestProperty(“Authorization”, “Basic ” + encoding);
restServiceAdapter.addRequestProperty(“Accept”, “application/json; charset=UTF-8”);
restServiceAdapter.addRequestProperty(“Content-Type”, “application/json; charset=UTF-8”);

restServiceAdapter.setRetryLimit(0);

restServiceAdapter.setRequestURI(“/rest/api/resourceIndex”);

try {

System.out.println(restServiceAdapter.send(“”));

} catch (Exception e) {

e.printStackTrace();

}

Using ADB tool and command adb logcat, you can see the part of the response as a result of the command System.out.println(restServiceAdapter.send(“”)):

{“version”:”11.1.1.6.0″,”resourceType”:”urn:oracle:webcenter:
resourceindex”,”links”:[{“href”:”http://192.168.1.33:8888/rest/api/discussions/forums?utoken=FJnkGqA-W6YrfhjgagLvMj_AJapY_w**&#8221;,”template”:”http://192.168.1.33:8888/rest/api/discussions/forums?startIndex={startIndex}&itemsPerPage={itemsPerPage}&utoken=FJnkGqA-W6YrfhjgagLvMj_AJapY_w**”,”resourceType”:”urn:oracle:webcenter:discussions:forums”,”capabilities”:”urn:oracle:webcenter:read”},{“href”:”http://192.168.1.33:8888/rest/api/preferences/general?utoken=FJnkGqA-W6YrfhjgagLvMj_AJapY_w**&#8221;,”resourceType”:”urn:oracle:webcenter:preferences:general”,”capabilities”:”urn:oracle:webcenter:read”},{“href”:”http://192.168.1.33:8888/rest/api/spaces/siteresources?utoken=FJnkGqA-W6YrfhjgagLvMj_AJapY_w**&#8221;,”template”:”http://192.168.1.33:8888/rest/api/spaces/siteresources?projection={projection}&q={q}&utoken=FJnkGqA-W6YrfhjgagLvMj_AJapY_w**”,”resourceType”:”urn:oracle:webcenter:spaces:siteresources”,”capabilities”:”urn:oracle:webcenter:read”},{“href”:”http://192.168

After the above verification of access to WebCenter Spaces REST Services you can continue with development.

Running into trouble? Want to have a chat with members from TEAM? Contact us here.


WebCenter Portal 11g Best Practices – Policy Store Migration

August 13, 2013

Background:

WebCenter Portal 11g uses a policy store to maintain your application’s security policies and grants those policies to users and groups of the application.  By default, the policy store is stored within an XML file located at: [domain directory]/config/fmwconfig/system-jazn-data.xml. WebCenter supports migrating the policy store to an LDAP or database repository. Examples of when the policy store is updated are:

  • – Spaces are added or deleted
  • – Pages are added or deleted
  • – User or Group access is changed

These changes are part of the regular maintenance of a Portal or Spaces application and therefore the policy store needs to be scalable for a production implementation.

To maintain Oracle support, a production-implemented WebCenter Portal 11g policy store must reside in an LDAP or database repository. The migration can be done through the Enterprise Manager application or through the WebLogic Scripting (WLST) command line tool. Details of those steps are covered within Oracle’s documentation, which can be found here for the 11.1.1.6 WebCenter Portal implementation:

http://docs.oracle.com/cd/E23943_01/webcenter.1111/e12405/wcadm_security_credstore.htm#BABGJEHC

To migrate the policy store to a database the Open Platform Security Services (OPSS) schema needs to be installed through the Repository Creation Utility (RCU)  and the datasource assigned within the WebLogic console.  This is the same process that’s used to install the WebCenter schemas during the initial implementation.

Best Practice:

Perform the policy store migration as soon as possible after the initial environment has been verified and before beginning any customizations to the WebCenter Portal application. This will reduce the chance of errors during the migration.

Before the policy store migration make sure to back-up the following files as there is not an automated script to rollback the migration if there are issues:

  • – [domain]/config/fmwconfig/jps-config.xml
  • – [domain]/config/fmwconfig/system-jazn-data.xml

Rolling back these two files will change the domain back to a file-based policy store.

Issue with client that did not migrate the policy store:

I have recently been assisting a client with their production WebCenter Spaces 11g environment that could no longer create any new spaces. Prior to this error, the client created a new space and noticed that their admin user could no longer access the administrative screens and lost other application and space permissions.

The client’s production spaces application was using a file-based policy store and through analysis and backup of the system-jazn-data.xml file we noticed that any increase to this file would trigger the policy store to not load properly. With this client’s environment the system-jazn-data.xml file was approximately 2.5MB in size when this issue occurred but we were able to recreate the issue with Oracle’s assistance in other environments with varying file sizes.  Rather than an exact file size issue it seemed to be an issue on how the application loads this data from the file.

Working with Oracle Support, our next step was to migrate the policy store to the database in our Test environment. We added the OPSS schema through RCU and added the OPSS datasource within our WebLogic console. Using Enterprise Manager we migrated the policy store to the database. We then restarted our domain and logged into our Spaces application. Immediately after login we were redirected to an “Unauthorized” page and the redirect was happening infinitely.

After further analysis it seemed there was a separate issue where the system-jazn-data.xml had duplicate grants within the file. Any duplicate grants actually caused the policy store migration to fail, but no message was sent to the UI of this error. With Oracle Support’s assistance, we manually removed the duplicate grants from the system-jazn-data.xml file and re-performed the database policy store migration.  It was successful.

It is still unknown how these duplicate policy store grants were added to the policy store file, but that is why the best practice is to migrate from the file-based policy store to a database or LDAP repository before any customizations (i.e. Spaces or Pages) are added to the application to prevent any issues during the migration.

After the policy store migration we were able to successfully create spaces. We successfully tested creation of 100 spaces in the WebCenter Spaces application. This satisfied the client, and the issue was resolved. We then performed the same policy store migration to a database within the production system and regression tested. We saw the same successful results and were able to create new spaces again.

If you have questions about WebCenter Portal, trust TEAM to answer them. Contact us today.


TEAM’s Customer, Mortenson Construction, Makes the Cover of Oracle Profit Magazine!

August 9, 2013

TEAM was excited to share when Mortenson Construction was awarded Oracle’s 2012 Industry Excellence Award for Engineering and Construction at Oracle OpenWorld last year. TEAM led the architecture and development of the WebCenter Spaces and WebCenter Content 11g implementations of their ProjectConnect application, and the project continues to receive great recognition. Now Mortenson has made the cover of Oracle Profit Magazine!

Check out the article.

mortmagazineDesign. Connect. Build.

Mortenson Construction helps large project teams collaborate and share information—on and off the construction site.

by David Baum, August 2013

 

 

 

 

If you want your own award winning project, contact TEAM today!