Index: msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages.properties =================================================================== --- msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages.properties (revision 122815) +++ msgcntr/messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages.properties (working copy) @@ -755,6 +755,9 @@ hiddenGroups_selectGroup=--Select Group-- hiddenGroups_remove=Remove +# Add a text msg for A copy of this message will be sent to each recipient's email address. +cc_all=A copy of this message will be sent to each recipient's email address. + ranks=Ranks ranks_desc=Use ranks to identify people based on their post count or to denote certain individuals, such as the teaching staff within a class. add_rank=Add Rank Index: msgcntr/messageforums-app/pom.xml =================================================================== --- msgcntr/messageforums-app/pom.xml (revision 122815) +++ msgcntr/messageforums-app/pom.xml (working copy) @@ -88,6 +88,27 @@ ${sakai.scheduler.version} provided + + net.sf.json-lib + json-lib + 2.2.3 + jdk15 + jar + + + net.sf.json-lib + json-lib + 2.2.3 + jdk15-sources + jar + + + net.sf.json-lib + json-lib + 2.2.3 + jdk15-javadoc + jar + javax.servlet.jsp jsp-api Index: msgcntr/messageforums-app/src/java/org/sakaiproject/tool/messageforums/PrivateMessagesTool.java =================================================================== --- msgcntr/messageforums-app/src/java/org/sakaiproject/tool/messageforums/PrivateMessagesTool.java (revision 121841) +++ msgcntr/messageforums-app/src/java/org/sakaiproject/tool/messageforums/PrivateMessagesTool.java (working copy) @@ -35,8 +35,6 @@ import java.util.Set; import java.util.TimeZone; import java.util.Map.Entry; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import javax.faces.application.FacesMessage; import javax.faces.context.ExternalContext; @@ -45,12 +43,21 @@ import javax.faces.model.SelectItem; import javax.servlet.http.HttpServletRequest; +import net.sf.json.JSON; +import net.sf.json.JSONArray; +import net.sf.json.JSONObject; +import net.sf.json.JSONSerializer; +import net.sf.json.JsonConfig; +import org.sakaiproject.site.api.Group; +import org.sakaiproject.authz.api.Role; +import java.util.StringTokenizer; + +import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.commons.validator.EmailValidator; import org.sakaiproject.api.app.messageforums.Area; import org.sakaiproject.api.app.messageforums.Attachment; -import org.sakaiproject.api.app.messageforums.DBMembershipItem; import org.sakaiproject.api.app.messageforums.DefaultPermissionsManager; import org.sakaiproject.api.app.messageforums.DiscussionForumService; import org.sakaiproject.api.app.messageforums.HiddenGroup; @@ -80,7 +87,6 @@ import org.sakaiproject.entity.api.Reference; import org.sakaiproject.event.cover.EventTrackingService; import org.sakaiproject.exception.IdUnusedException; -import org.sakaiproject.site.api.Group; import org.sakaiproject.site.api.Site; import org.sakaiproject.site.cover.SiteService; import org.sakaiproject.time.cover.TimeService; @@ -89,7 +95,6 @@ import org.sakaiproject.tool.cover.SessionManager; import org.sakaiproject.tool.cover.ToolManager; import org.sakaiproject.tool.messageforums.ui.DecoratedAttachment; -import org.sakaiproject.tool.messageforums.ui.PermissionBean; import org.sakaiproject.tool.messageforums.ui.PrivateForumDecoratedBean; import org.sakaiproject.tool.messageforums.ui.PrivateMessageDecoratedBean; import org.sakaiproject.tool.messageforums.ui.PrivateTopicDecoratedBean; @@ -212,6 +217,7 @@ public static final String SET_AS_YES="yes"; public static final String SET_AS_NO="no"; + public static final String AGGREGATE_DELIMITER = "&"; public static final String THREADED_VIEW = "threaded"; @@ -243,6 +249,8 @@ private boolean validEmail=true ; //Compose Screen-webpage + private String aggregatedComposeToItemIds; + private String aggregatedComposeBccItemIds; private List selectedComposeToList = new ArrayList(); private List selectedComposeBccList = new ArrayList(); private String composeSendAsPvtMsg=SET_AS_YES; // currently set as Default as change by user is allowed @@ -935,7 +943,57 @@ { return selectedComposeToList; } - + + + /** + * new method to handle the new UI submission as we're now using a custom widget, not a select list, and we need to aggregate + * id's to parse into a List + * + * @param aggregatedComposeToNames + */ + public void setAggregatedComposeToItemIds(String aggregatedComposeToItemIds) { + this.aggregatedComposeToItemIds = aggregatedComposeToItemIds; + this.selectedComposeToList = parseAggregatedComposeToItemIds(this.aggregatedComposeToItemIds); + } + + public void setAggregatedComposeBccItemIds(String aggregatedComposeBccItemIds) { + this.aggregatedComposeBccItemIds = aggregatedComposeBccItemIds; + this.selectedComposeBccList = parseAggregatedComposeToItemIds(this.aggregatedComposeBccItemIds); + } + + private List parseAggregatedComposeToItemIds(String aggregateItems) { + List itemIdList = null; + Set itemIdSet = null; + if (StringUtils.isEmpty(aggregateItems)) { + // make an empty list so regular error handling will work with new hidden form field data + // aggregate_compose_to_item_ids + itemIdList = new ArrayList(0); + if (LOG.isDebugEnabled()) { + LOG.debug("aggregateItems is null or empty, check you post data param aggregate_compose_to_item_ids"); + } + } else if (aggregateItems.contains(AGGREGATE_DELIMITER)) { + StringTokenizer st = new StringTokenizer(aggregateItems, AGGREGATE_DELIMITER, false); + itemIdSet = new HashSet(st.countTokens()); + while (st.hasMoreTokens()) { + itemIdSet.add(st.nextToken()); + } + itemIdList = new ArrayList(itemIdSet.size()); + itemIdList.addAll(itemIdSet); + } else { + itemIdList = new ArrayList(1); + itemIdList.add(aggregateItems); + } + return itemIdList; + } + + public String getAggregatedComposeToItemIds() { + return aggregatedComposeToItemIds; + } + + public String getAggregatedComposeBccItemIds() { + return aggregatedComposeBccItemIds; + } + public List getSelectedComposeBccList() { return selectedComposeBccList; @@ -958,6 +1016,16 @@ { return "/site/" + ToolManager.getCurrentPlacement().getContext(); } + + + // to seed up the compose page load, now initializing on tool landing page pvtMshHpView.jsp + public void initializeCourseMemberMap() { + if (this.courseMemberMap == null) { + courseMemberMap = membershipManager.getFilteredCourseMembers(true, getHiddenGroupIds(area.getHiddenGroups())); + } + } + + public List getTotalComposeToList() { @@ -989,6 +1057,154 @@ return selectItemList; } + + + public String getTotalComposeToListJSON() { + if (this.courseMemberMap == null) { + this.courseMemberMap = membershipManager.getFilteredCourseMembers(true, getHiddenGroupIds(area.getHiddenGroups())); + } + getTotalComposeToList(); // we have to load this data also for + // pvtMsgReply pages - standin for call from + // jsp + List members = membershipManager.convertMemberMapToList(courseMemberMap); + + List jsonList = transformItemList(members); + JsonConfig config = new JsonConfig(); + JSON json = JSONSerializer.toJSON(jsonList); + if (LOG.isDebugEnabled()) + LOG.debug("converted totalComposeToList to json : " + json.toString(4, 0)); + return json.toString(4, 0); + } + + private List transformItemList(List members) { + Map> allParticipantsMap = new HashMap>(1); + allParticipantsMap.put("allParticipants", new ArrayList(1)); + + Map> rolesMap = new HashMap>(1); + rolesMap.put("roles", new ArrayList(1)); + + Map> groupsMap = new HashMap>(1); + groupsMap.put("groups", new ArrayList(1)); + + Map> usersMap = new HashMap>(1); + usersMap.put("users", new ArrayList()); + + for (Iterator iterator = members.iterator(); iterator.hasNext();) { + MembershipItem item = (MembershipItem) iterator.next(); + if (MembershipItem.TYPE_ALL_PARTICIPANTS.equals(item.getType())) { + parseAllParticipants(item, allParticipantsMap); + } else if (MembershipItem.TYPE_ROLE.equals(item.getType())) { + parseRoles(item, rolesMap); + } else if (MembershipItem.TYPE_GROUP.equals(item.getType())) { + parseGroups(item, groupsMap); + } else if (MembershipItem.TYPE_USER.equals(item.getType())) { + continue; + } else { + LOG.error("Could not determine type of MembershipItem" + item); + } + } + // now that roles and groups are parsed, walk users, adding them + // to users map and their ids to the groups and/or roles the belong to + for (Iterator iterator = members.iterator(); iterator.hasNext();) { + MembershipItem item = (MembershipItem) iterator.next(); + LOG.info("parseUsers.... itemtype = "+ item.getType()); + LOG.info("parseUsers.... MembershipItem.TYPE_USER= "+ MembershipItem.TYPE_USER); + if (MembershipItem.TYPE_USER.equals(item.getType())) { + parseUsers(item, groupsMap, rolesMap, usersMap); + } else { + LOG.error("parseUsers...Could not determine type of MembershipItem" + item); + } + } + List allItemsList = new ArrayList(3); + allItemsList.add(allParticipantsMap); + allItemsList.add(rolesMap); + + // we only need the userIds to setup the individual user data + // so remove it before delivering to page + List groupsList = groupsMap.get("groups"); + for (JSONObject groupJSON : groupsList) { + groupJSON.remove("userIds"); + } + allItemsList.add(groupsMap); + allItemsList.add(usersMap); + return allItemsList; + } + + private void parseRoles(MembershipItem item, Map> rolesMap) { + String roleId = item.getRole().getId(); + List rolesList = rolesMap.get("roles"); + if (rolesList == null) { + rolesList = new ArrayList(); + } + Role role = item.getRole(); + List userIds = new ArrayList(); + JSONObject rolesJSON = new JSONObject(); + rolesJSON.element("membershipItemId", item.getId()).element("roleId", role.getId()).element("description", + role.getDescription()).element("userIds", userIds); + rolesList.add(rolesJSON); + } + + private void parseGroups(MembershipItem item, Map> groupsMap) { + Group group = item.getGroup(); + List groupsList = groupsMap.get("groups"); + if (groupsList == null) { + groupsList = new ArrayList(); + } + Set groupMembers = (Set) group.getMembers(); + List userIds = new ArrayList(groupMembers.size()); + for (Member member : groupMembers) { + userIds.add(member.getUserId()); + } + JSONObject groupJSON = new JSONObject() + .element("membershipItemId", item.getId()) + .element("groupId",group.getId()) + .element("title", group.getTitle()) + .element("userIds", userIds); + groupsList.add(groupJSON); + } + + private void parseUsers(MembershipItem item, Map> groupsMap, + Map> rolesMap, Map> usersMap) { + String itemRoleId = item.getRole().getId(); + Group itemGroup = item.getGroup(); + List usersList = usersMap.get("users"); + if (usersList == null) { + usersList = new ArrayList(); + } + + JSONObject jsonMembershipItem = new JSONObject(); + jsonMembershipItem.element("membershipItemId", item.getId()) + .element("roleId",item.getRole().getId()) + .element("userDisplayName", item.getUser().getDisplayName()) + .element("eid",item.getUser().getEid()); + usersList.add(jsonMembershipItem); + + JSONArray memberGroupsArray = new JSONArray(); + List groupsList = groupsMap.get("groups"); + for (JSONObject jsonGroup : groupsList) { + List userIds = (List) jsonGroup.get("userIds"); + if (userIds.contains(item.getUser().getId())) { + JSONObject memberGroupJSON = new JSONObject(); + memberGroupJSON.element("groupId", jsonGroup.get("groupId")); + memberGroupJSON.element("title", jsonGroup.get("title")); + memberGroupsArray.add(memberGroupJSON); + } + } + jsonMembershipItem.element("groups", memberGroupsArray); + } + + private void parseAllParticipants(MembershipItem item, Map> allParticipantsMap) { + List allParticipantsList = allParticipantsMap.get("allParticipants"); + if (allParticipantsList == null) { + allParticipantsList = new ArrayList(); + } + + JSONObject jsonMembershipItem = new JSONObject(); + jsonMembershipItem.element("name", item.getName()).element("membershipItemId", item.getId()); + allParticipantsList.add(jsonMembershipItem); + } + + private List getHiddenGroupIds(Set hiddenGroups){ List returnList = new ArrayList(); @@ -1057,6 +1273,18 @@ return userPreferencesManager.getTimeZone(); } + + public String getUserEmail() { + String userEmail = null; + User currentUser = UserDirectoryService.getCurrentUser(); + String defaultEmail = "postmaster@" + ServerConfigurationService.getServerName(); + userEmail = currentUser.getEmail(); + if (userEmail == null || "".equals(userEmail.trim())) { + userEmail = ServerConfigurationService.getString("msgcntr.notification.from.address", defaultEmail); + } + return userEmail; + } + //Reply time public Date getTime() { @@ -1927,6 +2155,7 @@ this.getAllAttachments().clear(); //reset label this.setSelectedLabel("pvt_priority_normal"); + this.setAggregatedComposeToItemIds(""); } public String processPvtMsgPreview(){ Index: msgcntr/messageforums-app/src/webapp/css/messages.css =================================================================== --- msgcntr/messageforums-app/src/webapp/css/messages.css (revision 0) +++ msgcntr/messageforums-app/src/webapp/css/messages.css (revision 0) @@ -0,0 +1,346 @@ +/* +CSS for the UC Berkeley's implementation of the Sakai Messages Tool + +Copyright 2009 University of California, Berkeley + +Licensed under the Educational Community License (ECL), Version 2.0 or the New +BSD license. You may not use this file except in compliance with one these +Licenses. +*/ + +/* + * Tab Selected Bkgrnd Color: #777F92 + * Tab Unselected Bkgrnd Color: #8CA9C5 + * Tab Unselected Border Color: #666 + * Box Bkgrnd Color: #EBECED + * Box Border Color: #D0D3D7 + */ + +.sakai-ppkr { + display: none; + background-color: #FFF; + width: 540px; + padding: 6px; +} + +.sakai-ppkr form { + margin: 0; +} + +.sakai-ppkr .instructions { + font-style: italic; + padding-left: 0.2em; + margin-top: 0; +} + +/* Filter Section */ + +.sakai-ppkr-filter { + margin-bottom: 0.4em; +} + +.sakai-ppkr .header-title { + font-style: normal; + display: block; + font-size: 1.1em; + color: #000; +} + +.sakai-ppkr-search-field { + margin: 0.1em 0 0.6em 0.4em; + width: 430px; +} + +.sakai-ppkr-filter-fields { + border: 1px solid #D0D3D7; + padding: 0.4em 0.6em; +} + +.sakai-ppkr-filter-fields label { + font-size: 1.1em !important; + margin-left: 0 !important; +} + +/* lists */ + +.sakai-ppkr-source { + float: left; + top: 0; + display: inline; +} + +.sakai-ppkr-collection { + margin-left: 16px; +} + +.sakai-ppkr-collection, .sakai-ppkr-source { + width: 266px; +} + +.sakai-ppkr-source, .sakai-ppkr-collection { + display: block; + float: left; + clear: right; + position: relative; +} + +.sakai-ppkr-source select { + font-size: 0.8em; +} + +.sakai-ppkr-header, .sakai-ppkr-source-picker, .sakai-ppkr-collection-picker { + border: 1px solid #D0D3D7; + background-color: #EBECED; + margin-bottom: 2px; +} + +.sakai-ppkr-header { + padding: 3px 6px; +} + +.sakai-ppkr-header h3 { + margin: 0; + font-size: 1.3em; +} + +.sakai-ppkr-submit { + display:block; + clear: both; + text-align: right; + padding-top: 0.4em; +} + +.sakai-ppkr-submit input[type="text"] { + margin-left: 6px; + width: 124px; +} +.sakai-ppkr-source-footer, .sakai-ppkr-collection-footer { + border-top: 1px solid #D0D3D7; + padding: 5px 12px; +} + +.sakai-ppkr-source-list-header, .sakai-ppkr-collection-list-header { + border-bottom: 1px solid #D0D3D7; + padding: 4px 6px; + font-size: 1.1em; +} + +.sakai-ppkr-source-list-header .sakai-ppkr-num-filtered { + float: right; +} + +/* Scroller */ + +.sakai-ppkr .scroller { + overflow: auto; + overflow-x: hidden; + overflow-y: auto; + background-color: white; + height: 248px; + margin: 0; + padding: 0; +} + +.sakai-ppkr-source-list div, .sakai-ppkr-collection-list div { + cursor: pointer; + padding: 6px; + border-bottom: 1px solid #999; + background-image: url(../images/checkbox-off.gif); + background-repeat: no-repeat; + background-position: 6px 6px; +} + +.sakai-ppkr-source-list div { + padding-left: 24px; +} + +.sakai-ppkr-collection-list div { + padding-right: 24px; +} + +.scroller div.even { + background-color: #F8F9FA; +} + +.sakai-ppkr-source-list div.selected { + background-color: #FFD; + background-image: url(../images/checkbox-on.gif); +} + +.sakai-ppkr-source-list div.key-highlight, #collection div.key-highlight { + background-color: #FFD; + color: #000; + border: 2px solid #333; + padding: 4px 4px 0 5px; +} + +.sakai-ppkr-collection-list div.key-highlight { + padding: 4px 4px 0 19px; +} + +.sakai-ppkr-collection-list div { + padding-right: 24px; + background: white url(../images/remove-dim.png) 98% 6px no-repeat; +} + +.sakai-ppkr-collection-list div:hover { + background: #FFD url(../images/remove.png) 98% 6px no-repeat; +} + +.sakai-ppkr-btn-add-all, .sakai-ppkr-btn-remove-all { + cursor: pointer; + text-decoration: underline !important; +} + +.sakai-ppkr-btn-add-all { + margin: 0 3px; +} + +.sakai-ppkr-btn-remove-all { + float: right; + margin-right: 14px; +} + +.sakai-ppkr-item-indent { + padding-left: 18px; +} + +.sakai-ppkr-to-container-scroller, .sakai-ppkr-bcc-container-scroller, .messages-to-field, .sakai-ppkr-to-choice, .sakai-ppkr-bcc-choice { + width: 537px; +} + +.sakai-ppkr-to-container-scroller, .sakai-ppkr-bcc-container-scroller, .messages-to-field { + margin-top: 2px; + border: 1px solid #CCC; + min-height: 34px; + overflow: auto; + padding-bottom: 3px; +} + +.messages-to-field { + border-color: #D4DCE7; + max-height: 116px; + padding: 3px; +} + +html>body .messages-to-field { + max-height: 124px; + height: auto; +} + +.sakai-ppkr-to-container p, .sakai-ppkr-bcc-container p { + color: #666; + margin: 0.4em 0.6em 0.2em; +} + +.sakai-ppkr-to-choice, .sakai-ppkr-bcc-choice { + float:left; + margin: 0.4em 0.6em; +} + +.sakai-ppkr-to-choice a { + white-space: nowrap; +} + +.display-sakai-ppkr { + display: block; + float: right !important; + color: #333 !important; +} + +.sakai-ppkr-by-role input[type="checkbox"] { + float: left; + margin: 1px 0 0; +} + +.sakai-ppkr-by-role label { + display: block; + margin-left: 0.4em; +} + +.jsfFormTable td { + border-bottom: 1px solid #CCC; +} + +.sakai-ppkr-token { + font-size: 0.9em; + display: block; + white-space: nowrap; + float: left; + background-color: #D8E2FF; + border-width: 1px; + border-style: solid; + border-color: #D8E2FF #BDC5DF #BDC5DF #D8E2FF; + margin: 2px 0 0 2px; + padding: 1px 1px 1px 4px; + -moz-border-radius: 0.3em; + -webkit-border-radius: 0.3em; +} + +.token-all { + background-color: #ECD8FF; + border-color: #ECD8FF #CEBDDF #CEBDDF #ECD8FF; + font-style: italic; +} + +.token-role-participates { + background-color: #FFD8D8; +} + +.token-hidden { + display: none; +} + +.token-inactive { + color:gray +} + +.ppkr-remove { + cursor: pointer; + font-family: Tahoma, Arial, Helvetica, sans-serif; + color: rgb(102, 102, 102); + font-size: 90%; + font-weight: bold; + font-style: normal; + background-color: transparent; + padding: 0 4px 2px; + -moz-border-radius: 50%; + -webkit-border-radius: 50%; +} + +.ppkr-remove:hover { + color: #9F2A2A; + background-color: white; +} + +.jsfFormTable td.sakai-ppkr-from { + vertical-align: middle; +} + +.sakai-ppkr-collection-counter, .sakai-ppkr-source-counter, .sakai-ppkr-source-total { + font-weight: bolder; +} + +/* Overrides for jQuery UI Dialog styles */ + +.ui-dialog { + margin-top: 2em; +} + +.ui-dialog .ui-dialog-title { + font-size: 1.4em !important; +} + +.ui-dialog .ui-dialog-titlebar { + padding: 0.5em 0.3em 0.5em 1em !important; +} + +/* overrides to tool_base.css */ + +.jsfFormTable td .shorttext label { + width: auto !important; +} + +.shorttext label, .filepicker label { + width: auto !important; +} Index: msgcntr/messageforums-app/src/webapp/js/Scroller.js =================================================================== --- msgcntr/messageforums-app/src/webapp/js/Scroller.js (revision 0) +++ msgcntr/messageforums-app/src/webapp/js/Scroller.js (revision 0) @@ -0,0 +1,132 @@ +/* +Copyright 2008-2009 University of Cambridge +Copyright 2008-2009 University of Toronto +Copyright 2007-2009 University of California, Berkeley + +Licensed under the Educational Community License (ECL), Version 2.0 or the New +BSD license. You may not use this file except in compliance with one these +Licenses. + +You may obtain a copy of the ECL 2.0 License and BSD License at +https://source.fluidproject.org/svn/LICENSE.txt +*/ + +/*global jQuery*/ +/*global fluid_1_1*/ + +fluid_1_1 = fluid_1_1 || {}; + +(function ($, fluid) { + + var refreshView = function (that) { + var maxHeight = that.options.maxHeight; + var isOverMaxHeight = (that.scrollingElm.children().eq(0).height() > maxHeight); + var setHeight = (isOverMaxHeight) ? maxHeight : ""; + that.scrollingElm.height(setHeight); + }; + + var scrollBottom = function (that) { + that.scrollingElm[0].scrollTop = that.scrollingElm[0].scrollHeight; + }; + + var scrollTo = function (that, element) { + if (!element || element.length < 1) { + return; + } + + var padTop = 0; + var padBottom = 0; + + var elmPosTop = element[0].offsetTop; + var elmHeight = element.height(); + var containerScrollTop = that.scrollingElm[0].scrollTop; + var containerHeight = that.scrollingElm.height(); + + if (that.options.padScroll) { + // if the combined height of the elements is greater than the + // viewport then then scrollTo element would not be in view + var prevElmHeight = element.prev().height(); + padTop = (prevElmHeight + elmHeight <= containerHeight) ? prevElmHeight : 0; + var nextElmHeight = element.next().height(); + padBottom = (nextElmHeight + elmHeight <= containerHeight) ? nextElmHeight : 0; + } + + // if the top of the row is ABOVE the view port move the row into position + if ((elmPosTop - padTop) < containerScrollTop) { + that.scrollingElm[0].scrollTop = elmPosTop - padTop; + } + + // if the bottom of the row is BELOW the viewport then scroll it into position + if (((elmPosTop + elmHeight) + padBottom) > (containerScrollTop + containerHeight)) { + elmHeight = (elmHeight < containerHeight) ? elmHeight : containerHeight; + that.scrollingElm[0].scrollTop = (elmPosTop - containerHeight + elmHeight + padBottom); + } + }; + + var setupScroller = function (that) { + that.scrollingElm = that.container.parents(that.options.selectors.wrapper); + + // We should render our own sensible default if the scrolling element is missing. + if (!that.scrollingElm.length) { + fluid.fail({ + name: "Missing Scroller", + message: "The scroller wrapper element was not found." + }); + } + + // set the height of the scroller unless this is IE6 + if (!$.browser.msie || $.browser.version > 6) { + that.scrollingElm.css("max-height", that.options.maxHeight); + } + }; + + /** + * Creates a new Scroller component. + * + * @param {Object} container the element containing the collection of things to make scrollable + * @param {Object} options configuration options for the component + */ + fluid.scroller = function (container, options) { + var that = fluid.initView("fluid.scroller", container, options); + setupScroller(that); + + /** + * Scrolls the specified element into view + * + * @param {jQuery} element the element to scroll into view + */ + that.scrollTo = function (element) { + scrollTo(that, element); + }; + + /** + * Scrolls to the bottom of the view. + */ + that.scrollBottom = function () { + scrollBottom(that); + }; + + /** + * Refreshes the scroller's appearance based on any changes to the document. + */ + that.refreshView = function () { + if ($.browser.msie && $.browser.version < 7) { + refreshView(that); + } + }; + + that.refreshView(); + return that; + }; + + fluid.defaults("fluid.scroller", { + selectors: { + wrapper: ".flc-scroller" + }, + + maxHeight: 180, + + padScroll: true + }); + +})(jQuery, fluid_1_1); Index: msgcntr/messageforums-app/src/webapp/js/jquery.bgiframe.js =================================================================== --- msgcntr/messageforums-app/src/webapp/js/jquery.bgiframe.js (revision 0) +++ msgcntr/messageforums-app/src/webapp/js/jquery.bgiframe.js (revision 0) @@ -0,0 +1,104 @@ +/* Copyright (c) 2006 Brandon Aaron (http://brandonaaron.net) + * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) + * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. + * + * $LastChangedDate: 2009-05-05 08:14:12 -0700 (Tue, 05 May 2009) $ + * $Rev: 7137 $ + * + * Version 2.1 + */ + +(function($){ + +/** + * The bgiframe is chainable and applies the iframe hack to get + * around zIndex issues in IE6. It will only apply itself in IE + * and adds a class to the iframe called 'bgiframe'. The iframe + * is appeneded as the first child of the matched element(s) + * with a tabIndex and zIndex of -1. + * + * By default the plugin will take borders, sized with pixel units, + * into account. If a different unit is used for the border's width, + * then you will need to use the top and left settings as explained below. + * + * NOTICE: This plugin has been reported to cause perfromance problems + * when used on elements that change properties (like width, height and + * opacity) a lot in IE6. Most of these problems have been caused by + * the expressions used to calculate the elements width, height and + * borders. Some have reported it is due to the opacity filter. All + * these settings can be changed if needed as explained below. + * + * @example $('div').bgiframe(); + * @before

Paragraph

+ * @result